projectstore-claude 0.30.0 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/projectstore-claude.mjs +1 -1
- 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 +92 -0
- package/node_modules/projectstore/commands/bind.md +5 -5
- package/node_modules/projectstore/commands/statusline.md +8 -8
- package/node_modules/projectstore/docs/getting-started.md +33 -11
- package/node_modules/projectstore/docs/harnesses.md +6 -0
- package/node_modules/projectstore/harnesses/claude-code.json +76 -4
- package/node_modules/projectstore/harnesses/codex.json +32 -1
- 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-menu.mjs +589 -0
- package/node_modules/projectstore/scripts/configure.mjs +919 -0
- package/node_modules/projectstore/scripts/harness.mjs +21 -3
- package/node_modules/projectstore/scripts/install-harness.mjs +29 -11
- package/node_modules/projectstore/scripts/lib.mjs +41 -5
- package/node_modules/projectstore/scripts/menu.mjs +742 -0
- package/node_modules/projectstore/scripts/models.mjs +505 -0
- package/node_modules/projectstore/scripts/term.mjs +167 -0
- package/package.json +2 -2
|
@@ -24,7 +24,7 @@ const HARNESS = "claude-code";
|
|
|
24
24
|
// scripts/cli.mjs; the packaging test pins it). Any other verb — doctor,
|
|
25
25
|
// status, search, --version — passes through untouched: the core refuses an
|
|
26
26
|
// option a verb does not declare, so a blanket insert would break `doctor`.
|
|
27
|
-
const HARNESS_VERBS = new Set(["setup","plan","install","uninstall","upgrade","agents"]);
|
|
27
|
+
const HARNESS_VERBS = new Set(["setup","configure","plan","install","uninstall","upgrade","agents"]);
|
|
28
28
|
|
|
29
29
|
// The bundled core, by path — never by package resolution: a hoisted or global
|
|
30
30
|
// copy at another version is exactly the pairing the pin exists to prevent,
|
|
@@ -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.30.
|
|
15
|
+
"version": "0.30.2",
|
|
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.30.
|
|
4
|
+
"version": "0.30.2",
|
|
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",
|
|
@@ -85,6 +85,30 @@ npx projectstore setup
|
|
|
85
85
|
|
|
86
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.
|
|
87
87
|
|
|
88
|
+
**What it needs:**
|
|
89
|
+
|
|
90
|
+
- Node.js 20 or later, always. The plugin's hooks, its MCP server and the status line run as `node` inside the agent session.
|
|
91
|
+
- npm, for Codex only: setup fetches the Codex plugin through npm. Claude Code alone needs no npm.
|
|
92
|
+
- The agent's own CLI on PATH (`claude`, `codex`), and git for a new vault.
|
|
93
|
+
- A terminal outside every Claude Code and Codex session: inside one, the registration waits and the plan says so.
|
|
94
|
+
- macOS or Linux. Windows is not verified yet.
|
|
95
|
+
|
|
96
|
+
Other package runners start setup the same way. Each was observed running it with Claude Code and Codex:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
bunx projectstore@latest setup
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
pnpm dlx projectstore@latest setup
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
deno run -A npm:projectstore@latest setup
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`pnpm dlx` needs pnpm 6.13 or later. Without npm on PATH, a runner sets up Claude Code and refuses Codex, naming npm. The runner only fetches the package: setup copies the plugin into the agents' own directories, so clearing the runner's cache later breaks nothing.
|
|
111
|
+
|
|
88
112
|
`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
113
|
|
|
90
114
|
If you prefer a global command:
|
|
@@ -176,6 +200,37 @@ npx projectstore scaffold --write
|
|
|
176
200
|
`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).
|
|
177
201
|
</details>
|
|
178
202
|
|
|
203
|
+
## Configure
|
|
204
|
+
|
|
205
|
+
Setup asks only what a first install needs. Everything after it is one command, from a terminal in your project:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
npx projectstore configure
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
At a terminal it opens a settings menu that works like a monitor's: ↑ and ↓ move, → opens a group or a setting in a frame beside it, space chooses, ← goes back, and Enter shows the plan. A second Enter writes it. Under the frames, a few lines say what the row under the cursor means. The settings:
|
|
212
|
+
|
|
213
|
+
- each agent's model, per harness. Claude Code offers its aliases: `fable`, `opus`, `sonnet` and `haiku`. Codex offers its own list of models, with its descriptions. With `ANTHROPIC_API_KEY` set, each alias also names the newest model of its line. A list is kept for a day in projectstore's user cache, and "refresh the list" asks again;
|
|
214
|
+
- the status line and the band above the prompt (Claude Code);
|
|
215
|
+
- the vault's template language, the default author and tags;
|
|
216
|
+
- the vault's spec policy and lifecycle gates.
|
|
217
|
+
|
|
218
|
+
Without a terminal it prints every setting with its current value and where it lives, and writes nothing. To change a setting there, name it with `--set`; naming it is the confirmation:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
npx projectstore configure --set agents.critic=fable --set statusline=on
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`--dry-run` shows the plan and writes nothing, and `--json` returns the settings as one envelope. In a project that uses two harnesses, an agent's model names its overlay with `--harness <id>`.
|
|
225
|
+
|
|
226
|
+
Each setting has exactly one writer, and the plan says which file it changes:
|
|
227
|
+
|
|
228
|
+
- **The agents' models** live in `.projectstore/harness/<harness>.json`. That file is committed, so a change there is a team change.
|
|
229
|
+
- **The status line, the band, the language and the other binding keys** live in `.projectstore/projectstore.json`, which stays on this machine.
|
|
230
|
+
- **The spec policy and the lifecycle gates** live in the vault's `.projectstore.json`, which commits with the vault. Setting `spec_policy=required` records when it took effect; stories closed before then are exempt.
|
|
231
|
+
|
|
232
|
+
The vault itself is changed by `bind --rebind`, which configure names when asked. In a Claude Code session, `/projectstore:statusline` and `/projectstore:agents configure` run the same writers.
|
|
233
|
+
|
|
179
234
|
## Upgrading
|
|
180
235
|
|
|
181
236
|
Installed with setup or from npm: from a terminal in your project, name the version you want to run (`latest` is the newest):
|
|
@@ -346,8 +401,45 @@ A Claude Code install from the git marketplace is removed in the session:
|
|
|
346
401
|
/plugin uninstall projectstore@SmartAndPoint
|
|
347
402
|
```
|
|
348
403
|
|
|
404
|
+
**What a removal keeps:**
|
|
405
|
+
|
|
406
|
+
- the vault;
|
|
407
|
+
- the binding `.projectstore/projectstore.json`, with the status-line choice, so a later setup wires the line again;
|
|
408
|
+
- the agents' models in `.projectstore/harness/`, which is committed team configuration;
|
|
409
|
+
- Codex's user-wide registration, until `--global` removes it.
|
|
410
|
+
|
|
411
|
+
`--global` removes the Codex plugin for every other project on this machine too. Their `AGENTS.md` blocks stay and name agents that no longer load: uninstall Codex in each of them as well, or set them up again.
|
|
412
|
+
|
|
349
413
|
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.
|
|
350
414
|
|
|
415
|
+
### Reinstalling clean
|
|
416
|
+
|
|
417
|
+
Keeping the vault and the binding, uninstall and set up again. The second setup finds both:
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
npx projectstore uninstall
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
npx projectstore setup
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
To start as if the project was never bound, remove only the machine-local half after the uninstall. `.projectstore/harness/` and `.projectstore/.gitignore` are committed; keep them. The binding also carries the template language, the default author and tags, the skills switch, the band and the status-line choice, and removing it drops them. Setup then asks for the vault again.
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
rm -f .projectstore/projectstore.json
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
rm -rf .projectstore/state
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
To point the project at another vault, no reinstall is needed. From a terminal in your project:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
npx projectstore bind ~/vaults/other --rebind
|
|
441
|
+
```
|
|
442
|
+
|
|
351
443
|
## Extending
|
|
352
444
|
|
|
353
445
|
See [`docs/extending.md`](./docs/extending.md) for adding layouts, templates, and skills, and [`docs/harnesses.md`](./docs/harnesses.md) for which coding agents projectstore runs on — what "experimental" means there, and what adding one takes.
|
|
@@ -3,7 +3,7 @@ description: Bind this project to an Obsidian vault (or any markdown directory)
|
|
|
3
3
|
argument-hint: <vault-path> | --inherit [--layout engineering] [--lang en|ru|es|de|fr|zh]
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are binding the current project to a markdown vault for projectstore. The config is written by the core's `bind` / `init` verbs (roadmap A8); this interview decides the values, previews them, and asks — it never writes `projectstore.json` itself, except on the inherit path (step 0a), which copies the parent's file verbatim
|
|
6
|
+
You are binding the current project to a markdown vault for projectstore. The config is written by the core's `bind` / `init` verbs (roadmap A8); this interview decides the values, previews them, and asks — it never writes `projectstore.json` itself, except on the inherit path (step 0a), which copies the parent's file verbatim. The settings later steps change — the vault policy (step 7.5) and the status line (step 11) — go through `configure --set`, each key's one writer. The interview's `--lang` is the verb's `--language`.
|
|
7
7
|
|
|
8
8
|
Parse `$ARGUMENTS`:
|
|
9
9
|
- First positional arg: vault path. Expand `~` if present.
|
|
@@ -81,7 +81,7 @@ Steps:
|
|
|
81
81
|
node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" bind "<vault-path>" [--layout <name>] [--language <code>] [--rebind]
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
`init "<vault-path>" …` instead when step 1 chose to create the vault — it creates the whole vault, so step 7 has nothing to offer after it; `--rebind` only when step 0 ended on **Replace bind**. Naming the vault is the verb's confirmation (there is no `--yes`); the interview's AskUserQuestion in step 4 is the in-session gate. Print the verb's output. A non-zero exit is a refusal or a usage error — relay it and stop — except an `init` whose output reports `git init failed` or `Scaffold failed`: the binding is written, so relay the remedy line it prints and continue.
|
|
84
|
+
`init "<vault-path>" …` instead when step 1 chose to create the vault — it creates the whole vault, so step 7 has nothing to offer after it; `--rebind` only when step 0 ended on **Replace bind**. Naming the vault is the verb's confirmation (there is no `--yes`); the interview's AskUserQuestion in step 4 is the in-session gate. Print the verb's output. A non-zero exit is a refusal or a usage error — relay it and stop — except an `init` whose output reports `git init failed` or `Scaffold failed`: the binding is written, so relay the remedy line it prints and continue.
|
|
85
85
|
|
|
86
86
|
6. **Check `.gitignore`**: read `<project>/.gitignore` if it exists. Our own files are self-ignored inside `.projectstore/` — the verb writes that `.gitignore` itself, carrying `projectstore.json` and `state/`, with `harness/` committed on purpose (the layout ADR); on the inherit path of step 0a, which bypasses the verb, you wrote both by hand. Unless `.claude/` is ignored wholesale, the one machine-specific entry left is the host's `.claude/settings.local.json`. If it is missing, offer (AskUserQuestion) to append it. If the user declines, skip silently.
|
|
87
87
|
|
|
@@ -97,7 +97,7 @@ Steps:
|
|
|
97
97
|
- If it already exists with a `spec_policy` key — respect it, print the current policy, do not re-ask.
|
|
98
98
|
- **New bind into a vault with no artifacts** (a vault `init` just made counts: its folders and README indexes are not artifacts): ask via AskUserQuestion — "Enable spec-first policy for this vault (every story must be covered by a spec; doctor enforces it)?" with options **Yes, `spec_policy: required` (Recommended)** / **Not yet, `optional`**. Second question: "Enable lifecycle gates (plan/close sections + evidence checks on stories)?" — **Yes, `lifecycle_gates: on` (Recommended)** / **Off for now**.
|
|
99
99
|
- **Bind to an existing vault with artifacts**: default to `spec_policy: optional`, `lifecycle_gates: off` and say doctor will suggest enabling once specs appear. Do not impose the gate on an existing backlog.
|
|
100
|
-
- On any choice,
|
|
100
|
+
- On any choice, run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set spec_policy=<required|optional> --set lifecycle_gates=<on|off> --dry-run --project "${CLAUDE_PROJECT_DIR}"` and show its PLAN, then ask via AskUserQuestion: "Record this policy in the vault? [Yes / No]". On **Yes**, run the same command without `--dry-run`; a non-zero exit is a refusal — relay it and go on to step 8 (the binding is already written). It writes `<vault>/.projectstore.json` — vault ROOT, deliberately not inside `<vault>/.projectstore/`, whose .gitignore would keep the policy out of git — and keeps any other key in it:
|
|
101
101
|
|
|
102
102
|
```json
|
|
103
103
|
{
|
|
@@ -107,7 +107,7 @@ Steps:
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`spec_policy_since`
|
|
110
|
+
configure stamps `spec_policy_since` whenever spec_policy becomes `required` — it anchors the legacy exemption (stories done before it stay exempt; stories in progress/review at enable time are in scope).
|
|
111
111
|
|
|
112
112
|
8. **Agent registration** (v0.13, ADR-002): ask via AskUserQuestion — "Register projectstore's agents in CLAUDE.md/AGENTS.md so every session routes to them (critic after authoring artifacts, planner before implementing, reviewer before commit)? [Yes (Recommended) / No]". On Yes, run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" install --harness claude-code --surface agents_block --project "${CLAUDE_PROJECT_DIR}"` and print its output (it renders from the layout's roster, migrates rather than duplicates, previews every write, and applies because the harness is named). On a rebind where a block already exists the verb reports it current or replaces it in place — do not re-ask blindly.
|
|
113
113
|
|
|
@@ -121,4 +121,4 @@ Steps:
|
|
|
121
121
|
|
|
122
122
|
(for `ru`: `[PS#<version>] 📚 Супер-фича в супер-продукте › Ручка для туалетной бумаги (in-progress)`; every bundled language ships its own example pair)
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
First run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=on --dry-run --project "${CLAUDE_PROJECT_DIR}"` and show its PLAN; a non-zero exit is a refusal (a foreign status line, an unparseable settings file) — relay it and skip this step. Then ask via AskUserQuestion: "Show your current epic/story in the status line, composed above any existing HUD? [Yes / No]". On Yes, run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=on --project "${CLAUDE_PROJECT_DIR}"` and print its output — a non-zero exit is a refusal to relay (it records `statusline.enabled: true` and writes the `settings.local.json` entry and the launcher, in one plan), and report: "Enabled — restart Claude Code in this project to apply. A fresh session shows: `[PS#<version>] 📚 <statusline_no_work>`."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Enable/disable the projectstore status line (current epic & story in the HUD).
|
|
2
|
+
description: Enable/disable the projectstore status line (current epic & story in the HUD). Runs `configure --set statusline=on|off`, which records the flag and wires or removes the entry behind a preview; the SessionStart hook keeps an existing entry current across plugin updates.
|
|
3
3
|
argument-hint: "on | off | status"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ You are toggling the projectstore status line for THIS project. When enabled, `i
|
|
|
7
7
|
|
|
8
8
|
Resolution is **per-session with zero cross-session and zero vault reads** (ADR-006): the 📚 segment comes from this session's pointer (`.projectstore/state/sessions/<session_id>.json`, maintained by the tool hooks — a write's patch after the write — by `story close`, and at session start; a done, closed or parked story leaves the segment); a fresh session shows an explicit localized cold-start line ("No epic or story in this session yet" / «Эпик и стори ещё не в работе в этой сессии») — never a silent blank; a corrupt pointer shows an error-marked string. Strings localize via `templates/<lang>/strings.json` (en fallback). The version badge is controlled by `statusline.show_version` (default true).
|
|
9
9
|
|
|
10
|
-
This command
|
|
10
|
+
This command runs `configure --set statusline=on|off` (the configure spec, contracts 6 and 9): install's own items record `statusline.enabled` and write or remove the `settings.local.json` entry and the launcher, in one plan. The command never writes `projectstore.json` itself. The SessionStart hook only refreshes an entry that is already ours and removes it when the flag is off — it never creates one.
|
|
11
11
|
|
|
12
12
|
## 1. Require a bound project
|
|
13
13
|
|
|
@@ -21,15 +21,15 @@ Read `.projectstore/projectstore.json`. If missing → "No vault bound. Run `/pr
|
|
|
21
21
|
|
|
22
22
|
## 3. `on`
|
|
23
23
|
|
|
24
|
-
1. **Foreign status line check** (read-only): read `.claude/settings.local.json` if present. If it has a `statusLine` whose `command` is **not** ours (ours contains either `.projectstore/state/<harness>/statusline.mjs` — the launcher; a not-yet-migrated project's legacy launcher is `.claude/.projectstore/statusline.mjs`, also ours — or `scripts/statusline.mjs` — an older or dev wiring), warn —
|
|
25
|
-
2. **
|
|
26
|
-
3. On
|
|
24
|
+
1. **Foreign status line check** (read-only): read `.claude/settings.local.json` if present. If it has a `statusLine` whose `command` is **not** ours (ours contains either `.projectstore/state/<harness>/statusline.mjs` — the launcher; a not-yet-migrated project's legacy launcher is `.claude/.projectstore/statusline.mjs`, also ours — or `scripts/statusline.mjs` — an older or dev wiring), warn — configure refuses to enable over a foreign local status line (and `statusline.mjs` composes only over a base in `.claude/settings.json` or `~/.claude/settings.json`, never one in `settings.local.json`). AskUserQuestion: **Help me clear it / Cancel**. If a base HUD lives in `~/.claude/settings.json` (e.g. oh-my-claudecode), that's fine — we compose over it; no warning needed.
|
|
25
|
+
2. **Preview**: run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=on --dry-run --project "${CLAUDE_PROJECT_DIR}"` and print its PLAN. A non-zero exit is a refusal — relay it and stop; nothing is asked or written.
|
|
26
|
+
3. **AskUserQuestion** (Yes / No): "Wire the status line for this project?" On Yes, run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=on --project "${CLAUDE_PROJECT_DIR}"` and print its output verbatim — it records `statusline.enabled: true` (keeping `statusline.position` and every other key) and writes the `statusLine` entry and the launcher (`.projectstore/state/claude-code/statusline.mjs`, provenance-stamped), in one plan. A non-zero exit is a refusal (a foreign status line, an unparseable settings file, a layout still to move) — relay it and stop; nothing was written.
|
|
27
27
|
4. Report: "Enabled. `📚 <epic> › <story>` renders above your existing HUD. **Restart Claude Code in this project** to apply now (statusLine loads at session start). If `.claude/settings.local.json` is tracked in git, add it to `.gitignore` — the entry carries a machine-specific absolute path."
|
|
28
28
|
|
|
29
29
|
## 4. `off`
|
|
30
30
|
|
|
31
|
-
1. **
|
|
32
|
-
2.
|
|
31
|
+
1. **Preview**: run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=off --dry-run --project "${CLAUDE_PROJECT_DIR}"` and print its PLAN. A non-zero exit is a refusal — relay it and stop.
|
|
32
|
+
2. **AskUserQuestion** (Yes / No): "Remove the projectstore status line from this project?" On Yes, run `node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" configure --set statusline=off --project "${CLAUDE_PROJECT_DIR}"` and print its output — it records `statusline.enabled: false` (even if the flag was absent, so the hook keeps it off) and removes our `statusLine` entry and the launcher, and leaves a foreign entry alone.
|
|
33
33
|
3. Report: "Disabled; restart to apply."
|
|
34
34
|
|
|
35
35
|
## 5. `status`
|
|
@@ -42,5 +42,5 @@ Report, read-only:
|
|
|
42
42
|
## Notes
|
|
43
43
|
|
|
44
44
|
- Every write goes through AskUserQuestion. Never write without approval.
|
|
45
|
-
- The command
|
|
45
|
+
- The command runs `configure --set statusline=on|off`, whose install items are the flag's one writer; the SessionStart hook only refreshes an entry that is already ours (and removes it when the flag is off) — it never creates one, and never touches a foreign status line.
|
|
46
46
|
- Config shape: `.projectstore/projectstore.json` → `"statusline": { "enabled": true, "position": "above", "show_version": true }`. `position` is `above` (default) or `below` — the side our 📚 line sits relative to the base HUD; `show_version` toggles the `[PS#…]` badge.
|
|
@@ -8,13 +8,13 @@ From a terminal in your project:
|
|
|
8
8
|
npx projectstore setup
|
|
9
9
|
```
|
|
10
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
|
|
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
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.
|
|
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
14
|
|
|
15
|
-
##
|
|
15
|
+
## From a checkout
|
|
16
16
|
|
|
17
|
-
|
|
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.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
git clone https://github.com/SmartAndPoint/ProjectStore.git ~/Projects/SmartAndPoint/ProjectStore
|
|
@@ -40,7 +40,7 @@ Verify the plugin appears:
|
|
|
40
40
|
|
|
41
41
|
You should see `projectstore` (displayName) with prefix `projectstore`.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## In a Claude Code session, from the git marketplace
|
|
44
44
|
|
|
45
45
|
```
|
|
46
46
|
/plugin marketplace add SmartAndPoint/ProjectStore
|
|
@@ -49,6 +49,8 @@ You should see `projectstore` (displayName) with prefix `projectstore`.
|
|
|
49
49
|
|
|
50
50
|
## First-time setup
|
|
51
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
|
+
|
|
52
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.
|
|
53
55
|
|
|
54
56
|
2. **Bind your current project to that vault**:
|
|
@@ -87,6 +89,22 @@ You should see `projectstore` (displayName) with prefix `projectstore`.
|
|
|
87
89
|
npx projectstore scaffold --write
|
|
88
90
|
```
|
|
89
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
|
+
At a terminal it opens a settings menu: each agent's model, the status line and the band, the template language, and the vault's spec policy and lifecycle gates. ↑ and ↓ move, → opens, space chooses, ← goes back, and Enter shows one plan; a second Enter writes it. A model is chosen from its harness's list: Claude Code's aliases, or Codex's own models. Without a terminal it prints the settings with their values and the file each one lives in. 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
|
+
|
|
90
108
|
## Daily flow
|
|
91
109
|
|
|
92
110
|
```
|
|
@@ -122,12 +140,10 @@ Skills (`projectstore-decision-detector`, `projectstore-story-completion`) are p
|
|
|
122
140
|
|
|
123
141
|
## Disabling skills
|
|
124
142
|
|
|
125
|
-
|
|
143
|
+
From a terminal in your project:
|
|
126
144
|
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
"active_skills": false
|
|
130
|
-
}
|
|
145
|
+
```
|
|
146
|
+
npx projectstore configure --set active_skills=off
|
|
131
147
|
```
|
|
132
148
|
|
|
133
149
|
## Multi-language templates
|
|
@@ -138,7 +154,13 @@ Default is English (`en`). Also bundled: Russian (`ru`), Spanish (`es`), German
|
|
|
138
154
|
/projectstore:bind <path> --lang de
|
|
139
155
|
```
|
|
140
156
|
|
|
141
|
-
|
|
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).
|
|
142
164
|
|
|
143
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.
|
|
144
166
|
|
|
@@ -96,6 +96,12 @@ project-local. Ordinary uninstall leaves the global plugin in place; add
|
|
|
96
96
|
build runs through the same flow: [`packaging/README.md`](../packaging/README.md#running-a-checkouts-build)
|
|
97
97
|
has the tarball loop.
|
|
98
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
|
+
|
|
99
105
|
What is known, and how:
|
|
100
106
|
|
|
101
107
|
- **Hooks load from Codex's default file, so the shell carries no root
|
|
@@ -494,9 +494,30 @@
|
|
|
494
494
|
],
|
|
495
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`.",
|
|
496
496
|
"update_hint": {
|
|
497
|
-
"line": "
|
|
498
|
-
"welcome": [
|
|
499
|
-
|
|
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."
|
|
500
521
|
},
|
|
501
522
|
"update_instructions": [
|
|
502
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+):",
|
|
@@ -512,6 +533,57 @@
|
|
|
512
533
|
"carries_model": true,
|
|
513
534
|
"cheap_model": "sonnet",
|
|
514
535
|
"cheap_model_evidence": "measured",
|
|
515
|
-
"cheap_model_reason": "The model the clerk is pinned to when a strong default would otherwise lift it (ADR-008). A literal here, in the one file allowed to name a harness's vocabulary; it used to be a literal in scripts/cli.mjs, which made `agents configure --harness codex` write an Anthropic model name into a Codex overlay. MEASURED in the sense that matters: this is the model the clerk has been running on since ADR-008, in this repository, and tests/overlay.test.mjs asserts the pin end to end."
|
|
536
|
+
"cheap_model_reason": "The model the clerk is pinned to when a strong default would otherwise lift it (ADR-008). A literal here, in the one file allowed to name a harness's vocabulary; it used to be a literal in scripts/cli.mjs, which made `agents configure --harness codex` write an Anthropic model name into a Codex overlay. MEASURED in the sense that matters: this is the model the clerk has been running on since ADR-008, in this repository, and tests/overlay.test.mjs asserts the pin end to end.",
|
|
537
|
+
"models": {
|
|
538
|
+
"spawn_takes": "alias",
|
|
539
|
+
"spawn_takes_evidence": "measured",
|
|
540
|
+
"spawn_takes_reason": "The Agent tool's `model` parameter is an enum, [\"sonnet\", \"opus\", \"haiku\", \"fable\"], in the tool schema Claude Code 2.1.296 gave a session on 2026-10-13. The registration block tells the session to pass the overlay's model as that parameter, so an overlay value outside the enum fails at every spawn of that agent. Only an agent definition's frontmatter takes a full model name, and ADR-008 retired the copies that carried one. So the list offers the aliases, never an exact id (the ADR \"Agent model lists are read from each harness's own catalog, declared in its manifest and cached for a day\", decision 1).",
|
|
541
|
+
"static": [
|
|
542
|
+
{
|
|
543
|
+
"id": "fable",
|
|
544
|
+
"family": "fable",
|
|
545
|
+
"description": "Follows the newest Fable model: Claude Code resolves the alias when it starts the agent."
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"id": "opus",
|
|
549
|
+
"family": "opus",
|
|
550
|
+
"description": "Follows the newest Opus model: Claude Code resolves the alias when it starts the agent."
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"id": "sonnet",
|
|
554
|
+
"family": "sonnet",
|
|
555
|
+
"description": "Follows the newest Sonnet model: Claude Code resolves the alias when it starts the agent."
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"id": "haiku",
|
|
559
|
+
"family": "haiku",
|
|
560
|
+
"description": "Follows the newest Haiku model: Claude Code resolves the alias when it starts the agent."
|
|
561
|
+
}
|
|
562
|
+
],
|
|
563
|
+
"static_reason": "The enum above, in the order the menu lists it. A line the catalog below reports that this list lacks is never offered: a Claude Code release that adds an alias is named with `--set`, with the warning, until this list learns it.",
|
|
564
|
+
"catalogs": [
|
|
565
|
+
{
|
|
566
|
+
"kind": "http",
|
|
567
|
+
"source": "the Anthropic API",
|
|
568
|
+
"caption": "Anthropic API",
|
|
569
|
+
"url": "https://api.anthropic.com/v1/models?limit=1000",
|
|
570
|
+
"base_env": "ANTHROPIC_BASE_URL",
|
|
571
|
+
"path": "/v1/models?limit=1000",
|
|
572
|
+
"key_env": "ANTHROPIC_API_KEY",
|
|
573
|
+
"key_header": "x-api-key",
|
|
574
|
+
"headers": { "anthropic-version": "2023-06-01" },
|
|
575
|
+
"fields": {
|
|
576
|
+
"list": "data",
|
|
577
|
+
"id": "id",
|
|
578
|
+
"label": "display_name",
|
|
579
|
+
"family": "line",
|
|
580
|
+
"order": "created_at",
|
|
581
|
+
"order_desc": true,
|
|
582
|
+
"keep_when": { "lifecycle": "active" }
|
|
583
|
+
},
|
|
584
|
+
"reason": "Measured 2026-10-13: 14 models in 0.5–0.7 s, each with id, display_name, created_at, line (fable, opus, sonnet, haiku) and lifecycle. `line` is the family an alias follows, so the newest active entry of a line is the best local account of what that alias starts; Claude Code resolves the alias itself. Read only when a model list opens with a stale cache, or on refresh, and only with ANTHROPIC_API_KEY set; ANTHROPIC_BASE_URL, when set, is the base the key belongs to."
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
}
|
|
516
588
|
}
|
|
517
589
|
}
|
|
@@ -357,6 +357,37 @@
|
|
|
357
357
|
"effort_key": "model_reasoning_effort",
|
|
358
358
|
"effort_evidence": "documented",
|
|
359
359
|
"effort_precedence": "explicit spawn value, then the [agents] default from config, then the parent agent's value. A model chosen without an effort uses that model's default effort.",
|
|
360
|
-
"effort_reason": "Recorded, not yet consumed: the overlay's allowlist rejects an effort key and rewrites it out, deliberately, until the story Per-agent effort returns decides whether a role's effort travels in a rendered definition or at spawn. Documented 2026-09-08; the `ultra` level is Codex's and is not a level Claude Code accepts."
|
|
360
|
+
"effort_reason": "Recorded, not yet consumed: the overlay's allowlist rejects an effort key and rewrites it out, deliberately, until the story Per-agent effort returns decides whether a role's effort travels in a rendered definition or at spawn. Documented 2026-09-08; the `ultra` level is Codex's and is not a level Claude Code accepts.",
|
|
361
|
+
"models": {
|
|
362
|
+
"spawn_takes": "id",
|
|
363
|
+
"spawn_takes_evidence": "documented",
|
|
364
|
+
"spawn_takes_reason": "A Codex agent's model is passed at spawn by its slug: Codex's subagents documentation (learn.chatgpt.com/docs/agent-configuration/subagents, read 2026-10-13) says explicit spawn values override agents.default_subagent_model, and a wrong slug fails at spawn time, on the harness (cheap_model_reason above). So the list is Codex's own catalog, the slugs it shows in its picker.",
|
|
365
|
+
"static": [],
|
|
366
|
+
"fields": {
|
|
367
|
+
"list": "models",
|
|
368
|
+
"id": "slug",
|
|
369
|
+
"label": "display_name",
|
|
370
|
+
"description": "description",
|
|
371
|
+
"order": "priority",
|
|
372
|
+
"shown_when": { "visibility": "list" }
|
|
373
|
+
},
|
|
374
|
+
"catalogs": [
|
|
375
|
+
{
|
|
376
|
+
"kind": "file",
|
|
377
|
+
"source": "Codex",
|
|
378
|
+
"caption": "Codex",
|
|
379
|
+
"path": "models_cache.json",
|
|
380
|
+
"fields": { "at": "fetched_at" },
|
|
381
|
+
"reason": "Codex's own catalog under its home ($CODEX_HOME, else ~/.codex), which Codex refreshes itself. Measured 2026-10-13 on codex-cli 0.162.1: fetched_at, client_version and models[] with slug, display_name, description, visibility (list or hide), priority and upgrade."
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"kind": "command",
|
|
385
|
+
"source": "Codex",
|
|
386
|
+
"caption": "Codex",
|
|
387
|
+
"argv": ["codex", "debug", "models"],
|
|
388
|
+
"reason": "Prints the same catalog as JSON after Codex refreshes it with its own login: 88–103 ms on codex-cli 0.162.1, measured 2026-10-13. A debug subcommand: if a release drops it, the file above keeps the list, and this argv is where it changes (app-server's model/list is the experimental alternative)."
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
}
|
|
361
392
|
}
|
|
362
393
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectstore",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.2",
|
|
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
|
+
}
|