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

109 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
issue: 321
issue_title: "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.
[#288]: https://github.com/gotgenes/pi-packages/issues/288
## 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 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.