projectstore-claude 0.28.2 → 0.29.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 +3 -1
- package/bin/projectstore-claude.mjs +14 -9
- 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 +2 -2
- package/node_modules/projectstore/commands/review.md +1 -1
- package/node_modules/projectstore/harnesses/claude-code.json +20 -2
- package/node_modules/projectstore/harnesses/codex.json +24 -4
- package/node_modules/projectstore/hooks/pre-compact.mjs +2 -1
- package/node_modules/projectstore/hooks/session-rules.mjs +19 -7
- package/node_modules/projectstore/hooks/session-start.mjs +20 -18
- package/node_modules/projectstore/package.json +2 -2
- package/node_modules/projectstore/scripts/binding.mjs +3 -3
- package/node_modules/projectstore/scripts/build-adapters.mjs +51 -18
- package/node_modules/projectstore/scripts/cli.mjs +176 -29
- package/node_modules/projectstore/scripts/codemap.mjs +2 -2
- package/node_modules/projectstore/scripts/doctor.mjs +110 -58
- package/node_modules/projectstore/scripts/draft.mjs +4 -3
- package/node_modules/projectstore/scripts/graph.mjs +3 -2
- package/node_modules/projectstore/scripts/harness.mjs +89 -0
- package/node_modules/projectstore/scripts/install-harness.mjs +336 -93
- package/node_modules/projectstore/scripts/kanban.mjs +3 -2
- package/node_modules/projectstore/scripts/lib.mjs +69 -8
- package/node_modules/projectstore/scripts/mcp.mjs +2 -2
- package/node_modules/projectstore/scripts/query.mjs +2 -1
- package/node_modules/projectstore/scripts/reconcile.mjs +4 -3
- package/node_modules/projectstore/scripts/story-section.mjs +2 -1
- package/node_modules/projectstore/scripts/surfaces.mjs +2 -1
- package/node_modules/projectstore/scripts/term.mjs +149 -0
- package/node_modules/projectstore/scripts/touch-session.mjs +6 -1
- package/node_modules/projectstore/scripts/worktree.mjs +12 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ The Claude Code installer for [projectstore](https://www.npmjs.com/package/proje
|
|
|
6
6
|
npx projectstore-claude install --project "$PWD"
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
It registers the plugin for that checkout at the host's local scope
|
|
9
|
+
It registers the plugin for that checkout at the host's local scope. It prints its plan first — every write and every host command, verbatim — and at a terminal asks `Apply N changes? [Y/n]` before anything runs; then each step as it runs, and what to do next. Without a terminal (a script, CI, an agent's tool) naming the shell is the confirmation; `--json` never asks. Restart Claude Code afterwards.
|
|
10
|
+
|
|
11
|
+
- See before you write: `npx projectstore-claude plan --project "$PWD"` prints the same plan and writes nothing; `--verbose` adds every row's reasoning; `npx projectstore-claude <verb> --help` lists a verb's options with examples.
|
|
10
12
|
|
|
11
13
|
- Upgrade, or pin: `npx projectstore-claude@<version> upgrade --project "$PWD"` — the version you name is the version you run.
|
|
12
14
|
- Uninstall: `npx projectstore-claude uninstall --project "$PWD"` — forgets the registration for that checkout and nothing else; your vault is plain markdown and stays yours.
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// `--harness claude-code` inserted after a verb that takes it. Every other
|
|
9
9
|
// argument passes through, so `projectstore-claude <verb> …` is exactly
|
|
10
10
|
// `projectstore <verb> --harness claude-code …` — the same preview, the
|
|
11
|
-
// same files, the same exit code. Naming the shell
|
|
12
|
-
//
|
|
11
|
+
// same files, the same exit code. Naming the shell names the harness, exactly
|
|
12
|
+
// as --harness does: without a terminal that is the confirmation the core's
|
|
13
|
+
// install gate asks for; at a terminal the core shows the plan and asks (the
|
|
14
|
+
// install spec, contract 9 as amended 2026-10-04).
|
|
13
15
|
import { existsSync } from "node:fs";
|
|
14
16
|
import { spawnSync } from "node:child_process";
|
|
15
17
|
import { constants as osConstants } from "node:os";
|
|
@@ -72,17 +74,20 @@ if (!core) {
|
|
|
72
74
|
process.stderr.write(`${SHELL}: ${fixed.error}\n`);
|
|
73
75
|
process.exitCode = 2;
|
|
74
76
|
} else {
|
|
75
|
-
// stdio inherited: the core's install gate asks on a
|
|
76
|
-
// without one, so the child must see the
|
|
77
|
-
// timeout — the child waits on a human at the
|
|
78
|
-
// exit(): the core's own bin says why (a pending
|
|
77
|
+
// stdio inherited: the core's install gate shows the plan and asks on a
|
|
78
|
+
// terminal, and refuses a bare run without one, so the child must see the
|
|
79
|
+
// real stdin and stdout. No timeout — the child waits on a human at the
|
|
80
|
+
// question. exitCode, not exit(): the core's own bin says why (a pending
|
|
81
|
+
// write on a pipe). PROJECTSTORE_SHELL lets the core's --help speak this
|
|
82
|
+
// shell's name; nothing the core plans or writes reads it.
|
|
79
83
|
const r = spawnSync(process.execPath, [core, ...fixed.argv], {
|
|
80
84
|
stdio: "inherit",
|
|
81
|
-
env: { ...process.env, PROJECTSTORE_DISTRIBUTION_ROOT: root },
|
|
85
|
+
env: { ...process.env, PROJECTSTORE_DISTRIBUTION_ROOT: root, PROJECTSTORE_SHELL: SHELL },
|
|
82
86
|
});
|
|
83
87
|
if (r.error) process.stderr.write(`${SHELL}: ${r.error.message}\n`);
|
|
84
|
-
// A signal is relayed the shell way (128 + its number):
|
|
85
|
-
//
|
|
88
|
+
// A signal is relayed the shell way (128 + its number): the core reads
|
|
89
|
+
// its answer in the terminal's line mode, so Ctrl-C at the question is a
|
|
90
|
+
// real SIGINT — 130 here as it would be on the core itself.
|
|
86
91
|
process.exitCode = r.status ?? (r.signal ? 128 + (osConstants.signals[r.signal] || 0) : 2);
|
|
87
92
|
}
|
|
88
93
|
}
|
|
@@ -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.
|
|
15
|
+
"version": "0.29.1",
|
|
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.
|
|
4
|
+
"version": "0.29.1",
|
|
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",
|
|
@@ -94,7 +94,7 @@ Contributors: `git clone` this repo, then `claude --plugin-dir ./ProjectStore`.
|
|
|
94
94
|
npx projectstore-claude install --project "$PWD"
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
The same tree is published to npm as [`projectstore`](https://www.npmjs.com/package/projectstore) — one source package carrying every harness's manifest — and `projectstore-claude` is its Claude Code shell: the core pinned at the same version and bundled inside, the harness fixed, so the one command has the same shape on every harness. It registers the plugin with Claude Code: it writes a small local marketplace of its own under your Claude home, then drives `claude plugin marketplace add` / `plugin install` **at local scope**, so the registration lands in this checkout's `.claude/settings.local.json` and nowhere else.
|
|
97
|
+
The same tree is published to npm as [`projectstore`](https://www.npmjs.com/package/projectstore) — one source package carrying every harness's manifest — and `projectstore-claude` is its Claude Code shell: the core pinned at the same version and bundled inside, the harness fixed, so the one command has the same shape on every harness. It registers the plugin with Claude Code: it writes a small local marketplace of its own under your Claude home, then drives `claude plugin marketplace add` / `plugin install` **at local scope**, so the registration lands in this checkout's `.claude/settings.local.json` and nowhere else. It prints its plan first — every file it writes and every host command, verbatim — and at a terminal asks `Apply N changes? [Y/n]`; then it shows each step as it runs and what to do next. Without a terminal (a script, CI, an agent's tool) naming the harness is the confirmation. `plan` prints the same plan and writes nothing; `--verbose` adds every row's reasoning; `npx projectstore-claude <verb> --help` lists a verb's options with examples. Restart Claude Code afterwards. A git-marketplace copy already enabled for the checkout is silenced there (not globally) so the plugin does not load twice; `uninstall` turns it back on. Pin or upgrade with `npx projectstore-claude@<version> upgrade --project "$PWD"` — the version you name is the version you run. The core's low-level form, `npx projectstore <verb> --harness claude-code …`, is exactly what the shell runs. bun works the same on the packed bin.
|
|
98
98
|
|
|
99
99
|
**Codex has its own shell with the same one-command shape:**
|
|
100
100
|
`npx projectstore-codex install --project "$PWD"`. It carries a Codex
|
|
@@ -120,7 +120,7 @@ The package also carries a `bin`. Without a session — in CI, or in a shell —
|
|
|
120
120
|
|
|
121
121
|
```
|
|
122
122
|
npx projectstore doctor --json
|
|
123
|
-
npx projectstore install --harness claude-code # the low-level form the shell runs:
|
|
123
|
+
npx projectstore install --harness claude-code # the low-level form the shell runs: shows the plan, asks at a terminal, then writes; without a terminal naming the harness is the confirmation — there is no --yes
|
|
124
124
|
npx projectstore reconcile --write --only kanban
|
|
125
125
|
```
|
|
126
126
|
|
|
@@ -23,7 +23,7 @@ You are running a peer review on a projectstore artifact.
|
|
|
23
23
|
|
|
24
24
|
5. **Gather domain context**: read the vault's top-level `README.md` and the folder README of the artifact's parent (e.g. `adr/README.md`). Keep both short — they're context for the critic, not the focus.
|
|
25
25
|
|
|
26
|
-
6. **Spawn the critic agent**. Prefer this plugin's own `projectstore:critic` (purpose-built fresh-context critic, no sycophancy
|
|
26
|
+
6. **Spawn the critic agent**. Prefer this plugin's own `projectstore:critic` (purpose-built fresh-context critic, no sycophancy). If unavailable, fall back to `oh-my-claudecode:critic`, then `general-purpose`. Use this exact prompt template:
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
You are a critic-mode reviewer. You have ONLY the artifact and the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Harness capability manifest — Claude Code. This is the SOURCE layout (generation spec, contract 2): the plugin's own commands/, agents/, skills/ and hooks/hooks.json are already in Claude Code's shapes, so nothing is emitted for it (emit: false). The manifest still exists in full because the portability lint reads every harness's vocabulary, and a token only Claude Code understands is only detectable as unmapped in another harness's tree if Claude Code's own names are written down somewhere. It carries no `lint` block and an empty `rewrites` because linting runs over emitted trees only. Shape contributed by Maxim Podreshetnikov (PR #13); `verified`, `output_channels`, `scope_reason`, `root_placeholder_literal` and `agent_overrides` are the spec's additions.",
|
|
2
|
+
"_comment": "Harness capability manifest — Claude Code. This is the SOURCE layout (generation spec, contract 2): the plugin's own commands/, agents/, skills/ and hooks/hooks.json are already in Claude Code's shapes, so nothing is emitted for it (emit: false). The manifest still exists in full because the portability lint reads every harness's vocabulary, and a token only Claude Code understands is only detectable as unmapped in another harness's tree if Claude Code's own names are written down somewhere. It carries no `lint` block and an empty `rewrites` because linting runs over emitted trees only; its `ui_vocabulary` lists the UI affordances that mean nothing elsewhere, read by the generator's leak check and by the runtime vocabulary lint over what the core prints for another harness (generation spec, contracts 2 and 18). Shape contributed by Maxim Podreshetnikov (PR #13); `verified`, `output_channels`, `scope_reason`, `root_placeholder_literal` and `agent_overrides` are the spec's additions.",
|
|
3
3
|
"id": "claude-code",
|
|
4
4
|
"display_name": "Claude Code",
|
|
5
5
|
"emit": false,
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"/plugin marketplace add SmartAndPoint/ProjectStore && /plugin install projectstore@SmartAndPoint (git marketplace, user-wide)",
|
|
21
21
|
"restart Claude Code"
|
|
22
22
|
],
|
|
23
|
+
"next": [
|
|
24
|
+
"restart Claude Code in this project"
|
|
25
|
+
],
|
|
23
26
|
"notes": [
|
|
24
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>`.",
|
|
25
28
|
"Pin a git-marketplace release with: /plugin marketplace add SmartAndPoint/ProjectStore#<tag>; pin the npm registration with npx projectstore-claude@<version>.",
|
|
@@ -461,9 +464,24 @@
|
|
|
461
464
|
"interactive_approval_tool": true,
|
|
462
465
|
"permission_decision_hook": "PreToolUse",
|
|
463
466
|
"subagent_tool_allowlist": true,
|
|
464
|
-
"subagent_model_per_invocation": true
|
|
467
|
+
"subagent_model_per_invocation": true,
|
|
468
|
+
"session_rename": "/rename <name>",
|
|
469
|
+
"session_rename_reason": "Claude Code's own command for naming a session; the session-name offer prints it filled (generation spec, contract 18). A harness without one gets no offer.",
|
|
470
|
+
"bind_inherit": true,
|
|
471
|
+
"bind_inherit_reason": "commands/bind.md adopts the binding of the checkout a worktree was forked from (`bind --inherit`), so an unbound worktree is told to run that. A harness whose bind cannot inherit is told the explicit vault instead (generation spec, contract 18)."
|
|
465
472
|
},
|
|
466
473
|
"rewrites": [],
|
|
474
|
+
"ui_vocabulary": [
|
|
475
|
+
"/plugin",
|
|
476
|
+
"/reload-plugins",
|
|
477
|
+
"AskUserQuestion"
|
|
478
|
+
],
|
|
479
|
+
"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`.",
|
|
480
|
+
"update_hint": {
|
|
481
|
+
"line": "See /plugin → Marketplaces to enable auto-update.",
|
|
482
|
+
"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#updates for details."],
|
|
483
|
+
"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."
|
|
484
|
+
},
|
|
467
485
|
"update_instructions": [
|
|
468
486
|
"**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+):",
|
|
469
487
|
"1. Open `/plugin` → **Marketplaces** tab.",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"shared_env_reason": "Codex sets Claude Code's plugin-root names too, for compatibility — measured in every one of the 759 captured hook payloads. Without declaring them, a Codex session scores a strong detection hit for claude-code, ties with codex, and loses the tie to manifest filename order. A variable two harnesses both set identifies neither, so these are demoted to weak for every manifest (generation spec, contract 1, amended 2026-09-08).",
|
|
31
31
|
"session_env": [],
|
|
32
|
-
"session_env_reason": "
|
|
32
|
+
"session_env_reason": "Deliberately empty, though measured. Codex's exec tool sets CODEX_THREAD_ID and CODEX_SESSION_ID in every command (measured 2026-10-05, codex-cli 0.160.0; CODEX_SANDBOX* depend on the sandbox mode), and CODEX_HOME never appeared in a hook environment. Declaring them would identify a session, but session_env also defers the registration and the layout move inside a session (insideHostSession), which would stop the doctor skill's upgrade --json fix from running there. Splitting identity from deferral is the question of *Codex hook contract measured in a live session* (PS-HARNESS); until then a core run from a rendered skill is identified by the shell that bundles it (generation spec, contract 18)."
|
|
33
33
|
},
|
|
34
34
|
"tools": {
|
|
35
35
|
"write_tools": [
|
|
@@ -161,7 +161,10 @@
|
|
|
161
161
|
"kind": "host",
|
|
162
162
|
"scope": "user",
|
|
163
163
|
"scope_reason": "Codex spawns subagents through a tool (collaborationspawn_agent), not by loading a plugin's agents/ directory.",
|
|
164
|
-
"why_unsupported": "Measured: told to run projectstore:critic, Codex spawned six of its own generic subagents named after our roster and briefed them itself; none of our agents/*.md prose reached them. No route from a plugin's agents/ entry to that mechanism has been found (roadmap B4)."
|
|
164
|
+
"why_unsupported": "Measured: told to run projectstore:critic, Codex spawned six of its own generic subagents named after our roster and briefed them itself; none of our agents/*.md prose reached them. No route from a plugin's agents/ entry to that mechanism has been found (roadmap B4).",
|
|
165
|
+
"rendered_as": "skill",
|
|
166
|
+
"rendered_name": "projectstore-<name>",
|
|
167
|
+
"rendered_reason": "Each role is rendered as a skill of this name (the generator reads this key), so a role is called through skills.invocation: $projectstore-critic. Messages name roles this way (generation spec, contract 18)."
|
|
165
168
|
},
|
|
166
169
|
"agents_block": {
|
|
167
170
|
"supported": true,
|
|
@@ -186,7 +189,10 @@
|
|
|
186
189
|
"kind": "host",
|
|
187
190
|
"scope": "user",
|
|
188
191
|
"scope_reason": "Codex has no registrable root slash command; the /prompts: mechanism is deprecated.",
|
|
189
|
-
"why_unsupported": "Shipping commands/ is actively harmful here: Codex rewrites them into skills itself and keeps ${CLAUDE_PLUGIN_ROOT} in the body, producing entry points that exit 1 with Cannot find module '/bin/projectstore.mjs' (reproduced). The generator renders them as skills instead (roadmap B5)."
|
|
192
|
+
"why_unsupported": "Shipping commands/ is actively harmful here: Codex rewrites them into skills itself and keeps ${CLAUDE_PLUGIN_ROOT} in the body, producing entry points that exit 1 with Cannot find module '/bin/projectstore.mjs' (reproduced). The generator renders them as skills instead (roadmap B5).",
|
|
193
|
+
"rendered_as": "skill",
|
|
194
|
+
"rendered_name": "projectstore-<name>",
|
|
195
|
+
"rendered_reason": "Each command is rendered as a skill of this name (the generator reads this key), so a command is called through skills.invocation: $projectstore-doctor. Messages name commands this way (generation spec, contract 18)."
|
|
190
196
|
},
|
|
191
197
|
"hooks": {
|
|
192
198
|
"supported": true,
|
|
@@ -286,7 +292,12 @@
|
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
294
|
},
|
|
289
|
-
"capabilities": {
|
|
295
|
+
"capabilities": {
|
|
296
|
+
"session_rename": null,
|
|
297
|
+
"session_rename_reason": "No session-rename command is known for Codex, so it gets no session-name offer (generation spec, contract 18).",
|
|
298
|
+
"bind_inherit": false,
|
|
299
|
+
"bind_inherit_reason": "The rendered bind skill and the CLI's bind verb have no inherit path; only Claude Code's command does. An unbound worktree is told to bind the parent checkout's vault explicitly. A follow-up story gives the core an inherit path and flips this."
|
|
300
|
+
},
|
|
290
301
|
"rewrites": [],
|
|
291
302
|
"install": {
|
|
292
303
|
"mechanism": "the package's portable plugin root, registered through its distribution shell and Codex's own marketplace CLI",
|
|
@@ -298,6 +309,10 @@
|
|
|
298
309
|
"restart Codex and approve the ProjectStore hooks when prompted",
|
|
299
310
|
"run `$projectstore-bind <vault-path>` in Codex"
|
|
300
311
|
],
|
|
312
|
+
"next": [
|
|
313
|
+
"restart Codex from a terminal and approve the ProjectStore hooks when it asks",
|
|
314
|
+
"in an unbound project, run `$projectstore-bind <vault-path>` in Codex"
|
|
315
|
+
],
|
|
301
316
|
"notes": [
|
|
302
317
|
"The installation is user-global because Codex's marketplace, plugin row and cache live in CODEX_HOME; the AGENTS.md block is project-local.",
|
|
303
318
|
"Upgrade with `npx projectstore-codex@<version> upgrade --project \"$PWD\"`; the named package version is also the plugin version.",
|
|
@@ -306,6 +321,11 @@
|
|
|
306
321
|
],
|
|
307
322
|
"docs": "https://github.com/SmartAndPoint/ProjectStore#install--one-message"
|
|
308
323
|
},
|
|
324
|
+
"update_hint": {
|
|
325
|
+
"line": "Update with `{upgrade}`.",
|
|
326
|
+
"welcome": ["**About future updates**: run `{upgrade}` from a terminal, then restart Codex and approve any hook it asks about.", "", "See https://github.com/SmartAndPoint/ProjectStore#updates for details."],
|
|
327
|
+
"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 this harness's shell command at latest, so what is printed runs as printed. 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."
|
|
328
|
+
},
|
|
309
329
|
"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.",
|
|
310
330
|
"agent_translation": {
|
|
311
331
|
"carries_model": false,
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
readActivityAsync,
|
|
32
32
|
resolveInFlightArtifact,
|
|
33
33
|
pathCell,
|
|
34
|
+
commandForm,
|
|
34
35
|
} from "../scripts/lib.mjs";
|
|
35
36
|
|
|
36
37
|
// Exits after the flush, never before: process.exit does not drain a pending
|
|
@@ -110,7 +111,7 @@ async function main() {
|
|
|
110
111
|
} else {
|
|
111
112
|
// True on every path: plugin commands are installation-scoped and not
|
|
112
113
|
// gated by auto_inject.
|
|
113
|
-
parts.push(
|
|
114
|
+
parts.push(`run ${commandForm("status")} to reorient`);
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
emit(`projectstore: compacting — ${parts.join(", ")}`);
|
|
@@ -16,22 +16,34 @@
|
|
|
16
16
|
// Spec: "Entry-rule detection: the score, the open-story predicate, and the
|
|
17
17
|
// delivery seams", contract 17.
|
|
18
18
|
|
|
19
|
-
import { readConfig, readStdinJson, adoptHookInput } from "../scripts/lib.mjs";
|
|
19
|
+
import { readConfig, readStdinJson, adoptHookInput, roleForm, sharedRoleForm } from "../scripts/lib.mjs";
|
|
20
20
|
|
|
21
|
-
// Kept well under the cap; asserted by a test rather than by intention.
|
|
22
|
-
|
|
21
|
+
// Kept well under the cap; asserted by a test rather than by intention. The
|
|
22
|
+
// roles are named the way the session's harness calls them (generation spec,
|
|
23
|
+
// contract 18). The shared AGENTS.md block names them in the source harness's
|
|
24
|
+
// form until that block is per-harness, so a session whose form differs is
|
|
25
|
+
// told once that both name one role — otherwise the second rule below would
|
|
26
|
+
// have it report the block as a contradiction every session.
|
|
27
|
+
function rules() {
|
|
28
|
+
const critic = roleForm("critic"), reviewer = roleForm("reviewer");
|
|
29
|
+
const shared = sharedRoleForm("critic");
|
|
30
|
+
const bridge = shared === critic
|
|
31
|
+
? ""
|
|
32
|
+
: `\n\nThe \`AGENTS.md\` block may name roles as \`${shared}\`; that is the same role, called \`${critic}\` here.`;
|
|
33
|
+
return `# projectstore — standing rules for this session
|
|
23
34
|
|
|
24
35
|
**Artifact-first order.** A feature-sized request opens a vault artifact before
|
|
25
36
|
it opens an editor: analysis → placement (which epic, which story) → an ADR
|
|
26
|
-
and/or spec when the "how" is non-trivial →
|
|
27
|
-
implementation →
|
|
37
|
+
and/or spec when the "how" is non-trivial → \`${critic}\` → only then
|
|
38
|
+
implementation → \`${reviewer}\`. "Feature-sized" is not a judgement
|
|
28
39
|
call about how the request was phrased — it is about what the work touches. If
|
|
29
40
|
you are about to write across several source files, open the story first.
|
|
30
41
|
|
|
31
42
|
**Report instruction conflicts; do not arbitrate them.** If a session-level or
|
|
32
43
|
harness-level instruction contradicts these rules or the \`AGENTS.md\`
|
|
33
44
|
registration block, say so and ask which wins. Resolving it silently is how the
|
|
34
|
-
contradiction becomes invisible to the person who could have settled it
|
|
45
|
+
contradiction becomes invisible to the person who could have settled it.${bridge}`;
|
|
46
|
+
}
|
|
35
47
|
|
|
36
48
|
function emit(additionalContext) {
|
|
37
49
|
process.stdout.write(JSON.stringify({
|
|
@@ -55,7 +67,7 @@ function main() {
|
|
|
55
67
|
// Honour auto_inject: a session that opted out of context injection is
|
|
56
68
|
// exactly the case for which the AGENTS.md block remains the durable copy.
|
|
57
69
|
if (cfg.auto_inject === false) return;
|
|
58
|
-
emit(
|
|
70
|
+
emit(rules());
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
try { main(); } catch {
|
|
@@ -36,6 +36,11 @@ import {
|
|
|
36
36
|
PATH_CELL,
|
|
37
37
|
ERROR_CELL,
|
|
38
38
|
TITLE_CELL,
|
|
39
|
+
commandForm,
|
|
40
|
+
updateHint,
|
|
41
|
+
speakingDisplayName,
|
|
42
|
+
inheritForm,
|
|
43
|
+
bindInherits,
|
|
39
44
|
} from "../scripts/lib.mjs";
|
|
40
45
|
import { runStartupChecks } from "../scripts/doctor.mjs";
|
|
41
46
|
import { resolveBinding, bindingOfferText } from "../scripts/worktree.mjs";
|
|
@@ -60,8 +65,8 @@ function welcomedMarkerWritePath(proj) {
|
|
|
60
65
|
// the same message.
|
|
61
66
|
function buildWelcome(cfg = null) {
|
|
62
67
|
const start = cfg && cfg.vault_path
|
|
63
|
-
? `**Already bound**: this project's vault is \`${truncFront(String(cfg.vault_path), PATH_CELL)}\`. Ask for what you want — the agent picks up commands like
|
|
64
|
-
:
|
|
68
|
+
? `**Already bound**: this project's vault is \`${truncFront(String(cfg.vault_path), PATH_CELL)}\`. Ask for what you want — the agent picks up commands like \`${commandForm("adr")}\` and \`${commandForm("epic")}\` from the conversation, and you approve every write. If the vault has no folders yet, \`${commandForm("scaffold")}\` lays them out.`
|
|
69
|
+
: `**To start using it**: run \`${commandForm("bind", { args: "<vault-path>" })}\` and point it at an Obsidian vault (or any folder). After that, the agent will pick up commands like \`${commandForm("adr")}\` and \`${commandForm("epic")}\` from the conversation; you only approve the writes.`;
|
|
65
70
|
return [
|
|
66
71
|
"# 👋 projectstore is loaded for the first time in this project",
|
|
67
72
|
"",
|
|
@@ -69,12 +74,9 @@ function buildWelcome(cfg = null) {
|
|
|
69
74
|
"",
|
|
70
75
|
start,
|
|
71
76
|
"",
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"3. Toggle **auto-update** on.",
|
|
76
|
-
"",
|
|
77
|
-
"Without it, you'd run `/plugin marketplace update SmartAndPoint` manually. See https://github.com/SmartAndPoint/ProjectStore#updates for details.",
|
|
77
|
+
// The update advice is the harness's own (its manifest's update_hint):
|
|
78
|
+
// a git marketplace's toggle means nothing to a harness installed from npm.
|
|
79
|
+
...updateHint().welcome,
|
|
78
80
|
"",
|
|
79
81
|
"_This message appears once per project._",
|
|
80
82
|
"",
|
|
@@ -128,7 +130,7 @@ function buildOthersWarning(others) {
|
|
|
128
130
|
"",
|
|
129
131
|
`## ⚠️ Multi-session warning — ${others.length} other projectstore session(s) active on this vault`,
|
|
130
132
|
"",
|
|
131
|
-
|
|
133
|
+
`Another ${speakingDisplayName()} session is currently working on the same vault.`,
|
|
132
134
|
"Active session(s):",
|
|
133
135
|
"",
|
|
134
136
|
];
|
|
@@ -143,13 +145,13 @@ function buildOthersWarning(others) {
|
|
|
143
145
|
);
|
|
144
146
|
}
|
|
145
147
|
if (others.length > SIBLING_CAP) {
|
|
146
|
-
lines.push(`- …and ${others.length - SIBLING_CAP} more — run
|
|
148
|
+
lines.push(`- …and ${others.length - SIBLING_CAP} more — run \`${commandForm("status")}\``);
|
|
147
149
|
}
|
|
148
150
|
lines.push(
|
|
149
151
|
"",
|
|
150
152
|
"**Before creating new ADRs / epics / stories / research:**",
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
`1. Run \`${commandForm("search", { args: "<topic-keywords>" })}\` to check for in-flight artifacts on the same topic.`,
|
|
154
|
+
`2. Run \`${commandForm("status")}\` to see what artifacts have been touched recently.`,
|
|
153
155
|
"3. After creation, the plugin re-checks file existence right before write — collisions are detected, but topic / number reservation across sessions is on you and the other agent to coordinate.",
|
|
154
156
|
"",
|
|
155
157
|
);
|
|
@@ -179,8 +181,8 @@ async function main() {
|
|
|
179
181
|
// one: a bound project is told what it is bound to, not to bind again.
|
|
180
182
|
const welcomeSystemMessage = welcome
|
|
181
183
|
? (cfg && cfg.vault_path
|
|
182
|
-
? `👋 projectstore: first-run welcome shown. Bound to ${cfg.vault_path}.
|
|
183
|
-
:
|
|
184
|
+
? `👋 projectstore: first-run welcome shown. Bound to ${cfg.vault_path}. ${updateHint().line}`
|
|
185
|
+
: `👋 projectstore: first-run welcome shown. Start with ${commandForm("bind", { args: "<vault-path>" })}. ${updateHint().line}`)
|
|
184
186
|
: null;
|
|
185
187
|
|
|
186
188
|
if (!cfg) {
|
|
@@ -191,7 +193,7 @@ async function main() {
|
|
|
191
193
|
// redundancy is accepted rather than papered over with copy that will rot.
|
|
192
194
|
const offer = binding && binding.state === "inheritable" ? bindingOfferText(binding) : "";
|
|
193
195
|
const offerSystemMessage = offer
|
|
194
|
-
?
|
|
196
|
+
? `projectstore: this worktree is unbound — ${inheritForm(binding.vaultPath, { layout: binding.layout, language: binding.language })} ${bindInherits() ? "adopts the binding of" : "binds it to the vault, layout and language of"} the checkout it was forked from.`
|
|
195
197
|
: null;
|
|
196
198
|
const body = offer + (welcome || "");
|
|
197
199
|
if (body) {
|
|
@@ -273,9 +275,9 @@ async function main() {
|
|
|
273
275
|
try {
|
|
274
276
|
const r = runStartupChecks(cfg, proj);
|
|
275
277
|
if (r.skipped) {
|
|
276
|
-
doctorMsg =
|
|
278
|
+
doctorMsg = `projectstore doctor: startup checks skipped — run ${commandForm("doctor")}`;
|
|
277
279
|
} else if (r.count > 0) {
|
|
278
|
-
doctorMsg = `projectstore doctor: ${r.count} install issue(s) — run
|
|
280
|
+
doctorMsg = `projectstore doctor: ${r.count} install issue(s) — run ${commandForm("doctor")}`;
|
|
279
281
|
}
|
|
280
282
|
// Offers (doctor's OFFER_CHECKS): one-time steps a user should see once,
|
|
281
283
|
// e.g. the re-stamp after a plugin update — not issues, not silent.
|
|
@@ -287,7 +289,7 @@ async function main() {
|
|
|
287
289
|
if (gatherError) {
|
|
288
290
|
emit(
|
|
289
291
|
welcome +
|
|
290
|
-
`# projectstore: vault load failed\n\n${truncEnd(String(gatherError.message), ERROR_CELL)}\n\nFix \`.projectstore/projectstore.json\` or run
|
|
292
|
+
`# projectstore: vault load failed\n\n${truncEnd(String(gatherError.message), ERROR_CELL)}\n\nFix \`.projectstore/projectstore.json\` or run \`${commandForm("bind", { args: "<path>" })}\` again.`,
|
|
291
293
|
systemMessage,
|
|
292
294
|
);
|
|
293
295
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectstore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
|
-
"test": "node --test tests/*.test.mjs",
|
|
60
|
+
"test": "node --test --import ./tests/fixtures/hermetic.mjs tests/*.test.mjs",
|
|
61
61
|
"guard": "node scripts/version-guard.mjs",
|
|
62
62
|
"packlist": "node scripts/version-guard.mjs --write-packlist",
|
|
63
63
|
"adapters": "node scripts/build-adapters.mjs --write",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, realpathSync } from "node:fs";
|
|
28
28
|
import { join, resolve, isAbsolute, dirname } from "node:path";
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
|
-
import { writeFileAtomic, pluginRoot, ensureRuntimeDir, layoutPaths } from "./lib.mjs";
|
|
30
|
+
import { writeFileAtomic, pluginRoot, ensureRuntimeDir, layoutPaths, commandForm } from "./lib.mjs";
|
|
31
31
|
import { configPath as harnessConfigPath } from "./harness.mjs";
|
|
32
32
|
|
|
33
33
|
export const DEFAULT_LAYOUT = "engineering";
|
|
@@ -154,12 +154,12 @@ export function renderBindPlan(p, done = null) {
|
|
|
154
154
|
const lines = [];
|
|
155
155
|
if (!p.ok) { for (const r of p.refusals) lines.push(r.message); return lines.join("\n") + "\n"; }
|
|
156
156
|
if (p.state === "same") {
|
|
157
|
-
lines.push(`Already bound to ${p.vault}${p.ignored.length ? ` — --${p.ignored.join(" and --")} ignored: a change of ${p.ignored.join("/")} is not a rebind (edit the config, or rebind to another vault)` : ""}. Run
|
|
157
|
+
lines.push(`Already bound to ${p.vault}${p.ignored.length ? ` — --${p.ignored.join(" and --")} ignored: a change of ${p.ignored.join("/")} is not a rebind (edit the config, or rebind to another vault)` : ""}. Run ${commandForm("scaffold")} to (re)create the layout, or \`projectstore status\` to inspect it.`);
|
|
158
158
|
return lines.join("\n") + "\n";
|
|
159
159
|
}
|
|
160
160
|
if (done && done.created_vault) lines.push(`Created ${p.vault}`);
|
|
161
161
|
lines.push(`Wrote ${p.configPath}${p.state === "different" ? ` (rebind from ${p.before.vault_path}; kept: ${p.keptKeys.join(", ") || "nothing else"})` : ""}`);
|
|
162
162
|
lines.push(` vault_path: ${p.vault}`, ` layout: ${p.layout}`, ` language: ${p.language}`);
|
|
163
|
-
if (done) lines.push("", p.vaultExists && !done.created_vault ?
|
|
163
|
+
if (done) lines.push("", p.vaultExists && !done.created_vault ? `Next: \`projectstore status\`, or ${commandForm("scaffold")} if the vault has no layout folders yet.` : `Next: ${commandForm("scaffold")} in a session creates the layout's folders and READMEs.`);
|
|
164
164
|
return lines.join("\n") + "\n";
|
|
165
165
|
}
|
|
@@ -6,11 +6,28 @@
|
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
7
7
|
import { dirname, join, relative, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { emittingHarnesses, sourceHarness } from "./harness.mjs";
|
|
9
|
+
import { emittingHarnesses, sourceHarness, invocation, uiWordPatterns } from "./harness.mjs";
|
|
10
10
|
import { writeFileAtomic } from "./lib.mjs";
|
|
11
11
|
|
|
12
12
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
13
|
const SOURCE = sourceHarness();
|
|
14
|
+
// The source harness's UI affordances, from its manifest (ui_vocabulary): the
|
|
15
|
+
// same list the runtime vocabulary lint reads (generation spec, contract 18).
|
|
16
|
+
// The source's command form and the target's skill namespace, from the
|
|
17
|
+
// manifests (generation spec, contract 18): the body rewrite, the passive-skill
|
|
18
|
+
// name rule and the leak check read these, so a renamed form cannot leave
|
|
19
|
+
// directory names and in-body references disagreeing.
|
|
20
|
+
const escapeRe = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21
|
+
const SOURCE_COMMAND_PREFIX = SOURCE.surfaces.commands.invocation.split("<name>")[0];
|
|
22
|
+
const SOURCE_COMMAND = new RegExp(`${escapeRe(SOURCE_COMMAND_PREFIX)}([a-z0-9-]+|\\*)`, "g");
|
|
23
|
+
const SOURCE_UI = uiWordPatterns(SOURCE);
|
|
24
|
+
// The source's role form (`projectstore:<name>`) for each of its roles, by file
|
|
25
|
+
// name: a rendered skill that says "spawn projectstore:critic" names a role the
|
|
26
|
+
// way only the source harness calls it. Never inside another form
|
|
27
|
+
// (`/projectstore:`, `$projectstore-`), never a longer name.
|
|
28
|
+
const ROLE_SUFFIX = SOURCE.surfaces.agents.file.replace("<name>", "");
|
|
29
|
+
const SOURCE_ROLES = readdirSync(join(ROOT, SOURCE.surfaces.agents.dir)).filter((f) => f.endsWith(ROLE_SUFFIX)).map((f) => f.slice(0, -ROLE_SUFFIX.length)).sort();
|
|
30
|
+
const SOURCE_ROLE = new RegExp(`(?<![\\w/$:-])${escapeRe(SOURCE.surfaces.agents.invocation.split("<name>")[0])}(${SOURCE_ROLES.map(escapeRe).join("|")})(?![\\w-])`, "g");
|
|
14
31
|
const TARGETS = emittingHarnesses();
|
|
15
32
|
if (TARGETS.length !== 1) throw new Error(`this renderer currently requires exactly one emitting harness; found ${TARGETS.length}`);
|
|
16
33
|
const TARGET = TARGETS[0];
|
|
@@ -61,8 +78,8 @@ function rewriteBody(body) {
|
|
|
61
78
|
.replaceAll(sourceWord, TARGET.id)
|
|
62
79
|
.replaceAll(SOURCE.surfaces.agents_block.reads_natively, TARGET.surfaces.agents_block.reads_natively)
|
|
63
80
|
.replaceAll(SOURCE.runtime.harness_dir + "/", TARGET.runtime.harness_dir + "/")
|
|
64
|
-
.
|
|
65
|
-
.replace(
|
|
81
|
+
.replace(SOURCE_COMMAND, (_, name) => invocation(TARGET, name, { kind: "commands" }))
|
|
82
|
+
.replace(SOURCE_ROLE, (_, name) => invocation(TARGET, name, { kind: "agents" }))
|
|
66
83
|
.replaceAll("AskUserQuestion", "the harness's user-input mechanism")
|
|
67
84
|
.replaceAll("Read tool", "file-reading tool")
|
|
68
85
|
.replaceAll("Write tool", "file-writing tool")
|
|
@@ -80,10 +97,11 @@ const COMMAND_OVERRIDES = {
|
|
|
80
97
|
|
|
81
98
|
## register / unregister
|
|
82
99
|
|
|
83
|
-
Preview the requested change
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
Preview the requested change with \`plan --harness codex --surface agents_block
|
|
101
|
+
--project "$PWD"\` and ask for explicit approval. On approval, run the core's
|
|
102
|
+
\`install\` or \`uninstall\` verb with \`--harness codex --surface agents_block
|
|
103
|
+
--project "$PWD" --json\` — \`--json\` never waits on a terminal's question — and
|
|
104
|
+
report the envelope's result. Never edit the managed block by hand.
|
|
87
105
|
|
|
88
106
|
## status
|
|
89
107
|
|
|
@@ -104,13 +122,15 @@ contract. A configured model is consumed by the role-orchestration skills on
|
|
|
104
122
|
their next spawn; no restart is needed.`,
|
|
105
123
|
},
|
|
106
124
|
bind: {
|
|
107
|
-
description: "Bind this project to an existing ProjectStore vault, or initialize and bind a new vault, using the harness-neutral core. Arguments: [vault-path].",
|
|
125
|
+
description: "Bind this project to an existing ProjectStore vault, or initialize and bind a new vault, using the harness-neutral core. Arguments: [vault-path] [--layout <name>] [--language <code>].",
|
|
108
126
|
body: `Bind the current project through the core; never write the binding by
|
|
109
127
|
hand.
|
|
110
128
|
|
|
111
|
-
1. Resolve the requested vault path
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
1. Resolve the requested vault path, and the layout and language when the
|
|
130
|
+
request passes \`--layout <name>\` or \`--language <code>\`. If the vault
|
|
131
|
+
exists, use \`bind\`; if the user explicitly asks to create it, use
|
|
132
|
+
\`init\`. Ask for layout and language only when the request has not
|
|
133
|
+
supplied them.
|
|
114
134
|
2. Show the resolved project, vault, verb, layout and language. Ask for explicit
|
|
115
135
|
approval. Naming the vault is the core's non-interactive confirmation.
|
|
116
136
|
3. Run \`node "\${PROJECTSTORE_CORE_ROOT}/bin/projectstore.mjs" <bind|init>
|
|
@@ -134,7 +154,9 @@ arguments and \`--json\`. Summarize every finding without re-deriving it.
|
|
|
134
154
|
When \`--fix\` is absent, remain read-only. When it is present, separate fixes
|
|
135
155
|
by owner: derived vault views use \`$projectstore-reconcile\`; Codex plugin or
|
|
136
156
|
agents-block drift uses the core's \`upgrade --harness codex\` path. Preview
|
|
137
|
-
each mutation and ask for explicit approval before
|
|
157
|
+
each mutation with \`plan --harness codex\` and ask for explicit approval before
|
|
158
|
+
running it; then run \`upgrade --harness codex --json\`, whose envelope is the
|
|
159
|
+
result — \`--json\` never waits on a terminal's question. Unsupported
|
|
138
160
|
surfaces remain unsupported; do not create host configuration by hand. Never
|
|
139
161
|
claim a fix after a non-zero exit.`,
|
|
140
162
|
},
|
|
@@ -148,10 +170,21 @@ Codex application's own task and terminal UI.`,
|
|
|
148
170
|
},
|
|
149
171
|
};
|
|
150
172
|
|
|
173
|
+
// The rendered skills' namespace: the part of rendered_name before the name.
|
|
174
|
+
const NAMESPACE = (TARGET.surfaces?.commands?.rendered_name || "").split("<name>")[0];
|
|
175
|
+
|
|
176
|
+
// A command or role becomes the skill its manifest names (rendered_name), the
|
|
177
|
+
// same name every message calls it by (generation spec, contract 18).
|
|
178
|
+
const renderedName = (kind, name) => {
|
|
179
|
+
const tpl = TARGET.surfaces?.[kind]?.rendered_name;
|
|
180
|
+
if (!tpl) throw new Error(`${TARGET.id}: surfaces.${kind}.rendered_name is required to render ${kind} as skills`);
|
|
181
|
+
return tpl.split("<name>").join(name);
|
|
182
|
+
};
|
|
183
|
+
|
|
151
184
|
function renderCommand(file) {
|
|
152
185
|
const { data, body } = frontmatter(read(file), file);
|
|
153
186
|
const verb = file.split("/").pop().replace(/\.md$/, "");
|
|
154
|
-
const name =
|
|
187
|
+
const name = renderedName("commands", verb);
|
|
155
188
|
const override = COMMAND_OVERRIDES[verb];
|
|
156
189
|
if (override) return [join("skills", name, "SKILL.md"), skill(name, override.description, override.body)];
|
|
157
190
|
const hint = data["argument-hint"] ? ` Arguments: ${data["argument-hint"]}.` : "";
|
|
@@ -162,14 +195,14 @@ function renderPassive(file) {
|
|
|
162
195
|
const { data, body } = frontmatter(read(file), file);
|
|
163
196
|
const dir = file.split("/").at(-2);
|
|
164
197
|
const name = data.name || dir;
|
|
165
|
-
if (!name.startsWith(
|
|
198
|
+
if (!name.startsWith(NAMESPACE)) throw new Error(`${relative(ROOT, file)}: skill name must be ${NAMESPACE}*`);
|
|
166
199
|
return [join("skills", name, "SKILL.md"), skill(name, data.description || name, body)];
|
|
167
200
|
}
|
|
168
201
|
|
|
169
202
|
function renderRole(file) {
|
|
170
203
|
const { data, body } = frontmatter(read(file), file);
|
|
171
204
|
const role = file.split("/").pop().replace(/\.md$/, "");
|
|
172
|
-
const name =
|
|
205
|
+
const name = renderedName("agents", role);
|
|
173
206
|
const orchestration = `## Codex orchestration\n\nThis is a role-orchestration skill, not a native agent registration. Resolve the\nrole model by running:\n\n\`\`\`bash\nnode "\${PROJECTSTORE_CORE_ROOT}/bin/projectstore.mjs" agents model ${role} --json --project "$PWD"\n\`\`\`\n\nSpawn a collaboration agent for the bounded task. If the result names a model,\npass that model and use an empty or bounded context fork; otherwise inherit the\ncurrent model. Do not pass a reasoning-effort override: per-role effort belongs\nto a separate accepted story. Give the spawned agent the role contract below\nand the exact artifact/diff it must inspect. Wait for its final result.\n\n## Role contract\n\n`;
|
|
174
207
|
const description = (data.description || `Run the ProjectStore ${role} role in a fresh collaboration agent.`)
|
|
175
208
|
.replace(/^(?:Opus|Sonnet) \(max-effort\)\s+/, "");
|
|
@@ -200,7 +233,7 @@ export function renderCodexAdapter(root = ROOT) {
|
|
|
200
233
|
...files(join(root, "commands")).map(renderCommand),
|
|
201
234
|
...files(join(root, "agents")).map(renderRole),
|
|
202
235
|
...readdirSync(join(root, "skills"), { withFileTypes: true })
|
|
203
|
-
.filter((e) => e.isDirectory() && e.name.startsWith(
|
|
236
|
+
.filter((e) => e.isDirectory() && e.name.startsWith(NAMESPACE))
|
|
204
237
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
205
238
|
.map((e) => renderPassive(join(root, "skills", e.name, "SKILL.md"))),
|
|
206
239
|
renderHooks(manifest, root),
|
|
@@ -214,8 +247,8 @@ export function renderCodexAdapter(root = ROOT) {
|
|
|
214
247
|
for (const [rel, text] of out) {
|
|
215
248
|
const sourceTokens = [SOURCE.runtime.plugin_root_env, SOURCE.runtime.project_dir_env, SOURCE.display_name, SOURCE.runtime.harness_dir + "/"].filter(Boolean);
|
|
216
249
|
const sourceAgentEnvs = (SOURCE.runtime.agent_overrides || []).map((row) => row.env).filter(Boolean);
|
|
217
|
-
if (sourceTokens.some((token) => text.includes(token)) || sourceAgentEnvs.some((token) => text.includes(token)) || /\$ARGUMENTS
|
|
218
|
-
if (
|
|
250
|
+
if (sourceTokens.some((token) => text.includes(token)) || sourceAgentEnvs.some((token) => text.includes(token)) || text.includes(SOURCE_COMMAND_PREFIX) || new RegExp(SOURCE_ROLE.source).test(text) || /\$ARGUMENTS/.test(text)) throw new Error(`${rel}: source-harness vocabulary leaked into ${TARGET.display_name} adapter`);
|
|
251
|
+
if (SOURCE_UI.some(({ re }) => new RegExp(re.source).test(text)) || /harness\/codex-code\.json|\b(?:opus|sonnet|fable)\b/i.test(text)) throw new Error(`${rel}: source-harness model, environment or UI semantics leaked into ${TARGET.display_name} adapter`);
|
|
219
252
|
}
|
|
220
253
|
return out;
|
|
221
254
|
}
|