@gotgenes/pi-subagents 17.4.0 → 18.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 +22 -0
- package/docs/architecture/architecture.md +34 -22
- package/docs/decisions/0004-reconsider-ui-direction.md +25 -0
- package/docs/plans/0441-remove-agent-definition-management-subtree.md +180 -0
- package/docs/plans/0442-dissolve-agents-conversation-viewer.md +195 -0
- package/docs/plans/0463-file-snapshot-source-evicted-agents.md +306 -0
- package/docs/retro/0441-remove-agent-definition-management-subtree.md +45 -0
- package/docs/retro/0442-dissolve-agents-conversation-viewer.md +108 -0
- package/docs/retro/0462-navigation-renderer-tui-components.md +56 -0
- package/docs/retro/0463-file-snapshot-source-evicted-agents.md +103 -0
- package/package.json +1 -1
- package/src/index.ts +13 -32
- package/src/lifecycle/subagent-manager.ts +47 -1
- package/src/ui/session-navigation.ts +67 -13
- package/src/ui/session-navigator.ts +18 -7
- package/src/ui/subagents-settings.ts +2 -2
- package/src/ui/agent-config-editor.ts +0 -199
- package/src/ui/agent-creation-wizard.ts +0 -233
- package/src/ui/agent-file-ops.ts +0 -59
- package/src/ui/agent-file-writer.ts +0 -55
- package/src/ui/agent-menu.ts +0 -331
- package/src/ui/conversation-viewer.ts +0 -241
- package/src/ui/message-formatters.ts +0 -195
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 463
|
|
3
|
+
issue_title: "pi-subagents: add file-snapshot source to /subagent-sessions for evicted agents"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# File-snapshot source for evicted agents in `/subagent-sessions`
|
|
7
|
+
|
|
8
|
+
## Release Recommendation
|
|
9
|
+
|
|
10
|
+
**Release:** ship independently
|
|
11
|
+
|
|
12
|
+
Architecture roadmap Phase 19 Step 4b ([#463]) carries `Release: independent`.
|
|
13
|
+
This is a new capability the bespoke viewer never had — it gates nothing and is not a Step 5 prerequisite — so it ships on its own once landed.
|
|
14
|
+
|
|
15
|
+
## Problem Statement
|
|
16
|
+
|
|
17
|
+
The [#445] native-session-navigation slice sources transcripts live, from `manager.listAgents()` only.
|
|
18
|
+
The manager's cleanup sweep (`SubagentManager.cleanup()`, every 60 s) disposes a completed/stopped/errored record 10 minutes after it finishes — `disposeSession()` frees the heavy in-memory session (its message history included) and the record is deleted from the map.
|
|
19
|
+
The lightweight session JSONL persists on disk at `Subagent.outputFile`, but with no live record the agent is unreachable from `/subagent-sessions`.
|
|
20
|
+
|
|
21
|
+
This step makes a fully-evicted agent navigable: it adds the file-snapshot `TranscriptSource` branch the #445 seam was designed for, and broadens the picker's candidate set to include evicted agents.
|
|
22
|
+
|
|
23
|
+
## Goals
|
|
24
|
+
|
|
25
|
+
- Implement `fileSnapshotSource(outputFile, readFile)` in `src/ui/session-navigation.ts`: `parseSessionEntries(readFile(outputFile))` → drop the `SessionHeader` → `buildSessionContext(...).messages` → a static (no-subscribe, no-streaming) `TranscriptSource`.
|
|
26
|
+
- Broaden the `/subagent-sessions` candidate set to include evicted agents, deduped against live records.
|
|
27
|
+
- Render an evicted agent's transcript read-only from its persisted file when picked; the renderer (`buildTranscriptComponents`) and overlay (`TranscriptOverlay`) are untouched.
|
|
28
|
+
- Inject `readFile` as a parameter so the pure module performs no `fs` calls.
|
|
29
|
+
|
|
30
|
+
This change is **not** breaking: it is additive (a new source branch and a broadened candidate set); no existing behavior, output shape, or default changes for a tracked agent.
|
|
31
|
+
|
|
32
|
+
## Non-Goals
|
|
33
|
+
|
|
34
|
+
- No directory scan of the tasks directory and no surfacing of prior-process orphan session files (see Design Overview → Candidate-set decision).
|
|
35
|
+
- No change to the cleanup sweep's 10-minute eviction policy or to `disposeSession()`.
|
|
36
|
+
- No renderer or overlay changes — `session-navigator.ts`'s `buildTranscriptComponents` / `TranscriptOverlay` / `addMessageComponents` are unchanged (Step 4a, [#462], already brought them to parity).
|
|
37
|
+
- No `switchSession` / `loadEntriesFromFile` (rejected by the Step 1 spike, [#446]).
|
|
38
|
+
- No live re-read or file-watching of the snapshot — a file source is a static snapshot by design.
|
|
39
|
+
|
|
40
|
+
## Background
|
|
41
|
+
|
|
42
|
+
Relevant modules:
|
|
43
|
+
|
|
44
|
+
- `src/ui/session-navigation.ts` — the pure, unit-testable core: `NavigableSubagent` (narrow record view), `NavigationEntry`, `TranscriptSource`, `listNavigableAgents(agents, registry)`, `liveSource(record)`, `buildLabel(record, registry)`.
|
|
45
|
+
It imports SDK *types* only (`ToolDefinition`) and the lifecycle type `SubagentStatus`.
|
|
46
|
+
- `src/ui/session-navigator.ts` — the SDK/TUI consumer half: `SessionNavigatorHandler` (picker + source selection) and `TranscriptOverlay` (read-only scrollable renderer).
|
|
47
|
+
The handler already calls `liveSource(entry.record)`; the source is the only seam that varies.
|
|
48
|
+
- `src/lifecycle/subagent-manager.ts` — `listAgents()` (sorted live records), `cleanup()` (the 10-minute sweep), `clearCompleted()` (new-session wipe, called from `handlers/lifecycle.ts` on `session_start` / `session_before_switch`), `removeRecord()` (`disposeSession()` + `agents.delete`).
|
|
49
|
+
- `src/lifecycle/subagent.ts` — `Subagent` exposes `id`, `type`, `description` (readonly), and getters `status`, `startedAt`, `completedAt`, `toolUses`, `outputFile`.
|
|
50
|
+
- `src/index.ts` — registers `subagent-sessions`, calling `sessionNavigator.handle({ ui, agents: manager.listAgents(), registry, cwd })`.
|
|
51
|
+
|
|
52
|
+
SDK functions (`@earendil-works/pi-coding-agent`): `parseSessionEntries(content): FileEntry[]`, `buildSessionContext(entries: SessionEntry[]): { messages: AgentMessage[]; … }`, and the types `SessionEntry` / `SessionHeader`.
|
|
53
|
+
`FileEntry = SessionHeader | SessionEntry`; only the header has `type: "session"`, so `entries.filter((e): e is SessionEntry => e.type !== "session")` is a sound type guard.
|
|
54
|
+
`SessionMessage` (the navigator's message type) is `SessionContext["messages"][number]` = `AgentMessage`, so `buildSessionContext(...).messages` matches `TranscriptSource.getMessages()` with no cast.
|
|
55
|
+
|
|
56
|
+
Constraints from AGENTS.md / skills that apply:
|
|
57
|
+
|
|
58
|
+
- `code-design` SDK-boundary guideline: pure helpers should avoid SDK *runtime* imports.
|
|
59
|
+
`fileSnapshotSource` calls the SDK runtime functions `parseSessionEntries` / `buildSessionContext` directly (see Design Overview → SDK-runtime decision); there is no `no-restricted-imports` lint rule, and the module already depends on SDK types.
|
|
60
|
+
- `testing`: components used by `session-navigator.ts` need `initTheme(undefined, false)` in `beforeAll` (already present in `session-navigator.test.ts`).
|
|
61
|
+
- `fallow dead-code`: a new export with no caller fails the gate.
|
|
62
|
+
Producers (`fileSnapshotSource`, `listEvicted`) gain their caller in the integration step within the same push; the pushed tip is dead-code-clean (see Risks).
|
|
63
|
+
|
|
64
|
+
## Design Overview
|
|
65
|
+
|
|
66
|
+
### Candidate-set decision — manager-retained descriptors (not a directory scan)
|
|
67
|
+
|
|
68
|
+
The issue proposes enumerating persisted JSONL files in the tasks directory.
|
|
69
|
+
The persisted child session carries **no** subagent `type` or `description` — those live only on the in-memory `Subagent` record; the file holds only the conversation plus a header (`id`, `timestamp`, `cwd`, `parentSession`).
|
|
70
|
+
A directory scan therefore yields degraded labels (agent type → generic placeholder; curated `description` → a raw first-prompt snippet or filename) and must parse every file on every picker open.
|
|
71
|
+
|
|
72
|
+
Decision (confirmed with the operator): the manager retains a lightweight **descriptor** at eviction time instead.
|
|
73
|
+
When `cleanup()` disposes a record that has a persisted file, it first copies the record's label fields plus `outputFile` into a separate `evicted` map (no messages — the heavy state is still freed, so memory stays bounded by the number of subagents spawned, not their transcripts).
|
|
74
|
+
The picker's candidate set is `live ∪ evicted`, deduped by id, with identical rich labels for both.
|
|
75
|
+
|
|
76
|
+
Coverage: this surfaces agents evicted **in the current root session** — which are the cleanup sweep's only targets.
|
|
77
|
+
A fresh `SubagentManager` is created per session and `clearCompleted()` runs on `session_start`, so prior-process subagents are never reloaded into the manager; the only files a directory scan would *additionally* surface are old-session orphans, which are exactly the ones with degraded labels.
|
|
78
|
+
Picking an evicted agent still reads its file to render (the messages are gone from memory regardless) — so `fileSnapshotSource` is required either way; the descriptor only feeds the *label*.
|
|
79
|
+
|
|
80
|
+
### SDK-runtime decision — call `parseSessionEntries` / `buildSessionContext` directly
|
|
81
|
+
|
|
82
|
+
`fileSnapshotSource` imports and calls these SDK runtime functions directly rather than injecting them.
|
|
83
|
+
Rationale: they are deterministic parsers of Pi's own session format; the injected `readFile` already provides the unit-test seam (a fake `readFile` returning fixture JSONL fully exercises parse → drop-header → build → messages), so injecting the parsers adds wiring without testability gain.
|
|
84
|
+
There is no `no-restricted-imports` rule, and `session-navigation.ts` already imports SDK types.
|
|
85
|
+
|
|
86
|
+
### Data shapes
|
|
87
|
+
|
|
88
|
+
`EvictedSubagent` — the descriptor, owned by lifecycle (the manager constructs it), imported as a type by the UI (the UI already imports `SubagentStatus` from lifecycle):
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// src/lifecycle/subagent-manager.ts
|
|
92
|
+
export interface EvictedSubagent {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly type: SubagentType;
|
|
95
|
+
readonly description: string;
|
|
96
|
+
readonly status: SubagentStatus;
|
|
97
|
+
readonly startedAt: number;
|
|
98
|
+
readonly completedAt: number | undefined;
|
|
99
|
+
readonly toolUses: number;
|
|
100
|
+
readonly outputFile: string;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`NavigationEntry` changes from `{ record, label }` to a discriminated union so the handler can pick the source by kind:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// src/ui/session-navigation.ts
|
|
108
|
+
export type NavigationEntry =
|
|
109
|
+
| { readonly kind: "live"; readonly label: string; readonly record: NavigableSubagent }
|
|
110
|
+
| { readonly kind: "evicted"; readonly label: string; readonly outputFile: string };
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`buildLabel` is narrowed to a per-call interface (ISP) that both a `NavigableSubagent` and an `EvictedSubagent` satisfy, and gains an `evicted` marker:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface LabelFields {
|
|
117
|
+
readonly type: SubagentType;
|
|
118
|
+
readonly description: string;
|
|
119
|
+
readonly status: SubagentStatus;
|
|
120
|
+
readonly startedAt: number;
|
|
121
|
+
readonly completedAt: number | undefined;
|
|
122
|
+
readonly toolUses: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildLabel(fields: LabelFields, registry: AgentConfigLookup, evicted = false): string {
|
|
126
|
+
const name = getDisplayName(fields.type, registry);
|
|
127
|
+
const duration = formatDuration(fields.startedAt, fields.completedAt);
|
|
128
|
+
const marker = evicted ? " · evicted (snapshot)" : "";
|
|
129
|
+
return `${name} (${fields.description}) · ${fields.toolUses} tools · ${fields.status} · ${duration}${marker}`;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `fileSnapshotSource`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
export function fileSnapshotSource(
|
|
137
|
+
outputFile: string,
|
|
138
|
+
readFile: (path: string) => string,
|
|
139
|
+
): TranscriptSource {
|
|
140
|
+
const entries = parseSessionEntries(readFile(outputFile));
|
|
141
|
+
const sessionEntries = entries.filter(
|
|
142
|
+
(entry): entry is SessionEntry => entry.type !== "session",
|
|
143
|
+
);
|
|
144
|
+
const { messages } = buildSessionContext(sessionEntries);
|
|
145
|
+
return {
|
|
146
|
+
getMessages: () => messages,
|
|
147
|
+
subscribe: () => undefined, // static snapshot — no live updates
|
|
148
|
+
streaming: () => undefined, // never streaming
|
|
149
|
+
getToolDefinition: () => undefined, // no live tool registry off disk
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `listNavigableAgents` (broadened) and the handler call site
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
export function listNavigableAgents(
|
|
158
|
+
agents: readonly NavigableSubagent[],
|
|
159
|
+
evicted: readonly EvictedSubagent[],
|
|
160
|
+
registry: AgentConfigLookup,
|
|
161
|
+
): NavigationEntry[] {
|
|
162
|
+
const live = agents
|
|
163
|
+
.filter((record) => record.isSessionReady())
|
|
164
|
+
.map((record): NavigationEntry => ({ kind: "live", record, label: buildLabel(record, registry) }));
|
|
165
|
+
const liveIds = new Set(agents.map((record) => record.id));
|
|
166
|
+
const evictedEntries = evicted
|
|
167
|
+
.filter((d) => !liveIds.has(d.id))
|
|
168
|
+
.map((d): NavigationEntry => ({ kind: "evicted", outputFile: d.outputFile, label: buildLabel(d, registry, true) }));
|
|
169
|
+
return [...live, ...evictedEntries];
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Dedup is keyed by `id` (both shapes carry it) rather than `outputFile`: a record leaves `listAgents()` at the same instant its descriptor is captured, so the sets cannot overlap; the filter is defensive.
|
|
174
|
+
Order is live-then-evicted, which is already recency-ordered (evicted agents completed > 10 min ago).
|
|
175
|
+
|
|
176
|
+
Handler consumer sketch (Tell-Don't-Ask: the entry tells the handler its kind; the handler asks the right source constructor — no reach-through into the record's internals):
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const entry = entries.find((candidate) => candidate.label === choice);
|
|
180
|
+
if (!entry) return;
|
|
181
|
+
let source: TranscriptSource;
|
|
182
|
+
try {
|
|
183
|
+
source = entry.kind === "live" ? liveSource(entry.record) : fileSnapshotSource(entry.outputFile, readFile);
|
|
184
|
+
} catch {
|
|
185
|
+
ui.notify("Could not read the session transcript file.", "error");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// …unchanged: getMarkdownTheme(), ui.custom(new TranscriptOverlay({ source, … }))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Manager retention sketch
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
private readonly evicted = new Map<string, EvictedSubagent>();
|
|
195
|
+
|
|
196
|
+
private cleanup() {
|
|
197
|
+
const cutoff = Date.now() - 10 * 60_000;
|
|
198
|
+
for (const [id, record] of this.agents) {
|
|
199
|
+
if (record.status === "running" || record.status === "queued") continue;
|
|
200
|
+
if ((record.completedAt ?? 0) >= cutoff) continue;
|
|
201
|
+
if (record.outputFile) this.evicted.set(id, toEvictedSubagent(record));
|
|
202
|
+
this.removeRecord(id, record);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
listEvicted(): EvictedSubagent[] {
|
|
207
|
+
return [...this.evicted.values()].sort((a, b) => b.startedAt - a.startedAt);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`clearCompleted()` and `dispose()` also `this.evicted.clear()` — descriptors belong to the session that evicted them, so a new session starts empty.
|
|
212
|
+
`toEvictedSubagent(record)` copies the eight fields; it is a private helper (or a free function in the module) and reads `record.outputFile` only after the `if (record.outputFile)` guard, so the `outputFile` field is always a defined `string`.
|
|
213
|
+
|
|
214
|
+
### Edge cases
|
|
215
|
+
|
|
216
|
+
- **`outputFile` undefined at eviction** (headless / never-persisted session) → no descriptor captured; nothing to navigate.
|
|
217
|
+
- **File deleted/unreadable after eviction** → `fileSnapshotSource`'s eager read throws; the handler's `try/catch` notifies and returns without opening the overlay.
|
|
218
|
+
- **Empty or header-only JSONL** → `buildSessionContext` returns `messages: []`; the overlay renders an empty transcript (no crash).
|
|
219
|
+
- **No navigable sessions at all** (no live, no evicted) → existing `ui.notify("No subagent sessions to view.", "info")` path, unchanged.
|
|
220
|
+
|
|
221
|
+
## Module-Level Changes
|
|
222
|
+
|
|
223
|
+
- `src/lifecycle/subagent-manager.ts` — add the `EvictedSubagent` interface (exported), the `evicted` map field, descriptor capture in `cleanup()`, `evicted.clear()` in `clearCompleted()` and `dispose()`, the `listEvicted()` method, and the `toEvictedSubagent` helper.
|
|
224
|
+
Import `SubagentType` (already in `#src/types`) and `SubagentStatus` (`#src/lifecycle/subagent-state`) for the interface.
|
|
225
|
+
- `src/ui/session-navigation.ts` — add `fileSnapshotSource`; import `EvictedSubagent` (type), and the SDK runtime `parseSessionEntries` / `buildSessionContext` plus the `SessionEntry` type; change `NavigationEntry` to the discriminated union; broaden `listNavigableAgents` to `(agents, evicted, registry)`; narrow `buildLabel` to `LabelFields` + `evicted` flag.
|
|
226
|
+
- `src/ui/session-navigator.ts` — `SessionNavigatorParams` gains `evicted: readonly EvictedSubagent[]` and `readFile: (path: string) => string`; the handler selects the source by `entry.kind` inside a `try/catch`.
|
|
227
|
+
No change to `TranscriptOverlay`, `buildTranscriptComponents`, `addMessageComponents`, or `addUserComponents`.
|
|
228
|
+
- `src/index.ts` — add `import { readFileSync } from "node:fs"`; the `subagent-sessions` handler passes `evicted: manager.listEvicted()` and `readFile: (path) => readFileSync(path, "utf8")`.
|
|
229
|
+
- `docs/architecture/architecture.md` — mark Step 4b ([#463]) **Landed** with a note recording the descriptor-vs-scan decision and that ADR-0004 Addendum 2's dual-source design is now realized; flip the `S4b` node label in the step-dependency Mermaid diagram to ✅.
|
|
230
|
+
- `docs/decisions/0004-reconsider-ui-direction.md` — optional one-line note under Addendum 2 that Step 4b chose manager-retained descriptors over a directory scan (the addendum's "evicted/untracked" wording said file snapshot for *rendering*, which still holds; only the *candidate-set* mechanism is being pinned down).
|
|
231
|
+
|
|
232
|
+
No SKILL update: the package SKILL's UI section references no session-navigation internals and the file/domain counts are unchanged (no new module — `fileSnapshotSource` joins the existing `session-navigation.ts`).
|
|
233
|
+
The architecture LOC/complexity tables are explicit end-of-phase snapshots, not per-file inventories, so a ~30-LOC addition does not update them.
|
|
234
|
+
|
|
235
|
+
## Test Impact Analysis
|
|
236
|
+
|
|
237
|
+
1. **New unit tests enabled by this change:**
|
|
238
|
+
- `fileSnapshotSource` (pure, in `session-navigation.test.ts`): fixture JSONL via a fake `readFile` → assert `getMessages()` equals the parsed messages, the `SessionHeader` is dropped, and `subscribe()` / `streaming()` / `getToolDefinition()` return `undefined`.
|
|
239
|
+
- `listNavigableAgents` with evicted descriptors: evicted entries appear with the `· evicted (snapshot)` marker; an evicted descriptor whose id matches a live record is deduped out.
|
|
240
|
+
- `SubagentManager.listEvicted()` (in `subagent-manager.test.ts`): after advancing fake timers past the cutoff, a completed agent with an `outputFile` produces a descriptor; one without an `outputFile` does not; `clearCompleted()` empties the set.
|
|
241
|
+
- `SessionNavigatorHandler` evicted path (in `session-navigator.test.ts`): picking an evicted label opens an overlay sourced from `readFile`; a throwing `readFile` notifies and skips the overlay.
|
|
242
|
+
2. **Tests that become redundant:** none — the live-source tests still exercise the tracked-agent branch.
|
|
243
|
+
3. **Tests that must stay as-is:** the existing `liveSource`, `listNavigableAgents` (live-only), `TranscriptOverlay`, and live-source handler tests — they pin the tracked-agent behavior this change must not regress.
|
|
244
|
+
|
|
245
|
+
## Invariants at risk
|
|
246
|
+
|
|
247
|
+
Step 4 (#445) and Step 4a (#462) established invariants on the navigation surface; this step must preserve them:
|
|
248
|
+
|
|
249
|
+
- **Handler is a reactive consumer with no inbound call into the core** (Invariant #423/#445).
|
|
250
|
+
Pinned by the existing `session-navigator.test.ts` assertion `expect(record.getToolDefinition).not.toHaveBeenCalled()` in the live-source handler test.
|
|
251
|
+
The evicted path reads only `entry.outputFile` (a string) + the injected `readFile`; it makes no inbound call into a record.
|
|
252
|
+
- **Read-only, non-interactive overlay** (#445/#446).
|
|
253
|
+
The file source returns `subscribe: () => undefined` and `streaming: () => undefined`; the overlay's existing read-only behavior is unchanged.
|
|
254
|
+
Pinned by the existing overlay tests.
|
|
255
|
+
- **Renderer parity via Pi per-entry components** (#462).
|
|
256
|
+
The renderer is untouched; both sources still yield `SessionMessage[]` into the same `buildTranscriptComponents`.
|
|
257
|
+
Pinned by the existing `TranscriptOverlay` render tests.
|
|
258
|
+
|
|
259
|
+
No new test is needed for these — existing tests already pin them; the integration step keeps them green.
|
|
260
|
+
|
|
261
|
+
## TDD Order
|
|
262
|
+
|
|
263
|
+
1. **`fileSnapshotSource` (pure, additive).**
|
|
264
|
+
Surface: `test/ui/session-navigation.test.ts`.
|
|
265
|
+
Red: a `describe("fileSnapshotSource")` with a fake `readFile` returning fixture JSONL (a `session` header line + a couple of `message` entries) — assert messages parsed, header dropped, and the three static-source methods return `undefined`.
|
|
266
|
+
Green: implement `fileSnapshotSource` and the SDK runtime imports.
|
|
267
|
+
Commit: `test(pi-subagents): cover file-snapshot transcript source` then `feat(pi-subagents): add file-snapshot transcript source` (or a single `feat` commit with test + impl).
|
|
268
|
+
2. **Manager evicted-descriptor retention (additive).**
|
|
269
|
+
Surface: `test/lifecycle/subagent-manager.test.ts`.
|
|
270
|
+
Red: with fake timers, advance past the 10-minute cutoff and assert `listEvicted()` returns a descriptor for a completed agent with an `outputFile`, none for one without, and an empty set after `clearCompleted()`.
|
|
271
|
+
Green: add `EvictedSubagent`, the `evicted` map, capture in `cleanup()`, `clear()` in `clearCompleted()`/`dispose()`, `listEvicted()`, and `toEvictedSubagent`.
|
|
272
|
+
Run `pnpm run check` after this commit (new exported interface).
|
|
273
|
+
Commit: `feat(pi-subagents): retain evicted-agent descriptors in the manager`.
|
|
274
|
+
3. **Integrate: broaden the candidate set and dual-source the handler (breaking the `NavigationEntry` shape).**
|
|
275
|
+
This step changes `NavigationEntry` and `listNavigableAgents`'s signature, so the handler, `index.ts` call site, and both UI test files break and are updated together.
|
|
276
|
+
Surfaces: `src/ui/session-navigation.ts`, `src/ui/session-navigator.ts`, `src/index.ts`, `test/ui/session-navigation.test.ts`, `test/ui/session-navigator.test.ts`.
|
|
277
|
+
Red: update `listNavigableAgents` tests to the union shape and add the evicted-entry (marker + dedup) cases; add the handler evicted-path tests (file source + throwing-`readFile` notify).
|
|
278
|
+
Green: change `NavigationEntry` to the union, broaden `listNavigableAgents`, narrow `buildLabel`, add `evicted` + `readFile` to `SessionNavigatorParams`, switch the handler source by kind inside `try/catch`, and wire `manager.listEvicted()` + `readFileSync` in `index.ts`.
|
|
279
|
+
Run `pnpm run check` (shared interface change with a single non-test call site).
|
|
280
|
+
Commit: `feat(pi-subagents): source evicted-agent transcripts from disk in /subagent-sessions`.
|
|
281
|
+
4. **Docs.**
|
|
282
|
+
Update `docs/architecture/architecture.md` Step 4b to Landed (descriptor decision + dual-source realized) and the `S4b` diagram node to ✅; add the optional Addendum 2 note in `docs/decisions/0004-reconsider-ui-direction.md`.
|
|
283
|
+
Commit: `docs(pi-subagents): mark Phase 19 Step 4b landed (#463)`.
|
|
284
|
+
|
|
285
|
+
## Risks and Mitigations
|
|
286
|
+
|
|
287
|
+
- **Transient dead code between steps 1–2 and 3.**
|
|
288
|
+
`fileSnapshotSource` and `listEvicted()` have no caller until step 3.
|
|
289
|
+
CI / pre-completion `fallow dead-code` runs on the pushed tip, which is clean once step 3 lands; all steps ship in one push.
|
|
290
|
+
Mitigation: do not run `/ship-issue` before step 3 is committed.
|
|
291
|
+
- **`buildSessionContext` / `parseSessionEntries` behavior on malformed JSONL.**
|
|
292
|
+
Mitigation: the handler `try/catch` covers a throwing read/parse; a TDD fixture exercises a valid header-plus-messages file, and an empty-messages case confirms a graceful empty render.
|
|
293
|
+
- **Memory: the `evicted` map grows over a long session.**
|
|
294
|
+
Each descriptor is eight scalar fields (no messages), bounded by subagents spawned; the heavy session objects are still disposed.
|
|
295
|
+
`clearCompleted()` resets it per session.
|
|
296
|
+
- **Divergence from the issue's "enumerate persisted JSONL files" wording.**
|
|
297
|
+
Documented and operator-confirmed (descriptors over scan, for rich labels and bounded IO); the architecture/ADR notes record the rationale so the file-scan option is not silently lost.
|
|
298
|
+
|
|
299
|
+
## Open Questions
|
|
300
|
+
|
|
301
|
+
None — the candidate-set strategy and the evicted-entry marker were resolved with the operator during planning.
|
|
302
|
+
|
|
303
|
+
[#445]: https://github.com/gotgenes/pi-packages/issues/445
|
|
304
|
+
[#446]: https://github.com/gotgenes/pi-packages/issues/446
|
|
305
|
+
[#462]: https://github.com/gotgenes/pi-packages/issues/462
|
|
306
|
+
[#463]: https://github.com/gotgenes/pi-packages/issues/463
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 441
|
|
3
|
+
issue_title: "pi-subagents: remove the orphaned agent-definition management subtree"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #441 — pi-subagents: remove the orphaned agent-definition management subtree
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-23T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a deletion-only plan for Phase 19 Step 6: `git rm` the orphaned creation wizard, config editor, and their two file-ops helpers (plus tests), prune `test/helpers/ui-stubs.ts` to just `makeMenuUI`, and update the two current-state docs.
|
|
13
|
+
Verified against `main` that the five modules are pure orphans (no `src/` importer, `index.ts` clean) and that this is the unreleased tail of release batch "dissolve-agents".
|
|
14
|
+
The plan routes to `/build-plan` (no test cycles) with two commits.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Two deviations from the issue body, both forced by the codebase + the authoritative architecture doc rather than by preference, so no `ask_user` gate was used:
|
|
19
|
+
1. `menu-ui.ts` (the `MenuUI` interface) must also be deleted — it is orphaned by the same cut, and `architecture.md` lines 346/1076 explicitly schedule its removal under #441.
|
|
20
|
+
The issue body omits it.
|
|
21
|
+
2. `makeMenuManager` is removed whole, not just its `spawnAndWait` field — after the four test files go, its only consumer is its own self-test, so it is residual clutter.
|
|
22
|
+
The architecture doc's "if no surviving consumer remains" phrasing licenses this.
|
|
23
|
+
- `ui-stubs.ts` survives because `makeMenuUI` still has a real consumer (`subagents-settings.test.ts`); only the three other helpers (and the private `DEFAULT_TEST_AGENT_CONFIG` + the `AgentConfig` import) are pruned.
|
|
24
|
+
- Commit type is `refactor(pi-subagents):`, not `feat!:` — deleting already-unreachable code changes no observable behavior at this step.
|
|
25
|
+
The release is driven by Step 5's unreleased breaking `feat!:` (`cb813f2c`, after tag `pi-subagents-v17.5.0`); landing this tail lets release-please cut the major bump.
|
|
26
|
+
- Production duplication goes to zero when `agent-config-editor.ts` is deleted (the 11-line `disableAgent`/`ejectAgent` clone); pin with `pnpm fallow dupes`.
|
|
27
|
+
- Historical docs under `docs/plans/`, `docs/retro/`, and `docs/architecture/history/` mention the deleted modules only as records of completed phases — left untouched per convention; only `architecture.md` current-state and `SKILL.md` are updated.
|
|
28
|
+
|
|
29
|
+
## Stage: Implementation — Build (2026-06-23T17:00:00Z)
|
|
30
|
+
|
|
31
|
+
### Session summary
|
|
32
|
+
|
|
33
|
+
Executed the two-commit plan: `git rm` the five orphaned `src/ui/` modules and four test files in one atomic commit; pruned `test/helpers/ui-stubs.ts` and `ui-stubs.test.ts` to `makeMenuUI` only in the same commit.
|
|
34
|
+
Second commit updated `architecture.md` (directory tree, Step 6 ✅ and Landed note, Mermaid node) and `SKILL.md` (UI domain row 11→6).
|
|
35
|
+
Three additional doc-fixup commits addressed stale `architecture.md` prose (domain flowchart, cross-extension diagram, "What the core owns," "Composition model") deferred from #442.
|
|
36
|
+
All checks green: 62 test files / 950 tests, `fallow dead-code` clean, `fallow dupes` shows 0 production clone groups.
|
|
37
|
+
|
|
38
|
+
### Observations
|
|
39
|
+
|
|
40
|
+
- The pre-completion reviewer surfaced five stale `architecture.md` sections not covered by the plan's declared doc scope — all carried over from #442's retro note "Deferred the holistic architecture-doc refresh … to the batch tail [#441]."
|
|
41
|
+
Required three extra doc-fixup commits (`e440d0d1`, `04b13812`, and the SKILL.md file-count fix) beyond the plan's two.
|
|
42
|
+
**Lesson:** when a retro note explicitly defers a doc refresh to the batch tail, include it in the batch-tail plan's "Module-Level Changes" doc section so it isn't discovered only by the reviewer.
|
|
43
|
+
- `makeMenuManager` was removed whole (not just its `spawnAndWait` relay) because its only post-cut consumer was its own self-test — exactly the right call, consistent with the planning decision.
|
|
44
|
+
- Final file count in `src/`: 57 (was 58 in SKILL.md header; now corrected).
|
|
45
|
+
- Pre-completion reviewer: **PASS** (third dispatch, after two WARN rounds on the stale doc sections).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 442
|
|
3
|
+
issue_title: "pi-subagents: dissolve /agents and remove the conversation-viewer subtree"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #442 — pi-subagents: dissolve /agents and remove the conversation-viewer subtree
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-23T18:06:31Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 19 Step 5: dissolve the `/agents` command and delete the conversation-viewer subtree (`agent-menu.ts`, `conversation-viewer.ts`, `message-formatters.ts`, plus their three tests), dewire `index.ts`.
|
|
13
|
+
Investigation surfaced a blocker the issue overlooked — a bidirectional type cycle between the hub and its leaves — and the operator chose a tidy-first resolution.
|
|
14
|
+
Plan committed at `packages/pi-subagents/docs/plans/0442-dissolve-agents-conversation-viewer.md`; this is a `/build-plan` (no red→green cycles), batched for release with the tail [#441].
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Key finding: `agent-creation-wizard.ts` and `agent-config-editor.ts` (which survive until [#441]) both `import type { MenuUI } from "#src/ui/agent-menu"` and use it throughout, while the hub value-imports the wizard/editor classes — a bidirectional cycle.
|
|
19
|
+
Deleting either subtree first breaks `tsc --noEmit` (type-checks `src` + `test`).
|
|
20
|
+
The issue's "pure orphans" premise holds for runtime reachability but not at the type level.
|
|
21
|
+
- Flipping the [#442]/[#441] order does **not** fix it (cycle is bidirectional) — surfaced this directly when the operator asked about step ordering.
|
|
22
|
+
- Operator decision via `ask_user`: keep two commits, relocate `MenuUI` to a surviving `src/ui/menu-ui.ts` first (tidy-first).
|
|
23
|
+
Rejected alternatives: merge [#441] into [#442] (one deletion commit), or inline a throwaway `MenuUI` into the doomed leaves.
|
|
24
|
+
- Operator confirmed release timing: do not release until both [#442] and [#441] land — marker is `mid-batch — defer (batch "dissolve-agents")`, tail is [#441].
|
|
25
|
+
- `menu-ui.ts` is intentionally transient (one issue's lifespan); it keeps two live consumers (wizard/editor) immediately, so no fallow dead-code flag; [#441] deletes it with its consumers.
|
|
26
|
+
- Verified no collateral: `FsAgentFileOps` stays off the dead-code list because its own test still imports it; `subagents-settings.ts` defines its own `SubagentsSettingsUI` (no `MenuUI` coupling); `ui-stubs.ts`'s `makeMenuUI` is structurally typed; `join`/`buildParentSnapshot` become dead `index.ts` imports and are removed; deleting the consumers orphans no `display.ts` export.
|
|
27
|
+
- Change is breaking (the `/agents` command disappears) → deletion commit is `feat(pi-subagents)!:` with a `BREAKING CHANGE:` footer naming `/subagents:settings`, `/subagents:sessions`, and the background widget (verified the real registered command names, not the architecture's proposed `/subagents-settings`).
|
|
28
|
+
- Deferred the holistic architecture-doc refresh (Mermaid domain diagram, complexity/health tables, Phase-19-to-history migration) to the batch tail [#441] to avoid double-editing tables that [#441] also touches; [#442] keeps only the current-state file tree, the Step 5 `Outcome:` annotation, and the SKILL.md UI count accurate.
|
|
29
|
+
|
|
30
|
+
[#441]: https://github.com/gotgenes/pi-packages/issues/441
|
|
31
|
+
[#442]: https://github.com/gotgenes/pi-packages/issues/442
|
|
32
|
+
|
|
33
|
+
## Stage: Implementation — Build (2026-06-23T19:30:00Z)
|
|
34
|
+
|
|
35
|
+
### Session summary
|
|
36
|
+
|
|
37
|
+
Executed both plan commits (Step 1: extract `MenuUI`; Step 2: delete hub+viewer+formatters and dewire `index.ts`).
|
|
38
|
+
A mid-session diversion investigated the commitlint `#N`-in-body false positive (issue [#4099]), determined it is still present in v21.0.2 despite the issue being closed, and removed the `--strict` flag from `prek.toml` so the warning no longer blocks commits.
|
|
39
|
+
Pre-completion reviewer returned PASS.
|
|
40
|
+
|
|
41
|
+
### Observations
|
|
42
|
+
|
|
43
|
+
- **Unplanned fallow finding:** after deleting `agent-menu.ts`, `showAgentDetail` (`agent-config-editor.ts`) and `showCreateWizard` (`agent-creation-wizard.ts`) lost their only external callers and became unused class members.
|
|
44
|
+
The plan's risk analysis covered unused *files* (`FsAgentFileOps`) but not unused class *methods*.
|
|
45
|
+
Added `// fallow-ignore-next-line unused-class-member` suppressions with a comment pointing to [#441] as the removal commit.
|
|
46
|
+
Future plans for hub-deletion steps should explicitly check whether public methods on surviving leaf classes lose their only caller.
|
|
47
|
+
- **Unplanned commitlint diversion:** bodies with `#N` mid-sentence in multi-paragraph messages are still misidentified as footer tokens by commitlint v21.0.2 despite issue [#4099] being closed COMPLETED on 2026-06-02 with no linked PR.
|
|
48
|
+
Removed `--strict` from the `commit-msg` hook in `prek.toml` so the false-positive is a non-blocking warning; updated `AGENTS.md` accordingly.
|
|
49
|
+
This also required untangling a bad commit (the `git rm`'d pi-subagents files were swept into the commitlint fix commit) via `git reset HEAD~1` + selective re-staging.
|
|
50
|
+
- **Commit message refinement:** learned empirically that `#N` appearing in a wrapped body line (near end of line) triggers the false positive even in a single-paragraph body; a two-paragraph body with `#N` in the second paragraph was the original failure mode.
|
|
51
|
+
Workaround before the `--strict` removal: keep `#N` out of body prose; use `Refs #N` as a true footer with a blank-line separator.
|
|
52
|
+
- Pre-completion reviewer: PASS — all deterministic checks green; doc-staleness WARNs (Mermaid domain diagram, structural tables, Phase 19 history) are intentional mid-batch deferred work per the plan's Non-Goals, to be resolved at the batch tail [#441].
|
|
53
|
+
|
|
54
|
+
[#4099]: https://github.com/conventional-changelog/commitlint/issues/4099
|
|
55
|
+
|
|
56
|
+
## Stage: Final Retrospective (2026-06-23T20:05:13Z)
|
|
57
|
+
|
|
58
|
+
### Session summary
|
|
59
|
+
|
|
60
|
+
Shipped Phase 19 Step 5 across plan → build → ship: extracted `MenuUI` to break a bidirectional type cycle, deleted the `/agents` hub plus the conversation-viewer subtree, and dewired `index.ts`.
|
|
61
|
+
The release was correctly deferred at ship time per the `mid-batch — defer` marker (batch tail is [#441]).
|
|
62
|
+
The session's dominant friction was an unplanned commitlint diversion that produced a blunt fix, then a superseding surgical fix, a bad-commit untangle, and a new tracking issue ([#468]).
|
|
63
|
+
|
|
64
|
+
### Observations
|
|
65
|
+
|
|
66
|
+
#### What went well
|
|
67
|
+
|
|
68
|
+
1. The Planning `ask_user` gate caught the hub↔leaf bidirectional type cycle before any code was written and produced a clean tidy-first plan (relocate `MenuUI` first), so the two implementation commits were each green — no broken-commit sequence.
|
|
69
|
+
2. The commitlint claim was verified empirically by running `commitlint` against crafted test messages, not by trusting the closed-issue status — this confirmed [#4099] was closed `COMPLETED` without a shipped fix and that the false positive still fired in `v21.0.2`.
|
|
70
|
+
3. Incremental verification: `pnpm run check`/`lint`/`test`/`fallow dead-code` ran after each implementation step, so the unused-class-member finding surfaced at the right step rather than at the end.
|
|
71
|
+
|
|
72
|
+
#### What caused friction (agent side)
|
|
73
|
+
|
|
74
|
+
1. `premature-convergence` — the first commitlint fix (`9b60c943`, remove `--strict`) was blunt: dropping `--strict` silences every warning-level rule, including the load-bearing malformed-`!`-header rejection (Refs #457).
|
|
75
|
+
The surgical fix (`40189cc4`, disable only `footer-leading-blank`, keep `--strict`) only emerged from a separate branch reconsideration.
|
|
76
|
+
Impact: one superseded `fix(commitlint):` commit now permanent in pushed history; a #457 regression was avoided only because the blunt fix was reconsidered before the batch released.
|
|
77
|
+
2. `other` (git workflow) — `git rm` pre-stages its deletions, so a later `git add prek.toml AGENTS.md && git commit` for the commitlint fix swept the already-staged `/agents` deletions into the wrong commit.
|
|
78
|
+
Required `git reset HEAD~1` plus selective re-staging to untangle.
|
|
79
|
+
Impact: ~4 extra tool calls; caught immediately, no lasting damage.
|
|
80
|
+
3. `missing-context` / unused-tool — determining the upstream commitlint issue state took several rounds of `web_search` + `fetch_content` until the user said "Remember you have access to the `gh` CLI, too"; `gh issue view 4099 --repo conventional-changelog/commitlint` then returned the authoritative state (`CLOSED`/`COMPLETED`, no linked PR) in one call.
|
|
81
|
+
Impact: several wasted research rounds; user-caught.
|
|
82
|
+
4. `missing-context` (minor) — the plan's risk analysis covered unused *files* (`FsAgentFileOps`, kept live by its test import) but not unused class *methods*; deleting the hub orphaned `showAgentDetail` and `showCreateWizard`, tripping `fallow dead-code`.
|
|
83
|
+
Impact: low — caught by the gate, resolved with two `fallow-ignore-next-line unused-class-member` suppressions pointing at [#441].
|
|
84
|
+
|
|
85
|
+
#### What caused friction (user side)
|
|
86
|
+
|
|
87
|
+
1. The agent reached for `web_search`/`fetch_content` to check a GitHub issue's state instead of `gh`; the user had to point at an available tool.
|
|
88
|
+
Opportunity: reach for `gh issue view`/`gh pr view` first for GitHub issue/PR state, including upstream repos.
|
|
89
|
+
2. The user supplied the steer toward a surgical fix ("prefer surgical fixes over blunt ones").
|
|
90
|
+
Opportunity: a standing rule about disabling the single offending rule rather than the enforcement mode could have produced the surgical fix on the first pass.
|
|
91
|
+
|
|
92
|
+
### Diagnostic details
|
|
93
|
+
|
|
94
|
+
- **Model-performance correlation** — the `pre-completion-reviewer` subagent ran on `anthropic/claude-sonnet-4-6` (per its agent frontmatter), appropriate for judgment-heavy review; the implementation ran on `claude-opus-4-8`.
|
|
95
|
+
No quality mismatch.
|
|
96
|
+
The many `model_change` entries are transient UI cycling (no assistant turn under most), not real reassignments.
|
|
97
|
+
- **Escalation-delay tracking** — the upstream-issue-state investigation ran ~6 consecutive `web_search`/`fetch_content` calls before the `gh` reminder; one `gh issue view` would have resolved it immediately.
|
|
98
|
+
- **Unused-tool detection** — `gh` was available throughout but not used for upstream issue state until prompted.
|
|
99
|
+
- **Feedback-loop gap analysis** — no gap; verification ran incrementally after each step.
|
|
100
|
+
|
|
101
|
+
[#468]: https://github.com/gotgenes/pi-packages/issues/468
|
|
102
|
+
|
|
103
|
+
### Changes made
|
|
104
|
+
|
|
105
|
+
1. `AGENTS.md` (`## Commits`) — tightened the `footer-leading-blank` note from ~62 words to rule + `Refs #468`, dropping the parser-variant list and the two upstream issue numbers (now here and in [#468]).
|
|
106
|
+
2. `AGENTS.md` (`## Commits`) — added a single-rule-vs-mode principle: disable the single offending lint rule (`[0]`), not the `--strict` enforcement mode that gates the others.
|
|
107
|
+
3. `AGENTS.md` (`## Workflow`) — added a bullet to use `gh issue view N --repo owner/repo` for GitHub issue/PR state instead of web search.
|
|
108
|
+
4. `AGENTS.md` (`## Commits`, git mechanics) — added a note that staged `git rm` deletions ride along with the next `git commit` even when only unrelated paths are `git add`ed; commit with an explicit pathspec or check `git status` first.
|
|
@@ -47,3 +47,59 @@ Test count went from 1084 to 1088 (+4 net: +4 accessor/seam tests, +3 new naviga
|
|
|
47
47
|
Pi's own JS passes `{ truncated: true }` but that path is untyped.
|
|
48
48
|
- Pre-completion reviewer verdict: **WARN** (no failures; 3 non-blocking findings).
|
|
49
49
|
Reviewer warnings: (1) `package-pi-subagents/SKILL.md` UI module count is stale (10 listed vs 13 actual) — predates this issue, plan left it deliberately; (2) `addMessageComponents` mutates the received `pendingTools` accumulator (output-argument pattern) — intentional, mirrors Pi's own `renderedPendingTools` local and was plan-reviewed; (3) invariant #423 held and is type-enforced but lacked an explicit spy pin — **addressed** by the follow-up `test:` commit asserting the handler never calls `record.getToolDefinition` directly.
|
|
50
|
+
|
|
51
|
+
## Stage: Final Retrospective (2026-06-23T00:00:00Z)
|
|
52
|
+
|
|
53
|
+
### Session summary
|
|
54
|
+
|
|
55
|
+
Shipped Phase 19 Step 4a end-to-end across plan → TDD → ship: `/subagent-sessions` now renders through Pi's own per-entry interactive components behind the unchanged `TranscriptSource` seam, released as `pi-subagents` v17.4.0.
|
|
56
|
+
Execution was notably clean — six commits, no rework of production code, CI green on the first push, and the only two friction points (a missing `initTheme()` in tests, a wrong post-review assertion) were each self-caught and resolved in a single iteration.
|
|
57
|
+
|
|
58
|
+
### Observations
|
|
59
|
+
|
|
60
|
+
#### What went well
|
|
61
|
+
|
|
62
|
+
1. **Upfront SDK verification in planning paid off at implementation.**
|
|
63
|
+
Planning inspected the actual component constructors, the `renderSessionContext` mapping, and `AgentSession.getToolDefinition` in `node_modules/**/*.d.ts` before committing to a design.
|
|
64
|
+
Result: the production code (`session-navigation.ts`, `session-navigator.ts`, `index.ts`) type-checked clean on the *first* `tsc` run during TDD — the SDK message-union narrowing and component construction needed no casts.
|
|
65
|
+
2. **Probe-driven de-risking.**
|
|
66
|
+
Before the renderer rewrite, a throwaway `_probe-theme.test.ts` confirmed `getMarkdownTheme()` + a Pi component render works outside interactive mode — validating the riskiest assumption cheaply, then deleted.
|
|
67
|
+
3. **The operator's planning interjections were strategic, not mechanical.**
|
|
68
|
+
Rather than picking an `ask_user` option, the operator asked "what update frequency are we anticipating?"
|
|
69
|
+
and flagged a dependency-inversion risk on tool definitions.
|
|
70
|
+
Both redirects improved the design: the frequency analysis justified rebuild-on-change, and the inversion concern led to the dependency-safe `getToolDefinition` read accessor (discovered on the SDK, not invented).
|
|
71
|
+
4. **Incremental verification throughout.**
|
|
72
|
+
Targeted `vitest` per Red→Green step, `pnpm run check` immediately after each shared-interface change, then full suite + root lint + `fallow dead-code` before each commit — no end-of-session verification pile-up.
|
|
73
|
+
5. **Pre-completion review caught a real gap and the fix's own bug was caught by the loop.**
|
|
74
|
+
The reviewer flagged the missing #423 spy pin; while adding it, an over-assertion (`getToolDefinition` called on a no-toolCall fixture) failed the test run and was removed before committing.
|
|
75
|
+
|
|
76
|
+
#### What caused friction (agent side)
|
|
77
|
+
|
|
78
|
+
1. `missing-context` (self-identified) — the `initTheme()` requirement for Pi's interactive components.
|
|
79
|
+
The de-risking probe exercised only `AssistantMessageComponent`, which happens *not* to read the global theme, so the probe passed and gave false confidence.
|
|
80
|
+
The real tests then threw `"Theme not initialized"` from `UserMessageComponent`/`ToolExecutionComponent`.
|
|
81
|
+
Impact: one extra Red cycle (4 failing tests) → added `beforeAll(() => initTheme(undefined, false))`; no production rework.
|
|
82
|
+
2. `other` (self-identified) — a wrong second assertion in the post-review #423 pin (`expect(record.getToolDefinition).toHaveBeenCalled()` on a fixture whose assistant message had no `toolCall`, so the lookup never fires).
|
|
83
|
+
Impact: one failed test run, immediately diagnosed and the bad assertion removed; ~1 iteration.
|
|
84
|
+
|
|
85
|
+
#### What caused friction (user side)
|
|
86
|
+
|
|
87
|
+
- None.
|
|
88
|
+
The operator's interventions were timely and strategic (see What went well #3); no context was withheld and no correction was reactive.
|
|
89
|
+
|
|
90
|
+
### Diagnostic details
|
|
91
|
+
|
|
92
|
+
- **Feedback-loop gap analysis** — no gap.
|
|
93
|
+
Verification ran incrementally: per-step targeted `vitest`, `pnpm run check` after each interface change, full suite + lint + `fallow` before commits.
|
|
94
|
+
Both friction points were caught by running tests *before* committing, not after.
|
|
95
|
+
- **Escalation-delay tracking** — no rabbit holes; each friction point resolved in a single iteration (well under the 5-tool-call threshold).
|
|
96
|
+
- **Probe-coverage note** — the `initTheme()` miss is a probe *coverage* gap, not a tool-availability gap: a one-variant probe (`AssistantMessageComponent`) did not validate the environment dependency of the broader component set actually used.
|
|
97
|
+
- **Model-performance / unused-tool** — nothing notable.
|
|
98
|
+
The only subagent dispatch (`pre-completion-reviewer`) is purpose-built for judgment-heavy review; SDK inspection used direct `grep`/`bash` over `.d.ts` files, which was appropriate.
|
|
99
|
+
|
|
100
|
+
### Changes made
|
|
101
|
+
|
|
102
|
+
1. `.pi/skills/package-pi-subagents/SKILL.md` — added a `## Testing` note: tests mounting Pi's per-entry interactive components must call `initTheme(undefined, false)` in `beforeAll`, with the component-specific / false-confidence caveat.
|
|
103
|
+
2. `.pi/skills/package-pi-subagents/SKILL.md` — corrected the stale UI domain module count (10 → 13) and extended its responsibility blurb to include session navigation.
|
|
104
|
+
3. `.pi/skills/testing/SKILL.md` — extended the existing "Exploration before planning" disposable-probe bullet to require exercising the full input variety (variant-specific environment dependencies defeat a one-representative probe).
|
|
105
|
+
Placement confirmed with the operator: kept in the `testing` skill (loaded by both `/plan-issue` and `/tdd-plan`) next to the sibling rule, rather than duplicated into the plan prompt.
|