projectstore-claude 0.29.2 → 0.29.3
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/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 +27 -7
- package/node_modules/projectstore/commands/bar.md +30 -0
- package/node_modules/projectstore/commands/bind.md +14 -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 +1 -1
- 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 +7 -0
- package/node_modules/projectstore/harnesses/claude-code.json +18 -3
- package/node_modules/projectstore/harnesses/codex.json +12 -1
- package/node_modules/projectstore/hooks/band-state.d.ts +44 -0
- package/node_modules/projectstore/hooks/band.tsx +594 -0
- package/node_modules/projectstore/hooks/hooks.json +1 -0
- package/node_modules/projectstore/hooks/session-start.mjs +6 -2
- package/node_modules/projectstore/package.json +2 -1
- package/node_modules/projectstore/scaffold/headings.json +8 -0
- package/node_modules/projectstore/scripts/band-shared.mjs +329 -0
- package/node_modules/projectstore/scripts/band.mjs +156 -0
- package/node_modules/projectstore/scripts/binding.mjs +157 -17
- package/node_modules/projectstore/scripts/build-adapters.mjs +26 -7
- package/node_modules/projectstore/scripts/cli.mjs +545 -179
- package/node_modules/projectstore/scripts/convergence.mjs +468 -0
- package/node_modules/projectstore/scripts/doctor-report.mjs +185 -0
- package/node_modules/projectstore/scripts/doctor.mjs +80 -62
- package/node_modules/projectstore/scripts/fetch-shell.mjs +283 -0
- package/node_modules/projectstore/scripts/harness.mjs +178 -7
- package/node_modules/projectstore/scripts/install-harness.mjs +1488 -257
- package/node_modules/projectstore/scripts/kanban.mjs +4 -20
- package/node_modules/projectstore/scripts/lib.mjs +257 -58
- package/node_modules/projectstore/scripts/portable-registration.mjs +62 -15
- package/node_modules/projectstore/scripts/query.mjs +83 -12
- package/node_modules/projectstore/scripts/run-host.mjs +94 -0
- package/node_modules/projectstore/scripts/scaffold.mjs +183 -0
- package/node_modules/projectstore/scripts/stages.mjs +80 -0
- package/node_modules/projectstore/scripts/statusline.mjs +25 -2
- package/node_modules/projectstore/scripts/surfaces.mjs +78 -13
- package/node_modules/projectstore/scripts/term.mjs +729 -39
- package/node_modules/projectstore/scripts/version-guard.mjs +5 -0
- 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
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
claudeHome,
|
|
45
45
|
whichOnPath,
|
|
46
46
|
installedPluginEntries,
|
|
47
|
+
projectCopiesByKey,
|
|
47
48
|
pluginEnabled,
|
|
48
49
|
treeDigest,
|
|
49
50
|
packageDigest,
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
RUNTIME_GITIGNORE_HEADER,
|
|
53
54
|
LAUNCHER_HEADER,
|
|
54
55
|
commandForm,
|
|
56
|
+
registrationProvenance,
|
|
55
57
|
} from "./lib.mjs";
|
|
56
58
|
import { analysePortableRegistration, portableRegistrationPaths, portablePayloadRoot } from "./portable-registration.mjs";
|
|
57
59
|
|
|
@@ -89,7 +91,12 @@ export function isOurFile(text) {
|
|
|
89
91
|
|
|
90
92
|
// ─── markdown-block: the ADR-002 block ─────────────────────────────────
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
// `layout` names the roster the run leaves (a layout name; the binding's when
|
|
95
|
+
// null). `readers` is a run's harnesses as [{ display, files }], passed when a
|
|
96
|
+
// run names more than one: the migration reason then names the one whose
|
|
97
|
+
// files lack the block's file, rather than the harness whose surface `s` is
|
|
98
|
+
// (the story "One run plans the agents block once…", rules 1 and 3).
|
|
99
|
+
export function analyseBlock(projectDir, s, { root = pluginRoot(), manifestDir = MANIFEST_DIR, layout = null, readers = null } = {}) {
|
|
93
100
|
const files = s.files || ["AGENTS.md", "CLAUDE.md"];
|
|
94
101
|
const PREFERRED = files[0], FALLBACK = files[files.length - 1];
|
|
95
102
|
// LOOK across every file any manifest names; WRITE to one this harness can
|
|
@@ -120,8 +127,9 @@ export function analyseBlock(projectDir, s, { root = pluginRoot(), manifestDir =
|
|
|
120
127
|
a.version = agentsBlockVersion(tmpl.text);
|
|
121
128
|
const cfg = readConfigAt(projectDir);
|
|
122
129
|
let roster = null;
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
const name = layout ?? cfg?.layout;
|
|
131
|
+
if (typeof name === "string") {
|
|
132
|
+
try { roster = loadLayout(name, root).agents || null; } catch { roster = null; }
|
|
125
133
|
}
|
|
126
134
|
a.desired = renderAgentsBlock(tmpl.text, roster);
|
|
127
135
|
a.current = withBlock.find((e) => e.file === preferred.file) || withBlock[0] || null;
|
|
@@ -134,7 +142,10 @@ export function analyseBlock(projectDir, s, { root = pluginRoot(), manifestDir =
|
|
|
134
142
|
// never acquires an AGENTS.md it did not ask for, while installing Codex into
|
|
135
143
|
// that same project moves the substance to the file Codex can read and leaves
|
|
136
144
|
// CLAUDE.md importing it.
|
|
137
|
-
if (!a.current.own)
|
|
145
|
+
if (!a.current.own) {
|
|
146
|
+
const reader = readers?.find((r) => !(r.files || []).includes(a.current.file))?.display;
|
|
147
|
+
return { ...a, state: "ours-stale", reason: `in ${a.current.file}, which ${reader || s.harness_display || "this harness"} does not read; install moves it to ${preferred.file}` };
|
|
148
|
+
}
|
|
138
149
|
if (a.current.file !== preferred.file && preferred.present) return { ...a, state: "ours-stale", reason: `in ${a.current.file}; install migrates it to ${preferred.file}` };
|
|
139
150
|
if (a.duplicates.length) return { ...a, state: "ours-stale", reason: `also in ${a.duplicates.map((e) => e.file).join(", ")}; install keeps the one in ${a.current.file}` };
|
|
140
151
|
if (a.current.block.v === a.version && a.current.block.block === a.desired) return { ...a, state: "ours-current" };
|
|
@@ -250,8 +261,8 @@ export function analyseRegistration(projectDir, s, { root = pluginRoot(), home =
|
|
|
250
261
|
// Machine-global facts: our directory, the host's marketplace registry, every row of ours.
|
|
251
262
|
if (a.dir.present) {
|
|
252
263
|
a.dir.manifest = readJson(paths.manifest);
|
|
253
|
-
const prov = a.dir.manifest
|
|
254
|
-
if (prov
|
|
264
|
+
const prov = registrationProvenance(a.dir.manifest, s);
|
|
265
|
+
if (prov) {
|
|
255
266
|
a.dir.prov = prov; a.dir.pkg = prov.pkg;
|
|
256
267
|
a.dir.disabled = Array.isArray(prov.disabled) ? prov.disabled.filter((x) => typeof x === "string") : [];
|
|
257
268
|
if (typeof prov.project === "string" && !samePath(prov.project, projectDir)) a.writtenBy = prov.project;
|
|
@@ -278,12 +289,13 @@ export function analyseRegistration(projectDir, s, { root = pluginRoot(), home =
|
|
|
278
289
|
a.installPath = row ? row.path : null;
|
|
279
290
|
a.installedVersion = row ? row.version : null;
|
|
280
291
|
a.enabled = pluginEnabled(id, home, projectDir);
|
|
281
|
-
// Competing registrations of the same plugin, enabled for this project
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
292
|
+
// Competing registrations of the same plugin, enabled for this project:
|
|
293
|
+
// contract 12's selection, one row per key — the row the host loads here,
|
|
294
|
+
// this checkout's own before a user-scope one. The convergence story's rule
|
|
295
|
+
// 6 reads the same rows (lib's projectCopiesByKey), so the deferral of a
|
|
296
|
+
// newer copy and the stop never disagree about its version.
|
|
297
|
+
for (const e of projectCopiesByKey(home, projectDir)) {
|
|
298
|
+
if (e.key !== id) a.others.push({ key: e.key, path: e.path, version: e.version });
|
|
287
299
|
}
|
|
288
300
|
a.newer = Boolean(a.dir.pkg) && cmpPrecedence(a.dir.pkg, pkg) > 0;
|
|
289
301
|
// Same version, different payload: the directory's recorded digest against
|
|
@@ -318,6 +330,15 @@ export function analyseRegistration(projectDir, s, { root = pluginRoot(), home =
|
|
|
318
330
|
return { ...a, state: "current", reason: a.newer ? `the directory is at ${a.dir.pkg}, newer than this package (${pkg})${a.writtenBy ? `, written from ${a.writtenBy}` : ""} — not downgraded` : null };
|
|
319
331
|
}
|
|
320
332
|
|
|
333
|
+
// The root a registration renders the project's dependent surfaces against,
|
|
334
|
+
// read-only (contract 4′, two phases): the host's install path of this
|
|
335
|
+
// checkout's row, when that copy is on disk and enabled; else null. One rule
|
|
336
|
+
// for plan() (a run that leaves the registration out) and surfaceStates
|
|
337
|
+
// (doctor), so the two cannot disagree about which root a launcher is for.
|
|
338
|
+
export function registrationRenderRoot(a) {
|
|
339
|
+
return a && a.installed && (a.installed.present ?? true) && a.enabled ? a.installPath : null;
|
|
340
|
+
}
|
|
341
|
+
|
|
321
342
|
// ─── the project-level layout: legacy / new / both (the layout ADR) ─────
|
|
322
343
|
//
|
|
323
344
|
// Which of the two layouts a project holds, file by file — what the `layout`
|
|
@@ -357,6 +378,35 @@ export function analyseLayout(projectDir, { harness = null } = {}) {
|
|
|
357
378
|
};
|
|
358
379
|
}
|
|
359
380
|
|
|
381
|
+
// ─── a harness's state directory: the welcome marker (contract 13) ──────
|
|
382
|
+
//
|
|
383
|
+
// What a project uninstall may take from .projectstore/state/<id>/: the
|
|
384
|
+
// welcome marker, and the directory once it is empty. Without that the
|
|
385
|
+
// directory outlives the uninstall and the next bare upgrade selects the
|
|
386
|
+
// removed harness again (layout spec contract 5). The marker carries no
|
|
387
|
+
// provenance line, so it is recognised three ways at once — the third
|
|
388
|
+
// recogniser contract 13 accepts beside the provenance line and rung 1″: its
|
|
389
|
+
// path, layoutPaths(p).welcomed(id); the state directory's .gitignore carrying
|
|
390
|
+
// RUNTIME_GITIGNORE_HEADER; and its content, one ISO timestamp line, exactly
|
|
391
|
+
// as the SessionStart hook writes it. Anything else in the directory — a
|
|
392
|
+
// launcher, a file of the user's — is named in `others` and never ours to
|
|
393
|
+
// take here: a launcher is its surface item's. Read-only, like everything in
|
|
394
|
+
// this module.
|
|
395
|
+
const WELCOME_STAMP = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\n?$/;
|
|
396
|
+
export function analyseHarnessState(projectDir, id) {
|
|
397
|
+
const p = layoutPaths(projectDir);
|
|
398
|
+
const dir = p.harnessState(id);
|
|
399
|
+
let names;
|
|
400
|
+
try { names = readdirSync(dir).sort(); } catch { return { dir, present: false, names: [], headed: false, marker: null, others: [] }; }
|
|
401
|
+
let headed = false;
|
|
402
|
+
try { headed = readFileSync(p.stateGitignore, "utf8").includes(RUNTIME_GITIGNORE_HEADER); } catch {}
|
|
403
|
+
const markerPath = p.welcomed(id);
|
|
404
|
+
const markerName = markerPath.slice(dir.length + 1);
|
|
405
|
+
const t = names.includes(markerName) ? readText(markerPath) : { present: false, text: null };
|
|
406
|
+
const marker = headed && t.present && typeof t.text === "string" && WELCOME_STAMP.test(t.text) ? markerPath : null;
|
|
407
|
+
return { dir, present: true, names, headed, marker, others: names.filter((n) => !(marker && n === markerName)) };
|
|
408
|
+
}
|
|
409
|
+
|
|
360
410
|
// ─── every surface, for doctor ─────────────────────────────────────────
|
|
361
411
|
|
|
362
412
|
export { analysePortableRegistration, portableRegistrationPaths };
|
|
@@ -378,9 +428,21 @@ export function surfaceStates(projectDir, { home = homedir(), root = pluginRoot(
|
|
|
378
428
|
// that merely contained the harness's directory.
|
|
379
429
|
const rows = Object.entries(m.surfaces || {}).filter(([k, s]) => !k.startsWith("_") && s.kind !== "host" && s.supported !== false && ANALYSERS[s.format]
|
|
380
430
|
&& !(s.condition === "distribution_root" && !portablePayloadRoot(s, { root, env })));
|
|
431
|
+
// The render root, as plan() takes it (contract 0 as amended 2026-10-10):
|
|
432
|
+
// an enabled, produced registration's install path. The launcher and the
|
|
433
|
+
// status-line entry are analysed against it — from the marketplace
|
|
434
|
+
// directory the host loads an npm registration from, or from a package
|
|
435
|
+
// root, the running root alone read the launcher as "not produced here".
|
|
436
|
+
// Read once, before the rows (the registration sorts last in the
|
|
437
|
+
// manifest); its own row reuses the result, since the analysis digests
|
|
438
|
+
// the payload.
|
|
439
|
+
const reg = rows.find(([, s]) => s.kind === "registration");
|
|
440
|
+
const regA = reg ? ANALYSERS[reg[1].format](projectDir, reg[1], { root, home, harness: m, env }) : null;
|
|
441
|
+
const renderRoot = (regA && regA.produced !== false && registrationRenderRoot(regA)) || root;
|
|
381
442
|
const states = [];
|
|
382
443
|
for (const [key, s] of rows) {
|
|
383
|
-
const
|
|
444
|
+
const dependent = renderRoot !== root && ["json-entry", "mjs"].includes(s.format);
|
|
445
|
+
const a = reg && s === reg[1] ? regA : ANALYSERS[s.format](projectDir, s, { root, home, harness: m, env, ...(dependent ? { renderRoot } : {}) });
|
|
384
446
|
const entry = s.kind === "shared" ? (a.entryKey || s.marker?.pointer || null) : null;
|
|
385
447
|
const path = a.legacyPath || a.path || (a.current ? a.current.path : (a.preferred ? a.preferred.path : join(projectDir, s.file || "")));
|
|
386
448
|
const row = { harness: m.id, surface: key, kind: s.kind, path, entry, state: a.state, reason: a.reason || a.refusal || null, writtenBy: a.writtenBy || null, sameProject: Boolean(a.sameProject), produced: a.produced !== false, legacy: Boolean(a.legacy), installedPkg: a.installedPkg || null, present: a.file ? a.file.present : (a.current ? true : (a.curEntry ? true : false)) };
|
|
@@ -392,6 +454,9 @@ export function surfaceStates(projectDir, { home = homedir(), root = pluginRoot(
|
|
|
392
454
|
dirPkg: a.dir?.pkg || a.ownership?.version || null,
|
|
393
455
|
installedVersion: a.installedVersion,
|
|
394
456
|
installPath: a.installPath,
|
|
457
|
+
// The plugin's root inside our directory (registrationPaths' payload):
|
|
458
|
+
// what a host that loads the registration in place runs from.
|
|
459
|
+
payload: a.paths?.payload || null,
|
|
395
460
|
enabled: a.enabled,
|
|
396
461
|
others: a.others || [],
|
|
397
462
|
otherProjects: a.otherProjects || 0,
|