projectstore-claude 0.29.4 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,26 @@
1
1
  # projectstore-claude
2
2
 
3
- The Claude Code installer for [projectstore](https://www.npmjs.com/package/projectstore): the core, pinned at exactly this version and bundled inside this tarball, with the harness fixed. One command, from a terminal **outside** a Claude Code session:
3
+ > **To install ProjectStore, run `npx projectstore setup` from a terminal in your project.** This package is a release artifact; you do not need it.
4
+
5
+ The Claude Code distribution shell of [projectstore](https://www.npmjs.com/package/projectstore): the core, pinned at exactly this version and bundled inside this tarball, with the harness fixed to Claude Code.
6
+
7
+ From a terminal in your project:
4
8
 
5
9
  ```sh
6
- npx projectstore-claude install --project "$PWD"
10
+ npx projectstore setup
7
11
  ```
8
12
 
9
- It registers the plugin for that checkout at the host's local scope. It prints its plan first — every write and every host command, verbatim — and at a terminal asks `◆ Apply N changes? [Y/n]` before anything runs; then each step as it runs, and what to do next. Without a terminal (a script, CI, an agent's tool) naming the shell is the confirmation; `--json` never asks. Restart Claude Code afterwards.
10
-
11
- - See before you write: `npx projectstore-claude plan --project "$PWD"` prints the same plan and writes nothing; `--verbose` adds every row's reasoning; `npx projectstore-claude <verb> --help` lists a verb's options with examples.
13
+ Setup finds the coding agents the project uses, Claude Code among them, and installs ProjectStore for each, with a vault for the project's notes. It asks a few questions, prints one plan — every write and every host command, verbatim — and asks once before anything runs. For Claude Code it registers the plugin for that checkout at the host's local scope, through a small local marketplace under your Claude home. Restart Claude Code afterwards.
12
14
 
13
- - Upgrade, or pin: `npx projectstore-claude@<version> upgrade --project "$PWD"` — the version you name is the version you run.
14
- - Behind a registry mirror: if `npx` answers `could not determine executable to run`, the mirror's index knows only the reserved `0.0.1` placeholder — name the version, and if needed the public registry: `npx --registry https://registry.npmjs.org projectstore-claude@<version> install --project "$PWD"`.
15
- - Uninstall: `npx projectstore-claude uninstall --project "$PWD"` — forgets the registration for that checkout and nothing else; your vault is plain markdown and stays yours.
16
- - `doctor`, `status`, `search` and the other read verbs pass through unchanged: `npx projectstore-claude doctor --json`.
15
+ Upgrade, or pin, from the same place; the version you name is the version you run:
17
16
 
18
- This shell is `projectstore <verb> --harness claude-code` and nothing more. `bin/projectstore-claude.mjs` locates the bundled core under `node_modules/projectstore/` and execs it; the core's low-level form — `npx projectstore install --harness claude-code --project "$PWD"` — is exactly what runs. A different `--harness` is refused (exit 2). The shell carries no plugin of its own: the plugin Claude Code loads is the bundled core, registered through a small local marketplace under your Claude home.
17
+ ```sh
18
+ npx projectstore@<version> upgrade
19
+ ```
19
20
 
20
- Why from a terminal: the host CLI and a live session both rewrite the same settings files, so the core defers the registration inside a session and says so.
21
+ This shell's bin is `projectstore <verb> --harness claude-code` and nothing more. `bin/projectstore-claude.mjs` locates the bundled core under `node_modules/projectstore/` and execs it; a different `--harness` is refused (exit 2). The shell carries no plugin of its own: the plugin Claude Code loads is the bundled core.
21
22
 
22
- - Docs: https://github.com/SmartAndPoint/ProjectStore#install--one-message
23
+ - Docs: https://github.com/SmartAndPoint/ProjectStore#install
23
24
  - Source: https://github.com/SmartAndPoint/ProjectStore
24
25
  - Issues: https://github.com/SmartAndPoint/ProjectStore/issues
25
26
 
@@ -57,7 +57,7 @@ function fixHarness(argv) {
57
57
  }
58
58
  }
59
59
  const other = given.find((g) => g !== HARNESS);
60
- if (other !== undefined) return { error: `installs for ${HARNESS} only — \`--harness ${other}\` names another harness. Run that harness's shell, or the core: npx projectstore <verb> --harness ${other} …` };
60
+ if (other !== undefined) return { error: `installs for ${HARNESS} only — \`--harness ${other}\` names another harness. Run the core: npx projectstore <verb> --harness ${other} …` };
61
61
  if (given.length) return { argv }; // named already: pass through, never twice (the core's option repeats)
62
62
  const at = scan.findIndex((a) => !a.startsWith("-"));
63
63
  if (at === -1 || !HARNESS_VERBS.has(scan[at])) return { argv };
@@ -12,7 +12,7 @@
12
12
  "name": "projectstore",
13
13
  "displayName": "projectstore",
14
14
  "description": "📚 Your agent runs the project through a verified loop: task → artifact (ADR · spec · epic · story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in an Obsidian-friendly vault, every write approved by you — and any model can pick the project up tomorrow.",
15
- "version": "0.29.4",
15
+ "version": "0.30.0",
16
16
  "author": {
17
17
  "name": "Evgenii Konev",
18
18
  "email": "ekonev@smartandpoint.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "projectstore",
3
3
  "displayName": "projectstore",
4
- "version": "0.29.4",
4
+ "version": "0.30.0",
5
5
  "description": "Your agent runs the project through a verified loop: task → artifact (ADR / spec / epic / story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in git — any model can pick the project up tomorrow.",
6
6
  "author": {
7
7
  "name": "Evgenii Konev @ SmartAndPoint",
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![release](https://img.shields.io/github/v/release/SmartAndPoint/ProjectStore?label=release)](https://github.com/SmartAndPoint/ProjectStore/releases) [![license](https://img.shields.io/github/license/SmartAndPoint/ProjectStore?label=license)](./LICENSE) [![Star on GitHub](https://img.shields.io/badge/%E2%AD%90-star_us-yellow?logo=github)](https://github.com/SmartAndPoint/ProjectStore/stargazers)
6
6
 
7
- A project workflow plugin for [Claude Code](https://claude.com/claude-code) and [OpenAI Codex](https://developers.openai.com/codex/) (experimental). Both are released together at one version, and each installs with one command.
7
+ A project workflow plugin for [Claude Code](https://claude.com/claude-code) and [OpenAI Codex](https://developers.openai.com/codex/) (experimental). Both are released together at one version, and one command sets a project up for either or both: see [Install](#install).
8
8
 
9
9
  ---
10
10
 
@@ -75,24 +75,40 @@ In Claude Code a band above the prompt goes further (experimental): the session'
75
75
 
76
76
  Open the vault in [Obsidian](https://obsidian.md) and you get the graph view and the board for free. Don't use Obsidian? Everything renders on GitHub and in any editor.
77
77
 
78
- ## Install — one message
78
+ ## Install
79
79
 
80
- Open Claude Code in your project and say:
80
+ From a terminal in your project:
81
81
 
82
- > Install the projectstore plugin from https://github.com/SmartAndPoint/ProjectStore and set it up for this project.
82
+ ```
83
+ npx projectstore setup
84
+ ```
83
85
 
84
- That's the whole setup. Claude adds the marketplace, installs the plugin, and walks you through binding a vault, scaffolding it and wiring the status line — every step previewed, nothing written without your Yes.
86
+ Setup finds the coding agents the project uses — Claude Code, Codex or both — and installs ProjectStore for each, with a vault for the project's notes. At a terminal it asks a few questions: which folder is the project, which agents to install for, where the vault lives (an existing folder is bound; a missing or empty one becomes a new vault, with its own git repository and the layout's folders) and whether to show ProjectStore in the status line. Then it prints one plan — every file it writes and every host command, verbatim — and asks once before it writes anything. Restart the agent afterwards; Codex asks you to approve the ProjectStore hooks once.
85
87
 
86
- **On Codex**, run one command from a terminal in your project:
88
+ `npx projectstore setup --dry-run` shows the questions and the plan and writes nothing. Without a terminal (a script, CI, an agent's tool) nothing is asked: `--harness <id>` names an agent and is the confirmation, and `--vault <path>` names the vault. `npx projectstore setup --help` lists the rest.
89
+
90
+ If you prefer a global command:
87
91
 
88
92
  ```
89
- npx projectstore-codex install --project "$PWD"
93
+ npm install -g projectstore
94
+ projectstore setup
90
95
  ```
91
96
 
92
- Then restart Codex, approve the ProjectStore hooks when it asks, and run `$projectstore-bind ~/Documents/my-project-vault`. Codex support is newer and still labelled experimental: [`docs/harnesses.md`](./docs/harnesses.md#codex) says what has been verified and what has not. A project can be worked from both agents: they share the binding in `.projectstore/`, and each keeps its own overlay and session state beside it ([running both over one project](./docs/harnesses.md#running-both-over-one-project)).
97
+ Behind a registry mirror (often a company's), `npx` runs the newest `projectstore` the mirror knows. That can be a release older than setup, which answers that `setup` is an unknown verb, or no release at all. Name the version, and if that is not enough, the public registry:
93
98
 
94
- <details>
95
- <summary>Prefer to type it yourself?</summary>
99
+ ```
100
+ npm_config_registry=https://registry.npmjs.org npx projectstore@<version> setup
101
+ ```
102
+
103
+ npm reads that variable wherever it runs, so the Codex plugin setup fetches comes from the same registry.
104
+
105
+ ### In a Claude Code session
106
+
107
+ Open Claude Code in your project and say:
108
+
109
+ > Install the projectstore plugin from https://github.com/SmartAndPoint/ProjectStore and set it up for this project.
110
+
111
+ Claude adds the git marketplace, installs the plugin, and walks you through binding a vault, scaffolding it and wiring the status line — every step previewed, nothing written without your Yes. Or type it yourself:
96
112
 
97
113
  ```
98
114
  /plugin marketplace add SmartAndPoint/ProjectStore
@@ -103,49 +119,31 @@ Then restart Codex, approve the ProjectStore hooks when it asks, and run `$proje
103
119
 
104
120
  One switch worth flipping: Claude Code does **not** auto-update third-party plugins by default — `/plugin` → **Marketplaces** → **SmartAndPoint** → toggle **auto-update** on. If you skip it, `/projectstore:doctor` will remind you later with the exact setting.
105
121
 
106
- Contributors: `git clone` this repo, then `claude --plugin-dir ./ProjectStore`.
107
-
108
- **Or from npm, in one command** — from a terminal, not inside a Claude Code session:
122
+ Contributors: `git clone` this repo, then run Claude Code from the clone's parent folder:
109
123
 
110
124
  ```
111
- npx projectstore-claude install --project "$PWD"
125
+ claude --plugin-dir ./ProjectStore
112
126
  ```
113
127
 
114
- The same tree is published to npm as [`projectstore`](https://www.npmjs.com/package/projectstore) — one source package carrying every harness's manifest — and `projectstore-claude` is its Claude Code shell: the core pinned at the same version and bundled inside, the harness fixed, so the one command has the same shape on every harness. It registers the plugin with Claude Code: it writes a small local marketplace of its own under your Claude home, then drives `claude plugin marketplace add` / `plugin install` **at local scope**, so the registration lands in this checkout's `.claude/settings.local.json` and nowhere else. It prints its plan first — every file it writes and every host command, verbatim — and at a terminal asks `◆ Apply N changes? [Y/n]`; then it shows each step as it runs and what to do next. Without a terminal (a script, CI, an agent's tool) naming the harness is the confirmation. `plan` prints the same plan and writes nothing; `--verbose` adds every row's reasoning; `npx projectstore-claude <verb> --help` lists a verb's options with examples. Restart Claude Code afterwards. A git-marketplace copy already enabled for the checkout is silenced there (not globally) so the plugin does not load twice; `uninstall` turns it back on. Pin or upgrade with `npx projectstore-claude@<version> upgrade --project "$PWD"` — the version you name is the version you run. The core's low-level form, `npx projectstore <verb> --harness claude-code …`, is exactly what the shell runs. bun works the same on the packed bin.
128
+ <details>
129
+ <summary>What setup does for each agent, the low-level verbs, the CLI and MCP</summary>
115
130
 
116
- **Codex has its own shell with the same one-command shape:**
117
- `npx projectstore-codex install --project "$PWD"`. It carries a Codex
118
- plugin manifest, namespaced workflow and role skills, lifecycle hooks, and
119
- the exact bundled core. It stages a stable marketplace under `CODEX_HOME`,
120
- drives `codex plugin marketplace add` and `codex plugin add`, then reads the
121
- installation back and verifies its version and payload digest. Before it
122
- changes anything it asks Codex for that read-back (`codex plugin list --json`),
123
- so a Codex too old to answer stops the registration before it changes anything
124
- and tells you to upgrade Codex. Restart Codex,
125
- approve the hooks, and run `$projectstore-bind <vault-path>`. For later
126
- releases, `npx projectstore-codex@<version> upgrade --project "$PWD"`. Because
127
- Codex's plugin registry is user-global, ordinary uninstall removes only the
128
- project's agents block; `uninstall --global` is the explicit machine-wide
129
- removal. If `npx` answers `could not determine executable to run`, your
130
- registry (often a company mirror) serves a stale index that knows only the
131
- reserved `0.0.1` placeholder: name the version, and if that is not enough, the
132
- public registry: `npx --registry https://registry.npmjs.org
133
- projectstore-codex@<version> install --project "$PWD"` (the same holds for
134
- `projectstore-claude`). Codex is **experimental** until a live session has exercised every
135
- surface it installs; [`docs/harnesses.md`](./docs/harnesses.md) says what has
136
- been measured and what has not. From a checkout, the same npx path runs against
137
- a built tarball:
131
+ **Claude Code.** Setup registers the plugin from the npm package [`projectstore`](https://www.npmjs.com/package/projectstore) — one source package carrying every harness's manifest. It writes a small local marketplace of its own under your Claude home, then drives `claude plugin marketplace add` / `plugin install` **at local scope**, so the registration lands in this checkout's `.claude/settings.local.json` and nowhere else. The host CLI and a live session both rewrite the same settings files, so inside a Claude Code session the registration is deferred, and the plan says so: run setup from a terminal outside the session. When the checkout already loads ProjectStore from the git marketplace, setup asks whether to switch this project to the npm install or keep the git marketplace. A switch silences the git-marketplace copy for this checkout only (not globally), so the plugin does not load twice; `uninstall` turns it back on.
138
132
 
139
- ```sh
140
- npm run shells:build -- --only projectstore-codex --dev --out dist
141
- npx --package "./dist/projectstore-codex-$(node -p 'require("./package.json").version').tgz" projectstore-codex install --project "$PWD"
133
+ **Codex.** The Codex plugin ships in a package of its own; setup fetches it at setup's own version and plans Codex in the same plan as everything else. It stages a stable marketplace under `CODEX_HOME`, drives `codex plugin marketplace add` and `codex plugin add`, then reads the installation back and verifies its version and payload digest. Before it changes anything it asks Codex for that read-back (`codex plugin list --json`), so a Codex too old to answer stops the registration before it changes anything and tells you to upgrade Codex. Codex's plugin registry is user-global; the agents block in `AGENTS.md` stays project-local. Codex is **experimental** until a live session has exercised every surface it installs: [`docs/harnesses.md`](./docs/harnesses.md#codex) says what has been verified and what has not. A project can be worked from both agents: they share the binding in `.projectstore/`, and each keeps its own overlay and session state beside it ([running both over one project](./docs/harnesses.md#running-both-over-one-project)).
134
+
135
+ **The low-level verbs.** Setup composes verbs that also run on their own. One agent's install, with no questions about the project or the vault:
136
+
137
+ ```
138
+ npx projectstore install --harness claude-code # shows the plan, asks at a terminal, then writes; without a terminal naming the harness is the confirmation — there is no --yes
142
139
  ```
143
140
 
144
- The package also carries a `bin`. Without a session — in CI, or in a shell — the same core answers token-free, with a `--json` envelope on every verb:
141
+ `plan` prints the same plan and writes nothing; `--verbose` adds every row's reasoning; `npx projectstore <verb> --help` lists a verb's options with examples. bun works the same on the packed bin.
142
+
143
+ Without a session — in CI, or in a shell — the same core answers token-free, with a `--json` envelope on every verb:
145
144
 
146
145
  ```
147
146
  npx projectstore doctor --json
148
- npx projectstore install --harness claude-code # the low-level form the shell runs: shows the plan, asks at a terminal, then writes; without a terminal naming the harness is the confirmation — there is no --yes
149
147
  npx projectstore reconcile --write --only kanban
150
148
  ```
151
149
 
@@ -175,43 +173,76 @@ npx projectstore init ~/vaults/new-project --language ru
175
173
  npx projectstore scaffold --write
176
174
  ```
177
175
 
178
- `projectstore-claude`, `projectstore-codex` and `projectstore-opencode` are this package's per-harness shells — the core pinned and bundled, the harness fixed. The Claude Code and Codex shells are published at the core's version; Codex stays labelled experimental until a live run has exercised every surface it installs ([`docs/harnesses.md`](./docs/harnesses.md)). The opencode shell publishes after its plugin root is rendered. The other `projectstore-*` names are reserved placeholders pointing back here. One source package, one version, N tarballs.
176
+ `projectstore-claude`, `projectstore-codex` and `projectstore-opencode` are release artifacts: per-harness packages that carry the core, pinned at the same version and bundled, with the harness fixed. Nobody needs to type their names. Setup fetches the Codex one, which carries the Codex plugin; the Claude Code one is published at the core's version too; the opencode one publishes after its plugin root is rendered. The other `projectstore-*` names are reserved placeholders pointing back here. One source package, one version, N tarballs. How a release builds and publishes them, and how to run a checkout's own build: [`packaging/README.md`](./packaging/README.md).
179
177
  </details>
180
178
 
181
179
  ## Upgrading
182
180
 
183
- `/plugin update` (or auto-update) and a restart, then one command per project
184
- bound before 0.28. What an existing project sees afterwards, and why:
181
+ Installed with setup or from npm: from a terminal in your project, name the version you want to run (`latest` is the newest):
182
+
183
+ ```
184
+ npx projectstore@<version> upgrade
185
+ ```
186
+
187
+ It upgrades every agent the project uses — each one whose own directory (`.claude/`, `.codex/`) or `.projectstore/state/<id>/` is there — in one plan, and fetches the Codex plugin at the same version. `npx projectstore setup` converges a project the same way and asks its questions again. Installed from the git marketplace in a Claude Code session: `/plugin update` (or auto-update) and a restart.
188
+
189
+ A bare upgrade selects agents by those directories, so on Codex name it, which upgrades Codex even in a project where no Codex session has run yet:
190
+
191
+ ```
192
+ npx projectstore@<version> upgrade --harness codex
193
+ ```
194
+
195
+ With the global command, update it first, then run it in the project:
196
+
197
+ ```
198
+ npm install -g projectstore@latest
199
+ projectstore upgrade
200
+ ```
201
+
202
+ A project bound before 0.28 needs one more command. What an existing project sees afterwards, and why:
185
203
 
186
204
  - **The project's files move to `.projectstore/`.** `.claude/projectstore.json`
187
205
  and `.claude/.projectstore/` become `.projectstore/projectstore.json`,
188
206
  `.projectstore/harness/claude-code.json` and `.projectstore/state/`. Nothing
189
207
  breaks before you move them: every reader falls back to the old paths through
190
- 0.29, and the startup line names the command until the move is done. Close
191
- every Claude Code session in the project, run that command from a terminal,
192
- then restart. The command depends on how you installed:
193
- - **From the git marketplace** (every 0.27.x install): the installed copy's
194
- own `bin/projectstore.mjs`, with its path spelled out in the startup line —
195
- `node "<plugin cache>/bin/projectstore.mjs" upgrade --harness claude-code --no-register --project "$PWD"`.
196
- `--no-register` leaves your plugin registration as it is.
197
- - **From npm**: `npx projectstore-claude@<version> upgrade --no-register --project "$PWD"`.
198
-
199
- Both forms move only the project's files, so neither touches your plugin
200
- registration. The same run re-stamps the status-line launcher at its new path
201
- and re-registers the agents block, whose template is now v4. A plain
202
- `npx projectstore-claude upgrade` on a git-marketplace install does more: it
208
+ 0.30, and the startup line names the command until the move is done. Close
209
+ every Claude Code session in the project, run that command from a terminal in
210
+ the project, then restart. The command depends on how you installed. From the
211
+ git marketplace (every 0.27.x install), it is the installed copy's own
212
+ `bin/projectstore.mjs`, with its path spelled out in the startup line:
213
+
214
+ ```
215
+ node "<plugin cache>/bin/projectstore.mjs" upgrade --harness claude-code --no-register
216
+ ```
217
+
218
+ From npm:
219
+
220
+ ```
221
+ npx projectstore@<version> upgrade --harness claude-code --no-register
222
+ ```
223
+
224
+ `--no-register` leaves your plugin registration as it is: both forms move
225
+ only the project's files. The same run re-stamps the status-line launcher at
226
+ its new path and re-registers the agents block, whose template is now v4. An
227
+ upgrade without `--no-register` on a git-marketplace install does more: it
203
228
  also registers the plugin from npm for this checkout and turns the
204
229
  git-marketplace copy off here, so `/plugin update` stops reaching the
205
- checkout. The 0.28.0-rc.1 and rc.2 startup lines named that shell form
206
- (`npx projectstore-claude@<version> upgrade`, without `--no-register`), so on
207
- those, update first and run what the new startup line names. If it already
208
- happened,
209
- `npx projectstore-claude@<version> uninstall --surface plugin --project "$PWD"`
210
- (0.28.0 or later) turns the git-marketplace copy back on. That copy must be 0.28 or later — an
211
- older one reads a moved project as unbound — so update it first if it is not.
212
- Restart, then run
213
- `/projectstore:doctor --fix` in the new session, which re-stamps the status
214
- line against that copy.
230
+ checkout. The 0.28.0-rc.1 and rc.2 startup lines named that form, without
231
+ `--no-register`, so on those, update first and run what the new startup line
232
+ names. If it already happened, this turns the git-marketplace copy back on
233
+ (0.28.0 or later), from a terminal in the project:
234
+
235
+ ```
236
+ npx projectstore@<version> uninstall --harness claude-code --surface plugin
237
+ ```
238
+
239
+ That copy must be 0.28 or later — an older one reads a moved project as
240
+ unbound — so update it first if it is not. Restart, then in the new session,
241
+ to re-stamp the status line against that copy:
242
+
243
+ ```
244
+ /projectstore:doctor --fix
245
+ ```
215
246
  - **The status line keeps rendering.** A launcher written by an earlier version
216
247
  still works, but it carries no file stamp and its embedded fallback root is
217
248
  frozen at the old version; the move above re-stamps it. Nothing rewrites that
@@ -242,9 +273,9 @@ bound before 0.28. What an existing project sees afterwards, and why:
242
273
  move once more, and the move re-stamps the launcher. If a session already
243
274
  re-stamped the status line or re-registered the agents block before the move
244
275
  (rc.2's `/projectstore:doctor --fix` did both), 0.27.x reports a foreign
245
- status line and a v4 block and leaves both alone — unless you run its
246
- `/projectstore:agents register`, which rewrites the block; the status line
247
- still renders, and the move settles both.
276
+ status line and a v4 block and leaves both alone — unless you run
277
+ `/projectstore:agents register` in it, which rewrites the block; the status
278
+ line still renders, and the move settles both.
248
279
  - **Rolling back** to 0.27.x after the move: 0.27.x looks for its binding under
249
280
  `.claude/`, finds none and offers `bind`. Do not accept. A re-bind writes
250
281
  `.claude/projectstore.json` again, and 0.28's `install` and `upgrade` refuse
@@ -252,11 +283,11 @@ bound before 0.28. What an existing project sees afterwards, and why:
252
283
  `.claude/projectstore.json`, then run the command the startup line names
253
284
  once more: a 0.27.x session writes its welcome marker back under `.claude/`.
254
285
  - **Installed from npm?** Then `/plugin update` has nothing to fetch: the
255
- registration is refreshed by the package itself — from a terminal outside
256
- the session, `npx projectstore-claude@<version> upgrade --project "$PWD"`
257
- rewrites the local marketplace and runs the host's
258
- `plugin update` for this checkout. `/projectstore:doctor` says when the
259
- registration is behind the package, and names that command.
286
+ package itself refreshes the registration. The upgrade at the top of this
287
+ section, run from a terminal outside the session, rewrites the local
288
+ marketplace and runs the host's `plugin update` for this checkout.
289
+ `/projectstore:doctor` says when the registration is behind the package, and
290
+ names the command.
260
291
 
261
292
  ## When an agent starts a task, it can find its way
262
293
 
@@ -297,7 +328,25 @@ The deep dive — real session files, measured payloads, how every mechanism wor
297
328
 
298
329
  ## Uninstalling
299
330
 
300
- `/plugin uninstall projectstore@SmartAndPoint` for a Claude git-marketplace install; `npx projectstore-claude uninstall --project "$PWD"` for its npm registration. For Codex, `npx projectstore-codex uninstall --project "$PWD"` removes only project-owned wiring; add `--global` only to remove the user-global Codex plugin and marketplace. Your vault is yours — plain markdown, untouched. One leftover of a host-managed plugin path can be the agents block in `CLAUDE.md`/`AGENTS.md`; remove it with the harness's agents unregister skill, or delete everything between `<!-- projectstore:agents … -->` and `<!-- /projectstore:agents -->` by hand. A one-harness `uninstall` leaves a block that lives in `AGENTS.md`, because that file is read by other coding agents too. Claude Code's then removes the `CLAUDE.md` import when that file holds nothing else; Codex's keeps it, so Claude Code still sees the block. `uninstall --surface agents_block` removes the block as well. A bare core `npx projectstore uninstall --project "$PWD"`, with no `--harness`, covers every harness the project uses — each one whose own directory (`.claude/`, `.codex/`) or `.projectstore/state/<id>/` is there. Once every harness that can read the block's file is among them (for `AGENTS.md`, Claude Code and Codex), the block goes too, and the `CLAUDE.md` import with it unless your own text keeps both files.
331
+ From a terminal in your project:
332
+
333
+ ```
334
+ npx projectstore uninstall
335
+ ```
336
+
337
+ With no `--harness` it covers every agent the project uses — each one whose own directory (`.claude/`, `.codex/`) or `.projectstore/state/<id>/` is there — and removes what install wrote, and only that. For Codex that is the project-owned wiring: its plugin and marketplace are user-global, and removing them for every project is explicit:
338
+
339
+ ```
340
+ npx projectstore uninstall --harness codex --global
341
+ ```
342
+
343
+ A Claude Code install from the git marketplace is removed in the session:
344
+
345
+ ```
346
+ /plugin uninstall projectstore@SmartAndPoint
347
+ ```
348
+
349
+ Your vault is yours — plain markdown, untouched. One leftover of a host-managed plugin path can be the agents block in `CLAUDE.md`/`AGENTS.md`; remove it with the harness's agents unregister skill, or delete everything between `<!-- projectstore:agents … -->` and `<!-- /projectstore:agents -->` by hand. A one-harness `uninstall`, with `--harness <id>`, leaves a block that lives in `AGENTS.md`, because that file is read by other coding agents too. Claude Code's then removes the `CLAUDE.md` import when that file holds nothing else; Codex's keeps it, so Claude Code still sees the block. `uninstall --surface agents_block` removes the block as well. Once every harness that can read the block's file is among those uninstalled (for `AGENTS.md`, Claude Code and Codex), the block goes too, and the `CLAUDE.md` import with it unless your own text keeps both files.
301
350
 
302
351
  ## Extending
303
352
 
@@ -322,7 +371,17 @@ One formatting-only commit is listed in `.git-blame-ignore-revs`. GitHub skips i
322
371
  git config blame.ignoreRevsFile .git-blame-ignore-revs
323
372
  ```
324
373
 
325
- A branch opened before that commit rebases in two steps, so that the lint fixes before it are kept: `git rebase <the commit before it>` (real conflicts, resolved as usual), then `git rebase --onto <it> <the commit before it>`, where every conflict is formatting only: take your side, run `npx prettier --write <file>`, `git add` it and continue.
374
+ A branch opened before that commit rebases in two steps, so that the lint fixes before it are kept. First onto the commit before it, resolving real conflicts as usual:
375
+
376
+ ```sh
377
+ git rebase <the commit before it>
378
+ ```
379
+
380
+ Then onto the formatting commit itself, where every conflict is formatting only: take your side, format the file with `npx prettier --write <file>` and `git add` it, then continue.
381
+
382
+ ```sh
383
+ git rebase --onto <it> <the commit before it>
384
+ ```
326
385
 
327
386
  ## License
328
387
 
@@ -49,12 +49,17 @@ vault-side layout or template override):
49
49
  4. **Checklist entry** — `scaffold/checklists.json`, consumed by
50
50
  `/projectstore:review` and the `projectstore-peer-reviewer` skill. English-only by design.
51
51
 
52
- 5. **Command prompt** — `commands/<kind>.md`, a prompt (not code) that calls
53
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/draft.mjs" <kind> "$ARGUMENTS"`, previews,
54
- and gates every write behind AskUserQuestion. Copy `commands/research.md`
55
- for a plain kind, `commands/adr.md` for one that renders the draft's
56
- `collision`/`warnings` fields and updates an index, `commands/spec.md`
57
- for one with status transitions.
52
+ 5. **Command prompt** — `commands/<kind>.md`, a prompt (not code). It renders
53
+ the draft with the line below, previews it, and gates every write behind
54
+ AskUserQuestion:
55
+
56
+ ```
57
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/draft.mjs" <kind> "$ARGUMENTS"
58
+ ```
59
+
60
+ Copy `commands/research.md` for a plain kind, `commands/adr.md` for one
61
+ that renders the draft's `collision`/`warnings` fields and updates an
62
+ index, `commands/spec.md` for one with status transitions.
58
63
 
59
64
  6. **Folder strings** — `templates/<lang>/strings.json` → `folders.<kind>`,
60
65
  a `name` and a one-line `description`, in **every** bundled language's
@@ -1,5 +1,17 @@
1
1
  # Getting started with projectstore
2
2
 
3
+ ## Install
4
+
5
+ From a terminal in your project:
6
+
7
+ ```
8
+ npx projectstore setup
9
+ ```
10
+
11
+ Setup finds the coding agents the project uses (Claude Code, Codex or both), asks a few questions — among them where the vault lives — then shows one plan and asks once before it writes. It binds the vault, or creates one when the folder is missing or empty. The README's [Install](../README.md#install) section has the details, the global form and the registry-mirror form.
12
+
13
+ The next two sections are the other ways in: a checkout, and the git marketplace inside a Claude Code session. After either of them, the first-time setup below binds the vault.
14
+
3
15
  ## Install (local dev)
4
16
 
5
17
  Until projectstore is published to a public marketplace, install it from a local clone.
@@ -132,6 +144,14 @@ What the language does and does not change: section headings, table labels and p
132
144
 
133
145
  ## Updating to a new version
134
146
 
147
+ Installed with setup or from npm: from a terminal in your project, name the version you want to run:
148
+
149
+ ```
150
+ npx projectstore@<version> upgrade
151
+ ```
152
+
153
+ Installed from the git marketplace, in a Claude Code session:
154
+
135
155
  ```
136
156
  /plugin marketplace update SmartAndPoint
137
157
  /reload-plugins
@@ -139,4 +159,4 @@ What the language does and does not change: section headings, table labels and p
139
159
 
140
160
  Or enable auto-update once (`/plugin` → **Marketplaces** → **SmartAndPoint** → toggle **auto-update**) and Claude Code will detect new releases at startup.
141
161
 
142
- **After any update, run `/projectstore:doctor`.** It compares your project's wiring against what the new version expects and names each fix with the command to run — stale agents block in `CLAUDE.md` (`/projectstore:agents register`), leftover agent copies that override nothing (`/projectstore:agents configure`), auto-update still off (the exact setting and file), a newer release than the one running. `doctor --fix` applies the install-side repairs interactively; `/projectstore:reconcile` rebuilds the board/indexes/code-map if content drifted. Silence at session start means healthy — the cheap checks run automatically and only speak up when something is wrong.
162
+ **After any update, run `/projectstore:doctor` in a session.** It compares your project's wiring against what the new version expects and names each fix with the command to run: a stale agents block in `CLAUDE.md` gets `/projectstore:agents register` as its fix and leftover agent copies that override nothing get `/projectstore:agents configure` as theirs. It also names auto-update still off (the exact setting and file) and a newer release than the one running. `doctor --fix` applies the install-side repairs interactively; `/projectstore:reconcile` rebuilds the board/indexes/code-map if content drifted. Silence at session start means healthy — the cheap checks run automatically and only speak up when something is wrong.
@@ -75,24 +75,27 @@ not started from a terminal can miss a `node` that only `.zprofile` puts on
75
75
  `PATH`.
76
76
 
77
77
  The shell is published as `projectstore-codex` from 0.28.1, ahead of that live
78
- run, by the maintainer's decision:
78
+ run, by the maintainer's decision. Nobody types its name: setup, install and
79
+ upgrade fetch it at their own version and plan Codex from it. From a terminal
80
+ in your project:
79
81
 
80
82
  ```sh
81
- npx projectstore-codex install --project "$PWD"
83
+ npx projectstore setup
82
84
  ```
83
85
 
84
- For later releases, `npx projectstore-codex@<version> upgrade --project "$PWD"`.
85
- Installation is user-global because Codex stores marketplace registrations and
86
- plugin caches in `CODEX_HOME`; the agents block in `AGENTS.md` remains
87
- project-local. Ordinary uninstall leaves the global plugin in place; add
88
- `--global` only when you mean to remove it for every project. From this
89
- checkout, the same flow runs against a built tarball:
86
+ To upgrade Codex alone, naming the version you want to run:
90
87
 
91
88
  ```sh
92
- npm run shells:build -- --only projectstore-codex --dev --out dist
93
- npx --package "./dist/projectstore-codex-$(node -p 'require("./package.json").version').tgz" projectstore-codex install --project "$PWD"
89
+ npx projectstore@<version> upgrade --harness codex
94
90
  ```
95
91
 
92
+ Installation is user-global because Codex stores marketplace registrations and
93
+ plugin caches in `CODEX_HOME`; the agents block in `AGENTS.md` remains
94
+ project-local. Ordinary uninstall leaves the global plugin in place; add
95
+ `--global` only when you mean to remove it for every project. A checkout's own
96
+ build runs through the same flow: [`packaging/README.md`](../packaging/README.md#running-a-checkouts-build)
97
+ has the tarball loop.
98
+
96
99
  What is known, and how:
97
100
 
98
101
  - **Hooks load from Codex's default file, so the shell carries no root
@@ -191,7 +191,7 @@ resistance. v0.13 makes that safe instead of forbidden:
191
191
  sees Bash-mediated writes, which bypass hook path extraction entirely.
192
192
 
193
193
  The division of labor: deterministic checks catch the mechanical 90% for free;
194
- the LLM critic (`/projectstore:review`) is saved for the judgment calls.
194
+ the LLM critic that `/projectstore:review` runs is saved for the judgment calls.
195
195
 
196
196
  ## Naming a session after the work it settled on
197
197
 
@@ -11,26 +11,27 @@
11
11
  "date": "2026-09-04"
12
12
  },
13
13
  "install": {
14
- "mechanism": "the package's own registration through its distribution shell, or the built-in plugin marketplace",
14
+ "mechanism": "the package's own registration (setup or install, from npm), or the built-in plugin marketplace",
15
15
  "shell": "projectstore-claude",
16
- "shell_reason": "the distribution shell that installs this harness from npm (the layout ADR decision 6; the shells ADR): `npx projectstore-claude install --project <dir>` is the core's `install --harness claude-code`, with the core pinned at the same version and bundled inside the shell's tarball. Data, not `projectstore-<id>`: the id is claude-code and the reserved, published name is projectstore-claude. Every command a finding or a deferred reason names is built from this key (harness.mjs packageCommand).",
17
- "why_not_scripted": "Two channels. `npx projectstore-claude install --project <dir>` registers the plugin for that project from the npm package itself — the shell bundles the core and fixes the harness; the core's low-level form is `npx projectstore install --harness claude-code --project <dir>` — through a local marketplace under the Claude home, driven by Claude Code's own `claude plugin` CLI. `/plugin install projectstore@SmartAndPoint` installs it from the git marketplace. `/plugin` lists and toggles either. Upgrading the npm registration is `npx projectstore-claude@<version> upgrade --project <dir>` — that is also the pin.",
16
+ "shell_reason": "the distribution shell of this harness on npm (the layout ADR decision 6; the shells ADR): `projectstore-claude install` is the core's `install --harness claude-code`, with the core pinned at the same version and bundled inside the shell's tarball. Data, not `projectstore-<id>`: the id is claude-code and the reserved, published name is projectstore-claude. Since the front-door ADR the shell is a release artifact: no command a person is told to type names it (packageCommand builds the core's form, `npx projectstore <verb> --harness claude-code`), and this key tells the core which harness a running shell fixes (harness.mjs bundlingShellHarnessId, speakingHarnessId).",
17
+ "why_not_scripted": "Two channels. The npm package registers the plugin for a project itself — run `npx projectstore setup` or the core's low-level `install --harness claude-code` from a terminal in the project — through a local marketplace under the Claude home, driven by Claude Code's own `claude plugin` CLI. `/plugin install projectstore@SmartAndPoint` installs it from the git marketplace. `/plugin` lists and toggles either. The npm registration is upgraded, and pinned, by `npx projectstore@<version> upgrade` from a terminal in the project.",
18
18
  "steps": [
19
- "npx projectstore-claude install --project \"$PWD\" (npm; registers the plugin for this project — the shell runs the core's install with the harness fixed)",
20
- "/plugin marketplace add SmartAndPoint/ProjectStore && /plugin install projectstore@SmartAndPoint (git marketplace, user-wide)",
19
+ "npm, for this checkout (the core registers the plugin), from a terminal in the project: npx projectstore install --harness claude-code",
20
+ "git marketplace, user-wide, in a Claude Code session: /plugin marketplace add SmartAndPoint/ProjectStore && /plugin install projectstore@SmartAndPoint",
21
21
  "restart Claude Code"
22
22
  ],
23
23
  "next": [
24
24
  "restart Claude Code in this project"
25
25
  ],
26
26
  "notes": [
27
- "Git marketplace only: auto-update is OFF by default for third-party plugins: /plugin -> Marketplaces -> SmartAndPoint -> toggle auto-update. The npm registration updates when you run `npx projectstore-claude upgrade --project <dir>`.",
28
- "Pin a git-marketplace release with: /plugin marketplace add SmartAndPoint/ProjectStore#<tag>; pin the npm registration with npx projectstore-claude@<version>.",
27
+ "Git marketplace only: auto-update is OFF by default for third-party plugins: /plugin -> Marketplaces -> SmartAndPoint -> toggle auto-update. The npm registration updates when you run `npx projectstore@<version> upgrade` from a terminal in the project.",
28
+ "Pin a git-marketplace release by adding the marketplace at a tag: /plugin marketplace add SmartAndPoint/ProjectStore#<tag>",
29
+ "The npm registration is pinned by the version its upgrade names: npx projectstore@<version> upgrade",
29
30
  "Two enabled copies of the plugin (git marketplace and npm) in one checkout would load twice; the npm registration silences the other one for that checkout only (settings.local.json), and says so in its preview.",
30
31
  "From a checkout instead: claude --plugin-dir ./ProjectStore",
31
32
  "The npm registration is per checkout (local scope) and per machine; a fresh clone or a new worktree runs the install command again. It must run from a terminal outside a Claude Code session: the host CLI and a live session both rewrite settings files."
32
33
  ],
33
- "docs": "https://github.com/SmartAndPoint/ProjectStore#install--one-message"
34
+ "docs": "https://github.com/SmartAndPoint/ProjectStore#install"
34
35
  },
35
36
  "runtime": {
36
37
  "detect_env": [
@@ -498,14 +499,14 @@
498
499
  "reason": "What the first-run welcome and its one-line notice say about updates (generation spec, contract 18): `line` ends the notice, `welcome` is the welcome's update block. update_instructions is the long-form guidance; this is the part a session prints, kept here so no hook names a harness's update path."
499
500
  },
500
501
  "update_instructions": [
501
- "**About future updates**: installed from the git marketplace, Claude Code does NOT auto-update third-party marketplaces by default (an npm registration updates with `npx projectstore-claude upgrade --project <dir>`). To get notified of new releases (v0.7+):",
502
+ "**About future updates**: installed from the git marketplace, Claude Code does NOT auto-update third-party marketplaces by default; an npm registration updates with `npx projectstore@<version> upgrade` from a terminal in the project. To get notified of new git-marketplace releases (v0.7+):",
502
503
  "1. Open `/plugin` → **Marketplaces** tab.",
503
504
  "2. Find **SmartAndPoint**.",
504
505
  "3. Toggle **auto-update** on.",
505
506
  "",
506
507
  "Without it, you would run `/plugin marketplace update SmartAndPoint` manually. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details.",
507
508
  "",
508
- "**Upgrading from 0.27.x to 0.28**: restart after the update, then move the project's files to `.projectstore/` with the one command the first session names, run from a terminal outside the session: for a git-marketplace install, the installed copy's own `bin/projectstore.mjs upgrade --harness claude-code --no-register --project <dir>`, which leaves the registration as it is; for the npm registration, `npx projectstore-claude@<version> upgrade --no-register --project <dir>`. The same run re-stamps the status-line launcher, and until then every reader falls back to the old paths. `status` and `search` answer from artifact facts; `doctor` gains surface, version-drift and mcp lines and exits 1 on findings; the plugin registers an MCP server the host may ask you to approve once. On 0.28.0-rc.1 or rc.2, update first: their startup line named the shell form without `--no-register` (`npx projectstore-claude@<version> upgrade`), which also switches a git-marketplace install to npm for the checkout (the README's Upgrading section has the way back). Before the move, a rollback to 0.27.x keeps working and the move is the way forward again; after the move, 0.27.x reads the project as unbound."
509
+ "**Upgrading from 0.27.x to 0.28**: restart after the update, then move the project's files to `.projectstore/` with the one command the first session names, run from a terminal in the project, outside the session. For a git-marketplace install it is the installed copy's own `bin/projectstore.mjs` with `upgrade --harness claude-code --no-register`, which leaves the registration as it is. For the npm registration it is `npx projectstore@<version> upgrade --harness claude-code --no-register` and nothing else. The same run re-stamps the status-line launcher, and until then every reader falls back to the old paths. `status` and `search` answer from artifact facts; `doctor` gains surface, version-drift and mcp lines and exits 1 on findings; the plugin registers an MCP server the host may ask you to approve once. On 0.28.0-rc.1 or rc.2, update first: their startup line named the form without `--no-register`, which also switches a git-marketplace install to npm for the checkout (the README's Upgrading section has the way back). Before the move, a rollback to 0.27.x keeps working and the move is the way forward again; after the move, 0.27.x reads the project as unbound."
509
510
  ],
510
511
  "agent_translation": {
511
512
  "carries_model": true,
@@ -311,32 +311,32 @@
311
311
  },
312
312
  "rewrites": [],
313
313
  "install": {
314
- "mechanism": "the package's portable plugin root, registered through its distribution shell and Codex's own marketplace CLI",
314
+ "mechanism": "the package's portable plugin root, fetched in its distribution shell and registered through Codex's own marketplace CLI",
315
315
  "shell": "projectstore-codex",
316
316
  "shell_reason": "The built `projectstore-codex` shell carries the rendered Codex skills and hooks plus an exact bundled core, fixes --harness codex, stages a stable marketplace under CODEX_HOME, and asks Codex itself to install it. It is published from 0.28.1, by the maintainer's decision of 2026-10-04. Until then this key was absent, so generated guidance could not send users to the reserved 0.0.1 placeholder; the core alone is not Codex's plugin root and cannot register it.",
317
- "why_not_scripted": "The shell does not edit Codex's registry or cache. It atomically stages the plugin source, previews the host commands, then runs `codex plugin marketplace add` and `codex plugin add`; a read-back verifies the installed version and payload digest. The global plugin survives ordinary project uninstall; `uninstall --global` is the explicit destructive form.",
317
+ "why_not_scripted": "The installer does not edit Codex's registry or cache. It atomically stages the plugin source from the shell it fetched, previews the host commands, then runs `codex plugin marketplace add` and `codex plugin add`; a read-back verifies the installed version and payload digest. The global plugin survives ordinary project uninstall; `uninstall --global` is the explicit destructive form.",
318
318
  "steps": [
319
- "npx projectstore-codex install --project \"$PWD\"",
319
+ "from a terminal in the project (the core fetches the Codex shell at its own version): npx projectstore install --harness codex",
320
320
  "restart Codex and approve the ProjectStore hooks when prompted",
321
- "run `$projectstore-bind <vault-path>` in Codex"
321
+ "in Codex, bind a vault: $projectstore-bind <vault-path>"
322
322
  ],
323
323
  "next": [
324
324
  "restart Codex from a terminal and approve the ProjectStore hooks when it asks"
325
325
  ],
326
326
  "notes": [
327
327
  "The installation is user-global because Codex's marketplace, plugin row and cache live in CODEX_HOME; the AGENTS.md block is project-local.",
328
- "Upgrade with `npx projectstore-codex@<version> upgrade --project \"$PWD\"`; the named package version is also the plugin version.",
328
+ "Upgrade with `npx projectstore@<version> upgrade --harness codex` from a terminal in the project; the version it names is also the plugin version.",
329
329
  "A second enabled ProjectStore plugin from another marketplace is a refusal, not silently disabled globally. Remove or disable that older registration explicitly, then retry.",
330
- "Ordinary uninstall removes only project-owned surfaces. `npx projectstore-codex uninstall --global --project \"$PWD\"` also removes the user-global plugin registration after a preview."
330
+ "Ordinary uninstall removes only project-owned surfaces. `npx projectstore uninstall --harness codex --global` also removes the user-global plugin registration after a preview."
331
331
  ],
332
- "docs": "https://github.com/SmartAndPoint/ProjectStore#install--one-message"
332
+ "docs": "https://github.com/SmartAndPoint/ProjectStore#install"
333
333
  },
334
334
  "update_hint": {
335
335
  "line": "Update from a terminal in this project:\n{upgrade}",
336
336
  "welcome": ["**About future updates**: from a terminal in this project, run:", "", "```", "{upgrade}", "```", "", "Then restart Codex and approve any hook it asks about. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details."],
337
337
  "reason": "What the first-run welcome and its one-line notice say about updates (generation spec, contract 18): `line` ends the notice, `welcome` is the welcome's update block, and `{upgrade}` is filled with the core's upgrade for this harness at latest (`--harness codex`, no `--project`), so what is printed runs as printed. Each says where to run it and puts the command alone on its line (the story \"User-facing text says where to run and names no project path and no shell\", rules 4 and 5). update_instructions is the long-form guidance; this is the part a session prints, kept here so no hook names a harness's update path."
338
338
  },
339
- "update_instructions": "Run `npx projectstore-codex@<version> upgrade --project <dir>`. The installer atomically replaces its stable source and `codex plugin add projectstore@projectstore-npx` materialises the new version; this was measured across two dev builds on codex-cli 0.153.4. Restart Codex afterwards so the new skills and hooks are loaded.",
339
+ "update_instructions": "From a terminal in the project, run `npx projectstore@<version> upgrade --harness codex` and restart Codex afterwards, so the new skills and hooks are loaded. The run fetches the shell at that version, atomically replaces its stable source, and `codex plugin add projectstore@projectstore-npx` materialises the new version; this was measured across two dev builds on codex-cli 0.153.4.",
340
340
  "agent_translation": {
341
341
  "carries_model": false,
342
342
  "carries_model_reason": "The surface projectstore ships — a plugin's agents/ entry — carries no model on Codex: surfaces.agents is supported:false because no route from a plugin's agents/ directory to collaborationspawn_agent has been found (measured). Codex DOES have a definition file that carries model and effort, recorded below, but it is project- or user-scoped and not plugin-shippable, so contract 12 holds as written: what we ship cannot carry a model. Was true here until 2026-09-08, which contradicted both contract 12 and this file's own agents row.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectstore",
3
- "version": "0.29.4",
3
+ "version": "0.30.0",
4
4
  "description": "Your agent runs the project through a verified loop: task → artifact (ADR / spec / epic / story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in git — any model can pick the project up tomorrow.",
5
5
  "keywords": [
6
6
  "project-management",
@@ -18,7 +18,8 @@
18
18
  // npmUserRegistry({ home, env }) → the user's own registry, read from home, or null
19
19
  // fetchShell(job, { dir, registry }) → { ok, root, ms } or { ok: false, code, refusal }
20
20
  // verifyFetched(root, { … }) → null, or the refusal naming file and values
21
- // fetchRefusal(code, ctx) → rule 4's texts
21
+ // fetchRefusal(code, ctx) → rule 4's texts, the envelope's string
22
+ // fetchRefusalAdvice(code, ctx) → the same, as PLAN draws it: { text, blocks }
22
23
  // sweepFetchRuns(fetchDir) → the leftovers of dead runs, removed
23
24
  // fetchRunId() → <pid>-<epoch ms>
24
25
  //
@@ -29,8 +30,8 @@
29
30
 
30
31
  import { existsSync, readFileSync, readdirSync } from "node:fs";
31
32
  import { join } from "node:path";
32
- import { registrationDeferred } from "./harness.mjs";
33
- import { removeTreeUnder, pidAlive, whichOnPath } from "./lib.mjs";
33
+ import { registrationDeferred, packageCommand } from "./harness.mjs";
34
+ import { removeTreeUnder, pidAlive, whichOnPath, fromTerminalIn } from "./lib.mjs";
34
35
  import {
35
36
  isPortablePluginRoot,
36
37
  payloadManifest,
@@ -213,12 +214,45 @@ export function npmError(stdout, stderr) {
213
214
 
214
215
  const seconds = (ms) => `${Number((ms / 1000).toFixed(ms % 1000 ? 1 : 0))} s`;
215
216
 
216
- // Rule 4's refusals, by npm's code or ours. `ctx`: pkg ("<shell>@<v>"),
217
- // shell, registry (null: npm's own), display (the harness's display name),
218
- // budget, summary (npm's), cause (a start failure's), path/from/variable (the
219
- // cache). Every way out is a terminal command or a variable; none names a
220
- // version but the one this run is at.
221
- export function fetchRefusal(code, ctx = {}) {
217
+ // npm's own registry: the one README's registry-mirror advice names.
218
+ export const PUBLIC_REGISTRY = "https://registry.npmjs.org";
219
+
220
+ // A registry that has no such shell may be a mirror that has not synced it:
221
+ // the run once more, from npm's public registry, in README's form — the
222
+ // variable, not `npx --registry`, because `npm config get registry`, which
223
+ // the fetch's resolver reads, honours it (the story "User-facing text says
224
+ // where to run and names no project path and no shell", rule 7). It is the
225
+ // run the person just saw: its verb and version, and its selection as given —
226
+ // each named --harness, each --surface, and --no-register — never --project
227
+ // (the front-door ADR, decision 5); the directory goes in the lead, and the
228
+ // command stands alone after it (rule 4). `args` is the shape the converge
229
+ // story's stop repeats a run with. Null when the run is unknown, or when the
230
+ // registry is already the public one; a null registry is npm's own
231
+ // resolution, which can be a mirror, so it gets the line.
232
+ function mirrorBlock({ registry = null, verb = null, version = null, args = {}, project = null } = {}) {
233
+ if (!verb || !version || String(registry || "").replace(/\/+$/, "") === PUBLIC_REGISTRY) return null;
234
+ const { harnesses = [], surfaces = null, register = true } = args || {};
235
+ const narrowing = [
236
+ ...(surfaces || []).map((s) => `--surface ${s}`),
237
+ ...(register === false ? ["--no-register"] : []),
238
+ ];
239
+ const command = packageCommand(
240
+ harnesses.map((id) => ({ id })),
241
+ verb,
242
+ { version, args: narrowing.join(" ") },
243
+ );
244
+ const where = project ? fromTerminalIn(project) : "from a terminal in this project";
245
+ return {
246
+ lead: `A mirror that has not synced it? Run this again from npm's public registry, ${where}:`,
247
+ command: `npm_config_registry=${PUBLIC_REGISTRY} ${command}`,
248
+ };
249
+ }
250
+
251
+ // Rule 4's refusals, as PLAN draws them: `{ text, blocks }`, the prose, then
252
+ // each lead and its command (presentation spec contract 6), so PLAN prints a
253
+ // command whole, unwrapped, alone on its line. Only the missing shell has a
254
+ // block today; every other refusal is its text alone.
255
+ export function fetchRefusalAdvice(code, ctx = {}) {
222
256
  const {
223
257
  pkg = "the shell",
224
258
  shell = "the shell",
@@ -233,29 +267,61 @@ export function fetchRefusal(code, ctx = {}) {
233
267
  } = ctx;
234
268
  const where = registry || "the registry npm resolves";
235
269
  const without = `name --harness without ${display}`;
270
+ const said = (text, blocks = []) => ({ text, blocks });
236
271
  switch (code) {
237
272
  case "ETARGET":
238
- case "E404":
239
- return `${pkg} is not on ${where} (npm ${code}); ${display} is registered from that shell at this exact version, and no other version is tried. Just released? The shell is published after the core, so retry in a few minutes. From a checkout at an unpublished version, point PROJECTSTORE_DISTRIBUTION_ROOT at the output of \`npm run shells:build -- --dev\` (dist/build/${shell}). Or ${without}.`;
273
+ case "E404": {
274
+ const mirror = mirrorBlock(ctx);
275
+ return said(
276
+ `${pkg} is not on ${where} (npm ${code}); ${display} is registered from that shell at this exact version, and no other version is tried. Just released? The shell is published after the core, so retry in a few minutes. From a checkout at an unpublished version, point PROJECTSTORE_DISTRIBUTION_ROOT at the output of \`npm run shells:build -- --dev\` (dist/build/${shell}). Or ${without}.`,
277
+ mirror ? [mirror] : [],
278
+ );
279
+ }
240
280
  // Our budget, not npm's: npm's own ETIMEDOUT (a socket) is any other code.
241
281
  case "EBUDGET":
242
- return `fetching ${pkg} from ${where} took longer than ${seconds(budget)} and was stopped. Retry, or ${without}.`;
282
+ return said(
283
+ `fetching ${pkg} from ${where} took longer than ${seconds(budget)} and was stopped. Retry, or ${without}.`,
284
+ );
243
285
  case "FETCH_TIMEOUT":
244
- return `npm gave up on ${ctx.url || where} (network timeout) while fetching ${pkg}. Retry, or ${without}.`;
286
+ return said(`npm gave up on ${ctx.url || where} (network timeout) while fetching ${pkg}. Retry, or ${without}.`);
245
287
  case "E401":
246
288
  case "E403":
247
- return `${where} refused the credentials for ${pkg} (npm ${code}). A project .npmrc's token does not reach this fetch — only its registry does — so the registry's token must be in user config: ~/.npmrc, or an NPM_CONFIG_… variable. Or point PROJECTSTORE_DISTRIBUTION_ROOT at a ${shell} root obtained another way.`;
289
+ return said(
290
+ `${where} refused the credentials for ${pkg} (npm ${code}). A project .npmrc's token does not reach this fetch — only its registry does — so the registry's token must be in user config: ~/.npmrc, or an NPM_CONFIG_… variable. Or point PROJECTSTORE_DISTRIBUTION_ROOT at a ${shell} root obtained another way.`,
291
+ );
248
292
  case "ESTART":
249
- return `${cause}; ${display} is registered from its shell, which npm fetches. Put npm on PATH, or ${without}.`;
293
+ return said(
294
+ `${cause}; ${display} is registered from its shell, which npm fetches. Put npm on PATH, or ${without}.`,
295
+ );
250
296
  case "ENOCACHE":
251
- return `the shell fetch has no cache directory: ${variable} is not an absolute path and the home directory is unknown. Set ${variable} to a writable directory, then run this again.`;
297
+ return said(
298
+ `the shell fetch has no cache directory: ${variable} is not an absolute path and the home directory is unknown. Set ${variable} to a writable directory, then run this again.`,
299
+ );
252
300
  case "ECACHE":
253
- return `the cache directory ${path} cannot be written (${ctx.errno || "error"}); it comes from ${from}. Set ${variable} to a writable directory, then run this again.`;
301
+ return said(
302
+ `the cache directory ${path} cannot be written (${ctx.errno || "error"}); it comes from ${from}. Set ${variable} to a writable directory, then run this again.`,
303
+ );
254
304
  default:
255
- return `npm could not fetch ${pkg} from ${where}: ${code || "an unknown error"}${summary ? ` — ${summary.split(/\r?\n/)[0]}` : ""}. ${display} is registered from its shell, which needs the network: retry once the registry is reachable, or ${without}.`;
305
+ return said(
306
+ `npm could not fetch ${pkg} from ${where}: ${code || "an unknown error"}${summary ? ` — ${summary.split(/\r?\n/)[0]}` : ""}. ${display} is registered from its shell, which needs the network: retry once the registry is reachable, or ${without}.`,
307
+ );
256
308
  }
257
309
  }
258
310
 
311
+ // Rule 4's refusals, by npm's code or ours: the envelope's string, every part
312
+ // on its own line — the prose, then each lead and its command — which an agent
313
+ // relays as it is. `ctx`: pkg ("<shell>@<v>"), shell, registry (null: npm's
314
+ // own), display (the harness's display name), budget, summary (npm's), cause
315
+ // (a start failure's), path/from/variable (the cache), and the run — its verb,
316
+ // version, project and `args` (the selection as given) — which the missing
317
+ // shell's refusal repeats from the public registry. Every way out is a
318
+ // terminal command or a variable; none names a version but the one this run
319
+ // is at.
320
+ export function fetchRefusal(code, ctx = {}) {
321
+ const { text, blocks } = fetchRefusalAdvice(code, ctx);
322
+ return [text, ...blocks.flatMap((b) => [b.lead, b.command])].join("\n");
323
+ }
324
+
259
325
  // Rule 3: the fetched tree is what this run plans, before it is planned —
260
326
  // the shell's name and version, a plugin root by the analyser's own predicate,
261
327
  // its plugin manifest's name and version, and the bundled core at the same
@@ -294,26 +360,29 @@ export function verifyFetched(root, { shell, version, pluginName, coreName }) {
294
360
  // as *** (measured). A failure is a refusal before the plan, never a fallback.
295
361
  export async function fetchShell(
296
362
  { harness, s, version, coreName },
297
- { dir, registry = null, env = process.env, spawn, budget = HOST_BUDGET_MS } = {},
363
+ { dir, registry = null, env = process.env, spawn, budget = HOST_BUDGET_MS, run = {} } = {},
298
364
  ) {
299
365
  const shell = harness.install.shell;
300
366
  const pkg = `${shell}@${version}`;
301
- const ctx = { pkg, shell, registry, display: harness.display_name, budget };
367
+ // `run`: the verb, selection (`args`) and project of the run that fetches,
368
+ // for the missing shell's public-registry form; the version is this one.
369
+ const ctx = { pkg, shell, registry, display: harness.display_name, budget, ...run, version };
370
+ // A refusal in both forms: the envelope's string, and what PLAN draws.
371
+ const refused = (code, extra = {}) => ({
372
+ refusal: fetchRefusal(code, { ...ctx, ...extra }),
373
+ advice: fetchRefusalAdvice(code, { ...ctx, ...extra }),
374
+ });
302
375
  const call = npmCall(fetchArgv({ prefix: dir, registry, shell, version }), env);
303
376
  const t0 = Date.now();
304
377
  const r = await runHost(call.bin, call.argv, { env, cwd: dir, budget, spawn, shell: call.shell });
305
378
  const ms = Date.now() - t0;
306
379
  if (r.error) {
307
- if (r.error.code === "ETIMEDOUT")
308
- return { ok: false, ms, code: "ETIMEDOUT", refusal: fetchRefusal("EBUDGET", ctx) };
380
+ if (r.error.code === "ETIMEDOUT") return { ok: false, ms, code: "ETIMEDOUT", ...refused("EBUDGET") };
309
381
  return {
310
382
  ok: false,
311
383
  ms,
312
384
  code: r.error.code || "ESTART",
313
- refusal: fetchRefusal("ESTART", {
314
- ...ctx,
315
- cause: startFailure(r.error, { bin: "npm", resolved: call.resolved, cwd: dir }),
316
- }),
385
+ ...refused("ESTART", { cause: startFailure(r.error, { bin: "npm", resolved: call.resolved, cwd: dir }) }),
317
386
  };
318
387
  }
319
388
  if (r.status !== 0) {
@@ -321,14 +390,8 @@ export async function fetchShell(
321
390
  // npm reports a request it gave up on as FETCH_ERROR, "network timeout
322
391
  // at: <url>" (measured in review, 2026-10-08), not as ETIMEDOUT.
323
392
  const timeout = e.code === "FETCH_ERROR" && /network timeout at:\s*(\S+)/.exec(e.summary);
324
- if (timeout)
325
- return { ok: false, ms, code: e.code, refusal: fetchRefusal("FETCH_TIMEOUT", { ...ctx, url: timeout[1] }) };
326
- return {
327
- ok: false,
328
- ms,
329
- code: e.code || `exit ${r.status}`,
330
- refusal: fetchRefusal(e.code, { ...ctx, summary: e.summary }),
331
- };
393
+ if (timeout) return { ok: false, ms, code: e.code, ...refused("FETCH_TIMEOUT", { url: timeout[1] }) };
394
+ return { ok: false, ms, code: e.code || `exit ${r.status}`, ...refused(e.code, { summary: e.summary }) };
332
395
  }
333
396
  const root = join(dir, "node_modules", shell);
334
397
  const wrong = verifyFetched(root, { shell, version, pluginName: s.plugin_name, coreName });
@@ -5050,10 +5050,11 @@ async function fetchShells(
5050
5050
  }
5051
5051
  }
5052
5052
  if (!jobs.length) return none;
5053
- const refuse = (message, cleanup = () => {}) => ({
5054
- ...none,
5055
- cleanup,
5056
- refused: {
5053
+ // `advice`: the refusal as PLAN draws it, its commands in their own blocks
5054
+ // (fetchRefusalAdvice) — kept off the envelope, as the channel "git"
5055
+ // refusal's is, so `--json` keeps the one string.
5056
+ const refuse = (message, cleanup = () => {}, advice = null) => {
5057
+ const refused = {
5057
5058
  projectDir,
5058
5059
  mode: "install",
5059
5060
  named,
@@ -5066,8 +5067,10 @@ async function fetchShells(
5066
5067
  incomplete: false,
5067
5068
  root,
5068
5069
  plannedAgainst: {},
5069
- },
5070
- });
5070
+ };
5071
+ if (advice?.blocks?.length) Object.defineProperty(refused, "refusalAdvice", { value: advice, enumerable: false });
5072
+ return { ...none, cleanup, refused };
5073
+ };
5071
5074
  const cache = cachePaths({ env, home });
5072
5075
  if (!cache.root) return refuse(fetchRefusal("ENOCACHE", { variable: cache.variable }));
5073
5076
  sweepFetchRuns(cache.fetch);
@@ -5104,6 +5107,19 @@ async function fetchShells(
5104
5107
  const differs = Boolean(registry) && bare(registry) !== bare(mine);
5105
5108
  const payloadRoots = {},
5106
5109
  fetched = [];
5110
+ // The run, for the missing shell's public-registry form: its verb, its
5111
+ // project, and its selection as given, in the shape the stop repeats a
5112
+ // run with — each named --harness (setup's answered set is named too),
5113
+ // each --surface, --no-register.
5114
+ const run = {
5115
+ verb,
5116
+ project: projectDir,
5117
+ args: {
5118
+ harnesses: named ? (opts.harnesses || []).filter(Boolean) : [],
5119
+ surfaces: opts.surfaces || null,
5120
+ register: opts.register !== false,
5121
+ },
5122
+ };
5107
5123
  for (const job of jobs) {
5108
5124
  const { harness } = job;
5109
5125
  const pkg = `${harness.install.shell}@${version}`;
@@ -5112,10 +5128,13 @@ async function fetchShells(
5112
5128
  const dir = jobs.length === 1 ? runDir : join(runDir, harness.id);
5113
5129
  if (dir !== runDir) mkdirSync(dir, { recursive: true });
5114
5130
  line = opts.onFetch ? opts.onFetch(`fetching ${pkg} for ${harness.display_name}`, ids) : null;
5115
- const r = await fetchShell({ ...job, version, coreName }, { dir, registry, env, spawn: opts.fetchSpawn, budget });
5131
+ const r = await fetchShell(
5132
+ { ...job, version, coreName },
5133
+ { dir, registry, env, spawn: opts.fetchSpawn, budget, run },
5134
+ );
5116
5135
  if (!r.ok) {
5117
5136
  if (line) line.end(false, `fetch failed ${pkg}`, r.code);
5118
- return refuse(r.refusal, cleanup);
5137
+ return refuse(r.refusal, cleanup, r.advice);
5119
5138
  }
5120
5139
  if (line) line.end(true, `fetched ${pkg} for ${harness.display_name}`);
5121
5140
  line = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "projectstore-claude",
3
- "version": "0.29.4",
4
- "description": "Installs projectstore for Claude Code from npm: the core pinned and bundled, the harness fixed — npx projectstore-claude install --project \"$PWD\".",
3
+ "version": "0.30.0",
4
+ "description": "projectstore for Claude Code: the core pinned and bundled, the harness fixed. A release artifact; the way in is npx projectstore setup",
5
5
  "keywords": [
6
6
  "projectstore",
7
7
  "installer",
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "dependencies": {
40
- "projectstore": "=0.29.4"
40
+ "projectstore": "=0.30.0"
41
41
  },
42
42
  "bundleDependencies": [
43
43
  "projectstore"