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.
- package/README.md +13 -12
- package/bin/projectstore-claude.mjs +2 -2
- package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
- package/node_modules/projectstore/.claude-plugin/plugin.json +1 -1
- package/node_modules/projectstore/README.md +229 -78
- package/node_modules/projectstore/commands/bind.md +5 -5
- package/node_modules/projectstore/commands/statusline.md +8 -8
- package/node_modules/projectstore/docs/extending.md +11 -6
- package/node_modules/projectstore/docs/getting-started.md +52 -10
- package/node_modules/projectstore/docs/harnesses.md +19 -10
- package/node_modules/projectstore/docs/how-it-works.md +1 -1
- package/node_modules/projectstore/harnesses/claude-code.json +35 -13
- package/node_modules/projectstore/harnesses/codex.json +8 -8
- package/node_modules/projectstore/package.json +1 -1
- package/node_modules/projectstore/scripts/agents-change.mjs +84 -0
- package/node_modules/projectstore/scripts/binding.mjs +40 -0
- package/node_modules/projectstore/scripts/cli.mjs +86 -85
- package/node_modules/projectstore/scripts/configure.mjs +908 -0
- package/node_modules/projectstore/scripts/fetch-shell.mjs +97 -34
- package/node_modules/projectstore/scripts/harness.mjs +8 -0
- package/node_modules/projectstore/scripts/install-harness.mjs +56 -19
- package/node_modules/projectstore/scripts/lib.mjs +41 -5
- package/package.json +3 -3
|
@@ -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)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
3
|
+
## Install
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
143
|
+
From a terminal in your project:
|
|
114
144
|
|
|
115
|
-
```
|
|
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
|
-
|
|
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
|
|
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
|
|
83
|
+
npx projectstore setup
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
17
|
-
"why_not_scripted": "Two channels.
|
|
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
|
-
"
|
|
20
|
-
"/plugin marketplace add SmartAndPoint/ProjectStore && /plugin install projectstore@SmartAndPoint
|
|
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
|
|
28
|
-
"Pin a git-marketplace release
|
|
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
|
|
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": "
|
|
497
|
-
"welcome": [
|
|
498
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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.
|
|
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
|