Skip to content

feat(v0.20): value-per-token selection (#131) + signature resolution (#132) + SPI default-readiness criteria (#134)#138

Merged
mohanagy merged 2 commits into
mainfrom
feat/v0.20-context-compiler
May 11, 2026
Merged

feat(v0.20): value-per-token selection (#131) + signature resolution (#132) + SPI default-readiness criteria (#134)#138
mohanagy merged 2 commits into
mainfrom
feat/v0.20-context-compiler

Conversation

@mohanagy
Copy link
Copy Markdown
Owner

@mohanagy mohanagy commented May 11, 2026

Closes #131
Closes #132
Closes #134

Summary

Bundles three v0.20-context-compiler items. #135 (task-conditioned slicing v1) intentionally deferred — it's a multi-PR substrate, not a single-shot slice.

What's in the bundle

#131 — value-per-token selection_strategy on compileContextPack

  • New optional input: selection_strategy: 'evidence-order' | 'value-per-token' (default = evidence-order, unchanged)
  • When value-per-token:
    1. Required-evidence-class candidates placed first (must-include)
    2. Remaining optional candidates go through selectByValuePerToken with residual budget
  • Score uses candidate's position in orderedNodes as rank proxy (1/(idx+1))
  • Required candidates can't be dropped even if expensive; optional ones compete by density

#132 — 'signature' resolution level

  • Extended ContextPackResolution from detail | summary | mixed to also accept signature
  • Signature mode keeps the first 1-2 lines of each snippet (function/class signature) and drops the body
  • Heuristic: keep lines up through the one ending in { or => (max 3 line scan); fallback to first 2 lines
  • Middle ground between detail (full snippet) and summary (no body) — useful when the agent needs param types and return shape but not implementation

#134 — SPI default-readiness decision framework

  • New doc: docs/decisions/2026-05-11-spi-default-readiness.md
  • Codifies what --spi must achieve before becoming default:
    • Build-time parity within 1.5x cold, ≥ 2x faster cache-hit
    • Compatibility checklist across 9 commands
    • Project-shape coverage (6 shapes)
    • Retrieval-quality threshold
    • Diagnostics surface parity
  • Defines post-flip fallback: --no-spi flag remains accessible for 3 releases minimum
  • Decision framework, not code. The next code change (the actual default flip) has to meet this checklist.

Not in this bundle (intentionally)

#135 — task-conditioned slicing v1. Requires new anchor-detection module, slice walker, task-mode-specific traversal rules. Multi-PR effort; doesn't fit a single-shot bundle alongside #131/#132. Worth its own slice train.

Test plan

  • npm run typecheck clean
  • npm run build clean
  • npm run test:run — 1833/1833 pass (113 files, +9 new tests)

Summary by CodeRabbit

  • New Features

    • Added a signature resolution mode that condenses context by truncating snippets to function signatures and reports bytes saved.
    • Added a value-per-token selection strategy for context packing to optimize token efficiency after required evidence is placed.
  • Tests

    • Added unit tests verifying signature-mode truncation behavior and value-per-token selection outcomes across edge cases.
  • Documentation

    • Added SPI default-readiness criteria and a multi-release transition/fallback policy.
  • Style/Types

    • Expanded resolution annotations to include the new signature option.

Review Change Stack

…132) + SPI default-readiness criteria (#134)

Bundles three v0.20-context-compiler items. #135 (task-conditioned slicing v1) intentionally deferred — it's a multi-PR substrate, not a single-shot slice.

## #131 — value-per-token selection_strategy on compileContextPack

- New optional input field: selection_strategy: 'evidence-order' | 'value-per-token' (default unchanged)

- When 'value-per-token': required-evidence-class candidates are placed first (must-include), then remaining optional candidates go through selectByValuePerToken with the residual budget

- Score for value-per-token uses the candidate's position in orderedNodes as a rank proxy (1/(idx+1)) — earlier candidates rank higher

- Required candidates can't be dropped even if expensive; optional candidates compete by density (score / token_cost)

## #132 — 'signature' resolution level

- Extended ContextPackResolution from 'detail' | 'summary' | 'mixed' to also accept 'signature'

- Signature mode keeps the first 1-2 lines of each snippet (function/class signature) and drops the body

- Heuristic: keep lines up through the one ending in '{' or '=>' (max 3 line scan); fallback to first 2 lines if neither found

- Middle ground between 'detail' (full snippet) and 'summary' (no snippet body at all) — useful when agent needs param types and return shape but not implementation

## #134 — SPI default-readiness decision framework

- New doc: docs/decisions/2026-05-11-spi-default-readiness.md

- Codifies the criteria the --spi flag must meet before becoming default: build-time parity within 1.5x, compatibility checklist across 9 commands, project-shape coverage across 6 shapes, retrieval-quality threshold, diagnostics surface parity

- Defines the post-flip fallback path: --no-spi flag remains accessible for 3 releases minimum, with deprecation window pause if SPI-specific regressions surface

- This is a DECISION FRAMEWORK, not code. The next code change (the default flip itself) has to meet this checklist.

## Tests

- 4 new tests in context-pack-value-per-token-131.test.ts: required candidates must-include, density beats expense, evidence-order default still works, omitted candidates reported correctly

- 5 new tests in context-pack-resolution-signature-132.test.ts: brace boundary, arrow boundary, fallback when neither found, null snippet handling, bytes_saved scales with body size

- 1833/1833 pass, typecheck + build clean

## Not in this bundle (deferred)

- #135 task-conditioned slicing v1 — requires new anchor-detection module, slice walker, task-mode-specific traversal rules. Multi-PR effort; doesn't fit a single-shot bundle alongside #131/#132.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

Adds a 'value-per-token' selection strategy and placement refactor to context-pack compilation, introduces a 'signature' resolution mode with extraction helpers and updated resolution_map typing, and adds a SPI default-readiness decision document with graduation criteria and a fallback timeline.

Changes

Value-Per-Token Selection Strategy

Layer / File(s) Summary
Selection Strategy Interface
src/runtime/context-pack.ts
Adds selection_strategy?: 'evidence-order' | 'value-per-token' and imports selectByValuePerToken.
Selection Logic & Placement Refactor
src/runtime/context-pack.ts
Refactors placement into placeCandidate; implements 'value-per-token' path that greedily places required-evidence candidates, computes residual budget, selects optional candidates via selectByValuePerToken, places selected candidates, and computes omitted nodes via set difference.
Tests
tests/unit/context-pack-value-per-token-131.test.ts
Unit tests validate required-evidence priority, density-based optional selection, default evidence-order fallback, and omitted/expandable pack behavior.

Signature Resolution Mode

Layer / File(s) Summary
Resolution Type
src/runtime/context-pack-resolution.ts, src/runtime/stdio/tools.ts
Extends ContextPackResolution and resolution_map entries to include 'signature'.
Signature Extraction & Application
src/runtime/context-pack-resolution.ts
Adds 'signature' branch in applyContextPackResolution, implements signatureResolution and extractSignature to truncate snippets to function signatures (up to { or =>, fallback to first 2 lines), and reports bytes_saved. Updates mixedResolution typing.
Tests
tests/unit/context-pack-resolution-signature-132.test.ts
Tests truncation rules, fallback behavior, null-snippet passthrough, and bytes_saved scaling.

SPI Default-Readiness Decision Document

Layer / File(s) Summary
Document Context & Baseline
docs/decisions/2026-05-11-spi-default-readiness.md
Adds decision doc framing it as a measurable gate, records v0.19 baseline and benchmark context.
Graduation Criteria
docs/decisions/2026-05-11-spi-default-readiness.md
Defines five required graduation criteria (build-time parity, downstream compatibility, project-shape coverage, retrieval quality, diagnostics parity).
Fallback Plan & Scope
docs/decisions/2026-05-11-spi-default-readiness.md
Specifies multi-release fallback and deprecation timeline for legacy --no-spi and lists out-of-scope items; adds initial decision log entry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I trimmed the tokens, found denser light,

signatures gleam where bodies took flight,
required bits first, then value-per-token cheer,
a safety fallback kept ever near,
hop, review, and ship — the pack is clear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies all three main changes: value-per-token selection (#131), signature resolution (#132), and SPI default-readiness criteria (#134), with appropriate issue references.
Description check ✅ Passed The description comprehensively covers the summary, objectives of each bundled feature, testing verification, and related issues; all template sections are addressed.
Linked Issues check ✅ Passed The PR successfully implements all three linked issues: #131 adds value-per-token selection strategy to compileContextPack with required-candidates-first placement and optional-candidate density-based selection; #132 extends ContextPackResolution to include signature mode for function signatures; #134 documents SPI default-readiness decision framework with measurable criteria.
Out of Scope Changes check ✅ Passed All code changes align with the three linked issues: context-pack selection strategy, resolution types, signature extraction, tests for both features, and documentation of SPI readiness criteria. No unrelated changes are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.20-context-compiler

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/runtime/context-pack.ts (1)

628-653: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Track placed candidates directly when computing omitted nodes.

omittedNodes is derived from label:source_file:line_number after materialization. That can mark a selected candidate as omitted when build_entry() fills missing source metadata, and it also collapses distinct candidates that share the same triple. pack.expandable should be based on the actual candidates accepted by placeCandidate().

Proposed fix
   const selectedLabelsByEvidence = new Map<ContextPackEvidenceClass, string[]>()
   const selectedCommunities = new Set<number>()
+  const placedCandidates = new Set<ContextPackNodeCandidate<TNode>>()
   let tokenCount = 0
   let breakIndex = orderedNodes.length

   const placeCandidate = (candidate: ContextPackNodeCandidate<TNode>, candidateTokens: number): void => {
+    placedCandidates.add(candidate)
     const entry = candidate.build_entry()
     selectedNodes.push(entry)
@@
-  const placedLabelKey = (c: ContextPackNodeCandidate<TNode>): string => `${c.label}:${c.source_file}:${c.line_number}`
-  const placedKeys = new Set(selectedNodes.map((n) => `${n.label}:${n.source_file}:${n.line_number}`))
   const omittedNodes = input.selection_strategy === 'value-per-token'
-    ? orderedNodes.filter((c) => !placedKeys.has(placedLabelKey(c)))
+    ? orderedNodes.filter((c) => !placedCandidates.has(c))
     : orderedNodes.slice(breakIndex)

Also applies to: 710-717

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/context-pack.ts` around lines 628 - 653, The code currently
computes omittedNodes from materialized entry keys
(label:source_file:line_number) which can misclassify or collapse distinct
candidates after build_entry(); to fix, record placed candidates directly inside
placeCandidate() (e.g., add candidate or a stable unique id to a new Set like
placedCandidates when calling placeCandidate), use that placedCandidates Set
when computing omittedNodes and pack.expandable, and update the same logic used
around lines 710-717 to consult placedCandidates instead of derived entry
triples so expandable reflects the actual candidates accepted by
placeCandidate().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/context-pack-resolution.ts`:
- Around line 24-25: The resolution_map currently maps the 'signature'
ContextPackResolution to 'summary', causing applyContextPackResolution and
downstream diagnostics to treat signature output as summary; update the
resolution_map to include 'signature' as its own entry (e.g., map 'signature' ->
'signature') and adjust any switch/conditional logic in
applyContextPackResolution, ContextPackResolution type usages, and the other
spots referencing resolution_map so nodes are recorded as 'signature' when
resolution === 'signature' instead of 'summary' (check the blocks around
resolution_map, applyContextPackResolution, and the adjacent mapping logic and
mirror the change wherever resolution values 59-61 and 76-90 are handled).

In `@src/runtime/context-pack.ts`:
- Around line 674-680: The loop over requiredCandidates incorrectly uses break
when a required candidate would overflow the budget (checking
candidate.estimate_tokens(), tokenCount, input.task_contract.budget, and
selectedNodes), which stops processing later required candidates; change the
control flow so that when candidateTokens would exceed the budget and
selectedNodes.length > 0 you continue (skip/drop this overflowing candidate)
instead of break, leaving the rest of the loop to evaluate subsequent required
candidates and still call placeCandidate for those that fit.
- Around line 681-694: The value-per-token score is using idx from
optionalCandidates instead of the node's position in orderedNodes, compressing
ranks if required nodes preceded optionals; update the mapping that builds
valueCandidates so score uses the node's index within orderedNodes (e.g., find
the orderedIndex via orderedNodes.findIndex(...) using the same identity/key
used elsewhere) and set score: 1 / (orderedIndex + 1) (fall back to a large
index if not found). Keep other fields (id, payload, token_cost) unchanged and
then call selectByValuePerToken as before.

---

Outside diff comments:
In `@src/runtime/context-pack.ts`:
- Around line 628-653: The code currently computes omittedNodes from
materialized entry keys (label:source_file:line_number) which can misclassify or
collapse distinct candidates after build_entry(); to fix, record placed
candidates directly inside placeCandidate() (e.g., add candidate or a stable
unique id to a new Set like placedCandidates when calling placeCandidate), use
that placedCandidates Set when computing omittedNodes and pack.expandable, and
update the same logic used around lines 710-717 to consult placedCandidates
instead of derived entry triples so expandable reflects the actual candidates
accepted by placeCandidate().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14020cac-27d5-4924-adf0-c28c01a4f336

📥 Commits

Reviewing files that changed from the base of the PR and between f11a4d0 and 3f7b2f2.

📒 Files selected for processing (5)
  • docs/decisions/2026-05-11-spi-default-readiness.md
  • src/runtime/context-pack-resolution.ts
  • src/runtime/context-pack.ts
  • tests/unit/context-pack-resolution-signature-132.test.ts
  • tests/unit/context-pack-value-per-token-131.test.ts

Comment thread src/runtime/context-pack-resolution.ts
Comment thread src/runtime/context-pack.ts
Comment thread src/runtime/context-pack.ts
…uired continue + global-rank score + signature label

Four valid findings from CodeRabbit on PR #138:

1) MAJOR — omittedNodes was derived from materialized label:source_file:line_number triples, which can mis-attribute when build_entry() fills missing source metadata or two distinct candidates share a triple. Fixed by tracking candidate object identity directly via a placedCandidates Set populated inside placeCandidate().

2) requiredCandidates loop used 'break' on overflow — one oversize required candidate killed subsequent smaller required ones. Changed to 'continue': skip the overflowing one, try the next. Required candidates each have an independent must-include semantic, so one's failure shouldn't cascade.

3) value-per-token score used the optionalCandidates-local idx, not the orderedNodes-global index. A leading run of required candidates would compress the optional rank space and inflate every optional's score. Now uses a Map<candidate, orderedIndex> for the correct global rank.

4) signature resolution mis-labeled itself as 'summary' in resolution_map. Extended the type to include 'signature' explicitly. Downstream diagnostics can now differentiate 'has signature info' from 'has no body at all'. Updated stdio/tools.ts applyResolutionToNodes return type to match.

1833/1833 still pass, typecheck + build clean.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/runtime/stdio/tools.ts (1)

906-910: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

resolution: "signature" is currently unreachable in context_pack.

Lines 907-910 only admit 'summary' | 'mixed', so 'signature' falls back to 'detail' and never reaches applyContextPackResolution.

Proposed fix
-      const resolution: ContextPackResolution =
-        resolutionParam === 'summary' || resolutionParam === 'mixed'
+      const resolution: ContextPackResolution =
+        resolutionParam === 'summary' || resolutionParam === 'mixed' || resolutionParam === 'signature'
           ? resolutionParam
           : 'detail'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/stdio/tools.ts` around lines 906 - 910, The resolution variable
currently only accepts 'summary' or 'mixed', causing 'signature' to fall back to
'detail' and never reach applyContextPackResolution; update the validation logic
where resolutionParam is read (helpers.stringParam and the resolution
assignment) to treat 'signature' as a valid ContextPackResolution (e.g., include
resolutionParam === 'signature' in the conditional or otherwise allow any
ContextPackResolution value), so resolution can be 'summary' | 'mixed' |
'signature' and will be passed through to applyContextPackResolution unchanged.
🧹 Nitpick comments (1)
src/runtime/stdio/tools.ts (1)

916-916: ⚡ Quick win

Avoid re-declaring the resolution_map union locally.

Line 916 duplicates the resolution union type; prefer reusing the exported ApplyResolutionResult typing to prevent future drift between modules.

Refactor sketch
-import { applyContextPackResolution, type ContextPackResolution } from '../context-pack-resolution.js'
+import {
+  applyContextPackResolution,
+  type ContextPackResolution,
+  type ApplyResolutionResult,
+} from '../context-pack-resolution.js'
...
-        resolution_map: Array<{ node_id: string | undefined; resolution: 'detail' | 'summary' | 'signature' }>
+        resolution_map: ApplyResolutionResult<ContextPackNode>['resolution_map']
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/stdio/tools.ts` at line 916, Replace the local re-declaration of
the resolution union on the resolution_map property with the exported
ApplyResolutionResult type: import ApplyResolutionResult and use it (e.g.,
resolution_map: ApplyResolutionResult[] or the appropriate ApplyResolutionResult
property) instead of redeclaring 'resolution: "detail" | "summary" |
"signature"'; update the resolution_map declaration in the same location to
reference ApplyResolutionResult and add the necessary import for
ApplyResolutionResult at the top of the module.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/runtime/stdio/tools.ts`:
- Around line 906-910: The resolution variable currently only accepts 'summary'
or 'mixed', causing 'signature' to fall back to 'detail' and never reach
applyContextPackResolution; update the validation logic where resolutionParam is
read (helpers.stringParam and the resolution assignment) to treat 'signature' as
a valid ContextPackResolution (e.g., include resolutionParam === 'signature' in
the conditional or otherwise allow any ContextPackResolution value), so
resolution can be 'summary' | 'mixed' | 'signature' and will be passed through
to applyContextPackResolution unchanged.

---

Nitpick comments:
In `@src/runtime/stdio/tools.ts`:
- Line 916: Replace the local re-declaration of the resolution union on the
resolution_map property with the exported ApplyResolutionResult type: import
ApplyResolutionResult and use it (e.g., resolution_map: ApplyResolutionResult[]
or the appropriate ApplyResolutionResult property) instead of redeclaring
'resolution: "detail" | "summary" | "signature"'; update the resolution_map
declaration in the same location to reference ApplyResolutionResult and add the
necessary import for ApplyResolutionResult at the top of the module.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e5737c4-048d-41d7-89ec-7f28a3ca3117

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7b2f2 and e5dd2cf.

📒 Files selected for processing (3)
  • src/runtime/context-pack-resolution.ts
  • src/runtime/context-pack.ts
  • src/runtime/stdio/tools.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/runtime/context-pack.ts

@mohanagy mohanagy merged commit e250590 into main May 11, 2026
7 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

1 participant