projectstore-claude 0.29.2 → 0.29.4

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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/bin/projectstore-claude.mjs +1 -1
  3. package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
  4. package/node_modules/projectstore/.claude-plugin/plugin.json +2 -1
  5. package/node_modules/projectstore/README.md +44 -7
  6. package/node_modules/projectstore/commands/bar.md +30 -0
  7. package/node_modules/projectstore/commands/bind.md +16 -20
  8. package/node_modules/projectstore/commands/doctor.md +12 -7
  9. package/node_modules/projectstore/commands/scaffold.md +24 -16
  10. package/node_modules/projectstore/commands/status.md +1 -1
  11. package/node_modules/projectstore/commands/statusline.md +2 -2
  12. package/node_modules/projectstore/commands/story.md +15 -2
  13. package/node_modules/projectstore/docs/alongside.md +167 -0
  14. package/node_modules/projectstore/docs/extending.md +17 -3
  15. package/node_modules/projectstore/docs/getting-started.md +18 -9
  16. package/node_modules/projectstore/docs/harnesses.md +1 -1
  17. package/node_modules/projectstore/docs/how-it-works.md +12 -3
  18. package/node_modules/projectstore/harnesses/claude-code.json +23 -8
  19. package/node_modules/projectstore/harnesses/codex.json +16 -6
  20. package/node_modules/projectstore/hooks/band-state.d.ts +44 -0
  21. package/node_modules/projectstore/hooks/band.tsx +622 -0
  22. package/node_modules/projectstore/hooks/hooks.json +1 -0
  23. package/node_modules/projectstore/hooks/pre-compact.mjs +7 -10
  24. package/node_modules/projectstore/hooks/session-rules.mjs +14 -8
  25. package/node_modules/projectstore/hooks/session-start.mjs +56 -20
  26. package/node_modules/projectstore/hooks/session-stop.mjs +8 -6
  27. package/node_modules/projectstore/package.json +12 -2
  28. package/node_modules/projectstore/scaffold/headings.json +8 -0
  29. package/node_modules/projectstore/scripts/band-shared.mjs +446 -0
  30. package/node_modules/projectstore/scripts/band.mjs +207 -0
  31. package/node_modules/projectstore/scripts/binding.mjs +733 -62
  32. package/node_modules/projectstore/scripts/build-adapters.mjs +98 -35
  33. package/node_modules/projectstore/scripts/cli.mjs +1463 -267
  34. package/node_modules/projectstore/scripts/codemap.mjs +22 -7
  35. package/node_modules/projectstore/scripts/convergence.mjs +638 -0
  36. package/node_modules/projectstore/scripts/diff-refs.mjs +14 -8
  37. package/node_modules/projectstore/scripts/doctor-report.mjs +253 -0
  38. package/node_modules/projectstore/scripts/doctor.mjs +1384 -420
  39. package/node_modules/projectstore/scripts/draft.mjs +5 -2
  40. package/node_modules/projectstore/scripts/fetch-shell.mjs +369 -0
  41. package/node_modules/projectstore/scripts/graph.mjs +19 -13
  42. package/node_modules/projectstore/scripts/harness.mjs +283 -37
  43. package/node_modules/projectstore/scripts/install-harness.mjs +4701 -575
  44. package/node_modules/projectstore/scripts/kanban.mjs +6 -21
  45. package/node_modules/projectstore/scripts/lib.mjs +933 -199
  46. package/node_modules/projectstore/scripts/mcp.mjs +386 -105
  47. package/node_modules/projectstore/scripts/portable-registration.mjs +276 -56
  48. package/node_modules/projectstore/scripts/provenance.mjs +26 -10
  49. package/node_modules/projectstore/scripts/query.mjs +441 -72
  50. package/node_modules/projectstore/scripts/reconcile.mjs +55 -18
  51. package/node_modules/projectstore/scripts/run-host.mjs +139 -0
  52. package/node_modules/projectstore/scripts/scaffold.mjs +244 -0
  53. package/node_modules/projectstore/scripts/setup.mjs +987 -0
  54. package/node_modules/projectstore/scripts/stages.mjs +84 -0
  55. package/node_modules/projectstore/scripts/statusline-launcher.mjs +36 -11
  56. package/node_modules/projectstore/scripts/statusline.mjs +55 -14
  57. package/node_modules/projectstore/scripts/story-section.mjs +39 -17
  58. package/node_modules/projectstore/scripts/surfaces.mjs +485 -99
  59. package/node_modules/projectstore/scripts/term.mjs +1027 -52
  60. package/node_modules/projectstore/scripts/tokens.mjs +106 -28
  61. package/node_modules/projectstore/scripts/touch-session.mjs +109 -70
  62. package/node_modules/projectstore/scripts/version-guard.mjs +79 -27
  63. package/node_modules/projectstore/scripts/worktree.mjs +16 -5
  64. package/node_modules/projectstore/templates/de/strings.json +63 -1
  65. package/node_modules/projectstore/templates/de/vault-readme.md.tmpl +13 -0
  66. package/node_modules/projectstore/templates/en/strings.json +63 -1
  67. package/node_modules/projectstore/templates/en/vault-readme.md.tmpl +13 -0
  68. package/node_modules/projectstore/templates/es/strings.json +63 -1
  69. package/node_modules/projectstore/templates/es/vault-readme.md.tmpl +13 -0
  70. package/node_modules/projectstore/templates/fr/strings.json +63 -1
  71. package/node_modules/projectstore/templates/fr/vault-readme.md.tmpl +13 -0
  72. package/node_modules/projectstore/templates/ru/strings.json +63 -1
  73. package/node_modules/projectstore/templates/ru/vault-readme.md.tmpl +13 -0
  74. package/node_modules/projectstore/templates/zh/strings.json +63 -1
  75. package/node_modules/projectstore/templates/zh/vault-readme.md.tmpl +13 -0
  76. package/package.json +2 -2
@@ -6,7 +6,7 @@
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, invocation, uiWordPatterns } from "./harness.mjs";
9
+ import { emittingHarnesses, sourceHarness, invocation, uiWordPatterns, hasBand } from "./harness.mjs";
10
10
  import { writeFileAtomic } from "./lib.mjs";
11
11
 
12
12
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -26,19 +26,27 @@ const SOURCE_UI = uiWordPatterns(SOURCE);
26
26
  // way only the source harness calls it. Never inside another form
27
27
  // (`/projectstore:`, `$projectstore-`), never a longer name.
28
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");
29
+ const SOURCE_ROLES = readdirSync(join(ROOT, SOURCE.surfaces.agents.dir))
30
+ .filter((f) => f.endsWith(ROLE_SUFFIX))
31
+ .map((f) => f.slice(0, -ROLE_SUFFIX.length))
32
+ .sort();
33
+ const SOURCE_ROLE = new RegExp(
34
+ `(?<![\\w/$:-])${escapeRe(SOURCE.surfaces.agents.invocation.split("<name>")[0])}(${SOURCE_ROLES.map(escapeRe).join("|")})(?![\\w-])`,
35
+ "g",
36
+ );
31
37
  const TARGETS = emittingHarnesses();
32
- if (TARGETS.length !== 1) throw new Error(`this renderer currently requires exactly one emitting harness; found ${TARGETS.length}`);
38
+ if (TARGETS.length !== 1)
39
+ throw new Error(`this renderer currently requires exactly one emitting harness; found ${TARGETS.length}`);
33
40
  const TARGET = TARGETS[0];
34
41
  const OUT = resolve(ROOT, TARGET.output_dir);
35
42
 
36
43
  const read = (p) => readFileSync(p, "utf8");
37
44
  const json = (p) => JSON.parse(read(p));
38
- const files = (dir, suffix = ".md") => readdirSync(dir)
39
- .filter((name) => name.endsWith(suffix))
40
- .sort()
41
- .map((name) => join(dir, name));
45
+ const files = (dir, suffix = ".md") =>
46
+ readdirSync(dir)
47
+ .filter((name) => name.endsWith(suffix))
48
+ .sort()
49
+ .map((name) => join(dir, name));
42
50
 
43
51
  function frontmatter(text, file) {
44
52
  const m = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
@@ -48,7 +56,8 @@ function frontmatter(text, file) {
48
56
  const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
49
57
  if (!kv) continue;
50
58
  let value = kv[2].trim();
51
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
59
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
60
+ value = value.slice(1, -1);
52
61
  data[kv[1]] = value;
53
62
  }
54
63
  return { data, body: m[2].replace(/^\n+/, "") };
@@ -92,7 +101,8 @@ function skill(name, description, body, { runtime = true } = {}) {
92
101
 
93
102
  const COMMAND_OVERRIDES = {
94
103
  agents: {
95
- description: "Register or remove ProjectStore's shared agents block, inspect its Codex model overlay, or configure per-role models. Arguments: <register | unregister | status | configure>.",
104
+ description:
105
+ "Register or remove ProjectStore's shared agents block, inspect its Codex model overlay, or configure per-role models. Arguments: <register | unregister | status | configure>.",
96
106
  body: `You are managing ProjectStore's Codex agent integration. Require a bound project.
97
107
 
98
108
  ## register / unregister
@@ -122,32 +132,42 @@ contract. A configured model is consumed by the role-orchestration skills on
122
132
  their next spawn; no restart is needed.`,
123
133
  },
124
134
  bind: {
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>].",
135
+ description:
136
+ "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>].",
126
137
  body: `Bind the current project through the core; never write the binding by
127
138
  hand.
128
139
 
129
140
  1. Resolve the requested vault path, and the layout and language when the
130
141
  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
142
+ exists and holds files, use \`bind\`. If it exists and is empty, use
143
+ \`init\`. If it is missing, use \`init\` when the user asks to create it.
144
+ \`init\` creates the whole vault: the directory (unless it is there and
145
+ empty), its git repository with no commit, and the layout's folders and
146
+ READMEs. Ask for layout and language only when the request has not
133
147
  supplied them.
134
148
  2. Show the resolved project, vault, verb, layout and language. Ask for explicit
135
149
  approval. Naming the vault is the core's non-interactive confirmation.
136
150
  3. Run \`node "\${PROJECTSTORE_CORE_ROOT}/bin/projectstore.mjs" <bind|init>
137
151
  "<vault-path>" --layout <layout> --language <language> --project "$PWD"
138
152
  --json\` and report the result. Use \`--rebind\` only when the user explicitly
139
- approved replacing an existing binding.
140
- 4. Run \`status --json\` to verify the stored path and policies. Offer
141
- \`$projectstore-scaffold\` only for a newly initialized empty vault.
153
+ approved replacing an existing binding. If the result carries
154
+ \`git.failed\` or \`scaffold.failed\`, the binding is written; relay the
155
+ remedy and continue.
156
+ 4. Run \`status --json\` to verify the stored path and policies. After
157
+ \`bind\` (an \`init\` vault is already scaffolded), run \`scaffold --json
158
+ --project "$PWD"\` and offer \`$projectstore-scaffold\` only when a row
159
+ reads \`create\`.
142
160
 
143
- Codex plugin updates are performed with the \`projectstore-codex upgrade\`
144
- installer command; this skill never sends the user to another harness's plugin
145
- UI. Codex has no ProjectStore status-line surface, so binding does not offer or
146
- write one. Model choices, when requested, go through \`$projectstore-agents
147
- configure\` and the Codex overlay.`,
161
+ Codex plugin updates are the core's \`upgrade\` with \`--harness codex\`, which
162
+ the person runs from a terminal in this project; this skill never sends the
163
+ user to another harness's plugin UI.
164
+ Codex has no ProjectStore status-line surface, so binding does not offer or
165
+ write one. Model choices, when requested, go through
166
+ \`$projectstore-agents configure\` and the Codex overlay.`,
148
167
  },
149
168
  doctor: {
150
- description: "Diagnose the ProjectStore install and vault through the harness-neutral doctor; fixes remain previewed and approval-gated. Arguments: [--install | --vault] [--fix].",
169
+ description:
170
+ "Diagnose the ProjectStore install and vault through the harness-neutral doctor; fixes remain previewed and approval-gated. Arguments: [--install | --vault] [--fix].",
151
171
  body: `Run the deterministic doctor through the bundled core with the user's
152
172
  arguments and \`--json\`. Summarize every finding without re-deriving it.
153
173
 
@@ -159,9 +179,19 @@ running it; then run \`upgrade --harness codex --json\`, whose envelope is the
159
179
  result — \`--json\` never waits on a terminal's question. Unsupported
160
180
  surfaces remain unsupported; do not create host configuration by hand. Never
161
181
  claim a fix after a non-zero exit.`,
182
+ },
183
+ bar: {
184
+ description:
185
+ "Explain ProjectStore band availability for Codex. This surface is unsupported and the skill never writes configuration. Arguments: on | off | status | epic [<ID>].",
186
+ body: `ProjectStore's band above the prompt is not supported by the Codex
187
+ harness: Codex has no surface that draws above the prompt and no module
188
+ runtime to draw it. Do not run the band verb's writes and do not edit project
189
+ or host configuration. Report the surface as unsupported. For project state
190
+ use \`$projectstore-status\`.`,
162
191
  },
163
192
  statusline: {
164
- description: "Explain ProjectStore status-line availability for Codex. This surface is unsupported and the skill never writes configuration. Arguments: on | off | status.",
193
+ description:
194
+ "Explain ProjectStore status-line availability for Codex. This surface is unsupported and the skill never writes configuration. Arguments: on | off | status.",
165
195
  body: `ProjectStore's status-line surface is not supported by the Codex
166
196
  harness. Do not run install or uninstall for \`--surface statusline\`, and do
167
197
  not edit project or host configuration. Report the surface as unsupported.
@@ -188,7 +218,10 @@ function renderCommand(file) {
188
218
  const override = COMMAND_OVERRIDES[verb];
189
219
  if (override) return [join("skills", name, "SKILL.md"), skill(name, override.description, override.body)];
190
220
  const hint = data["argument-hint"] ? ` Arguments: ${data["argument-hint"]}.` : "";
191
- return [join("skills", name, "SKILL.md"), skill(name, `${data.description || `Run the ProjectStore ${verb} workflow.`}${hint}`, body)];
221
+ return [
222
+ join("skills", name, "SKILL.md"),
223
+ skill(name, `${data.description || `Run the ProjectStore ${verb} workflow.`}${hint}`, body),
224
+ ];
192
225
  }
193
226
 
194
227
  function renderPassive(file) {
@@ -204,23 +237,32 @@ function renderRole(file) {
204
237
  const role = file.split("/").pop().replace(/\.md$/, "");
205
238
  const name = renderedName("agents", role);
206
239
  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`;
207
- const description = (data.description || `Run the ProjectStore ${role} role in a fresh collaboration agent.`)
208
- .replace(/^(?:Opus|Sonnet) \(max-effort\)\s+/, "");
240
+ const description = (data.description || `Run the ProjectStore ${role} role in a fresh collaboration agent.`).replace(
241
+ /^(?:Opus|Sonnet) \(max-effort\)\s+/,
242
+ "",
243
+ );
209
244
  return [join("skills", name, "SKILL.md"), skill(name, description, orchestration + body)];
210
245
  }
211
246
 
212
- function renderHooks(manifest, root = ROOT) {
247
+ export function renderHooks(manifest, root = ROOT) {
213
248
  const source = json(join(root, "hooks", "hooks.json"));
249
+ // The band is a hooks module only a harness with a band runs (the band
250
+ // spec, contract 1): elsewhere `modules` would name a file that harness
251
+ // cannot load, so its tree carries the classic hooks alone.
252
+ if (!hasBand(manifest)) delete source.modules;
214
253
  const post = source.hooks?.PostToolUse || [];
215
254
  for (const group of post) if (group.matcher) group.matcher = (manifest.tools?.write_tools || []).join("|");
216
255
  const rewrite = (value) => {
217
256
  if (Array.isArray(value)) return value.map(rewrite);
218
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, rewrite(v)]));
257
+ if (value && typeof value === "object")
258
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, rewrite(v)]));
219
259
  if (typeof value !== "string") return value;
220
260
  const sourceRoot = "${" + SOURCE.runtime.plugin_root_env + "}";
221
261
  const targetRoot = "${" + manifest.runtime.plugin_root_env + "}";
222
262
  const command = value.startsWith(`node ${sourceRoot}/`) ? value.slice(`node ${sourceRoot}/`.length) : null;
223
- return command ? `node "${targetRoot}/node_modules/projectstore/${command}"` : value.replaceAll(sourceRoot, `${targetRoot}/node_modules/projectstore`);
263
+ return command
264
+ ? `node "${targetRoot}/node_modules/projectstore/${command}"`
265
+ : value.replaceAll(sourceRoot, `${targetRoot}/node_modules/projectstore`);
224
266
  };
225
267
  const text = JSON.stringify(rewrite(source), null, 2) + "\n";
226
268
  return [join("hooks", "hooks.json"), text];
@@ -228,7 +270,8 @@ function renderHooks(manifest, root = ROOT) {
228
270
 
229
271
  export function renderCodexAdapter(root = ROOT) {
230
272
  const manifest = json(join(root, "harnesses", `${TARGET.id}.json`));
231
- if (!manifest.emit || !manifest.output_dir) throw new Error(`${TARGET.id} must declare an output_dir when emit is true`);
273
+ if (!manifest.emit || !manifest.output_dir)
274
+ throw new Error(`${TARGET.id} must declare an output_dir when emit is true`);
232
275
  const rows = [
233
276
  ...files(join(root, "commands")).map(renderCommand),
234
277
  ...files(join(root, "agents")).map(renderRole),
@@ -240,15 +283,34 @@ export function renderCodexAdapter(root = ROOT) {
240
283
  ];
241
284
  const out = new Map();
242
285
  for (const [rel, text] of rows) {
243
- if (rel.startsWith("..") || resolve(OUT, rel).slice(0, OUT.length + 1) !== OUT + "/") throw new Error(`adapter path escapes output root: ${rel}`);
286
+ if (rel.startsWith("..") || resolve(OUT, rel).slice(0, OUT.length + 1) !== OUT + "/")
287
+ throw new Error(`adapter path escapes output root: ${rel}`);
244
288
  if (out.has(rel)) throw new Error(`adapter collision: ${rel}`);
245
289
  out.set(rel, text);
246
290
  }
247
291
  for (const [rel, text] of out) {
248
- const sourceTokens = [SOURCE.runtime.plugin_root_env, SOURCE.runtime.project_dir_env, SOURCE.display_name, SOURCE.runtime.harness_dir + "/"].filter(Boolean);
292
+ const sourceTokens = [
293
+ SOURCE.runtime.plugin_root_env,
294
+ SOURCE.runtime.project_dir_env,
295
+ SOURCE.display_name,
296
+ SOURCE.runtime.harness_dir + "/",
297
+ ].filter(Boolean);
249
298
  const sourceAgentEnvs = (SOURCE.runtime.agent_overrides || []).map((row) => row.env).filter(Boolean);
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`);
299
+ if (
300
+ sourceTokens.some((token) => text.includes(token)) ||
301
+ sourceAgentEnvs.some((token) => text.includes(token)) ||
302
+ text.includes(SOURCE_COMMAND_PREFIX) ||
303
+ new RegExp(SOURCE_ROLE.source).test(text) ||
304
+ /\$ARGUMENTS/.test(text)
305
+ )
306
+ throw new Error(`${rel}: source-harness vocabulary leaked into ${TARGET.display_name} adapter`);
307
+ if (
308
+ SOURCE_UI.some(({ re }) => new RegExp(re.source).test(text)) ||
309
+ /harness\/codex-code\.json|\b(?:opus|sonnet|fable)\b/i.test(text)
310
+ )
311
+ throw new Error(
312
+ `${rel}: source-harness model, environment or UI semantics leaked into ${TARGET.display_name} adapter`,
313
+ );
252
314
  }
253
315
  return out;
254
316
  }
@@ -294,4 +356,5 @@ function main(argv) {
294
356
  return result.ok ? 0 : 1;
295
357
  }
296
358
 
297
- if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = main(process.argv.slice(2));
359
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url))
360
+ process.exitCode = main(process.argv.slice(2));