Context
Verified findings (2026-07-26):
-
Repeat findings are patched, never questioned. vipune accumulated 4 diverged copies of its SQL filter builder, 3 row mappers (two missing a validation the third performs), and 3 limit validations with 3 error types (all counts verified in /Users/janni/projects/vipune). A lens fix-loop patches each instance; the missing SEAM is never surfaced. Today nothing escalates same-shaped findings to a design-level question before the round-3 cap.
-
BONUS BUG — step-back is unreachable. Verification found that NOTHING in the driver ever emits nextStep: "step-back": the 3-round lens cap routes to handoff (nextStep, work-driver.ts :473-475), and no other path emits it. runStepBack (:2735) — the SDD spec-gap analysis machinery from PR12 — is dead code in the live pipeline. This issue gives it its first real trigger; expect first-live-exercise bugs and test accordingly.
-
Invariants die without record. When a constraint is removed (type widening — see the invariant-scan issue), the fact that it was load-bearing vanishes with it. The memory substrate (vipune) exists but is not triggered by the events that matter.
This work must ship as its own separate PR, independent of any other open issue. It reuses extension/src/invariant-scan.ts from the verify-full/widening-scan issue, so land that one first.
What to build
A. detectRepeatSeam(findings) (pure, ~35 LOC). Over one lens round's findings JSON: cluster by (lens, normalised title — lowercase, strip file-specific tokens); a cluster spanning ≥3 DISTINCT file paths = a missing-seam signal, not N defects.
B. Round-1 escalation routing. In runLens's ISSUES_FOUND handling: when detectRepeatSeam fires on round 1, instead of routing to lens-fix, emit step-back-triggered + cap-hit { cap: "repeat-finding-seam", nextStep: "step-back" } so the existing nextStep cap routing sends the cycle to runStepBack (the @explore SDD analysis → handoff with the proposed revision). Add the "repeat-finding-seam" cap literal to the workflow-state union (additive) + an explainCap branch explaining the operator-facing WHY ("same finding shape across N files is a missing seam — patching each instance would entrench the duplication; explore analysed which spec element under-specifies the shared behaviour").
- Round ≥2 behaviour unchanged (fix loop continues; the cap still fires only at round 3).
C. Invariant-removal guard memories. When the widening scan (invariant-scan.ts) fires during lens prep: write ONE vipune guard memory per (file, symbol) via an injectable vipuneWriteFn (default: shell out to vipune add … --memory-type guard; follow the injection-seam pattern of verifyExecFn): "constraint → removed in issue #N; verify what now guarantees the old invariant before writing code that exploits the widened type." Emit a plumb-report noting the write. Dedupe: no duplicate write for the same (file, symbol) within a cycle.
- One prompt line in
inlineExplorePrompt: instruct explore to vipune search for guard memories touching the files it is about to analyse.
Env: PI_ENSEMBLE_SEAM_ESCALATION=0, PI_ENSEMBLE_INVARIANT_MEMORY=0 (default ON).
Acceptance criteria
Out of scope
- Changing the round-3 cap or wall-clock caps.
- Escalation triggers other than the ≥3-distinct-files rule (keep v1 deterministic and singular).
- Automatic dedup/refactoring of the found seam (the escalation SURFACES it; the operator/spec revision decides).
Test plan
Offline: pure-fn fixtures + full-driver routing tests via injected lensReviewFn/dispatchFn/vipuneWriteFn in test-work-driver.ts. Live (nessie): seed a diff duplicating one helper across 3 files, run /work N, confirm round-1 step-back handoff names the seam instead of three per-file findings surviving to merge.
Addendum (2026-07-27 nessie post-mortem) — upstream evidence: plan-step under-decomposition
Fresh evidence for the seam problem this issue targets, from nessie #604: an 8.6-second plan dispatch collapsed the issue's 6 enumerated findings into ONE workstream declared with ONE path (src/cron/wiki_state.rs). The developer then sprawled to 11 files (26 edits on out-of-scope sweep_tools.rs alone), thrashed through ~17 consecutive failed cargo builds, and burned 10.5M tokens / 37 min before dying on a provider stall. The tree never compiled.
While the scope-fanout gate (#285) catches the sprawl downstream, the UPSTREAM failure is plan quality on multi-finding issues. In scope for this issue (or its immediate follow-up):
- When the issue body enumerates N ≥ 3 discrete findings (numbered/checkbox list) and the plan returns 1 workstream, the plan prompt should be required to either (a) map each finding to a workstream explicitly, or (b) declare which findings are deferred out of this cycle. A plan that silently absorbs 6 findings into 1 path is the same missing-seam smell this issue's ≥3-files trigger detects at review time — catching it at plan time is one prompt-layer instruction + one deterministic count check.
Additional acceptance criteria
Addendum (2026-07-27) — interaction with the parallel-work series (#287–#290)
The plan-quality addendum (lines 48-60) is absorbed and superseded by the plan-decomposition issue (PR-C), which upgrades "warn, not halt" to one corrective re-dispatch and satisfies both addendum acceptance criteria — strike them here. The main body (repeat-finding seam escalation, dead step-back) is untouched and remains this issue's scope.
Context
Verified findings (2026-07-26):
Repeat findings are patched, never questioned. vipune accumulated 4 diverged copies of its SQL filter builder, 3 row mappers (two missing a validation the third performs), and 3 limit validations with 3 error types (all counts verified in /Users/janni/projects/vipune). A lens fix-loop patches each instance; the missing SEAM is never surfaced. Today nothing escalates same-shaped findings to a design-level question before the round-3 cap.
BONUS BUG — step-back is unreachable. Verification found that NOTHING in the driver ever emits
nextStep: "step-back": the 3-round lens cap routes tohandoff(nextStep, work-driver.ts:473-475), and no other path emits it.:2735) — the SDD spec-gap analysis machinery from PR12 — is dead code in the live pipeline. This issue gives it its first real trigger; expect first-live-exercise bugs and test accordingly.runStepBack(Invariants die without record. When a constraint is removed (type widening — see the invariant-scan issue), the fact that it was load-bearing vanishes with it. The memory substrate (vipune) exists but is not triggered by the events that matter.
This work must ship as its own separate PR, independent of any other open issue. It reuses
extension/src/invariant-scan.tsfrom the verify-full/widening-scan issue, so land that one first.What to build
A.
detectRepeatSeam(findings)(pure, ~35 LOC). Over one lens round's findings JSON: cluster by (lens, normalised title — lowercase, strip file-specific tokens); a cluster spanning ≥3 DISTINCT file paths = a missing-seam signal, not N defects.B. Round-1 escalation routing. In
runLens's ISSUES_FOUND handling: whendetectRepeatSeamfires on round 1, instead of routing to lens-fix, emitstep-back-triggered+cap-hit { cap: "repeat-finding-seam", nextStep: "step-back" }so the existing nextStep cap routing sends the cycle torunStepBack(the @explore SDD analysis → handoff with the proposed revision). Add the"repeat-finding-seam"cap literal to the workflow-state union (additive) + anexplainCapbranch explaining the operator-facing WHY ("same finding shape across N files is a missing seam — patching each instance would entrench the duplication; explore analysed which spec element under-specifies the shared behaviour").C. Invariant-removal guard memories. When the widening scan (invariant-scan.ts) fires during lens prep: write ONE vipune guard memory per (file, symbol) via an injectable
vipuneWriteFn(default: shell out tovipune add … --memory-type guard; follow the injection-seam pattern ofverifyExecFn): "constraint → removed in issue #N; verify what now guarantees the old invariant before writing code that exploits the widened type." Emit a plumb-report noting the write. Dedupe: no duplicate write for the same (file, symbol) within a cycle.inlineExplorePrompt: instruct explore tovipune searchfor guard memories touching the files it is about to analyse.Env:
PI_ENSEMBLE_SEAM_ESCALATION=0,PI_ENSEMBLE_INVARIANT_MEMORY=0(default ON).Acceptance criteria
detectRepeatSeamunit fixtures: 3 same-lens/same-title findings across 3 files → fires; across 2 files → does not; 3 different titles → does not; title normalisation tolerates file-specific tokens.lensReviewFnreturning a vipune-V4-shaped duplication cluster): round-1 cycle routes to step-back —step-back-triggered+cap-hit repeat-finding-seamevents present,runStepBack's @explore dispatch fires (injected dispatchFn), handoff carries the SDD analysis. THIS IS STEP-BACK'S FIRST LIVE PATH — assert the full routing chain, not just the events.vipuneWriteFn; content includes before/after and the issue number; plumb-report present; second hit same symbol → no duplicate write.explainCap("repeat-finding-seam", …)produces the operator-facing explanation.Out of scope
Test plan
Offline: pure-fn fixtures + full-driver routing tests via injected
lensReviewFn/dispatchFn/vipuneWriteFnin test-work-driver.ts. Live (nessie): seed a diff duplicating one helper across 3 files, run/work N, confirm round-1 step-back handoff names the seam instead of three per-file findings surviving to merge.Addendum (2026-07-27 nessie post-mortem) — upstream evidence: plan-step under-decomposition
Fresh evidence for the seam problem this issue targets, from nessie #604: an 8.6-second plan dispatch collapsed the issue's 6 enumerated findings into ONE workstream declared with ONE path (
src/cron/wiki_state.rs). The developer then sprawled to 11 files (26 edits on out-of-scope sweep_tools.rs alone), thrashed through ~17 consecutive failed cargo builds, and burned 10.5M tokens / 37 min before dying on a provider stall. The tree never compiled.While the scope-fanout gate (#285) catches the sprawl downstream, the UPSTREAM failure is plan quality on multi-finding issues. In scope for this issue (or its immediate follow-up):
Additional acceptance criteria
Addendum (2026-07-27) — interaction with the parallel-work series (#287–#290)
The plan-quality addendum (lines 48-60) is absorbed and superseded by the plan-decomposition issue (PR-C), which upgrades "warn, not halt" to one corrective re-dispatch and satisfies both addendum acceptance criteria — strike them here. The main body (repeat-finding seam escalation, dead step-back) is untouched and remains this issue's scope.