9.1 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 335 | Extract a ConfigStore from the runtime free-functions |
Retro: #335 — Extract a ConfigStore from the runtime free-functions
Stage: Planning (2026-06-05T01:50:00Z)
Session summary
Produced the numbered implementation plan for Phase 4 Step 2 — extracting a ConfigStore class that owns config + lastConfigWarning and converts the three (runtime, …) config free functions into methods.
Predecessor #334 (inject a single PermissionManager) is already merged; this is the first session on #335 (no prior retro).
The plan is six behavior-preserving TDD cycles using a lift-and-shift migration (introduce the store, back the runtime with it via a temporary get config() getter, migrate consumers one at a time, then delete the free functions).
Observations
- The one genuine design fork — how the store obtains the runtime context that
refresh/logResolvedPathsneed — was settled by the roadmap, not byask_user.docs/architecture/architecture.mddeliberately scopes the store toconfig+lastConfigWarning(context unification ontoPermissionSessionis Step 4 / #337), so owningruntimeContextin the store was rejected. The plan injects a transitionalRuntimeContextRef(get/set) backed by the still-runtime-ownedruntimeContextfield; it dissolves in #337. - Tension between the issue's "the 4×
() => runtime.configclosures … are gone" and the roadmap reserving theindex.tsdeps-bag collapse for Step 5 (#338). Resolved by removing thePermissionSession,PermissionPrompter, and command() => runtime.configclosures here (those consumers hold the store / aConfigReader), while the loggergetConfigadapter is redirected toconfigStore.current()and fully retired in Step 3 (#336), and the forwardingshouldAutoApproveadapter survives to Step 5. Documented as an explicit scoping decision in the plan's Non-Goals / Open Questions. - The logger ↔ config temporal coupling (the store must be built before the logger whose sink the store defers to) is preserved verbatim — it is the existing deferred-binding pattern and Step 3 (#336) removes it. Avoided pulling that fix forward into this Step.
createSessionLogger(runtime)readsruntime.writeDebugLog/writeReviewLog/runtimeContextbut notruntime.config, so removing theconfigfield does not touch it — confirmed by grep before finalizing the module list.- Lift-and-shift chosen because the alternative (remove
config+ the free functions in one commit) would force every consumer and test into a single oversized commit; the temporaryget config()getter keepsindex.tscompiling across the four consumer-migration steps.
Stage: Implementation — TDD (2026-06-05T22:25:00Z)
Session summary
All 6 TDD cycles completed across config-store.ts (new), runtime.ts, permission-session.ts, permission-prompter.ts, config-modal.ts, and index.ts.
Test count went from 1831 (86 files) to 1840 (87 files): +22 new config-store.test.ts tests, -13 deleted refreshExtensionConfig tests from runtime.test.ts.
Pre-completion reviewer returned PASS with no findings requiring action.
Observations
- Step 2 (
runtime.ts) requiredObject.definePropertyfor the transitionalget config()getter bridge; the plain-object-literal factory pattern can't have getters inline. Theas unknown as ExtensionRuntimecast is needed becauseObject.definePropertyreturns the pre-cast type. The bridge was removed cleanly in Step 6 when the last consumer (canResolveAskPermissionRequestinindex.ts) migrated. @deprecatedJSDoc tags on the Step-2 delegator functions triggered@typescript-eslint/no-deprecatedon every call site inindex.ts. Replaced with prose comments per the AGENTS.md lift-and-shift rule ("do not mark it@deprecated").ConfigStore.savewas flagged as dead code by fallow because fallow cannot trace calls through theCommandConfigStoreinterface. Suppressed with// fallow-ignore-next-line unused-class-memberplus an explanatory prose comment above it.- Step 5 (
config-modal.ts) needed two passes: the batch edit for the interface + import was rejected (edit[2] in the first call failed to match), but the body-method edits applied. Lesson: when one edit in a batch fails, the entire batch is rolled back — check match fidelity for every oldText before sending. - Architecture doc updated:
config-store.tsadded to the module-structure listing; Steps 1 and 2 marked✓ completein the Phase 4 roadmap. - Pre-completion reviewer verdict: PASS — all deterministic checks green, no structural findings,
fallowclean.
Stage: Final Retrospective (2026-06-05T10:56:41Z)
Session summary
Shipped #335 end-to-end: the TDD implementation (6 green cycles), pre-completion review (PASS), push, CI verification, issue close, and release of pi-permission-system-v10.3.0.
The refactor is behavior-preserving — config now has one owner (ConfigStore) and the () => runtime.config closures plus three (runtime, …) free functions are gone.
Friction was concentrated in mechanical Edit-tool usage (failed batches, a clumsy large-block deletion), not in design or strategy.
Observations
What went well
- Narrow-interface segregation (
ConfigReader/SessionConfigStore/CommandConfigStore) let every test double satisfy the type structurally — zeroas unknown ascasts in the new fixtures. The pre-completion reviewer called this out explicitly; it validates thecode-design"inject a narrow interface, not the concrete class" rule. - Incremental verification:
check+vitest+lintran after each of the 6 TDD steps, so the@deprecatedlint error and the batch-application gaps surfaced inside the step that caused them rather than at the end. - The
ask_userbatching gate at ship time worked cleanly — surfaced that #335 is 1 of 9 steps and let the user choose release-now vs. batch instead of assuming.
What caused friction (agent side)
wrong-abstraction— deleting the ~145-linerefreshExtensionConfigdescribeblock fromruntime.test.tswas attempted with incremental surgicalEdits (rename toPLACEHOLDER_DELETED, adddescribe.skip, addeslint-disable-linecomments) before abandoning that for a one-shotpython3truncation at a marker. Impact: ~5 fumbling tool calls before switching to the bulk approach; no rework to shipped code.other(tool batch atomicity) — in both Step 4 (permission-prompter.test.ts) and Step 5 (config-modal.ts) a large multi-editEditcall was rejected because oneoldText(edit[2]) failed to match; the whole batch rolled back, but follow-up calls only re-applied a subset, so the import/interface edits silently never landed and a laterpnpm run checkfailed. Impact: 2 extra check-fix cycles (one per step).instruction-violation(self-identified) — marked the Step-2 delegator functions@deprecateddespite thetestingskill's explicit "do not mark it@deprecated" rule;@typescript-eslint/no-deprecatedcaught it at the Step-2 lint gate. Impact: one extra edit cycle (3 JSDoc→prose edits); rule already exists and lint enforced it, so the safety net held.missing-context(user-caught) — the Phase 4 roadmap step-completion markers inarchitecture.mdwere not updated when #335 shipped (nor was #334's when it shipped earlier); the user had to ask "mark step 1 as complete." Impact: one extra user round-trip + a follow-up commit (bc0fd5f5).
What caused friction (user side)
- None material. The one user intervention (marking roadmap steps complete) was mechanical oversight of a doc-bookkeeping gap, not strategic redirection — the kind of thing the ship flow should catch automatically.
Diagnostic details
- Escalation-delay tracking — the
runtime.test.tsblock deletion ran ~5 consecutiveEdit/Readcalls on the same goal before switching tactics; just under the 5-call dispatch threshold, but a single boundary-to-boundary replacement (or thepython3cut) should have been the first move. - Feedback-loop gap analysis — no gap: verification was incremental after every step, and
fallow dead-coderan from the repo root at Step 6 and again at ship. This is why the@deprecatedand batch-application slips cost a cycle each instead of compounding. - Model-performance correlation — the only subagent dispatch was the
pre-completion-revieweron its default model for judgment-heavy code review; appropriate, returned a thorough PASS. No mismatch.
Changes made
.pi/skills/package-pi-permission-system/SKILL.md— added a note to mark a roadmap step✓ completeinarchitecture.mdas part of the shipping change (addresses the user-caught completion-marking gap).AGENTS.md— added an### Edit tool batchessubsection under Workflow: a rejected multi-editEditcall applies nothing, so re-apply every intended edit and runpnpm run checkafter a rejection (addresses the Step 4 / Step 5 batch-atomicity friction).packages/pi-permission-system/docs/retro/0335-extract-config-store.md— this Final Retrospective stage entry.