Files
my-pi/pi-permission-system/docs/retro/0336-inject-session-logger.md

98 lines
8.5 KiB
Markdown
Raw Permalink 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: 336
issue_title: "Make the logger injectable; drop createSessionLogger(runtime)"
---
# Retro: #336 — Make the logger injectable; drop `createSessionLogger(runtime)`
## Stage: Planning (2026-06-06T21:30:54Z)
### Session summary
Produced a numbered implementation plan for Phase 4 Step 3 (Track B): repurpose `createSessionLogger` to take narrow deps (`globalLogsDir` + a `getConfig` thunk + a `notify` sink) instead of the whole `ExtensionRuntime`, fold the JSONL-writer composition + warning-dedup + `warn` into that one factory, expose the built logger as `runtime.logger`, and drop the five `.bind(runtime)` adapters in `index.ts`.
The plan is a two-step lift-and-shift (Step 1 adds `runtime.logger` while keeping the old runtime methods as thin delegators; Step 2 removes the old methods + the `.bind` adapters), keeping the repo green between commits.
### Observations
- The logger ↔ `ConfigStore` cycle (logger needs config toggle; `ConfigStore` writes through the logger) is broken cleanly with a lazy `getConfig: () => configStore.current()` thunk — the logger object is fully built before `ConfigStore`; only the config *value* is read lazily.
This replaces the existing stub-then-reassign forward reference, not just relocates it.
- Naming is intentionally left split: consumer deps keep `writeReviewLog` / `writeDebugLog` field names mapped to `logger.review` / `logger.debug` values.
Unifying the names is [#338]'s job — flagged as an Open Question.
- Logger construction stays in `createExtensionRuntime()` (not `index.ts`) because `ConfigStore` is built there until [#337] dissolves the runtime; moving it now would pre-empt and re-do [#337].
- The architecture doc's Step 3 target list (`session-logger.ts`, `logging.ts`, `index.ts`) omits `runtime.ts`, but removing the `writeDebugLog` / `writeReviewLog` fields and the inline logger construction unavoidably edits `runtime.ts` — noted in the plan.
`logging.ts` itself needs no edit (it already takes narrow options and has no runtime reference); it is merely composed by the new `createSessionLogger`.
- `SessionLogger` interface (`debug` / `review` / `warn`) is unchanged, so `decision-reporter.ts`, `handlers/lifecycle.ts` (sole `warn` caller), `permission-session.ts`, and the test fixtures need no edits — keeps blast radius small.
- Grep confirms `runtime.writeDebugLog` / `writeReviewLog` live only in `runtime.ts`, `index.ts`, `test/runtime.test.ts`; `createSessionLogger` only in `index.ts`, `session-logger.ts`, `test/session-logger.test.ts`.
- `[#335]` (ConfigStore) is complete and provides the `RuntimeContextRef` seam reused by the notify sink and the `ConfigReader` for the debug toggle.
[#337]: https://github.com/gotgenes/pi-packages/issues/337
[#338]: https://github.com/gotgenes/pi-packages/issues/338
## Stage: Implementation — TDD (2026-06-06T22:00:00Z)
### Session summary
Completed two TDD cycles (Step 1: inject `SessionLoggerDeps` + expose `runtime.logger`; Step 2: remove `writeDebugLog`/`writeReviewLog` from the interface and replace `.bind(runtime)` adapters).
Test count moved from 1840 → 1837 (3 net: 4 delegation tests removed, 11 new `session-logger.test.ts` tests added, some overlap with rewritten tests).
Architecture doc updated with Step 3 `✓ complete` mark and revised `session-logger.ts` description.
### Observations
- Two ESLint issues in Step 1: `prefer-const` on `let configStore` (fixed by initializing to `null as unknown as ConfigStore`) and `unbound-method` on bare `logger.debug`/`logger.review` references in the `ConfigStoreLogger` (fixed with arrow wrappers).
- Step 2 had the same `unbound-method` issue on the five `index.ts` adapter sites; same arrow-wrapper fix applied.
The type-level fix (`this: void` on `SessionLogger` interface) was blocked by `@typescript-eslint/no-invalid-void-type` which does not allow `allowAsThisParameterType` in this project's config.
Five unnecessary wrapper closures remain as an ESLint-compatibility workaround.
- Pre-completion reviewer: WARN (two observations, neither blocking).
### Reviewer warnings
- **WARN** — `src/index.ts` lines 4952, 61, 109: five `(event, details) => runtime.logger.review(event, details)` closures are unnecessary (bare references type-check under contravariance), but `@typescript-eslint/unbound-method` blocks bare references without a project-wide ESLint config change.
Deferred to [#338] which already owns the consumer deps bag cleanup.
- **WARN** — `src/runtime.ts` notify sink (`runtimeContext?.ui.notify`) is a transitional LoD seam; acknowledged in the plan, deferred to [#337].
## Stage: Final Retrospective (2026-06-06T22:18:35Z)
### Session summary
One continuous session carried #336 through plan → TDD → ship → retro across three models (`opus-4-8` planning, `sonnet-4-6` TDD, `deepseek-v4-flash` ship).
The injectable-`SessionLogger` refactor landed in two clean lift-and-shift commits plus a docs commit, CI passed, and the issue closed with no release (all `refactor:`/`docs:` commits).
The only notable friction was a mid-TDD path-confusion correction from the user and a one-round-trip ESLint rule conflict.
### Observations
#### What went well
- The lazy `getConfig: () => configStore.current()` thunk broke the logger ↔ `ConfigStore` construction cycle on the first try — no stub-then-reassign, no test flakiness.
The plan predicted this exactly; implementation matched.
- Two-step lift-and-shift kept the repo green between commits: Step 1 added `runtime.logger` with the old methods as thin delegators, Step 2 removed them.
`pnpm run check` / `lint` / full suite ran after every step, not just at the end — no feedback-loop gap.
- Pre-completion reviewer (fresh-context subagent) correctly flagged the five gratuitous wrapper closures as a non-blocking WARN, and the agent verified the contravariance claim with a throwaway `tsc` check before accepting it.
#### What caused friction (agent side)
- `instruction-violation` (user-caught) — during TDD the agent ran package-scoped commands as `cd packages/pi-permission-system && grep/sed/pnpm run …`, and in one case mixed that with a bare `grep "init-declar" ../../eslint.config.js` (no `cd`), which resolved *outside* the repo from the CWD and returned a misleading "not found".
`AGENTS.md` already says to run package scripts from the root via `pnpm --filter` / `pnpm -C`, not `cd packages/<pkg> && …`.
Impact: user intervened ("I don't trust that you are looking at the correct paths") at message 37; the agent re-verified with `pwd` + absolute paths.
No rework to committed code (the config facts happened to be correct), but a real trust dip and one wrong-path read.
- `rabbit-hole` (minor) — the `unbound-method` ESLint error on bare `logger.review` references was first "fixed" by annotating the `SessionLogger` interface with `this: void` (the rule's own suggestion), which `@typescript-eslint/no-invalid-void-type` then rejected.
Impact: ~5 tool calls and one revert before settling on arrow wrappers; self-resolved, no user involvement.
#### What caused friction (user side)
- The path-confusion correction at message 37 was a terse distrust signal rather than a pointer to the specific bad command.
A redirect naming the `cd`-into-subdir + `../../` mix (or the `AGENTS.md` root-run rule) would have shortened the recovery.
Framed as opportunity, not criticism — the signal was correct and well-timed.
### Diagnostic details
- **Model-performance correlation** — planning on `opus-4-8` (judgment-heavy design) and TDD on `sonnet-4-6` (implementation) were well-matched.
Ship ran on `deepseek-v4-flash`, a low-cost model, and handled a non-trivial judgment (detecting the `#335 → #338` stacked sequence, asking the user, diagnosing why no release-please PR existed) correctly — no quality mismatch in outcome.
The pre-completion-reviewer ran as a separate subagent (its model is set by agent frontmatter, not visible in the parent transcript).
- **Escalation-delay tracking** — the `unbound-method` / `this: void` / `no-invalid-void-type` sequence spanned ~5 consecutive tool calls (messages 6371) with one wrong turn; below the "dispatch a subagent" threshold and self-resolved.
- **Feedback-loop gap analysis** — none; verification ran incrementally after each change, plus full suite + `pnpm fallow dead-code` before the Step 2 commit.
### Changes made
1. `AGENTS.md` — added the `unbound-method` / `no-invalid-void-type` conflict (rule + arrow-wrapper fix) to the existing "Biome / ESLint linter conflicts" section.
2. `packages/pi-permission-system/docs/retro/0336-inject-session-logger.md` — appended this Final Retrospective stage entry.