projectstore-claude 0.28.2 → 0.29.1

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 (32) hide show
  1. package/README.md +3 -1
  2. package/bin/projectstore-claude.mjs +14 -9
  3. package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
  4. package/node_modules/projectstore/.claude-plugin/plugin.json +1 -1
  5. package/node_modules/projectstore/README.md +2 -2
  6. package/node_modules/projectstore/commands/review.md +1 -1
  7. package/node_modules/projectstore/harnesses/claude-code.json +20 -2
  8. package/node_modules/projectstore/harnesses/codex.json +24 -4
  9. package/node_modules/projectstore/hooks/pre-compact.mjs +2 -1
  10. package/node_modules/projectstore/hooks/session-rules.mjs +19 -7
  11. package/node_modules/projectstore/hooks/session-start.mjs +20 -18
  12. package/node_modules/projectstore/package.json +2 -2
  13. package/node_modules/projectstore/scripts/binding.mjs +3 -3
  14. package/node_modules/projectstore/scripts/build-adapters.mjs +51 -18
  15. package/node_modules/projectstore/scripts/cli.mjs +176 -29
  16. package/node_modules/projectstore/scripts/codemap.mjs +2 -2
  17. package/node_modules/projectstore/scripts/doctor.mjs +110 -58
  18. package/node_modules/projectstore/scripts/draft.mjs +4 -3
  19. package/node_modules/projectstore/scripts/graph.mjs +3 -2
  20. package/node_modules/projectstore/scripts/harness.mjs +89 -0
  21. package/node_modules/projectstore/scripts/install-harness.mjs +336 -93
  22. package/node_modules/projectstore/scripts/kanban.mjs +3 -2
  23. package/node_modules/projectstore/scripts/lib.mjs +69 -8
  24. package/node_modules/projectstore/scripts/mcp.mjs +2 -2
  25. package/node_modules/projectstore/scripts/query.mjs +2 -1
  26. package/node_modules/projectstore/scripts/reconcile.mjs +4 -3
  27. package/node_modules/projectstore/scripts/story-section.mjs +2 -1
  28. package/node_modules/projectstore/scripts/surfaces.mjs +2 -1
  29. package/node_modules/projectstore/scripts/term.mjs +149 -0
  30. package/node_modules/projectstore/scripts/touch-session.mjs +6 -1
  31. package/node_modules/projectstore/scripts/worktree.mjs +12 -7
  32. package/package.json +2 -2
@@ -217,6 +217,8 @@ function ranked(env, dir) {
217
217
  // process.env to impersonate a harness needs the next call to look again.
218
218
  export function resetDetection() {
219
219
  _detected = null;
220
+ _speaking = null;
221
+ _bundling = undefined;
220
222
  }
221
223
 
222
224
  // Which harnesses this PROJECT uses — by directory (install spec, contract
@@ -247,6 +249,93 @@ export function activeHarness(env = process.env, dir = MANIFEST_DIR) {
247
249
  return loadHarness(detectHarnessId(env, dir), dir) || sourceHarness(dir);
248
250
  }
249
251
 
252
+ // ─── Who a message is for (generation spec, contract 18) ───────────────
253
+ //
254
+ // The core prints for ONE harness at a time, and a command it names must be
255
+ // one that harness can run: `/projectstore:doctor` in Claude Code,
256
+ // `$projectstore-doctor` in Codex, where commands arrive as rendered skills.
257
+ // The form is manifest data; these functions only fill it in.
258
+
259
+ // How a harness calls one of our commands (kind "commands") or roles (kind
260
+ // "agents"): the surface's own `invocation` template, or — for a surface the
261
+ // generator renders as skills — its `rendered_name` filled into the skills'
262
+ // template. `*` names the family ("/projectstore:*"). Filled by split/join, so
263
+ // a `$` in a template is never read as a replacement pattern.
264
+ export function invocation(manifest, name, { kind = "commands", args = "" } = {}) {
265
+ // No manifest (a partial tarball): the bare name, never a throw in a hook.
266
+ const surfaces = manifest?.surfaces || {};
267
+ const s = surfaces[kind] || {};
268
+ let template = s.invocation || null;
269
+ if (!template && s.rendered_as === "skill" && s.rendered_name && surfaces.skills?.invocation) {
270
+ template = surfaces.skills.invocation.split("<name>").join(s.rendered_name);
271
+ }
272
+ const form = template ? template.split("<name>").join(name) : name;
273
+ return args ? `${form} ${args}` : form;
274
+ }
275
+
276
+ // The UI words a manifest declares (ui_vocabulary), as patterns. A word glued
277
+ // to a longer name or to a path is not the word: `/plugin-x` is not `/plugin`,
278
+ // and neither is the one in `.claude-plugin/plugin.json`; a full stop after it
279
+ // still ends a sentence. The generator's leak check and the vocabulary lint
280
+ // read these same patterns.
281
+ export function uiWordPatterns(manifest) {
282
+ const esc = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
283
+ return (manifest?.ui_vocabulary || []).map((word) => ({ word, re: new RegExp(`(?<![\\w.-])${esc(word)}(?![\\w-]|\\.\\w)`, "g") }));
284
+ }
285
+
286
+ // The harness whose distribution shell bundles this core, by the shell's
287
+ // package name: a shell installs the core at <shell>/node_modules/projectstore,
288
+ // and its package.json names it as a manifest's install.shell. A hook, a
289
+ // rendered skill's command and an npx run inside that tree all belong to that
290
+ // harness by construction — nothing to export, nothing a model can forget.
291
+ // A source checkout or a host's own cache of the core answers null.
292
+ let _bundling;
293
+ export function bundlingShellHarnessId({ core = REPO_ROOT, dir = MANIFEST_DIR } = {}) {
294
+ const memo = core === REPO_ROOT && dir === MANIFEST_DIR;
295
+ if (memo && _bundling !== undefined) return _bundling;
296
+ let id = null;
297
+ try {
298
+ const real = realOr(core);
299
+ const parent = dirname(real);
300
+ if (parent.split(sep).pop() === "node_modules") {
301
+ const name = JSON.parse(readFileSync(join(dirname(parent), "package.json"), "utf8")).name;
302
+ id = [...loadHarnesses(dir).values()].find((m) => m.install?.shell && m.install.shell === name)?.id ?? null;
303
+ }
304
+ } catch { id = null; }
305
+ if (memo) _bundling = id;
306
+ return id;
307
+ }
308
+
309
+ // The harness a message is FOR: forced (PROJECTSTORE_HARNESS); else the one
310
+ // that identifies itself — a strong signal or its session marker; else the
311
+ // shell that bundles this core; else the shell the environment names
312
+ // (PROJECTSTORE_SHELL, which only the shells set); else the source harness.
313
+ // `--harness <id>` names an install target and plays no part in this.
314
+ let _speaking = null;
315
+ export function speakingHarnessId(env = process.env, dir = MANIFEST_DIR) {
316
+ const memo = env === process.env && dir === MANIFEST_DIR;
317
+ if (memo && _speaking) return _speaking;
318
+ const id = speaker(env, dir);
319
+ if (memo) _speaking = id;
320
+ return id;
321
+ }
322
+
323
+ function speaker(env, dir) {
324
+ const identified = identifiedHarnessId(env, dir);
325
+ if (identified) return identified;
326
+ const bundled = bundlingShellHarnessId({ dir });
327
+ if (bundled) return bundled;
328
+ if (env.PROJECTSTORE_SHELL) {
329
+ const named = [...loadHarnesses(dir).values()].find((m) => m.install?.shell === env.PROJECTSTORE_SHELL);
330
+ if (named) return named.id;
331
+ }
332
+ return sourceHarness(dir)?.id ?? null;
333
+ }
334
+
335
+ export function speakingHarness(env = process.env, dir = MANIFEST_DIR) {
336
+ return loadHarness(speakingHarnessId(env, dir), dir) || sourceHarness(dir);
337
+ }
338
+
250
339
  // ─── Runtime names and paths ───────────────────────────────────────────
251
340
 
252
341
  // The three branded names the active harness uses, so a caller can say which