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.
- package/README.md +1 -1
- package/bin/projectstore-claude.mjs +1 -1
- package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
- package/node_modules/projectstore/.claude-plugin/plugin.json +2 -1
- package/node_modules/projectstore/README.md +44 -7
- package/node_modules/projectstore/commands/bar.md +30 -0
- package/node_modules/projectstore/commands/bind.md +16 -20
- package/node_modules/projectstore/commands/doctor.md +12 -7
- package/node_modules/projectstore/commands/scaffold.md +24 -16
- package/node_modules/projectstore/commands/status.md +1 -1
- package/node_modules/projectstore/commands/statusline.md +2 -2
- package/node_modules/projectstore/commands/story.md +15 -2
- package/node_modules/projectstore/docs/alongside.md +167 -0
- package/node_modules/projectstore/docs/extending.md +17 -3
- package/node_modules/projectstore/docs/getting-started.md +18 -9
- package/node_modules/projectstore/docs/harnesses.md +1 -1
- package/node_modules/projectstore/docs/how-it-works.md +12 -3
- package/node_modules/projectstore/harnesses/claude-code.json +23 -8
- package/node_modules/projectstore/harnesses/codex.json +16 -6
- package/node_modules/projectstore/hooks/band-state.d.ts +44 -0
- package/node_modules/projectstore/hooks/band.tsx +622 -0
- package/node_modules/projectstore/hooks/hooks.json +1 -0
- package/node_modules/projectstore/hooks/pre-compact.mjs +7 -10
- package/node_modules/projectstore/hooks/session-rules.mjs +14 -8
- package/node_modules/projectstore/hooks/session-start.mjs +56 -20
- package/node_modules/projectstore/hooks/session-stop.mjs +8 -6
- package/node_modules/projectstore/package.json +12 -2
- package/node_modules/projectstore/scaffold/headings.json +8 -0
- package/node_modules/projectstore/scripts/band-shared.mjs +446 -0
- package/node_modules/projectstore/scripts/band.mjs +207 -0
- package/node_modules/projectstore/scripts/binding.mjs +733 -62
- package/node_modules/projectstore/scripts/build-adapters.mjs +98 -35
- package/node_modules/projectstore/scripts/cli.mjs +1463 -267
- package/node_modules/projectstore/scripts/codemap.mjs +22 -7
- package/node_modules/projectstore/scripts/convergence.mjs +638 -0
- package/node_modules/projectstore/scripts/diff-refs.mjs +14 -8
- package/node_modules/projectstore/scripts/doctor-report.mjs +253 -0
- package/node_modules/projectstore/scripts/doctor.mjs +1384 -420
- package/node_modules/projectstore/scripts/draft.mjs +5 -2
- package/node_modules/projectstore/scripts/fetch-shell.mjs +369 -0
- package/node_modules/projectstore/scripts/graph.mjs +19 -13
- package/node_modules/projectstore/scripts/harness.mjs +283 -37
- package/node_modules/projectstore/scripts/install-harness.mjs +4701 -575
- package/node_modules/projectstore/scripts/kanban.mjs +6 -21
- package/node_modules/projectstore/scripts/lib.mjs +933 -199
- package/node_modules/projectstore/scripts/mcp.mjs +386 -105
- package/node_modules/projectstore/scripts/portable-registration.mjs +276 -56
- package/node_modules/projectstore/scripts/provenance.mjs +26 -10
- package/node_modules/projectstore/scripts/query.mjs +441 -72
- package/node_modules/projectstore/scripts/reconcile.mjs +55 -18
- package/node_modules/projectstore/scripts/run-host.mjs +139 -0
- package/node_modules/projectstore/scripts/scaffold.mjs +244 -0
- package/node_modules/projectstore/scripts/setup.mjs +987 -0
- package/node_modules/projectstore/scripts/stages.mjs +84 -0
- package/node_modules/projectstore/scripts/statusline-launcher.mjs +36 -11
- package/node_modules/projectstore/scripts/statusline.mjs +55 -14
- package/node_modules/projectstore/scripts/story-section.mjs +39 -17
- package/node_modules/projectstore/scripts/surfaces.mjs +485 -99
- package/node_modules/projectstore/scripts/term.mjs +1027 -52
- package/node_modules/projectstore/scripts/tokens.mjs +106 -28
- package/node_modules/projectstore/scripts/touch-session.mjs +109 -70
- package/node_modules/projectstore/scripts/version-guard.mjs +79 -27
- package/node_modules/projectstore/scripts/worktree.mjs +16 -5
- package/node_modules/projectstore/templates/de/strings.json +63 -1
- package/node_modules/projectstore/templates/de/vault-readme.md.tmpl +13 -0
- package/node_modules/projectstore/templates/en/strings.json +63 -1
- package/node_modules/projectstore/templates/en/vault-readme.md.tmpl +13 -0
- package/node_modules/projectstore/templates/es/strings.json +63 -1
- package/node_modules/projectstore/templates/es/vault-readme.md.tmpl +13 -0
- package/node_modules/projectstore/templates/fr/strings.json +63 -1
- package/node_modules/projectstore/templates/fr/vault-readme.md.tmpl +13 -0
- package/node_modules/projectstore/templates/ru/strings.json +63 -1
- package/node_modules/projectstore/templates/ru/vault-readme.md.tmpl +13 -0
- package/node_modules/projectstore/templates/zh/strings.json +63 -1
- package/node_modules/projectstore/templates/zh/vault-readme.md.tmpl +13 -0
- package/package.json +2 -2
|
@@ -25,11 +25,13 @@ import { readConfig, readStdinJson, adoptHookInput, roleForm, sharedRoleForm } f
|
|
|
25
25
|
// told once that both name one role — otherwise the second rule below would
|
|
26
26
|
// have it report the block as a contradiction every session.
|
|
27
27
|
function rules() {
|
|
28
|
-
const critic = roleForm("critic"),
|
|
28
|
+
const critic = roleForm("critic"),
|
|
29
|
+
reviewer = roleForm("reviewer");
|
|
29
30
|
const shared = sharedRoleForm("critic");
|
|
30
|
-
const bridge =
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const bridge =
|
|
32
|
+
shared === critic
|
|
33
|
+
? ""
|
|
34
|
+
: `\n\nThe \`AGENTS.md\` block may name roles as \`${shared}\`; that is the same role, called \`${critic}\` here.`;
|
|
33
35
|
return `# projectstore — standing rules for this session
|
|
34
36
|
|
|
35
37
|
**Artifact-first order.** A feature-sized request opens a vault artifact before
|
|
@@ -46,9 +48,11 @@ contradiction becomes invisible to the person who could have settled it.${bridge
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function emit(additionalContext) {
|
|
49
|
-
process.stdout.write(
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext },
|
|
54
|
+
}) + "\n",
|
|
55
|
+
);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
function main() {
|
|
@@ -70,6 +74,8 @@ function main() {
|
|
|
70
74
|
emit(rules());
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
try {
|
|
77
|
+
try {
|
|
78
|
+
main();
|
|
79
|
+
} catch {
|
|
74
80
|
// A hook must never break session startup.
|
|
75
81
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// open. Bounded and O(1) in vault size by construction.
|
|
14
14
|
|
|
15
15
|
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
16
|
-
import {
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
17
|
import {
|
|
18
18
|
readConfig,
|
|
19
19
|
gatherVaultFacts,
|
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
layoutPaths,
|
|
28
28
|
pickExisting,
|
|
29
29
|
activeHarnessId,
|
|
30
|
-
|
|
30
|
+
ensureStateRoot,
|
|
31
31
|
syncStatusLine,
|
|
32
32
|
cleanupStaleSessionState,
|
|
33
|
+
refreshStoryPointer,
|
|
33
34
|
armReminder,
|
|
34
35
|
truncEnd,
|
|
35
36
|
truncFront,
|
|
@@ -64,9 +65,10 @@ function welcomedMarkerWritePath(proj) {
|
|
|
64
65
|
// the one instruction it carries branches on the binding; everything else is
|
|
65
66
|
// the same message.
|
|
66
67
|
function buildWelcome(cfg = null) {
|
|
67
|
-
const start =
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const start =
|
|
69
|
+
cfg && cfg.vault_path
|
|
70
|
+
? `**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
|
+
: `**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.`;
|
|
70
72
|
return [
|
|
71
73
|
"# 👋 projectstore is loaded for the first time in this project",
|
|
72
74
|
"",
|
|
@@ -90,7 +92,11 @@ function showWelcomeOnce(proj, cfg = null) {
|
|
|
90
92
|
const text = buildWelcome(cfg);
|
|
91
93
|
try {
|
|
92
94
|
const marker = welcomedMarkerWritePath(proj);
|
|
93
|
-
|
|
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);
|
|
94
100
|
mkdirSync(dirname(marker), { recursive: true });
|
|
95
101
|
writeFileSync(marker, new Date().toISOString() + "\n", "utf8");
|
|
96
102
|
} catch {}
|
|
@@ -174,15 +180,25 @@ async function main() {
|
|
|
174
180
|
// an unbound one that is not a worktree pays a single ~10 ms probe.
|
|
175
181
|
let binding = null;
|
|
176
182
|
if (!cfg) {
|
|
177
|
-
try {
|
|
183
|
+
try {
|
|
184
|
+
binding = resolveBinding(proj);
|
|
185
|
+
} catch {}
|
|
178
186
|
}
|
|
179
187
|
const welcome = showWelcomeOnce(proj, cfg);
|
|
180
188
|
// The person's channel carries one instruction, and it has to be the right
|
|
181
|
-
// one: a bound project is told what it is bound to, not to bind again.
|
|
189
|
+
// one: a bound project is told what it is bound to, not to bind again. A
|
|
190
|
+
// command stands alone on its line, the update hint on lines of its own
|
|
191
|
+
// after it (the story "User-facing text says where to run…", rules 4 and 5).
|
|
182
192
|
const welcomeSystemMessage = welcome
|
|
183
|
-
?
|
|
193
|
+
? cfg && cfg.vault_path
|
|
184
194
|
? `👋 projectstore: first-run welcome shown. Bound to ${cfg.vault_path}. ${updateHint().line}`
|
|
185
|
-
:
|
|
195
|
+
: [
|
|
196
|
+
"👋 projectstore: first-run welcome shown. Start with:",
|
|
197
|
+
commandForm("bind", { args: "<vault-path>" }),
|
|
198
|
+
updateHint().line,
|
|
199
|
+
]
|
|
200
|
+
.filter(Boolean)
|
|
201
|
+
.join("\n")
|
|
186
202
|
: null;
|
|
187
203
|
|
|
188
204
|
if (!cfg) {
|
|
@@ -192,8 +208,10 @@ async function main() {
|
|
|
192
208
|
// whose parent is already bound. Ordering is the cheap fix; the small
|
|
193
209
|
// redundancy is accepted rather than papered over with copy that will rot.
|
|
194
210
|
const offer = binding && binding.state === "inheritable" ? bindingOfferText(binding) : "";
|
|
211
|
+
// What binding does first, then the command alone on its line: an
|
|
212
|
+
// in-session form that can carry the parent's vault path (rule 4).
|
|
195
213
|
const offerSystemMessage = offer
|
|
196
|
-
? `projectstore: this worktree is unbound
|
|
214
|
+
? `projectstore: this worktree is unbound. ${bindInherits() ? "Adopt the binding of" : "Bind it to the vault, layout and language of"} the checkout it was forked from, in the session:\n${inheritForm(binding.vaultPath, { layout: binding.layout, language: binding.language })}`
|
|
197
215
|
: null;
|
|
198
216
|
const body = offer + (welcome || "");
|
|
199
217
|
if (body) {
|
|
@@ -209,10 +227,23 @@ async function main() {
|
|
|
209
227
|
// Opt-in status line: keep settings.local.json pointed at this plugin
|
|
210
228
|
// version's statusline.mjs (self-heals on update). Best-effort; a settings
|
|
211
229
|
// write must never break session-context injection.
|
|
212
|
-
try {
|
|
230
|
+
try {
|
|
231
|
+
syncStatusLine(cfg, proj);
|
|
232
|
+
} catch {}
|
|
213
233
|
// Statusline-feature housekeeping, like syncStatusLine — must run even when
|
|
214
234
|
// auto_inject=false (touch-session writes pointers regardless of it).
|
|
215
|
-
try {
|
|
235
|
+
try {
|
|
236
|
+
cleanupStaleSessionState(proj);
|
|
237
|
+
} catch {}
|
|
238
|
+
// This session's story fields from the story file, once (ADR-006, amended
|
|
239
|
+
// 2026-10-12): a pointer written before a close, or a story closed elsewhere
|
|
240
|
+
// since, stops naming finished work. A bounded read; a lost race writes
|
|
241
|
+
// nothing.
|
|
242
|
+
if (sid) {
|
|
243
|
+
try {
|
|
244
|
+
await refreshStoryPointer(proj, cfg.vault_path, sid);
|
|
245
|
+
} catch {}
|
|
246
|
+
}
|
|
216
247
|
|
|
217
248
|
// Arm BEFORE the auto_inject gate. The entry reminder's markers must be
|
|
218
249
|
// re-armed after a compaction whether or not this session injects context —
|
|
@@ -227,7 +258,9 @@ async function main() {
|
|
|
227
258
|
// context while its marker persists on disk. Arming lets it fire once more;
|
|
228
259
|
// the cap of two is enforced by the election, not here.
|
|
229
260
|
if (sid && (input?.source === "compact" || input?.source === "clear")) {
|
|
230
|
-
try {
|
|
261
|
+
try {
|
|
262
|
+
armReminder(proj, sid);
|
|
263
|
+
} catch {}
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
if (cfg.auto_inject === false) {
|
|
@@ -268,23 +301,26 @@ async function main() {
|
|
|
268
301
|
}
|
|
269
302
|
}
|
|
270
303
|
|
|
271
|
-
// Cheap install-only doctor subset (ADR-005): one
|
|
272
|
-
// aborted past its budget rather than reporting a false "clean".
|
|
304
|
+
// Cheap install-only doctor subset (ADR-005): one part, only when N > 0;
|
|
305
|
+
// aborted past its budget rather than reporting a false "clean". Its doctor
|
|
306
|
+
// form stands alone on its line, as every command of this line does.
|
|
273
307
|
let doctorMsg = null;
|
|
274
308
|
let offers = [];
|
|
275
309
|
try {
|
|
276
310
|
const r = runStartupChecks(cfg, proj);
|
|
277
311
|
if (r.skipped) {
|
|
278
|
-
doctorMsg = `projectstore doctor: startup checks skipped
|
|
312
|
+
doctorMsg = `projectstore doctor: startup checks skipped. Run them in the session:\n${commandForm("doctor")}`;
|
|
279
313
|
} else if (r.count > 0) {
|
|
280
|
-
doctorMsg = `projectstore doctor: ${r.count} install issue(s)
|
|
314
|
+
doctorMsg = `projectstore doctor: ${r.count} install issue(s). See them in the session:\n${commandForm("doctor")}`;
|
|
281
315
|
}
|
|
282
316
|
// Offers (doctor's OFFER_CHECKS): one-time steps a user should see once,
|
|
283
317
|
// e.g. the re-stamp after a plugin update — not issues, not silent.
|
|
284
318
|
offers = (r.offers || []).map((m) => `projectstore: ${m}`);
|
|
285
319
|
} catch {}
|
|
286
|
-
|
|
287
|
-
|
|
320
|
+
// Each part on lines of its own: an offer ends with its command alone on
|
|
321
|
+
// its line, and a part joined after it with ` · ` would put text after the
|
|
322
|
+
// command (the story "User-facing text says where to run…", rule 4).
|
|
323
|
+
const systemMessage = [welcomeSystemMessage, doctorMsg, ...offers].filter(Boolean).join("\n") || null;
|
|
288
324
|
|
|
289
325
|
if (gatherError) {
|
|
290
326
|
emit(
|
|
@@ -71,12 +71,14 @@ async function main() {
|
|
|
71
71
|
|
|
72
72
|
if (!electEmitter(proj, sid)) return;
|
|
73
73
|
appendEntryLog(proj, { at: new Date().toISOString(), session_id: sid, score, via: "Stop" });
|
|
74
|
-
process.stdout.write(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
process.stdout.write(
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
hookSpecificOutput: {
|
|
77
|
+
hookEventName: "Stop",
|
|
78
|
+
additionalContext: entryReminderText(score),
|
|
79
|
+
},
|
|
80
|
+
}) + "\n",
|
|
81
|
+
);
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
main().catch(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectstore",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.4",
|
|
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",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"files": [
|
|
42
42
|
"bin/",
|
|
43
43
|
".claude-plugin/",
|
|
44
|
+
"!.claude-plugin/types/",
|
|
44
45
|
"agents/",
|
|
45
46
|
"commands/",
|
|
46
47
|
"harnesses/",
|
|
@@ -65,6 +66,15 @@
|
|
|
65
66
|
"shells": "node packaging/shells.mjs --write",
|
|
66
67
|
"shells:check": "node packaging/shells.mjs --check",
|
|
67
68
|
"shells:build": "node packaging/shells.mjs --build --out dist",
|
|
68
|
-
"release:check": "npm run adapters:check && npm run guard && npm run shells:check && npm test && npm pack --dry-run"
|
|
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"
|
|
69
79
|
}
|
|
70
80
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_description": "Language-independent registry of section headings, inline keywords and index-table column names recognized by the deterministic scripts (doctor, reconcile, story-section). Per id, per language: an ARRAY of accepted forms; the FIRST form of the bound language is the canonical form used when writing. Matching accepts every form of every language, so a ru-headed file lints in an en-bound vault. New sections/keywords added by any story MUST register a form for every bundled language (en, ru, es, de, fr, zh) here (PS-SPEC story-002).",
|
|
3
3
|
"headings": {
|
|
4
|
+
"goal": {
|
|
5
|
+
"en": ["Goal"],
|
|
6
|
+
"ru": ["Цель"],
|
|
7
|
+
"es": ["Objetivo"],
|
|
8
|
+
"de": ["Ziel"],
|
|
9
|
+
"fr": ["Objectif"],
|
|
10
|
+
"zh": ["目标"]
|
|
11
|
+
},
|
|
4
12
|
"description": {
|
|
5
13
|
"en": ["Description"],
|
|
6
14
|
"ru": ["Описание"],
|