@primitive.ai/prim 0.1.0-alpha.51 → 0.1.0-alpha.53
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/SKILL.md +7 -5
- package/dist/{chunk-UJEQX52M.js → chunk-ROL7SDXX.js} +74 -0
- package/dist/hooks/session-start.js +18 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prim
|
|
3
|
-
description: Use the prim CLI for Primitive’s decision graph. MUST INVOKE before finishing any coding, planning, specification, or review task where the user or agent chose between plausible approaches or established or changed a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction—even when Primitive was not mentioned. Also invoke for Primitive setup, reading decisions, conflict gates, reconcile, rationale confirmations, linking, and team presence. SKIP routine implementation that merely follows an existing decision
|
|
3
|
+
description: Use the prim CLI for Primitive’s decision graph. MUST INVOKE before finishing any coding, planning, specification, or review task where the user or agent chose between plausible approaches or established or changed a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction—even when Primitive was not mentioned. Also invoke for Primitive setup, reading decisions, conflict gates, reconcile, rationale confirmations, linking, and team presence. SKIP — do not invoke even to check — when the task shows no fork that should guide future work, for example temporary tactics (a change that is explicitly temporary or to-be-reverted); routine implementation that merely follows an existing decision or a convention already visible in the code or repo instructions; or the word “decision” merely naming code, data, or UI. Such signals need no graph lookup to rule out.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Working with the prim CLI
|
|
@@ -80,7 +80,7 @@ Confirmations are author-targeted and rare by design; answering keeps the graph'
|
|
|
80
80
|
|
|
81
81
|
Capture is automatic for low-level choices made while coding in an active repo. Use the deliberate CLI path for higher-order decisions that emerge in conversation: goals, priorities, principles, invariants, constraints, defaults, commitments, durable tradeoffs, and exceptions.
|
|
82
82
|
|
|
83
|
-
A decision worth deliberately recording is a genuine **fork in the road**: the user or agent encountered multiple plausible paths, selected one, and that selection should inform future work. Record the chosen behavior, direction, constraint, or tradeoff—not routine implementation needed to finish the task or follow an existing convention. A teammate working elsewhere should benefit from knowing it.
|
|
83
|
+
A decision worth deliberately recording is a genuine **fork in the road**: the user or agent encountered multiple plausible paths, selected one, and that selection should inform future work. Record the chosen behavior, direction, constraint, or tradeoff—not routine implementation needed to finish the task or follow an existing convention. Small design choices made in-flight while implementing—such as edge-case handling, parameter defaults, retry or timeout values, log levels, naming, or the shape of an internal data structure—are implementation details even when you chose between alternatives: never deliberately record them or spend a rationale question on them; passive capture already covers coding activity. A teammate working elsewhere should benefit from knowing it.
|
|
84
84
|
|
|
85
85
|
### Ground the rationale in real sources
|
|
86
86
|
|
|
@@ -91,8 +91,10 @@ Record only rationale supported by those sources. Do not mistake the implementat
|
|
|
91
91
|
For proactively identified decisions in an active repo, let confidence in the rationale determine the interaction:
|
|
92
92
|
|
|
93
93
|
- **Clear and well-supported** — record the decision and rationale silently at the natural task boundary.
|
|
94
|
-
- **Plausible but uncertain** — at the task boundary, state the proposed rationale and ask for lightweight confirmation: “I understand the reason for choosing X to be Y. Is that right?” Record
|
|
95
|
-
- **No supported rationale** — at the task boundary, ask one focused question: “What made you choose X over the other path?” Record
|
|
94
|
+
- **Plausible but uncertain** — at the task boundary, state the proposed rationale and ask for lightweight confirmation: “I understand the reason for choosing X to be Y. Is that right?” Record in the same turn with the rationale omitted; add it once confirmed or corrected.
|
|
95
|
+
- **No supported rationale** — at the task boundary, ask one focused question: “What made you choose X over the other path?” Record in the same turn with the rationale omitted; add it when the answer arrives.
|
|
96
|
+
|
|
97
|
+
In both cases the question is a courtesy, not a gate: never make the record wait on an answer, and never fabricate a rationale to fill the gap. An unanswered question with no record is the only wrong outcome.
|
|
96
98
|
|
|
97
99
|
These questions share the interruption budget below; never ask separate questions for the decision and its rationale. If both are uncertain, combine them into one concise prompt. An explicit request to “add this decision to Primitive” still records immediately with the information supplied—do not delay it to demand rationale.
|
|
98
100
|
|
|
@@ -124,7 +126,7 @@ When the user asks to record a decision—for example, “add this decision to P
|
|
|
124
126
|
|
|
125
127
|
### Clear decisions: record without interrupting
|
|
126
128
|
|
|
127
|
-
When the user clearly makes a durable fork-in-the-road decision without explicitly asking to record it, record it at the next natural task boundary without asking a redundant confirmation question **when passive capture is active**. In an inactive repo, present it and obtain the per-create approval above. Preserve the user's meaning and stated rationale; do not strengthen, broaden, or embellish it. Prefer the governing position over the implementation activity that revealed it.
|
|
129
|
+
When the user clearly makes a durable fork-in-the-road decision without explicitly asking to record it, record it at the next natural task boundary without asking a redundant confirmation question **when passive capture is active**. An explicit ratification of a direction—“agreed,” “ship it that way,” or any equivalent affirmation—is such a decision even when the ratified option was your own proposal: the ratification is the approval, so record it and do not ask whether to record. That rule is general: in an active repository, never ask permission to record — the only question the budget permits is the single rationale question, and only when no rationale was stated. In an inactive repo, present it and obtain the per-create approval above. Preserve the user's meaning and stated rationale; do not strengthen, broaden, or embellish it. Prefer the governing position over the implementation activity that revealed it.
|
|
128
130
|
|
|
129
131
|
Examples that qualify:
|
|
130
132
|
|
|
@@ -8,11 +8,15 @@ import {
|
|
|
8
8
|
// src/commands/skill.ts
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
10
|
import {
|
|
11
|
+
constants as constants2,
|
|
11
12
|
closeSync as closeSync2,
|
|
12
13
|
existsSync as existsSync2,
|
|
14
|
+
fstatSync as fstatSync2,
|
|
13
15
|
fsyncSync,
|
|
16
|
+
lstatSync as lstatSync2,
|
|
14
17
|
openSync as openSync2,
|
|
15
18
|
readFileSync as readFileSync2,
|
|
19
|
+
readSync as readSync2,
|
|
16
20
|
renameSync,
|
|
17
21
|
rmSync as rmSync2,
|
|
18
22
|
writeFileSync
|
|
@@ -21,6 +25,7 @@ import { homedir as homedir2 } from "os";
|
|
|
21
25
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
22
26
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23
27
|
import { createPatch } from "diff";
|
|
28
|
+
import { parse as parseYaml2 } from "yaml";
|
|
24
29
|
|
|
25
30
|
// src/output.ts
|
|
26
31
|
function printJson(data) {
|
|
@@ -322,6 +327,74 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
322
327
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
323
328
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
324
329
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
330
|
+
var CODEX_GUIDANCE_LIMIT_BYTES = 1024 * 1024;
|
|
331
|
+
var CODEX_DEFAULT_PROJECT_GUIDANCE_BYTES = 32 * 1024;
|
|
332
|
+
var SKILL_BEGIN_BUFFER = Buffer.from(SKILL_BEGIN);
|
|
333
|
+
var SKILL_END_BUFFER = Buffer.from(SKILL_END);
|
|
334
|
+
function readCodexGuidance(path) {
|
|
335
|
+
try {
|
|
336
|
+
const entry = lstatSync2(path);
|
|
337
|
+
if (entry.isSymbolicLink() || !entry.isFile()) return null;
|
|
338
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
339
|
+
try {
|
|
340
|
+
const opened = fstatSync2(fd);
|
|
341
|
+
if (!opened.isFile() || opened.size > CODEX_GUIDANCE_LIMIT_BYTES) return null;
|
|
342
|
+
const bytes = Buffer.allocUnsafe(CODEX_GUIDANCE_LIMIT_BYTES + 1);
|
|
343
|
+
let length = 0;
|
|
344
|
+
while (length < bytes.length) {
|
|
345
|
+
const read = readSync2(fd, bytes, length, bytes.length - length, length);
|
|
346
|
+
if (read === 0) break;
|
|
347
|
+
length += read;
|
|
348
|
+
}
|
|
349
|
+
return length > CODEX_GUIDANCE_LIMIT_BYTES ? null : bytes.subarray(0, length);
|
|
350
|
+
} finally {
|
|
351
|
+
closeSync2(fd);
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const code = error.code;
|
|
355
|
+
return code === "ENOENT" || code === "ENOTDIR" ? void 0 : null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function effectiveCodexGuidance(scope) {
|
|
359
|
+
const override = readCodexGuidance(join2(scope, "AGENTS.override.md"));
|
|
360
|
+
if (override === null) return null;
|
|
361
|
+
if (override !== void 0 && override.toString("utf8").trim().length > 0) return override;
|
|
362
|
+
const base = readCodexGuidance(join2(scope, "AGENTS.md"));
|
|
363
|
+
return base ?? null;
|
|
364
|
+
}
|
|
365
|
+
function hasUsablePrimBlock(content, visibleBytes) {
|
|
366
|
+
const begin = content.indexOf(SKILL_BEGIN_BUFFER);
|
|
367
|
+
const end = content.indexOf(SKILL_END_BUFFER);
|
|
368
|
+
if (begin === -1 || end <= begin + SKILL_BEGIN_BUFFER.length || end + SKILL_END_BUFFER.length > visibleBytes || content.indexOf(SKILL_BEGIN_BUFFER, begin + SKILL_BEGIN_BUFFER.length) !== -1 || content.indexOf(SKILL_END_BUFFER, end + SKILL_END_BUFFER.length) !== -1) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const block = content.subarray(begin + SKILL_BEGIN_BUFFER.length, end).toString("utf8").trim();
|
|
372
|
+
const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)([\s\S]*)$/u.exec(block);
|
|
373
|
+
if (!match || match[2].trim().length === 0) return false;
|
|
374
|
+
try {
|
|
375
|
+
const metadata = parseYaml2(match[1]);
|
|
376
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) return false;
|
|
377
|
+
const fields = metadata;
|
|
378
|
+
return fields.name === "prim" && typeof fields.description === "string" && fields.description.trim().length > 0;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function hasUsableCodexGuidance(projectRoot) {
|
|
384
|
+
try {
|
|
385
|
+
const codexHome = process.env.CODEX_HOME?.trim() ? resolve2(process.env.CODEX_HOME) : join2(homedir2(), ".codex");
|
|
386
|
+
const scopes = [
|
|
387
|
+
{ path: codexHome, visibleBytes: CODEX_GUIDANCE_LIMIT_BYTES },
|
|
388
|
+
{ path: projectRoot, visibleBytes: CODEX_DEFAULT_PROJECT_GUIDANCE_BYTES }
|
|
389
|
+
];
|
|
390
|
+
return scopes.some(({ path, visibleBytes }) => {
|
|
391
|
+
const guidance = effectiveCodexGuidance(path);
|
|
392
|
+
return guidance !== null && hasUsablePrimBlock(guidance, visibleBytes);
|
|
393
|
+
});
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
325
398
|
var TARGET_CANDIDATES = [
|
|
326
399
|
"CLAUDE.md",
|
|
327
400
|
"AGENTS.md",
|
|
@@ -516,5 +589,6 @@ function registerSkillCommands(program) {
|
|
|
516
589
|
export {
|
|
517
590
|
printJson,
|
|
518
591
|
refreshClaudePlugins,
|
|
592
|
+
hasUsableCodexGuidance,
|
|
519
593
|
registerSkillCommands
|
|
520
594
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
hasUsableCodexGuidance,
|
|
3
4
|
refreshClaudePlugins
|
|
4
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-ROL7SDXX.js";
|
|
5
6
|
import {
|
|
6
7
|
buildHookOutput,
|
|
7
8
|
handoffHookOutput
|
|
@@ -65,7 +66,14 @@ function reauthNoticeFields(agent) {
|
|
|
65
66
|
|
|
66
67
|
// src/hooks/session-start-core.ts
|
|
67
68
|
var DAEMON_TIMEOUT_MS = 250;
|
|
68
|
-
var
|
|
69
|
+
var PRIM_TRIGGER = "When this task chooses between plausible approaches or establishes or changes a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction";
|
|
70
|
+
var PRIM_EXCLUSIONS = "Never invoke `prim` for routine implementation that merely follows an existing decision made before this task or for temporary tactics; they never qualify, including for evaluation.";
|
|
71
|
+
function primReminder(action, rationaleAction) {
|
|
72
|
+
return `Primitive is active in this repository. ${PRIM_TRIGGER}, ${action} before finishing. ${PRIM_EXCLUSIONS} When a direct request replaces one lasting default with another but supplies no rationale, complete the work, ${rationaleAction}, and ask one concise rationale question at the task boundary, even if the user requested only implementation or recording fails.`;
|
|
73
|
+
}
|
|
74
|
+
var PRIM_SKILL_REMINDER = primReminder("invoke the `prim` skill", "invoke the skill");
|
|
75
|
+
var CODEX_PRIM_ACTION = "follow the installed Prim workflow and use the `prim` CLI";
|
|
76
|
+
var CODEX_PRIM_REMINDER = primReminder(CODEX_PRIM_ACTION, CODEX_PRIM_ACTION);
|
|
69
77
|
async function processSessionStart(raw, agent, feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS)) {
|
|
70
78
|
let envelope;
|
|
71
79
|
try {
|
|
@@ -119,13 +127,15 @@ async function processSessionStart(raw, agent, feedbackSignal = AbortSignal.time
|
|
|
119
127
|
{ callerEnv: getSiteUrl() },
|
|
120
128
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
121
129
|
);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
const presence = snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number" ? `[prim] team: ${snapshot.onlineCount} online` : void 0;
|
|
131
|
+
let proactive = false;
|
|
132
|
+
try {
|
|
133
|
+
const projectRoot = gitToplevel(cwd);
|
|
134
|
+
proactive = projectRoot !== null && isRepoActiveForCapture(cwd) && hasUsableCodexGuidance(projectRoot);
|
|
135
|
+
} catch {
|
|
128
136
|
}
|
|
137
|
+
const additionalContext = [proactive ? CODEX_PRIM_REMINDER : void 0, presence].filter((value) => value !== void 0).join("\n\n");
|
|
138
|
+
return { output: buildHookOutput({ additionalContext }) };
|
|
129
139
|
}
|
|
130
140
|
if (agent === "claude_code") {
|
|
131
141
|
const additionalContext = active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED