⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
renderRepoMap in packages/loopover-engine/src/miner/repo-map.ts is documented as rendering "a
bounded plain-text outline" that "Stops once maxOutputChars would be exceeded and appends a
truncation marker, so a caller/prompt-builder can tell the map is partial rather than complete."
Its internal pushLine correctly refuses any line that would push length past maxOutputChars and
sets truncated = true. But the marker itself is appended unconditionally and unbudgeted:
if (truncated) lines.push("… (repo map truncated to fit the output budget)");
return lines.join("\n");
lines.push bypasses pushLine entirely, so the returned string is up to 47 characters (the marker)
plus one newline over maxOutputChars. In the degenerate case where the very first line already
exceeds the budget, the function returns only the marker — a string longer than the budget with no
content in it at all.
The consumer is the prompt packet: this module exists "without paying the token cost of dumping full
file contents into a prompt" (module header), so maxOutputChars is a token-budget guarantee a
prompt builder relies on. A guarantee that is silently violated by a fixed overshoot is worse than one
that is honest about its bound.
Requirements
renderRepoMap's return value MUST satisfy result.length <= maxOutputChars for every input and
every maxOutputChars >= 0.
- The truncation marker MUST still be present whenever content was dropped. To make room, the render
loop MUST reserve the marker's own length (plus its joining newline) from the budget up front, so the
marker always fits.
- When
maxOutputChars is smaller than the marker itself, renderRepoMap MUST return the empty
string rather than an over-budget marker.
- When nothing is truncated, the output MUST be byte-for-byte identical to today's — no reserved
headroom may shrink a complete render. (Reserve the marker only against the truncation decision, and
emit the full budget's worth of content when everything fits.)
- The marker string, the per-entry line formats (
"<path>: (skipped: <reason>)",
"<path>: (no symbols)", "<path>:", " <kind> <name> (line N): <signature>"), the entry order,
and the default maxOutputChars = 20_000 MUST be unchanged.
⚠️ Required pattern: keep the existing pushLine closure and the outer: labelled loop
(packages/loopover-engine/src/miner/repo-map.ts:344-383); the fix is a budget adjustment inside
that structure. What does NOT satisfy this issue: dropping the marker to stay in budget; truncating
the marker itself; rewriting the renderer as a string-builder or a reduce; or changing the default
maxOutputChars.
Deliverables
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example
reserving headroom but not handling the budget-smaller-than-the-marker case, which still returns an
over-budget string — does not resolve this issue.
Test Coverage Requirements
packages/loopover-engine/src/**/*.ts is inside coverage.include in vitest.config.ts and
carries its own engine Codecov flag; the 99%+ branch-counted codecov/patch gate applies here in
full. Both arms of every changed conditional need a test: truncated vs not truncated, budget larger
than the marker vs smaller than it, and the skipped / no-symbols / with-symbols entry arms each
hitting the budget boundary. The length <= maxOutputChars assertion under truncation is the required
named regression test.
Expected Outcome
renderRepoMap(entries, n).length <= n holds for every input, so a prompt builder's token budget is
actually respected instead of being overshot by the marker that claims the budget was respected.
Links & Resources
packages/loopover-engine/src/miner/repo-map.ts:341-383
packages/loopover-engine/test/ (the repo-map suite)
Context
renderRepoMapinpackages/loopover-engine/src/miner/repo-map.tsis documented as rendering "abounded plain-text outline" that "Stops once
maxOutputCharswould be exceeded and appends atruncation marker, so a caller/prompt-builder can tell the map is partial rather than complete."
Its internal
pushLinecorrectly refuses any line that would pushlengthpastmaxOutputCharsandsets
truncated = true. But the marker itself is appended unconditionally and unbudgeted:lines.pushbypassespushLineentirely, so the returned string is up to 47 characters (the marker)plus one newline over
maxOutputChars. In the degenerate case where the very first line alreadyexceeds the budget, the function returns only the marker — a string longer than the budget with no
content in it at all.
The consumer is the prompt packet: this module exists "without paying the token cost of dumping full
file contents into a prompt" (module header), so
maxOutputCharsis a token-budget guarantee aprompt builder relies on. A guarantee that is silently violated by a fixed overshoot is worse than one
that is honest about its bound.
Requirements
renderRepoMap's return value MUST satisfyresult.length <= maxOutputCharsfor every input andevery
maxOutputChars >= 0.loop MUST reserve the marker's own length (plus its joining newline) from the budget up front, so the
marker always fits.
maxOutputCharsis smaller than the marker itself,renderRepoMapMUST return the emptystring rather than an over-budget marker.
headroom may shrink a complete render. (Reserve the marker only against the truncation decision, and
emit the full budget's worth of content when everything fits.)
"<path>: (skipped: <reason>)","<path>: (no symbols)","<path>:"," <kind> <name> (line N): <signature>"), the entry order,and the default
maxOutputChars = 20_000MUST be unchanged.Deliverables
packages/loopover-engine/test/asserting that for an entry setlarge enough to truncate,
renderRepoMap(entries, 120).length <= 120and the result endswith
"… (repo map truncated to fit the output budget)".renderRepoMap(entries, 10) === ""when the budget is smaller than themarker.
string as before the change (no marker, no dropped lines), at both the default budget and an
explicit generous budget.
0,60,500) assertingrenderRepoMap(entries, n).length <= nfor each.renderRepoMapassertions in the repo-map suite still pass unmodified.All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example
reserving headroom but not handling the budget-smaller-than-the-marker case, which still returns an
over-budget string — does not resolve this issue.
Test Coverage Requirements
packages/loopover-engine/src/**/*.tsis insidecoverage.includeinvitest.config.tsandcarries its own
engineCodecov flag; the 99%+ branch-countedcodecov/patchgate applies here infull. Both arms of every changed conditional need a test: truncated vs not truncated, budget larger
than the marker vs smaller than it, and the skipped / no-symbols / with-symbols entry arms each
hitting the budget boundary. The
length <= maxOutputCharsassertion under truncation is the requirednamed regression test.
Expected Outcome
renderRepoMap(entries, n).length <= nholds for every input, so a prompt builder's token budget isactually respected instead of being overshot by the marker that claims the budget was respected.
Links & Resources
packages/loopover-engine/src/miner/repo-map.ts:341-383packages/loopover-engine/test/(the repo-map suite)