pi-session-mcp 0.14.1 → 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.
@@ -225,18 +225,22 @@ Git metadata can redirect reads to worker-controlled configuration. Setup checks
225
225
  clone layout and runs clone-side Git only in throwaway no-network containers.
226
226
  Collect with `--writer <name> --collect --apply`, then review
227
227
  `refs/remotes/writer-<name>/<branch>` in your own checkout, with dirty work at
228
- `<branch>-wip` and a clean detached HEAD at `<base>-detached` (for example
229
- `main-detached`). Imported refs are untrusted until reviewed.
228
+ `<branch>-wip`, staged work that differs from both HEAD and the worktree at
229
+ `<branch>-index`, a clean detached HEAD at `<base>-detached` (for example
230
+ `main-detached`), and every stash entry at `<base>-stash-<n>`. Tags, notes and
231
+ other refs are not exported; one that names an object only the clone has makes
232
+ collection incomplete. Imported refs are untrusted until reviewed.
230
233
 
231
234
  After taking over the result and closing its coding session, run
232
235
  `pi-session-mcp-setup --writer <name> --finish --apply` before another task.
233
236
  It collects then resets (persistent) or recreates (ephemeral). If any branch name
234
- was refused, collection is incomplete and finish stops; a `<base>-detached` or
235
- `<branch>-wip` name that an exported branch takes or whose path it conflicts
237
+ was refused, collection is incomplete and finish stops; a `<base>-detached`,
238
+ `<branch>-wip`, `<branch>-index` or `<base>-stash-<n>` name that an exported branch takes or whose path it conflicts
236
239
  with is refused the same way. The release digest is
237
- recorded only when the clone's HEAD commit and every branch commit exist in the
238
- operator checkout, otherwise collection is `writer_collect_incomplete` and no
239
- digest is recorded. Rename inside the writer
240
+ recorded only when the clone's HEAD commit, every branch commit and every stash
241
+ commit exist in the operator checkout and every other ref names an object the
242
+ operator checkout has, otherwise collection is `writer_collect_incomplete` and
243
+ no digest is recorded. Rename inside the writer
240
244
  or have the operator explicitly resolve loss with `--reset --discard --apply`.
241
245
  Finish returns persistent and ephemeral writers to the workspace default base.
242
246
  No reconnect is needed after finish, reset or recreate: each session starts a
package/docs/roadmap.md CHANGED
@@ -50,6 +50,18 @@ mean they are implemented or that their gates passed.
50
50
  full writer profile comparison in the relay
51
51
  ([#194](https://github.com/plagor44-ux/pi-session-mcp/issues/194)). Levels 1
52
52
  to 3 passed for the candidate. #120 still waits for a Pi SDK release.
53
+ - 2026-10-30: [Sprint 13](sprints/2026-10-30-s13-index-only-work.md) fixes the
54
+ loss of work that exists only in a writer's Git index
55
+ ([#200](https://github.com/plagor44-ux/pi-session-mcp/issues/200)) in one
56
+ pull request, released in patch release 0.14.2: inspection evaluates the
57
+ original index tree, the versioned collection digest covers it, and
58
+ collection exports staged-only work as `<branch>-index`. The published-release Level 2 of 0.14.1 passed. The
59
+ same loss for work that exists only in a stash entry is the follow-up
60
+ [#202](https://github.com/plagor44-ux/pi-session-mcp/issues/202).
61
+ - 2026-10-30: #202 is fixed in one pull request, released in patch release
62
+ 0.14.2: collection exports every stash entry as `<base>-stash-<n>`, and tags,
63
+ notes and other unexported refs must name objects the operator checkout
64
+ already has before a writer counts as baseline or a digest is recorded.
53
65
 
54
66
  The explicit 2026-10-17 user request adds a bounded development-only
55
67
  [token-footprint measurement](sprints/2026-10-17-token-footprint.md), following
@@ -132,7 +132,9 @@ Recorded as work lands on the branch.
132
132
  (Grok's variant with a plain replacement did not reproduce, the graft did);
133
133
  the reachability gate now also runs on `nothing_to_collect`. Recorded as
134
134
  limits, not changed: an index-only staged change that the worktree reverted
135
- (pre-existing), a base name of 192 to 200 characters (fails closed), and a
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
136
138
  runner that reports a fetch it did not do (outside the threat model).
137
139
  - Final integration review (Sonnet 5.5, read-only, on `896e29d`):
138
140
  `NON_BLOCKING_ONLY`. Fixed: the in-boundary guard probe now ignores replace
@@ -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.
@@ -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.1" },
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.1",
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` and a clean detached HEAD as `<base>-detached`. 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`