@gotgenes/pi-subagents 7.0.0 → 7.2.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,39 @@ 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.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.1.0...pi-subagents-v7.2.0) (2026-05-25)
9
+
10
+
11
+ ### Features
12
+
13
+ * SubagentRuntime stores typed SessionContext and owns context queries ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([4ca5319](https://github.com/gotgenes/pi-packages/commit/4ca531934e36c37c7cbc8fef8314a483e7dec479))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * mark Phase 11 Layer 1 complete, update metrics ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([32233ed](https://github.com/gotgenes/pi-packages/commit/32233ed89f27dca854ce858978c3acc029b1e801))
19
+ * plan SubagentRuntime owns context queries ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([6ea475a](https://github.com/gotgenes/pi-packages/commit/6ea475af94f8456c1d665adcd007dd2833ab7a4b))
20
+ * **retro:** add planning stage notes for issue [#193](https://github.com/gotgenes/pi-packages/issues/193) ([7da6d5a](https://github.com/gotgenes/pi-packages/commit/7da6d5abac82bdea0cbdbc8677dda04d38a7887d))
21
+ * **retro:** add retro notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([1223de4](https://github.com/gotgenes/pi-packages/commit/1223de4a68d4514eb504c5c95d64fb35500d286b))
22
+ * **retro:** add TDD stage notes for issue [#193](https://github.com/gotgenes/pi-packages/issues/193) ([3950b81](https://github.com/gotgenes/pi-packages/commit/3950b81228a3db57eb4c24236fa7d75c638a335a))
23
+
24
+ ## [7.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.0.0...pi-subagents-v7.1.0) (2026-05-24)
25
+
26
+
27
+ ### Features
28
+
29
+ * **pi-subagents:** define SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([a043d4d](https://github.com/gotgenes/pi-packages/commit/a043d4d970a8aacc084a125d9a07b860a7fb6e9b))
30
+
31
+
32
+ ### Documentation
33
+
34
+ * **pi-subagents:** archive Phase 10, propose Phase 11 roadmap ([d474eef](https://github.com/gotgenes/pi-packages/commit/d474eef98c1a39757d933da291725ff126f1b8ac))
35
+ * **pi-subagents:** revise Phase 11 roadmap — layered class conversion ([35d1083](https://github.com/gotgenes/pi-packages/commit/35d1083445aece783e344c37fc949395e190f9e5))
36
+ * plan SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([95cb16e](https://github.com/gotgenes/pi-packages/commit/95cb16e46aa630ecc34f423aaaa4ff02845ed5b5))
37
+ * **retro:** add planning stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([31fd729](https://github.com/gotgenes/pi-packages/commit/31fd7290985b0ef1d240cd5afd5e0e0e7eec9131))
38
+ * **retro:** add retro notes for issue [#185](https://github.com/gotgenes/pi-packages/issues/185) ([66e49cf](https://github.com/gotgenes/pi-packages/commit/66e49cfb4129b9bba3b78e0850402bc61d99dda8))
39
+ * **retro:** add TDD stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([6cf3f95](https://github.com/gotgenes/pi-packages/commit/6cf3f95f6c39f3f81c1d335348d5abd74d948ff3))
40
+
8
41
  ## [7.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.19.1...pi-subagents-v7.0.0) (2026-05-24)
9
42
 
10
43
 
@@ -314,16 +314,16 @@ The widget reads agent state by polling a shared `Map<string, AgentActivityTrack
314
314
 
315
315
  ```mermaid
316
316
  flowchart TD
317
- subgraph core["@gotgenes/pi-subagents (this package)"]
317
+ subgraph core["@gotgenes/pi-subagents"]
318
318
  direction TB
319
- exports["SubagentsService interface\npublish / getSubagentsService()\nSubagentRecord, SubagentStatus, LifetimeUsage\nSUBAGENT_EVENTS constants"]
320
- engine["Agent + get_subagent_result + steer_subagent tools\nAgentManager, agent-runner, agent-types\npublishSubagentsService() called at init"]
321
- ui_int["Internal UI: widget, viewer, /agents menu\n(candidate for extraction to pi-subagents-ui)"]
319
+ exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
320
+ engine["Tools: Agent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, agent-runner"]
321
+ ui_int["Internal UI: widget, viewer,<br/>/agents menu"]
322
322
  end
323
323
 
324
- core -- "Symbol.for() on globalThis" --> sched["scheduling extension\n(hypothetical)"]
325
- core -- "Symbol.for() on globalThis" --> subui["pi-subagents-ui\n(deferred)"]
326
- core -- "Symbol.for() on globalThis" --> future["any future extension"]
324
+ core -- "Symbol.for on globalThis" --> sched["scheduling extension<br/>(hypothetical)"]
325
+ core -- "Symbol.for on globalThis" --> subui["pi-subagents-ui<br/>(deferred)"]
326
+ core -- "Symbol.for on globalThis" --> future["any future extension"]
327
327
  ```
328
328
 
329
329
  Consumers call `getSubagentsService()?.spawn(...)` at runtime.
@@ -599,121 +599,220 @@ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
599
599
  `RunnerIO` is kept as a type alias for the intersection.
600
600
  All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
601
601
 
602
- ## Improvement roadmap (Phase 10)
602
+ ## Improvement roadmap (Phase 11)
603
603
 
604
- Phase 10 addresses the structural gaps identified in this analysis: flat code organization, oversized dependency bags, and complexity hotspots.
604
+ Phase 11 converts closure factories to classes, eliminating the adapter closure density in `index.ts` (the package's #1 churn hotspot: 128 commits, accelerating, fan-out 25).
605
+ The approach is layered: each step makes the next step trivial.
605
606
 
606
- ### Step 1: Reorganize source into domain directories ([#164][164]) Done
607
+ > "Make the change that makes the change easy." —Kent Beck
607
608
 
608
- Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
609
- All `src/` internal imports now use `#src/` path aliases (same style as `test/` files), eliminating relative depth arithmetic for future moves.
609
+ ### Findings
610
610
 
611
- - Domain model is now visible in the filesystem.
612
- - Root reduced to 5 files + 8 directories (was 31 files + 3 directories).
613
- - All subsequent steps can move or extract files without `../` import churn.
611
+ | Metric | Value |
612
+ | ------------------------- | --------------------------------------- |
613
+ | Health score | 75/100 (B) |
614
+ | #1 hotspot | `index.ts` (128 commits, accelerating) |
615
+ | Dead exports | 1 (`getToolCallName` re-export) |
616
+ | Production duplication | 0 |
617
+ | Test duplication | 1,396 lines (69 clone groups, 22 files) |
618
+ | `as any` casts in index | 1 (down from 5; Layer 1 resolved 4) |
619
+ | Adapter closures in index | 41 (down from 44; Layer 1 resolved 3) |
620
+ | Index fan-out | 25 imports |
614
621
 
615
- ### Step 2: Decompose ResolvedSpawnConfig ([#165][165])
622
+ ### Root cause
616
623
 
617
- Split the 15-field bag into `SpawnIdentity`, `SpawnExecution`, and `SpawnPresentation`.
618
- Each consumer declares its real dependencies.
619
- Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
624
+ The 44 adapter closures in `index.ts` exist because the tool factories accept narrow interfaces that don't structurally match the real objects.
625
+ The real objects can't satisfy the interfaces because:
620
626
 
621
- ### Step 3: Extract ParentSessionInfo from AgentSpawnConfig ([#166][166]) Complete
627
+ 1. ~~`SubagentRuntime.currentCtx` is typed `{ pi: unknown; ctx: unknown }`so every consumer must `as any` cast to read fields.~~
628
+ Resolved by Layer 0 (#192) + Layer 1 (#193).
629
+ 2. ~~Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) live as closures in index.ts instead of methods on the state holder.~~
630
+ Resolved by Layer 1 (#193).
631
+ 3. `AgentToolManager` mixes fields from `AgentManager` and `SettingsManager` (source mismatch).
632
+ 4. `AgentToolWidget` uses different method names than `SubagentRuntime` (name mismatch).
622
633
 
623
- Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessionInfo`.
624
- `AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
634
+ Fix these structural misalignments and the class conversions become mechanical.
625
635
 
626
- ### Step 4: Narrow RunnerIO ([#167][167]) ✓ Done
636
+ ### Layer 0: Define `SessionContext` narrow interface ([#192][192])
627
637
 
628
- `RunnerIO` split into `EnvironmentIO` (3 methods: environment discovery) and `SessionFactoryIO` (5 methods + `assemblerIO`: SDK object creation).
629
- `RunnerIO` kept as a backward-compatible type alias for the intersection.
630
- All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
638
+ `SubagentRuntime` currently types its context as `unknown` to avoid SDK coupling.
639
+ But `ExtensionContext` is exported by the SDK — the `unknown` is a historical choice, not a constraint.
640
+ Define a narrow `SessionContext` interface capturing the 5 fields runtime actually needs:
631
641
 
632
- ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168]) ✓ Done
642
+ ```typescript
643
+ export interface SessionContext {
644
+ readonly cwd: string;
645
+ readonly model: unknown;
646
+ readonly modelRegistry: ModelRegistry | undefined;
647
+ getSystemPrompt(): string;
648
+ readonly sessionManager: {
649
+ getSessionFile(): string | undefined;
650
+ getSessionId(): string;
651
+ getBranch(): unknown[];
652
+ };
653
+ }
654
+ ```
633
655
 
634
- `ToolFilterConfig` extracted from `SessionConfig`, grouping `toolNames`, `disallowedSet`, and `extensions`.
635
- `filterActiveTools` now accepts a single `ToolFilterConfig` argument.
636
- `SessionConfig` reduced from 10 to 8 top-level fields.
656
+ - Target: `src/types.ts`
657
+ - Smell: Category C (platform type threading)
658
+ - Outcome: typed foundation for Layers 1–4; no `as any` needed by consumers of `SubagentRuntime`
659
+
660
+ ### Layer 1: `SubagentRuntime` stores typed context, owns its queries ([#193][193]) ✓ done
661
+
662
+ Change `currentCtx` from `{ pi: unknown; ctx: unknown }` to `SessionContext | undefined`.
663
+ The single `as SessionContext` cast moves into `handleSessionStart` — the boundary where the SDK hands us the value.
664
+ Add typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessionInfo()`.
665
+
666
+ - Target: `src/runtime.ts`, `src/handlers/lifecycle.ts`, `src/service/service-adapter.ts`, `src/index.ts`
667
+ - Smell: Category C (closure queries on mutable field → methods on state owner)
668
+ - Outcome: 3 closure queries in index.ts → 0; `SubagentRuntime` is self-sufficient for tool deps
669
+ - Enables: Layer 3 (tools accept `SubagentRuntime` directly)
670
+
671
+ ### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194])
637
672
 
638
- ### Step 6: Extract RunContext from RunOptions ([#169][169]) ✓ Done
673
+ Three alignment changes:
639
674
 
640
- Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`, nested as `RunOptions.context`.
641
- `RunOptions` reduced from 12 to 9 fields (1 nested `context` + 8 flat execution fields).
675
+ 1. **Move `getMaxConcurrent` off `AgentToolManager`** it reads from `SettingsManager`, not `AgentManager`.
676
+ The tool already receives `settings`; read it from there.
677
+ 2. **Rename widget methods** — align `SubagentRuntime` method names with `AgentToolWidget` (either rename `updateWidget()` → `update()` on runtime, or rename the interface to match).
678
+ 3. **Remove dead re-export** — `getToolCallName` in `ui/message-formatters.ts` (fallow finding).
642
679
 
643
- ### Step 7: Reduce buildContentLines complexity ([#170][170]) Done
680
+ After this step, `AgentManager` structurally satisfies `AgentToolManager` and `SubagentRuntime` structurally satisfies `AgentToolWidget`.
644
681
 
645
- Extracted formatting sub-functions for each content type (user, assistant, tool result, bash execution, streaming indicator) into `ui/message-formatters.ts`.
646
- `buildContentLines` in `conversation-viewer.ts` is now a ~30-line dispatch loop delegating to `formatMessage` and `formatStreamingIndicator`.
682
+ - Target: `src/tools/agent-tool.ts` (interface), `src/runtime.ts` (method names), `src/ui/message-formatters.ts`
683
+ - Smell: Category C (source mismatch, name mismatch) + Category A (dead export)
684
+ - Outcome: structural typing connects real objects to tool interfaces without adapters
685
+ - Enables: Layer 3 (class constructors accept real objects directly)
647
686
 
648
- ### Step 8: Reduce renderResult complexity ([#171][171]) ✓ Done
687
+ ### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
649
688
 
650
- Extracted per-status result formatting from `renderResult` in `agent-tool.ts` into `tools/result-renderer.ts`.
651
- `renderResult` reduced from ~80 lines (cognitive complexity 43) to a 10-line guard + `renderAgentResult` dispatcher.
652
- The inline `stats()` closure became the exported `renderStats` helper, shared by all status renderers.
689
+ With Layers 0–2 complete, each factory is a mechanical conversion:
653
690
 
654
- ### Step 9: Extract shared turn-formatting logic ([#172][172]) ✓ Done
691
+ | Factory | Class | Constructor params |
692
+ | -------------------------------- | ------------------------------ | --------------------------------------------------- |
693
+ | `createAgentTool({...})` | `AgentTool` | `manager`, `runtime`, `settings`, `registry` |
694
+ | `createGetResultTool(...)` | `GetResultTool` | `manager`, `notifications`, `registry` |
695
+ | `createSteerTool(...)` | `SteerTool` | `manager`, `events` |
696
+ | `createAgentRunner(runnerIO)` | `AgentRunner` (concrete class) | `io: RunnerIO` |
697
+ | `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | `manager`, `registry`, `settings`, `fileOps`, paths |
655
698
 
656
- Extracted `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` into `session/content-items.ts`.
657
- Both `lifecycle/agent-runner.ts` (`getAgentConversation`) and `ui/message-formatters.ts` (`formatAssistantMessage`) now import from the shared module.
658
- Eliminates the 18-line production-duplication finding.
699
+ Each class satisfies the existing interface via structural typing.
700
+ The `defineTool()` wrapper moves into a `toToolDefinition()` method on each tool class.
701
+
702
+ - Target: `src/tools/*.ts`, `src/lifecycle/agent-runner.ts`, `src/ui/agent-menu.ts`
703
+ - Smell: Category C (closure factories masquerading as classes)
704
+ - Outcome: deps are constructor params (inspectable, testable); no captured closures
705
+ - Enables: Layer 4 (index.ts simplification)
706
+
707
+ ### Layer 4: Simplify index.ts (included in [#196][196])
708
+
709
+ With real objects satisfying tool interfaces and queries living on `SubagentRuntime`, the composition root becomes pure construction:
710
+
711
+ ```typescript
712
+ const runtime = new SubagentRuntime();
713
+ const settings = new SettingsManager(...);
714
+ const manager = new AgentManager(...);
715
+ const agentTool = new AgentTool(manager, runtime, settings, registry);
716
+ pi.registerTool(agentTool.toToolDefinition());
717
+ ```
718
+
719
+ No adapter closures.
720
+ No `as any`.
721
+ Fan-out drops from 25 to ~15 (internal factories eliminated).
722
+
723
+ - Target: `src/index.ts`
724
+ - Smell: Category B (god file) + Category C (adapter closure density)
725
+ - Outcome: index.ts shrinks from 280 to ~150 lines; churn hotspot stabilizes
659
726
 
660
727
  ### Step dependencies
661
728
 
662
729
  ```mermaid
663
730
  flowchart LR
664
- subgraph organization["Code organization"]
665
- S1["#164: Domain directories"]
666
- end
667
- subgraph bags["Dependency bags"]
668
- S2["#165: ResolvedSpawnConfig"] --> S3["#166: AgentSpawnConfig"]
669
- S4["#167: RunnerIO"]
670
- S5["#168: SessionConfig"]
671
- S6["#169: RunOptions"]
672
- end
673
- subgraph complexity["Complexity reduction"]
674
- S7["#170: buildContentLines"]
675
- S8["#171: renderResult"]
676
- S9["#172: Shared turn-formatting"]
677
- end
678
- S1 --> S2 & S4 & S5 & S6
679
- S1 --> S7 & S8 & S9
731
+ L0["Layer 0: SessionContext interface"] --> L1["Layer 1: Runtime owns queries"]
732
+ L1 --> L3["Layer 3: Classes replace factories"]
733
+ L2["Layer 2: Align interfaces"] --> L3
734
+ L3 --> L4["Layer 4: Simplify index.ts"]
680
735
  ```
681
736
 
682
- Step 1 ([#164][164], directory restructuring) unblocks all other steps by co-locating related files.
683
- Steps 2–6 (bag decomposition) and Steps 7–9 (complexity reduction) are independent tracks that can proceed in parallel.
684
- Within the bag track, Step 2 ([#165][165], ResolvedSpawnConfig) enables Step 3 ([#166][166], AgentSpawnConfig).
737
+ Layers 0 and 2 are independent of each other.
738
+ Layer 1 depends on Layer 0.
739
+ Layer 3 depends on both Layer 1 and Layer 2.
740
+ Layer 4 depends on Layer 3.
741
+
742
+ ## Improvement roadmap (Phase 12)
743
+
744
+ Phase 12 addresses the remaining fallow refactoring targets and test duplication.
745
+ These are independent of Phase 11 and can proceed in parallel if desired.
746
+
747
+ ### Step 1: Decompose `renderWidgetLines` (cognitive 44)
748
+
749
+ `renderWidgetLines` in `ui/widget-renderer.ts` handles agent-status formatting, tree connectors, overflow, and empty states.
750
+ Extract per-status renderers and a tree-connector utility.
751
+
752
+ - Target: `src/ui/widget-renderer.ts`
753
+ - Outcome: cognitive complexity < 10
754
+
755
+ ### Step 2: Decompose `showAgentDetail` (cognitive 33)
756
+
757
+ `showAgentDetail` in `ui/agent-config-editor.ts` handles display, edit, eject, and delete flows.
758
+ Extract sub-functions per menu action.
759
+
760
+ - Target: `src/ui/agent-config-editor.ts`
761
+ - Outcome: cognitive complexity < 10
762
+
763
+ ### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31)
764
+
765
+ `update` mixes timer lifecycle, agent list assembly, render delegation, and visibility state.
766
+ Extract `assembleWidgetState` (pure) and timer management.
767
+
768
+ - Target: `src/ui/agent-widget.ts`
769
+ - Outcome: cognitive complexity < 10
770
+
771
+ ### Step 4: Extract shared test fixtures
772
+
773
+ The 3 heaviest clone families:
774
+
775
+ - `agent-runner.test.ts` + `agent-runner-extension-tools.test.ts` (60-line shared setup)
776
+ - `agent-menu.test.ts` + `agent-creation-wizard.test.ts` + `agent-config-editor.test.ts` (54+51+24 lines)
777
+ - `agent-manager.test.ts` (18 internal clone groups, 210 duplicated lines)
778
+
779
+ Extract shared factories into `test/fixtures/` modules.
780
+
781
+ - Target: new `test/fixtures/` modules
782
+ - Outcome: test duplication reduced by ~400 lines
685
783
 
686
784
  ## Refactoring history
687
785
 
688
- Phases 1–5 and 7–9 are complete.
786
+ Phases 1–5 and 7–10 are complete.
689
787
  Phase 6 (UI extraction to a separate package) is deferred.
690
788
  Detailed records are preserved in per-phase history files:
691
789
 
692
- | Phase | Title | Status | History |
693
- | ----- | --------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
694
- | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
695
- | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
696
- | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
697
- | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
698
- | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
699
- | 6 | Extract UI to separate package | Deferred | — |
700
- | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
701
- | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
702
- | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
790
+ | Phase | Title | Status | History |
791
+ | ----- | --------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
792
+ | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
793
+ | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
794
+ | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
795
+ | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
796
+ | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
797
+ | 6 | Extract UI to separate package | Deferred | — |
798
+ | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
799
+ | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
800
+ | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
801
+ | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
703
802
 
704
803
  ### Structural refactoring issues
705
804
 
706
- | Phase | Issue | Summary |
707
- | ------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
708
- | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
709
- | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
710
- | Interface polish | #66, #77 | SDK types, projectAgentsDir |
711
- | Features | #61 | JSONL session transcripts |
712
- | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
713
- | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
714
- | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
715
- | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
716
- | Phase 10 | #164, #166, #167, #168, #169, #170, #171 | Domain directories, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult |
805
+ | Phase | Issue | Summary |
806
+ | ------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
807
+ | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
808
+ | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
809
+ | Interface polish | #66, #77 | SDK types, projectAgentsDir |
810
+ | Features | #61 | JSONL session transcripts |
811
+ | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
812
+ | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
813
+ | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
814
+ | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
815
+ | Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
717
816
 
718
817
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
719
818
 
@@ -733,12 +832,12 @@ The upstream test suite is run periodically as a regression canary for the agent
733
832
  [gotgenes/pi-packages]: https://github.com/gotgenes/pi-packages
734
833
  [tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
735
834
 
736
- [164]: https://github.com/gotgenes/pi-packages/issues/164
737
- [165]: https://github.com/gotgenes/pi-packages/issues/165
738
835
  [166]: https://github.com/gotgenes/pi-packages/issues/166
739
836
  [167]: https://github.com/gotgenes/pi-packages/issues/167
740
837
  [168]: https://github.com/gotgenes/pi-packages/issues/168
741
838
  [169]: https://github.com/gotgenes/pi-packages/issues/169
742
- [170]: https://github.com/gotgenes/pi-packages/issues/170
743
- [171]: https://github.com/gotgenes/pi-packages/issues/171
744
- [172]: https://github.com/gotgenes/pi-packages/issues/172
839
+ [192]: https://github.com/gotgenes/pi-packages/issues/192
840
+ [193]: https://github.com/gotgenes/pi-packages/issues/193
841
+ [194]: https://github.com/gotgenes/pi-packages/issues/194
842
+ [195]: https://github.com/gotgenes/pi-packages/issues/195
843
+ [196]: https://github.com/gotgenes/pi-packages/issues/196
@@ -0,0 +1,141 @@
1
+ # Phase 10: Domain organization, bag decomposition, and complexity reduction
2
+
3
+ Target: reorganize source into domain directories; decompose remaining wide parameter bags into focused value objects; reduce cyclomatic complexity in rendering functions; eliminate production code duplication.
4
+
5
+ ## Current smells
6
+
7
+ | Smell | Location | Evidence | Severity |
8
+ | ------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------- |
9
+ | Flat `src/` directory | `src/` root | 20+ files in a single directory with no domain grouping; hard to see module boundaries | Medium |
10
+ | `ResolvedSpawnConfig` is a 15-field bag | `spawn-config.ts` | Mixes identity (name, slug), execution (model, permissions), and presentation (icon, color) concerns in one interface | Medium |
11
+ | `AgentSpawnConfig` threads parent session fields | `agent-manager.ts` | `parentSessionFile`, `parentSessionId`, `toolCallId` always travel together but are separate parameters | Low |
12
+ | `RunnerIO` is too wide | `foreground-runner.ts` | 9 methods spanning environment concerns (cwd, env) and session factory concerns (create, attach, resume) | Medium |
13
+ | `SessionConfig` mixes tool filtering with session setup | `session-config.ts` | `toolNames`, `disallowedSet`, `extensions` are a cohesive filter group buried in a larger config | Low |
14
+ | `RunOptions` is a 12-field bag | `foreground-runner.ts` | Mixes execution context (exec, registry, cwd) with per-run options (model, prompt, permissions) | Medium |
15
+ | `buildContentLines` high complexity | `ui/build-content-lines.ts` | ~60-line function with a switch over every content type; each branch is an independent formatter | Medium |
16
+ | `renderResult` high complexity | `tools/agent-tool.ts` | ~80-line function with status-based branching; each status is an independent rendering concern | Medium |
17
+ | Duplicated turn-formatting logic | `session/session-runner.ts`, `tools/agent-tool.ts` | 18 lines of identical `ToolCallContent` extraction and `getToolCallName` logic in two production files | Low |
18
+
19
+ ## Step 1: Reorganize source into domain directories (#164)
20
+
21
+ Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
22
+ All `src/` internal imports now use `#src/` path aliases.
23
+ Root `src/` reduced to 5 files + 8 directories.
24
+
25
+ Impact: domain boundaries are visible in the directory tree; imports communicate intent via path alias.
26
+
27
+ ## Step 2: Decompose ResolvedSpawnConfig (#165)
28
+
29
+ Split the 15-field `ResolvedSpawnConfig` into three focused value objects:
30
+
31
+ - `SpawnIdentity` — name, slug, key
32
+ - `SpawnExecution` — model, permissions, tools, prompt, systemPrompt
33
+ - `SpawnPresentation` — icon, color, description
34
+
35
+ `ResolvedSpawnConfig` composes these three plus the remaining spawn-level fields.
36
+
37
+ Impact: each consumer receives only the fields it needs; adding a presentation field no longer touches execution code.
38
+
39
+ ## Step 3: Extract ParentSessionInfo from AgentSpawnConfig (#166)
40
+
41
+ Extracted `parentSessionFile`, `parentSessionId`, and `toolCallId` into `ParentSessionInfo`.
42
+ `AgentSpawnConfig` and `AgentManager.spawn()` accept the single value object instead of three separate parameters.
43
+
44
+ Impact: eliminated parameter co-travel; the three fields that always move together are now a single concept.
45
+
46
+ ## Step 4: Narrow RunnerIO (#167)
47
+
48
+ Split `RunnerIO` into two focused interfaces:
49
+
50
+ - `EnvironmentIO` (3 methods) — cwd, env, platform concerns
51
+ - `SessionFactoryIO` (5+1 methods) — create, attach, resume, and related session lifecycle
52
+
53
+ `RunnerIO` kept as a backward-compatible type alias (`EnvironmentIO & SessionFactoryIO`).
54
+
55
+ Impact: consumers declare which IO surface they actually need; test doubles shrink to the relevant subset.
56
+
57
+ ## Step 5: Extract ToolFilterConfig from SessionConfig (#168)
58
+
59
+ Grouped `toolNames`, `disallowedSet`, and `extensions` into `ToolFilterConfig`.
60
+ `filterActiveTools` accepts a single `ToolFilterConfig` argument instead of three separate parameters.
61
+
62
+ Impact: tool-filtering concern is encapsulated; `SessionConfig` is narrower and more focused.
63
+
64
+ ## Step 6: Extract RunContext from RunOptions (#169)
65
+
66
+ Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`.
67
+ `RunOptions` reduced from 12 fields to 9 (the 4 extracted fields replaced by a single `context` field, plus the remaining per-run options).
68
+
69
+ Impact: execution context is a reusable value object; `RunOptions` focuses on per-run configuration.
70
+
71
+ ## Step 7: Reduce buildContentLines complexity (#170)
72
+
73
+ Extracted per-content-type formatters into `ui/message-formatters.ts`.
74
+ Each content type (text, tool-use, tool-result, image, etc.) has a dedicated pure function.
75
+ `buildContentLines` is now a ~30-line dispatch loop that delegates to the appropriate formatter.
76
+
77
+ Impact: cyclomatic complexity of `buildContentLines` dropped from ~15 to ~5; each formatter is independently testable.
78
+
79
+ ## Step 8: Reduce renderResult complexity (#171)
80
+
81
+ Extracted per-status formatters into `tools/result-renderer.ts`.
82
+ Each result status (success, error, timeout, cancelled) has a dedicated pure function.
83
+ `renderResult` reduced from ~80 lines to a 10-line guard that dispatches to the appropriate renderer.
84
+
85
+ Impact: cyclomatic complexity of `renderResult` dropped from ~10 to ~3; status rendering is independently testable.
86
+
87
+ ## Step 9: Extract shared turn-formatting logic (#172)
88
+
89
+ Extracted `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` into `session/content-items.ts`.
90
+ Both `session/session-runner.ts` and `tools/agent-tool.ts` import from the shared module.
91
+
92
+ Impact: eliminated 18 lines of production code duplication; single source of truth for turn-content extraction.
93
+
94
+ ## Step dependencies
95
+
96
+ ```mermaid
97
+ flowchart LR
98
+ subgraph bag["Bag decomposition track"]
99
+ S2["2: Decompose ResolvedSpawnConfig #165"] --> S3["3: Extract ParentSessionInfo #166"]
100
+ S4["4: Narrow RunnerIO #167"]
101
+ S5["5: Extract ToolFilterConfig #168"]
102
+ S6["6: Extract RunContext #169"]
103
+ end
104
+ subgraph complexity["Complexity reduction track"]
105
+ S7["7: Reduce buildContentLines #170"]
106
+ S8["8: Reduce renderResult #171"]
107
+ S9["9: Extract turn-formatting #172"]
108
+ end
109
+ S1["1: Reorganize into domain dirs #164"] --> bag
110
+ S1 --> complexity
111
+ ```
112
+
113
+ Step 1 unblocked all other steps.
114
+ Within the bag decomposition track, Step 2 enabled Step 3.
115
+ The bag decomposition and complexity reduction tracks were independent of each other.
116
+
117
+ ## Impact
118
+
119
+ | Metric | Before | After |
120
+ | ------------------------------ | ------------------------------ | --------------------------------------------- |
121
+ | Health score | ~65 | 75 |
122
+ | `src/` root files | 20+ | 5 files + 8 directories |
123
+ | `ResolvedSpawnConfig` fields | 15 | 3 composed value objects |
124
+ | `RunOptions` fields | 12 | 9 |
125
+ | `RunnerIO` methods | 9 | 3 (`EnvironmentIO`) + 6 (`SessionFactoryIO`) |
126
+ | `buildContentLines` complexity | ~15 | ~5 |
127
+ | `renderResult` lines | ~80 | ~10 |
128
+ | Production duplication | 18 lines | 0 |
129
+ | Test duplication | ~1,400 lines (69 clone groups) | ~1,400 lines (69 clone groups) — not targeted |
130
+
131
+ ## Related issues
132
+
133
+ - #164 — Reorganize source into domain directories
134
+ - #165 — Decompose ResolvedSpawnConfig
135
+ - #166 — Extract ParentSessionInfo from AgentSpawnConfig
136
+ - #167 — Narrow RunnerIO
137
+ - #168 — Extract ToolFilterConfig from SessionConfig
138
+ - #169 — Extract RunContext from RunOptions
139
+ - #170 — Reduce buildContentLines complexity
140
+ - #171 — Reduce renderResult complexity
141
+ - #172 — Extract shared turn-formatting logic