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.
- package/node_modules/projectstore/.claude-plugin/marketplace.json +2 -2
- package/node_modules/projectstore/.claude-plugin/plugin.json +6 -2
- package/node_modules/projectstore/.mcp.json +1 -2
- package/node_modules/projectstore/README.md +65 -16
- package/node_modules/projectstore/agents/archaeologist.md +1 -1
- package/node_modules/projectstore/agents/librarian.md +1 -1
- package/node_modules/projectstore/agents/planner.md +1 -1
- package/node_modules/projectstore/commands/doctor.md +14 -11
- package/node_modules/projectstore/commands/reconcile.md +1 -1
- package/node_modules/projectstore/commands/review.md +2 -2
- package/node_modules/projectstore/commands/story.md +1 -1
- package/node_modules/projectstore/docs/getting-started.md +3 -4
- package/node_modules/projectstore/docs/harnesses.md +1 -1
- package/node_modules/projectstore/docs/images/icon.png +0 -0
- package/node_modules/projectstore/harnesses/claude-code.json +58 -9
- package/node_modules/projectstore/hooks/hooks.json +6 -6
- package/node_modules/projectstore/hooks/session-start.mjs +55 -24
- package/node_modules/projectstore/package.json +11 -12
- package/node_modules/projectstore/scripts/binding.mjs +1 -1
- package/node_modules/projectstore/scripts/build-adapters.mjs +45 -4
- package/node_modules/projectstore/scripts/cli.mjs +74 -1373
- package/node_modules/projectstore/scripts/convergence.mjs +59 -16
- package/node_modules/projectstore/scripts/doctor.mjs +184 -32
- package/node_modules/projectstore/scripts/harness.mjs +34 -3
- package/node_modules/projectstore/scripts/install-handlers.mjs +925 -0
- package/node_modules/projectstore/scripts/install-harness.mjs +129 -2081
- package/node_modules/projectstore/scripts/install-layout.mjs +453 -0
- package/node_modules/projectstore/scripts/install-preview.mjs +747 -0
- package/node_modules/projectstore/scripts/lib.mjs +167 -68
- package/node_modules/projectstore/scripts/mcp-server.mjs +33 -0
- package/node_modules/projectstore/scripts/mcp.mjs +60 -60
- package/node_modules/projectstore/scripts/model-spec.mjs +24 -0
- package/node_modules/projectstore/scripts/models.mjs +6 -10
- package/node_modules/projectstore/scripts/query.mjs +7 -6
- package/node_modules/projectstore/scripts/read-verbs.mjs +277 -0
- package/node_modules/projectstore/scripts/setup.mjs +62 -12
- package/node_modules/projectstore/scripts/statusline-launcher.mjs +36 -3
- package/node_modules/projectstore/scripts/surfaces.mjs +18 -5
- package/node_modules/projectstore/scripts/verbs.mjs +1202 -0
- package/node_modules/projectstore/scripts/version-guard.mjs +9 -5
- package/package.json +3 -3
- package/node_modules/projectstore/docs/images/loop-light.svg +0 -94
- 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
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
56
|
-
|
|
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
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
const id = activeHarnessId();
|
|
110
|
+
if (welcomeSeen(proj, id)) return "";
|
|
92
111
|
const text = buildWelcome(cfg);
|
|
93
112
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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.
|
|
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://
|
|
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": "
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"
|
|
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
|
-
// (
|
|
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
|
-
|
|
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
|
|
276
|
-
const
|
|
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`);
|