4.0 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 107 | refactor: break handleToolCall into per-gate functions |
Retro: #107 — break handleToolCall into per-gate functions
Final Retrospective (2026-05-07T00:30:00Z)
Session summary
Extracted four permission gates from a ~600-line handleToolCall into src/handlers/gates/, added 44 per-gate unit tests, removed 18 redundant integration tests, and wired the orchestrator as a ~30-line chain.
Also added an npm→pnpm shim via mise.toml after discovering the project had no enforcement of its declared package manager.
Released as v5.4.0.
Observations
What went well
- The TDD cycle was clean: red→green→commit for each gate, with existing integration tests providing a safety net during the final wiring step. All 1165 (later 1147) tests passed at every checkpoint.
- The user's "why do we need deep mocking?"
question surfaced the real design issue (
ExtensionRuntimeas a god object, #111) rather than letting us paper over it withRecord<string, any>. This is a good example of asking "why" to get past the surface symptom. - The npm shim pass-through for
npm rootwas a pragmatic solution that let us enforce pnpm without breaking our own startup path.
What caused friction (agent side)
-
instruction-violation— Usednpm run build,npx vitest run, andnpm run lint:allthroughout all 9 TDD steps despite the project using pnpm exclusively (pnpm-lock.yaml,"packageManager"inpackage.json). User-caught after all steps were complete. Impact: no functional breakage (scripts are runner-agnostic), but undermines the pnpm enforcement the session itself added. Root cause:AGENTS.mdand all prompt templates (tdd-plan.md,build-plan.md) saidnpm/npx, and no rule said otherwise. -
instruction-violation— IntroducedRecord<string, any>in gate test factories to work around deep mock typing, violating the "avoidany" rule inAGENTS.md. User-caught. Impact: one extrastyle:commit (eeb9d20) to replace withRecord<string, unknown>. -
rabbit-hole— First attempt at the npm shim destroyed positional parameters withset -- $PATH, causingnpm root -gto silently return the wrong path (local instead of global). Self-identified on test. Impact: two revisions of the shim script before it worked correctly. -
missing-context— Tried to useisToolCallEventTypefrom the Pi SDK inside extracted gates by reconstructing a fake event object. The SDK checksevent.toolNamebut the reconstructed event usedevent.name. Self-identified during TDD red→green. Impact: minor — removed the SDK call in favor of a directtcc.toolNamecheck, which is simpler anyway. -
missing-context— Forgot[env]section header inmise.toml, causing_.pathto have no effect. User-caught after restarting Pi. Impact: one round-trip of "restart Pi → still broken → fix config → restart again."
What caused friction (user side)
- The
npmvspnpmissue could have been caught earlier if the project had established the pnpm rule inAGENTS.mdbefore this session. The user noticed it organically mid-session, which led to the productive shim work, but the TDD steps had already landed 9 commits usingnpm. - The user's sequential "why" questions (deep mocking →
ExtensionRuntime→ existing issues) were highly effective at reaching root cause. This pattern of redirecting from symptom to cause saved us from filing a narrow issue (#114) when the real target (#111) already existed.
Changes made
AGENTS.md— added pnpm-over-npm rule in § Code Style; fixednpm run build→pnpm run buildandnpx vitest run→pnpm vitest runin § Testing..pi/prompts/tdd-plan.md— replaced allnpx vitest run,npm run build,npm run lint:all,npm run lint:fixwithpnpmequivalents..pi/prompts/build-plan.md— same replacements..pi/prompts/README.md— replacednpmtest/lint script references withpnpm.