Files
my-pi/pi-permission-system/docs/retro/0321-continue-shared-test-fixture-extraction.md
T

10 KiB
Raw Blame History

issue, issue_title
issue issue_title
321 Continue shared test-fixture extraction for the largest clone families

Retro: #321 — Continue shared test-fixture extraction for the largest clone families

Stage: Planning (2026-06-03T21:30:00Z)

Session summary

Produced a numbered build plan (docs/plans/0321-continue-shared-test-fixture-extraction.md) for migrating the four largest remaining test clone families onto the shared test/helpers/ fixtures. Grounded the four families in a live fallow dupes run (133 clone groups, 7.6%) and confirmed by reading each file that all four already import the shared fixtures — the remaining clones are duplicate local factory definitions plus repeated override expressions, not unmigrated files. This is a pure test refactor (no src/ changes), so the next stage is /build-plan, with migrate → full-suite-green → commit cycles.

Observations

  • One ask_user design decision: how aggressively to extend the shared fixture API. User chose both — consolidate the duplicate factories AND add convenience shortcuts (makeSurfaceCheck, makeBashCommandCheck, makeDenialDescriptor, makePathDispatchResolver, a makeGateRunner resolveResult option, and a makeHandler tools shortcut) to hit the sub-6% target.
  • Key correctness risk identified: the local makeSession in external-directory-integration.test.ts diverges from the shared one in two defaults (getInfrastructureReadDirs → [] vs ["/test/agent", …]; checkPermission → deny vs neutral allow). Analysis shows both differences are moot for that file's tests (explicit checkPermission everywhere; infra dirs never intersect test paths), but the full-suite green gate after the ext-dir step is the verification.
  • Applied the code-design "structural reasons before extracting duplication" heuristic to fence off genuine per-test intent that must stay inline: the per-agent agentAwareCheck, toolName-alias event literals, multi-condition path dispatch, and the bash regex/pattern values.
  • Discovered makeTcc() already defaults input to { command: "cat .env" }, so many bash-path.test.ts clones collapse to a bare makeTcc() with no new helper.
  • The production refactors this step is "best sequenced after" ([#314], [#317]–[#320]) have all landed — the shared fixtures already import their outputs (PermissionResolver, GateRunner, the two pipelines, GateDecisionReporter), so no soft dependency blocks the work.
  • Carried forward the #288 recurring friction as an explicit per-step instruction: grep each removed symbol before committing, because a stale value import passes tsc and the lint exit code but is a biome warning.
  • Scope guard: external-directory-session-dedup.test.ts shares the local-makeSession clone family but is the fifth file, outside the issue's named four; flagged as a conditional follow-up issue if the sub-6% target is missed, not scope creep here.

Stage: Implementation — Build (2026-06-03T11:40:00Z)

Session summary

Completed all 5 build steps from the plan: runner gate migration (Step 1), bash-path gate migration (Step 2), tool-call handler migration (Step 3), external-directory integration migration (Step 4), and docs refresh (Step 5). Test count held steady at 86 files / 1834 tests throughout — pure refactor, no assertions changed. Pre-completion reviewer returned PASS.

Observations

  • Step 1 Fixed a TS2783 (state specified twice) in the makeSurfaceCheck implementation in handler-fixtures.ts; resolved by removing the redundant explicit state: base.state before the spread, letting ...base cover it. One extra check+fix cycle.
  • Step 2 A pre-commit eslint hook reformatted gate-fixtures.ts on the first commit attempt (exit 1); re-staged the auto-fixed file and committed cleanly.
  • Steps 3–4 makeSurfaceCheck and makeExtDirCheck (a local thin wrapper in external-directory-integration.test.ts) replaced the surface-dispatch boilerplate cleanly; no assertion changes needed. The shared makeSession default getInfrastructureReadDirs ([\u201c/test/agent\u201d, ...]) did not intersect any ext-dir test path, confirming the planning analysis.
  • Target miss: duplication landed at 6.6% (122 clone groups), not under 6%. The remaining gap is the external-directory-session-dedup.test.ts family (local makeSession/makeToolRegistry clones across ext-dir + session-dedup + handler-fixtures), which was out of the four-file scope. A follow-up issue should be filed per the plan’s Open Questions.
  • No stale imports or GateDescriptor/makeCheckPermission/makeDenialContextDescriptor leaks found at any step.
  • Reviewer verdict: PASS — all deterministic checks green, new helpers documented in SKILL.md, architecture roadmap updated. [#314]: https://github.com/gotgenes/pi-packages/issues/314 [#317]: https://github.com/gotgenes/pi-packages/issues/317 [#320]: https://github.com/gotgenes/pi-packages/issues/320

Stage: Final Retrospective (2026-06-03T22:30:00Z)

Session summary

Single-day execution of the full lifecycle (plan → build → ship) for the four-family test-fixture extraction. All 5 build steps landed green with the suite holding at 86 files / 1834 tests, duplication dropped 7.6% → 6.6% (clone groups 133 → 122), and the pre-completion reviewer returned PASS. No release-please PR was opened because the change is entirely test:/docs: commits; the issue closed cleanly with no version bump.

Observations

What went well

  1. The #288 recurring friction — stale imports after deleting local factory definitions — was carried forward from the prior retro into the [#321] plan as an explicit per-step instruction (“grep each removed symbol before committing”). The build session then had zero stale-import slips: every deletion step ran a verifying grep (msgs 38, 47, 62) and found only legitimate survivors (makeResolver) or doc-comment references. This is a prior retro observation closing the loop — a documented friction pattern eliminated by a planning adjustment.
  2. The upfront ask_user in planning (one decision: “both” — consolidate factories AND add convenience shortcuts) produced zero design churn across all 5 build steps; every helper the plan named was used as specified.
  3. Incremental verification was clean: pnpm run check + vitest run ran after every build step (msgs 37, 46, 53/55, 61, 74), so each commit left the suite green with no broken-baseline commits.
  4. The code-design “structural reasons before extracting duplication” heuristic was applied at plan time to fence off genuine per-test intent (per-agent agentAwareCheck, toolName-alias events, multi-condition path dispatch), so no shared helper became a discriminator-laden leaky abstraction.

What caused friction (agent side)

  1. other (tooling) — a fallow dupes --json attempt during planning (msg 13) returned exit 2 with an empty file; fallow also truncates its plain-text output to the top 10 clone groups, so a follow-up tee to capture the full list also came up short (msg 15, error). The agent recovered by reading the four target files directly instead of relying on fallow's per-file clone breakdown. Impact: ~3 extra exploratory tool calls in planning; no rework, and the direct reads were the higher-fidelity path anyway.
  2. other (mechanical) — a TS2783 (state specified more than once) in the new makeSurfaceCheck (msg 53): the explicit state: base.state was redundant with the trailing ...base spread. Caught immediately by the post-step pnpm run check, fixed in one edit (msg 54). Impact: 1 extra check+fix cycle (~2 tool calls); no rework beyond the single line.
  3. other (tooling) — the first commit of step 2 (msg 48) failed because the pre-commit eslint hook reformatted gate-fixtures.ts (import sort); re-staging the auto-fixed file and re-committing succeeded (msg 50). Impact: 1 extra add+commit cycle; no rework.

What caused friction (user side)

  1. None substantive. The two Continue. nudges in the build session (msgs 42, 45) were mechanical pacing prompts, not redirections — the work was on-track (mid-step-2 migration) at each.

Estimation gap (not friction)

  1. The plan's stated target was duplication < 6%; the realized figure was 6.6%. The gap was foreseen in planning (external-directory-session-dedup.test.ts was explicitly scoped out as a fifth family) and handled correctly at build time — the architecture roadmap records the realized 6.6%, and the residual session-dedup family is flagged for a follow-up issue. No correction needed; this is an accurate-estimate-with-documented-shortfall, not a miss.

Diagnostic details

  • Model-performance correlation — no mismatches. Planning + retro ran on claude-opus-4-8 (judgment-heavy: design decision, plan synthesis, cross-stage retro), the build on claude-sonnet-4-6 (mechanical migration with type-checking), the pre-completion reviewer subagent on anthropic/claude-sonnet-4-6 (judgment-heavy review), and shipping on opencode-go/deepseek-v4-flash (deterministic checklist). Each model matched its task complexity; the cheap flash model on the mechanical ship checklist is appropriate cost optimization.
  • Escalation-delay tracking — no rabbit-hole friction points; the two mechanical issues (TS2783, eslint hook) each resolved in a single cycle, well under the 5-call threshold.
  • Unused-tool detection — colgrep was not used despite the planning prompt recommending it, but the agent knew exact symbol names (makeSession, makeCheckPermission, etc.), so grep and direct file reads were the correct lower-latency choice; no missing-context friction resulted.
  • Feedback-loop gap analysis — no gaps. Verification ran incrementally after each of the 5 build steps, not just at the end.

Changes made

  1. Appended this Final Retrospective stage entry to packages/pi-permission-system/docs/retro/0321-continue-shared-test-fixture-extraction.md. No prompt or AGENTS.md changes — the user chose retro-file-only; the single tooling candidate (a fallow dupes truncation/--json note) was a single self-recovered instance below the bar for a new rule, recorded here only.