pi-session-mcp 0.7.0 → 0.8.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.
Files changed (66) hide show
  1. package/.pi-session-mcp-cli/config.js +47 -7
  2. package/.pi-session-mcp-cli/doctor.js +68 -2
  3. package/.pi-session-mcp-cli/execution-profile.js +1 -0
  4. package/.pi-session-mcp-cli/extension-spec.js +7 -2
  5. package/.pi-session-mcp-cli/setup-result.js +8 -0
  6. package/.pi-session-mcp-cli/skill-catalog.js +35 -0
  7. package/.pi-session-mcp-cli/skill-customize.js +183 -0
  8. package/.pi-session-mcp-cli/skill-library.js +120 -0
  9. package/CHANGELOG.md +110 -0
  10. package/README.md +15 -3
  11. package/dist/capabilities.d.ts +1 -0
  12. package/dist/capabilities.js +2 -1
  13. package/dist/client-adapters/mcp-verifier.js +6 -2
  14. package/dist/config.d.ts +4 -0
  15. package/dist/config.js +47 -7
  16. package/dist/doctor.d.ts +4 -1
  17. package/dist/doctor.js +68 -2
  18. package/dist/execution-profile.d.ts +4 -0
  19. package/dist/execution-profile.js +1 -0
  20. package/dist/extension-spec.js +7 -2
  21. package/dist/pi-adapter.d.ts +1 -1
  22. package/dist/pi-adapter.js +1 -0
  23. package/dist/pi-skills.d.ts +17 -0
  24. package/dist/pi-skills.js +64 -0
  25. package/dist/sdk-pi-adapter.d.ts +7 -3
  26. package/dist/sdk-pi-adapter.js +57 -11
  27. package/dist/server.js +1 -0
  28. package/dist/session-tools.d.ts +1 -1
  29. package/dist/session-tools.js +20 -4
  30. package/dist/setup-cli.d.ts +2 -0
  31. package/dist/setup-cli.js +16 -0
  32. package/dist/setup-main.d.ts +1 -1
  33. package/dist/setup-main.js +18 -3
  34. package/dist/setup-ownership.d.ts +6 -0
  35. package/dist/setup-ownership.js +29 -5
  36. package/dist/setup-skills.d.ts +37 -0
  37. package/dist/setup-skills.js +353 -0
  38. package/dist/setup.d.ts +7 -0
  39. package/dist/setup.js +49 -6
  40. package/dist/skill-catalog.d.ts +26 -0
  41. package/dist/skill-catalog.js +35 -0
  42. package/dist/skill-customize.d.ts +23 -0
  43. package/dist/skill-customize.js +183 -0
  44. package/dist/skill-library.d.ts +61 -0
  45. package/dist/skill-library.js +120 -0
  46. package/docs/acceptance.md +372 -3
  47. package/docs/adr/0006-pi-extension-allowlist.md +5 -2
  48. package/docs/adr/0007-profile-skill-selection.md +217 -0
  49. package/docs/architecture.md +9 -5
  50. package/docs/client-setup-design.md +133 -1
  51. package/docs/client-setup.md +74 -3
  52. package/docs/doctor-design.md +9 -1
  53. package/docs/installation.md +129 -9
  54. package/docs/pi-extensions-design.md +41 -12
  55. package/docs/releasing.md +15 -6
  56. package/docs/session-mcp.md +7 -2
  57. package/docs/skills.md +487 -0
  58. package/docs/threat-model.md +21 -4
  59. package/docs/tool-contracts.md +9 -5
  60. package/package.json +3 -1
  61. package/pi-session-mcp.example.json +9 -3
  62. package/skills/pi-session-implementation/SKILL.md +42 -0
  63. package/skills/pi-session-orchestration/SKILL.md +93 -0
  64. package/skills/pi-session-review/SKILL.md +58 -0
  65. package/docs/superpowers/plans/2026-10-10-macos-setup.md +0 -1725
  66. package/docs/superpowers/plans/2026-10-11-pi-extensions.md +0 -2450
package/docs/skills.md ADDED
@@ -0,0 +1,487 @@
1
+ # Pi skills in execution profiles
2
+
3
+ Status: decided in [ADR 0007](adr/0007-profile-skill-selection.md), issue
4
+ [#46](https://github.com/plagor44-ux/pi-session-mcp/issues/46).
5
+
6
+ A Pi skill is a `SKILL.md` file with instructions that Pi lists to the model in
7
+ its system prompt. The model may read the file when a task matches the skill's
8
+ description. Pi Session MCP lets the operator register skill files in a catalog
9
+ and select them per execution profile. MCP callers never choose skills.
10
+
11
+ The package ships three skills and keeps them in a skill library together with
12
+ the operator's own overrides. `pi-session-mcp-setup --apply` also installs the
13
+ controller skill into Claude Code and Codex. Nothing needs to be linked by hand.
14
+ Sections: [Configuration](#configuration), [The skill library](#the-skill-library),
15
+ [Customize and add skills](#customize-and-add-skills) and [Install the
16
+ orchestration skill into Codex and Claude Code](#install-the-orchestration-skill-into-codex-and-claude-code).
17
+
18
+ ## Configuration
19
+
20
+ Two optional parts of `pi-session-mcp.json`:
21
+
22
+ ```json
23
+ {
24
+ "workspaces": {
25
+ "my-project": "/absolute/path/to/my-project"
26
+ },
27
+ "skillCatalog": {
28
+ "pi-session-implementation": "builtin:pi-session-implementation",
29
+ "pi-session-review": "builtin:pi-session-review",
30
+ "release-notes": "skills-local/release-notes/SKILL.md"
31
+ },
32
+ "executionProfiles": {
33
+ "safe-readonly": {
34
+ "default": true,
35
+ "permissionProfile": "read-only",
36
+ "provider": "your-configured-provider",
37
+ "model": "your-configured-model",
38
+ "thinkingLevel": "medium",
39
+ "skills": ["pi-session-review"]
40
+ },
41
+ "coding-worker": {
42
+ "default": false,
43
+ "permissionProfile": "coding",
44
+ "provider": "your-configured-provider",
45
+ "model": "your-configured-model",
46
+ "thinkingLevel": "high",
47
+ "skills": ["pi-session-implementation", "release-notes"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Catalog: `skillCatalog`
54
+
55
+ - An optional top-level object that maps a skill alias to the path of a
56
+ `SKILL.md` file. A missing catalog is an empty catalog.
57
+ - At most 64 entries. An alias matches `^[a-z][a-z0-9_-]{0,63}$`, like every
58
+ other alias.
59
+ - Each value is either `builtin:<name>` or names exactly one regular, readable
60
+ file whose last path segment is `SKILL.md`. `builtin:<name>` refers to a
61
+ skill of the [skill library](#the-skill-library); `<name>` must follow the
62
+ skill name rule below, and `builtin:` followed by anything else is rejected.
63
+ Directories, globs, URLs and every other scheme-prefixed source (`npm:`,
64
+ `git+…`, `https:`) are rejected. So are `~` and backslashes. A value has at
65
+ most 4096 characters.
66
+ - A relative path resolves against the directory of the configuration file,
67
+ like a workspace. It never resolves against a workspace or the shell's
68
+ working directory. `~` is not expanded: write an absolute path, or use
69
+ `builtin:<name>` for a library skill.
70
+ - A skill folder needs no `package.json` and no extension. The file can live
71
+ anywhere the server's OS user can read. Keep catalog files outside every
72
+ configured workspace: a `coding` session can change any file in its
73
+ workspace, and a changed skill reaches other sessions too (see
74
+ [snapshot semantics](#snapshot-semantics)).
75
+ - The catalog alias is only the public handle. It may differ from the `name` in
76
+ the skill's own frontmatter. Nothing is renamed.
77
+
78
+ ### Selection: `executionProfiles.<alias>.skills`
79
+
80
+ - An optional list of catalog aliases, at most 16, no duplicates.
81
+ - Missing or empty means no skills for that profile. There is no inheritance
82
+ between profiles and no default skill.
83
+ - All other profile fields and the rule that unknown fields are rejected are
84
+ unchanged.
85
+
86
+ ### Invalid configuration
87
+
88
+ These make the whole configuration invalid. The server does not start, and
89
+ Doctor reports `config_invalid`. Stderr shows the stable startup code
90
+ `invalid_execution_profile_config`:
91
+
92
+ - a selected alias that is not in the catalog;
93
+ - a duplicate alias in one profile's list;
94
+ - two selected aliases of one profile whose paths resolve to the same path,
95
+ for example two aliases for `builtin:pi-session-review`;
96
+ - a catalog value that violates the rules above (including a malformed
97
+ `builtin:` name), or more than 64 entries;
98
+ - more than 16 selected aliases.
99
+
100
+ ## The skill library
101
+
102
+ The skill library has two sources and nothing else:
103
+
104
+ 1. **Shipped skills.** The package's `skills/` directory.
105
+ 2. **Operator overrides.** A `skills/` directory next to the configuration
106
+ file, that is, in the directory of the path in `PI_SESSION_MCP_CONFIG`. For
107
+ the default location `~/.config/pi-session-mcp/pi-session-mcp.json` it is
108
+ `~/.config/pi-session-mcp/skills/<name>/SKILL.md`.
109
+
110
+ An override with the same name replaces the shipped skill. A name that is not
111
+ shipped adds a skill. A name follows the Agent Skills rule: lowercase letters
112
+ and digits in hyphen-separated groups, 1 to 64 characters. No other directory
113
+ is searched: not the current directory, not a workspace and not a client's
114
+ skill directory.
115
+
116
+ ### `builtin:<name>` in the catalog
117
+
118
+ A catalog value `builtin:<name>` resolves to `<override dir>/<name>/SKILL.md`
119
+ if that file exists, else to the shipped skill of that name. If neither
120
+ exists, the value keeps the shipped location. Nothing fails when the
121
+ configuration loads, but Doctor then reports `skill_missing` for the profile
122
+ that selects it, and a session start fails with `skill_load_failed`.
123
+
124
+ Resolution happens when the server loads the configuration. Adding or removing
125
+ an override therefore needs a server restart, like any configuration change.
126
+ Edits to a skill file that already resolves reach new sessions, as described
127
+ under [snapshot semantics](#snapshot-semantics). Deleting a resolved override
128
+ leaves the catalog pointing at a missing file until the restart.
129
+
130
+ The shipped example configuration registers the two worker skills with
131
+ `builtin:`. After you copy the example, its skill entries need no path edits.
132
+
133
+ ### Shipped skills
134
+
135
+ The package ships three skills under `skills/`. Each has one canonical copy and
136
+ frontmatter with only `name` and `description`.
137
+
138
+ | Skill | For | Use |
139
+ | --- | --- | --- |
140
+ | `pi-session-implementation` | A Pi worker | One bounded implementation task and a fixed final report. Register it as `builtin:pi-session-implementation` and select it on coding profiles. |
141
+ | `pi-session-review` | A Pi worker | A review of a fixed target with findings and evidence. Register it as `builtin:pi-session-review` and select it on read-only profiles. |
142
+ | `pi-session-orchestration` | The controlling Claude Code or Codex agent | How to run Pi worker sessions through this server. `setup --apply` installs it into the clients. It is not a Pi worker skill: do not select it in a profile. |
143
+
144
+ A package update can change the shipped skills. Close affected sessions before
145
+ you update, as for any skill change.
146
+
147
+ ## Customize and add skills
148
+
149
+ Do not edit the files inside the installed package: an update replaces them.
150
+ Put your version in the override directory.
151
+
152
+ ### Customize a shipped skill
153
+
154
+ ```bash
155
+ export PI_SESSION_MCP_CONFIG="$HOME/.config/pi-session-mcp/pi-session-mcp.json"
156
+ pi-session-mcp-setup --customize-skill pi-session-review # dry-run
157
+ pi-session-mcp-setup --apply --customize-skill pi-session-review
158
+ ```
159
+
160
+ The dry-run reports what would happen, for example:
161
+
162
+ ```text
163
+ dry-run: planned
164
+ skills/override/pi-session-review: planned (skill_customize_planned)
165
+ ```
166
+
167
+ `--apply` copies the shipped skill directory into the override directory as a
168
+ starting point and records the fingerprint of the shipped skill in
169
+ `<override dir>/.pi-session-mcp-bases.json`. Then edit
170
+ `<override dir>/pi-session-review/SKILL.md`, restart the server so that the new
171
+ override is picked up, and start new sessions.
172
+
173
+ - The command works without `--target`; it touches no client. It cannot be
174
+ combined with `--target`, `--verify`, `--rollback` or `--remove`, which are
175
+ usage errors (exit 64).
176
+ - It requires `PI_SESSION_MCP_CONFIG` (otherwise `config_required`) and the
177
+ same platform as the rest of `setup`: Linux or macOS, otherwise
178
+ `platform_unsupported`. It takes no ownership lock.
179
+ - It never overwrites an existing override and never edits your files.
180
+ - Only regular files are copied: at most 32 files, 1 MiB and 8 directory
181
+ levels, no links, and entries whose name starts with `.` are skipped.
182
+
183
+ The finding target is `{ client: "skills", scope: "override", alias: <name> }`:
184
+
185
+ | Code | Status | Meaning |
186
+ | --- | --- | --- |
187
+ | `skill_customize_planned` | planned | Dry-run: no override exists; `--apply` would copy the shipped skill. |
188
+ | `skill_customized` | ok | The override was created and its base recorded. |
189
+ | `skill_override_current` | unchanged | The override exists and its recorded base equals the current shipped skill. |
190
+ | `skill_override_outdated` | planned | The shipped skill changed since the copy, or no base is recorded for the existing override. |
191
+ | `skill_base_updated` | ok | `--apply` after `skill_override_outdated`: only the recorded base moved. |
192
+ | `skill_not_shipped` | failed | The name is not valid or the package ships no such skill. |
193
+ | `skill_bases_corrupt` | failed | The bases file is not valid. Repair or remove it. |
194
+ | `skill_override_invalid` | failed | The override entry is not a directory, or its tree is unusable (no `SKILL.md`, a link, too large). |
195
+ | `skill_customize_failed` | failed | Any other failure while copying or recording. |
196
+
197
+ ### Follow a changed shipped skill
198
+
199
+ After a package update, `skill_override_outdated` (from the command above, or
200
+ as a [Doctor](#doctor-boundary) warning) means that the shipped skill differs
201
+ from the one your override was copied from. Review the shipped text, merge what
202
+ you want into your override by hand, and then acknowledge it:
203
+
204
+ ```bash
205
+ pi-session-mcp-setup --apply --customize-skill pi-session-review
206
+ ```
207
+
208
+ That moves only the recorded base (`skill_base_updated`). It never touches your
209
+ files. The override keeps winning until you delete it; the base only decides
210
+ whether Doctor warns.
211
+
212
+ ### Add a new skill
213
+
214
+ No command is needed. Create `<override dir>/<new-name>/SKILL.md` (a valid
215
+ Agent Skills file with `name` and `description` frontmatter), reference it as
216
+ `builtin:<new-name>` in `skillCatalog`, select its alias in a profile, and
217
+ restart the server. Companion files belong in the same directory. A name that
218
+ equals a shipped skill replaces that skill instead of adding one.
219
+
220
+ Where `setup` is unsupported, create an override by copying a shipped skill
221
+ directory by hand. Doctor does not track a copy that has no recorded base.
222
+
223
+ ## Install the orchestration skill into Codex and Claude Code
224
+
225
+ The orchestration skill is for the controlling agent, not for a Pi worker.
226
+ `pi-session-mcp-setup --apply --target <target>` installs it after the
227
+ registration, as a real copy of the effective skill: your override if you have
228
+ one, else the shipped skill. Clients discover it without further steps.
229
+
230
+ | Target | Skill directory |
231
+ | --- | --- |
232
+ | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` |
233
+ | `claude-code:user:pi-session-mcp` and `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` (one shared directory) |
234
+ | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration`, where `<project>` is the directory in which `setup` runs |
235
+
236
+ Claude Code reads personal skills in `~/.claude/skills/<name>/SKILL.md` and
237
+ project skills in `.claude/skills/<name>/SKILL.md`
238
+ ([Claude Code skills](https://code.claude.com/docs/en/skills)). Codex reads user
239
+ skills in `~/.agents/skills`
240
+ ([Codex skills](https://learn.chatgpt.com/docs/build-skills)).
241
+
242
+ Every `setup` result has a second finding per target, with the same client and
243
+ scope and the alias `pi-session-orchestration`. A skill problem never blocks
244
+ the registration, but a `divergent` or `failed` skill finding makes the overall
245
+ status `failed` (exit 1), so it is not overlooked.
246
+
247
+ | Operation | Code (status) |
248
+ | --- | --- |
249
+ | `--dry-run` | `skill_absent` (absent), `skill_equivalent` (unchanged), `skill_linked` (unchanged), `skill_update_planned` (planned), `skill_divergent` (divergent), `skill_source_invalid` (failed) |
250
+ | `--apply` | `skill_installed` (ok), `skill_already_equivalent` (unchanged), `skill_linked` (unchanged), `skill_updated` (ok), `skill_divergent` (divergent), `skill_source_invalid` (failed), `skill_install_failed` (failed) |
251
+ | `--verify` | `skill_verified` (ok), `skill_missing` (failed), `skill_not_equivalent` (failed) |
252
+ | `--rollback`, `--remove` | `skill_removed` (ok), `skill_kept_shared` (unchanged), `skill_not_owned` (unchanged), `skill_absent` (ok), `skill_divergent` (divergent) |
253
+
254
+ - `--apply` installs the skill only when the registration finding is `ok` or
255
+ `unchanged`; `--rollback` and `--remove` handle it only after the
256
+ registration was removed.
257
+ - `skill_updated` means that setup replaced its own unmodified copy after a
258
+ package update or a changed override.
259
+ - `skill_kept_shared` means that the other Claude Code registration (`user` or
260
+ `local`) still uses the shared directory. The last one removes the skill.
261
+ - A copy that already equals the effective skill but was not created by `setup`
262
+ is reported as equivalent and stays unowned; a later `--remove` reports
263
+ `skill_not_owned`.
264
+ - `--verify` compares the installed copy with the effective skill and does not
265
+ look at ownership.
266
+
267
+ ### What setup never replaces or deletes
268
+
269
+ Setup reports `skill_divergent` and leaves the entry alone when it finds:
270
+
271
+ - an installed copy that was modified after setup wrote it;
272
+ - a skill of the same name that setup did not install and that differs from the
273
+ effective skill;
274
+ - any entry whose name starts with `.` inside an installed copy, such as your
275
+ own notes or a `.DS_Store` that macOS Finder adds: setup never writes such
276
+ entries, so it treats them as yours; delete them to let setup manage the copy
277
+ again;
278
+ - a link at the skill entry that points anywhere but the effective skill, or a
279
+ non-directory there.
280
+
281
+ A link at the skill entry that points exactly at the effective skill (your
282
+ override, else the shipped skill) is your own equivalent setup: setup reports
283
+ `skill_linked` (unchanged), `--verify` reports `skill_verified`, and removal
284
+ leaves the link alone (`skill_not_owned`). If you replace setup's own copy with
285
+ such a link, setup forgets its copy on the next `--apply` or `--remove`. A link
286
+ counts only while the effective skill itself is valid.
287
+
288
+ If an update is interrupted, for example by a full disk or a signal, setup still
289
+ recognizes the old copy as its own: the next `--apply` finishes the update and
290
+ `--remove` deletes it. A project copy belongs to its project: setup records an
291
+ opaque project identity, so a `--remove` in another project never deletes it.
292
+
293
+ Links in the path to the entry are handled by place. In your home directory,
294
+ the client's skill directory may be your own link, for example into a dotfiles
295
+ checkout, and is followed. Inside a project, which may be untrusted, any link
296
+ below the project root makes the entry divergent, so setup never writes outside
297
+ the project.
298
+
299
+ ### Customize the controller skill
300
+
301
+ Never edit the installed copy: it becomes divergent. Instead:
302
+
303
+ 1. `pi-session-mcp-setup --apply --customize-skill pi-session-orchestration`
304
+ 2. Edit `<override dir>/pi-session-orchestration/SKILL.md`.
305
+ 3. `pi-session-mcp-setup --apply --target <target>` again. The unmodified
306
+ installed copy is replaced (`skill_updated`).
307
+
308
+ To recover after editing the installed copy, move your edits into the override,
309
+ delete the installed copy, and run `--apply` again.
310
+
311
+ ### Scope of what setup installs
312
+
313
+ Setup installs only `pi-session-orchestration` into clients. The worker skills
314
+ are for Pi profiles and are used through `builtin:`. Controller skills of your
315
+ own belong in the clients' native skill directories; setup does not manage
316
+ them.
317
+
318
+ ### Install by hand where setup is unsupported
319
+
320
+ Where `setup` does not run, copy the directory; do not link it. Copy
321
+ `skills/pi-session-orchestration` from the installed package (in an npm
322
+ installation `"$(npm root -g)/pi-session-mcp/skills/pi-session-orchestration"`)
323
+ to `~/.claude/skills/` or `.claude/skills/` of a project for Claude Code, or to
324
+ `~/.agents/skills/` for Codex. Repeat the copy after a package update; nothing
325
+ keeps a manual copy current. Do not replace an existing skill of the same name
326
+ without checking it first.
327
+
328
+ Neither client needs a skill to call the Pi Session MCP tools; the skill only
329
+ describes the working method. Headless `codex exec` runs still need the
330
+ per-tool approvals for `pi_session_prompt`, `pi_session_abort` and
331
+ `pi_session_close` that [client
332
+ setup](client-setup.md#run-codex-without-prompts) describes.
333
+
334
+ ### Pi workers
335
+
336
+ Pi workers never discover skills on their own in Pi Session MCP. Register the
337
+ worker skills with `builtin:` and select them on profiles, as described above.
338
+ Do not select the orchestration skill for a Pi worker: a worker has only the
339
+ tools of its profile and is not a controller of other Pi sessions.
340
+
341
+ ## What happens at session start
342
+
343
+ The server loads exactly the selected files through the Pi SDK's public
344
+ `loadSkills`, without any default directories, and validates them completely.
345
+ The SDK's skill override returns exactly that set, also after an extension adds
346
+ resources later (`resources_discover`). A profile without skills gets no
347
+ skills, whatever is installed on the machine or shipped by an extension.
348
+
349
+ The start fails with `skill_load_failed` (message `Configured Pi skill could
350
+ not be loaded`) if:
351
+
352
+ - a selected file is missing, unreadable, or larger than 256 KiB (also a
353
+ `builtin:` value that resolved to no skill);
354
+ - the SDK reports the skill as invalid, for example a missing description or an
355
+ invalid name;
356
+ - two selected files declare the same Pi skill name;
357
+ - two aliases point at the same canonical file, for example through a symlink;
358
+ - the skill sets `disable-model-invocation: true`.
359
+
360
+ The message is fixed. It never contains a path, a skill name or SDK text.
361
+ Stderr gets a structured diagnostic. A selected file that fails validation
362
+ reports the stage `skill_load` and, when one alias is to blame, that catalog
363
+ alias in a `skill` field. If the session's actual skill catalog differs from
364
+ the selection after construction, the stage is `postcheck`, or
365
+ `extension_load` and `extension_bind` for a profile with extensions. Neither
366
+ carries a path.
367
+
368
+ If the session's prompt-template or theme catalog is not empty after
369
+ construction, which the overrides should prevent, the start fails with
370
+ `pi_session_creation_failed`.
371
+
372
+ Explicit-only skills (`disable-model-invocation: true`) are incompatible in
373
+ this version. Pi hides such skills from the model, and MCP prompts never run
374
+ `/skill:` commands, so the skill could never be used. The server does not
375
+ rewrite the flag. The same Pi skill name in different sessions is fine.
376
+
377
+ ## Doctor boundary
378
+
379
+ Doctor stays offline and never parses or executes a skill. For each profile and
380
+ selected alias it adds three checks after the extension checks, with the
381
+ subject `<profile>:<skill-alias>`. A `builtin:` alias is checked at the
382
+ location it resolved to:
383
+
384
+ | ID | Condition |
385
+ | --- | --- |
386
+ | `skill_missing` | The selected file does not exist. |
387
+ | `skill_unreadable` | The path is not a regular file, is over 256 KiB, or cannot be read. |
388
+ | `skill_duplicate` | Two selected aliases of one profile resolve to the same canonical file. |
389
+
390
+ Frontmatter, name rules, name collisions and the explicit-only flag are checked
391
+ only at session start by the SDK contract. A clean Doctor run therefore does
392
+ not promise that the skill loads.
393
+
394
+ A fourth check concerns overrides, not profiles:
395
+
396
+ | ID | Severity | Subject | Condition |
397
+ | --- | --- | --- | --- |
398
+ | `skill_override_outdated` | warning | `skill:<name>` | The shipped skill's current fingerprint differs from the base recorded in `<override dir>/.pi-session-mcp-bases.json`. |
399
+ | `skill_override_outdated` | warning | `skill:bases` | The bases file is corrupt. |
400
+
401
+ It is `ok` for a recorded base that still matches. The remediation for a
402
+ changed skill names the command
403
+ `pi-session-mcp-setup --apply --customize-skill <name>`, after you reviewed the
404
+ change (see [Follow a changed shipped skill](#follow-a-changed-shipped-skill)).
405
+ Only skills with a recorded base are compared. Doctor reads the bases file and
406
+ the shipped files and executes nothing. A warning gives exit status `1`; it does
407
+ not block `setup`.
408
+
409
+ ## Three states, and a fourth thing
410
+
411
+ Keep these apart:
412
+
413
+ 1. **Configured.** `pi_capabilities_get` lists the alias under the profile's
414
+ `skills`. This is configuration intent only. It says nothing about the file.
415
+ 2. **Available in the SDK catalog.** The skill passed validation at session
416
+ start and Pi lists it to the model in the system prompt.
417
+ 3. **Actually read by the model.** The model decides to read the `SKILL.md`
418
+ with its `read` tool. This is not guaranteed.
419
+
420
+ Task success is separate from all three. A skill that the model read can still
421
+ produce a bad result, and a task can succeed without the skill.
422
+
423
+ ## Snapshot semantics
424
+
425
+ The profile and its skill selection are fixed per session. The skill content
426
+ is not snapshotted: the SDK keeps file paths, and the model's later reads may
427
+ see changed files. This also applies to companion files that a skill refers
428
+ to.
429
+
430
+ - Manage skill files and companion files as operator content. Do not change
431
+ them while sessions that use them are active.
432
+ - Close the affected sessions before an update. New sessions then load the new
433
+ content.
434
+ - The configuration fingerprint covers the configuration bytes, not skill
435
+ contents.
436
+ - There is no hot reload, no watcher, no copy store for sessions and no
437
+ versioning. A change
438
+ to `skillCatalog` or `skills` needs a new server process like any other
439
+ configuration change. So does adding or removing an override of a library
440
+ skill, because `builtin:` is resolved when the configuration loads. Editing
441
+ the file of an override that already resolves needs no restart for new
442
+ sessions.
443
+
444
+ ## Trust and limits
445
+
446
+ Skills are operator-managed prompt content. The override directory and its
447
+ bases file are operator-trusted configuration next to the configuration file,
448
+ never a workspace; keep them out of every workspace and writable only by you.
449
+ Setup copies the effective orchestration skill into the clients' skill
450
+ directories, where it becomes instructions for the controlling agent, so review
451
+ an override like code. In a `coding` profile a skill's
452
+ instructions are executed with that profile's tools, so review a skill like
453
+ code. A skill grants no rights: the tools of the permission profile stay the
454
+ limit.
455
+
456
+ Skill selection limits the regular skill catalog only. An allowlisted
457
+ extension remains trusted executable code. It can still read files or inject
458
+ prompt text. This is not a sandbox.
459
+
460
+ No MCP tool and no tool argument takes a skill. `pi_session_start` and
461
+ `pi_session_prompt` are unchanged.
462
+
463
+ Pi lists each available skill to the model with the absolute path of its
464
+ `SKILL.md`, so that the model can read it. The model may repeat that path in
465
+ its answer. Capabilities, errors and diagnostics stay path-free, but completed
466
+ `assistantText` is model output and is not sanitized.
467
+
468
+ ## Migration: profiles that relied on package skills
469
+
470
+ With Pi SDK 1.1.0, an allowlisted extension package's bundled skills reached
471
+ the session although `noSkills` was set. The same held for prompt
472
+ templates and themes that a package shipped. Now an extension allowlist alone
473
+ makes none of its skills available. Prompt templates and themes that packages
474
+ ship are closed as well, through the SDK's public overrides. They were never
475
+ expanded for MCP prompts. Profiles without extensions are not affected.
476
+
477
+ If a profile used a skill that a package shipped:
478
+
479
+ 1. Find the package's `SKILL.md` inside Pi's install, normally
480
+ `<Pi agent dir>/npm/node_modules/<package>/…`.
481
+ 2. Add a catalog entry with the absolute path of that file. `~` is not
482
+ expanded.
483
+ 3. Select the alias in the profile's `skills`.
484
+ 4. Run Doctor and start a session to confirm.
485
+
486
+ Keep in mind that a package update can move or change the file. The catalog
487
+ path then needs a check.
@@ -26,6 +26,14 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
26
26
  `process.execPath`, `dist/`, and `PI_SESSION_MCP_CONFIG`; no arbitrary `cwd` is
27
27
  accepted. Durable pending/owned ownership state contains neither paths nor
28
28
  secrets.
29
+ - **Skill installation into clients:** `setup --apply` writes the controller skill `pi-session-orchestration` into the clients' skill directories, in the home directory (`~/.agents/skills`, `~/.claude/skills`) and, for Claude Code `project`, in the project that setup runs in (`<project>/.claude/skills`). What is written there becomes instructions for the controlling agent, so the copy is as trusted as the package or the override it comes from. Mitigations:
30
+ - Copies come only from the package's `skills/` directory or from the operator's override directory next to the configuration file. No tool argument, client prompt or workspace names a source.
31
+ - The override directory and its `.pi-session-mcp-bases.json` are operator-trusted configuration next to the configuration file, never a workspace. Keep them out of every workspace and writable only by the operator; a `coding` session that can write there could change what setup installs next.
32
+ - Setup reads a bounded tree of regular files (at most 32 files and 1 MiB, no links or special files) and installs it as a real copy, never as a link. The copy is staged and renamed into place; only the exact old tree that setup itself moved is deleted afterwards.
33
+ - Setup never replaces or deletes a modified installed copy, hidden operator entries inside it, a foreign skill of the same name or a linked or non-directory entry; it reports `skill_divergent`. A link to exactly the effective skill is accepted as the operator's own (`skill_linked`) and never written through or deleted. Ownership records hold a content fingerprint (and, during an update, the replaced fingerprint), no path; a project record carries an unsalted, truncated SHA-256 of the project's real path as an opaque identity, which someone who can read the state file could test against guessed paths.
34
+ - A project may be untrusted. Inside a project setup never follows a link: any link below the project root makes the slot divergent, so a repository cannot redirect the write outside itself. In the home directory the client's skills directory may be the operator's own link (for example into a dotfiles checkout) and is followed; the skill entry itself is never followed.
35
+ - `--customize-skill` writes only below the override directory, never overwrites an existing override and never edits an operator's file. It moves the recorded base only on an explicit `--apply`, which acknowledges a reviewed change to the shipped skill.
36
+ - Worker skills are not installed into clients. Reports name the skill and a stable code, never a path or content.
29
37
  - **False verification:** Verify combines Doctor and exact registration
30
38
  equality with a bounded direct MCP initialize, `tools/list`, and
31
39
  `pi_capabilities_get({})` call. It never starts a session or invokes
@@ -68,7 +76,7 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
68
76
  - **Thinking-level clamping:** SDK capability handling can alter a requested level. The adapter compares the effective `session.thinkingLevel` after creation and disposes on a mismatch.
69
77
  - **Orphaned SDK session:** a post-creation mismatch or later registry failure could otherwise leave a live Pi handle. The adapter holds it locally until all postconditions pass, best-effort disposes on each later failure, and the registry admits only verified handles.
70
78
  - **Excess capability:** the execution profile selects a permission-derived tool allowlist. Read-only permits only `read`, `grep`, `find`, and `ls`; coding remains high impact.
71
- - **Project resource code execution:** Pi resource loading disables extension discovery, skills, prompts, and themes; only a profile's allowlisted extensions load (see below). Non-executable `AGENTS.md` context remains untrusted instruction content, never authorization.
79
+ - **Project resource code execution:** Pi resource loading disables extension discovery, default skill directories, prompts, and themes; only a profile's allowlisted extensions and its explicitly selected catalog skills load (see below). A skill, prompt template or theme that an extension package ships is not made available by the allowlist. Non-executable `AGENTS.md` context remains untrusted instruction content, never authorization.
72
80
  - **Concurrent state corruption:** a per-session queue serializes prompt admission and close; concurrent abort callers share one attempt for the active turn. Closing is terminal, settles an active turn, and disposes once.
73
81
  - **Secret/prompt disclosure:** stdout is protocol-only. Stderr diagnostics use stable code and safe context only. Public metadata is whitelisted and omits auth objects/sources, credential paths, headers, base URLs, complete model objects, SDK IDs, raw provider errors, prompts, transcripts, and tool data. Completed `assistantText` can still repeat model-visible data and is potentially sensitive.
74
82
  - **Capability-enumeration disclosure:** the trusted local MCP peer can read configured workspace aliases and execution-profile provider/model intent. Discovery omits workspace paths, authentication state, SDK state, sessions, turns, prompts, transcripts, and tool data. Operators must choose aliases that are suitable for this trusted-peer boundary.
@@ -78,8 +86,17 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
78
86
  - Only exact `npm:<name>@<version>` specs from the operator's configuration load; the MCP client cannot name an extension.
79
87
  - Resolution never installs, and a different installed version fails closed. Only Pi's own user install (`<Pi agent dir>/npm/node_modules/<name>`, normally below `~/.pi/agent`) counts; a package installed elsewhere, for example with `npm i -g`, is reported as not installed.
80
88
  - `read-only` profiles never activate extension tools. At session start, after load and again after binding, no extension tool may shadow a built-in or granted tool; later registrations are covered in the residual risks.
81
- - Client prompts never dispatch extension commands, but extension input hooks can still see, rewrite or consume a prompt.
89
+ - Client prompts never dispatch extension commands, but extension input hooks can still see, rewrite or consume a prompt. They receive it with `source: "rpc"`, Pi's marker for input from a program, so a hook that reads the source sees that no human typed it. A hook that ignores the source treats it like any other input.
82
90
  - Each extension session gets its own model runtime, and close emits `session_shutdown`.
91
+ - **Skills as prompt content:** a profile's `skills` select files from the operator's `skillCatalog`. The MCP client cannot name a skill, a path or a catalog entry; `pi_session_start` and `pi_session_prompt` take no skill input. Mitigations:
92
+ - Catalog values are single local `SKILL.md` files: no directories, globs, URLs, scheme-prefixed sources, `~` or backslashes. The one accepted scheme is `builtin:<name>`, which resolves to the operator's override next to the configuration file or else to the skill shipped in the package, and to no other place. Relative paths resolve against the configuration file's directory, never against a workspace.
93
+ - The server loads exactly the selected files with no default directories, validates them at session start and rejects oversized (over 256 KiB), duplicate, invalid and explicit-only skills with `skill_load_failed`. Its message and diagnostics never contain a path, skill name or SDK text.
94
+ - Selection limits the regular skill catalog only. An allowlisted extension stays trusted executable code and can still read files or inject prompt text.
95
+ - Skills are operator-managed prompt content. In `coding` profiles a skill's instructions run with the profile's tools, so a skill grants no rights but can direct everything the profile may do. Review skills like code.
96
+ - Content is not snapshotted: the SDK keeps file paths, so a changed skill or companion file can reach a running session. Operators must not change them during active sessions. The configuration fingerprint covers configuration bytes, not skill contents.
97
+ - A `coding` session can write any file in its workspace, including a catalog file that lies inside it, and so change the instructions of other sessions and profiles. Operators keep catalog files outside every configured workspace; nothing enforces this.
98
+ - Whether the model reads a listed skill is the model's decision; configured, available and actually read are different states.
99
+ - Pi lists each available skill to the model with the absolute path of its `SKILL.md`, so the model knows that path and can repeat it in `assistantText`. Capabilities, errors and diagnostics stay path-free; completed `assistantText` is model output and not sanitized.
83
100
  - **stdout corruption:** every write through `process.stdout` or `console.log`, `console.info` and `console.debug` other than the MCP transport's is dropped. The stdout guard intercepts `process.stdout.write`, `process.stdout.end` and `console.log`, `console.info` and `console.debug`, which covers output from extensions and the SDK in the server process. stderr gets only `foreign_stdout_suppressed` with byte and write counts, never the content. Anything that writes file descriptor 1 directly is not covered; see the residual risks.
84
101
  - **Misleading annotations:** annotations are client hints only and never grant access.
85
102
  - **Shutdown orphaning:** `SIGINT`, `SIGTERM`, and stdin EOF invoke the same bounded registry cleanup and transport close, so a client that only closes stdin does not leave sessions or their MCP children running. Handles are disposed at most once; late promises are ignored after removal. Explicit process exit prevents local shutdown from hanging indefinitely, but SDK limitations can leave external work unconfirmed or abruptly cut off.
@@ -104,7 +121,7 @@ sanitizing catch. A foreign transport exception with identical text remains
104
121
  generic. This does not raise the limit, permit additional remote error codes or
105
122
  change public terminal errors; cancellation and close still take precedence.
106
123
 
107
- Aliases and profile definitions are trusted administrator configuration, not a filesystem or provider-security sandbox. A locally available model may later be unreachable; no live provider request is made during selection. Symlinks and Pi tools can reach resources permitted to the OS identity. `AGENTS.md` can influence model behavior. The number of active sessions and the duration of an already accepted turn remain unbounded. Stdio peer authentication, quotas, persistent recovery, provider hard-kill capability, dependency integrity policy, and OS/filesystem isolation remain outside the verified boundary. Coding-profile acceptance checks behavior in a selected fixture; it does not prove OS-level isolation.
124
+ Aliases, profile definitions, skill catalog entries and the skill override directory are trusted administrator configuration, not a filesystem or provider-security sandbox. A locally available model may later be unreachable; no live provider request is made during selection. Symlinks and Pi tools can reach resources permitted to the OS identity. `AGENTS.md` can influence model behavior. The number of active sessions and the duration of an already accepted turn remain unbounded. Stdio peer authentication, quotas, persistent recovery, provider hard-kill capability, dependency integrity policy, and OS/filesystem isolation remain outside the verified boundary. Coding-profile acceptance checks behavior in a selected fixture; it does not prove OS-level isolation.
108
125
  The trusted same-UID operator can unlink or replace the setup lock path between
109
126
  checks and must not mutate the ownership directory during a transaction.
110
127
 
@@ -119,7 +136,7 @@ behind `pi-cursor-sdk` starts processes of its own during turns (observed with
119
136
  Cursor, in a `read-only` profile too); none inherited the server's stdout in
120
137
  that run. Extensions may persist their own state outside Pi
121
138
  Session MCP's in-memory boundary. Extension tool names are checked for
122
- conflicts after loading and again after binding; a tool that an extension
139
+ conflicts and for unsafe or MCP-reserved names after loading and again after binding; a tool that an extension
123
140
  registers later, during a turn, is not re-checked and may replace a built-in
124
141
  tool, `read` included, because allowlisted extensions are trusted code. An extension may deactivate built-in or granted
125
142
  tools for its own session.
@@ -22,7 +22,7 @@ There are exactly eight tools. No `pi_turn_wait` tool or live provider/model inv
22
22
  ```json
23
23
  {
24
24
  "ok": true,
25
- "server": { "name": "pi-session-mcp", "version": "0.7.0" },
25
+ "server": { "name": "pi-session-mcp", "version": "0.8.0" },
26
26
  "configuration": {
27
27
  "fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
28
28
  "reloadPolicy": "restart-required"
@@ -35,12 +35,13 @@ There are exactly eight tools. No `pi_turn_wait` tool or live provider/model inv
35
35
  "provider": "configured-provider",
36
36
  "model": "configured-model",
37
37
  "thinkingLevel": "medium",
38
- "extensions": []
38
+ "extensions": [],
39
+ "skills": []
39
40
  }]
40
41
  }
41
42
  ```
42
43
 
43
- The server sorts both arrays by alias with a deterministic code-point comparison. It copies only loaded configuration fingerprint/reload policy, configured aliases, and the seven documented profile fields. `extensions` lists the configured extension specs (`npm:<name>@<version>`), empty when a profile loads none; it makes no claim that the package is installed. It never copies the configuration path or contents, workspace paths, authentication state, SDK data, session/turn data, prompts, transcripts, or tool arguments/results. The handler reads the in-memory loaded snapshot only and does not call the registry, Pi adapter, provider, filesystem, or network. The fingerprint identifies the exact loaded bytes, not current-file freshness; provider/model fields describe configured intent and make no availability claim.
44
+ The server sorts both arrays by alias with a deterministic code-point comparison. It copies only loaded configuration fingerprint/reload policy, configured aliases, and the eight documented profile fields. `extensions` lists the configured extension specs (`npm:<name>@<version>`), empty when a profile loads none; it makes no claim that the package is installed. `skills` lists the skill-catalog aliases that the profile selects, in configured order, `[]` when it selects none. It contains no paths, file names, contents or SDK diagnostics, and it is configuration intent only: it makes no claim that a skill file exists, loads, or will be read by the model. See [Pi skills](skills.md). It never copies the configuration path or contents, workspace paths, authentication state, SDK data, session/turn data, prompts, transcripts, or tool arguments/results. The handler reads the in-memory loaded snapshot only and does not call the registry, Pi adapter, provider, filesystem, or network. The fingerprint identifies the exact loaded bytes, not current-file freshness; provider/model fields describe configured intent and make no availability claim.
44
45
 
45
46
  Annotations are `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, and `openWorldHint: false`. Clients may use them for presentation, but the strict input schema and server-owned projection enforce the contract.
46
47
 
@@ -52,6 +53,8 @@ The former `profile` input is rejected; it is not reinterpreted as an execution-
52
53
 
53
54
  Successful session metadata is a whitelist: `id`, `workspace`, `executionProfile`, `profile`, `provider`, `model`, `thinkingLevel`, `state`, `createdAt`, and `updatedAt`. `profile` remains derived permission metadata (`read-only` or `coding`) for compatibility; it is not client-selectable. Provider, model, and thinking level are projected only after SDK post-verification.
54
55
 
56
+ The selected skills of an execution profile are never an input. A session loads exactly the skills that the profile selects from the operator's skill catalog, validated at start; a failure is `skill_load_failed`. A catalog value is the path of one `SKILL.md` or `builtin:<name>`, which names a skill of the skill library: the operator's override next to the configuration file if it exists, else the skill shipped in the package. It is resolved when the server loads the configuration, and the value never appears in tool output. See [Pi skills](skills.md).
57
+
55
58
  ## Deterministic MCP errors
56
59
 
57
60
  Public MCP errors are stable and sanitized. Their messages do not include inputs, auth details, provider responses, filesystem paths, or secrets.
@@ -71,7 +74,8 @@ Public MCP errors are stable and sanitized. Their messages do not include inputs
71
74
  | `extension_not_installed` | Pi has no installed package for a configured extension. |
72
75
  | `extension_version_mismatch` | Pi's installed extension package has another version than the configured one. |
73
76
  | `extension_load_failed` | A configured extension has an invalid manifest, failed to load or failed to bind. |
74
- | `extension_tool_conflict` | An extension tool name collides with a built-in, MCP or other extension tool. |
77
+ | `extension_tool_conflict` | An extension tool name collides with a built-in, MCP or other extension tool, or is unsafe: not 1 to 64 letters, digits, `_` or `-` (not starting with `-`), or reserved for MCP (`mcp__` prefix, `list_mcp_resources`, `list_mcp_resource_templates`, `read_mcp_resource`). |
78
+ | `skill_load_failed` | A skill that the profile selects could not be loaded: a missing (also a `builtin:` name that matches no override and no shipped skill), unreadable or oversized (over 256 KiB) file, an invalid skill, two selected files with the same Pi skill name, two aliases on the same canonical file, or a skill with `disable-model-invocation: true`. The message is the fixed `Configured Pi skill could not be loaded`; it never contains a path, skill name or SDK text. |
75
79
  | `pi_session_creation_failed` | Pi session construction failed without a more specific safe classification. |
76
80
  | `server_stopping` | Shutdown has begun, so a new session cannot be admitted. |
77
81
 
@@ -81,7 +85,7 @@ Invalid execution-profile configuration prevents startup, so it cannot be an MCP
81
85
 
82
86
  | Startup diagnostic code | Meaning |
83
87
  | --- | --- |
84
- | `invalid_execution_profile_config` | The execution-profile configuration violates its invariants or strict shape. |
88
+ | `invalid_execution_profile_config` | The execution-profile configuration violates its invariants or strict shape, including an unknown or duplicate selected skill alias, two selected skill aliases with the same path, or an invalid `skillCatalog` entry (including a malformed `builtin:` name). |
85
89
  | `invalid_thinking_level` | A configured thinking level is outside the supported enum. |
86
90
 
87
91
  `pi_session_prompt` returns `{session,turn}` only after successful Pi preflight. Preflight is bounded at 5,000 ms; a missing callback, hanging prompt acceptance, or timeout returns `prompt_timeout` / `Pi prompt preflight timed out` and creates no public turn. Late settlement may only release the unpublished operation's quarantine; it cannot publish a turn. `pi_session_abort` waits at most 5,000 ms per caller for one shared SDK abort attempt and returns `abort_timeout` / `Pi abort timed out` without claiming the turn was aborted. Rejection/failure remain `prompt_rejected`/`abort_failed`. Close allows at most 5,000 ms for abort cleanup, then performs best-effort disposal and removes the public registry entry; it remains idempotent through the retained tombstone. Registry shutdown rejects new starts, allows 12,000 ms, and disposes a handle from a late-resolving start instead of admitting it. Process shutdown, started by `SIGINT`, `SIGTERM`, or stdin EOF, allows 15,000 ms, attempts transport cleanup even after registry failure/timeout, then explicitly exits the process. These boundaries do not guarantee graceful cancellation of a provider request. `pi_turn_get` returns a `TurnView` with `turnId`, `sessionId`, state, timestamps, and `completedAt` for terminal turns. Completed views add normalized final `assistantText` and `truncated`; failed views contain only `{code:"turn_failed",message:"Pi turn failed"}`. Final assistant text is capped at 64 KiB UTF-8 bytes, each active session retains at most 20 turns, and close removes its turn data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-session-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Local stdio MCP server for controlled Pi coding-agent sessions",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -29,7 +29,9 @@
29
29
  ".pi-session-mcp-cli/",
30
30
  "scripts/doctor.mjs",
31
31
  "pi-session-mcp.example.json",
32
+ "skills/",
32
33
  "docs/",
34
+ "!docs/superpowers/",
33
35
  "CHANGELOG.md",
34
36
  "SECURITY.md",
35
37
  "CONTRIBUTING.md"