projectstore-claude 0.30.5 → 0.30.6

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 (43) hide show
  1. package/node_modules/projectstore/.claude-plugin/marketplace.json +2 -2
  2. package/node_modules/projectstore/.claude-plugin/plugin.json +6 -2
  3. package/node_modules/projectstore/.mcp.json +1 -2
  4. package/node_modules/projectstore/README.md +65 -16
  5. package/node_modules/projectstore/agents/archaeologist.md +1 -1
  6. package/node_modules/projectstore/agents/librarian.md +1 -1
  7. package/node_modules/projectstore/agents/planner.md +1 -1
  8. package/node_modules/projectstore/commands/doctor.md +14 -11
  9. package/node_modules/projectstore/commands/reconcile.md +1 -1
  10. package/node_modules/projectstore/commands/review.md +2 -2
  11. package/node_modules/projectstore/commands/story.md +1 -1
  12. package/node_modules/projectstore/docs/getting-started.md +3 -4
  13. package/node_modules/projectstore/docs/harnesses.md +1 -1
  14. package/node_modules/projectstore/docs/images/icon.png +0 -0
  15. package/node_modules/projectstore/harnesses/claude-code.json +58 -9
  16. package/node_modules/projectstore/hooks/hooks.json +6 -6
  17. package/node_modules/projectstore/hooks/session-start.mjs +55 -24
  18. package/node_modules/projectstore/package.json +11 -12
  19. package/node_modules/projectstore/scripts/binding.mjs +1 -1
  20. package/node_modules/projectstore/scripts/build-adapters.mjs +45 -4
  21. package/node_modules/projectstore/scripts/cli.mjs +74 -1373
  22. package/node_modules/projectstore/scripts/convergence.mjs +59 -16
  23. package/node_modules/projectstore/scripts/doctor.mjs +184 -32
  24. package/node_modules/projectstore/scripts/harness.mjs +34 -3
  25. package/node_modules/projectstore/scripts/install-handlers.mjs +925 -0
  26. package/node_modules/projectstore/scripts/install-harness.mjs +129 -2081
  27. package/node_modules/projectstore/scripts/install-layout.mjs +453 -0
  28. package/node_modules/projectstore/scripts/install-preview.mjs +747 -0
  29. package/node_modules/projectstore/scripts/lib.mjs +167 -68
  30. package/node_modules/projectstore/scripts/mcp-server.mjs +33 -0
  31. package/node_modules/projectstore/scripts/mcp.mjs +60 -60
  32. package/node_modules/projectstore/scripts/model-spec.mjs +24 -0
  33. package/node_modules/projectstore/scripts/models.mjs +6 -10
  34. package/node_modules/projectstore/scripts/query.mjs +7 -6
  35. package/node_modules/projectstore/scripts/read-verbs.mjs +277 -0
  36. package/node_modules/projectstore/scripts/setup.mjs +62 -12
  37. package/node_modules/projectstore/scripts/statusline-launcher.mjs +36 -3
  38. package/node_modules/projectstore/scripts/surfaces.mjs +18 -5
  39. package/node_modules/projectstore/scripts/verbs.mjs +1202 -0
  40. package/node_modules/projectstore/scripts/version-guard.mjs +9 -5
  41. package/package.json +3 -3
  42. package/node_modules/projectstore/docs/images/loop-light.svg +0 -94
  43. package/node_modules/projectstore/docs/images/team-light.svg +0 -79
@@ -25,6 +25,7 @@ import {
25
25
  adoptHookInput,
26
26
  projectRoot,
27
27
  layoutPaths,
28
+ cachePaths,
28
29
  pickExisting,
29
30
  activeHarnessId,
30
31
  ensureStateRoot,
@@ -46,31 +47,48 @@ import {
46
47
  import { runStartupChecks } from "../scripts/doctor.mjs";
47
48
  import { resolveBinding, bindingOfferText } from "../scripts/worktree.mjs";
48
49
 
49
- // The marker lives under the harness's state directory; a legacy marker at
50
- // .claude/.projectstore-welcomed still counts while the window is open.
51
- function welcomedMarkerPath(proj) {
52
- const p = layoutPaths(proj);
53
- return pickExisting(p.welcomed(activeHarnessId()), p.legacy.welcomed);
50
+ // The welcome has been seen, by this harness, when either marker exists: the
51
+ // project's (state/<id>/welcomed, or the legacy .claude/.projectstore-welcomed
52
+ // while the window is open) or the machine's (the user-level cache's
53
+ // welcomed/<id>). Both branches read both; only the write follows the binding
54
+ // (the story "Opening a project that is not bound writes nothing into it…",
55
+ // decisions 1 and 2). So a project that holds a marker an unbound session of
56
+ // 0.30.5 or earlier wrote is not welcomed again.
57
+ //
58
+ // The machine's marker is null with no cache root, and a home the platform
59
+ // cannot answer for must not break startup either (skeleton spec, contract 17).
60
+ function machineMarker(id) {
61
+ try {
62
+ return cachePaths().welcomed(id);
63
+ } catch {
64
+ return null;
65
+ }
54
66
  }
55
- function welcomedMarkerWritePath(proj) {
56
- return layoutPaths(proj).welcomed(activeHarnessId());
67
+ function welcomeSeen(proj, id) {
68
+ const p = layoutPaths(proj);
69
+ const machine = machineMarker(id);
70
+ return existsSync(pickExisting(p.welcomed(id), p.legacy.welcomed)) || (machine !== null && existsSync(machine));
57
71
  }
58
72
 
59
- // One-time orientation packet shown when projectstore first loads in a project.
60
- // Idempotent via a marker file at <project>/.claude/.projectstore-welcomed.
61
- // The welcome fires once per project, and a project can already be bound when
62
- // it does — a fresh install into a project someone bound first is the ordinary
73
+ // One-time orientation packet shown when projectstore first loads. A bound
74
+ // project shows it once per project; a project that is not bound shows it once
75
+ // on this machine, because a user-scope install is on in every repository the
76
+ // person opens, and writing a marker into each of them is a write into
77
+ // projects nobody set up. A project can already be bound when the welcome
78
+ // fires — a fresh install into a project someone bound first is the ordinary
63
79
  // case, not a corner (measured 2026-09-06: the first line a new user read told
64
80
  // them to bind a project whose vault the install preview had just named). So
65
- // the one instruction it carries branches on the binding; everything else is
66
- // the same message.
81
+ // the one instruction it carries branches on the binding, and so does the
82
+ // footer that says how often it appears; everything else is the same message.
67
83
  function buildWelcome(cfg = null) {
68
84
  const start =
69
85
  cfg && cfg.vault_path
70
86
  ? `**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.`
71
87
  : `**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.`;
72
88
  return [
73
- "# 👋 projectstore is loaded for the first time in this project",
89
+ cfg
90
+ ? "# 👋 projectstore is loaded for the first time in this project"
91
+ : "# 👋 projectstore is loaded for the first time on this machine",
74
92
  "",
75
93
  "**What it does**: turns the conversation's decisions into a structured Obsidian-friendly markdown vault — ADRs, epics, stories, runbooks, research. Agent-maintained, you approve every write.",
76
94
  "",
@@ -80,7 +98,7 @@ function buildWelcome(cfg = null) {
80
98
  // a git marketplace's toggle means nothing to a harness installed from npm.
81
99
  ...updateHint().welcome,
82
100
  "",
83
- "_This message appears once per project._",
101
+ cfg ? "_This message appears once per project._" : "_This message appears once on this machine._",
84
102
  "",
85
103
  "_If projectstore helps you ship, a [GitHub star](https://github.com/SmartAndPoint/ProjectStore) helps others discover it. No pressure._",
86
104
  "",
@@ -88,17 +106,30 @@ function buildWelcome(cfg = null) {
88
106
  }
89
107
 
90
108
  function showWelcomeOnce(proj, cfg = null) {
91
- if (existsSync(welcomedMarkerPath(proj))) return "";
109
+ const id = activeHarnessId();
110
+ if (welcomeSeen(proj, id)) return "";
92
111
  const text = buildWelcome(cfg);
93
112
  try {
94
- const marker = welcomedMarkerWritePath(proj);
95
- // The state directory's own .gitignore, header included, even in an
96
- // unbound project — and no sessions/ there: it is how a project uninstall
97
- // recognises this marker as ours (install spec contract 13, as amended by
98
- // the shell-fetch story).
99
- ensureStateRoot(proj);
100
- mkdirSync(dirname(marker), { recursive: true });
101
- writeFileSync(marker, new Date().toISOString() + "\n", "utf8");
113
+ let marker = null;
114
+ if (cfg) {
115
+ // Bound: the project's marker, beside the launcher (layout spec contract
116
+ // 5). The state directory's own .gitignore comes with it, header
117
+ // included, and no sessions/: it is how a project uninstall recognises
118
+ // this marker as ours (install spec contract 13, as amended by the
119
+ // shell-fetch story).
120
+ ensureStateRoot(proj);
121
+ marker = layoutPaths(proj).welcomed(id);
122
+ } else {
123
+ // Not bound: the machine's marker, and nothing in the project. With no
124
+ // cache root (no usable home) nothing is written and the welcome shows
125
+ // again next time — startup never breaks over it (skeleton spec,
126
+ // contract 17).
127
+ marker = machineMarker(id);
128
+ }
129
+ if (marker) {
130
+ mkdirSync(dirname(marker), { recursive: true });
131
+ writeFileSync(marker, new Date().toISOString() + "\n", "utf8");
132
+ }
102
133
  } catch {}
103
134
  return text;
104
135
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectstore",
3
- "version": "0.30.5",
3
+ "version": "0.30.6",
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",
@@ -17,7 +17,7 @@
17
17
  "agentic",
18
18
  "ai-agents"
19
19
  ],
20
- "homepage": "https://github.com/SmartAndPoint/ProjectStore#readme",
20
+ "homepage": "https://projectstore.dev",
21
21
  "bugs": {
22
22
  "url": "https://github.com/SmartAndPoint/ProjectStore/issues"
23
23
  },
@@ -60,21 +60,20 @@
60
60
  "scripts": {
61
61
  "test": "node --test --import ./tests/fixtures/hermetic.mjs tests/*.test.mjs",
62
62
  "guard": "node scripts/version-guard.mjs",
63
+ "directory:check": "node packaging/directory-guard.mjs",
63
64
  "packlist": "node scripts/version-guard.mjs --write-packlist",
64
65
  "adapters": "node scripts/build-adapters.mjs --write",
65
66
  "adapters:check": "node scripts/build-adapters.mjs --check",
66
67
  "shells": "node packaging/shells.mjs --write",
67
68
  "shells:check": "node packaging/shells.mjs --check",
68
69
  "shells:build": "node packaging/shells.mjs --build --out dist",
69
- "release:check": "npm run adapters:check && npm run guard && npm run shells:check && npm run lint && npm run format:check && npm test && npm pack --dry-run",
70
- "lint": "eslint --max-warnings 0 bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs",
71
- "format": "prettier --write bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs",
72
- "format:check": "prettier --check bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs"
73
- },
74
- "devDependencies": {
75
- "@eslint/js": "10.0.1",
76
- "eslint": "10.12.0",
77
- "eslint-plugin-n": "18.4.1",
78
- "prettier": "3.9.10"
70
+ "release:check": "npm run adapters:check && npm run guard && npm run directory:check && npm run shells:check && npm run lint && npm run format:check && npm test && npm pack --dry-run",
71
+ "lint:setup": "npm ci --prefix tools/lint",
72
+ "prelint": "node tools/lint/setup-check.mjs",
73
+ "preformat": "node tools/lint/setup-check.mjs",
74
+ "preformat:check": "node tools/lint/setup-check.mjs",
75
+ "lint": "node tools/lint/node_modules/eslint/bin/eslint.js --max-warnings 0 bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs tools/lint/eslint.config.mjs tools/lint/setup-check.mjs",
76
+ "format": "node tools/lint/node_modules/prettier/bin/prettier.cjs --write bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs tools/lint/eslint.config.mjs tools/lint/setup-check.mjs",
77
+ "format:check": "node tools/lint/node_modules/prettier/bin/prettier.cjs --check bin scripts \"hooks/*.mjs\" \"packaging/**/*.mjs\" \"tests/**/*.mjs\" eslint.config.mjs tools/lint/eslint.config.mjs tools/lint/setup-check.mjs"
79
78
  }
80
79
  }
@@ -20,7 +20,7 @@
20
20
  // project's decisions, not the vault's.
21
21
  //
22
22
  // Never reads ambient cwd or ambient env: the caller resolves the project
23
- // (cli.mjs's resolveProject) and supplies the author. Normative: the CLI
23
+ // (verbs.mjs's resolveProject) and supplies the author. Normative: the CLI
24
24
  // story's slice-2 plan (PS-CORE); the fresh config's keys mirror
25
25
  // commands/bind.md step 3 (a test pins the two together).
26
26
  //
@@ -251,7 +251,39 @@ function renderRole(file) {
251
251
  const { data, body } = frontmatter(read(file), file);
252
252
  const role = file.split("/").pop().replace(/\.md$/, "");
253
253
  const name = renderedName("agents", role);
254
- const orchestration = `## Codex orchestration\n\nThis is a role-orchestration skill, not a native agent registration. Resolve the\nrole's model and effort 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. If the result names an effort, pass it as \`${effortParam()}\`,\nwith or without a model; otherwise pass none. Give the spawned agent the role\ncontract below and the exact artifact/diff it must inspect. Wait for its final\nresult.\n\n## Role contract\n\n`;
254
+ // The spawn sentences name the fields and the value, verbatim: "if the
255
+ // result names a model, pass that model" let a parent read "that model" as
256
+ // its own, and pass it in 2 of 3 runs (the story "Codex role skills pass the
257
+ // model the core names, verbatim", PS-HARNESS). No example slug: the failure
258
+ // is a substituted model name, and an example is one more name to copy.
259
+ const orchestration = `## Codex orchestration
260
+
261
+ This is a role-orchestration skill, not a native agent registration. Resolve the
262
+ role's model and effort by running:
263
+
264
+ \`\`\`bash
265
+ node "\${PROJECTSTORE_CORE_ROOT}/bin/projectstore.mjs" agents model ${role} --json --project "$PWD"
266
+ \`\`\`
267
+
268
+ Spawn a collaboration agent for the bounded task. Its model and effort are the
269
+ two fields that command printed, \`result.model\` and \`result.effort\`, passed
270
+ verbatim:
271
+
272
+ - If \`result.model\` is a string, pass \`result.model\` exactly as \`model\`: the
273
+ string the command printed, even when it names the model you are running
274
+ on. Never substitute your own model for it. Use an empty or bounded context
275
+ fork.
276
+ - If \`result.model\` is \`null\`, pass no model at all, not even your own; the
277
+ agent inherits the current model.
278
+ - If \`result.effort\` is a string, pass it as \`${effortParam()}\`, verbatim,
279
+ with or without a model. If \`result.effort\` is \`null\`, pass none.
280
+
281
+ Give the spawned agent the role contract below and the exact artifact/diff it
282
+ must inspect. Wait for its final result.
283
+
284
+ ## Role contract
285
+
286
+ `;
255
287
  const description = (data.description || `Run the ProjectStore ${role} role in a fresh collaboration agent.`).replace(
256
288
  /^(?:Opus|Sonnet) \([a-z]+-effort\)\s+/,
257
289
  "",
@@ -267,14 +299,23 @@ export function renderHooks(manifest, root = ROOT) {
267
299
  if (!hasBand(manifest)) delete source.modules;
268
300
  const post = source.hooks?.PostToolUse || [];
269
301
  for (const group of post) if (group.matcher) group.matcher = (manifest.tools?.write_tools || []).join("|");
302
+ const sourceRoot = "${" + SOURCE.runtime.plugin_root_env + "}";
303
+ const targetRoot = "${" + manifest.runtime.plugin_root_env + "}";
304
+ // The source may write its root bare, quote the whole path ("${ROOT}/x") or
305
+ // quote the variable alone ("${ROOT}"/x): the host's two documentation pages
306
+ // show the last two. Codex keys hook trust to the rendered command text, so
307
+ // all three render the one command, and a re-quoted source cannot ask every
308
+ // Codex user to trust the hooks again (the story "Every hook command quotes
309
+ // its plugin root…", PS-DIRECTORY, decision 2).
310
+ const r = escapeRe(sourceRoot);
311
+ const sourceCommand = new RegExp(`^node (?:"${r}/([^"\\s]+)"|"${r}"/([^"\\s]+)|${r}/([^"\\s]+))$`);
270
312
  const rewrite = (value) => {
271
313
  if (Array.isArray(value)) return value.map(rewrite);
272
314
  if (value && typeof value === "object")
273
315
  return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, rewrite(v)]));
274
316
  if (typeof value !== "string") return value;
275
- const sourceRoot = "${" + SOURCE.runtime.plugin_root_env + "}";
276
- const targetRoot = "${" + manifest.runtime.plugin_root_env + "}";
277
- const command = value.startsWith(`node ${sourceRoot}/`) ? value.slice(`node ${sourceRoot}/`.length) : null;
317
+ const m = sourceCommand.exec(value);
318
+ const command = m ? m[1] || m[2] || m[3] : null;
278
319
  return command
279
320
  ? `node "${targetRoot}/node_modules/projectstore/${command}"`
280
321
  : value.replaceAll(sourceRoot, `${targetRoot}/node_modules/projectstore`);