@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 +1 -1
- package/SKILL.md +2 -1
- package/dist/index.js +70 -17
- package/package.json +1 -1
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(
|
|
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
|
-
|
|
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
|
|
449
|
-
|
|
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]
|
|
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 =
|
|
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,
|
|
3044
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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