Description
In split-output mode (output.splitOutput), every generated part renders the full repository tree in its Directory Structure section, even though each part only contains a subset of the files. The tree and the Files section are inconsistent within every part except the last-assembled grouping.
Verified on repomix 1.18.0 (published npm CLI) and on main (72807df).
Why it matters: split output exists so each part can be fed to an LLM independently. When part N's tree lists files that are not in part N, the model sees "listed in the directory structure but missing from Files" — it will either report files as missing, or (worse) hallucinate contents for paths it was told exist. This silently degrades the core use case of the feature.
Reproduction
Fixture — 9 small files in a/, b/, c/ plus the config itself:
mkdir -p a b c
for d in a b c; do for i in 1 2 3; do printf 'content-%s-%s padding padding padding\n' "$d" "$i" > $d/file$i.txt; done; done
repomix.config.json:
{
"output": {
"filePath": "out.txt",
"style": "plain",
"splitOutput": 3000,
"fileSummary": true,
"directoryStructure": true,
"files": true
}
}
Run repomix --quiet → produces out.1.txt and out.2.txt.
Actual (v1.18.0):
out.2.txt Directory Structure:
a/
file1.txt
file2.txt
file3.txt
b/
file1.txt
file2.txt
file3.txt
c/
file1.txt
file2.txt
file3.txt
repomix.config.json
…while its Files section contains only c/file1.txt … c/file3.txt and repomix.config.json. Same inversion on out.1.txt (tree lists c/, files don't include it).
Expected: each part's Directory Structure covers exactly the files included in that part (part 1: a/, b/; part 2: c/, repomix.config.json).
Root cause
src/core/packager.ts:288 builds filePathsByRoot from all repository files (with the default filePathStyle: 'target-relative', usesRootLabels() is always true, so this mapping is always constructed — single root included).
src/core/output/outputSplit.ts (renderGroups) passes that whole-repo filePathsByRoot unchanged into generateOutput for every chunk.
src/core/output/outputGenerate.ts:428 prefers filePathsByRoot over the chunk-scoped filePathsForTree when generating the tree:
let treeString: string;
if (filePathsByRoot) {
treeString = generateTreeStringWithRoots(filePathsByRoot, directoryPathsForTree);
} else {
treeString = generateTreeString(filePathsForTree, directoryPathsForTree);
}
So the per-chunk scoping computed in buildOutputGeneratorContext is bypassed whenever filePathsByRoot is present — which is always, under the default config.
Fix
Scope filePathsByRoot to the chunk's own files in renderGroups before calling generateOutput:
--- a/src/core/output/outputSplit.ts
+++ b/src/core/output/outputSplit.ts
@@ -152,6 +152,18 @@ const renderGroups = async (
const chunkAllFilePaths = groupsToRender.flatMap((g) => g.allFilePaths);
const chunkConfig = makeChunkConfig(baseConfig, partIndex);
+ // Scope filePathsByRoot (used for the multi-root tree) to this chunk's files.
+ // The caller passes the whole-repo mapping, and generateOutput prefers it over
+ // filePathsForTree when building the Directory Structure — unscoped, every
+ // split part would render the full repository tree while only containing a
+ // subset of the files.
+ const chunkFilePathSet = new Set(chunkAllFilePaths);
+ const chunkFilePathsByRoot = filePathsByRoot
+ ? filePathsByRoot
+ .map(({ rootLabel, files }) => ({ rootLabel, files: files.filter((file) => chunkFilePathSet.has(file)) }))
+ .filter(({ files }) => files.length > 0)
+ : undefined;
+
return await generateOutput(
rootDirs,
chunkConfig,
@@ -159,7 +171,7 @@ const renderGroups = async (
chunkAllFilePaths,
partIndex === 1 ? gitDiffResult : undefined,
partIndex === 1 ? gitLogResult : undefined,
- filePathsByRoot,
+ chunkFilePathsByRoot,
emptyDirPaths,
);
};
Verification
- Reproduced on stock v1.18.0 CLI (npx) — wrong tree in both parts.
- Applied the patch, rebuilt (
npm run build), re-ran the same fixture:
out.1.txt tree = a/, b/ only; Files = a/*, b/* (0 references to c/).
out.2.txt tree = c/, repomix.config.json; Files = c/*, repomix.config.json.
npx vitest run tests/core tests/config → 101 files, 1114 tests, all passing (includes tests/core/packager/splitOutput.test.ts; note the existing test mocks produceOutput, so it does not cover per-chunk tree contents — the above fixture does).
tsc --noEmit clean.
- Single-file groups still hit the "cannot split further" error path correctly (subdivide → per-file groups unchanged).
- Multi-root labeled trees keep their labels: each root's file list is filtered, not re-derived, so
rootLabel handling in generateTreeStringWithRoots is untouched.
One behavior note: filePathsByRoot mapping is per-root; roots whose files all land in other parts are dropped entirely (.filter(({ files }) => files.length > 0)), so no empty labeled sections appear.
Found while doing a docs-vs-behavior drift check on context-packing tools (I run a small $5 "FreshContext Pack" service that does exactly this kind of repo-vs-claims verification: https://deploy-foorge-team.vercel.app). Happy to open a PR if the fix direction looks right.
Description
In split-output mode (
output.splitOutput), every generated part renders the full repository tree in its Directory Structure section, even though each part only contains a subset of the files. The tree and the Files section are inconsistent within every part except the last-assembled grouping.Verified on repomix 1.18.0 (published npm CLI) and on
main(72807df).Why it matters: split output exists so each part can be fed to an LLM independently. When part N's tree lists files that are not in part N, the model sees "listed in the directory structure but missing from Files" — it will either report files as missing, or (worse) hallucinate contents for paths it was told exist. This silently degrades the core use case of the feature.
Reproduction
Fixture — 9 small files in
a/,b/,c/plus the config itself:repomix.config.json:{ "output": { "filePath": "out.txt", "style": "plain", "splitOutput": 3000, "fileSummary": true, "directoryStructure": true, "files": true } }Run
repomix --quiet→ producesout.1.txtandout.2.txt.Actual (v1.18.0):
out.2.txtDirectory Structure:…while its Files section contains only
c/file1.txt…c/file3.txtandrepomix.config.json. Same inversion onout.1.txt(tree listsc/, files don't include it).Expected: each part's Directory Structure covers exactly the files included in that part (part 1:
a/,b/; part 2:c/,repomix.config.json).Root cause
src/core/packager.ts:288buildsfilePathsByRootfrom all repository files (with the defaultfilePathStyle: 'target-relative',usesRootLabels()is always true, so this mapping is always constructed — single root included).src/core/output/outputSplit.ts(renderGroups) passes that whole-repofilePathsByRootunchanged intogenerateOutputfor every chunk.src/core/output/outputGenerate.ts:428prefersfilePathsByRootover the chunk-scopedfilePathsForTreewhen generating the tree:So the per-chunk scoping computed in
buildOutputGeneratorContextis bypassed wheneverfilePathsByRootis present — which is always, under the default config.Fix
Scope
filePathsByRootto the chunk's own files inrenderGroupsbefore callinggenerateOutput:Verification
npm run build), re-ran the same fixture:out.1.txttree =a/,b/only; Files =a/*,b/*(0 references toc/).out.2.txttree =c/,repomix.config.json; Files =c/*,repomix.config.json.npx vitest run tests/core tests/config→ 101 files, 1114 tests, all passing (includestests/core/packager/splitOutput.test.ts; note the existing test mocksproduceOutput, so it does not cover per-chunk tree contents — the above fixture does).tsc --noEmitclean.rootLabelhandling ingenerateTreeStringWithRootsis untouched.One behavior note:
filePathsByRootmapping is per-root; roots whose files all land in other parts are dropped entirely (.filter(({ files }) => files.length > 0)), so no empty labeled sections appear.Found while doing a docs-vs-behavior drift check on context-packing tools (I run a small $5 "FreshContext Pack" service that does exactly this kind of repo-vs-claims verification: https://deploy-foorge-team.vercel.app). Happy to open a PR if the fix direction looks right.