pi-session-mcp 0.8.0 → 0.8.2

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 (45) hide show
  1. package/.pi-session-mcp-cli/doctor.js +14 -7
  2. package/.pi-session-mcp-cli/override-lock.js +125 -0
  3. package/.pi-session-mcp-cli/setup-platform.js +195 -0
  4. package/.pi-session-mcp-cli/skill-customize.js +131 -46
  5. package/.pi-session-mcp-cli/skill-library.js +72 -4
  6. package/CHANGELOG.md +159 -0
  7. package/CONTRIBUTING.md +5 -3
  8. package/README.md +5 -3
  9. package/dist/doctor.d.ts +3 -0
  10. package/dist/doctor.js +14 -7
  11. package/dist/override-lock.d.ts +34 -0
  12. package/dist/override-lock.js +125 -0
  13. package/dist/pi-adapter.d.ts +6 -4
  14. package/dist/sdk-pi-adapter.js +28 -5
  15. package/dist/session-registry.d.ts +8 -6
  16. package/dist/session-registry.js +3 -2
  17. package/dist/setup-ownership.d.ts +4 -1
  18. package/dist/setup-ownership.js +6 -3
  19. package/dist/setup-skills.d.ts +46 -1
  20. package/dist/setup-skills.js +349 -78
  21. package/dist/setup.js +21 -4
  22. package/dist/skill-customize.d.ts +19 -1
  23. package/dist/skill-customize.js +131 -46
  24. package/dist/skill-library.d.ts +46 -1
  25. package/dist/skill-library.js +72 -4
  26. package/docs/acceptance.md +747 -26
  27. package/docs/adr/0007-profile-skill-selection.md +11 -0
  28. package/docs/architecture.md +1 -1
  29. package/docs/client-setup-design.md +157 -24
  30. package/docs/client-setup.md +38 -7
  31. package/docs/doctor-design.md +1 -1
  32. package/docs/installation.md +8 -2
  33. package/docs/pi-extensions-design.md +11 -0
  34. package/docs/releasing.md +25 -10
  35. package/docs/reviews/2026-10-13-post-0.8.1-audit.md +393 -0
  36. package/docs/roadmap.md +127 -0
  37. package/docs/skills.md +173 -16
  38. package/docs/sprints/2026-10-13-s1-correctness.md +355 -0
  39. package/docs/sprints/2026-10-13-s2-evidence.md +431 -0
  40. package/docs/sprints/2026-10-13-s3-live-usage.md +267 -0
  41. package/docs/sprints/2026-10-13-s4-decision-inputs.md +241 -0
  42. package/docs/threat-model.md +3 -3
  43. package/docs/tool-contracts.md +20 -2
  44. package/package.json +1 -1
  45. package/skills/pi-session-orchestration/SKILL.md +8 -4
@@ -215,3 +215,14 @@ the controller skill that `setup --apply` installs, so the next release after
215
215
  0.7.1 is 0.8.0. The change is recorded under
216
216
  `## Unreleased` in the changelog. Implementing it does not itself release or
217
217
  bump the version.
218
+
219
+ ## Note of 2026-10-12: fingerprints, locking and recovery after 0.8.0
220
+
221
+ The decisions above stand. Three details changed after 0.8.0 (issues #50, #51
222
+ and #52): skill tree fingerprints are version 2 (`v2:` values; a 0.8.0 value
223
+ counts only when a trusted reference proves it), `--apply --customize-skill`
224
+ runs under a kernel lock on the override directory and writes the bases file as
225
+ schema version 2, and an interrupted update of an installed copy is settled by
226
+ the next `--apply`, `--remove` or `--rollback`. See [Pi
227
+ skills](../skills.md#upgrade-from-080) and the [setup lifecycle
228
+ design](../client-setup-design.md#skill-installation).
@@ -71,7 +71,7 @@ The registry owns public UUIDs, lifecycle state, the turn read model, background
71
71
 
72
72
  ## Lifecycle and public data
73
73
 
74
- `pi_session_prompt` creates a provisional turn identity, invokes Pi preflight, and only after accepted preflight publishes `{session, turn}`; rejected preflight leaves no orphan turn. Completion returns to `idle`; failure is generic. Each active session retains at most 20 turns. A turn is `running`, `completed`, `failed`, or `aborted`; its public view contains IDs, timestamps, and terminal `completedAt`. Completed text is normalized and capped at 64 KiB UTF-8 with Unicode-safe truncation. It can contain model-visible workspace data and remains potentially sensitive.
74
+ `pi_session_prompt` creates a provisional turn identity, invokes Pi preflight, and only after accepted preflight publishes `{session, turn}`; rejected preflight leaves no orphan turn. Completion returns to `idle`; failure is generic. Each active session retains at most 20 turns. A turn is `running`, `completed`, `failed`, or `aborted`; its public view contains IDs, timestamps, and terminal `completedAt`. While a turn runs, its `updatedAt` is the time of the last observed public SDK activity (assistant message and tool execution events, content-free) and is a liveness lower bound only; see [Tool contracts](tool-contracts.md). Completed text is normalized and capped at 64 KiB UTF-8 with Unicode-safe truncation. It can contain model-visible workspace data and remains potentially sensitive.
75
75
 
76
76
  Session metadata contains only whitelisted values: public session ID, workspace alias, execution-profile alias, derived permission `profile`, post-verified provider/model/effective thinking level, lifecycle state, and timestamps. Capability metadata uses a separate whitelist: server name/version, loaded configuration fingerprint/reload policy, workspace aliases, and configured profile fields, including the selected skill aliases. Neither view contains workspace paths, full model objects, SDK session IDs, auth sources, credential paths, headers, base URLs, prompts, transcripts, or raw provider errors.
77
77
 
@@ -79,7 +79,12 @@ findings make the overall result `failed`, like any other finding.
79
79
  bounded reader: regular files only, at most 32 files, 1 MiB and 8 levels, no
80
80
  links or special files inside the tree, entries whose name starts with `.`
81
81
  skipped. A missing or unusable source is `skill_source_invalid`. The fingerprint
82
- is a SHA-256 over the sorted relative paths and contents.
82
+ is version 2 (`src/skill-library.ts`): `v2:` and a SHA-256 over a domain and
83
+ version tag, the file count, and per file in path byte order the length-prefixed
84
+ UTF-8 path and the length-prefixed content. Version 1 (0.8.0) hashed
85
+ `path NUL content NUL` per file and was ambiguous, because content may contain
86
+ NUL bytes. A version 1 value never proves identity alone (see *Legacy records*
87
+ below).
83
88
 
84
89
  **Slots.** A slot is the skill directory a client reads:
85
90
 
@@ -97,20 +102,86 @@ alias `skill:pi-session-orchestration`. A project slot uses
97
102
  of a SHA-256 over the project's real path: an opaque identity, not a path, so a
98
103
  `--remove` in another project never touches this project's copy. The `pending`
99
104
  phase is written before the first filesystem change and promoted to `owned`
100
- after a post-install inspection. During an update, the pending record also
101
- keeps the fingerprint of the copy it replaces (`replaces`), so an interrupted
102
- update leaves a copy that setup still recognizes as its own: the next
103
- `--apply` finishes the update and `--remove` deletes it. A later run that finds
104
- a `pending` record whose slot already equals the source promotes it.
105
+ after a post-install inspection. Skill records hold `v2:` values in
106
+ `fingerprint` and `replaces`; registration records keep their 64-hex snapshot
107
+ hash. During an update, the pending record also keeps the fingerprint of the
108
+ copy it replaces (`replaces`), so an interrupted update leaves a copy that
109
+ setup still recognizes as its own: the next `--apply` finishes the update and
110
+ `--remove` deletes it. A later run that finds a `pending` record whose slot
111
+ already equals the source promotes it.
105
112
 
106
113
  **Atomic copy.** Setup creates missing parent directories (mode `0755`), stages
107
- the tree in a temporary directory in the slot's parent (files `0644`, each file
114
+ the tree in a staging directory in the slot's parent (files `0644`, each file
108
115
  and directory fsynced), and re-checks that the slot is unchanged
109
116
  (fingerprint, device and inode). It then renames the exact old tree to a backup
110
- name, renames the staged directory into place, inspects the result, and deletes
111
- the backup only if it is still the exact old tree it moved. An unhealthy
112
- ownership fence retains staged and backup content for conservative recovery.
113
- The slot is never edited in place.
117
+ name, renames the staged directory into place, inspects the result, verifies
118
+ that the backup is still the exact old tree it moved, renames it to a trash
119
+ name and deletes the trash entry by entry. The slot is never edited in place.
120
+
121
+ **Intermediates and recovery.** The three intermediate directories are named
122
+ after the pending record's transaction id, in the slot's parent:
123
+ `.pi-session-mcp-<skill>-<transactionId>.staging`, `.backup` and `.trash`.
124
+ Recovery derives them from the persisted record and never searches. The order
125
+ is: pending record, staging, backup rename, install, verify backup, rename to
126
+ trash, delete the trash entry by entry, owned record. `--apply`, `--remove` and
127
+ `--rollback` settle a pending transaction first, inside the ownership
128
+ transaction, and only when the slot is empty, a link, or one of setup's two
129
+ copies:
130
+
131
+ - A trash directory is deleted only when its current contents are the
132
+ complete, unchanged replaced copy: its version 2 tree fingerprint equals the
133
+ replaced fingerprint in the pending record. A partial trash tree (for
134
+ example after an interruption during the entry-by-entry deletion, including
135
+ an empty one), a modified, added-to, foreign or otherwise unprovable tree,
136
+ and any trash when the record names no usable replaced fingerprint are
137
+ retained. A directory name, the transaction id or a structural check alone
138
+ never proves ownership of the contents. This deliberately replaces the
139
+ earlier automatic deletion of partial trash trees; continuing a partial
140
+ deletion would need additional durable evidence, and no journal is added
141
+ for it. The fingerprint covers the paths and contents of regular files, not
142
+ empty directories or file modes, so an empty directory or a mode change
143
+ inside an otherwise complete trash does not prevent its deletion; no file
144
+ content is lost that way.
145
+ - The backup must equal the replaced copy; the staging directory must equal
146
+ the target or be an interrupted write of it. A partially written staging
147
+ directory counts only when every file is a prefix-consistent part of the
148
+ current library tree and that tree is the record's target.
149
+ - `--apply` with an empty slot finishes forward with a complete staging
150
+ directory, else moves the verified backup back. `--remove` and `--rollback`
151
+ delete the verified intermediates and leave the slot to the removal.
152
+ - Anything modified or foreign, the trash directory included, stays and is
153
+ reported as `skill_recovery_retained` (divergent). The pending record and
154
+ the slot then stay unchanged, and nothing is reported as settled. A
155
+ retained trash stops recovery before any other intermediate is touched; a
156
+ retained backup or staging directory can follow the deletion of a proven
157
+ own trash. A failed update leaves its staging directory until the
158
+ next run settles it, and an unhealthy ownership fence retains intermediates
159
+ the same way.
160
+ - Operator settlement of a retained trash directory: inspect the
161
+ `.pi-session-mcp-<skill>-<transactionId>.trash` directory next to the slot,
162
+ move any own content elsewhere, delete that directory, and rerun the same
163
+ setup command.
164
+ - For a pending version 2 transaction, `--dry-run` reports
165
+ `skill_recovery_retained` (divergent, exit 1) whenever `--apply` would
166
+ retain an intermediate, using the same read-only classification as the
167
+ recovery, and otherwise `skill_recovery_planned`. A dry-run never writes.
168
+ - Random-name directories of 0.8.0 updates are never searched for or deleted;
169
+ an operator may remove them by hand.
170
+ - `--dry-run` and `--verify` do not report leftover intermediates.
171
+
172
+ **Legacy records.** A skill record whose `fingerprint` or `replaces` is a bare
173
+ 64-hex value was written by 0.8.0. It names the slot only if a trusted
174
+ reference has that version 1 value and is byte-identical, by its version 2
175
+ fingerprint, to the installed copy. The references are the shipped skills of
176
+ 0.8.0 (a fixed table in `src/skill-library.ts`) and the current library tree.
177
+ When proven, `--apply` migrates the record to version 2 and then updates the
178
+ copy as usual; `--remove` removes the proven copy. Otherwise `--dry-run`,
179
+ `--apply`, `--remove` and `--rollback` report `skill_legacy_unverified`
180
+ (divergent) and change neither the copy nor the record. The operator inspects
181
+ the copy and, if it is unchanged setup content that is no longer needed,
182
+ deletes it; an absent slot clears the record on the next operation. The
183
+ intermediates of an interrupted 0.8.0 update cannot be derived and are not
184
+ recovered.
114
185
 
115
186
  **Divergence rules.** Setup never replaces or deletes, and reports
116
187
  `skill_divergent` for:
@@ -118,6 +189,8 @@ The slot is never edited in place.
118
189
  - an installed copy whose fingerprint differs from setup's own copy (the
119
190
  owned fingerprint, or the one a pending update replaces), even if it happens
120
191
  to equal the new source;
192
+ - an installed copy that a 0.8.0 record names but no trusted reference proves
193
+ (`skill_legacy_unverified`);
121
194
  - a copy that differs from the source without a record (a foreign skill of the
122
195
  same name);
123
196
  - any entry whose name starts with `.` inside the installed copy: setup never
@@ -171,11 +244,41 @@ status and a stable code. No skill content, fingerprint or path leaves setup.
171
244
  `src/skill-customize.ts`) does not touch clients and takes no ownership lock. It
172
245
  copies a shipped skill into the override directory `skills/` next to the
173
246
  configuration file and records the shipped fingerprint under the skill name in
174
- `<override dir>/.pi-session-mcp-bases.json` (`{"schemaVersion":1,"bases":{...}}`,
175
- strictly validated, at most 64 KiB, replaced atomically). The copy is staged in
247
+ `<override dir>/.pi-session-mcp-bases.json`
248
+ (`{"schemaVersion":2,"bases":{"<name>":"<fingerprint>"}}`, strictly validated,
249
+ at most 64 KiB). A value is a `v2:` fingerprint or a legacy 64-hex value that
250
+ could not be migrated. A version 1 file is still read. The copy is staged in
176
251
  a temporary directory and renamed into place; an existing override is never
177
252
  overwritten. A changed shipped skill is only reported (`skill_override_outdated`);
178
- `--apply` then moves the recorded base and nothing else. It cannot be combined
253
+ `--apply` then moves the recorded base and nothing else.
254
+
255
+ `--apply` is a cross-process transaction per override directory
256
+ (`src/override-lock.ts`). It takes an exclusive kernel lock on the stable lock
257
+ file `<override dir>/.pi-session-mcp-bases.lock` (mode `0600`, never deleted;
258
+ on Linux the trusted util-linux `flock` at `/usr/bin/flock` or `/bin/flock`, on
259
+ macOS `O_EXLOCK`; the same supported local filesystems as the ownership
260
+ state). Under the lock it re-reads and validates the bases file, copies the
261
+ skill if no override exists, merges the base and writes the file atomically: an
262
+ fsynced temporary file, a rename and a directory fsync. The lock is released on
263
+ every path. The wait is bounded at 30 seconds (`skill_customize_lock_busy`);
264
+ a lock that cannot be taken is `skill_customize_lock_unavailable`. A killed
265
+ holder releases the lock through the kernel and may leave a stale
266
+ `.pi-session-mcp-bases.json.<hex>.tmp` or a temporary copy directory
267
+ `.<skill>.<hex>.tmp`, which are harmless. The dry-run takes no
268
+ lock and writes nothing: no directory, lock file or temporary file. Every write
269
+ produces version 2 and migrates each legacy value that names a released 0.8.0
270
+ shipped skill; other legacy values are kept as they are.
271
+
272
+ Decisions for an existing legacy base: if it names the current shipped skill,
273
+ the dry-run reports `skill_base_migration_planned` and `--apply` reports
274
+ `skill_base_migrated`; if it names no released shipped skill, the dry-run
275
+ reports `skill_base_unverified` and `--apply` acknowledges it as
276
+ `skill_base_updated`; if it names another released skill, the result is
277
+ `skill_override_outdated`. An existing override that equals the shipped skill
278
+ but has no base completes a customization whose bases commit failed
279
+ (`skill_customize_planned`, `skill_customized`). Of two concurrent applies for
280
+ the same skill, one reports `skill_customized` and the other
281
+ `skill_override_current`. Doctor stays offline, read-only and lock-free. It cannot be combined
179
282
  with `--target`, `--verify`, `--rollback` or `--remove`. The finding target is
180
283
  `{ client: "skills", scope: "override", alias: <name> }`; the alias is the
181
284
  skill name only if it is valid, else `invalid`.
@@ -184,38 +287,68 @@ skill name only if it is valid, else `invalid`.
184
287
 
185
288
  Production verification requires the offline Doctor, exact public client-CLI registration inspection, and a directly launched bounded MCP handshake. The verifier calls `pi_capabilities_get` with `{}` only; session start/prompt/turn and provider/model operations are forbidden. Its child process is terminated and awaited before success is reported.
186
289
 
290
+ Verify evaluates one target in this order, and the first failure ends that
291
+ target's verification:
292
+
293
+ 1. An unknown client: `client_unsupported`.
294
+ 2. Ownership: `ownership_unavailable`.
295
+ 3. Release binding: `release_binding_mismatch`.
296
+ 4. Doctor, under its own code. A failed Doctor ends the target's verify before
297
+ the registration is inspected and before any handshake.
298
+ 5. Inspection. A client the inspection reports as unsupported or not
299
+ discoverable, for example because its CLI is not on `PATH`, gives status
300
+ `unsupported` and code `unsupported`. An inspection that throws gives
301
+ `inspect_failed`. An absent registration, a divergent one, and an
302
+ equivalent one with another fingerprint give `registration_not_equivalent`.
303
+ 6. Only a truly equivalent registration runs the bounded handshake, which
304
+ reports `mcp_verified` or `mcp_<status>`.
305
+
306
+ None of the failure cases mutates a registration or starts the handshake, and
307
+ all exit with `1`. There is no separate client-unavailable code; the
308
+ dry-run already separates these states the same way.
309
+
187
310
  ## Compatibility and reporting
188
311
 
189
312
  Codex and Claude Code are separate adapters with explicit supported scopes and documented capability fixtures. Unknown output fails closed with stable codes. Absolute paths are passed only to client commands where required and are never included in results, logs, or ownership metadata. Client command diagnostics must be reduced to stable codes before crossing this boundary.
190
313
 
191
- Human and JSON output derive from one `schemaVersion: 1` result. Exit code `0` is success, `1` is an operational or reconciliation failure, `64` is command-line misuse, and interrupted setup returns `130` for `SIGINT` or `143` for `SIGTERM` only after the guardian cleanup path has completed or retained the fence. Automatic replacement of a divergent registration is deliberately unsupported.
314
+ Human and JSON output derive from one `schemaVersion: 1` result; this version is the setup report's own, not the package version or the MCP tool contract (see [Versions](tool-contracts.md#versions)). Exit code `0` is success, `1` is an operational or reconciliation failure, `64` is command-line misuse, and interrupted setup returns `130` for `SIGINT` or `143` for `SIGTERM` only after the guardian cleanup path has completed or retained the fence. Automatic replacement of a divergent registration is deliberately unsupported.
192
315
 
193
316
  Stable finding codes are grouped by responsibility:
194
317
 
195
318
  - input/prerequisites: `target_invalid`, `config_required`, `usage_invalid`,
196
319
  `platform_unsupported`, `operation_interrupted`,
197
320
  `doctor_failed`, `release_unavailable`, `client_unsupported`, `inspect_failed`;
198
- - planning/reconciliation: `absent`, `equivalent`, `divergent`,
321
+ - planning/reconciliation: `absent`, `equivalent`, `divergent`, `unsupported`,
199
322
  `already_equivalent`, `divergence_requires_replacement`,
200
323
  `registration_fingerprint_mismatch`, `release_binding_mismatch`;
201
324
  - ownership/apply: `applied`, `pending_recovered`, `ownership_unavailable`,
202
325
  `apply_failed_recovery_pending`;
203
- - verification: `registration_not_equivalent`, `mcp_verified`,
204
- `mcp_unsupported`, `mcp_timeout`, `mcp_failed`;
326
+ - verification: `registration_not_equivalent` (absent, divergent or
327
+ differently fingerprinted registration), `mcp_verified`, `mcp_unsupported`,
328
+ `mcp_timeout`, `mcp_failed`; verify also reports `unsupported` (client not
329
+ discoverable) and `inspect_failed` (inspection threw), which belong to the
330
+ other groups;
205
331
  - rollback/removal: `rolled_back`, `removed`, `ownership_diverged`,
206
332
  `cleanup_failed`;
207
333
  - controller skill, dry-run: `skill_absent`, `skill_equivalent`, `skill_linked`,
208
- `skill_update_planned`, `skill_divergent`, `skill_source_invalid`;
209
- - controller skill, apply: `skill_installed`, `skill_already_equivalent`, `skill_linked`,
210
- `skill_updated`, `skill_divergent`, `skill_source_invalid`,
334
+ `skill_update_planned`, `skill_recovery_planned`,
335
+ `skill_recovery_retained`, `skill_divergent`, `skill_legacy_unverified`,
336
+ `skill_source_invalid`;
337
+ - controller skill, apply: `skill_installed`, `skill_already_equivalent`, `skill_recovered`, `skill_linked`,
338
+ `skill_updated`, `skill_divergent`, `skill_legacy_unverified`,
339
+ `skill_recovery_retained`, `skill_source_invalid`,
211
340
  `skill_install_failed`;
212
341
  - controller skill, verify: `skill_verified`, `skill_missing`,
213
342
  `skill_not_equivalent`;
214
343
  - controller skill, rollback/removal: `skill_removed`, `skill_kept_shared`,
215
- `skill_not_owned`, `skill_absent`, `skill_divergent`;
344
+ `skill_not_owned`, `skill_absent`, `skill_divergent`,
345
+ `skill_legacy_unverified`, `skill_recovery_retained`;
216
346
  - skill customization: `skill_customize_planned`, `skill_customized`,
217
- `skill_override_current`, `skill_override_outdated`, `skill_base_updated`,
218
- `skill_not_shipped`, `skill_bases_corrupt`, `skill_override_invalid`,
347
+ `skill_override_current`, `skill_override_outdated`,
348
+ `skill_base_migration_planned`, `skill_base_migrated`,
349
+ `skill_base_unverified`, `skill_base_updated`, `skill_not_shipped`,
350
+ `skill_bases_corrupt`, `skill_override_invalid`,
351
+ `skill_customize_lock_busy`, `skill_customize_lock_unavailable`,
219
352
  `skill_customize_failed`.
220
353
 
221
354
  The public statuses are `planned`, `ok`, `unchanged`, `absent`, `divergent`,
@@ -90,6 +90,25 @@ bounded direct MCP `initialize`, `tools/list`, and
90
90
  `pi_capabilities_get({})` check. It never starts a session or invokes
91
91
  a provider.
92
92
 
93
+ ### Verify diagnostics
94
+
95
+ Verify checks one target in a fixed order and stops that target at the first
96
+ failure. After an unknown client (`client_unsupported`), ownership
97
+ (`ownership_unavailable`) and release binding (`release_binding_mismatch`),
98
+ Doctor runs; if it fails, verify reports its code and neither inspects the
99
+ registration nor runs the handshake. Then the client's registration is
100
+ inspected:
101
+
102
+ | Result | Status | Code | What it means |
103
+ | --- | --- | --- | --- |
104
+ | Client unsupported or not discoverable | `unsupported` | `unsupported` | Setup could not run or recognize the client CLI. Usually the CLI is not on `PATH`, for example in a non-interactive SSH shell. Put the client's directory on `PATH` and rerun; the registration itself may be fine. `--dry-run` reports the same state |
105
+ | Inspection failed | `failed` | `inspect_failed` | Inspecting the registration threw an error. Rerun; if it persists, run the client's own `mcp` listing |
106
+ | Registration absent, divergent, or equivalent with another fingerprint | `failed` | `registration_not_equivalent` | The registration is missing or differs from the one setup would create. Run `--dry-run` for the exact state |
107
+ | Registration equivalent | `ok` or `failed` | `mcp_verified` or `mcp_<status>` | Only now does the bounded MCP handshake run |
108
+
109
+ None of the failure rows changes a registration or starts the handshake, and
110
+ each exits with `1`.
111
+
93
112
  ### Controller skill
94
113
 
95
114
  Besides the registration, `--apply` installs the controller skill
@@ -117,19 +136,26 @@ Skill findings per operation:
117
136
 
118
137
  - `--dry-run`: `skill_absent` (absent), `skill_equivalent` (unchanged),
119
138
  `skill_linked` (unchanged; a link to exactly the effective skill),
120
- `skill_update_planned` (planned), `skill_divergent` (divergent),
139
+ `skill_update_planned` (planned), `skill_recovery_planned` (planned; the
140
+ ownership record is pending for an update that `--apply` would settle),
141
+ `skill_divergent` (divergent), `skill_legacy_unverified` (divergent),
121
142
  `skill_source_invalid` (failed).
122
143
  - `--apply`: `skill_installed` (ok), `skill_already_equivalent` (unchanged),
123
- `skill_linked` (unchanged),
144
+ `skill_recovered` (ok; `--apply` settled an interrupted update and needed no
145
+ further update), `skill_linked` (unchanged),
124
146
  `skill_updated` (ok; setup replaced its own unmodified copy after a package
125
- update or a changed override), `skill_divergent`, `skill_source_invalid`,
147
+ update or a changed override), `skill_divergent`, `skill_legacy_unverified`,
148
+ `skill_recovery_retained` (divergent), `skill_source_invalid`,
126
149
  `skill_install_failed` (failed).
127
150
  - `--verify`: `skill_verified` (ok), `skill_missing` and
128
151
  `skill_not_equivalent` (failed).
129
152
  - `--rollback` and `--remove`: `skill_removed` (ok), `skill_kept_shared`
130
153
  (unchanged; the other Claude Code `user` or `local` registration still uses
131
154
  the shared directory), `skill_not_owned` (unchanged), `skill_absent` (ok) and
132
- `skill_divergent` (a modified copy stays in place).
155
+ `skill_divergent` (a modified copy stays in place), `skill_legacy_unverified`
156
+ (a record of 0.8.0 that no trusted reference proves; copy and record stay) and
157
+ `skill_recovery_retained` (a modified or foreign intermediate directory of an
158
+ interrupted update stays).
133
159
 
134
160
  Setup never replaces or deletes a modified installed copy, a skill of the same
135
161
  name that it did not install, or a link or non-directory at the skill entry; it
@@ -155,9 +181,14 @@ records the fingerprint of the shipped skill in
155
181
  `.pi-session-mcp-bases.json` there. The finding target is
156
182
  `skills/override/<name>`; the codes are `skill_customize_planned` (planned),
157
183
  `skill_customized` (ok), `skill_override_current` (unchanged),
158
- `skill_override_outdated` (planned), `skill_base_updated` (ok),
159
- `skill_not_shipped`, `skill_bases_corrupt`, `skill_override_invalid` and
160
- `skill_customize_failed` (failed). An existing override is never overwritten.
184
+ `skill_override_outdated` (planned), `skill_base_migration_planned` (planned),
185
+ `skill_base_unverified` (planned), `skill_base_migrated` (ok),
186
+ `skill_base_updated` (ok), `skill_not_shipped`, `skill_bases_corrupt`,
187
+ `skill_override_invalid`, `skill_customize_lock_busy`,
188
+ `skill_customize_lock_unavailable` and `skill_customize_failed` (failed). An
189
+ existing override is never overwritten. `--apply` runs under an exclusive lock
190
+ on the override directory (`.pi-session-mcp-bases.lock`, 30 s wait); the dry-run
191
+ takes no lock and writes nothing.
161
192
  After `skill_override_outdated`, `--apply` moves only the recorded base, which
162
193
  acknowledges a change you reviewed. Details: [Pi skills](skills.md#customize-and-add-skills).
163
194
 
@@ -43,7 +43,7 @@ Doctor reads extension manifests from Pi's user install location only; it never
43
43
 
44
44
  The skill checks run after the extension checks. They look only at file availability: Doctor never reads a skill's frontmatter, never parses or executes it, and the subject carries the catalog alias, never a path. Frontmatter, skill-name rules, name collisions and explicit-only flags (`disable-model-invocation`) are checked only at session start by the Pi SDK contract, so a clean Doctor run does not promise that a skill loads. Unknown or duplicate aliases and two selected aliases with one path are configuration errors and surface as `config_invalid`. See [Pi skills](skills.md).
45
45
 
46
- `skill_override_outdated` concerns the operator's skill overrides, not a profile. It is `ok` for a recorded base that still matches the shipped skill, and a warning (exit status 1, never blocking `setup`) when the shipped skill changed after the operator ran `pi-session-mcp-setup --customize-skill <name>`; its remediation names `pi-session-mcp-setup --apply --customize-skill <name>` to acknowledge a reviewed change. Only skills with a recorded base are compared, and a recorded name that is no longer shipped is skipped. A corrupt bases file yields a warning with the subject `skill:bases`. Doctor reads the bases file and the shipped skill files only; nothing is executed and the subject is a validated skill name, never a path. The check sorts after `skill_duplicate`.
46
+ `skill_override_outdated` concerns the operator's skill overrides, not a profile. It is `ok` for a recorded base that still matches the shipped skill, and a warning (exit status 1, never blocking `setup`) when the shipped skill changed after the operator ran `pi-session-mcp-setup --customize-skill <name>`; its remediation names `pi-session-mcp-setup --apply --customize-skill <name>` to acknowledge a reviewed change. Only skills with a recorded base are compared, and a recorded name that is no longer shipped is skipped. A recorded base of 0.8.0 (a version 1 fingerprint) is compared through the version 2 value of the released 0.8.0 skill it names; a version 1 base that names no released skill cannot be confirmed and yields a warning that says so, with the same remediation. A corrupt bases file yields a warning with the subject `skill:bases`. Doctor reads the bases file and the shipped skill files only; nothing is executed and the subject is a validated skill name, never a path. The check sorts after `skill_duplicate`.
47
47
 
48
48
  The Doctor reuses `loadConfig`; it does not duplicate alias, profile, thinking
49
49
  level, or read-only-default rules. Workspace values are used only for local
@@ -20,7 +20,7 @@ An installation consists of these locations:
20
20
  | --- | --- | --- |
21
21
  | Package | the npm global prefix, or a dedicated directory per release such as `~/.local/share/pi-session-mcp/releases/<tag>` | The built server, `setup` and Doctor. |
22
22
  | Configuration | a file outside the package, for example `~/.config/pi-session-mcp/pi-session-mcp.json` | Operator-owned, mode `0600`, never committed. |
23
- | Skill overrides (optional) | the `skills/` directory next to the configuration file, for example `~/.config/pi-session-mcp/skills/` | Your own or customized skills, plus `.pi-session-mcp-bases.json`. Created by you or by `setup --customize-skill`. See [Pi skills](#pi-skills). |
23
+ | Skill overrides (optional) | the `skills/` directory next to the configuration file, for example `~/.config/pi-session-mcp/skills/` | Your own or customized skills, plus `.pi-session-mcp-bases.json` and its lock file `.pi-session-mcp-bases.lock`. Created by you or by `setup --customize-skill`. See [Pi skills](#pi-skills). |
24
24
  | Controller skill | `~/.agents/skills/pi-session-orchestration` (Codex), `~/.claude/skills/pi-session-orchestration` or `<project>/.claude/skills/pi-session-orchestration` (Claude Code) | Written by `setup --apply`; see [Register a client](#4-register-a-client). |
25
25
  | Setup ownership state | `~/.local/state/pi-session-mcp/ownership.json` | Written by `setup` only. Path-free and secret-free. |
26
26
 
@@ -487,7 +487,8 @@ choose skills.
487
487
  create `<override dir>/<new-name>/SKILL.md` and reference it as
488
488
  `builtin:<new-name>`; no command is needed. After a package update, Doctor
489
489
  warns with `skill_override_outdated` when a shipped skill changed since you
490
- copied it.
490
+ copied it. `--apply --customize-skill` takes a kernel lock on the override
491
+ directory, so concurrent runs cannot lose a recorded base.
491
492
  - A `builtin:` name with no override and no shipped skill makes Doctor report
492
493
  `skill_missing` and the session start fail with `skill_load_failed`.
493
494
  - An extension allowlist alone makes none of a package's skills available. If a
@@ -513,6 +514,11 @@ the new text. A package update that changes a shipped skill does not touch an
513
514
  override of yours; Doctor warns with `skill_override_outdated` so that you can
514
515
  review the change.
515
516
 
517
+ After an upgrade from 0.8.0, run `setup` as a dry-run and then with `--apply`
518
+ for your targets. Copies and records of 0.8.0 are proven before setup acts on
519
+ them; one that cannot be proven is reported as `skill_legacy_unverified` and
520
+ stays untouched. See [Upgrade from 0.8.0](skills.md#upgrade-from-080).
521
+
516
522
  ### npm installation
517
523
 
518
524
  An npm upgrade replaces the package in place. Close the client sessions that
@@ -263,6 +263,17 @@ and step 6 leaves no session to dispose.
263
263
  - **UI and flags.** No UI context is bound; extension UI calls return the
264
264
  SDK's no-op results. `extensionFlagValues` is empty, so extension flags keep
265
265
  their defaults.
266
+ - **Turn liveness.** The `updatedAt` of a running turn follows the public SDK
267
+ events of the prompt: assistant message start, update and end, and tool
268
+ execution start, update and end. An extension provider with its own agent
269
+ loop, such as `pi-cursor-sdk`, therefore advances `updatedAt` whenever it
270
+ publishes streaming updates. Its internal steps that publish no public
271
+ event stay invisible, so an unchanged `updatedAt` is no proof of a hang, and
272
+ streaming is no evidence of finished or useful work. Only the event type and
273
+ the message role are inspected; no delta, thinking block, tool argument or
274
+ partial result is stored or projected, and the answer still comes only from
275
+ the final assistant message. See [Tool
276
+ contracts](tool-contracts.md).
266
277
 
267
278
  ### Lifecycle and close
268
279
 
package/docs/releasing.md CHANGED
@@ -16,7 +16,13 @@ does not authorize any of them.
16
16
  - A change to the public contract needs a minor release and its own decision.
17
17
 
18
18
  The public contract is the set of eight tools with their fields, states and
19
- error codes, as defined in the [tool contracts](tool-contracts.md).
19
+ error codes, as defined in the [tool contracts](tool-contracts.md). How the
20
+ package version, the MCP tool contract and the setup report's `schemaVersion`
21
+ differ is defined under [Versions](tool-contracts.md#versions).
22
+
23
+ Wherever this document asks for the Level 1 checks, the test step is
24
+ `NODE_OPTIONS=--unhandled-rejections=strict npm test`. CI uses the same
25
+ setting, so an unhandled promise rejection fails the run locally and in CI.
20
26
 
21
27
  A release is distributed twice: as the GitHub release with its source archive,
22
28
  and as the npm package `pi-session-mcp`. The npm package contains the built
@@ -131,9 +137,15 @@ Patch transitive advisories in the lockfile only:
131
137
 
132
138
  ## Prepare a release
133
139
 
140
+ A release travels in the pull request of the change it ships. That pull
141
+ request also carries the version, the changelog heading and the pre-release
142
+ acceptance record; there is no separate release pull request. Records that can
143
+ only be made after publishing go into the pull request of the next change.
144
+
134
145
  Preconditions:
135
146
 
136
- - `main` contains every change of the release, and Level 1 is green on `main`.
147
+ - The change pull request contains every change of the release, and Level 1
148
+ is green on its head.
137
149
  - `npm audit` reports 0 vulnerabilities.
138
150
  - The public contract is unchanged, or a minor release has been decided.
139
151
  - The acceptance that this release requires is recorded, or its absence is
@@ -141,8 +153,9 @@ Preconditions:
141
153
 
142
154
  Steps:
143
155
 
144
- 1. Create a release branch from the current `main`. Never reuse a release branch
145
- that predates later merges: its lockfile would undo later dependency patches.
156
+ 1. Bring the change branch up to date with `main` if `main` moved. Never reuse
157
+ a branch that predates later merges without that: its lockfile would undo
158
+ later dependency patches.
146
159
  2. Set the version:
147
160
 
148
161
  ```bash
@@ -159,11 +172,13 @@ Steps:
159
172
  5. Run the working-tree
160
173
  [Level 2 acceptance](acceptance.md#tooling) on the release candidate with
161
174
  `scripts/setup-acceptance.mjs`, unless the release notes state its absence.
162
- 6. Open one pull request named `chore: prepare v<x.y.z> release`.
175
+ 6. Record the pre-release acceptance in the status section of
176
+ [Acceptance](acceptance.md) and push it to the change pull request. Name the
177
+ release in the pull request's title and description.
163
178
 
164
179
  ## Publish a release
165
180
 
166
- After the release pull request is merged and CI is green on the merge commit:
181
+ After the change pull request is merged and CI is green on the merge commit:
167
182
 
168
183
  ```bash
169
184
  git switch main
@@ -217,9 +232,9 @@ After publishing:
217
232
 
218
233
  1. Run the published-release
219
234
  [Level 2 acceptance](acceptance.md#working-tree-and-published-release-variants)
220
- from a fresh clone of the tag and from the published npm package, and
221
- record it. `scripts/setup-acceptance.mjs` runs both
222
- ([Tooling](acceptance.md#tooling)).
235
+ from a fresh clone of the tag and from the published npm package.
236
+ `scripts/setup-acceptance.mjs` runs both ([Tooling](acceptance.md#tooling)).
223
237
  2. Upgrade the installation as described in
224
238
  [Installation](installation.md#upgrade).
225
- 3. Update the status section in [Acceptance](acceptance.md).
239
+ 3. Record both in the status section of [Acceptance](acceptance.md) with the
240
+ pull request of the next change, not in a pull request of their own.