Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3fac666
feat(paging): the budgeted-bundle candidate page (for / pack-task) + …
usehoplite[bot] Sep 21, 2026
d8c1d14
feat(paging twins): MCP for/explore serve the candidate page under bu…
usehoplite[bot] Sep 23, 2026
1d0c44b
feat(paging): the #294 ruling's suggestions — above_cliff=, next_tier…
usehoplite[bot] Sep 28, 2026
3f209c4
fix(paging): the #362 review round — global r=, verb-correct next=, r…
usehoplite[bot] Sep 28, 2026
df6970e
fix(paging): the #362 human review round, part 1 — the trim path, the…
usehoplite[bot] Sep 30, 2026
3ad30b9
fix(paging): the #362 human review round, part 2 — help lines, COMMAN…
usehoplite[bot] Sep 30, 2026
ead2c7f
fix(paging): the #362 review round 3 — ranking=, dialect-true handles…
usehoplite[bot] Sep 30, 2026
d145931
refactor(paging): the #362 review round 3's item 2 — runPackTaskPage …
usehoplite[bot] Oct 1, 2026
81831db
test(showcase): the capture-regen blockers — --lsp/--sections on the …
usehoplite[bot] Oct 1, 2026
32d8871
docs(captures): regenerate COMMANDS showcase on upstream-paging 81587…
usehoplite[bot] Oct 1, 2026
aa4a64d
docs: regenerate COMMANDS.md from the 2026-10-01 capture
usehoplite[bot] Oct 1, 2026
1586bf0
fix(changelog): drop an orphan conflict marker the rebase fixups left
usehoplite[bot] Oct 1, 2026
09fcc51
sync: regenerate showcase capture, COMMANDS.md and the parity manifes…
usehoplite[bot] Oct 1, 2026
c37b215
sync: re-pin the parity manifest for the whereis/edit-check help lines
usehoplite[bot] Oct 3, 2026
66e5c3e
sync: regenerate derived figures from the merged binary (train 23 reb…
usehoplite[bot] Oct 3, 2026
6bf9d9d
sync: re-pin the parity manifest for the train-25 help lines
usehoplite[bot] Oct 7, 2026
0a8432a
sync: regenerate derived figures from the merged binary (train 25 reb…
usehoplite[bot] Oct 7, 2026
161457d
fix(paging): the #362 review round 4, B1+B2 — one list across the fiv…
usehoplite[bot] Oct 8, 2026
67b2fa5
docs: the #362 review round 4, B3+B4 — the LIMITS table re-derived; t…
usehoplite[bot] Oct 8, 2026
5b7562a
fix(changelog): the #362 review round 4, B5 — the manifest claim rest…
usehoplite[bot] Oct 8, 2026
60a2893
test(paging): the #362 CI round — the new sweep arms fit the slowest …
usehoplite[bot] Oct 9, 2026
879a54b
fix(paging): the #362 review round 5 — the gate keeps proving B1; the…
usehoplite[bot] Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,37 @@ five times, round-robin across the sizes, and compares medians. The thresholds a
stall (one 640 KB run delayed 0.6 s) fails the old arm and passes the new one. A deliberately quadratic per-word
rescan in `scanAsanWordBoundaries` still fails B1, B2 and B3 (medians 1033 / 14094 ms / timeout).

### Added — the budgeted-bundle candidate page: `--for`/`--pack-task` under a budget take a resumable `--limit`/`--offset` window; MCP `explore` gains `limit`/`offset` (issue #294, PR #362)

`--for=TASK --token-budget=N` with an explicit `--limit`/`--offset` now answers with the **candidate
page**: one `<sigs>` window over the same ranked candidate set the bundle cuts — the pageview quintet
(`shown=/total=/capped=/has_more=/next_offset=`) on the root, `above_cliff=` the head-tier count,
`tier=` one tier per page (a head window ends at the cliff), `r=` each row's GLOBAL candidate rank,
`at=` the git index answered from, `next=` the pasteable continuation argv (dropped when
`has_more="0"`), `over_ceiling="1"` when the budget cannot fit the window (fewer rows served, never
silently more bytes), and the page's own legend in both dialects (`--legend=full|compact`, the central
compact table). `--pack-task` under a budget takes the same window; a window with no budget stays the
`--for` file page; `--partition` and a window refuse together. The un-paged bundles are byte-identical
(`forbudgetmonotoncheck`), and `--offset=0` alone remains the un-paged answer — its resume point is
its own `<sigs shown=>`.

MCP `explore` declares `limit`/`offset` (the same bounded pair `for` takes) and answers the same
page shape; previously it silently ignored `budget_tokens` beside a window and served the budgetless
file page. All five page paths — CLI `--for`, CLI `--pack-task`, MCP `for`/`explore`/`pack_task` —
page ONE candidate list: the CLI pages force the ranking's full distribution under a window (the
rule `--for`'s bundle path already followed), exactly the exhaustive scoring the MCP twins always
used, so the `ranking=` attribute the first round carried is gone — there is nothing left to
distinguish. Every page, CLI or MCP, carries the same pasteable `next=`/`next_tier=`: an MCP page's
handle is a CLI argv now, and it walks the same list. Every accepted ranking flag
(the CLI's `--no-route`, `--no-mention-boost`, `--no-doc-mention`, `--cochange-boost`; the twins'
`no_route`) rides `next=`, so a pasted continuation re-ranks identically — the
`test/pagingsweepcheck.sh` walk arms pin all five surfaces to `has_more="0"` with identical row
sequences and pin each flag's echo. The `tools/list` manifest grows 46,732 → 46,916 B (measured
against train 25's `main`; descriptions identical, and the +184 B of schema is `explore`'s
`limit`/`offset` alone — `for` already declared the pair, `pack_task` is a dispatch alias, not a
listed tool), and the ceiling moves 46,750 → 46,950 B
- the one sanctioned case that moves it: `explore`'s two declared schema properties
(`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement).
### Fixed — the Linux G1 sanitizer ritual completes: five string_view comparator lambdas stop wrapping, and the GCC ASan path builds (#342)

`LSAN_OPTIONS=… ./asan/ripwire .` — the sanitizer ritual AGENTS.md requires before a PR — aborted on any
Expand Down
4 changes: 2 additions & 2 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ $ ./build/ripwire . --max-tokens=1500
... [1 more line(s); run it to see the whole thing]
```

**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit`
**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`

**Caveats (stated by the binary):**

Expand Down Expand Up @@ -4441,7 +4441,7 @@ $ ./build/ripwire . --hotspots --json

**Answers:** paginate a high-cardinality verb paginate a high-cardinality verb.

HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one <f p= score= n= sym=> row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's <u> rows; its <t> rows repeat on every page, complete). --edit-check is the same shape for a different reason: its <c> rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the <def> overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by --token-budget the same way, and takes --limit/--offset only as its file page, where the budget flags are refused in turn); the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all.
HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one <f p= score= n= sym=> row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's <u> rows; its <t> rows repeat on every page, complete). --edit-check is the same shape for a different reason: its <c> rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the <def> overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one <sigs> window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all.

**Try it**

Expand Down
4 changes: 2 additions & 2 deletions docs/LIMITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ it once, marked `×N`.

| total caps | files | caps whose file discloses | caps whose file discloses NOTHING |
| --- | --- | --- | --- |
| 236 | 94 | 135 | **101** |
| 236 | 94 | 137 | **99** |

Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they
are not counted as caps, and 236 + 7 is the 243 constants this generator parses out of `src/`.
Expand Down Expand Up @@ -323,7 +323,7 @@ Discloses: `capped`, `hosts_capped`

### `src/forpage.h`

Discloses: **none**
Discloses: `capped`

| constant | value | class | note |
| --- | --- | --- | --- |
Expand Down
41 changes: 32 additions & 9 deletions src/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -2633,9 +2633,12 @@ inline constexpr char kHelpTail[] =
" Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and\n"
" ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/\n"
" --expand/--outline/--pack-signatures/--format=candidates) are shaped by\n"
" --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by\n"
" --token-budget the same way, and takes --limit/--offset only as its file page, where\n"
" the budget flags are refused in turn); the rest (--path/--connect/\n"
" --token-budget the same way; --for takes --limit/--offset as its FILE page, where\n"
" the budget flags are refused in turn (bare --pack-task --limit refuses: it has no\n"
" page without a budget), and --for/--pack-task beside --token-budget take the window\n"
" as the BUDGETED-BUNDLE CANDIDATE PAGE — one <sigs> window over the ranked candidates,\n"
" signatures only (bodies come via next= or --expand), their resumable continuation;\n"
" the rest (--path/--connect/\n"
" --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map)\n"
" answer with a single fixed-shape result that has no row list to window at all.\n"
" --exclude=SUBSTR drop matching paths (repeatable) --ignore-tests\n"
Expand Down Expand Up @@ -3735,7 +3738,11 @@ inline bool honorsPaging( const Config& c ) noexcept
// FILE-GRAIN widening page. Membership is conditional on purpose: the bare --for bundle keeps honoring
// --token-budget/--max-tokens/--format=candidates --top-k, which validateShapingFlagsHonored refuses on
// every paging member — and refuses beside the page too, where no byte ceiling exists to shape against.
|| ( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) );
|| ( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) )
// PAGING-POC (issue #294): --pack-task joins ONLY under a budgeted window — its candidate page
// mirrors --for's (the shared emitForCandidatePage). The bare --pack-task bundle keeps refusing
// the pair (it has no window to serve), and a windowless budget run is untouched.
|| ( c.packTaskFlag && c.tokenBudget != 0 && ( c.pageLimit > 0 || c.pageOffset > 0 ) );
}

// --limit/--offset on a verb that windows NOTHING. Same accept-then-silently-ignore class as every guard in
Expand Down Expand Up @@ -3911,7 +3918,8 @@ inline constexpr PagingFamilyFlagGuard kMaxTokensGuard
inline constexpr PagingFamilyFlagGuard kTokenBudgetGuard
{
"--token-budget is honored by the default map (the CI gate), --for, --pack-task, --recall, "
"--handoff, --from-trace, --run-trace and --pr-context — none of them, --pr-context aside (it pages AND shapes by budget), "
"--handoff, --from-trace, --run-trace and --pr-context — none of them, --pr-context and --for/--pack-task beside an explicit "
"--limit/--offset window aside (those page AND shape by budget: the budgeted-bundle candidate page, issue #294), "
"in the --limit/--offset-honoring set (",
")",
"no byte budget to gate",
Expand Down Expand Up @@ -3944,7 +3952,12 @@ inline void validateShapingFlagsHonored( Config& c ) noexcept
{
refusePagingFamilyFlag( c, kMaxTokensGuard );
}
if( c.tokenBudget != 0 && !c.prContext )
// PAGING-POC (issue #294): --for/--pack-task under an EXPLICIT --limit/--offset window page their
// budgeted bundle (the candidate-offset continuation) — they page AND shape by budget, the same
// class --pr-context holds alone today. The refusal skips that combination only; every other
// budgeted verb, and the bare bundles, refuse exactly as before.
const bool budgetWindowPages = ( !c.forTask.empty() || c.packTaskFlag ) && ( c.pageLimit > 0 || c.pageOffset > 0 );
if( c.tokenBudget != 0 && !c.prContext && !budgetWindowPages )
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{
refusePagingFamilyFlag( c, kTokenBudgetGuard );
}
Expand Down Expand Up @@ -4953,8 +4966,18 @@ inline void validateConfig( Config& c ) noexcept
// every bundle-shaping flag beside it would be accepted-and-ignored, the named failure family this file
// refuses everywhere else (§H4). Named one at a time, so the remedy is the flag to drop. --top-k,
// --max-tokens and --token-budget are refused by validateShapingFlagsHonored (the page is a paging member).
if( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) )
{
// #294 review (verbs_for.h:3824): --pack-task beside a budgeted window serves the CANDIDATE page,
// and the candidate page (like the file page) is a fixed <sigs>/<files> shape — bundle-shaping flags
// would be accepted-and-ignored there too. Both pages refuse them; the message names the page that
// was selected, so the remedy (drop the flag, or the window) reads correctly.
const bool forFilePageSelected = !c.forTask.empty() && c.tokenBudget == 0 && ( c.pageLimit > 0 || c.pageOffset > 0 );
const bool forCandidateSelected = ( ( !c.forTask.empty() || c.packTaskFlag ) && c.tokenBudget != 0
&& ( c.pageLimit > 0 || c.pageOffset > 0 ) );
if( forFilePageSelected || forCandidateSelected )
{
const char* const pageName = forFilePageSelected
? "--for --limit/--offset is the file-grain widening page (one <f> row per file, its own <files> document)"
: "--for/--pack-task --token-budget with --limit/--offset is the budgeted-bundle candidate page (one fixed <sigs> window, its own resumable document)";
struct PageShapeFlag { const char* name; bool set; };
const PageShapeFlag shapeFlags[] = {
{ "--json", c.json }, { "--format=candidates", c.candidates },
Expand All @@ -4967,7 +4990,7 @@ inline void validateConfig( Config& c ) noexcept
{
if( f.set )
{
rw::emitTo( stderr, "ripwire: --for --limit/--offset is the file-grain widening page (one <f> row per file, its own <files> document) — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop --limit/--offset for the bundle\n", f.name, f.name );
rw::emitTo( stderr, "ripwire: {} — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop the window for the bundle\n", pageName, f.name, f.name ); // 3 placeholders, 3 args (the #362 review caught a 4th)
c.ok = false;
}
}
Expand Down
Loading
Loading