Sprint 111: IterationSession context primitive Sprint 112: EnvironmentSnapshot and diff Sprint 113: Build output feedback loop Sprint 114: Test failure feedback loop Sprint 115: Requirements derivation from AST Sprint 116: FeedbackLoopOrchestrator (wires 111-115) Sprint 117: HiveMind integration (iteration steps as distributed jobs) These sprints begin Epoch 3 — closed-loop determinism. Sprints 1-110 made code generation open-loop deterministic. Sprints 111+ make the full dev workflow (sense→act→observe→repeat) structurally deterministic, with HiveMind as the execution layer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
187 lines
8.1 KiB
Markdown
187 lines
8.1 KiB
Markdown
# Sprint 114 Plan: Test Failure Feedback Loop
|
||
|
||
## Context
|
||
|
||
Build success is only the first gate. Tests are the second gate, and they carry
|
||
richer semantic signal than compiler errors: a failing assertion tells you not
|
||
just *where* something went wrong but *what* the code was supposed to do versus
|
||
what it actually did. That gap between expected and actual is exactly the kind of
|
||
structured signal Whetstone can act on deterministically.
|
||
|
||
Sprint 114 adds test output parsing, failure classification, test-to-source
|
||
location mapping, and fix candidate generation for test failures. It mirrors
|
||
the architecture of Sprint 113 (Build Feedback Loop) but operates on test
|
||
output formats (Google Test, pytest, Jest, TAP). The result integrates with
|
||
IterationSession: each test run becomes an AttemptRecord with structured
|
||
observations and a GapModel keyed on failing test count.
|
||
|
||
---
|
||
|
||
## Goals
|
||
|
||
1. Parse test runner output (gtest, pytest, Jest, TAP) into `TestFailureRecord`s
|
||
2. Classify failures by type: assertion, exception, timeout, infra, wrong output
|
||
3. Map each failure to a source location and AST node
|
||
4. Generate ranked fix candidates from test failures
|
||
5. Integrate with IterationSession (test run → AttemptRecord)
|
||
|
||
---
|
||
|
||
## Steps
|
||
|
||
### Step 1369: `TestFailureRecord` schema (12 tests)
|
||
|
||
Create `editor/src/TestFailureRecord.h`.
|
||
|
||
Fields: `failure_id`, `runner` (enum: gtest, pytest, jest, tap, unknown),
|
||
`test_suite`, `test_name`, `failure_type` (enum: assertion, exception,
|
||
timeout, missing_output, wrong_output, infra, unknown),
|
||
`expected_value` (string, may be empty), `actual_value` (string, may be empty),
|
||
`file_path`, `line`, `message`, `ast_node_id` (populated by locator).
|
||
|
||
Tests (12): constructable, runner enum round-trips, failure_type defaults to
|
||
unknown, expected/actual stored, file_path stored, line stored, message stored,
|
||
ast_node_id starts empty, JSON serialization, JSON deserialization, unknown
|
||
runner passthrough, deterministic output.
|
||
|
||
### Step 1370: Test output parser — gtest format (10 tests)
|
||
|
||
Create `editor/src/TestOutputParser.h`.
|
||
|
||
Parses Google Test stdout format:
|
||
- `[ FAILED ] SuiteName.TestName (Nms)` → failure header
|
||
- `Expected: <value>` / ` Actual: <value>` → assertion fields
|
||
- `<file>:<line>: Failure` → source location
|
||
|
||
Also parses pytest (`FAILED test_file.py::test_name - AssertionError: ...`)
|
||
and basic TAP (`not ok N - description`). Jest support for `✕ test name`
|
||
format. Unknown lines attached to current test as raw message.
|
||
|
||
Tests (10): gtest failure parsed, gtest assertion expected/actual extracted,
|
||
gtest file/line extracted, pytest failure parsed, jest failure parsed,
|
||
TAP not-ok parsed, pass lines ignored, multi-failure batch parsed,
|
||
empty input returns empty list, deterministic parse order.
|
||
|
||
### Step 1371: Test failure classifier (10 tests)
|
||
|
||
Create `editor/src/TestFailureClassifier.h`.
|
||
|
||
Classifies `TestFailureRecord` into semantic types using message patterns:
|
||
- `assertion` — Expect/Assert macro failure (expected != actual)
|
||
- `exception` — unhandled exception or signal in test
|
||
- `timeout` — test exceeded time limit
|
||
- `wrong_output` — stdout/output mismatch (snapshot tests)
|
||
- `missing_output` — expected output file not produced
|
||
- `infra` — test setup/teardown failure, missing fixture
|
||
- `unknown` — catch-all
|
||
|
||
Tests (10): assertion classified, unhandled exception classified, timeout
|
||
classified, snapshot mismatch → wrong_output, missing file → missing_output,
|
||
fixture failure → infra, unknown catch-all, deterministic classification,
|
||
classification applied to batch, JSON round-trip of classified record.
|
||
|
||
### Step 1372: Test-to-source locator (10 tests)
|
||
|
||
Create `editor/src/TestSourceLocator.h`.
|
||
|
||
Maps a `TestFailureRecord` to the implementation code under test, not just
|
||
the test file. Strategy:
|
||
1. If file_path is a test file (matches `*test*`, `*spec*`), find the
|
||
corresponding implementation file by naming convention.
|
||
2. Locate the function-under-test by test_name substring match against
|
||
AST function names.
|
||
3. Fall back to the test file's AST node if implementation not found.
|
||
|
||
Populates `ast_node_id` on the failure record.
|
||
|
||
Tests (10): test file maps to implementation file, function-under-test found
|
||
by name, fallback to test file node, no match returns nullopt gracefully,
|
||
gtest suite.test → function match, pytest test_name → function match,
|
||
deterministic selection, works across Python and C++, batch locating works,
|
||
ast_node_id written to record.
|
||
|
||
### Step 1373: Fix candidate generator for test failures (10 tests)
|
||
|
||
Create `editor/src/TestFixCandidateGenerator.h`.
|
||
|
||
Generates `FixCandidate` list from classified `TestFailureRecord`:
|
||
|
||
- `assertion` (expected != actual): suggest value correction or logic change
|
||
at the implementation AST node. Confidence = 0.4–0.7 (semantic, heuristic).
|
||
- `exception`: suggest try/catch addition or precondition guard. Confidence 0.3.
|
||
- `timeout`: suggest async/await addition or algorithm complexity reduction hint.
|
||
Confidence 0.2 (manual candidate prominent).
|
||
- `wrong_output` / `missing_output`: suggest regenerating snapshot or checking
|
||
file write path. Manual candidate.
|
||
- `infra`: suggest fixture setup. Manual.
|
||
- `unknown`: manual only.
|
||
|
||
Tests (10): assertion produces value-correction candidate, confidence in range,
|
||
exception produces guard candidate, timeout produces manual prominent,
|
||
wrong_output produces snapshot candidate, batch processing works, candidates
|
||
ordered by confidence, manual fallback always present, deterministic output,
|
||
candidate mutations JSON round-trip.
|
||
|
||
### Step 1374: Test iteration session integration (8 tests)
|
||
|
||
Create `editor/src/TestIterationAdapter.h`.
|
||
|
||
Mirrors `BuildIterationAdapter`. Runs test command via subprocess, parses
|
||
output, computes GapModel (metric_type=test_failure_count), records attempt
|
||
in IterationSession, returns FixCandidates.
|
||
|
||
Tests (8): attempt appended to session, observation raw_text captured,
|
||
gap metric_type is test_failure_count, gap converged when all tests pass,
|
||
session converges on zero failures, fix candidates returned on failures,
|
||
attempt outcome=success on zero failures, attempt outcome=failure on any failure.
|
||
|
||
### Step 1375: `whetstone_parse_test_output` MCP tool (8 tests)
|
||
|
||
Input: `{ "output": string, "runner": string? }`
|
||
Output: `{ "failures": TestFailureRecord[], "pass_count": int, "fail_count": int }`
|
||
|
||
Tests (8): tool registered, fail_count correct, pass_count correct, runner
|
||
field respected, empty output returns empty failures, malformed input handled,
|
||
classification applied, deterministic output.
|
||
|
||
### Step 1376: `whetstone_suggest_test_fix` MCP tool (8 tests)
|
||
|
||
Input: `{ "session_id": string?, "test_output": string, "runner": string? }`
|
||
Output: `{ "candidates": FixCandidate[], "fail_count": int }`
|
||
|
||
Tests (8): tool registered, candidates returned, confidence present,
|
||
mutation field present, manual fallback always present, missing AST graceful,
|
||
candidates sorted by confidence, deterministic output.
|
||
|
||
### Step 1377: `whetstone_run_test_iteration` MCP tool (8 tests)
|
||
|
||
Input: `{ "session_id": string, "test_command": string, "workspace": string }`
|
||
Output: `{ "attempt_id": string, "session_status": string,
|
||
"fail_count": int, "candidates": FixCandidate[] }`
|
||
|
||
Tests (8): tool registered, attempt_id returned, session_status updated,
|
||
fail_count correct, candidates on failure, session converged on all-pass,
|
||
timeout enforced (default 120s), deterministic output shape.
|
||
|
||
### Step 1378: Sprint 114 integration summary + regression (8 tests)
|
||
|
||
Create `editor/src/Sprint114IntegrationSummary.h`.
|
||
|
||
End-to-end: start session → run_test_iteration (failing tests) → verify
|
||
attempt recorded + candidates returned → apply top candidate → run again →
|
||
verify fail count reduced.
|
||
|
||
Tests (8): headers constructable, tools registered, end-to-end flow works,
|
||
gap model improves after fix, session transitions correctly, no header exceeds
|
||
600 lines, deterministic snapshots, full prior regression passes.
|
||
|
||
---
|
||
|
||
## Architecture Gate
|
||
|
||
- Assertion parsing is heuristic — expected/actual extraction may be empty
|
||
for non-standard formats; that is acceptable
|
||
- Fix candidates are suggestions, not guaranteed correct fixes
|
||
- Manual fallback always generated so the iteration loop is never stuck
|
||
- Max 600 lines per header
|