@gotgenes/pi-subagents 7.3.1 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.4.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.3.2...pi-subagents-v7.4.0) (2026-05-25)
9
+
10
+
11
+ ### Features
12
+
13
+ * add model attribution to formatAssistantMessage ([76f2ada](https://github.com/gotgenes/pi-packages/commit/76f2adaa6bad9d1a3b15a3ba208b3ab96e07ecd1))
14
+ * add model attribution to getAgentConversation ([c186c37](https://github.com/gotgenes/pi-packages/commit/c186c370b975e5ef6596d9a3d7719c720a580640))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * **retro:** add retro notes for issue [#214](https://github.com/gotgenes/pi-packages/issues/214) ([7e39c96](https://github.com/gotgenes/pi-packages/commit/7e39c96563517661c1c8c7f250402115b232a097))
20
+
21
+ ## [7.3.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.3.1...pi-subagents-v7.3.2) (2026-05-25)
22
+
23
+
24
+ ### Documentation
25
+
26
+ * mark Phase 13 Step 1 complete ([8d62590](https://github.com/gotgenes/pi-packages/commit/8d62590aeded8aba25df96ea2fbba6581572eaf7))
27
+ * **pi-subagents:** link Phase 13 issues [#214](https://github.com/gotgenes/pi-packages/issues/214)–[#219](https://github.com/gotgenes/pi-packages/issues/219) in architecture roadmap ([636a1e5](https://github.com/gotgenes/pi-packages/commit/636a1e500319b9ce1007c0a571dd13199cdd2606))
28
+ * **pi-subagents:** propose Phase 13 improvement roadmap ([caf2a53](https://github.com/gotgenes/pi-packages/commit/caf2a5376c69744db1743a9362096e764d9524b1))
29
+ * plan convert remaining closure factories to classes ([#214](https://github.com/gotgenes/pi-packages/issues/214)) ([2225920](https://github.com/gotgenes/pi-packages/commit/222592007e2ce29d5e6d5678a303f6cb781dae39))
30
+ * **retro:** add planning stage notes for issue [#214](https://github.com/gotgenes/pi-packages/issues/214) ([3072e61](https://github.com/gotgenes/pi-packages/commit/3072e618f08080065ef9f3c7520ff44e8dbc84b4))
31
+ * **retro:** add retro notes for issue [#208](https://github.com/gotgenes/pi-packages/issues/208) ([1436396](https://github.com/gotgenes/pi-packages/commit/1436396d71e79cbbf519877e9dc899000ebd0001))
32
+ * **retro:** add TDD stage notes for issue [#214](https://github.com/gotgenes/pi-packages/issues/214) ([201e1eb](https://github.com/gotgenes/pi-packages/commit/201e1ebbaba66e0d83cc006ef77b711c22fb1bb8))
33
+
8
34
  ## [7.3.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.3.0...pi-subagents-v7.3.1) (2026-05-25)
9
35
 
10
36
 
@@ -431,36 +431,38 @@ These are fire-and-forget broadcast events — no request IDs, no reply channels
431
431
 
432
432
  ### Health metrics
433
433
 
434
- | Metric | Value |
435
- | ------------------------- | ---------------------------- |
436
- | Health score | 75/100 (B) |
437
- | Total LOC | 8,218 (53 files) |
438
- | Dead code | 0 files, 0 exports |
439
- | Maintainability index | 90.7 (good) |
440
- | Avg cyclomatic complexity | 1.5 |
441
- | P90 cyclomatic complexity | 2 |
442
- | Production duplication | 18 lines (1 clone group) |
443
- | Test duplication | 71 clone groups, 1,424 lines |
434
+ | Metric | Value |
435
+ | -------------------------- | ------------------------------------ |
436
+ | Health score | 78/100 (B) |
437
+ | Total LOC | 8,180 (53 files) |
438
+ | Test LOC | 12,026 |
439
+ | Dead code | 0 files, 0 exports |
440
+ | Maintainability index | 90.7 (good) |
441
+ | Avg cyclomatic complexity | 1.5 |
442
+ | P90 cyclomatic complexity | 2 |
443
+ | Production duplication | 20 lines (1 clone group) |
444
+ | Test duplication | 59 clone groups, 1,046 lines |
445
+ | Fallow refactoring targets | 1 (buildParentContext, cognitive 30) |
444
446
 
445
447
  ### Dependency bag inventory
446
448
 
447
449
  These interfaces carry hidden dependencies that obscure true coupling.
448
450
  Bags with 10+ fields are the highest priority for decomposition.
449
451
 
450
- | Interface | Fields | Consumers | Severity |
451
- | --------------------------- | ------------------------------------------------------ | ------------------------------------------------- | -------- |
452
- | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
453
- | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
454
- | `RunOptions` | 9 (`RunContext` nested) | agent-runner | ✓ done |
455
- | `SessionConfig` | 8 (ToolFilterConfig nested) | agent-runner (output of assembler) | ✓ done |
456
- | `NotificationDetails` | 10 | notification | Medium |
457
- | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
458
- | `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
459
- | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
460
- | `AgentToolDeps` | 8 | agent-tool | ✓ done |
461
- | `AgentMenuDeps` | 8 | agent-menu | Low |
462
- | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
463
- | `AgentRecordInit` | 8 | agent-record | Low |
452
+ | Interface | Fields | Consumers | Severity |
453
+ | --------------------------- | ------------------------------------------------------ | ------------------------------------------------- | --------- |
454
+ | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
455
+ | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
456
+ | `RunOptions` | 9 (`RunContext` nested) | agent-runner | ✓ done |
457
+ | `SessionConfig` | 8 (ToolFilterConfig nested) | agent-runner (output of assembler) | ✓ done |
458
+ | `NotificationDetails` | 10 | notification | Low (DTO) |
459
+ | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Low (SDK) |
460
+ | `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
461
+ | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Low (SDK) |
462
+ | `AgentToolDeps` | 8 | agent-tool | ✓ done |
463
+ | `AgentMenuDeps` | 8 | agent-menu | ✓ done |
464
+ | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
465
+ | `AgentRecordInit` | 8 | agent-record | Low |
464
466
 
465
467
  ### Complexity hotspots
466
468
 
@@ -472,18 +474,22 @@ No functions remain above the critical threshold — all hotspots resolved in Ph
472
474
 
473
475
  Files with highest commit frequency × complexity (accelerating trend):
474
476
 
475
- | Score | File | Commits |
476
- | ----- | --------------------- | ------- |
477
- | 85.7 | `index.ts` | 65 |
478
- | 35.9 | `agent-manager.ts` | 31 |
479
- | 25.9 | `ui/agent-menu.ts` | 26 |
480
- | 23.3 | `tools/agent-tool.ts` | 30 |
477
+ | Score | File | Commits | Trend |
478
+ | ----- | --------------------------- | ------- | -------------- |
479
+ | 43.3 | `index.ts` | 81 | ▲ accelerating |
480
+ | 26.0 | `ui/agent-menu.ts` | 33 | ▲ accelerating |
481
+ | 13.6 | `tools/agent-tool.ts` | 41 | ▲ accelerating |
482
+ | 13.3 | `ui/conversation-viewer.ts` | 16 | ▲ accelerating |
483
+ | 12.6 | `ui/agent-config-editor.ts` | 10 | ▼ cooling |
484
+ | 11.7 | `ui/agent-widget.ts` | 14 | ▲ accelerating |
485
+
486
+ Note: accelerating trends reflect recent refactoring phases, not feature churn.
487
+ Once structural work stabilizes, these are expected to cool.
481
488
 
482
489
  ### Production duplication
483
490
 
484
- The 18-line clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
485
- `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` now live in `session/content-items.ts`.
486
- No known production duplication remains.
491
+ The prior clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
492
+ One 20-line clone group remains between `agent-config-editor.ts:138–151` and `agent-creation-wizard.ts:231–250` both implement the same overwrite-guard + write + reload + notify pattern.
487
493
 
488
494
  ### Proposed bag decompositions
489
495
 
@@ -600,51 +606,125 @@ Phase 11 converted all closure factories to classes, eliminating adapter closure
600
606
  Four layers: SessionContext typing → runtime query methods → interface alignment → class conversions → index.ts simplification.
601
607
  See [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) for details.
602
608
 
603
- ## Improvement roadmap (Phase 12)
609
+ ## Phase 12 (complete)
610
+
611
+ Phase 12 decomposed the three remaining high-complexity UI functions and extracted shared test fixtures.
612
+ All four steps are closed: [#205], [#206], [#207], [#208].
613
+
614
+ ## Improvement roadmap (Phase 13)
615
+
616
+ Phase 13 addresses the remaining fallow refactoring target, `agent-manager.ts` oversized method, production duplication, SDK boundary coupling, and the heaviest test clone families.
617
+ Health score target: 80+ (A).
618
+
619
+ ### Findings summary
620
+
621
+ | Finding | Category | Impact | Risk | Priority |
622
+ | ---------------------------------------------------------------- | -------------- | ------ | ---- | -------- |
623
+ | 3 remaining closure factories (Phase 11 survivors) | C: Coupling | 4 | 2 | 16 |
624
+ | `buildParentContext` cognitive 30 (only fallow target) | B: Oversized | 3 | 1 | 15 |
625
+ | `startAgent` in agent-manager.ts (~130 LOC method) | B: Oversized | 4 | 3 | 12 |
626
+ | Test duplication: 59 clone groups, 1,046 lines | D: Testability | 3 | 2 | 12 |
627
+ | Overwrite guard duplicated across UI modules (20 lines) | A: Redundant | 2 | 1 | 10 |
628
+ | `settings.ts` calls SDK function `getAgentDir()` at module level | C: Coupling | 2 | 1 | 10 |
629
+
630
+ ### Step 1: Convert remaining closure factories to classes — [#214] ✓
631
+
632
+ Three closure factories converted to classes in [#214].
633
+
634
+ | Factory → Class | File | Captures |
635
+ | ------------------------------------------------------ | ----------------------------- | ---------------------------------------- |
636
+ | `createAgentConfigEditor()` → `AgentConfigEditor` | `ui/agent-config-editor.ts` | `fileOps`, `registry`, 2 dirs |
637
+ | `createAgentCreationWizard()` → `AgentCreationWizard` | `ui/agent-creation-wizard.ts` | `fileOps`, `manager`, `registry`, 2 dirs |
638
+ | `createSubagentsService()` → `SubagentsServiceAdapter` | `service/service-adapter.ts` | `manager`, `resolveModel`, `runtime` |
639
+
640
+ - Smell: C (coupling — deps hidden in closure scope instead of explicit on class)
641
+ - Outcome: 0 remaining closure factories (excluding pure-function factories), deps visible as constructor parameters
604
642
 
605
- Phase 12 addresses the remaining fallow refactoring targets and test duplication.
606
- These are independent of Phase 11 and can proceed in parallel if desired.
643
+ ### Step 2: Decompose `buildParentContext` (cognitive 30) [#215]
607
644
 
608
- ### Step 1: Decompose `renderWidgetLines` (cognitive 44) [#205]
645
+ `buildParentContext` in `session/context.ts` is the only remaining fallow refactoring target.
646
+ The function loops over branch entries with 3 type-check branches, each with sub-branches for role or summary.
647
+ Extract per-entry-type formatters: `formatMessageEntry(entry)` and `formatCompactionEntry(entry)`.
609
648
 
610
- `renderWidgetLines` in `ui/widget-renderer.ts` handles agent-status formatting, tree connectors, overflow, and empty states.
611
- Extract per-status renderers and a tree-connector utility.
649
+ - Target: `src/session/context.ts`
650
+ - Smell: B (oversized function)
651
+ - Outcome: cognitive complexity < 10, function < 15 LOC
612
652
 
613
- - Target: `src/ui/widget-renderer.ts`
614
- - Outcome: cognitive complexity < 10
653
+ ### Step 3: Decompose `startAgent` in `agent-manager.ts` — [#216]
615
654
 
616
- ### Step 2: Decompose `showAgentDetail` (cognitive 33) [#206]
655
+ `startAgent` is a ~130-line private method that chains worktree setup → state transitions → observer notification → abort-signal wiring → runner invocation → `.then()` completion handler (~35 lines) `.catch()` error handler (~15 lines).
656
+ Both the `.then()` and `.catch()` blocks share common finalization logic (background counter decrement, observer notification, queue drain, worktree cleanup, detach signal).
617
657
 
618
- `showAgentDetail` in `ui/agent-config-editor.ts` handles display, edit, eject, and delete flows.
619
- Extract sub-functions per menu action.
658
+ Extract:
620
659
 
621
- - Target: `src/ui/agent-config-editor.ts`
622
- - Outcome: cognitive complexity < 10
660
+ 1. `handleRunCompletion(record, options, result)` — worktree cleanup, state transition, execution update, observer notification.
661
+ 2. `handleRunError(record, options, err)` error marking, worktree cleanup.
662
+ 3. `finalizeBackgroundRun(record)` — shared `runningBackground--`, observer, `drainQueue()`.
623
663
 
624
- ### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31) — [#207]
664
+ - Target: `src/lifecycle/agent-manager.ts`
665
+ - Smell: B (oversized method) + A (duplicated finalization logic in then/catch)
666
+ - Outcome: no method > 40 LOC, `agent-manager.ts` < 480 LOC
625
667
 
626
- `update` mixes timer lifecycle, agent list assembly, render delegation, and visibility state.
627
- Extract `assembleWidgetState` (pure) and timer management.
668
+ ### Step 4: Extract overwrite guard from UI [#217]
628
669
 
629
- - Target: `src/ui/agent-widget.ts`
630
- - Outcome: cognitive complexity < 10
670
+ The 20-line pattern duplicated between `agent-config-editor.ts:138–151` and `agent-creation-wizard.ts:231–250` checks file existence, prompts for confirmation, writes the file, reloads the registry, and notifies the user.
671
+ Extract a shared `writeAgentFile(fileOps, ui, registry, targetPath, content, label)` function.
631
672
 
632
- ### Step 4: Extract shared test fixtures [#208]
673
+ - Target: new `src/ui/agent-file-writer.ts`, consumers `src/ui/agent-config-editor.ts` and `src/ui/agent-creation-wizard.ts`
674
+ - Smell: A (production duplication)
675
+ - Outcome: 0 production clone groups
633
676
 
634
- The 3 heaviest clone families:
677
+ ### Step 5: Push SDK boundary in `settings.ts` — [#218]
635
678
 
636
- - `agent-runner.test.ts` + `agent-runner-extension-tools.test.ts` (60-line shared setup)
637
- - `agent-menu.test.ts` + `agent-creation-wizard.test.ts` + `agent-config-editor.test.ts` (54+51+24 lines)
638
- - `agent-manager.test.ts` (18 internal clone groups, 210 duplicated lines)
679
+ `globalPath()` calls `getAgentDir()` (a Pi SDK function) at invocation time.
680
+ This hides a platform dependency inside a module that is otherwise pure configuration logic.
681
+ Inject `agentDir: string` as a constructor parameter to `SettingsManager` and pass the global settings path from the boundary in `index.ts`.
682
+
683
+ - Target: `src/settings.ts`, `src/index.ts`
684
+ - Smell: C (platform type threading)
685
+ - Outcome: `settings.ts` has 0 Pi SDK imports, `loadSettings`/`saveSettings` become fully testable without SDK stubs
686
+
687
+ ### Step 6: Reduce test duplication — top 3 clone families — [#219]
688
+
689
+ The three heaviest remaining clone families after Phase 12:
690
+
691
+ 1. `agent-manager.test.ts` — 16 clone groups, 160 duplicated lines.
692
+ Extract shared setup/assertion helpers into `test/helpers/manager-stubs.ts`.
693
+ 2. `conversation-viewer.test.ts` — 8 clone groups, 91 duplicated lines.
694
+ Extract entry-builder helpers into existing `test/helpers/` or inline factory.
695
+ 3. `agent-config-editor.test.ts` — 5 clone groups, 42 duplicated lines.
696
+ Extract shared setup helpers.
697
+
698
+ - Target: `test/lifecycle/agent-manager.test.ts`, `test/conversation-viewer.test.ts`, `test/ui/agent-config-editor.test.ts`
699
+ - Smell: D (test duplication)
700
+ - Outcome: test duplication reduced by ~200 lines (from 1,046 to < 850)
701
+
702
+ ### Step dependency diagram
703
+
704
+ ```mermaid
705
+ flowchart LR
706
+ S1["Step 1\nclosure to class"]
707
+ S2["Step 2\nbuildParentContext"]
708
+ S3["Step 3\nstartAgent decomp"]
709
+ S4["Step 4\noverwrite guard"]
710
+ S5["Step 5\nsettings SDK"]
711
+ S6["Step 6\ntest duplication"]
712
+
713
+ S1 --> S4
714
+ S1 --> S6
715
+ S3 --> S6
716
+ S2 ~~~ S5
717
+ ```
639
718
 
640
- Extract shared factories into `test/helpers/` modules.
719
+ ### Tracks
641
720
 
642
- - Target: new `test/helpers/runner-io.ts` and `test/helpers/ui-stubs.ts` modules
643
- - Outcome: test duplication reduced by ~250 lines
721
+ 1. **Track A — Structural** (Steps 1, 3): closure-to-class conversions and method decomposition.
722
+ 2. **Track B — Complexity and coupling** (Steps 2, 5): independent, can proceed in parallel with Track A.
723
+ 3. **Track C — Duplication** (Steps 4, 6): Step 4 depends on Step 1 (overwrite guard lives in files being converted); Step 6 depends on Steps 1 and 3 (production code they test changes first).
644
724
 
645
725
  ## Refactoring history
646
726
 
647
- Phases 1–5 and 7–11 are complete.
727
+ Phases 1–5 and 7–12 are complete.
648
728
  Phase 6 (UI extraction to a separate package) is deferred.
649
729
  Detailed records are preserved in per-phase history files:
650
730
 
@@ -661,6 +741,7 @@ Detailed records are preserved in per-phase history files:
661
741
  | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
662
742
  | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
663
743
  | 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
744
+ | 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
664
745
 
665
746
  ### Structural refactoring issues
666
747
 
@@ -677,6 +758,7 @@ Detailed records are preserved in per-phase history files:
677
758
  | Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
678
759
  | Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
679
760
  | Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
761
+ | Phase 13 | #214, #215, #216, #217, #218, #219 | Closure-to-class, buildParentContext, startAgent decomp, overwrite guard, settings SDK, test duplication |
680
762
 
681
763
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
682
764
 
@@ -704,3 +786,9 @@ The upstream test suite is run periodically as a regression canary for the agent
704
786
  [#206]: https://github.com/gotgenes/pi-packages/issues/206
705
787
  [#207]: https://github.com/gotgenes/pi-packages/issues/207
706
788
  [#208]: https://github.com/gotgenes/pi-packages/issues/208
789
+ [#214]: https://github.com/gotgenes/pi-packages/issues/214
790
+ [#215]: https://github.com/gotgenes/pi-packages/issues/215
791
+ [#216]: https://github.com/gotgenes/pi-packages/issues/216
792
+ [#217]: https://github.com/gotgenes/pi-packages/issues/217
793
+ [#218]: https://github.com/gotgenes/pi-packages/issues/218
794
+ [#219]: https://github.com/gotgenes/pi-packages/issues/219
@@ -0,0 +1,55 @@
1
+ # Phase 12: Complexity reduction and test fixture extraction
2
+
3
+ ## Summary
4
+
5
+ Phase 12 decomposed the three remaining high-complexity UI functions and extracted shared test fixtures into `test/helpers/`.
6
+
7
+ ## Steps
8
+
9
+ ### Step 1: Decompose `renderWidgetLines` (cognitive 44) — [#205]
10
+
11
+ `renderWidgetLines` in `ui/widget-renderer.ts` handled agent-status formatting, tree connectors, overflow, and empty states.
12
+ Extracted per-status renderers and a tree-connector utility.
13
+
14
+ - Target: `src/ui/widget-renderer.ts`
15
+ - Outcome: cognitive complexity < 10
16
+
17
+ ### Step 2: Decompose `showAgentDetail` (cognitive 33) — [#206]
18
+
19
+ `showAgentDetail` in `ui/agent-config-editor.ts` handled display, edit, eject, and delete flows.
20
+ Extracted sub-functions per menu action.
21
+
22
+ - Target: `src/ui/agent-config-editor.ts`
23
+ - Outcome: cognitive complexity < 10
24
+
25
+ ### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31) — [#207]
26
+
27
+ `update` mixed timer lifecycle, agent list assembly, render delegation, and visibility state.
28
+ Extracted `assembleWidgetState` (pure) and timer management.
29
+
30
+ - Target: `src/ui/agent-widget.ts`
31
+ - Outcome: cognitive complexity < 10
32
+
33
+ ### Step 4: Extract shared test fixtures — [#208]
34
+
35
+ The 3 heaviest clone families:
36
+
37
+ - `agent-runner.test.ts` + `agent-runner-extension-tools.test.ts` (60-line shared setup)
38
+ - `agent-menu.test.ts` + `agent-creation-wizard.test.ts` + `agent-config-editor.test.ts` (54+51+24 lines)
39
+ - `agent-manager.test.ts` (18 internal clone groups, 210 duplicated lines)
40
+
41
+ Extracted shared factories into `test/helpers/runner-io.ts` and `test/helpers/ui-stubs.ts`.
42
+ Test duplication reduced from 71 clone groups (1,424 lines) to 59 clone groups (1,046 lines).
43
+
44
+ ## Metrics change
45
+
46
+ | Metric | Before | After |
47
+ | -------------------------- | ---------------------- | ---------------------- |
48
+ | Health score | 75/100 (B) | 78/100 (B) |
49
+ | Fallow refactoring targets | 4 | 1 |
50
+ | Test duplication | 71 groups, 1,424 lines | 59 groups, 1,046 lines |
51
+
52
+ [#205]: https://github.com/gotgenes/pi-packages/issues/205
53
+ [#206]: https://github.com/gotgenes/pi-packages/issues/206
54
+ [#207]: https://github.com/gotgenes/pi-packages/issues/207
55
+ [#208]: https://github.com/gotgenes/pi-packages/issues/208
@@ -0,0 +1,261 @@
1
+ ---
2
+ issue: 214
3
+ issue_title: "Convert remaining closure factories to classes (Phase 13, Step 1)"
4
+ ---
5
+
6
+ # Convert remaining closure factories to classes
7
+
8
+ ## Problem Statement
9
+
10
+ Three closure factories survived Phase 11 — each captures dependencies in closure scope and returns a method bag, the exact pattern Phase 11 eliminated elsewhere.
11
+ Converting them to classes makes dependencies explicit as constructor parameters and aligns with the class-based pattern established in Phase 11.
12
+
13
+ ## Goals
14
+
15
+ - Convert `createAgentConfigEditor()` to an `AgentConfigEditor` class.
16
+ - Convert `createAgentCreationWizard()` to an `AgentCreationWizard` class.
17
+ - Convert `createSubagentsService()` to a `SubagentsServiceAdapter` class implementing `SubagentsService`.
18
+ - Remove the `AgentCreationWizardDeps` interface (the class constructor replaces it).
19
+ - Update `AgentsMenuHandler` to store typed class instances instead of `ReturnType<typeof ...>`.
20
+ - Update `index.ts` to construct `SubagentsServiceAdapter` with `new`.
21
+ - 0 remaining closure factories (excluding pure-function factories like `createNotificationRenderer`).
22
+
23
+ ## Non-Goals
24
+
25
+ - Changing any behavior — all conversions are purely structural.
26
+ - Extracting the shared overwrite guard between `AgentConfigEditor` and `AgentCreationWizard` — that is #218.
27
+ - Reducing test duplication in `agent-config-editor.test.ts` — that is #219.
28
+ - Decomposing `buildParentContext` or `startAgent` — those are #215 and #216.
29
+ - Modifying `createNotificationRenderer()` — it returns a pure render function with no captured state.
30
+
31
+ ## Background
32
+
33
+ ### Phase 11 precedent
34
+
35
+ Phase 11 converted all closure factories in tools, runner, and menu layers to classes.
36
+ Issues #195 and #196 established the pattern:
37
+
38
+ - Closure-captured deps become constructor parameters stored as `private readonly` fields.
39
+ - Nested functions become private methods.
40
+ - Consumer call sites change from `createFoo(deps)` to `new Foo(deps)`.
41
+ - Tests update `makeXxx()` helpers to use `new` instead of calling the factory.
42
+
43
+ ### Current state
44
+
45
+ | Factory | File | Captures | Returns |
46
+ | ----------------------------- | ----------------------------- | ---------------------------------------- | ------------------------------------------- |
47
+ | `createAgentConfigEditor()` | `ui/agent-config-editor.ts` | `fileOps`, `registry`, 2 dirs | `{ showAgentDetail }` (7 nested async fns) |
48
+ | `createAgentCreationWizard()` | `ui/agent-creation-wizard.ts` | `fileOps`, `manager`, `registry`, 2 dirs | `{ showCreateWizard }` (3 nested async fns) |
49
+ | `createSubagentsService()` | `service/service-adapter.ts` | `manager`, `resolveModel`, `runtime` | 7-method `SubagentsService` |
50
+
51
+ ### Consumer sites
52
+
53
+ - `AgentsMenuHandler` (in `agent-menu.ts`) constructs both UI factories in its constructor and stores them as `private readonly` fields typed as `ReturnType<typeof createAgentConfigEditor>` and `ReturnType<typeof createAgentCreationWizard>`.
54
+ - `index.ts` calls `createSubagentsService(manager, resolveModel, runtime)` and passes the result to `publishSubagentsService`.
55
+
56
+ ### Structural typing
57
+
58
+ `AgentCreationWizardDeps` is only used within `agent-creation-wizard.ts` — no external imports.
59
+ The class constructor replaces the deps interface entirely.
60
+
61
+ ## Design Overview
62
+
63
+ ### AgentConfigEditor
64
+
65
+ Deps become positional constructor parameters (matching the factory's positional signature):
66
+
67
+ ```typescript
68
+ export class AgentConfigEditor {
69
+ constructor(
70
+ private readonly fileOps: AgentFileOps,
71
+ private readonly registry: AgentTypeRegistry,
72
+ private readonly personalAgentsDir: string,
73
+ private readonly projectAgentsDir: string,
74
+ ) {}
75
+
76
+ private agentDirs(): string[] { ... }
77
+ async showAgentDetail(ui: MenuUI, name: string): Promise<void> { ... }
78
+ private async handleEdit(ui: MenuUI, name: string, file: string): Promise<void> { ... }
79
+ private async handleDelete(ui: MenuUI, name: string, file: string): Promise<void> { ... }
80
+ private async handleReset(ui: MenuUI, name: string, file: string): Promise<void> { ... }
81
+ private async ejectAgent(ui: MenuUI, name: string, cfg: AgentConfig): Promise<void> { ... }
82
+ private async disableAgent(ui: MenuUI, name: string): Promise<void> { ... }
83
+ private async enableAgent(ui: MenuUI, name: string): Promise<void> { ... }
84
+ }
85
+ ```
86
+
87
+ The pure helper functions `buildMenuOptions` and `buildEjectContent` remain as exported free functions — they have no captured state and their tests exercise them independently.
88
+
89
+ ### AgentCreationWizard
90
+
91
+ Deps become positional constructor parameters (dissolving `AgentCreationWizardDeps`):
92
+
93
+ ```typescript
94
+ export class AgentCreationWizard {
95
+ constructor(
96
+ private readonly fileOps: AgentFileOps,
97
+ private readonly manager: WizardManager,
98
+ private readonly registry: WizardRegistry,
99
+ private readonly personalAgentsDir: string,
100
+ private readonly projectAgentsDir: string,
101
+ ) {}
102
+
103
+ async showCreateWizard(ui: MenuUI, parentSnapshot: ParentSnapshot): Promise<void> { ... }
104
+ private async showGenerateWizard(ui: MenuUI, parentSnapshot: ParentSnapshot, targetDir: string): Promise<void> { ... }
105
+ private async showManualWizard(ui: MenuUI, targetDir: string): Promise<void> { ... }
106
+ }
107
+ ```
108
+
109
+ `WizardManager` and `WizardRegistry` interfaces remain exported — they define the narrow contracts for the class's collaborators.
110
+
111
+ ### SubagentsServiceAdapter
112
+
113
+ The class implements `SubagentsService` directly:
114
+
115
+ ```typescript
116
+ export class SubagentsServiceAdapter implements SubagentsService {
117
+ constructor(
118
+ private readonly manager: AgentManagerLike,
119
+ private readonly resolveModel: (input: string, registry: ModelRegistry) => unknown,
120
+ private readonly runtime: ServiceRuntimeLike,
121
+ ) {}
122
+
123
+ spawn(type: string, prompt: string, options?: SpawnOptions): string { ... }
124
+ getRecord(id: string): SubagentRecord | undefined { ... }
125
+ listAgents(): SubagentRecord[] { ... }
126
+ abort(id: string): boolean { ... }
127
+ async steer(id: string, message: string): Promise<boolean> { ... }
128
+ async waitForAll(): Promise<void> { ... }
129
+ hasRunning(): boolean { ... }
130
+ }
131
+ ```
132
+
133
+ `AgentManagerLike` and `ServiceRuntimeLike` interfaces remain exported — they define the narrow contracts.
134
+ The `toSubagentRecord` helper remains an exported free function — it is pure and tested independently.
135
+
136
+ ### AgentsMenuHandler updates
137
+
138
+ The `editor` and `wizard` fields change from `ReturnType<typeof ...>` to the concrete class types:
139
+
140
+ ```typescript
141
+ private readonly editor: AgentConfigEditor;
142
+ private readonly wizard: AgentCreationWizard;
143
+ ```
144
+
145
+ Construction changes from `createAgentConfigEditor(...)` to `new AgentConfigEditor(...)` and from `createAgentCreationWizard({...})` to `new AgentCreationWizard(...)`.
146
+
147
+ ## Module-Level Changes
148
+
149
+ ### `src/ui/agent-config-editor.ts`
150
+
151
+ - Replace `createAgentConfigEditor` factory function with `AgentConfigEditor` class.
152
+ - `agentDirs()` becomes a private method.
153
+ - `showAgentDetail`, `handleEdit`, `handleDelete`, `handleReset`, `ejectAgent`, `disableAgent`, `enableAgent` become class methods (`showAgentDetail` is public, rest are private).
154
+ - `buildMenuOptions` and `buildEjectContent` remain as exported free functions.
155
+
156
+ ### `src/ui/agent-creation-wizard.ts`
157
+
158
+ - Replace `createAgentCreationWizard` factory function with `AgentCreationWizard` class.
159
+ - Remove `AgentCreationWizardDeps` interface.
160
+ - `showCreateWizard` becomes a public method; `showGenerateWizard` and `showManualWizard` become private methods.
161
+ - `WizardManager` and `WizardRegistry` interfaces remain exported.
162
+
163
+ ### `src/service/service-adapter.ts`
164
+
165
+ - Replace `createSubagentsService` factory function with `SubagentsServiceAdapter` class implementing `SubagentsService`.
166
+ - `AgentManagerLike` and `ServiceRuntimeLike` interfaces remain exported.
167
+ - `toSubagentRecord` remains an exported free function.
168
+ - Add import for `SpawnOptions` from `#src/service/service` (needed for the `spawn` method signature).
169
+
170
+ ### `src/ui/agent-menu.ts`
171
+
172
+ - Replace `ReturnType<typeof createAgentConfigEditor>` with `AgentConfigEditor`.
173
+ - Replace `ReturnType<typeof createAgentCreationWizard>` with `AgentCreationWizard`.
174
+ - Update constructor body: `new AgentConfigEditor(...)` instead of `createAgentConfigEditor(...)`.
175
+ - Update constructor body: `new AgentCreationWizard(...)` instead of `createAgentCreationWizard({...})`.
176
+ - Update imports: `AgentConfigEditor` instead of `createAgentConfigEditor`, `AgentCreationWizard` instead of `createAgentCreationWizard`.
177
+
178
+ ### `src/index.ts`
179
+
180
+ - Replace `createSubagentsService(manager, resolveModel, runtime)` with `new SubagentsServiceAdapter(manager, resolveModel, runtime)`.
181
+ - Update import: `SubagentsServiceAdapter` instead of `createSubagentsService`.
182
+
183
+ ### `docs/architecture/architecture.md`
184
+
185
+ - Mark Step 1 of Phase 13 as complete.
186
+ - Update the factory table to show conversions done.
187
+
188
+ ## Test Impact Analysis
189
+
190
+ ### `test/ui/agent-config-editor.test.ts`
191
+
192
+ - Update `makeEditor()` helper: replace `createAgentConfigEditor(...)` with `new AgentConfigEditor(...)`.
193
+ - Update import: `AgentConfigEditor` instead of `createAgentConfigEditor`.
194
+ - No test logic changes — all existing assertions remain valid.
195
+ - The `makeEditor()` helper centralizes the factory call, so only 1 line changes.
196
+
197
+ ### `test/ui/agent-creation-wizard.test.ts`
198
+
199
+ - Replace all `createAgentCreationWizard(deps)` calls with `new AgentCreationWizard(deps.fileOps, deps.manager, deps.registry, deps.personalAgentsDir, deps.projectAgentsDir)`.
200
+ - Update import: `AgentCreationWizard` instead of `createAgentCreationWizard`.
201
+ - This file has ~18 call sites that each construct the wizard inline; each changes from `createAgentCreationWizard(deps)` to `new AgentCreationWizard(deps.fileOps, deps.manager, deps.registry, deps.personalAgentsDir, deps.projectAgentsDir)`.
202
+ - Alternatively, add a `makeWizard(deps)` helper to centralize construction, then each call site becomes `makeWizard(deps)`.
203
+ - No test logic changes.
204
+
205
+ ### `test/service/service-adapter.test.ts`
206
+
207
+ - Replace all `createSubagentsService(manager, resolveModel, runtime)` calls with `new SubagentsServiceAdapter(manager, resolveModel, runtime)`.
208
+ - Update import: `SubagentsServiceAdapter` instead of `createSubagentsService`.
209
+ - ~12 call sites change; each is a mechanical find-and-replace.
210
+ - Update `describe` block names from `createSubagentsService —` to `SubagentsServiceAdapter —`.
211
+ - No test logic changes.
212
+
213
+ ### No new tests needed
214
+
215
+ The conversions are structural — existing tests fully cover all behavior.
216
+ Adding "verify it's a class" tests would test the language, not the code.
217
+
218
+ ## TDD Order
219
+
220
+ 1. **Convert `createAgentConfigEditor` to `AgentConfigEditor` class.**
221
+ Replace factory with class in `agent-config-editor.ts`.
222
+ Update `makeEditor()` in `agent-config-editor.test.ts`.
223
+ Update `agent-menu.ts` field types and constructor.
224
+ Remove factory function.
225
+ Verify: `pnpm vitest run test/ui/agent-config-editor.test.ts` and `pnpm run check`.
226
+ `refactor: convert createAgentConfigEditor to AgentConfigEditor class`
227
+
228
+ 2. **Convert `createAgentCreationWizard` to `AgentCreationWizard` class.**
229
+ Replace factory with class in `agent-creation-wizard.ts`.
230
+ Remove `AgentCreationWizardDeps` interface.
231
+ Update all call sites in `agent-creation-wizard.test.ts` (add `makeWizard` helper to centralize construction).
232
+ Update `agent-menu.ts` field type and constructor.
233
+ Remove factory function.
234
+ Verify: `pnpm vitest run test/ui/agent-creation-wizard.test.ts` and `pnpm run check`.
235
+ `refactor: convert createAgentCreationWizard to AgentCreationWizard class`
236
+
237
+ 3. **Convert `createSubagentsService` to `SubagentsServiceAdapter` class.**
238
+ Replace factory with class in `service-adapter.ts`.
239
+ Update all call sites in `service-adapter.test.ts`.
240
+ Update `describe` block names.
241
+ Update `index.ts` import and construction.
242
+ Remove factory function.
243
+ Verify: `pnpm vitest run test/service/service-adapter.test.ts` and `pnpm run check`.
244
+ `refactor: convert createSubagentsService to SubagentsServiceAdapter class`
245
+
246
+ 4. **Update architecture doc.**
247
+ Mark Phase 13 Step 1 as complete in `docs/architecture/architecture.md`.
248
+ `docs: mark Phase 13 Step 1 complete`
249
+
250
+ ## Risks and Mitigations
251
+
252
+ | Risk | Mitigation |
253
+ | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
254
+ | `AgentCreationWizard` test has ~18 inline factory calls that all need updating | Add a `makeWizard(deps)` test helper to centralize construction — same pattern used in `agent-config-editor.test.ts`. |
255
+ | `SubagentsServiceAdapter` class might not satisfy `SubagentsService` interface | The class uses `implements SubagentsService`, so `pnpm run check` catches any mismatches at compile time. |
256
+ | Spreading a class instance in tests produces a plain object lacking methods | Not applicable — none of the test files spread factory results. Tests call methods directly on the returned object. |
257
+ | `AgentCreationWizardDeps` removal might break external consumers | Verified: the interface is only used within `agent-creation-wizard.ts` itself. No external imports. |
258
+
259
+ ## Open Questions
260
+
261
+ None — the conversions are mechanical and follow the established Phase 11 pattern.