pi-rnd 0.2.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/agents/rnd-builder.md +98 -0
  4. package/agents/rnd-integrator.md +104 -0
  5. package/agents/rnd-planner.md +208 -0
  6. package/agents/rnd-verifier.md +164 -0
  7. package/dist/doctor.js +166 -0
  8. package/dist/doctor.js.map +1 -0
  9. package/dist/gates/bash-discipline.js +27 -0
  10. package/dist/gates/bash-discipline.js.map +1 -0
  11. package/dist/gates/read-evidence-pack.js +23 -0
  12. package/dist/gates/read-evidence-pack.js.map +1 -0
  13. package/dist/gates/registry.js +24 -0
  14. package/dist/gates/registry.js.map +1 -0
  15. package/dist/gates/rnd-dir-required.js +31 -0
  16. package/dist/gates/rnd-dir-required.js.map +1 -0
  17. package/dist/index.js +20 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/orchestrator/prompts.js +58 -0
  20. package/dist/orchestrator/prompts.js.map +1 -0
  21. package/dist/orchestrator/rnd-dir.js +20 -0
  22. package/dist/orchestrator/rnd-dir.js.map +1 -0
  23. package/dist/orchestrator/spawn.js +67 -0
  24. package/dist/orchestrator/spawn.js.map +1 -0
  25. package/dist/orchestrator/start.js +195 -0
  26. package/dist/orchestrator/start.js.map +1 -0
  27. package/dist/orchestrator/state.js +15 -0
  28. package/dist/orchestrator/state.js.map +1 -0
  29. package/dist/orchestrator/types.js +2 -0
  30. package/dist/orchestrator/types.js.map +1 -0
  31. package/docs/PI-API.md +574 -0
  32. package/docs/PORTING.md +105 -0
  33. package/package.json +57 -0
  34. package/skills/fp-practices/SKILL.md +128 -0
  35. package/skills/fp-practices/bash.md +114 -0
  36. package/skills/fp-practices/duckdb.md +116 -0
  37. package/skills/fp-practices/elixir.md +115 -0
  38. package/skills/fp-practices/javascript.md +119 -0
  39. package/skills/fp-practices/koka.md +120 -0
  40. package/skills/fp-practices/lean.md +120 -0
  41. package/skills/fp-practices/postgresql.md +120 -0
  42. package/skills/fp-practices/python.md +120 -0
  43. package/skills/fp-practices/svelte.md +114 -0
  44. package/skills/kiss-practices/SKILL.md +41 -0
  45. package/skills/kiss-practices/bash.md +70 -0
  46. package/skills/kiss-practices/duckdb.md +30 -0
  47. package/skills/kiss-practices/elixir.md +38 -0
  48. package/skills/kiss-practices/javascript.md +43 -0
  49. package/skills/kiss-practices/koka.md +34 -0
  50. package/skills/kiss-practices/lean.md +45 -0
  51. package/skills/kiss-practices/markdown.md +20 -0
  52. package/skills/kiss-practices/postgresql.md +31 -0
  53. package/skills/kiss-practices/python.md +64 -0
  54. package/skills/kiss-practices/svelte.md +59 -0
  55. package/skills/rnd-building/SKILL.md +256 -0
  56. package/skills/rnd-decomposition/SKILL.md +188 -0
  57. package/skills/rnd-experiments/SKILL.md +197 -0
  58. package/skills/rnd-failure-modes/SKILL.md +222 -0
  59. package/skills/rnd-iteration/SKILL.md +170 -0
  60. package/skills/rnd-orchestration/SKILL.md +314 -0
  61. package/skills/rnd-scaling/SKILL.md +188 -0
  62. package/skills/rnd-verification/SKILL.md +248 -0
  63. package/skills/using-rnd-framework/SKILL.md +65 -0
package/docs/PI-API.md ADDED
@@ -0,0 +1,574 @@
1
+ # PI Extension API Reference
2
+
3
+ > Citation format: `(file:line-range)` — paths abbreviated as `pi-agent/` for
4
+ > `@earendil-works/pi-coding-agent/dist/core/` and `pi-sub/` for
5
+ > `@tintinweb/pi-subagents/src/`.
6
+
7
+ ---
8
+
9
+ ## 1. PI Runtime API (ExtensionAPI)
10
+
11
+ Extensions export a default factory function `(pi: ExtensionAPI) => void | Promise<void>`
12
+ (`pi-agent/extensions/types.d.ts:983`). PI awaits the factory before firing `session_start`.
13
+ Extensions load via **jiti** — TypeScript works without a compile step.
14
+
15
+ **Auto-discovery locations** (`pi-agent/` docs/extensions.md:7):
16
+
17
+ - `~/.pi/agent/extensions/*.ts` or `/*/index.ts` (global)
18
+ - `.pi/extensions/*.ts` or `/*/index.ts` (project-local)
19
+ - Packages listed in `settings.json#packages` or `settings.json#extensions`
20
+ - npm packages: `"pi": { "extensions": [...] }` in `package.json`
21
+
22
+ ### ExtensionAPI method surface
23
+
24
+ (`pi-agent/extensions/types.d.ts:770-923`)
25
+
26
+ | Method | Signature | Notes |
27
+ |--------|-----------|-------|
28
+ | `pi.on(event, handler)` | overloaded per event type | subscribe lifecycle events |
29
+ | `pi.registerTool(tool)` | `(ToolDefinition) => void` | LLM-callable tools |
30
+ | `pi.registerCommand(name, opts)` | `(string, Omit<RegisteredCommand, "name"\|"sourceInfo">) => void` | slash commands |
31
+ | `pi.registerShortcut(shortcut, opts)` | `(KeyId, {handler}) => void` | keyboard shortcuts |
32
+ | `pi.registerFlag(name, opts)` | `(string, {type, default?}) => void` | CLI flags |
33
+ | `pi.getFlag(name)` | `() => boolean\|string\|undefined` | read flag value |
34
+ | `pi.registerMessageRenderer(type, fn)` | custom chat message renderer | |
35
+ | `pi.registerProvider(name, config)` | add/override model provider | can be called after init |
36
+ | `pi.unregisterProvider(name)` | remove provider | |
37
+ | `pi.sendMessage(msg, opts?)` | send custom message (not user turn) | |
38
+ | `pi.sendUserMessage(content, opts?)` | always triggers LLM turn | |
39
+ | `pi.appendEntry(type, data?)` | persist state in session (not sent to LLM) | |
40
+ | `pi.setSessionName(name)` / `getSessionName()` | session display name | |
41
+ | `pi.setLabel(entryId, label)` | bookmark/navigation labels | |
42
+ | `pi.exec(cmd, args, opts?)` | shell execution | |
43
+ | `pi.getActiveTools()` / `setActiveTools(names)` / `getAllTools()` | tool list management | |
44
+ | `pi.getCommands()` | list registered slash commands | |
45
+ | `pi.setModel(model)` / `getThinkingLevel()` / `setThinkingLevel(level)` | model control | |
46
+ | `pi.events` | shared `EventBus` for cross-extension comms | |
47
+
48
+ ### EventBus shape
49
+
50
+ (`pi-agent/event-bus.d.ts:1-4`)
51
+
52
+ ```typescript
53
+ interface EventBus {
54
+ emit(channel: string, data: unknown): void;
55
+ on(channel: string, handler: (data: unknown) => void): () => void; // returns unsub fn
56
+ }
57
+ ```
58
+
59
+ ### pi.on() — all supported events
60
+
61
+ (`pi-agent/extensions/types.d.ts:771-798`)
62
+
63
+ | Event name | Handler return | When fired |
64
+ |------------|---------------|------------|
65
+ | `resources_discover` | `ResourcesDiscoverResult?` | after `session_start`, for extra skill/prompt/theme paths |
66
+ | `session_start` | void | startup / reload / new / resume / fork |
67
+ | `session_before_switch` | `{cancel?}` | before switching session |
68
+ | `session_before_fork` | `{cancel?, skipConversationRestore?}` | before forking |
69
+ | `session_before_compact` | `{cancel?, compaction?}` | before context compaction |
70
+ | `session_compact` | void | after compaction |
71
+ | `session_shutdown` | void | before teardown (quit/reload/replace) |
72
+ | `session_before_tree` | `{cancel?, summary?, ...}` | before tree navigation |
73
+ | `session_tree` | void | after tree navigation |
74
+ | `context` | `{messages?}` | before each LLM call; can mutate messages |
75
+ | `before_provider_request` | `unknown` | before HTTP request to provider |
76
+ | `after_provider_response` | void | after HTTP response received |
77
+ | `before_agent_start` | `{message?, systemPrompt?}` | after user submit, before agent loop |
78
+ | `agent_start` | void | agent loop begins |
79
+ | `agent_end` | void | agent loop ends |
80
+ | `turn_start` | void | each turn starts |
81
+ | `turn_end` | void | each turn ends |
82
+ | `message_start` / `message_update` / `message_end` | void | message streaming |
83
+ | `tool_execution_start` / `tool_execution_update` / `tool_execution_end` | void | tool running |
84
+ | `model_select` | void | model changed |
85
+ | `tool_call` | `{block?, reason?}` | before tool executes; mutate `event.input` to patch args |
86
+ | `tool_result` | `{content?, details?, isError?}` | after tool executes; can rewrite result |
87
+ | `user_bash` | `{operations?, result?}` | user's `!cmd` or `!!cmd` |
88
+ | `input` | `{action: "continue"\|"transform"\|"handled"}` | user input received |
89
+
90
+ ### ToolDefinition fields
91
+
92
+ (`pi-agent/extensions/types.d.ts:325-356`)
93
+
94
+ Required: `name`, `label`, `description`, `parameters` (TypeBox schema), `execute`.
95
+ Optional: `promptSnippet`, `promptGuidelines`, `executionMode` (`"sequential"`|`"parallel"`),
96
+ `renderCall`, `renderResult`, `renderShell`, `prepareArguments`.
97
+
98
+ ### ProviderConfig (pi.registerProvider)
99
+
100
+ (`pi-agent/extensions/types.d.ts:924-953`)
101
+
102
+ Fields: `baseUrl?`, `apiKey?`, `api?`, `models?`, `headers?`, `authHeader?`,
103
+ `streamSimple?`, `oauth?`. If `models` provided, replaces all models for that provider.
104
+ Provider registration is queued during extension init, applied once runner binds context;
105
+ safe to call at any time after that.
106
+
107
+ ---
108
+
109
+ ## 2. pi-subagents RPC Contract
110
+
111
+ RPC is implemented over `pi.events` using scoped channels.
112
+ (`pi-sub/cross-extension-rpc.ts:1-95`)
113
+
114
+ ### Protocol
115
+
116
+ - `PROTOCOL_VERSION = 2` (`pi-sub/cross-extension-rpc.ts:24`)
117
+ - Request: emit on `subagents:rpc:<method>` with `{ requestId: string, ...params }`
118
+ - Reply: listen on `subagents:rpc:<method>:reply:<requestId>`
119
+ - Reply envelope (`pi-sub/cross-extension-rpc.ts:19-21`):
120
+
121
+ ```typescript
122
+ type RpcReply<T = void> =
123
+ | { success: true; data?: T }
124
+ | { success: false; error: string };
125
+ ```
126
+
127
+ ### Methods
128
+
129
+ **ping** (`pi-sub/cross-extension-rpc.ts:76-78`):
130
+
131
+ ```
132
+ Request: { requestId: string }
133
+ Reply: { success: true, data: { version: 2 } }
134
+ ```
135
+
136
+ **spawn** (`pi-sub/cross-extension-rpc.ts:80-85`):
137
+
138
+ ```
139
+ Request: { requestId: string, type: string, prompt: string, options?: any }
140
+ Reply: { success: true, data: { id: string } }
141
+ Error: { success: false, error: "No active session" }
142
+ ```
143
+
144
+ Agent ID is `randomUUID().slice(0, 17)` (`pi-sub/agent-manager.ts:92`).
145
+ The `type` field is a SubagentType string (matches an agent definition name).
146
+ Spawn returns immediately; agent may still be queued (see section 3).
147
+
148
+ **stop** (`pi-sub/cross-extension-rpc.ts:88-91`):
149
+
150
+ ```
151
+ Request: { requestId: string, agentId: string }
152
+ Reply: { success: true }
153
+ Error: { success: false, error: "Agent not found" }
154
+ ```
155
+
156
+ ### Handler wiring
157
+
158
+ `registerRpcHandlers(deps)` is called once during extension init.
159
+ Returns `{ unsubPing, unsubSpawn, unsubStop }` for cleanup.
160
+ (`pi-sub/cross-extension-rpc.ts:73-95`)
161
+
162
+ ---
163
+
164
+ ## 3. pi-subagents Lifecycle Events
165
+
166
+ All events emitted on `pi.events`. Sources: `pi-sub/index.ts:364-411`.
167
+
168
+ | Event | Payload | When fired |
169
+ |-------|---------|------------|
170
+ | `subagents:ready` | `{}` | once on extension init |
171
+ | `subagents:settings_loaded` | `{ settings: SubagentsSettings }` | settings loaded at init |
172
+ | `subagents:settings_changed` | `{ settings: SubagentsSettings, persisted: boolean }` | settings mutated via /agents |
173
+ | `subagents:created` | `{ id, type, description, isBackground: true }` | background spawn accepted |
174
+ | `subagents:started` | `{ id, type, description }` | agent transitions queued→running |
175
+ | `subagents:completed` | see below | terminal success |
176
+ | `subagents:failed` | same shape as completed | terminal failure (error/stopped/aborted) |
177
+ | `subagents:steered` | `{ id, message }` | `steer_subagent` tool called |
178
+
179
+ **completed / failed payload** (`pi-sub/index.ts:338-362`):
180
+
181
+ ```typescript
182
+ {
183
+ id: string;
184
+ type: string;
185
+ description: string;
186
+ result: string | undefined;
187
+ error: string | undefined;
188
+ status: "completed" | "error" | "stopped" | "aborted" | "steered";
189
+ toolUses: number;
190
+ durationMs: number;
191
+ tokens: { input: number; output: number; total: number } | undefined;
192
+ }
193
+ ```
194
+
195
+ ### Timing: subagents:completed vs spawn-RPC reply
196
+
197
+ Background `subagents:rpc:spawn` reply arrives immediately, carrying only `{ id }`.
198
+ The agent may be queued (`status: "queued"`) at that point.
199
+ `subagents:started` fires when it leaves the queue.
200
+ `subagents:completed` fires inside the AgentManager `onComplete` callback, after the agent
201
+ finishes. There is no ordering guarantee relative to the spawn reply — that reply arrived
202
+ before the agent even started. (`pi-sub/agent-manager.ts:62-100`, `pi-sub/index.ts:364-411`)
203
+
204
+ ### Transcript readability mid-flight
205
+
206
+ The output file is flushed to disk on every `turn_end` event during the agent's run.
207
+ (`pi-sub/output-file.ts:69-71`: `session.subscribe(event => { if (event.type === "turn_end") flush(); })`)
208
+ Transcripts are readable mid-flight; no need to wait for completion.
209
+
210
+ ---
211
+
212
+ ## 4. Custom Agent Frontmatter Schema
213
+
214
+ Custom agents defined as `.md` files in `.pi/agents/<name>.md` (project) or
215
+ `~/.pi/agent/agents/<name>.md` (global). Project overrides global on name collision.
216
+ (`pi-sub/custom-agents.ts:20-28`)
217
+
218
+ Parsed via `parseFrontmatter()` from `@earendil-works/pi-coding-agent`.
219
+ Body (after frontmatter block) becomes `systemPrompt`. (`pi-sub/custom-agents.ts:51-52`)
220
+
221
+ ### All supported frontmatter fields
222
+
223
+ (`pi-sub/custom-agents.ts:53-73`)
224
+
225
+ | Field | Type | Default | Notes |
226
+ |-------|------|---------|-------|
227
+ | `display_name` | string | — | human-readable name |
228
+ | `description` | string | filename stem | shown in UI / system prompt |
229
+ | `tools` | CSV string | BUILTIN_TOOL_NAMES | `"none"` → empty list; omit → all builtins |
230
+ | `disallowed_tools` | CSV string | — | tools blocked for this agent |
231
+ | `extensions` / `inherit_extensions` | bool or CSV | `true` (inherit all) | `false`/`"none"` → none; CSV → named list |
232
+ | `skills` / `inherit_skills` | bool or CSV | `true` (inherit all) | same semantics |
233
+ | `model` | string | — | model ID override |
234
+ | `thinking` | ThinkingLevel string | — | thinking level override |
235
+ | `max_turns` | integer ≥ 0 | — | 0 = unlimited |
236
+ | `prompt_mode` | `"replace"` \| `"append"` | `"replace"` | system prompt source |
237
+ | `inherit_context` | boolean | — | prepend parent conversation to prompt text |
238
+ | `run_in_background` | boolean | — | default run mode |
239
+ | `isolated` | boolean | — | deprecated alias for `isolation: worktree` |
240
+ | `memory` | `"user"` \| `"project"` \| `"local"` | — | memory scope |
241
+ | `isolation` | `"worktree"` | — | creates a temp git worktree |
242
+ | `enabled` | boolean | `true` | `false` disables the agent |
243
+
244
+ **`prompt_mode` × `inherit_context` interaction:**
245
+ `prompt_mode: "replace"` means the agent's body replaces the parent's system prompt
246
+ entirely. `inherit_context: true` is orthogonal: it prepends the parent's conversation
247
+ history to the prompt text. They affect different things — system prompt source vs
248
+ conversation context — and can be combined freely. (`pi-sub/custom-agents.ts:65-66`,
249
+ exploration cache `pi-subagents.md:54-57`)
250
+
251
+ ---
252
+
253
+ ## 5. Slash Command Registration
254
+
255
+ Extensions register slash commands at load time via:
256
+
257
+ ```typescript
258
+ pi.registerCommand(name, {
259
+ description?: string,
260
+ getArgumentCompletions?: (prefix: string) => AutocompleteItem[] | null | Promise<...>,
261
+ handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>,
262
+ });
263
+ ```
264
+
265
+ (`pi-agent/extensions/types.d.ts:802`, `RegisteredCommand` interface at line 755-761`)
266
+
267
+ `name` is the command name **without** the leading `/`. The runtime prepends `/` when
268
+ displaying and routing. (`pi-agent/docs/extensions.md` — example: `pi.registerCommand("mycommand", ...)`)
269
+
270
+ The handler receives `args: string` (everything after the command name) and a full
271
+ `ExtensionCommandContext` with `ctx.ui`, session navigation (`newSession`, `fork`,
272
+ `switchSession`), `ctx.waitForIdle()`, and `ctx.reload()`.
273
+ (`pi-agent/extensions/types.d.ts:238-273`)
274
+
275
+ Commands registered by `@tintinweb/pi-subagents`: `/agents` (`pi-sub/index.ts` — command
276
+ registered in the extension factory body).
277
+
278
+ ---
279
+
280
+ ## 6. Settings File Locations and Schema
281
+
282
+ ### pi (host) settings
283
+
284
+ File locations (`pi-agent/settings-manager.d.ts:103-108`):
285
+
286
+ - Global: `~/.pi/agent/settings.json` (default; `$PI_CODING_AGENT_DIR` overrides the
287
+ entire agent dir path)
288
+ - Project: `.pi/settings.json` (cwd)
289
+ - Precedence: project overrides global on a per-field basis
290
+
291
+ **Settings schema** (`pi-agent/settings-manager.d.ts:57-94`):
292
+
293
+ | Key | Type | Notes |
294
+ |-----|------|-------|
295
+ | `defaultProvider` | string | |
296
+ | `defaultModel` | string | |
297
+ | `defaultThinkingLevel` | `"off"\|"minimal"\|"low"\|"medium"\|"high"\|"xhigh"` | |
298
+ | `transport` | Transport | |
299
+ | `steeringMode` | `"all"\|"one-at-a-time"` | |
300
+ | `followUpMode` | `"all"\|"one-at-a-time"` | |
301
+ | `theme` | string | |
302
+ | `compaction` | `{enabled?, reserveTokens?, keepRecentTokens?}` | |
303
+ | `branchSummary` | `{reserveTokens?, skipPrompt?}` | |
304
+ | `retry` | `{enabled?, maxRetries?, baseDelayMs?, provider?}` | |
305
+ | `hideThinkingBlock` | boolean | |
306
+ | `shellPath` | string | |
307
+ | `lastChangelogVersion` | string | last seen changelog version |
308
+ | `quietStartup` | boolean | suppress startup output |
309
+ | `shellCommandPrefix` | string | prefix prepended to shell commands |
310
+ | `npmCommand` | `string[]` | override npm executable and args |
311
+ | `collapseChangelog` | boolean | collapse changelog UI on startup |
312
+ | `enableInstallTelemetry` | boolean | opt-in install telemetry |
313
+ | `doubleEscapeAction` | `"fork"\|"tree"\|"none"` | action triggered by double-Escape |
314
+ | `treeFilterMode` | `"default"\|"no-tools"\|"user-only"\|"labeled-only"\|"all"` | session tree display filter |
315
+ | `editorPaddingX` | number | horizontal editor padding in cells |
316
+ | `autocompleteMaxVisible` | number | max autocomplete suggestions shown |
317
+ | `showHardwareCursor` | boolean | show hardware cursor in TUI |
318
+ | `packages` | `PackageSource[]` | npm/git packages to load extensions/skills from |
319
+ | `extensions` | `string[]` | extra extension paths |
320
+ | `skills` | `string[]` | extra skill paths |
321
+ | `prompts` | `string[]` | extra prompt template paths |
322
+ | `themes` | `string[]` | extra theme paths |
323
+ | `enableSkillCommands` | boolean | |
324
+ | `sessionDir` | string | override for sessions directory |
325
+ | `enabledModels` | `string[]` | |
326
+ | `terminal` | `{showImages?, imageWidthCells?, clearOnShrink?, showTerminalProgress?}` | |
327
+ | `images` | `{autoResize?, blockImages?}` | |
328
+ | `thinkingBudgets` | `{minimal?, low?, medium?, high?}` | |
329
+ | `markdown` | `{codeBlockIndent?}` | |
330
+ | `warnings` | `{anthropicExtraUsage?}` | |
331
+
332
+ ### pi-subagents settings
333
+
334
+ (`pi-sub/settings.ts:1-20`, `pi-sub/settings.ts:74-80`)
335
+
336
+ - Global: `~/.pi/agent/subagents.json` — manual defaults, never written by the extension
337
+ - Project: `.pi/subagents.json` — written by `/agents → Settings`
338
+ - Precedence: project overrides global (`pi-sub/settings.ts:99-100`)
339
+
340
+ **SubagentsSettings schema:**
341
+
342
+ ```typescript
343
+ interface SubagentsSettings {
344
+ maxConcurrent?: number; // default 4; 1–1024
345
+ defaultMaxTurns?: number; // 0 = unlimited; 0–10000
346
+ graceTurns?: number; // 1–1000
347
+ defaultJoinMode?: "async" | "group" | "smart";
348
+ }
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 7. Skill Discovery
354
+
355
+ Skills are `.md` files injected into the system prompt as XML-formatted context blocks.
356
+
357
+ ### Discovery order
358
+
359
+ (`pi-agent/skills.d.ts:46-58`, exploration cache `settings-and-skills.md:28-43`)
360
+
361
+ 1. `~/.pi/agent/skills/` — root `.md` files discovered individually; SKILL.md dirs recursed
362
+ 2. `~/.agents/skills/` — SKILL.md dirs only (root `.md` files NOT auto-discovered here)
363
+ 3. `.pi/skills/` — root `.md` files + SKILL.md dirs
364
+ 4. `.agents/skills/` in cwd and ancestor dirs up to git root — SKILL.md dirs only
365
+ 5. npm packages: `"pi": { "skills": [...] }` in `package.json`, or `skills/` dir in package
366
+ 6. `settings.json#skills` array — explicit paths
367
+
368
+ ### Package manifest
369
+
370
+ For npm-published extensions, place in `package.json`:
371
+
372
+ ```json
373
+ {
374
+ "pi": {
375
+ "extensions": ["./dist/index.js"],
376
+ "skills": ["./skills"]
377
+ }
378
+ }
379
+ ```
380
+
381
+ (`pi-agent/docs/extensions.md` — extension auto-discovery via `pi.extensions` in package.json)
382
+
383
+ ### Skill file rules
384
+
385
+ (`pi-agent/skills.d.ts:26-33`)
386
+
387
+ - Directory containing `SKILL.md` → treated as one skill root; recursion stops there
388
+ - Otherwise: direct `.md` children of a skills dir are each a skill
389
+ - Sub-directories without `SKILL.md` are recursed to find `SKILL.md` files
390
+
391
+ ### Skill frontmatter
392
+
393
+ (`pi-agent/skills.d.ts:3-7`)
394
+
395
+ ```typescript
396
+ interface SkillFrontmatter {
397
+ name?: string;
398
+ description?: string;
399
+ "disable-model-invocation"?: boolean;
400
+ [key: string]: unknown; // open record — arbitrary additional keys permitted (skills.d.ts:7)
401
+ }
402
+ ```
403
+
404
+ `disable-model-invocation: true` excludes the skill from the system prompt; it can still
405
+ be invoked explicitly via `/skill:<name>`.
406
+
407
+ ---
408
+
409
+ ## 8. Session / Output File Locations
410
+
411
+ ### PI session files
412
+
413
+ Sessions stored in `~/.pi/agent/sessions/` by default.
414
+ Override via `settings.json#sessionDir`. (`pi-agent/settings-manager.d.ts:93`)
415
+
416
+ ### pi-subagents output files
417
+
418
+ Path template (`pi-sub/output-file.ts:15-23`):
419
+
420
+ ```
421
+ /tmp/pi-subagents-<uid>/<cwd-encoded>/<sessionId>/tasks/<agentId>.output
422
+ ```
423
+
424
+ Where `<cwd-encoded>` = `cwd.replace(/\//g, "-").replace(/^-/, "")`.
425
+ Directory permissions: `0700`. (`pi-sub/output-file.ts:17-19`)
426
+
427
+ **Format:** JSONL, one entry per message. Each entry:
428
+
429
+ ```typescript
430
+ {
431
+ isSidechain: true,
432
+ agentId: string,
433
+ type: "user" | "assistant" | "toolResult",
434
+ message: AgentMessage,
435
+ timestamp: string, // ISO 8601
436
+ cwd: string,
437
+ }
438
+ ```
439
+
440
+ (`pi-sub/output-file.ts:26-36`, `pi-sub/output-file.ts:52-64`)
441
+
442
+ Initial user prompt written by `writeInitialEntry()` at spawn time.
443
+ Subsequent messages appended by `streamToOutputFile()` on each `turn_end`.
444
+ Final flush performed on agent cleanup. (`pi-sub/output-file.ts:42-77`)
445
+
446
+ File path available in `AgentRecord.outputFile` for cross-extension access.
447
+
448
+ ---
449
+
450
+ ## 9. Resolved Open Questions
451
+
452
+ ### Q1: Slash command registration mechanism
453
+
454
+ Slash commands are registered via `pi.registerCommand(name, opts)` where `name` has no
455
+ leading `/`. (`pi-agent/extensions/types.d.ts:802`) The runtime routes `/name` user
456
+ input to the handler. This is confirmed by `@tintinweb/pi-subagents` registering `/agents`
457
+ this way in its extension factory (`pi-sub/index.ts` — `pi.registerCommand("agents", ...)`
458
+ pattern visible from imports and factory body). Commands are TypeScript — there are no
459
+ `.md`-based slash command definitions. The `/` prefix is entirely runtime-managed; the
460
+ extension only ever sees and registers the bare name.
461
+
462
+ ### Q2: subagents:completed timing relative to spawn-RPC reply
463
+
464
+ The spawn RPC reply (`{ success: true, data: { id } }`) arrives immediately after
465
+ `manager.spawn()` returns, before the agent executes any work. (`pi-sub/cross-extension-rpc.ts:81-85`)
466
+ The `spawn()` method assigns an ID, sets status to `"queued"` for background agents, and
467
+ returns synchronously. (`pi-sub/agent-manager.ts:92-98`) `subagents:completed` fires inside
468
+ the `onComplete` callback passed to `AgentManager`, which is called only after the agent
469
+ finishes all turns and cleans up. (`pi-sub/index.ts:364-373`) These two events are separated
470
+ by the full agent execution time. Importantly, the `onComplete` callback fires after the
471
+ agent promise resolves, not before — so a post-hoc audit listener on `subagents:completed`
472
+ can inspect the output file, but cannot veto or block the result from reaching the parent;
473
+ by the time `completed` fires, the agent has already returned its result string.
474
+
475
+ ### Q3: Transcript mid-flight readability
476
+
477
+ Transcripts are readable mid-flight. The output file is opened at spawn time
478
+ (`writeInitialEntry` at `pi-sub/output-file.ts:26-36`) and flushed to disk on every
479
+ `turn_end` during the agent's run (`pi-sub/output-file.ts:69-71`). Because flush is
480
+ synchronous (`appendFileSync`), each turn's messages are durably written before the next
481
+ turn begins. A reader monitoring the file path (`AgentRecord.outputFile`) sees new entries
482
+ accumulate turn-by-turn during execution, not just at completion.
483
+
484
+ ### Q4: prompt_mode: replace × inherit_context: true interaction
485
+
486
+ `prompt_mode` and `inherit_context` are orthogonal fields affecting different aspects of
487
+ agent context. (`pi-sub/custom-agents.ts:65-66`)
488
+
489
+ `prompt_mode: "replace"` (the default) means the agent's `.md` body is used as the full
490
+ system prompt, replacing whatever the parent agent's system prompt was. `prompt_mode: "append"`
491
+ appends the body to the parent system prompt instead.
492
+
493
+ `inherit_context: true` means the parent's conversation history (the messages array, not
494
+ the system prompt) is prepended to the agent's context when it starts. It is independent of
495
+ which system prompt source is used.
496
+
497
+ Combined effect of `prompt_mode: "replace"` + `inherit_context: true`: the agent gets the
498
+ parent's conversation history as context but runs under its own system prompt (the `.md`
499
+ body), not the parent's. This is the planner pattern: planner reads what's been discussed
500
+ but operates under its own instructions.
501
+
502
+ ### Q5: Full pi extension runtime API surface
503
+
504
+ The surface is `ExtensionAPI` defined in `pi-agent/extensions/types.d.ts:770-923`. Complete
505
+ method list documented in Section 1. Key observations that differ from prior assumptions:
506
+
507
+ - Tool interception is via `tool_call` / `tool_result` events with mutable `event.input`
508
+ and result override — not named PreToolUse/PostToolUse hooks.
509
+ - No file-specific event hooks (no Read-event, Write-event, etc.) — only a generic
510
+ `tool_call` event that carries `toolName` and `input`, requiring the handler to check
511
+ `toolName` itself.
512
+ - `pi.events` is the shared `EventBus` instance for cross-extension communication, exposed
513
+ directly on the `ExtensionAPI` object (`pi-agent/extensions/types.d.ts:922`).
514
+ - Provider registration (`registerProvider`/`unregisterProvider`) supports OAuth, custom
515
+ stream handlers, and model-level API overrides — more complete than expected.
516
+ - Session navigation (fork, branch tree, compaction control) is available in
517
+ `ExtensionCommandContext` passed to command handlers.
518
+ - Extensions can set custom TUI components: editor, footer, header, widgets, working
519
+ indicator. (`pi-agent/extensions/types.d.ts:94-188`)
520
+
521
+ ---
522
+
523
+ ## 10. API Gaps
524
+
525
+ ### What the brainstorm assumed PI has — that it does not
526
+
527
+ **PreToolUse hooks (absent).**
528
+ PI has no `PreToolUse` / `PostToolUse` hook system as found in Claude Code's
529
+ `~/.claude/settings.json#hooks`. The equivalent is registering a `tool_call` or
530
+ `tool_result` handler via `pi.on("tool_call", handler)`, which receives a mutable
531
+ `event.input` and can return `{ block: true, reason: "..." }` to halt execution.
532
+ (`pi-agent/extensions/types.d.ts:795, 705-709`) The mechanism exists but under different
533
+ names and a different programming model (in-process TypeScript callbacks, not external
534
+ shell processes).
535
+
536
+ **File-event hooks (absent).**
537
+ There are no per-file-operation hooks for Read, Write, Glob, or Grep. The `tool_call`
538
+ event is generic — it fires for all tools including `read`, `write`, `grep`, `find`, `ls`.
539
+ Type-narrowing via `isToolCallEventType("read", event)` is provided
540
+ (`pi-agent/extensions/types.d.ts:688-698`), but this is a `tool_call` handler with a type
541
+ check, not a dedicated file-event hook. No separate hook channels exist for these operations.
542
+
543
+ **`.pi-lens/` is a debug log directory, not a manifest directory.**
544
+ The path `~/.pi-lens/` (in user home) is written by `pi-lens/index.ts` as a debug log:
545
+ `const DEBUG_LOG_DIR = path.join(os.homedir(), ".pi-lens")` and
546
+ `const DEBUG_LOG = path.join(DEBUG_LOG_DIR, "sessionstart.log")`.
547
+ (`pi-lens/index.ts:47-48`) It contains `sessionstart.log` only. It is not a manifest
548
+ directory, not scanned for extensions or skills, and has no role in PI's extension
549
+ discovery system. The pi-lens package manifest is in its `package.json` under `"pi"`.
550
+
551
+ ### What PI has that the brainstorm missed or underspecified
552
+
553
+ - **TUI customization API** — `ctx.ui.setEditorComponent()`, `setFooter()`, `setHeader()`,
554
+ `setWidget()`, `setWorkingIndicator()`, `custom()` for arbitrary keyboard-focused
555
+ components. (`pi-agent/extensions/types.d.ts:96-188`) This is richer than expected.
556
+ - **OAuth provider support** — `pi.registerProvider()` supports a full OAuth login/refresh
557
+ flow for enterprise SSO providers. (`pi-agent/extensions/types.d.ts:940-952`)
558
+ - **Session tree navigation from commands** — `ctx.fork()`, `ctx.navigateTree()`,
559
+ `ctx.newSession()`, `ctx.switchSession()` in `ExtensionCommandContext`.
560
+ (`pi-agent/extensions/types.d.ts:242-272`)
561
+ - **Context compaction control** — `session_before_compact` event with cancel/custom result;
562
+ `ctx.compact()` callable from any event handler. (`pi-agent/extensions/types.d.ts:775, 229-231`)
563
+ - **`resources_discover` event** — extensions can dynamically add skill/prompt/theme paths
564
+ per session. (`pi-agent/extensions/types.d.ts:771, 366-377`)
565
+ - **`before_agent_start` system prompt injection** — returning `{ systemPrompt }` from this
566
+ handler replaces the system prompt for that turn; multiple extensions chain.
567
+ (`pi-agent/extensions/types.d.ts:783, 720-726`)
568
+ - **Agent manager global registry** — `pi-subagents` exposes the manager via
569
+ `Symbol.for("pi-subagents:manager")` on `globalThis` for cross-package access without
570
+ going through RPC. (`pi-sub/index.ts:413-422`)
571
+ - **Batch notification / group join** — background agent completions are debounced and
572
+ grouped by `GroupJoinManager` before emitting UI notifications, with a 200 ms hold window.
573
+ (`pi-sub/index.ts:262-335`) This affects timing of `subagent-notification` custom messages
574
+ but not `subagents:completed` event timing.
@@ -0,0 +1,105 @@
1
+ # Porting Claude Code Skills and Agents to PI
2
+
3
+ This document maps Claude Code rnd-framework conventions to PI equivalents. Use it when porting skill `.md` files or agent `.md` files from a Claude Code plugin to PI, and when reviewing ported output to confirm no Claude-specific terms remain.
4
+
5
+ ---
6
+
7
+ ## Frontmatter Translation
8
+
9
+ The table below covers every frontmatter key that appears in Claude Code skill and agent files. Keys marked **drop** have no PI equivalent and must be removed entirely.
10
+
11
+ | Claude Code | PI | Notes |
12
+ |---|---|---|
13
+ | `name: rnd-planner` | drop | PI uses the filename stem as the agent/skill name |
14
+ | `description: ...` | `description: ...` | Identical; preserve verbatim |
15
+ | `tools: Read, Grep` | `tools: Read, Grep` | CSV string; unchanged |
16
+ | `disallowedTools: X,Y` | `disallowed_tools: X,Y` | Snake_case rename; same CSV value |
17
+ | `effort: low\|medium\|high` | `thinking: low\|medium\|high\|xhigh\|minimal` | Rename; `effort: low` → `thinking: minimal` is the closest match; see §4 for level semantics |
18
+ | `maxTurns: N` | `max_turns: N` | Snake_case rename; same integer value |
19
+ | `subagent_type: foo` | `type` | PI uses the filename stem as the implicit agent type. The spawn-time argument `type: 'rnd-builder'` (passed to `pi.events.emit('subagents:rpc:spawn', ...)`) matches the agent file's basename. |
20
+ | `color: "#3B82F6"` | drop | Not in PI custom-agent schema (PI-API.md §4) |
21
+ | `model: opus` | `model: claude-opus-4-7` | Use full PI-supported model ID |
22
+ | `prompt_mode: replace\|append` | `prompt_mode: replace\|append` | Identical; PI default is `replace` |
23
+ | `inherit_context: bool` | `inherit_context: bool` | Identical |
24
+ | `isolation: worktree` | `isolation: worktree` | Identical; PI-API.md §4 |
25
+ | `memory: user\|project\|local` | `memory: user\|project\|local` | Identical |
26
+ | `skills: [a,b,c]` | drop; use `inherit_skills: true` | PI has no named-skill list in agent frontmatter; rely on auto-injection via skill discovery (PI-API.md §7) |
27
+ | any unlisted key | drop | PI's agent parser ignores unknown keys, but remove to keep frontmatter canonical |
28
+
29
+ ---
30
+
31
+ ## Vocabulary Substitution
32
+
33
+ These are Claude Code constructs that appear in skill and agent body text. Replace each with the PI equivalent when porting.
34
+
35
+ | Claude Code | PI equivalent | Notes |
36
+ |---|---|---|
37
+ | `Agent tool` | `subagents:rpc:spawn` event | The Claude Code `Agent` built-in tool has no PI counterpart; spawning is event-driven via `pi.events.emit('subagents:rpc:spawn', ...)` |
38
+ | `Agent({subagent_type, prompt})` | `pi.events.emit("subagents:rpc:spawn", {requestId, type, prompt, options})` | PI-API.md §2; reply on `subagents:rpc:spawn:reply:<requestId>` |
39
+ | `Skill tool` | auto-injected via skill discovery | Skills matching discovery paths are injected into the system prompt; explicit invocation via `/skill:<name>` when `disable-model-invocation: true` (PI-API.md §7) |
40
+ | `SendMessage` | not directly supported | Agent final result string is the primary inter-agent output; `subagents:completed` event payload `{id, result, ...}` carries it |
41
+ | `TaskCreate` / `TaskUpdate` / `TaskList` | `pi.appendEntry("rnd:phase"\|"rnd:wave"\|"rnd:verdict", data)` | PI-API.md §1; persists runtime state outside the LLM context window |
42
+ | `AskUserQuestion` | `ctx.ui.notify(text, level)` | No multi-option UI equivalent; the user's next turn carries their reply |
43
+ | `${CLAUDE_PLUGIN_ROOT}` | strip; use paths relative to extension root or resolve via `process.cwd()` | PI-API.md §1 |
44
+ | `CLAUDE_SESSION_ID` | strip | PI session ID is internal and not exposed to extension code |
45
+ | `~/.claude/agents/` | `.pi/agents/` (project) or `~/.pi/agent/agents/` (global) | See Install Locations below |
46
+ | `~/.claude/skills/` | `.pi/skills/` (project) or `~/.pi/agent/skills/` (global) | PI-API.md §7 |
47
+ | `${CLAUDE_PROJECT_DIR}` | `process.cwd()` | PI sets cwd to the project root before loading extensions |
48
+
49
+ ---
50
+
51
+ ## Install Locations
52
+
53
+ ### Agents
54
+
55
+ Place ported `.md` files at `.pi/agents/<name>.md` (project-local, **primary**). This path is loaded on every agent invocation — no PI restart required.
56
+
57
+ Global fallback: `~/.pi/agent/agents/<name>.md`. Project-local overrides global on name collision.
58
+
59
+ The npm package `agents/` directory is **not** auto-discovered. PI's `loadCustomAgents` function hard-codes only the two paths above (`pi-sub/custom-agents.ts:20-28`). Users must manually copy files into one of the two paths; a `package.json` key analogous to `"pi": { "agents": [...] }` does not exist.
60
+
61
+ ### Skills
62
+
63
+ PI auto-discovers skills from, in priority order (PI-API.md §7):
64
+
65
+ 1. `~/.pi/agent/skills/` — global
66
+ 2. `.pi/skills/` — project-local
67
+ 3. npm packages declaring `"pi": { "skills": ["./skills"] }` in `package.json`
68
+
69
+ For npm-install distribution, add the following to `package.json`:
70
+
71
+ ```json
72
+ {
73
+ "pi": {
74
+ "skills": ["./skills"]
75
+ }
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Gate / Hook Translation
82
+
83
+ **Claude Code** uses external shell scripts listed in `hooks/hooks.json`. Each hook matches a lifecycle event (e.g., `PreToolUse`) with optional tool matchers (`Bash`, `Read`, etc.) and runs as a separate process.
84
+
85
+ **PI** uses a single in-process subscription:
86
+
87
+ ```typescript
88
+ pi.on("tool_call", (event) => {
89
+ // return { block: true, reason: "..." } to veto; return void/null to allow
90
+ })
91
+ ```
92
+
93
+ The handler receives the tool name and full input object. Returning `{ block: true, reason }` halts execution before the tool runs (PI-API.md §10; confirmed by static analysis of `ToolCallEventResult` at `dist/core/extensions/types.d.ts:705-709`). Multiple gates compose by funneling all logic through a single `tool_call` subscriber — not via separate hook registrations.
94
+
95
+ ---
96
+
97
+ ## What Does Not Port
98
+
99
+ These Claude Code constructs have no PI equivalent and must be removed entirely when porting:
100
+
101
+ - `${CLAUDE_PLUGIN_ROOT}` — no equivalent path variable in PI
102
+ - Multi-option `AskUserQuestion` UI — PI exposes only `ctx.ui.notify`; no structured choice dialogs
103
+ - `SendMessage` — there is no explicit agent-to-orchestrator messaging channel; communication flows through the `subagents:completed` result payload
104
+ - Dedicated `Agent` tool — PI spawning is event-driven via `subagents:rpc:spawn`, not a built-in LLM tool
105
+ - Separate hook event types (`PreToolUse`, `PostToolUse`, etc.) — PI has one `tool_call` event covering pre-execution only