@gotgenes/pi-subagents 7.8.1 → 9.0.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.
@@ -0,0 +1,88 @@
1
+ # Phase 13: Remaining structural smells
2
+
3
+ ## Summary
4
+
5
+ Phase 13 addressed the remaining fallow refactoring target, oversized methods, production duplication, SDK boundary coupling, and test clone families.
6
+ All six steps are closed: [#214], [#215], [#216], [#217], [#218], [#219].
7
+
8
+ ## Steps
9
+
10
+ ### Step 1: Convert remaining closure factories to classes — [#214]
11
+
12
+ Three closure factories converted to classes, making their dependencies explicit as constructor parameters.
13
+
14
+ | Factory → Class | File |
15
+ | ------------------------------------------------------ | ----------------------------- |
16
+ | `createAgentConfigEditor()` → `AgentConfigEditor` | `ui/agent-config-editor.ts` |
17
+ | `createAgentCreationWizard()` → `AgentCreationWizard` | `ui/agent-creation-wizard.ts` |
18
+ | `createSubagentsService()` → `SubagentsServiceAdapter` | `service/service-adapter.ts` |
19
+
20
+ - Outcome: 0 remaining closure factories (excluding pure-function factories)
21
+
22
+ ### Step 2: Decompose `buildParentContext` (cognitive 30) — [#215]
23
+
24
+ `buildParentContext` in `session/context.ts` was the only remaining fallow refactoring target.
25
+ Extracted per-entry-type formatters: `formatMessageEntry()` and `formatCompactionEntry()`.
26
+
27
+ - Target: `src/session/context.ts`
28
+ - Outcome: cognitive complexity < 10, function < 15 LOC, 0 fallow refactoring targets
29
+
30
+ ### Step 3: Decompose `startAgent` in `agent-manager.ts` — [#216]
31
+
32
+ `startAgent` had two mutable closure variables and duplicated finalization logic in `.then()`/`.catch()`.
33
+ Introduced `RunHandle` lifecycle object (private to `agent-manager.ts`) that owns per-run cleanup state.
34
+ `WorktreeState` gained `performCleanup()` to eliminate ask-tell at cleanup sites.
35
+
36
+ Extracted:
37
+
38
+ 1. `RunHandle` class — owns `unsub`/`detachFn`, `complete()`, `fail()`, idempotent `fireOnFinished()`.
39
+ 2. `finalizeBackgroundRun()` — shared background finalization.
40
+ 3. `setupWorktree()` — worktree creation with strict failure.
41
+ 4. `flushPendingSteers()` — drain buffered steers on session creation.
42
+ 5. `WorktreeState.performCleanup()` — self-cleanup eliminating ask-tell.
43
+
44
+ - Target: `src/lifecycle/agent-manager.ts`, `src/lifecycle/worktree-state.ts`
45
+ - Outcome: `startAgent` reduced to ~40 LOC; zero mutable `let` bindings in `.then()`/`.catch()`
46
+
47
+ ### Step 4: Extract overwrite guard from UI — [#217]
48
+
49
+ Extracted the 20-line pattern duplicated between `agent-config-editor.ts` and `agent-creation-wizard.ts` into `writeAgentFile()` in `src/ui/agent-file-writer.ts`.
50
+
51
+ - Target: new `src/ui/agent-file-writer.ts`
52
+ - Outcome: 0 production clone groups (at the time; one internal group re-emerged later in `agent-config-editor.ts`)
53
+
54
+ ### Step 5: Push SDK boundary in `settings.ts` — [#218]
55
+
56
+ Injected `agentDir: string` as a constructor parameter to `SettingsManager`, replacing the module-level `getAgentDir()` SDK call.
57
+
58
+ - Target: `src/settings.ts`, `src/index.ts`
59
+ - Outcome: `settings.ts` has 0 Pi SDK imports; `loadSettings`/`saveSettings` fully testable without SDK stubs
60
+
61
+ ### Step 6: Reduce test duplication — top 3 clone families — [#219]
62
+
63
+ Extracted shared setup/assertion helpers for the three heaviest test clone families.
64
+
65
+ - Target: `test/lifecycle/agent-manager.test.ts`, `test/conversation-viewer.test.ts`, `test/ui/agent-config-editor.test.ts`
66
+ - Outcome: test duplication significantly reduced
67
+
68
+ ## Metrics change
69
+
70
+ | Metric | Before | After |
71
+ | -------------------------- | ---------------------- | --------------------------------- |
72
+ | Health score | 78/100 (B) | 78/100 (B) |
73
+ | Source files | 53 | 56 |
74
+ | Total LOC | 8,180 | 8,382 |
75
+ | Dead code | 0 files, 0 exports | 0 files, 0 exports |
76
+ | Maintainability index | 90.7 | 90.8 |
77
+ | Avg cyclomatic complexity | 1.5 | 1.4 |
78
+ | Fallow refactoring targets | 1 | 0 |
79
+ | Production duplication | 0 clone groups | 1 internal clone group (11 lines) |
80
+ | Test duplication | 59 groups, 1,046 lines | 38 groups, 645 lines (overall) |
81
+ | Churn hotspots | 6 accelerating | 1 accelerating (`index.ts`) |
82
+
83
+ [#214]: https://github.com/gotgenes/pi-packages/issues/214
84
+ [#215]: https://github.com/gotgenes/pi-packages/issues/215
85
+ [#216]: https://github.com/gotgenes/pi-packages/issues/216
86
+ [#217]: https://github.com/gotgenes/pi-packages/issues/217
87
+ [#218]: https://github.com/gotgenes/pi-packages/issues/218
88
+ [#219]: https://github.com/gotgenes/pi-packages/issues/219
@@ -0,0 +1,180 @@
1
+ ---
2
+ issue: 237
3
+ issue_title: "Remove disallowed_tools from pi-subagents (Phase 14, Step 1)"
4
+ ---
5
+
6
+ # Remove `disallowed_tools` from pi-subagents
7
+
8
+ ## Problem Statement
9
+
10
+ The `disallowed_tools` frontmatter field and its runtime counterpart `disallowedSet` duplicate what pi-permission-system already provides via the `permission:` frontmatter with richer semantics (allow/ask/deny vs. binary hide).
11
+ Removing it establishes a single source of truth for tool access control in pi-permission-system and simplifies `filterActiveTools`, `ToolFilterConfig`, and `AgentConfig`.
12
+
13
+ ## Goals
14
+
15
+ - Remove the `disallowedTools` field from `AgentConfig` and all code that reads, parses, or serializes it.
16
+ - Remove the `disallowedSet` field from `ToolFilterConfig` and the construction logic in `assembleSessionConfig`.
17
+ - Remove the `disallowedSet` branch from `filterActiveTools` in `agent-runner.ts`.
18
+ - Remove `disallowed_tools` from the agent config editor UI and the agent creation wizard.
19
+ - Update README.md to remove all `disallowed_tools` references and add a migration note.
20
+ - Update tests to remove `disallowedTools`/`disallowedSet` assertions and fixture data.
21
+ - This is a **breaking change** — users with `disallowed_tools` in agent frontmatter must migrate to `permission:` frontmatter.
22
+
23
+ ## Non-Goals
24
+
25
+ - Removing `extensions` filtering (Step 2, #238).
26
+ - Collapsing `filterActiveTools` to a recursion guard (Step 3, #239).
27
+ - Changing any pi-permission-system code — this issue is purely pi-subagents.
28
+ - Adding deprecation warnings or runtime migration helpers — the architecture doc calls for clean removal.
29
+
30
+ ## Background
31
+
32
+ ### Phase 14 context
33
+
34
+ This is Phase 14, Step 1 of the architecture roadmap in `docs/architecture/architecture.md`.
35
+ Steps 1 and 2 (#237, #238) are independent; Step 3 (#239) depends on both.
36
+
37
+ ### Files involved
38
+
39
+ | File | Role |
40
+ | --------------------------------- | -------------------------------------------------------------------------- |
41
+ | `src/types.ts` | `AgentConfig.disallowedTools` field definition |
42
+ | `src/config/custom-agents.ts` | Parses `disallowed_tools` from YAML frontmatter |
43
+ | `src/session/session-config.ts` | `ToolFilterConfig.disallowedSet` + construction in `assembleSessionConfig` |
44
+ | `src/lifecycle/agent-runner.ts` | `filterActiveTools` denylist branch + guard conditions at two call sites |
45
+ | `src/ui/agent-config-editor.ts` | Serializes `disallowedTools` to `disallowed_tools` frontmatter |
46
+ | `src/ui/agent-creation-wizard.ts` | Template text showing `disallowed_tools` as an available field |
47
+ | `README.md` | Feature list, frontmatter table, memory detection note, patch notes |
48
+
49
+ ### `filterActiveTools` after this change
50
+
51
+ With `disallowedSet` removed, the function simplifies:
52
+
53
+ ```typescript
54
+ function filterActiveTools(
55
+ activeTools: string[],
56
+ config: ToolFilterConfig,
57
+ ): string[] {
58
+ const { toolNames, extensions } = config;
59
+ if (extensions === false) {
60
+ return activeTools;
61
+ }
62
+ const builtinToolNameSet = new Set(toolNames);
63
+ return activeTools.filter((t) => {
64
+ if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
65
+ if (builtinToolNameSet.has(t)) return true;
66
+ if (Array.isArray(extensions)) {
67
+ return extensions.some((ext) => t.startsWith(ext) || t.includes(ext));
68
+ }
69
+ return true;
70
+ });
71
+ }
72
+ ```
73
+
74
+ The `extensions === false` branch becomes a trivial passthrough — no filtering at all.
75
+ The guard condition at both call sites simplifies from `cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet` to `cfg.toolFilter.extensions !== false`.
76
+
77
+ ## Design Overview
78
+
79
+ This is a pure removal — no new types, no new modules, no new behavior.
80
+ Every change deletes or simplifies existing code.
81
+
82
+ ### Migration path
83
+
84
+ Users currently using `disallowed_tools` in agent frontmatter migrate to pi-permission-system's `permission:` frontmatter:
85
+
86
+ ```yaml
87
+ # Before
88
+ disallowed_tools: bash
89
+
90
+ # After
91
+ permission:
92
+ bash: deny
93
+ ```
94
+
95
+ ## Module-Level Changes
96
+
97
+ 1. **`src/types.ts`** — Remove the `disallowedTools` field and its JSDoc comment from `AgentConfig`.
98
+ 2. **`src/config/custom-agents.ts`** — Remove the `disallowedTools: csvListOptional(fm.disallowed_tools)` line from the agent config construction.
99
+ 3. **`src/session/session-config.ts`** — Remove `disallowedSet` from the `ToolFilterConfig` interface.
100
+ Remove the `disallowedSet` construction block and its comment in `assembleSessionConfig`.
101
+ Remove `disallowedSet` from the `toolFilter` object literal.
102
+ 4. **`src/lifecycle/agent-runner.ts`** — Remove the `disallowedSet` destructure and denylist branches from `filterActiveTools`.
103
+ Simplify the `extensions === false` branch to `return activeTools`.
104
+ Simplify both guard conditions from `cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet` to `cfg.toolFilter.extensions !== false`.
105
+ Update comments that mention `disallowedTools denylist`.
106
+ 5. **`src/ui/agent-config-editor.ts`** — Remove the two lines that serialize `disallowedTools` to `disallowed_tools` frontmatter.
107
+ 6. **`src/ui/agent-creation-wizard.ts`** — Remove the `disallowed_tools:` line from the template text.
108
+ 7. **`README.md`** — Remove the "Tool denylist" feature bullet, the `disallowed_tools` row from the frontmatter table, the memory write-capability paragraph (memory was removed in #185; this reference is already stale), the `disallowed_tools` example in the memory section, and the `disallowedTools` mention in the Patch 2 notes.
109
+ Add a migration note under a "Migration" heading or in the breaking changes section.
110
+
111
+ ### Test files
112
+
113
+ 8. **`test/config/custom-agents.test.ts`** — Remove the two `disallowed_tools` tests ("parses disallowed_tools as csv list" and "disallowed_tools defaults to undefined when omitted").
114
+ 9. **`test/session/session-config.test.ts`** — Remove the `disallowedSet` assertion from the default config test and remove the entire "builds disallowedSet from agentConfig.disallowedTools" test.
115
+ 10. **`test/lifecycle/agent-runner-extension-tools.test.ts`** — Remove `disallowedTools` from both mock agent config objects.
116
+ Remove the three `disallowedTools`-specific tests ("post-bind re-filter respects disallowedTools denylist", "extensions: false still applies disallowedTools", "extensions: false with no disallowedTools skips the filter").
117
+ Update the file-level JSDoc comment.
118
+ 11. **`test/ui/agent-config-editor.test.ts`** — Remove `disallowedTools` from the test fixture and remove the `disallowed_tools` assertion.
119
+
120
+ ### Architecture docs
121
+
122
+ 12. **`docs/architecture/architecture.md`** — Mark Step 1 as complete (update the step heading or add a completion note).
123
+
124
+ ## Test Impact Analysis
125
+
126
+ 1. **Tests removed** — 7 tests that directly assert `disallowedTools`/`disallowedSet` behavior become meaningless and are deleted:
127
+ - `custom-agents.test.ts`: 2 tests (csv parsing, undefined default)
128
+ - `session-config.test.ts`: 1 test (`disallowedSet` construction) + 1 assertion in default config test
129
+ - `agent-runner-extension-tools.test.ts`: 3 tests (denylist for extension tools, denylist for built-in tools with `extensions: false`, filter skip with `extensions: false` + no denylist)
130
+ - `agent-config-editor.test.ts`: 1 assertion in the serialization test
131
+
132
+ 2. **Tests updated** — Several tests have `disallowedTools` in their mock fixtures but don't test it directly; those fixtures lose the field:
133
+ - `agent-runner-extension-tools.test.ts`: both mock config objects drop `disallowedTools`
134
+
135
+ 3. **Tests unchanged** — The remaining `agent-runner-extension-tools.test.ts` tests (`extensions: true`, `extensions: string[]` allowlist, `extensions: false` passthrough) remain valid — they test extension filtering, not the denylist.
136
+
137
+ ## TDD Order
138
+
139
+ 1. **Remove `disallowedTools` from `AgentConfig`** — Delete the field and JSDoc from `types.ts`.
140
+ Remove `disallowedTools` parsing from `custom-agents.ts`.
141
+ Remove the two `disallowed_tools` tests from `custom-agents.test.ts`.
142
+ Run `pnpm run check` to surface all downstream type errors.
143
+ Commit: `feat!: remove disallowedTools from AgentConfig`
144
+
145
+ 2. **Remove `disallowedSet` from `ToolFilterConfig` and `assembleSessionConfig`** — Delete the field from `ToolFilterConfig`, the construction block in `assembleSessionConfig`, and the `disallowedSet` key from the return literal.
146
+ Remove the `disallowedSet`-specific test and assertion from `session-config.test.ts`.
147
+ Run `pnpm run check`.
148
+ Commit: `refactor: remove disallowedSet from ToolFilterConfig`
149
+
150
+ 3. **Simplify `filterActiveTools` and guard conditions** — Remove the `disallowedSet` destructure and denylist branches from `filterActiveTools`.
151
+ Simplify the `extensions === false` branch to `return activeTools`.
152
+ Simplify both guard conditions to `cfg.toolFilter.extensions !== false`.
153
+ Update comments.
154
+ Remove the three `disallowedTools`-specific tests and update mock fixtures in `agent-runner-extension-tools.test.ts`.
155
+ Run tests: `pnpm vitest run`.
156
+ Commit: `refactor: remove disallowedSet branch from filterActiveTools`
157
+
158
+ 4. **Remove `disallowed_tools` from UI** — Remove serialization from `agent-config-editor.ts` and the template line from `agent-creation-wizard.ts`.
159
+ Remove `disallowedTools` from the test fixture and assertion in `agent-config-editor.test.ts`.
160
+ Run tests: `pnpm vitest run`.
161
+ Commit: `refactor: remove disallowed_tools from config editor and creation wizard`
162
+
163
+ 5. **Update README.md** — Remove all `disallowed_tools` references (feature bullet, frontmatter table row, stale memory paragraph, example, patch notes mention).
164
+ Add a migration note directing users to `permission:` frontmatter.
165
+ Commit: `docs: remove disallowed_tools from README and add migration note`
166
+
167
+ 6. **Mark Phase 14 Step 1 complete in architecture doc** — Update `docs/architecture/architecture.md` to reflect completion.
168
+ Commit: `docs: mark Phase 14 Step 1 complete in architecture`
169
+
170
+ ## Risks and Mitigations
171
+
172
+ | Risk | Mitigation |
173
+ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | Users with existing `disallowed_tools` frontmatter silently lose tool restrictions | Breaking change is intentional and documented; `feat!:` commit triggers a major version bump via release-please; README migration note directs to `permission:` frontmatter |
175
+ | `filterActiveTools` behavior regresses for non-denylist paths | Steps 1–3 each run `pnpm run check` or `pnpm vitest run`; existing extension-filtering tests remain unchanged |
176
+ | Stale references to `disallowed_tools` in docs or plans | Grep sweep in step 5 covers README; architecture doc updated in step 6; historical plan/retro files are left as-is (they document past state) |
177
+
178
+ ## Open Questions
179
+
180
+ None — the issue's proposed change is unambiguous and the architecture doc provides detailed step-by-step guidance.
@@ -0,0 +1,191 @@
1
+ ---
2
+ issue: 238
3
+ issue_title: "Remove extensions filtering from pi-subagents (Phase 14, Step 2)"
4
+ ---
5
+
6
+ # Remove `extensions` filtering from pi-subagents
7
+
8
+ ## Problem Statement
9
+
10
+ The `extensions: string[]` allowlist in agent configuration is tool filtering disguised as extension lifecycle control.
11
+ Extensions always initialize via `bindExtensions()` — the allowlist only hides their tools from the active set afterward, which is pi-permission-system's responsibility.
12
+ Simplifying `extensions` from `true | string[] | false` to `boolean` removes this overlap and makes pi-permission-system the sole authority for tool access control.
13
+
14
+ ## Goals
15
+
16
+ - Simplify `extensions` from `true | string[] | false` to `boolean` in `AgentConfig`, `ToolFilterConfig`, and all related code.
17
+ - Remove the `Array.isArray(extensions)` branch from `filterActiveTools` in `agent-runner.ts`.
18
+ - Remove extensions array handling from the agent config editor and creation wizard.
19
+ - Update custom agent frontmatter parsing to coerce array values to `true` (with a deprecation warning).
20
+ - Update tests to remove `extensions: string[]` assertions and fixture data.
21
+ - This is a **breaking change** — users with `extensions: <csv-list>` in agent frontmatter lose per-extension filtering.
22
+
23
+ ## Non-Goals
24
+
25
+ - Collapsing `filterActiveTools` to a recursion guard (Step 3, #239).
26
+ - Removing `extensions: false` — it is retained here (used by `isolated`) and dissolved in Phase 16.
27
+ - Changing any pi-permission-system code — this issue is purely pi-subagents.
28
+ - Removing Patch 2 (post-bind re-filter) — it still serves the `EXCLUDED_TOOL_NAMES` recursion guard when `extensions === true`.
29
+ Issue #239 collapses the two-pass dance.
30
+
31
+ ## Background
32
+
33
+ ### Phase 14 context
34
+
35
+ This is Phase 14, Step 2 of the architecture roadmap in `docs/architecture/architecture.md`.
36
+ Steps 1 (#237, completed) and 2 (#238) are independent; Step 3 (#239) depends on both.
37
+
38
+ ### Files involved
39
+
40
+ | File | Role |
41
+ | --------------------------------- | --------------------------------------------------------------------------- |
42
+ | `src/types.ts` | `AgentConfig.extensions` field type |
43
+ | `src/config/custom-agents.ts` | Parses `extensions` from YAML frontmatter via `inheritField()` |
44
+ | `src/session/session-config.ts` | `ToolFilterConfig.extensions` type + passthrough in `assembleSessionConfig` |
45
+ | `src/lifecycle/agent-runner.ts` | `filterActiveTools` array branch + Patch 2 comment |
46
+ | `src/ui/agent-config-editor.ts` | Serializes `extensions` array to frontmatter |
47
+ | `src/ui/agent-creation-wizard.ts` | Template text describing `extensions` field options |
48
+ | `README.md` | Frontmatter table row, Patch 2 notes |
49
+
50
+ ### `filterActiveTools` after this change
51
+
52
+ ```typescript
53
+ function filterActiveTools(
54
+ activeTools: string[],
55
+ config: ToolFilterConfig,
56
+ ): string[] {
57
+ const { toolNames, extensions } = config;
58
+ if (extensions === false) {
59
+ return activeTools;
60
+ }
61
+ const builtinToolNameSet = new Set(toolNames);
62
+ return activeTools.filter((t) => {
63
+ if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
64
+ if (builtinToolNameSet.has(t)) return true;
65
+ return true;
66
+ });
67
+ }
68
+ ```
69
+
70
+ The `Array.isArray(extensions)` branch is gone.
71
+ The `builtinToolNameSet.has(t)` check becomes redundant (always returns true for builtins, falls through to `return true` for non-builtins) — but simplifying further is #239's scope.
72
+
73
+ ### Custom agent frontmatter migration
74
+
75
+ The `inheritField()` function currently parses CSV values into `string[]`.
76
+ After this change, it must still accept array values gracefully: coerce them to `true` and emit a warning so users know to update their frontmatter.
77
+
78
+ ## Design Overview
79
+
80
+ This is primarily a removal — no new types, no new modules.
81
+ The one piece of new behavior is the deprecation warning when `inheritField` encounters an array value for `extensions`.
82
+
83
+ ### `inheritField` change
84
+
85
+ Currently `inheritField` is shared between `extensions` and `skills` parsing.
86
+ Since `skills` still supports `string[]`, the function itself cannot change.
87
+ Instead, the call site for `extensions` will post-process: if `inheritField` returns `string[]`, coerce to `true` and log a warning.
88
+
89
+ ### Type change scope
90
+
91
+ `AgentConfig.extensions` changes from `true | string[] | false` to `boolean`.
92
+ `ToolFilterConfig.extensions` changes from `boolean | string[]` to `boolean`.
93
+ Both are narrowing changes — existing `true` and `false` values remain valid.
94
+
95
+ ## Module-Level Changes
96
+
97
+ 1. **`src/types.ts`** — Change `extensions` type from `true | string[] | false` to `boolean`.
98
+ Update JSDoc comment to remove the `string[] = only listed` description.
99
+ 2. **`src/config/custom-agents.ts`** — After calling `inheritField(fm.extensions ?? fm.inherit_extensions)`, coerce `string[]` results to `true` and emit a `debugLog` warning.
100
+ The warning informs users that `extensions: <csv-list>` is no longer supported and treated as `extensions: true`.
101
+ 3. **`src/session/session-config.ts`** — Change `ToolFilterConfig.extensions` type from `boolean | string[]` to `boolean`.
102
+ Update the JSDoc comment.
103
+ The `assembleSessionConfig` passthrough `const extensions = options.isolated ? false : agentConfig.extensions;` works without changes since the type narrows.
104
+ 4. **`src/lifecycle/agent-runner.ts`** — Remove the `Array.isArray(extensions)` branch (lines 47–49) from `filterActiveTools`.
105
+ Update the Patch 2 comment (line 366) that references `extensions: string[]`.
106
+ Update the comment at line 341 ("apply extension allowlist if specified").
107
+ 5. **`src/ui/agent-config-editor.ts`** — Remove the `else if (Array.isArray(cfg.extensions))` branch (lines 51–52) from `buildEjectContent`.
108
+ 6. **`src/ui/agent-creation-wizard.ts`** — Simplify the `extensions:` line in the template text from `<true (inherit all MCP/extension tools), false (none), or comma-separated names. Default: true>` to `<true (inherit all MCP/extension tools) or false (none). Default: true>`.
109
+ 7. **`README.md`** — Simplify the `extensions` row in the frontmatter table to document boolean-only.
110
+ Update Patch 2 notes to remove `extensions: string[]` reference.
111
+ 8. **`src/config/default-agents.ts`** — No changes needed; all defaults already use `extensions: true`.
112
+
113
+ ### Test files
114
+
115
+ 9. **`test/config/custom-agents.test.ts`** — Update the "handles extension allowlist" test: change expectation from `toEqual(["web-search", "mcp-server"])` to `toBe(true)` since CSV values now coerce to `true`.
116
+ Add a test verifying the deprecation warning is emitted.
117
+ 10. **`test/session/session-config.test.ts`** — Update the "isolated:true forces extensions to false even for string[] extension list" test: since `AgentConfig.extensions` no longer supports `string[]`, simplify this test to use `extensions: true` and verify `isolated` still forces it to `false`.
118
+ The test name changes to reflect the boolean type.
119
+ 11. **`test/lifecycle/agent-runner-extension-tools.test.ts`** — Remove the "post-bind re-filter respects extensions: string[] allowlist" test (line 139).
120
+ Change the `extensions` type annotation in the mutable config mock (line 26) from `boolean | string[]` to `boolean`.
121
+ Update the file-level JSDoc comment to remove `extensions: string[]` references.
122
+ 12. **`test/ui/agent-config-editor.test.ts`** — Remove the "emits 'extensions: \<list\>' when extensions is an array" test.
123
+ 13. **Test fixtures** — All test helper defaults already use `extensions: false` or `extensions: true`; no fixture changes needed beyond the tests listed above.
124
+
125
+ ### Architecture docs
126
+
127
+ 14. **`docs/architecture/architecture.md`** — Mark Step 2 as complete.
128
+
129
+ ## Test Impact Analysis
130
+
131
+ 1. **Tests removed** — 2 tests that directly assert `extensions: string[]` behavior:
132
+ - `agent-runner-extension-tools.test.ts`: "post-bind re-filter respects extensions: string[] allowlist"
133
+ - `agent-config-editor.test.ts`: "emits 'extensions: \<list\>' when extensions is an array"
134
+
135
+ 2. **Tests updated** — 3 tests that reference `extensions: string[]` in fixtures or assertions:
136
+ - `custom-agents.test.ts`: "handles extension allowlist" → expectation changes to `true`
137
+ - `session-config.test.ts`: "isolated:true forces extensions to false even for string[] extension list" → uses `true` instead of array
138
+ - `agent-runner-extension-tools.test.ts`: mock config type annotation narrows
139
+
140
+ 3. **Tests added** — 1 test:
141
+ - `custom-agents.test.ts`: verifies deprecation warning when array value is provided
142
+
143
+ 4. **Tests unchanged** — All other `extensions: true` and `extensions: false` tests remain valid since the boolean cases are preserved.
144
+
145
+ ## TDD Order
146
+
147
+ 1. **Narrow `extensions` type in `AgentConfig` and `ToolFilterConfig`** — Change `extensions` from `true | string[] | false` to `boolean` in `types.ts`.
148
+ Change `ToolFilterConfig.extensions` from `boolean | string[]` to `boolean` in `session-config.ts`.
149
+ Update JSDoc comments.
150
+ Run `pnpm run check` to surface all downstream type errors.
151
+ Commit: `feat!: narrow extensions type from union to boolean`
152
+
153
+ 2. **Remove array branch from `filterActiveTools`** — Remove the `Array.isArray(extensions)` branch.
154
+ Update the Patch 2 comment and the pre-bind filter comment.
155
+ Remove the "post-bind re-filter respects extensions: string[] allowlist" test.
156
+ Narrow the mock config type annotation in `agent-runner-extension-tools.test.ts`.
157
+ Update the file-level JSDoc.
158
+ Run tests: `pnpm vitest run`.
159
+ Commit: `refactor: remove extensions array branch from filterActiveTools`
160
+
161
+ 3. **Coerce array values in custom agent frontmatter** — Update the `extensions` assignment in `custom-agents.ts` to coerce `string[]` results from `inheritField` to `true` with a deprecation warning.
162
+ Update the "handles extension allowlist" test expectation from array to `true`.
163
+ Add a test for the deprecation warning.
164
+ Update the "isolated:true forces extensions to false even for string[] extension list" test in `session-config.test.ts` to use `extensions: true`.
165
+ Run tests: `pnpm vitest run`.
166
+ Commit: `refactor: coerce extensions array values to true with deprecation warning`
167
+
168
+ 4. **Remove extensions array from UI** — Remove the `Array.isArray(cfg.extensions)` branch from `buildEjectContent`.
169
+ Simplify the `extensions:` line in the creation wizard template.
170
+ Remove the "emits 'extensions: \<list\>'" test from `agent-config-editor.test.ts`.
171
+ Run tests: `pnpm vitest run`.
172
+ Commit: `refactor: remove extensions array from config editor and creation wizard`
173
+
174
+ 5. **Update README** — Simplify the `extensions` frontmatter table row to document boolean-only.
175
+ Update Patch 2 notes to remove `extensions: string[]` reference.
176
+ Commit: `docs: simplify extensions field to boolean in README`
177
+
178
+ 6. **Mark Phase 14 Step 2 complete** — Update `docs/architecture/architecture.md`.
179
+ Commit: `docs: mark Phase 14 Step 2 complete in architecture`
180
+
181
+ ## Risks and Mitigations
182
+
183
+ | Risk | Mitigation |
184
+ | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
185
+ | Users with `extensions: <csv-list>` silently lose per-extension filtering | `feat!:` commit triggers major version bump; frontmatter parser emits deprecation warning and coerces to `true`; README documents the change |
186
+ | `filterActiveTools` behavior regresses for boolean paths | Steps 1–4 each run type checks or tests; `extensions: true` and `extensions: false` tests are unchanged |
187
+ | Type annotation `boolean | string[]` in test mock causes tsc errors after step 1 | Step 2 narrows the annotation in the same commit that removes the array branch |
188
+
189
+ ## Open Questions
190
+
191
+ None — the issue's proposed change and the architecture doc's step description are unambiguous.
@@ -34,3 +34,31 @@ All 4 commits landed cleanly; full suite green at every step.
34
34
  - The `createSessionRunner` + `createRunResult` chain required careful identity-check verification: `createRunResult(sess)` calls `toAgentSession(sess)` which casts without creating a new object, so `toBe(session)` assertions in the execution-state tests still pass. ✓
35
35
  - ESLint auto-fixed two cosmetic issues on commit (`activity = undefined` → `activity` destructuring, `session as unknown` cast removal) — caught by pre-commit hooks, not a problem in practice.
36
36
  - The `assertRenderFitsWidths` helper in `conversation-viewer.test.ts` reduced the 10 render-safety tests from ~8 lines each to 1–4 lines each; the `setupDetail` helper in `agent-config-editor.test.ts` eliminated 3 repeated setup lines per test across 18 `showAgentDetail` tests.
37
+
38
+ ## Stage: Final Retrospective (2026-05-26T17:50:31Z)
39
+
40
+ ### Session summary
41
+
42
+ All three stages (planning, TDD, ship) completed in a single session with zero user corrections.
43
+ Four TDD commits landed cleanly, CI passed on first push, issue #219 closed, and `pi-subagents-v7.8.1` released.
44
+
45
+ ### Observations
46
+
47
+ #### What went well
48
+
49
+ - Clean incremental verification: `pnpm vitest run <file>` ran after every code change, full suite at the end.
50
+ No regressions at any step.
51
+ - The plan's decision to keep gated runners inline proved correct — tests with `Promise.withResolvers` flow control stayed readable without abstraction.
52
+ - The `write` tool was the right choice for `conversation-viewer.test.ts` and `agent-config-editor.test.ts` (pervasive changes), while `edit` with 16 targeted replacements worked cleanly for `agent-manager.test.ts` (scattered but formulaic substitutions).
53
+ - Ship stage used `deepseek-v4-flash` for purely mechanical CI/release steps — appropriate model-task alignment.
54
+
55
+ #### What caused friction (agent side)
56
+
57
+ - `other` — ESLint pre-commit hooks auto-fixed two cosmetic patterns (`activity = undefined` → bare destructuring; `session as unknown` cast removal), requiring re-stage + re-commit.
58
+ Impact: ~10 seconds per occurrence, no rework.
59
+ This is routine and inherent to the workflow.
60
+
61
+ #### What caused friction (user side)
62
+
63
+ - None observed.
64
+ The user's three-stage prompt chain (`/plan-issue` → `/tdd-plan` → `/ship-issue`) provided sufficient context at each stage with no manual intervention needed.
@@ -0,0 +1,78 @@
1
+ ---
2
+ issue: 237
3
+ issue_title: "Remove disallowed_tools from pi-subagents (Phase 14, Step 1)"
4
+ ---
5
+
6
+ # Retro: #237 — Remove disallowed_tools from pi-subagents
7
+
8
+ ## Stage: Planning (2026-05-27T00:52:26Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a 6-step TDD plan to remove `disallowedTools` from `AgentConfig`, `disallowedSet` from `ToolFilterConfig`, and all parsing/serialization/UI/test code that references them.
13
+ The plan covers 7 source files, 4 test files, README, and the architecture doc.
14
+
15
+ ### Observations
16
+
17
+ - The issue label `pkg:pi-permission-system` was incorrect — all target files live in `packages/pi-subagents`.
18
+ Confirmed with the user that the plan targets pi-subagents.
19
+ - The README still references `disallowed_tools` in the context of memory write-capability detection, but memory was already removed in #185.
20
+ The plan treats this as a stale reference to clean up.
21
+ - After removing `disallowedSet`, the `filterActiveTools` `extensions === false` branch simplifies to a trivial passthrough (`return activeTools`), and both guard conditions at the call sites drop the `|| cfg.toolFilter.disallowedSet` arm.
22
+ This leaves the function in the exact shape that Step 3 (#239) expects.
23
+ - The plan orders steps to follow the type dependency chain: `AgentConfig` first (surfaces all downstream errors), then `ToolFilterConfig`, then `filterActiveTools`, then UI, then docs.
24
+
25
+ ## Stage: Implementation — TDD (2026-05-27T00:59:19Z)
26
+
27
+ ### Session summary
28
+
29
+ All 6 TDD steps completed in one session, producing 7 commits (6 planned + 1 fixup for dead code).
30
+ Test count dropped from 983 to 978 (5 tests removed: 2 from `custom-agents.test.ts`, 1 from `session-config.test.ts`, 2 from `agent-runner-extension-tools.test.ts` after renaming one deleted test into a retained form).
31
+ All checks (type check, lint, fallow dead-code gate) pass clean.
32
+
33
+ ### Observations
34
+
35
+ - `csvListOptional` in `custom-agents.ts` was left dead after removing the `disallowedTools` parsing call; Biome flagged it as unused.
36
+ Removed as a separate `refactor:` commit since it couldn't be amended into the `feat!:` commit (later commits already on top of it).
37
+ - One of the three deleted `agent-runner-extension-tools.test.ts` denylist tests ("extensions: false with no disallowedTools skips the filter") was reformulated into a new test ("extensions: false skips the filter entirely") rather than simply deleted, because it covers genuinely different behavior after the simplification: `extensions: false` now always skips the filter, not just when no denylist is present.
38
+ This adds coverage for the simplified code path.
39
+ - The `filterActiveTools` `extensions === false` branch simplified from "apply denylist to built-in tools" to `return activeTools`, exactly as the plan specified.
40
+ Both guard conditions at the call sites simplified to `cfg.toolFilter.extensions !== false`.
41
+ - No deviations from the plan's module-level changes list; all 7 source files and 4 test files were touched as specified.
42
+
43
+ ## Stage: Final Retrospective (2026-05-27T01:12:34Z)
44
+
45
+ ### Session summary
46
+
47
+ Three-stage lifecycle (planning → TDD → ship) completed in one session for Phase 14, Step 1.
48
+ All 6 TDD steps landed in 7 commits (6 planned + 1 fixup for dead `csvListOptional`).
49
+ Test count: 983 → 978 (−5).
50
+ Released as `pi-subagents-v8.0.0` (major bump from the `feat!:` breaking change).
51
+
52
+ ### Observations
53
+
54
+ #### What went well
55
+
56
+ - The plan's type-dependency-chain ordering (AgentConfig → ToolFilterConfig → `filterActiveTools` → UI → docs) meant each step produced the exact downstream type errors expected for the next step, with zero surprises.
57
+ This ordering pattern is worth preserving for future removal issues.
58
+ - Incremental verification ran after every step: `pnpm run check` after type changes, `pnpm vitest run <file>` after logic changes, full suite + lint + fallow at the end.
59
+ No regressions discovered at the final sweep.
60
+ - The reformulation of one deleted test into "extensions: false skips the filter entirely" added coverage for the simplified code path rather than just deleting it.
61
+ Good judgment call during implementation.
62
+
63
+ #### What caused friction (agent side)
64
+
65
+ - `missing-context` — The plan said "remove `disallowedTools: csvListOptional(fm.disallowed_tools)`" from `custom-agents.ts` but did not check whether `csvListOptional` had other callers.
66
+ It was the sole call site, so the function became dead code.
67
+ Biome caught it at the final lint sweep, requiring a separate `refactor:` commit (`f1ee7c1`).
68
+ Impact: one unplanned commit; no rework but added friction.
69
+ Self-identified at final lint.
70
+ Root cause: the `plan-issue.md` template had a rule for grepping removed *exports* but not for checking private-function orphans.
71
+
72
+ #### What caused friction (user side)
73
+
74
+ - No friction observed — the user's involvement was limited to confirming the target package (pi-subagents vs pi-permission-system) during planning, which was an appropriate clarification given the incorrect `pkg:` label on the issue.
75
+
76
+ ### Changes made
77
+
78
+ 1. `.pi/prompts/plan-issue.md` — Added rule: when a step removes a call to a private function, grep the file for other callers and list the function for removal if the removed call was the sole call site.
@@ -0,0 +1,39 @@
1
+ ---
2
+ issue: 238
3
+ issue_title: "Remove extensions filtering from pi-subagents (Phase 14, Step 2)"
4
+ ---
5
+
6
+ # Retro: #238 — Remove extensions filtering from pi-subagents
7
+
8
+ ## Stage: Planning (2026-05-27T01:31:01Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a 6-step TDD plan for narrowing `extensions` from `true | string[] | false` to `boolean` across `AgentConfig`, `ToolFilterConfig`, `filterActiveTools`, custom agent frontmatter parsing, UI serialization, and tests.
13
+ The plan mirrors the structure of the completed #237 plan (Step 1 of Phase 14) and explicitly defers `filterActiveTools` collapse to #239.
14
+
15
+ ### Observations
16
+
17
+ - The `pkg:pi-permission-system` label on this issue is intentional context (pi-permission-system becomes the sole tool-policy authority) but all code changes are in pi-subagents.
18
+ - The `inheritField()` helper is shared between `extensions` and `skills` parsing — it cannot be simplified since `skills` still supports `string[]`.
19
+ The plan coerces at the call site instead.
20
+ - After removing the `Array.isArray(extensions)` branch, the `builtinToolNameSet.has(t)` check in `filterActiveTools` becomes logically redundant (both branches return `true`), but simplifying further is #239's scope.
21
+ - Only 2 tests are removed and 3 updated; the boolean paths (`true`/`false`) are well-covered and unchanged.
22
+
23
+ ## Stage: Implementation — TDD (2026-05-27T01:39:43Z)
24
+
25
+ ### Session summary
26
+
27
+ All 6 TDD steps completed across 6 commits.
28
+ Test count went from 978 to 977 (net −1: 2 removed, 1 added for the deprecation warning).
29
+ Full suite: 62 files, 977 tests, all passing; type check and lint clean.
30
+
31
+ ### Observations
32
+
33
+ - **Plan deviation — step consolidation**: The type narrowing in step 1 caused immediate `tsc` errors in test files that the plan assigned to steps 2–4 (the `string[]`-specific tests in `agent-runner-extension-tools.test.ts`, `session-config.test.ts`, and `agent-config-editor.test.ts`).
34
+ All test fixes were folded into the step 1 commit to keep the build green at every commit.
35
+ Steps 2–4 became purely production-code changes.
36
+ - **Plan deviation — deprecation warning**: The plan said to use `debugLog` for the deprecation warning, but `debugLog` is debug-only (requires `PI_SUBAGENTS_DEBUG=1`) and its signature requires an error object as the second argument. `console.warn` was used instead, which is user-visible without special env vars and testable with `vi.spyOn`.
37
+ - **ESLint auto-fix in step 2**: ESLint rewrote `extensions === false` to `!extensions` and `cfg.toolFilter.extensions === false` to `!cfg.toolFilter.extensions` after the type narrowed to `boolean`.
38
+ Both are semantically identical; the changes were staged and included in the same commit.
39
+ - The `resolveBoolExtensions` helper in `custom-agents.ts` is a clean seam — if `skills` is ever also simplified to `boolean`, the same pattern applies.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "7.8.1",
3
+ "version": "9.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"