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 34 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 plans 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 detectioncolgrep 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.