Skip to content

Send a token-budgeted staged context to the model - #43

Merged
rafaeelricco merged 9 commits into
mainfrom
rafaeelricco/budgeted-staged-context
Oct 29, 2026
Merged

rafaeelricco merged 9 commits into
mainfrom
rafaeelricco/budgeted-staged-context

Conversation

@rafaeelricco

@rafaeelricco rafaeelricco commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Motivation

Large staged changes sent the full diff to the model on every generate, split, regenerate and adjust. On a 44-file change that came to about 49k estimated tokens per call. This PR caps it near a 12k budget.

What's New

Staged Context Pipeline

  • New src/domain/context/ module turns the staged diff into a StagedContext that is built once per commit run: a per-file manifest, grouping hints, and the text the model sees.
  • When the diff fits CONTEXT_BUDGET_TOKENS (12k estimated tokens, chars / 4), the model gets exactly the text getStagedDiff produced before, so small commits are unchanged.
  • Over budget, identical edits across files collapse into one "Same change in N files" unit. Detail then drops per file (full, then changed lines only, then declarations and test names, then a one-line stat), test files first, then the largest files.
  • Only the hunks that still show something are rendered. The legend and the # Files manifest list every staged path (A, M, D, R<similarity>, B) with line counts.

Git Snapshot

  • getStagedSnapshot replaces getStagedDiff. It returns the same diff body and generated-files note, plus git grep --cached import lines and git log --full-diff --name-only history from the last 200 commits.
  • The diff call pins its output shape (core.quotepath=off, --no-color, --no-ext-diff, a/ and b/ prefixes), so accented paths, diff.noprefix and color.ui=always parse correctly.
  • If the import or history lookup fails (no matches, a repo with no commits), the result is "" and the command still runs.

Prompts and Calls

  • The split prompt gets a <grouping_hints> block: up to 25 file pairs ranked by reason (test, imports, history). The pairs are not merged, so a chain of imports cannot pull every file into one group.
  • Package names (react, @scope/pkg, node:path) never match a staged file.
  • Adjust (refineCommitMessage) sends the ~400-token manifest instead of the diff.
  • Regenerate reuses the context that was already built.
  • generateSplitPlan now takes the context and validates the plan against context.files.
  • The "Analyzing staged changes..." spinner clears itself instead of leaving a "Ready!" line (quietLoading). "Failed." still shows on error.
  • On a large change the log reads "Large diff compressed 87% for the model: ~88.1k → ~11.6k tokens" (compressionNote), replacing "sending a compressed summary (~X of ~Y tokens)".

Split Plan Repair

  • On compressed contexts the model listed shared files such as src/app/model-options.ts in more than one commit, and commit generate aborted with "Split plan: duplicate path". dropRepeatedPaths now keeps each path in the first commit that lists it and drops commits left empty, the same way left-out paths were already repaired.
  • The split prompt adds the rule "Never list a path in two commits".
  • An unknown (unstaged) path still fails.

Clickable URLs

  • commit doctor cut the PR URL at the 40-column Info cell with cli-table3's "…", and the terminal linked the visible text, so a click opened …/c%E2%80%A6. The cell stays truncated but now carries an OSC-8 href to the full URL (prCell), so clicking the cut text opens the PR. Off a TTY it stays plain text.
  • A new hyperlink helper (src/infra/ui/hyperlink.ts) wraps http(s) URLs in OSC-8 links when hyperlinksEnabled() (stdout is a TTY). It is used for the PR and remote lines of the push note and the device verification URL in setup, so the full URL opens even where clack hard-wraps the line.

Measurement

  • pnpm measure (scripts/measure-context.ts) prints the estimated tokens per prompt section, whole vs compressed against the budget, the number of hints, and the heaviest files. It makes no model call.

Staged Context Flow

flowchart TB
    A["Commit.run"] --> B["getStagedSnapshot + listStagedPaths"]
    B --> C["buildStagedContext"]
    C --> D{"Fits CONTEXT_BUDGET_TOKENS?"}
    D -->|Yes| E["Whole staged diff, as before"]
    D -->|No| F["Dedupe identical edits"]
    F --> G["fitToBudget: tests and largest files lose detail first"]
    G --> H["Legend + manifest + changes"]
    E --> I{"Route"}
    H --> I
    I -->|split| J["Split prompt: diff + grouping hints"]
    I -->|single| K["Commit prompt: diff"]
    K -.->|adjust| L["Refine prompt: manifest only"]
Loading

Changed Files

File Change Type Summary
src/domain/context/diff.ts Added Parse git diff --staged into per-file records (added, deleted, modified, renamed, binary)
src/domain/context/compress.ts Added Detail levels, identical-edit dedupe, budget fitting
src/domain/context/hints.ts Added Ranked file-pair hints from tests, imports and co-change history
src/domain/context/context.ts Added buildStagedContext, StagedSnapshot, StagedContext, budget constant
src/infra/git/repo.ts Modified getStagedSnapshot replaces getStagedDiff; pinned diff format
src/domain/commit/prompts.ts Modified <grouping_hints> in the split prompt; refine takes the manifest
src/domain/llm/router.ts Modified generateSplitPlan takes the context; refine takes stagedFiles
src/cli/commit.ts Modified Build the context once and pass it through; log the compression ratio; clear the analysis spinner; drop unused diff()
src/domain/split/plan.ts Modified dropRepeatedPaths repairs paths listed in several commits
src/cli/doctor.ts Modified Truncated PR cell links to the full URL via an OSC-8 href
src/infra/ui/hyperlink.ts Added hyperlink and hyperlinksEnabled, plain text off a TTY
src/infra/ui/spinner.ts Modified quietLoading clears the spinner on success
src/infra/ui/push-note.ts, src/cli/setup.ts Modified Link the PR, remote and device verification URLs
src/cli/split.ts Modified runPlan and regenerate take the context
scripts/measure-context.ts Added pnpm measure token estimator
package.json, tsconfig.json, eslint.config.js Modified Register the measure script and typecheck and lint scripts/
test/domain/context/* Added Parser, compression, hints and context tests
test/infra/git/repo.integration.test.ts, test/cli/*, test/domain/commit/prompts.test.ts, test/domain/llm/router.test.ts, test/domain/split/plan.test.ts Modified Snapshot, git config, history, adjust, prompt, split-plan repair, compression notice and doctor URL coverage
test/infra/ui/hyperlink.test.ts Added OSC-8 wrapping, disabled and non-http cases

Testing & Feedback

  • Typecheck, lint and pnpm test (385 tests) pass locally on the latest commit. Prettier and build were last run before the split-plan, notice and URL commits.
  • On this repo's own d3f2178~1..31bd8b6 staged together (44 files, +3329 −363), the sent diff drops from ~49,254 to ~10,991 estimated tokens. Run pnpm measure on a large staged change to compare.
  • Real temp repos confirmed three things: the under-budget body is byte-equal to git diff --staged, a repo with no commits still works, and nothing staged still fails with "No staged changes found".
  • No live model call was made. Two things are worth checking with a real provider: how the model handles the compressed format and legend, and whether adjust is still good enough with only the file list.
  • Known limit: with roughly 1,200+ staged files, the manifest alone is over the budget, so the compressed text goes over 12k. The budget also covers only the diff block, not the system prompt or the <staged_files> list.
  • Reviewer focus: a repeated path always lands in the first commit that lists it, even when a later one fits better (the split editor can move it). OSC-8 links render as plain text in terminals that do not support them. Clicking the truncated doctor PR link has not been checked by hand.
  • git grep and git log run on every commit, including runs that don't use hints. Their cost in very large repos has not been timed.

If you find any bugs or have recommendations for improvements, please open an issue and assign it to me.

- Add `src/domain/context/` to parse the staged diff, fold identical edits, and lower per-file detail (full, changes, outline, stat) until the text fits `CONTEXT_BUDGET_TOKENS`.
- Keep the exact staged diff when it fits the budget, so small commits get the same prompt as before.
- Replace `getStagedDiff` with `getStagedSnapshot`, which pins the diff format against user git config and adds best-effort `git grep --cached` imports and `git log --full-diff` history.
- Build the context once in `Commit.run` and pass it through split, regenerate, and adjust.
- Add ranked file-pair grouping hints (tests, imports, co-change history) to the split prompt.
- Send only the staged-file manifest to the refine prompt instead of the full diff.
- Cover parsing, compression, hints, context building, the snapshot, and the adjust path with tests.
@rafaeelricco rafaeelricco self-assigned this Oct 29, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T19:36:44.774257Z e54d6d6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bb439bb. Configure here.

Comment thread src/infra/git/repo.ts Resolved
- Drop paths the model lists in more than one commit in `dropRepeatedPaths` instead of failing with "Split plan: duplicate path".
- Remove commits left empty after the dedupe.
- Tell the split prompt never to list a path in two commits.
- Cover the dedupe and the new prompt rule with unit tests.
- Print the reduction and both token counts via `compressionNote` in `src/cli/commit.ts`.
- Cover the wording with unit tests.
- Let the doctor table's Info column fit its widest cell so PR URLs are no longer cut with an ellipsis.
- Add `hyperlink` in `src/infra/ui/hyperlink.ts` to wrap URLs in OSC-8 links on a TTY.
- Link the PR and remote URLs in the push note and the device verification URL in setup.
- Cover the helper and the full doctor PR URL with unit tests.
- Add `quietLoading` in `src/infra/ui/spinner.ts`, which clears the spinner on success and still shows "Failed." on error.
- Use it for the "Analyzing staged changes..." step in `src/cli/commit.ts`.
- Mock `quietLoading` in the commit CLI tests.
- Run `stagedImportLines` with `-c core.quotepath=off` and `--no-color` so `color.ui=always` and non-ASCII paths still match staged files.
- Assert on `importLines` in the color and non-ASCII snapshot integration test.
@rafaeelricco

Copy link
Copy Markdown
Owner Author

cursor on bb439bb — 1 finding

  • stagedImportLines ran git grep without the diff's format pins, so import hints went empty under color.ui=always or non-ASCII paths; we fixed it by running grep with -c core.quotepath=off and --no-color, and the color and non-ASCII integration test now asserts on importLines (e54d6d6).

@rafaeelricco
rafaeelricco merged commit 64db358 into main Oct 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant