pi-session-mcp 0.14.0 → 0.14.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 (41) hide show
  1. package/.pi-session-mcp-cli/writer-boundary.js +2 -1
  2. package/.pi-session-mcp-cli/writer-lifecycle.js +99 -18
  3. package/.pi-session-mcp-cli/writer-models-store.js +64 -0
  4. package/.pi-session-mcp-cli/writer-tree-removal.js +75 -0
  5. package/CHANGELOG.md +95 -0
  6. package/README.md +1 -1
  7. package/dist/capabilities.d.ts +13 -0
  8. package/dist/capabilities.js +17 -13
  9. package/dist/model-catalog.d.ts +18 -5
  10. package/dist/model-catalog.js +8 -5
  11. package/dist/sdk-pi-adapter.d.ts +6 -1
  12. package/dist/sdk-pi-adapter.js +35 -7
  13. package/dist/writer-boundary.d.ts +1 -1
  14. package/dist/writer-boundary.js +2 -1
  15. package/dist/writer-guard.js +1 -1
  16. package/dist/writer-launch.d.ts +2 -0
  17. package/dist/writer-launch.js +3 -0
  18. package/dist/writer-lifecycle.d.ts +8 -0
  19. package/dist/writer-lifecycle.js +99 -18
  20. package/dist/writer-models-store.d.ts +16 -0
  21. package/dist/writer-models-store.js +64 -0
  22. package/dist/writer-relay.js +12 -1
  23. package/dist/writer-tree-removal.d.ts +16 -0
  24. package/dist/writer-tree-removal.js +75 -0
  25. package/docs/acceptance.md +314 -0
  26. package/docs/adr/0012-leased-writer-pool.md +10 -6
  27. package/docs/adr/0013-hardened-read-only-profiles.md +30 -0
  28. package/docs/adr/0014-profile-probe-and-model-catalog.md +1 -1
  29. package/docs/doctor-design.md +1 -1
  30. package/docs/execution-modes.md +66 -10
  31. package/docs/installation.md +9 -0
  32. package/docs/isolation-setup.md +12 -2
  33. package/docs/roadmap.md +21 -0
  34. package/docs/sprints/2026-10-24-s8-writer-migration.md +5 -3
  35. package/docs/sprints/2026-10-29-s12-audit-fixes.md +174 -0
  36. package/docs/sprints/2026-10-30-s13-index-only-work.md +199 -0
  37. package/docs/threat-model.md +10 -1
  38. package/docs/tool-contracts.md +1 -1
  39. package/package.json +1 -1
  40. package/skills/pi-session-orchestration/SKILL.md +1 -1
  41. package/skills/pi-session-profiles/SKILL.md +5 -1
@@ -0,0 +1,174 @@
1
+ # Sprint 12: audit findings F1–F3
2
+
3
+ Planning basis: main `9d9cf8ca49e6e34dc16be6846b9324ad26ad92b9` (merge of PR
4
+ #191), published 0.14.0, 2026-10-29. Entry: an external audit of that commit
5
+ reported three defects with reproductions. This file plans the work and records
6
+ it as it lands; it claims nothing that its progress log does not state with
7
+ evidence.
8
+
9
+ Branch: `fix/s12-audit-findings`, one pull request, no release.
10
+
11
+ ## Preflight (2026-10-29)
12
+
13
+ - main and `origin/main` at `9d9cf8c`; open issues #79, #120, #144; no open
14
+ pull requests; clean operator checkout; no other worktree in use.
15
+ - Doctor: no errors; only the known `workspace_gitfile` and
16
+ `workspace_mode_unset` warnings of the host worktree aliases.
17
+ - Level 1 on `9d9cf8c` in a clean detached worktree on the workstation:
18
+ `npm ci`, `npm run typecheck`, `npm run build:cli`,
19
+ `NODE_OPTIONS=--unhandled-rejections=strict npm test` (527 files, 4005
20
+ passed, 14 skipped, 0 failed, no unhandled error) and `npm run build` all exit
21
+ 0. The audit's cloud run of the same commit reported 70 failed tests and four
22
+ unhandled errors; they do not reproduce here, so they are not attributed to
23
+ the product. The audit environment is not available to separate them further.
24
+ - Both `pi-session-mcp` writers were at `baseline` on an older main and were
25
+ reset without `--discard` to `9d9cf8c` (`writer_reset`).
26
+
27
+ ## Findings, re-checked on `9d9cf8c`
28
+
29
+ | Id | Issue | Reproduction by the controller | Result |
30
+ | --- | --- | --- | --- |
31
+ | F1 | [#192](https://github.com/plagor44-ux/pi-session-mcp/issues/192) | Synthetic workspace whose `AGENTS.md` links to an outside marker file; `PI_CODING_AGENT_DIR` isolated; faux provider | Confined `read` refuses; `getAgentsFiles()` contains the marker; the real `SdkPiSessionAdapter` sends it as `project_instructions` to the faux provider. Confirmed. |
32
+ | F2 | [#193](https://github.com/plagor44-ux/pi-session-mcp/issues/193) | Real Git; boundary scripts run by an injected runner | Detached `HEAD` commit, clean tree: remove before collect `writer_uncollected`; collect `nothing_to_collect`, 0 refs, digest stored; commit absent from the operator checkout; finish `writer_reset`; remove `writer_removed`. Feature-branch counterproof exports the commit. Confirmed; not yet a Podman result. |
33
+ | F3 | [#194](https://github.com/plagor44-ux/pi-session-mcp/issues/194) | Real relay over stdio, fake writer server fixture | Identical profiles admitted; model drift refused with `settingsChanged`; host `skills: ["pi-session-implementation"]` against inner `skills: []` admitted. Confirmed. |
34
+
35
+ ## Decisions taken at planning
36
+
37
+ - F1: for read-only profiles every resource loader keeps only context files
38
+ whose real path lies inside the workspace's real path. Context files of
39
+ ancestor directories above the workspace, including a main repository above a
40
+ linked worktree, are no longer loaded for read-only profiles: they are outside
41
+ the read roots of ADR 0013. Coding profiles keep the SDK's behaviour.
42
+ - F2: smallest fix inside the existing collection contract; the export name for
43
+ a detached `HEAD` is a documented behaviour addition, no new public code.
44
+ - F3: the relay compares every public capability field a writer reports, with
45
+ set semantics for skills, extensions and granted extension tools.
46
+ - #120: still waiting for an SDK release (see below); no override, no
47
+ `npm audit fix --force`.
48
+ - No release on this branch.
49
+
50
+ ## Roles and routing
51
+
52
+ `pi_capabilities_get` and `--list-models` on 2026-10-29 settle which requested
53
+ models exist as authenticated profiles:
54
+
55
+ | Requested | Profile used | Status |
56
+ | --- | --- | --- |
57
+ | GPT 6.1 Sol | `code` (openai `gpt-6.1-sol`) | available |
58
+ | GPT 6.1 Terra | none | unavailable: the catalog lists no `gpt-6.1-terra`; not substituted with another version |
59
+ | DeepSeek | `code-deepseek` (`deepseek-flash`) | available |
60
+ | GLM 5.3 | `review-glm`, `code-glm` (`zai-glm-5-3`) | available |
61
+ | Mistral Large 4 | `code-mistral` (`mistral-large-4`) | available |
62
+ | Sonnet 5.5 | no profile; provider `anthropic` authenticated | added only in an expendable test configuration (acceptance item D) |
63
+ | Grok 4.7 | `review-grok` (xai `grok-4.7`) | available: the R2 turn completed |
64
+
65
+ agy (Antigravity CLI, Gemini) plans the work packages, dependencies and review
66
+ routing; Pi workers implement and review; the controller integrates, verifies
67
+ and is the only editor of this file, the roadmap, `docs/acceptance.md` and the
68
+ CHANGELOG.
69
+
70
+ ## Tasks
71
+
72
+ | Id | Task | Route | Change area |
73
+ | --- | --- | --- | --- |
74
+ | P1 | Work packages, dependencies, review routing | agy | none |
75
+ | P2 | Independent regression test plan | `review-glm` | none |
76
+ | T1 | F1, #192 | `pi-session-mcp` writer, `code` | `src/sdk-pi-adapter.ts`, `src/confined-tools.ts`, tests |
77
+ | T2 | F2, #193 | `pi-session-mcp` writer, `code-mistral` (agy's fallback for the unavailable Terra) | `src/writer-lifecycle.ts`, tests |
78
+ | T3 | F3, #194 | `pi-session-mcp` writer, `code-deepseek` | `src/writer-relay.ts`, `src/capabilities.ts`, tests, relay fixture |
79
+ | R1 | Security review of T1 and T3 | Sonnet 5.5, read-only | none |
80
+ | R2 | Adversarial review of T2 and the Git lifecycle checks | `review-grok` | none |
81
+ | D1 | Documentation and issue reconciliation (#120, ADR 0014 status, Sprint 8 status, #79, #144) | `pi-session-mcp` writer, `code-mistral`; controller as editor | `docs/`, `skills/pi-session-orchestration/SKILL.md` |
82
+ | A | Live acceptance A–E | controller | evaluation record |
83
+
84
+ ## Progress log
85
+
86
+ Recorded as work lands on the branch.
87
+
88
+ - 2026-10-29: issues #192, #193 and #194 filed; this plan written before any
89
+ implementation.
90
+ - P1: agy (`gemini-3.8-flash-high`, plan mode) proposed the packages,
91
+ `code-mistral` as the F2 author in place of Terra, Sonnet for R1 and Grok
92
+ with a fallback for R2, and at most three review rounds. Adopted.
93
+ - P2: `review-glm` wrote the regression test plan; its edge cases (absent
94
+ against empty set fields, real-Git collection, a Podman case) went into the
95
+ task prompts.
96
+ - T3 (`code-deepseek`, writer w2): collected as
97
+ `writer-pi-session-mcp-w2/fix/f3-relay-profile-drift`; the controller ran the
98
+ new tests against the old source (7 fail) and the fix (all pass); integrated
99
+ as `230e60e`, writer finished (`writer_reset`).
100
+ - T1 (`code`, writer w1): collected as
101
+ `writer-pi-session-mcp-w1/fix/f1-context-files`; new tests against the old
102
+ source 9 of 12 fail, with the fix 148 of 148 pass; integrated as `a0a7d87`,
103
+ writer finished. The container-writer suite showed only the eight known
104
+ lock and hook timeouts (`override-lock`, `skill-customize-concurrency`,
105
+ `wake-hook`).
106
+ - R1 (Sonnet 5.5, read-only, through the test configuration of item D):
107
+ `NON_BLOCKING_ONLY`. Fixed by the controller: kept context files are read
108
+ again from their verified real path (`79e8a7a`), the set comparison is
109
+ symmetric with a duplicated-entry test (`3e14279`); the shadowing case is
110
+ covered by a test and documented. Not changed: synchronous `realpathSync` in
111
+ the loader override (the SDK hook is synchronous) and the trust in the
112
+ writer's own capability report, which is the boundary the relay already
113
+ assumes.
114
+ - T2 (`code-mistral`, writer w2): collected as
115
+ `writer-pi-session-mcp-w2/fix/f2-detached-collect`; the new real-Git tests
116
+ against the old source 3 of 8 fail, with the fix 8 of 8 pass, and the F2
117
+ reproduction now gets `writer_collected`; integrated as `236ca28`, writer
118
+ finished. The controller added the Podman case (`6ee7add`): with real
119
+ rootless Podman and the pinned Node image it passes, against the old source
120
+ it fails.
121
+ - D1 (`code-mistral`, writer w1): collected as
122
+ `writer-pi-session-mcp-w1/docs/s12-reconcile`; split by issue into
123
+ `f5a9712`, `58e0e7b`, `17845ff` and `ebe3c52`. Controller edits: the
124
+ shadowing and re-read rule in the ADR 0013 amendment, and the relay wording
125
+ (a mismatch counts as `settingsChanged`; `writer_unavailable` with
126
+ `none_admitted` only when no writer is admitted). Writer finished.
127
+ - R2 (`review-grok`, Grok 4.7, read-only): `NON_BLOCKING_ONLY`. Verified by
128
+ the controller and fixed in `1dfe4da`: a derived `-wip` or `-detached` name
129
+ that conflicts with an exported branch path aborted the export under
130
+ `set -eu` (reproduced with a branch `main-detached/topic`); a worker-made
131
+ `git replace --graft` made an unexported detached commit look reachable
132
+ (Grok's variant with a plain replacement did not reproduce, the graft did);
133
+ the reachability gate now also runs on `nothing_to_collect`. Recorded as
134
+ limits, not changed: an index-only staged change that the worktree reverted
135
+ (pre-existing; fixed later in
136
+ [#200](https://github.com/plagor44-ux/pi-session-mcp/issues/200), see
137
+ [Sprint 13](2026-10-30-s13-index-only-work.md)), a base name of 192 to 200 characters (fails closed), and a
138
+ runner that reports a fetch it did not do (outside the threat model).
139
+ - Final integration review (Sonnet 5.5, read-only, on `896e29d`):
140
+ `NON_BLOCKING_ONLY`. Fixed: the in-boundary guard probe now ignores replace
141
+ refs too (`9426682`, with a test that fails without it), the BOM pattern is an
142
+ escape (`5d9f15b`), and execution modes names the pruned-base case of the
143
+ digest gate. Not changed: `inspectWork.head` stays optional because required
144
+ would break type-checked mocks outside this change.
145
+ - Acceptance: see
146
+ [Sprint 12 candidate acceptance](../acceptance.md#sprint-12-candidate-acceptance-2026-10-30).
147
+ Level 1 green on `9426682`; Level 2 green on the packed `896e29d`; live items
148
+ B, C and D passed on expendable configurations and were rolled back; the
149
+ Level 3 ten-task cycle 2 passed (10 of 10) after cycle 1 failed on fixture and
150
+ harness design (5 of 10, kept unchanged).
151
+
152
+ ## Results
153
+
154
+ | Finding | Status | Commits | Evidence |
155
+ | --- | --- | --- | --- |
156
+ | F1 #192 | fixed | `a0a7d87`, `79e8a7a`, `5d9f15b`; docs `f5a9712` | `test/read-only-context-files.test.ts` (fails on `9d9cf8c`), faux-provider context check, R1 and final review |
157
+ | F2 #193 | fixed | `236ca28`, `6ee7add`, `1dfe4da`, `9426682`; docs `58e0e7b`, `896e29d` | real-Git collect tests, Podman test, guard test (each fails on the source before), R2 and final review |
158
+ | F3 #194 | fixed | `230e60e`, `3e14279`; docs `17845ff` | `test/writer-relay.test.ts` drift tests (fail on `9d9cf8c`), live refusal with `settingsChanged`, R1 and final review |
159
+
160
+ Remaining risk: #120 is unchanged. `npm audit --omit=dev` on 2026-10-29 still
161
+ reports `undici` 8.10.2 (high) through `@earendil-works/pi-coding-agent` 1.4.0
162
+ (moderate), eight advisories fixed in `undici` >= 8.12.2; the only offered fix
163
+ is a semver-major downgrade. No override and no `npm audit fix --force`.
164
+
165
+ Kept open: #79 (Sprint 8 steps 5 to 7 are not done), #120 and #144 (no macOS
166
+ claim). The sandbox resource limits of ADR 0013 are the follow-up issue
167
+ [#195](https://github.com/plagor44-ux/pi-session-mcp/issues/195), not part of
168
+ this sprint.
169
+
170
+ Cleanup: all controller Pi sessions closed; both `pi-session-mcp` writers
171
+ finished (`writer_reset`); the test writer `s12-probe-w1` removed; expendable
172
+ configurations, the synthetic repository, the pack prefix and the
173
+ controller's worktrees removed.
174
+
@@ -0,0 +1,199 @@
1
+ # Sprint 13: work that exists only in the writer index
2
+
3
+ Planning basis: main `7752d3f1555d19fed06f43c0e046da07017a3320` (merge of PR
4
+ #197), published 0.14.1, 2026-10-30. Entry: issue
5
+ [#200](https://github.com/plagor44-ux/pi-session-mcp/issues/200), recorded as
6
+ an unchanged limit in [Sprint 12](2026-10-29-s12-audit-fixes.md), and the
7
+ published-release Level 2 of 0.14.1 that was still open. This file records the
8
+ work as it landed; it claims nothing that its progress log does not state with
9
+ evidence.
10
+
11
+ Branch: `fix/writer-index-only-work`, one pull request, no release. Merge,
12
+ tag, npm publication and the productive rollout are maintainer decisions.
13
+
14
+ ## Preflight (2026-10-30)
15
+
16
+ - main and `origin/main` at `7752d3f`; open issues #79, #120, #144, #195,
17
+ #198, #199, #200; open pull request #201 (#198, #199).
18
+ - Both machines run `0.14.1`; on the workstation both client targets verified
19
+ and all 18 writers accepted earlier the same day.
20
+ - Reproduced on `7752d3f` with the real-Git harness: for a clone whose HEAD and
21
+ worktree hold `base` and whose index holds a staged blob (`git status` `MM`),
22
+ `inspectWork` reported `baseline: true, dirty: false`, collect reported
23
+ `nothing_to_collect`, and remove and finish without `--discard` destroyed
24
+ the staged blob.
25
+
26
+ ## Decisions
27
+
28
+ - Inspection computes the original index tree with `write-tree` on a second
29
+ temporary copy of the index, before `git add -A` builds the worktree tree.
30
+ The clone stays read-only and its index is never written.
31
+ - `baseline` additionally requires the index tree to equal the base tree;
32
+ `dirty` is set when the index tree or the worktree tree differs from HEAD's.
33
+ - The collection digest is computed over a version tag and the inspection
34
+ output, which now includes the index tree. A digest recorded by 0.14.1 or
35
+ earlier can never match, so it never authorizes cleanup.
36
+ - Collection exports a staged tree that differs from both HEAD's tree and the
37
+ worktree tree as an extra `<branch>-index` commit, with the name rules of the
38
+ existing `-wip` export, rather than refusing: a refused writer could not be
39
+ admitted to a session or collected, so its work would only be recoverable by
40
+ `--discard`. The `-wip` and `-detached` exports are unchanged (#193).
41
+ - A digest is recorded only when the index and worktree tree objects exist in
42
+ the operator checkout, in addition to the #193 commit gate.
43
+ - An index that cannot be evaluated, such as an unmerged conflict, makes
44
+ inspection and collection fail closed (`writer_inspect_failed`,
45
+ `writer_collect_failed`); no cleanup without `--discard` proceeds.
46
+ - Pool admission needs no change: the in-boundary guard probe runs
47
+ `git status --porcelain=v1 -uall`, which reports staged changes. Doctor's
48
+ `writer_dirty` stays an offline ref-only hint and never authorizes cleanup;
49
+ its documentation now says so.
50
+ - #120: still waiting for an SDK release; no override, no
51
+ `npm audit fix --force`.
52
+
53
+ ## Roles and routing
54
+
55
+ `pi_capabilities_get` on 2026-10-30 settles which requested models were
56
+ available as profiles:
57
+
58
+ | Requested | Profile used | Status |
59
+ | --- | --- | --- |
60
+ | GPT 6.1 Terra (implementation) | `code` (openai `gpt-6.1-sol`) | Terra has no profile: the installed 0.14.1 catalog does not see Pi's persisted model overlay (#199, fixed in #201, unreleased); the requested fallback Sol was used |
61
+ | Sol (call-path analysis) | done by the controller | the call paths were short (one function and its five callers) |
62
+ | DeepSeek (real-Git fixtures) | `code-deepseek` (`deepseek-flash`) | available |
63
+ | GLM 5.3 (test plan, counter-checks) | `review-glm` (`zai-glm-5-3`) | available |
64
+ | Grok 4.7 (adversarial review) | `review-glm` | not used: xAI requests fail with HTTP 402 since 2026-10-25 |
65
+ | Mistral Large 4 (release evidence, docs) | controller with a Claude runner subagent | the evidence needed only local commands; the controller is the only docs editor |
66
+ | Sonnet 5.5 (final integration review) | a read-only Claude Code subagent on `claude-sonnet-5-5` | used instead of `review-sonnet`, because a read-only Pi worker reads only the workspace on `main`, not the branch worktree |
67
+
68
+ agy (Antigravity CLI, Gemini) was not used: the controller's planning call was
69
+ declined in this session, so the controller planned the packages and the
70
+ review route. At most two coding workers ran at once; `src/writer-lifecycle.ts`
71
+ had one implementer, and `test/writer-collect-real-git.test.ts` one author.
72
+
73
+ ## Tasks
74
+
75
+ | Id | Task | Route | Change area |
76
+ | --- | --- | --- | --- |
77
+ | P1 | Design counter-check and regression test plan | `review-glm` | none |
78
+ | T1 | Fix in inspection, collection and the digest | `pi-session-mcp` writer, `code` | `src/writer-lifecycle.ts`, unit tests and fakes |
79
+ | T2 | Real-Git regression matrix | `pi-session-mcp` writer, `code-deepseek` | `test/writer-collect-real-git.test.ts` |
80
+ | R1 | Adversarial review of the integrated diff | `review-glm` | none |
81
+ | R2 | Final integration review | Sonnet 5.5 subagent, read-only | none |
82
+ | A | Published-release Level 2 of 0.14.1, candidate Level 1 and 2, native Podman | controller, runner subagent | records |
83
+
84
+ ## Progress log
85
+
86
+ - P1 (`review-glm`, 1m32s): `NON_BLOCKING_ONLY`. Its main finding, that
87
+ `write-tree` fails on intent-to-add entries, did not reproduce: Git 2.56
88
+ skips such entries, so the case shows as dirty through the worktree tree. It
89
+ is now pinned by a test. Skip-worktree and assume-unchanged entries stay
90
+ invisible, as for Git itself (recorded as a limit).
91
+ - T1 (`code`, writer w1, 4m56s): `DONE_WITH_CONCERNS`; the concerns were the
92
+ ten known lock, hook and live-token timeouts of the container suite
93
+ (`override-lock`, `skill-customize-concurrency`, `token-live`,
94
+ `wake-hook`). Collected as `writer-pi-session-mcp-w1/fix/200-index-only-work`
95
+ and integrated as `9b5c72c`; writer finished (`writer_reset`).
96
+ - T2 (`code-deepseek`, writer w2, 5m00s): `DONE`, 7 of its 10 new tests fail on
97
+ the base for the bug, 3 are guards. Collected as
98
+ `writer-pi-session-mcp-w2/test/200-index-only-real-git` and integrated as
99
+ `4acb4bf`; writer finished (`writer_reset`).
100
+ - Controller: against the source of `7752d3f` the real-Git file reports 7
101
+ failed and 13 passed; with the fix, 21 of 21 pass. Added the intent-to-add
102
+ test (`1aff988`) and a native Podman case of the exact `MM` state
103
+ (`1e1e694`), which fails on the base source (`baseline: true`) and passes
104
+ with the fix in the real container.
105
+ - R1 (`review-glm`, 4m59s, on the integrated diff): `NON_BLOCKING_ONLY`, no
106
+ finding in the new lines.
107
+ - Confirmed and filed as
108
+ [#202](https://github.com/plagor44-ux/pi-session-mcp/issues/202): a stash
109
+ entry is not exported, yet collection records a digest and remove without
110
+ `--discard` then deletes it; reproduced by the controller with the real-Git
111
+ harness (`nothing_to_collect`, `writer_removed`, blob absent). Pre-existing.
112
+ - Not reproduced: an alias that shadows a builtin Git command; Git ignores
113
+ such aliases. A clean filter configured by the worker runs only inside the
114
+ throwaway boundary container, and the worker can already destroy its own
115
+ clone, so it adds no loss beyond the threat model.
116
+ - Fail closed, recorded as limits: a clone configured for a split index makes
117
+ `add -A` fail on the read-only clone; a worker branch named
118
+ `<branch>-index` makes the export refuse with `ref_name_refused`.
119
+ - R2 (Sonnet 5.5, read-only, on the whole branch at `4670264`):
120
+ `NON_BLOCKING_ONLY`, no code defect. Fixed by the controller:
121
+ - The test for an index-only change after a collection also changed the
122
+ worktree, so it did not pin that the digest covers the index tree. It now
123
+ stages a second blob with `update-index --cacheinfo` and leaves the
124
+ worktree unchanged; with a digest that omits the index line it fails, with
125
+ the fix it passes.
126
+ - A test for a taken and a path-conflicting `<branch>-index` name.
127
+ - The code table in execution modes, the `pi-session-profiles` skill (Doctor
128
+ and the `--collect` dry run do not prove a clone clean; `--remove` without
129
+ `--discard` does), the unmerged-index consequence, the merge note and a
130
+ wrong direction in the acceptance record.
131
+ - Not changed: the verbatim issue reproduction stays as written although the
132
+ stricter collect, finish and remove test covers more; Doctor's
133
+ `writer_dirty` stays a documented offline ref-only hint (a maintainer
134
+ call against the issue's criterion 3).
135
+
136
+ ## Checks
137
+
138
+ - Full suite on `1aff988` (the integrated fix and the intent-to-add test),
139
+ with `NODE_OPTIONS=--unhandled-rejections=strict`: 141 files, 4061 passed,
140
+ 14 skipped, exit 0.
141
+ - Native Podman tests: `test/writer-lifecycle.podman.test.ts` 8 of 8 and
142
+ `test/writer-boundary.podman.test.ts` 1 of 1, none skipped, on `1e1e694`.
143
+ - Candidate Level 1, working-tree and packed-tarball Level 2 on `1e1e694`: see
144
+ [Issue #200 candidate acceptance](../acceptance.md#issue-200-candidate-acceptance-2026-10-30).
145
+ - Published-release Level 2 of 0.14.1: see
146
+ [v0.14.1 post-release evidence](../acceptance.md#v0141-post-release-evidence).
147
+
148
+ ## Remaining risk and limits
149
+
150
+ - Work held only in a stash entry or in refs outside `refs/heads` is still lost
151
+ after a collection (#202).
152
+ - Skip-worktree and assume-unchanged entries hide worktree changes from Git and
153
+ therefore from inspection.
154
+ - The digest version means that a writer collected under 0.14.1 but not yet
155
+ reset must be collected once more before finish, reset or remove. Writers at
156
+ their baseline need no digest and are unaffected.
157
+ - The 18 workstation writers removed on 2026-10-30 for a profile change were
158
+ judged by the 0.14.1 inspection; whether any of them held index-only work
159
+ cannot be established afterwards.
160
+ - #120 is unchanged: Pi SDK 1.4.0 is still the newest release and pins `undici`
161
+ 8.10.2.
162
+
163
+ ## Rollout plan (maintainer decision)
164
+
165
+ After the maintainer has merged and published a release with this change:
166
+
167
+ 1. Close the coding sessions on all writers. For every writer that is not at
168
+ its baseline, run `pi-session-mcp-setup --writer <name> --collect` as a dry
169
+ run and resolve uncollected work before anything else.
170
+ 2. Upgrade each machine as [Installation](../installation.md#container-writers)
171
+ describes: client targets `--remove`, `npm install -g pi-session-mcp@<new>`,
172
+ then for every container repository the provisioning command with its full
173
+ settings and `--apply`, then client targets `--apply` and `--verify`,
174
+ `--writer <name> --verify` for every writer, and Doctor.
175
+ 3. Collect every writer that holds collected but not yet reset work once more,
176
+ because its 0.14.1 digest no longer authorizes cleanup.
177
+ 4. Second machine: the Anthropic profiles still need the maintainer's login
178
+ there. Until #198 (in #201) is released, removing a writer with
179
+ `--cache go` needs the documented `chmod -R u+w` workaround.
180
+
181
+ Rollback: a productive installation is not downgraded without the maintainer.
182
+ If the maintainer decides to return to `0.14.1`, the same steps install it; a
183
+ digest recorded by the new version does not match `0.14.1` either, so such a
184
+ writer has to be collected again, which fails safe. The source change itself is
185
+ reverted by reverting its commits.
186
+
187
+ Merge order with #201: both change `src/writer-lifecycle.ts` (#201 replaces the
188
+ recursive removal in `removeWriter`), `CHANGELOG.md` (`Unreleased`) and
189
+ `docs/execution-modes.md`, and #201's `test/writer-models-store.test.ts` fakes
190
+ the old three-tree inspection output. #201 merged first; the merge of `main`
191
+ into this branch keeps both `Unreleased` entries and adds the index tree line
192
+ to that fake.
193
+
194
+ Unmerged index: a writer whose index holds a merge conflict can no longer be
195
+ collected, and pool admission refuses it because it is not at its baseline; its
196
+ work stays in the clone until the operator explicitly decides on `--discard`.
197
+ Before this change `git add -A` collected such a state as `-wip` with conflict
198
+ markers. Exporting the worktree in that case without recording a digest would
199
+ keep it recoverable; that is a possible follow-up, not part of this change.
@@ -16,7 +16,16 @@ Pi Session MCP trusts the local process that launches and speaks MCP over inheri
16
16
  (arguments the SDK would rewrite, such as a leading `@` or `~`, a `file:`
17
17
  URL, Unicode spaces or NUL, are refused so both resolve the same path), and
18
18
  the `*Operations` hooks of `read`, `grep` and `ls` check the absolute path
19
- again. `rg` and `fd` do not follow symlinks during traversal. A profile with
19
+ again. `rg` and `fd` do not follow symlinks during traversal. For read-only
20
+ profiles the resource loader likewise keeps only Pi context files
21
+ (`AGENTS.override.md`, `AGENTS.md`, `AGENTS.MD`, `CLAUDE.md`, `CLAUDE.MD`)
22
+ whose real path lies inside the workspace's real path, on every reload and
23
+ before the first provider request: a symlinked context file that resolves
24
+ outside the workspace, a dangling one and the context files of ancestor
25
+ directories above the workspace (including a main repository above a linked
26
+ worktree) are not loaded, while the linked worktree's own file inside the
27
+ workspace is. Coding profiles keep the SDK behaviour (the working directory
28
+ and its ancestors). A profile with
20
29
  `shell: "sandboxed"` also gets `bash`, run as `/usr/bin/bwrap` (fixed path,
21
30
  root-owned and not group- or world-writable, no `PATH` lookup) with
22
31
  `--unshare-all`, `--disable-userns`, a cleared environment, no network, `/usr`
@@ -40,7 +40,7 @@ records name the package version and the build they ran on, see
40
40
  ```json
41
41
  {
42
42
  "ok": true,
43
- "server": { "name": "pi-session-mcp", "version": "0.14.0" },
43
+ "server": { "name": "pi-session-mcp", "version": "0.14.2" },
44
44
  "configuration": {
45
45
  "fingerprint": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
46
46
  "reloadPolicy": "restart-required"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-session-mcp",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Local stdio MCP server for controlled Pi coding-agent sessions",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -122,7 +122,7 @@ You integrate. Workers never integrate each other's changes. Resolve conflicts b
122
122
 
123
123
  After taking over the result and closing its coding session, before the next task:
124
124
 
125
- 1. Run `pi-session-mcp-setup --writer <name> --collect --apply` from the operator environment. Review the collected refs in the operator's checkout: `refs/remotes/writer-<name>/<branch>`, with uncommitted work as `<branch>-wip`. They are untrusted until reviewed. Integrate only verified work yourself.
125
+ 1. Run `pi-session-mcp-setup --writer <name> --collect --apply` from the operator environment. Review the collected refs in the operator's checkout: `refs/remotes/writer-<name>/<branch>`, with uncommitted work as `<branch>-wip`, staged work that differs from both HEAD and the worktree as `<branch>-index`, a clean detached HEAD as `<base>-detached`, and each stash entry as `<base>-stash-<n>`. They are untrusted until reviewed. Integrate only verified work yourself.
126
126
  2. Run `pi-session-mcp-setup --writer <name> --finish --apply`. It collects again, then resets a persistent writer or recreates an ephemeral writer. Do not reuse it until this succeeds. A refused branch name makes collection incomplete and finish stops: ask the operator to resolve it by renaming inside the boundary or explicitly discarding with `--reset --discard --apply`. Finish returns it to the workspace default base. No reconnect is needed after reset, finish or recreate. A setup operation on a writer held by a session fails with `writer_busy` after a short wait; close the session and allow launcher cleanup to finish.
127
127
  3. A coding start needs the writer's clone at its baseline, even after a complete collection; otherwise it fails with the controller-action code for uncollected work in [references/start-errors.md](references/start-errors.md). To let a fresh session continue from collected work, for example after a failed turn or for a review fix round, create a local branch at the collected ref and run `pi-session-mcp-setup --writer <name> --reset --base <branch> --apply`, then call `pi_session_start` on `pi-session-mcp` with the coding profile and `base: "<branch>"`. Read `writer` from that result for collection. Default starts cannot use a writer moved to the fix branch.
128
128
  4. Never run Git inside the writer's clone as the host user, including direct fetch from that clone. Use setup's bundle collection and inspect the imported refs in your own checkout. Never discard unreviewed work just to admit the next task.
@@ -84,8 +84,12 @@ For each container repository whose selected profiles or settings change,
84
84
  determine every writer's state using the documented read-only checks:
85
85
  Doctor's `writer_dirty` and `writer_uncollected_bundle`, and
86
86
  `pi-session-mcp-setup --writer <name> --collect` (dry run).
87
+ Neither proves a clone clean: Doctor reads only `HEAD` and refs, and the dry
88
+ run inspects nothing. The authoritative check is `--remove` without
89
+ `--discard`, which inspects the clone, including its index, and refuses with
90
+ `writer_uncollected` when it holds work.
87
91
  If a writer to remove or re-provision has uncollected work or a dirty clone,
88
- stop and ask before anything goes live. Never run `--collect --apply` in this
92
+ or `--remove` refuses, stop and ask before anything goes live. Never run `--collect --apply` in this
89
93
  workflow, and never discard work to get past a refusal.
90
94
 
91
95
  Stop and report `writer_busy`, `podman_unavailable`, `registration_divergent`