@primitive.ai/prim 0.1.0-alpha.32 → 0.1.0-alpha.34

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 CHANGED
@@ -127,7 +127,7 @@ prim daemon start # start (stop / restart / status)
127
127
  Read and respond to the decision graph.
128
128
 
129
129
  ```bash
130
- prim decisions recent # Recent decisions feed
130
+ prim decisions recent # Recent decisions feed (--author <name> for one teammate's)
131
131
  prim decisions show <id> # Drill into one decision
132
132
  prim decisions cascade <id> # Blast radius of a decision
133
133
  prim decisions check --files <…> # Active decisions referencing files (warn-only)
package/SKILL.md CHANGED
@@ -44,7 +44,7 @@ The gate fail-opens on its *own* infrastructure errors (no daemon, network blip,
44
44
  ## Read the graph before large or load-bearing edits
45
45
 
46
46
  - `npx --yes @primitive.ai/prim decisions check --files "src/a.ts,src/b.ts"` -- which active decisions reference the files you're about to touch (comma-separated paths, one `--files` value). Run it before a big change.
47
- - `npx --yes @primitive.ai/prim decisions recent` -- the team's recent decisions, each row badged by author and agent (`Your Claude Code` / `Your Codex` / `Your Hermes`); `--limit <n>` and `--since <dur>` narrow it.
47
+ - `npx --yes @primitive.ai/prim decisions recent` -- the team's recent decisions, each row badged by author and agent (`Your Claude Code` / `Your Codex` / `Your Hermes`); `--limit <n>` and `--since <dur>` narrow it. `--author "<name>"` filters to one teammate (feed name, `"First Last"`, last name, username, email, or email local-part) -- the way to answer "what has X decided?"; an unknown or ambiguous name comes back as `unavailable` with the reason, and `authorHasDecisions` in the JSON distinguishes "no feed-visible decisions" (false) from "has decisions, none in this window" (true).
48
48
  - `npx --yes @primitive.ai/prim decisions show <idOrShortId>` and `npx --yes @primitive.ai/prim decisions cascade <idOrShortId>` -- full detail, and the downstream blast radius a change would disturb.
49
49
 
50
50
  ## Reconcile and the verdict footer
@@ -130,6 +130,7 @@ Examples:
130
130
 
131
131
  - `npx --yes @primitive.ai/prim auth status --json | jq -r .authenticated` — boolean; the exit code remains the authoritative signal
132
132
  - `npx --yes @primitive.ai/prim decisions recent | jq -r '.decisions[].shortId'` — list recent decision short ids (STDOUT is already JSON)
133
+ - `npx --yes @primitive.ai/prim decisions recent --author "Maya" | jq -r 'if .unavailable then "UNAVAILABLE: \(.unavailable)" else .decisions[].intent end'` — one teammate's latest decisions (check `.unavailable` first; empty output alone is not "no decisions")
133
134
  - `npx --yes @primitive.ai/prim decisions show <id> | jq .` — full decision detail
134
135
 
135
136
  ## Pitfalls
package/dist/index.js CHANGED
@@ -342,6 +342,9 @@ function hookShimCommand(bin, args = "") {
342
342
  const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
343
343
  return `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
344
344
  }
345
+ function detachedHookShimCommand(bin, args = "") {
346
+ return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${hookShimCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
347
+ }
345
348
  function commandMatchesBin(command, bin) {
346
349
  if (!command) {
347
350
  return false;
@@ -395,12 +398,17 @@ var CAPTURE_EVENTS = [
395
398
  function makeRegistration(event, matcher, bin, args = "") {
396
399
  return { event, matcher, bin, command: hookShimCommand(bin, args) };
397
400
  }
401
+ function makeDetachedRegistration(event, matcher, bin, args = "") {
402
+ return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
403
+ }
398
404
  var REGISTRATIONS = [
399
- ...CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN)),
405
+ ...CAPTURE_EVENTS.map(
406
+ (event) => event === "SessionEnd" ? makeDetachedRegistration(event, "*", CAPTURE_BIN) : makeRegistration(event, "*", CAPTURE_BIN)
407
+ ),
400
408
  makeRegistration("PreToolUse", "Edit|Write|MultiEdit", GATE_BIN),
401
409
  makeRegistration("PostToolUse", "Edit|Write|MultiEdit", POST_TOOL_USE_BIN),
402
410
  makeRegistration("SessionStart", "*", SESSION_START_BIN),
403
- makeRegistration("SessionEnd", "*", SESSION_END_BIN)
411
+ makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
404
412
  ];
405
413
  var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
406
414
  var LEGACY_PRIM_PERMISSION_RULES = [
@@ -445,10 +453,9 @@ function stripCommand(list, bin) {
445
453
  return out;
446
454
  }
447
455
  function ensureRegistration(list, reg, force) {
448
- const hasCanonical = list.some(
449
- (e) => e.matcher === reg.matcher && e.hooks?.length === 1 && e.hooks[0].command === reg.command
450
- );
451
- if (hasCanonical && !force) {
456
+ const isCanonical = (e) => e.matcher === reg.matcher && e.hooks?.length === 1 && e.hooks[0].command === reg.command;
457
+ const hasStray = (e) => !isCanonical(e) && (e.hooks ?? []).some((h) => commandMatchesBin(h.command, reg.bin));
458
+ if (list.some(isCanonical) && !list.some(hasStray) && !force) {
452
459
  return list;
453
460
  }
454
461
  return [...stripCommand(list, reg.bin), canonicalEntry(reg)];
@@ -1325,6 +1332,12 @@ function formatCascadeJson(result) {
1325
1332
  var RECENT_TIMEOUT_MS = 1e4;
1326
1333
  var defaultDeps2 = { getClient };
1327
1334
  async function fetchRecent(args, deps = defaultDeps2) {
1335
+ if (args.author !== void 0 && args.author.trim() === "") {
1336
+ return {
1337
+ decisions: [],
1338
+ unavailable: "--author must be a non-empty name"
1339
+ };
1340
+ }
1328
1341
  const params = new URLSearchParams();
1329
1342
  if (args.limit !== void 0) {
1330
1343
  params.set("limit", String(args.limit));
@@ -1332,6 +1345,9 @@ async function fetchRecent(args, deps = defaultDeps2) {
1332
1345
  if (args.since !== void 0) {
1333
1346
  params.set("since", args.since);
1334
1347
  }
1348
+ if (args.author !== void 0) {
1349
+ params.set("author", args.author);
1350
+ }
1335
1351
  try {
1336
1352
  const client = deps.getClient();
1337
1353
  const res = await daemonOrDirectGet(
@@ -1340,10 +1356,22 @@ async function fetchRecent(args, deps = defaultDeps2) {
1340
1356
  client,
1341
1357
  RECENT_TIMEOUT_MS
1342
1358
  );
1359
+ if (args.author !== void 0 && res.author === void 0 && res.unavailable === void 0) {
1360
+ return {
1361
+ decisions: [],
1362
+ unavailable: "--author requires a newer Primitive backend (no author echo in response); retry without --author for the team-wide feed"
1363
+ };
1364
+ }
1343
1365
  const result = { decisions: res.decisions };
1344
1366
  if (res.viewerHasDecisions !== void 0) {
1345
1367
  result.viewerHasDecisions = res.viewerHasDecisions;
1346
1368
  }
1369
+ if (res.author !== void 0) {
1370
+ result.author = res.author;
1371
+ }
1372
+ if (res.authorHasDecisions !== void 0) {
1373
+ result.authorHasDecisions = res.authorHasDecisions;
1374
+ }
1347
1375
  if (res.unavailable !== void 0) {
1348
1376
  result.unavailable = res.unavailable;
1349
1377
  }
@@ -1400,10 +1428,20 @@ function formatRecentHuman(result) {
1400
1428
  if (result.unavailable !== void 0) {
1401
1429
  return `[prim] recent \xB7 feed not verified \u2014 ${result.unavailable}`;
1402
1430
  }
1431
+ if (result.author !== void 0 && result.decisions.length === 0) {
1432
+ if (result.authorHasDecisions === true) {
1433
+ return `[prim] recent \xB7 ${result.author.name} \xB7 0 decisions in this window (older decisions exist \u2014 widen --since or raise --limit)`;
1434
+ }
1435
+ if (result.authorHasDecisions === false) {
1436
+ return `[prim] recent \xB7 ${result.author.name} \xB7 no feed-visible decisions yet (if unexpected, check prim setup/doctor on their machine and the repo they work in)`;
1437
+ }
1438
+ return `[prim] recent \xB7 ${result.author.name} \xB7 0 decisions`;
1439
+ }
1440
+ const label = result.author === void 0 ? "recent" : `recent \xB7 ${result.author.name}`;
1403
1441
  if (result.decisions.length === 0) {
1404
1442
  return "[prim] recent \xB7 0 decisions";
1405
1443
  }
1406
- const lines = [`[prim] recent \xB7 ${String(result.decisions.length)} decision(s)`];
1444
+ const lines = [`[prim] ${label} \xB7 ${String(result.decisions.length)} decision(s)`];
1407
1445
  for (const row of result.decisions) {
1408
1446
  lines.push(formatRecentRow(row));
1409
1447
  }
@@ -1764,10 +1802,14 @@ function registerDecisionsCommands(program2) {
1764
1802
  decisions.command("recent").description("Show the team-wide chronological decision feed").option("--limit <n>", "Maximum number of rows to return (default 10)").option(
1765
1803
  "--since <duration>",
1766
1804
  "Lookback window \u2014 accepts `Nm`, `Nh`, `Nd` (minutes / hours / days) or absolute epoch ms"
1805
+ ).option(
1806
+ "--author <name>",
1807
+ `Filter to one teammate's decisions \u2014 feed name, "First Last", last name, username, email, or email local-part`
1767
1808
  ).action(async (opts) => {
1768
1809
  const result = await fetchRecent({
1769
1810
  limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
1770
- since: opts.since
1811
+ since: opts.since,
1812
+ author: opts.author
1771
1813
  });
1772
1814
  console.error(formatRecentHuman(result));
1773
1815
  console.log(formatRecentJson(result));
@@ -2882,7 +2924,7 @@ function planSetupSteps(opts) {
2882
2924
  });
2883
2925
  }
2884
2926
  steps.push({ key: "hooks", label: "Git hooks", args: ["hooks", "install"], required: true });
2885
- const skillArgs = opts.agent === "hermes" ? ["skill", "install", "--target", ".hermes.md"] : ["skill", "install"];
2927
+ const skillArgs = ["skill", "install", "--agent", opts.agent];
2886
2928
  steps.push({ key: "skill", label: "Agent skill", args: skillArgs, required: true });
2887
2929
  return steps;
2888
2930
  }
@@ -2993,6 +3035,11 @@ var TARGET_CANDIDATES = [
2993
3035
  ".github/instructions/primitive.md"
2994
3036
  ];
2995
3037
  var DEFAULT_TARGET = "CLAUDE.md";
3038
+ var AGENT_TARGET = {
3039
+ claude: "CLAUDE.md",
3040
+ codex: "AGENTS.md",
3041
+ hermes: ".hermes.md"
3042
+ };
2996
3043
  function loadSkill() {
2997
3044
  let dir = __dirname;
2998
3045
  while (dir !== dirname5(dir)) {
@@ -3040,8 +3087,14 @@ function atomicWrite2(target, content) {
3040
3087
  }
3041
3088
  renameSync4(tmp, target);
3042
3089
  }
3043
- function resolveTarget(cwd, override) {
3044
- if (override) return resolve2(cwd, override);
3090
+ function resolveTarget(cwd, opts) {
3091
+ if (opts.target) return resolve2(cwd, opts.target);
3092
+ if (opts.agent) {
3093
+ const mapped = AGENT_TARGET[opts.agent];
3094
+ if (typeof mapped === "string") return resolve2(cwd, mapped);
3095
+ console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
3096
+ return null;
3097
+ }
3045
3098
  const matches = detectTargets(cwd);
3046
3099
  if (matches.length === 0) return resolve2(cwd, DEFAULT_TARGET);
3047
3100
  if (matches.length === 1) return resolve2(cwd, matches[0]);
@@ -3050,7 +3103,7 @@ function resolveTarget(cwd, override) {
3050
3103
  return null;
3051
3104
  }
3052
3105
  function runInstall(cwd, opts) {
3053
- const target = resolveTarget(cwd, opts.target);
3106
+ const target = resolveTarget(cwd, opts);
3054
3107
  if (target === null) return 1;
3055
3108
  const existing = existsSync10(target) ? readFileSync8(target, "utf-8") : "";
3056
3109
  const eol = existing ? detectNewline(existing) : "\n";
@@ -3069,7 +3122,7 @@ function runInstall(cwd, opts) {
3069
3122
  return 0;
3070
3123
  }
3071
3124
  function runUninstall(cwd, opts) {
3072
- const target = resolveTarget(cwd, opts.target);
3125
+ const target = resolveTarget(cwd, opts);
3073
3126
  if (target === null) return 1;
3074
3127
  if (!existsSync10(target)) {
3075
3128
  console.log(`Skill block not present at ${target}`);
@@ -3086,7 +3139,7 @@ function runUninstall(cwd, opts) {
3086
3139
  return 0;
3087
3140
  }
3088
3141
  function runStatus(cwd, opts) {
3089
- const target = resolveTarget(cwd, opts.target);
3142
+ const target = resolveTarget(cwd, opts);
3090
3143
  if (target === null) return 1;
3091
3144
  const fileExists = existsSync10(target);
3092
3145
  let installed = false;
@@ -3111,7 +3164,7 @@ function runStatus(cwd, opts) {
3111
3164
  }
3112
3165
  function registerSkillCommands(program2) {
3113
3166
  const skill = program2.command("skill").description("Manage the prim skill in your project rules file");
3114
- skill.command("install").description("Install the prim skill block into your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--dry-run", "Print a unified diff without writing").action((opts) => {
3167
+ skill.command("install").description("Install the prim skill block into your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--dry-run", "Print a unified diff without writing").action((opts) => {
3115
3168
  try {
3116
3169
  process.exit(runInstall(process.cwd(), opts));
3117
3170
  } catch (err) {
@@ -3119,10 +3172,10 @@ function registerSkillCommands(program2) {
3119
3172
  process.exit(2);
3120
3173
  }
3121
3174
  });
3122
- skill.command("uninstall").description("Remove the prim skill block from your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").action((opts) => {
3175
+ skill.command("uninstall").description("Remove the prim skill block from your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").action((opts) => {
3123
3176
  process.exit(runUninstall(process.cwd(), opts));
3124
3177
  });
3125
- skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--json", "Output as JSON").action((opts) => {
3178
+ skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--json", "Output as JSON").action((opts) => {
3126
3179
  process.exit(runStatus(process.cwd(), opts));
3127
3180
  });
3128
3181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.32",
3
+ "version": "0.1.0-alpha.34",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",