Sprint plans 42-45: review 42, write 43-45
Sprint 42 (reviewed): fixed HiveMind-specific example in smoke test step, added review notes clarifying asToolResponse usage and test file pattern. Sprint 43: Cross-File Context Assembly — WorkspaceFileIndex, ContextSliceAssembler, TokenBudgetEnforcer, whetstone_assemble_context MCP tool (Steps 674-678). Sprint 44: Taskitem Self-Containment Validator — PrerequisiteOpResolver, SelfContainmentScorer, TaskitemQualityAuditor, whetstone_validate_taskitem MCP tool (Steps 679-683). Directly addresses whether taskitem token reduction is real. Sprint 45: Agent Session Metrics + A/B Harness — AgentSessionRecorder, TaskCompletionMetrics, ABTestComparison, whetstone_start_recording + whetstone_get_metrics MCP tools (Steps 684-688). Produces the measured comparison between Whetstone-assisted and baseline agent runs. All sprints are generic editor tooling — no coupling to external systems. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
217
sprint43_plan.md
Normal file
217
sprint43_plan.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Sprint 43 Plan: Cross-File Context Assembly
|
||||
|
||||
## Context
|
||||
|
||||
Sprint 42 completes the backlog of unregistered MCP tools (tool count 84→86).
|
||||
The architect intake pipeline (Sprints 32, 36) generates annotated taskitems with
|
||||
`prerequisiteOps` lists — file paths and symbol names the executing agent needs.
|
||||
Currently, agents read full files to satisfy those ops, negating much of the token
|
||||
budget advantage taskitems are designed to create.
|
||||
|
||||
Sprint 43 adds surgical context slicing: given a taskitem's `prerequisiteOps`,
|
||||
extract only the relevant declarations, function bodies, or type definitions from
|
||||
each file — not the whole file. Respects a configurable token budget. Entirely
|
||||
generic — no coupling to any external system.
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
1. Index workspace files by symbol definition (fast lookup by name, not full-file scan)
|
||||
2. Assemble minimal context slices for a given set of file paths + symbol names
|
||||
3. Enforce a token budget — trim to fit, report what was dropped
|
||||
4. Expose as `whetstone_assemble_context` MCP tool
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 674: `WorkspaceFileIndex` — symbol definition index (12 tests)
|
||||
|
||||
Create `editor/src/WorkspaceFileIndex.h`.
|
||||
|
||||
Index all files under a workspace root. For each file, extract top-level
|
||||
declarations (function signatures, class names, struct names, type aliases,
|
||||
constants) using line-level heuristics — no full parse required for this index.
|
||||
|
||||
```cpp
|
||||
struct SymbolLocation {
|
||||
std::string file;
|
||||
int lineStart;
|
||||
int lineEnd; // end of signature or short body
|
||||
std::string symbolName;
|
||||
std::string symbolKind; // "function" | "class" | "struct" | "type" | "const"
|
||||
};
|
||||
|
||||
class WorkspaceFileIndex {
|
||||
public:
|
||||
static WorkspaceFileIndex build(const std::string& workspaceRoot);
|
||||
std::vector<SymbolLocation> find(const std::string& symbolName) const;
|
||||
std::vector<SymbolLocation> findInFile(const std::string& filePath) const;
|
||||
int fileCount() const;
|
||||
int symbolCount() const;
|
||||
};
|
||||
```
|
||||
|
||||
Tests (12): empty root returns empty index, single-file workspace indexes it,
|
||||
function signatures found by name, class declarations found by name, struct
|
||||
declarations found by name, findInFile returns all symbols in that file, find()
|
||||
returns all matches across files, symbolCount > 0 after indexing real file,
|
||||
fileCount matches input file count, duplicate symbol names all returned, build()
|
||||
doesn't throw on unreadable file (skips gracefully), kind field is non-empty.
|
||||
|
||||
### Step 675: `ContextSliceAssembler` — extract minimal file sections (12 tests)
|
||||
|
||||
Create `editor/src/ContextSliceAssembler.h`.
|
||||
|
||||
Given a list of `{file, symbol_or_line_range}` requests and a workspace index,
|
||||
extract only the relevant lines from each file. A "slice" is:
|
||||
- For a named symbol: the lines from its declaration through the closing brace/semicolon
|
||||
- For a line range: exactly those lines
|
||||
- For a file with no symbol specified: first N lines (configurable, default 40)
|
||||
|
||||
```cpp
|
||||
struct SliceRequest {
|
||||
std::string filePath;
|
||||
std::string symbolName; // empty = use lineHint or head
|
||||
int lineHint; // 0 = not specified
|
||||
int headLines; // fallback if no symbol match; default 40
|
||||
};
|
||||
|
||||
struct ContextSlice {
|
||||
std::string filePath;
|
||||
std::string symbolName;
|
||||
int lineStart;
|
||||
int lineEnd;
|
||||
std::string content; // the actual extracted lines
|
||||
bool found;
|
||||
};
|
||||
|
||||
class ContextSliceAssembler {
|
||||
public:
|
||||
static std::vector<ContextSlice> assemble(
|
||||
const std::vector<SliceRequest>& requests,
|
||||
const WorkspaceFileIndex& index);
|
||||
};
|
||||
```
|
||||
|
||||
Tests (12): empty requests returns empty result, named symbol found returns
|
||||
correct lines, unknown symbol returns head fallback, line range returns correct
|
||||
lines, content non-empty on success, found==false when symbol missing, multiple
|
||||
requests all processed, head fallback respects headLines parameter, function body
|
||||
slice includes closing brace, class slice includes closing brace, file path
|
||||
preserved in result, slice content is valid UTF-8 substring of source file.
|
||||
|
||||
### Step 676: `TokenBudgetEnforcer` — trim assembled context to budget (12 tests)
|
||||
|
||||
Create `editor/src/TokenBudgetEnforcer.h`.
|
||||
|
||||
Given a list of `ContextSlice` objects and a max token budget, trim the list
|
||||
to fit. Strategy: priority order (slices earlier in the list are higher priority),
|
||||
each slice is dropped whole if it doesn't fit. Returns the slices that fit plus
|
||||
a report of what was dropped.
|
||||
|
||||
Token estimation: 1 token ≈ 4 characters (conservative approximation, no model
|
||||
dependency). The enforcer does NOT call any model API.
|
||||
|
||||
```cpp
|
||||
struct BudgetReport {
|
||||
int totalSlices;
|
||||
int includedSlices;
|
||||
int droppedSlices;
|
||||
int estimatedTokensUsed;
|
||||
int budgetTokens;
|
||||
bool budgetExceeded; // true if any slice was dropped
|
||||
std::vector<std::string> droppedFiles;
|
||||
};
|
||||
|
||||
class TokenBudgetEnforcer {
|
||||
public:
|
||||
static std::pair<std::vector<ContextSlice>, BudgetReport>
|
||||
enforce(const std::vector<ContextSlice>& slices, int maxTokens);
|
||||
|
||||
static int estimateTokens(const std::string& text);
|
||||
};
|
||||
```
|
||||
|
||||
Tests (12): empty slices returns empty + zero report, single slice under budget
|
||||
kept, single slice over budget dropped, multiple slices: high-priority kept when
|
||||
budget tight, droppedFiles list matches dropped slices, estimatedTokensUsed <=
|
||||
budgetTokens when no drops, budgetExceeded==false when all fit, budgetExceeded==true
|
||||
when any dropped, estimateTokens("") == 0, estimateTokens("abcd") == 1,
|
||||
report.totalSlices == input count, report.includedSlices + report.droppedSlices
|
||||
== report.totalSlices.
|
||||
|
||||
### Step 677: `whetstone_assemble_context` MCP tool (8 tests)
|
||||
|
||||
Add to `RegisterCodegenTools.h` (or new `RegisterContextTools.h` if file nears 600 lines).
|
||||
|
||||
Input schema:
|
||||
```json
|
||||
{
|
||||
"files": {
|
||||
"type": "array",
|
||||
"description": "List of slice requests.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": { "type": "string", "description": "File path relative to workspace." },
|
||||
"symbol": { "type": "string", "description": "Symbol name to extract (optional)." },
|
||||
"line_hint": { "type": "integer", "description": "Line number hint (optional)." },
|
||||
"head_lines": { "type": "integer", "description": "Head fallback line count (optional, default 40)." }
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
},
|
||||
"max_tokens": { "type": "integer", "description": "Token budget (default 8000)." }
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"slices": [
|
||||
{ "file": "...", "symbol": "...", "line_start": 10, "line_end": 25, "content": "..." }
|
||||
],
|
||||
"budget_report": {
|
||||
"total_slices": 3,
|
||||
"included_slices": 2,
|
||||
"dropped_slices": 1,
|
||||
"estimated_tokens_used": 1840,
|
||||
"budget_tokens": 2000,
|
||||
"budget_exceeded": true,
|
||||
"dropped_files": ["large_file.cpp"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wire into MCPServer.h, RegisterOnboardingAndAllTools.h, and tools.json.
|
||||
Tool count 86→87.
|
||||
|
||||
Tests (8): tool registered, empty files array returns success with empty slices,
|
||||
valid file path returns slice, missing path arg returns error, symbol slicing
|
||||
returns content, budget_report present in output, budget_exceeded==false when
|
||||
all slices fit, tool count in tools.json == 87.
|
||||
|
||||
### Step 678: Sprint 43 Integration Summary (8 tests)
|
||||
|
||||
Create `editor/src/Sprint43IntegrationSummary.h`.
|
||||
Record: tool_count_before=86, tool_count_after=87, steps_completed=5 (674-678),
|
||||
files_added=[WorkspaceFileIndex.h, ContextSliceAssembler.h, TokenBudgetEnforcer.h,
|
||||
Sprint43IntegrationSummary.h], success=true.
|
||||
|
||||
Tests (8): struct constructable, workspace_index_builds, slice_assembler_works,
|
||||
budget_enforcer_works, tool_count_before==86, tool_count_after==87,
|
||||
steps_completed==5, success==true.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Gate (All Steps)
|
||||
|
||||
- Header-only C++ for all new source files (no new .cpp except test files)
|
||||
- 600-line max per file
|
||||
- No new vcpkg dependencies — uses only `std::filesystem` and string operations
|
||||
- `estimateTokens` must not call any external model API
|
||||
- All slicing is done on raw file text — no AST dependency (fast, portable)
|
||||
- Full sprint regression at step 678
|
||||
Reference in New Issue
Block a user