@gotgenes/pi-subagents 7.8.0 → 8.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.
- package/CHANGELOG.md +35 -0
- package/README.md +10 -13
- package/docs/architecture/architecture.md +179 -133
- package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
- package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +162 -0
- package/docs/plans/0237-remove-disallowed-tools.md +180 -0
- package/docs/retro/0218-push-sdk-boundary-in-settings.md +37 -0
- package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +64 -0
- package/docs/retro/0237-remove-disallowed-tools.md +41 -0
- package/package.json +1 -1
- package/src/config/custom-agents.ts +0 -9
- package/src/lifecycle/agent-runner.ts +7 -11
- package/src/session/session-config.ts +3 -10
- package/src/types.ts +0 -2
- package/src/ui/agent-config-editor.ts +0 -2
- package/src/ui/agent-creation-wizard.ts +0 -1
|
@@ -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,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 219
|
|
3
|
+
issue_title: "Reduce test duplication — top 3 clone families (Phase 13, Step 6)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reduce test duplication — top 3 clone families
|
|
7
|
+
|
|
8
|
+
## Problem statement
|
|
9
|
+
|
|
10
|
+
After Phase 12, three test files carry the heaviest remaining clone families in pi-subagents:
|
|
11
|
+
|
|
12
|
+
1. `test/lifecycle/agent-manager.test.ts` (929 lines) — 16 clone groups, ~160 duplicated lines.
|
|
13
|
+
Repeated inline runner stubs, worktree stubs, and manager-lifecycle boilerplate.
|
|
14
|
+
2. `test/conversation-viewer.test.ts` (307 lines) — 8 clone groups, ~91 duplicated lines.
|
|
15
|
+
Near-identical `ConversationViewer` construction in every test, plus repeated width-loop assertion patterns.
|
|
16
|
+
3. `test/ui/agent-config-editor.test.ts` (471 lines) — 5 clone groups, ~42 duplicated lines.
|
|
17
|
+
Repeated `makeEditor()` + `makeMenuUI()` + `fileOps.findAgentFile.mockReturnValue(...)` setup.
|
|
18
|
+
|
|
19
|
+
Total target: reduce test duplication by ~200 lines (from ~1,046 combined test-setup lines to < 850).
|
|
20
|
+
|
|
21
|
+
## Goals
|
|
22
|
+
|
|
23
|
+
- Extract shared setup and assertion helpers for the three target test files.
|
|
24
|
+
- Reduce test duplication by ~200 lines without changing test semantics.
|
|
25
|
+
- Follow the existing `test/helpers/` convention (factory + matching `.test.ts` file).
|
|
26
|
+
|
|
27
|
+
## Non-goals
|
|
28
|
+
|
|
29
|
+
- No production code changes.
|
|
30
|
+
- No new test coverage — this is purely a refactoring of existing test infrastructure.
|
|
31
|
+
- Not consolidating clone families in other test files beyond the top 3.
|
|
32
|
+
- Not changing any assertion logic or test structure beyond replacing inline stubs with factory calls.
|
|
33
|
+
|
|
34
|
+
## Background
|
|
35
|
+
|
|
36
|
+
The project already has several shared test helpers in `test/helpers/`: `make-record.ts`, `mock-session.ts`, `ui-stubs.ts`, `runner-io.ts`, `stub-ctx.ts`, `make-deps.ts`.
|
|
37
|
+
Each helper has a companion `.test.ts` file — this convention must be followed.
|
|
38
|
+
|
|
39
|
+
Dependencies #214 (closure-to-class conversions) and #216 (startAgent decomposition) are both closed, so the production code these tests cover is stable.
|
|
40
|
+
|
|
41
|
+
## Design overview
|
|
42
|
+
|
|
43
|
+
### File 1: `agent-manager.test.ts` — extract to `test/helpers/manager-stubs.ts`
|
|
44
|
+
|
|
45
|
+
Five clone families to extract:
|
|
46
|
+
|
|
47
|
+
1. **Never-resolving runner** — `{ run: vi.fn().mockImplementation(() => new Promise(() => {})), resume: vi.fn() }` appears 5 times.
|
|
48
|
+
Extract as `createBlockingRunner(): AgentRunner`.
|
|
49
|
+
|
|
50
|
+
2. **Session-creating runner** — runner that calls `opts.onSessionCreated?.(session)` and resolves.
|
|
51
|
+
Appears 5+ times with minor variations (some emit events through the session, some don't).
|
|
52
|
+
Extract as `createSessionRunner(session?: MockSession): AgentRunner` that calls `onSessionCreated` and returns a standard result.
|
|
53
|
+
|
|
54
|
+
3. **Worktree stubs with path+branch** — `{ create: vi.fn().mockReturnValue({ path, branch }), cleanup: vi.fn(() => ({ hasChanges: false })), prune: vi.fn() }` appears 4 times identically, plus 1 variant with `create` returning `undefined`.
|
|
55
|
+
Extract as `createMockWorktrees(overrides?)`.
|
|
56
|
+
|
|
57
|
+
4. **Standard run result shape** — `{ responseText: "done", session, aborted: false, steered: false }` is repeated in many runner factories.
|
|
58
|
+
Extract as `createRunResult(overrides?)`.
|
|
59
|
+
|
|
60
|
+
5. **Gated runner** — uses `Promise.withResolvers` to control when the runner completes.
|
|
61
|
+
Appears 2 times.
|
|
62
|
+
Keep inline — too tightly coupled to individual test flow-control to generalize cleanly.
|
|
63
|
+
|
|
64
|
+
Tests that construct custom runners with unique behavior (event-emitting runners in the `lifetimeUsage` and `compactionCount` tests) keep their inline stubs — those encode test-specific emission sequences that a shared factory would obscure.
|
|
65
|
+
|
|
66
|
+
### File 2: `conversation-viewer.test.ts` — inline factory + assertion helper
|
|
67
|
+
|
|
68
|
+
Two clone families to extract:
|
|
69
|
+
|
|
70
|
+
1. **`ConversationViewer` construction** — 15 near-identical constructor calls with the same 8 fields.
|
|
71
|
+
Extract as an inline `createTestViewer(overrides?)` factory at the top of the test file.
|
|
72
|
+
The factory provides defaults for `tui`, `session`, `record`, `activity`, `theme`, `done`, `registry`, and `wrapText`, and accepts overrides including a convenience `width` and `messages` parameter.
|
|
73
|
+
|
|
74
|
+
2. **Width-loop assertion** — the `for (const w of widths) { create viewer; assertAllLinesFit(viewer.render(w), w) }` pattern repeats in 10 "render width safety" tests.
|
|
75
|
+
Extract as an inline `assertRenderFitsWidths(messages, widths?, viewerOverrides?)` helper.
|
|
76
|
+
|
|
77
|
+
These helpers stay inline (not in `test/helpers/`) because they depend on file-local helpers (`mockTui`, `mockSession`, `ansiTheme`) and are only used by this one test file.
|
|
78
|
+
|
|
79
|
+
### File 3: `agent-config-editor.test.ts` — inline setup helper
|
|
80
|
+
|
|
81
|
+
One clone family to extract:
|
|
82
|
+
|
|
83
|
+
1. **Detail-test setup** — `makeEditor()` + `makeMenuUI([...])` + `fileOps.findAgentFile.mockReturnValue(...)` + optional `fileOps.read.mockReturnValue(...)` appears in ~18 tests.
|
|
84
|
+
Extract as an inline `setupDetail(selectResults, options?)` factory that returns `{ fileOps, editor, ui }` with pre-configured mocks.
|
|
85
|
+
Options: `filePath`, `fileContent`, `config` (merged into default via `createTestAgentConfig`).
|
|
86
|
+
|
|
87
|
+
This stays inline because it's specific to the `showAgentDetail` test suite and depends on file-local `testRegistry` setup.
|
|
88
|
+
|
|
89
|
+
## Module-level changes
|
|
90
|
+
|
|
91
|
+
### New files
|
|
92
|
+
|
|
93
|
+
| File | Purpose |
|
|
94
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
95
|
+
| `test/helpers/manager-stubs.ts` | `createBlockingRunner`, `createSessionRunner`, `createMockWorktrees`, `createRunResult` |
|
|
96
|
+
| `test/helpers/manager-stubs.test.ts` | Smoke tests for the factories |
|
|
97
|
+
|
|
98
|
+
### Modified files
|
|
99
|
+
|
|
100
|
+
| File | Change |
|
|
101
|
+
| -------------------------------------- | --------------------------------------------------------------------------- |
|
|
102
|
+
| `test/lifecycle/agent-manager.test.ts` | Replace inline runner/worktree stubs with `manager-stubs` factories |
|
|
103
|
+
| `test/conversation-viewer.test.ts` | Add `createTestViewer` + `assertRenderFitsWidths` inline, migrate all tests |
|
|
104
|
+
| `test/ui/agent-config-editor.test.ts` | Add `setupDetail` inline, migrate `showAgentDetail` tests |
|
|
105
|
+
|
|
106
|
+
### Unchanged files
|
|
107
|
+
|
|
108
|
+
No production source files are modified.
|
|
109
|
+
No other test files are modified.
|
|
110
|
+
|
|
111
|
+
## Test impact analysis
|
|
112
|
+
|
|
113
|
+
1. **New unit tests**: `manager-stubs.test.ts` adds smoke tests verifying factory return shapes (blocking runner never resolves, session runner calls `onSessionCreated`, worktree factory returns the expected interface, run result contains the correct fields).
|
|
114
|
+
2. **Simplified tests**: ~30 tests across the three files replace 3–6 lines of inline stub construction with 1-line factory calls.
|
|
115
|
+
3. **Unchanged tests**: All existing test assertions remain identical — only the setup code changes.
|
|
116
|
+
Tests with custom runner behavior (event-emitting, gated, error-throwing) keep their inline stubs.
|
|
117
|
+
|
|
118
|
+
## TDD order
|
|
119
|
+
|
|
120
|
+
1. **Create `test/helpers/manager-stubs.ts` + `manager-stubs.test.ts`** Add `createBlockingRunner`, `createSessionRunner`, `createMockWorktrees`, `createRunResult`.
|
|
121
|
+
Add smoke tests verifying each factory's return shape and basic behavior.
|
|
122
|
+
Commit: `test: add manager-stubs helper factories (#219)`
|
|
123
|
+
|
|
124
|
+
2. **Migrate `agent-manager.test.ts` to use manager-stubs** Replace 5 inline never-resolving runners with `createBlockingRunner()`.
|
|
125
|
+
Replace 4 identical worktree stubs with `createMockWorktrees()` / `createMockWorktrees({ create: ... })`.
|
|
126
|
+
Replace inline session-creating runners with `createSessionRunner(session)` where the test only needs `onSessionCreated` wiring.
|
|
127
|
+
Replace inline run-result objects with `createRunResult()` where the default shape suffices.
|
|
128
|
+
Run `pnpm vitest run test/lifecycle/agent-manager.test.ts` to verify green.
|
|
129
|
+
Commit: `test: migrate agent-manager tests to manager-stubs (#219)`
|
|
130
|
+
|
|
131
|
+
3. **Add inline factories to `conversation-viewer.test.ts` and migrate** Add `createTestViewer(overrides?)` inline factory with defaults for all 8 constructor fields.
|
|
132
|
+
Add `assertRenderFitsWidths(messages, widths?, overrides?)` inline helper.
|
|
133
|
+
Migrate all 10 "render width safety" tests to use `assertRenderFitsWidths`.
|
|
134
|
+
Migrate all 5 "safety net" tests to use `createTestViewer`.
|
|
135
|
+
Run `pnpm vitest run test/conversation-viewer.test.ts` to verify green.
|
|
136
|
+
Commit: `test: reduce conversation-viewer test duplication (#219)`
|
|
137
|
+
|
|
138
|
+
4. **Add inline `setupDetail` to `agent-config-editor.test.ts` and migrate** Add `setupDetail(selectResults, options?)` returning `{ fileOps, editor, ui }`.
|
|
139
|
+
Migrate `showAgentDetail` tests to use `setupDetail`.
|
|
140
|
+
Run `pnpm vitest run test/ui/agent-config-editor.test.ts` to verify green.
|
|
141
|
+
Commit: `test: reduce agent-config-editor test duplication (#219)`
|
|
142
|
+
|
|
143
|
+
5. **Final verification** Run `pnpm vitest run` (full suite) to confirm no regressions.
|
|
144
|
+
Run `pnpm run check` to confirm no type errors.
|
|
145
|
+
Commit is not needed — this is a verification-only step.
|
|
146
|
+
|
|
147
|
+
## Risks and mitigations
|
|
148
|
+
|
|
149
|
+
1. **Factory defaults diverge from test intent** — If a shared factory's defaults don't match what an individual test expects, assertions silently pass or fail for the wrong reason.
|
|
150
|
+
Mitigation: diff all inline stubs against the proposed factory defaults before writing the factory.
|
|
151
|
+
Keep tests with unique mock behavior inline rather than force-fitting them into a factory.
|
|
152
|
+
|
|
153
|
+
2. **Over-abstraction obscures test intent** — Extracting too many details into helpers makes tests harder to read.
|
|
154
|
+
Mitigation: only extract truly duplicated boilerplate (stub construction); keep test-specific setup and assertions inline.
|
|
155
|
+
The gated runner pattern stays inline for this reason.
|
|
156
|
+
|
|
157
|
+
3. **Intermediate broken state** — Partially migrated test files may have import conflicts.
|
|
158
|
+
Mitigation: each TDD step fully migrates one file before committing.
|
|
159
|
+
|
|
160
|
+
## Open questions
|
|
161
|
+
|
|
162
|
+
None — the issue scope is well-defined and the dependencies are resolved.
|
|
@@ -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.
|
|
@@ -33,3 +33,40 @@ All 970 tests pass; `settings.ts` now has 0 Pi SDK imports and all `PI_CODING_AG
|
|
|
33
33
|
- **Test simplification was significant:** Removed `originalAgentDirEnv` save/restore scaffolding from 5 `describe` blocks; the test code shrank by 32 lines net.
|
|
34
34
|
- **`/nonexistent` sentinel:** Tests that construct `SettingsManager` but never call `load()` pass `agentDir: "/nonexistent"` — a clear signal the field is unused in that scope.
|
|
35
35
|
- Architecture doc Step 5 heading marked `✓` and folded into the last `feat:` commit by `pi-autoformat`.
|
|
36
|
+
|
|
37
|
+
## Stage: Final Retrospective (2026-05-26T17:22:11Z)
|
|
38
|
+
|
|
39
|
+
### Session summary
|
|
40
|
+
|
|
41
|
+
Issue #218 went from plan to shipped release (`pi-subagents-v7.8.0`) in a single continuous session.
|
|
42
|
+
Planning, TDD (2 feat commits + 1 doc commit), shipping, CI verification, issue close, and release-please merge all completed without user intervention beyond stage transitions.
|
|
43
|
+
|
|
44
|
+
### Observations
|
|
45
|
+
|
|
46
|
+
#### What went well
|
|
47
|
+
|
|
48
|
+
- **Clean mechanical execution:** The entire change was 2 production files (`settings.ts`, `index.ts`) and 1 test file, with zero unexpected test breakage and zero rework commits.
|
|
49
|
+
- **Test simplification payoff:** Removing `PI_CODING_AGENT_DIR` env var scaffolding from 5 `describe` blocks shrank the test file by 32 lines net — a tangible improvement in test readability.
|
|
50
|
+
- **Ship stage model efficiency:** The `/ship-issue` stage ran on `deepseek-v4-flash`, which was appropriate for the purely mechanical push/CI/close/merge workflow.
|
|
51
|
+
|
|
52
|
+
#### What caused friction (agent side)
|
|
53
|
+
|
|
54
|
+
1. `wrong-abstraction` — The plan split steps 1 and 2 into separate commits, but changing `loadSettings(cwd)` to `loadSettings(agentDir, cwd)` immediately broke `SettingsManager.load()` which calls it.
|
|
55
|
+
The agent recognized this during the red phase and combined them into one commit.
|
|
56
|
+
The existing testing skill rule ("When a TDD plan lists separate steps that share a type definition… fold them into one step") already covers this — the plan just didn't apply it.
|
|
57
|
+
Impact: added friction but no rework; recognized on first test run.
|
|
58
|
+
2. `missing-context` — Attempted to add `| ✓ #218 |` as an extra column to one row of the architecture doc's findings table, creating a column-count mismatch.
|
|
59
|
+
The autoformatter reverted the broken table.
|
|
60
|
+
The agent then spent ~5 tool calls (`git show --stat`, `git status`, `grep` ×2, `read`) investigating what happened before switching to the Step 5 heading approach.
|
|
61
|
+
Impact: ~2 minutes of investigation; no rework beyond the heading edit.
|
|
62
|
+
|
|
63
|
+
#### What caused friction (user side)
|
|
64
|
+
|
|
65
|
+
- The user asked "Are we ready for shipping?"
|
|
66
|
+
which surfaced that the TDD retro stage notes were still uncommitted.
|
|
67
|
+
This was a useful checkpoint — the ship stage committed them before pushing.
|
|
68
|
+
Opportunity: the `/tdd-plan` prompt could commit retro notes as part of its final step, but the current flow (write notes, then commit in ship) is lightweight enough that enforcing it would add complexity for marginal gain.
|
|
69
|
+
|
|
70
|
+
### Changes made
|
|
71
|
+
|
|
72
|
+
1. Retro file updated at `packages/pi-subagents/docs/retro/0218-push-sdk-boundary-in-settings.md` — no other files changed.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 219
|
|
3
|
+
issue_title: "Reduce test duplication — top 3 clone families (Phase 13, Step 6)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #219 — Reduce test duplication — top 3 clone families
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-26T20:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Analyzed duplication patterns in the three target test files (`agent-manager.test.ts`, `conversation-viewer.test.ts`, `agent-config-editor.test.ts`).
|
|
13
|
+
Produced a 5-step TDD plan with shared `manager-stubs.ts` helper for runner/worktree factories, plus inline factories for the two UI test files.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- The agent-manager test has the most diverse clone families (runner stubs, worktree stubs, run-result shapes) — these benefit from a shared helper file since the patterns are reused across 15+ describe blocks.
|
|
18
|
+
- The conversation-viewer and config-editor duplication is more localized — inline factories within each test file are the right granularity to avoid over-extraction.
|
|
19
|
+
- Gated runners (using `Promise.withResolvers`) were deliberately kept inline since they encode test-specific flow control that a factory would obscure.
|
|
20
|
+
- Both dependencies (#214, #216) are closed, so the production code is stable and the tests won't shift under us during implementation.
|
|
21
|
+
|
|
22
|
+
## Stage: Implementation — TDD (2026-05-26T17:42:41Z)
|
|
23
|
+
|
|
24
|
+
### Session summary
|
|
25
|
+
|
|
26
|
+
Completed all 4 TDD cycles: created `test/helpers/manager-stubs.ts` + `manager-stubs.test.ts` (13 smoke tests), migrated `agent-manager.test.ts`, `conversation-viewer.test.ts`, and `agent-config-editor.test.ts`.
|
|
27
|
+
Test count delta: 970 → 983 (+13 from smoke tests).
|
|
28
|
+
All 4 commits landed cleanly; full suite green at every step.
|
|
29
|
+
|
|
30
|
+
### Observations
|
|
31
|
+
|
|
32
|
+
- Target file line savings: `agent-manager.test.ts` −63, `conversation-viewer.test.ts` −58, `agent-config-editor.test.ts` −16; offset by +211 for the new helper files.
|
|
33
|
+
Net LOC is positive, but the _clone_ lines fallow detects are eliminated — the metric the issue targets.
|
|
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
|
+
- 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
|
+
- 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,41 @@
|
|
|
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.
|
package/package.json
CHANGED
|
@@ -58,7 +58,6 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
58
58
|
displayName: str(fm.display_name),
|
|
59
59
|
description: str(fm.description) ?? name,
|
|
60
60
|
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
61
|
-
disallowedTools: csvListOptional(fm.disallowed_tools),
|
|
62
61
|
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
63
62
|
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
64
63
|
model: str(fm.model),
|
|
@@ -110,14 +109,6 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
110
109
|
return parseCsvField(val) ?? [];
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
/**
|
|
114
|
-
* Parse an optional comma-separated list field.
|
|
115
|
-
* omitted → undefined; "none"/empty → undefined; csv → listed items.
|
|
116
|
-
*/
|
|
117
|
-
function csvListOptional(val: unknown): string[] | undefined {
|
|
118
|
-
return parseCsvField(val);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
112
|
/**
|
|
122
113
|
* Parse an inherit field (extensions, skills).
|
|
123
114
|
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|