projectstore-claude 0.29.4 → 0.30.1

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.
@@ -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,8 +1,20 @@
1
1
  # Getting started with projectstore
2
2
 
3
- ## Install (local dev)
3
+ ## Install
4
4
 
5
- Until projectstore is published to a public marketplace, install it from a local clone.
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 what setup needs (Node.js 20 or later; npm only for Codex), the other package runners, the global form and the registry-mirror form. Setup binds the vault itself, so after it the project is ready: restart the agent and go on to the daily flow.
12
+
13
+ The next two sections are the other ways in: a checkout, for contributors, and the git marketplace inside a Claude Code session. After either of them, the first-time setup below binds the vault from inside a session.
14
+
15
+ ## From a checkout
16
+
17
+ For contributors working on projectstore itself. Clone it; [Running a checkout's build](../packaging/README.md#running-a-checkouts-build) covers the packed form.
6
18
 
7
19
  ```bash
8
20
  git clone https://github.com/SmartAndPoint/ProjectStore.git ~/Projects/SmartAndPoint/ProjectStore
@@ -28,7 +40,7 @@ Verify the plugin appears:
28
40
 
29
41
  You should see `projectstore` (displayName) with prefix `projectstore`.
30
42
 
31
- ## Install (via marketplace)
43
+ ## In a Claude Code session, from the git marketplace
32
44
 
33
45
  ```
34
46
  /plugin marketplace add SmartAndPoint/ProjectStore
@@ -37,6 +49,8 @@ You should see `projectstore` (displayName) with prefix `projectstore`.
37
49
 
38
50
  ## First-time setup
39
51
 
52
+ `npx projectstore setup` binds a vault or creates one, adds the agents block, and offers the status line, in one plan. The steps below do that from inside a Claude Code session, for the two ways in above, and also ask for the vault's policy and the agents' models; after setup, those two are [Configure](#configure)'s.
53
+
40
54
  1. **Pick a vault directory** — any folder where you want your project artifacts to live. Obsidian opens it natively. Git tracks it cleanly. There is nothing to create by hand: step 2 creates the vault when the folder does not exist yet or is empty.
41
55
 
42
56
  2. **Bind your current project to that vault**:
@@ -75,6 +89,22 @@ You should see `projectstore` (displayName) with prefix `projectstore`.
75
89
  npx projectstore scaffold --write
76
90
  ```
77
91
 
92
+ ## Configure
93
+
94
+ After setup, every setting is one command away, from a terminal in your project:
95
+
96
+ ```
97
+ npx projectstore configure
98
+ ```
99
+
100
+ It shows each agent's model, the status line and the band, the template language, and the vault's spec policy and lifecycle gates — with their values and the file each one lives in. Then it asks which to change, shows one plan and asks once. From a script or an agent, name the change; naming it is the confirmation:
101
+
102
+ ```
103
+ npx projectstore configure --set language=de --set lifecycle_gates=on
104
+ ```
105
+
106
+ The README's [Configure](../README.md#configure) section lists what each setting writes and which files are committed.
107
+
78
108
  ## Daily flow
79
109
 
80
110
  ```
@@ -110,12 +140,10 @@ Skills (`projectstore-decision-detector`, `projectstore-story-completion`) are p
110
140
 
111
141
  ## Disabling skills
112
142
 
113
- Edit `.projectstore/projectstore.json`:
143
+ From a terminal in your project:
114
144
 
115
- ```jsonc
116
- {
117
- "active_skills": false
118
- }
145
+ ```
146
+ npx projectstore configure --set active_skills=off
119
147
  ```
120
148
 
121
149
  ## Multi-language templates
@@ -126,12 +154,26 @@ Default is English (`en`). Also bundled: Russian (`ru`), Spanish (`es`), German
126
154
  /projectstore:bind <path> --lang de
127
155
  ```
128
156
 
129
- Or edit `language: "de"` in `.projectstore/projectstore.json` (templates must exist at `templates/de/`). The language also localizes the status line strings (e.g. the "no epic or story in this session yet" line).
157
+ A bound project changes it with `configure` (the value must be a bundled language). From a terminal in your project:
158
+
159
+ ```
160
+ npx projectstore configure --set language=de
161
+ ```
162
+
163
+ New artifacts take the new language's templates. The language also localizes the status line strings (e.g. the "no epic or story in this session yet" line).
130
164
 
131
165
  What the language does and does not change: section headings, table labels and prose are translated; frontmatter keys and their values (`status: planned`, `priority: p2`) stay English, because they are machine-read. Section headings are registered in `scaffold/headings.json`, so doctor, reconcile and the story lifecycle gates recognize every bundled language — a Russian-headed file lints in a French-bound vault, and mixed-language vaults reconcile.
132
166
 
133
167
  ## Updating to a new version
134
168
 
169
+ Installed with setup or from npm: from a terminal in your project, name the version you want to run:
170
+
171
+ ```
172
+ npx projectstore@<version> upgrade
173
+ ```
174
+
175
+ Installed from the git marketplace, in a Claude Code session:
176
+
135
177
  ```
136
178
  /plugin marketplace update SmartAndPoint
137
179
  /reload-plugins
@@ -139,4 +181,4 @@ What the language does and does not change: section headings, table labels and p
139
181
 
140
182
  Or enable auto-update once (`/plugin` → **Marketplaces** → **SmartAndPoint** → toggle **auto-update**) and Claude Code will detect new releases at startup.
141
183
 
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.
184
+ **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,33 @@ 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
+
99
+ `npx projectstore configure` changes Codex's settings as it does Claude Code's.
100
+ In a project that uses only Codex, it offers no status line and no band: Codex
101
+ has neither surface. An agent's model goes to `.projectstore/harness/codex.json`:
102
+ name it in Codex's own vocabulary with `--set agents.<name>=<model>`. The
103
+ terminal's model presets are Claude Code's, so they are not offered for Codex.
104
+
96
105
  What is known, and how:
97
106
 
98
107
  - **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": [
@@ -493,19 +494,40 @@
493
494
  ],
494
495
  "ui_vocabulary_reason": "This harness's UI affordances and tool names: words that mean nothing on another harness. The generator refuses them in another harness's rendered tree, and the runtime vocabulary lint refuses them in what the core prints for another harness (generation spec, contracts 6 and 18). A word here must be followed by a non-word character to count, so `/plugins/` in a path is not `/plugin`.",
495
496
  "update_hint": {
496
- "line": "See /plugin → Marketplaces to enable auto-update.",
497
- "welcome": ["**About future updates**: Claude Code does NOT auto-update third-party marketplaces by default. To get notified of new releases (v0.7+):", "1. Open `/plugin` → **Marketplaces** tab.", "2. Find **SmartAndPoint**.", "3. Toggle **auto-update** on.", "", "Without it, you'd run `/plugin marketplace update SmartAndPoint` manually. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details."],
498
- "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."
497
+ "line": "Update from a terminal in this project:\n{upgrade}",
498
+ "welcome": [
499
+ "**About future updates**: from a terminal in this project, run:",
500
+ "",
501
+ "```",
502
+ "{upgrade}",
503
+ "```",
504
+ "",
505
+ "Then restart Claude Code. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details."
506
+ ],
507
+ "by_channel": {
508
+ "marketplace": {
509
+ "line": "See /plugin → Marketplaces to enable auto-update.",
510
+ "welcome": [
511
+ "**About future updates**: Claude Code does NOT auto-update third-party marketplaces by default. To get notified of new releases (v0.7+):",
512
+ "1. Open `/plugin` → **Marketplaces** tab.",
513
+ "2. Find **SmartAndPoint**.",
514
+ "3. Toggle **auto-update** on.",
515
+ "",
516
+ "Without it, you'd run `/plugin marketplace update SmartAndPoint` manually. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details."
517
+ ]
518
+ }
519
+ },
520
+ "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, so what is printed runs as printed. `by_channel` holds the text for a copy that loads from another channel, by installChannel's answer: a copy from the git marketplace is updated by Claude Code itself, so its welcome keeps the auto-update toggle; the npm registration and a checkout get the npm form. 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
521
  },
500
522
  "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+):",
523
+ "**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
524
  "1. Open `/plugin` → **Marketplaces** tab.",
503
525
  "2. Find **SmartAndPoint**.",
504
526
  "3. Toggle **auto-update** on.",
505
527
  "",
506
528
  "Without it, you would run `/plugin marketplace update SmartAndPoint` manually. See https://github.com/SmartAndPoint/ProjectStore#upgrading for details.",
507
529
  "",
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."
530
+ "**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
531
  ],
510
532
  "agent_translation": {
511
533
  "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.1",
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",
@@ -0,0 +1,84 @@
1
+ // The change to a harness overlay's agents block (the configure spec,
2
+ // contract 8): planned once, here, and called by `agents configure` and by
3
+ // `configure`, so the clerk pin, the precedence and the overlay's other keys
4
+ // behave the same whichever verb asks. The gate stays the caller's.
5
+ import { readOverlayAt, writeOverlayAt } from "./lib.mjs";
6
+ import { harnessForOverlay } from "./harness.mjs";
7
+
8
+ // What a change would write. `before` is readOverlayAt's read; `roster` the
9
+ // layout's agent names (null when no layout loads); `agents` the
10
+ // `<name>=<model>` specs. A malformed spec or a name outside the roster comes
11
+ // back as `usage`, with nothing planned.
12
+ export function planAgentsChange({ before, harness, layout, roster, defaultModel, agents = [], reset = false }) {
13
+ // --reset empties the block first; --default and --agent then apply on top of
14
+ // the emptied block — "reset, then set" is one call, and nothing given is
15
+ // silently dropped.
16
+ const base = reset ? { default: null, per_agent: {} } : before.agents;
17
+ const next = {
18
+ default: defaultModel !== undefined ? defaultModel || null : base.default,
19
+ per_agent: { ...base.per_agent },
20
+ };
21
+ for (const spec of agents) {
22
+ const m = /^([a-z][a-z0-9-]*)=(.*)$/.exec(spec);
23
+ if (!m) return { usage: `--agent takes <name>=<model>, got ${spec}` };
24
+ // A name outside the roster would be written, listed, and never run.
25
+ if (roster && !roster.includes(m[1]))
26
+ return { usage: `no agent named ${m[1]} in the ${layout} roster — known: ${roster.join(", ")}` };
27
+ if (m[2]) next.per_agent[m[1]] = m[2];
28
+ else delete next.per_agent[m[1]];
29
+ }
30
+ // The clerk transcribes; a strong default must not lift it (commands/agents.md,
31
+ // ADR-008). WHICH model it is pinned to is the harness's vocabulary, so the
32
+ // manifest names it and this file does not: pinning every harness to
33
+ // "sonnet" wrote an Anthropic model name into a Codex overlay, which is a
34
+ // model that does not exist there. A manifest with no cheap model declared
35
+ // does not pin, and the preview says so rather than inventing one.
36
+ // No fallback model here, deliberately, and it is not the same call as
37
+ // SOURCE_WRITE_TOOLS_FALLBACK: a missing manifest cannot reach this line.
38
+ // harnessIds() derives from the manifests, so with none loadable a named
39
+ // harness is refused as unknown before any caller plans a change (`agents
40
+ // configure` and `configure` both check it first). A literal here would be
41
+ // the deleted `"sonnet"` restored for a case that cannot happen.
42
+ let pinnedClerk = false;
43
+ let clerkPinUnavailable = false;
44
+ const clerkAsked = agents.some((s) => s.startsWith("clerk="));
45
+ if (next.default && !next.per_agent.clerk && !clerkAsked) {
46
+ const cheap = harnessForOverlay(harness)?.agent_translation?.cheap_model || null;
47
+ if (cheap) {
48
+ next.per_agent.clerk = cheap;
49
+ pinnedClerk = true;
50
+ } else clerkPinUnavailable = true;
51
+ }
52
+ // Keys inside the agents block that the allowlist rejected (an `effort`, a
53
+ // stray shape) are rewritten out by the write and announced here; keys
54
+ // outside the block are kept, and stay doctor's to name.
55
+ const dropped = before.rejected.filter((k) => k.startsWith("agents."));
56
+ const same =
57
+ !dropped.length &&
58
+ JSON.stringify({ d: before.agents.default, p: before.agents.per_agent }) ===
59
+ JSON.stringify({ d: next.default, p: next.per_agent });
60
+ const preview = [
61
+ `agents configure — ${harness} — ${before.path}`,
62
+ ` default: ${before.agents.default || "—"} → ${next.default || "—"}`,
63
+ ];
64
+ const names = new Set([...Object.keys(before.agents.per_agent), ...Object.keys(next.per_agent)]);
65
+ for (const n of [...names].sort())
66
+ preview.push(
67
+ ` ${n}: ${before.agents.per_agent[n] || "—"} → ${next.per_agent[n] || "—"}${pinnedClerk && n === "clerk" ? " (pinned: the clerk stays cheap under a strong default)" : ""}`,
68
+ );
69
+ // Silence here would read as "the clerk is fine", and it is not: it takes the
70
+ // strong default, which is the one thing ADR-008 says must not happen to it.
71
+ if (clerkPinUnavailable)
72
+ preview.push(
73
+ ` clerk: takes the default — ${harness} declares no cheap model to pin it to, so name one with --agent clerk=<model>`,
74
+ );
75
+ if (dropped.length)
76
+ preview.push(` drops from the agents block (an overlay carries only models): ${dropped.join(", ")}`);
77
+ return { next, pinnedClerk, clerkPinUnavailable, dropped, same, preview };
78
+ }
79
+
80
+ // Writes a planned change through the overlay's one writer and reads it back.
81
+ export function applyAgentsChange(projectDir, harness, next) {
82
+ const path = writeOverlayAt(projectDir, harness, next);
83
+ return { path, after: readOverlayAt(projectDir, harness) };
84
+ }
@@ -135,6 +135,46 @@ function readExisting(configPath) {
135
135
  }
136
136
  }
137
137
 
138
+ // The binding's plain settings and their one writer (the configure spec,
139
+ // contract 5). Every other key of the binding has a writer of its own: the
140
+ // vault path is rebind's, the band the band verb's, the status line's flag
141
+ // install's.
142
+ export const BINDING_SETTING_KEYS = Object.freeze([
143
+ "language",
144
+ "active_skills",
145
+ "auto_inject",
146
+ "default_author",
147
+ "tags",
148
+ ]);
149
+
150
+ // What a change to named plain keys would write: never a binding that does
151
+ // not exist (a bare file of settings would read as bound), never over one
152
+ // that does not parse.
153
+ export function planBindingKeys(projectDir, changes, { env = process.env } = {}) {
154
+ const path = harnessConfigPath(projectDir, env);
155
+ const unknown = Object.keys(changes).filter((k) => !BINDING_SETTING_KEYS.includes(k));
156
+ if (unknown.length) throw new Error(`planBindingKeys: not a binding setting: ${unknown.join(", ")}`);
157
+ const { cfg, corrupt } = readExisting(path);
158
+ if (corrupt || (cfg && (typeof cfg !== "object" || Array.isArray(cfg))))
159
+ return { path, refusal: { code: "UNREADABLE", message: `${path} is not valid JSON — fix or remove it first` } };
160
+ if (!cfg) return { path, refusal: { code: "UNBOUND", message: `${projectDir} is not bound to a vault` } };
161
+ const keys = Object.keys(changes).filter((k) => JSON.stringify(cfg[k]) !== JSON.stringify(changes[k]));
162
+ const before = Object.fromEntries(keys.map((k) => [k, cfg[k]]));
163
+ const after = Object.fromEntries(keys.map((k) => [k, changes[k]]));
164
+ return { path, keys, before, after, changed: keys.length > 0 };
165
+ }
166
+
167
+ // Applies a plan: reads the binding again and sets only the planned keys, so
168
+ // a write another writer made since the plan (the band, the flag) is kept.
169
+ export function applyBindingKeys(plan) {
170
+ if (plan.refusal || !plan.changed) return { wrote: false };
171
+ const { cfg, corrupt } = readExisting(plan.path);
172
+ if (corrupt || !cfg || typeof cfg !== "object" || Array.isArray(cfg))
173
+ throw new Error(`${plan.path} changed since the plan and no longer reads as a binding`);
174
+ writeFileAtomic(plan.path, JSON.stringify({ ...cfg, ...plan.after }, null, 2) + "\n", { sweep: false });
175
+ return { wrote: true };
176
+ }
177
+
138
178
  export const shellQuote = (s) => (/^[A-Za-z0-9_/.~-]+$/.test(s) ? s : `'${String(s).replace(/'/g, `'\\''`)}'`);
139
179
 
140
180
  // A directory with no entries at all. A `.DS_Store` makes it not empty: that