@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.80
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 +295 -37
- package/SKILL.md +269 -0
- package/dist/chunk-2KW7DVFJ.js +1400 -0
- package/dist/chunk-3EEFL6NJ.js +529 -0
- package/dist/chunk-4A2XB2PC.js +310 -0
- package/dist/chunk-4GLVJLEQ.js +55 -0
- package/dist/chunk-BQUJ4AFJ.js +771 -0
- package/dist/chunk-CUE2LKLR.js +155 -0
- package/dist/chunk-EZXEWFDG.js +210 -0
- package/dist/chunk-FI3LIRCB.js +262 -0
- package/dist/chunk-KKP2NYZ3.js +505 -0
- package/dist/chunk-LEMX3IOG.js +79 -0
- package/dist/chunk-LMRYZL3D.js +209 -0
- package/dist/chunk-OUXH3UEP.js +247 -0
- package/dist/chunk-RO3F6JYV.js +126 -0
- package/dist/chunk-TJTVHDJ5.js +321 -0
- package/dist/chunk-WB7AUZ4C.js +52 -0
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/chunk-YWO7O6QG.js +327 -0
- package/dist/chunk-Z7YPFWAZ.js +917 -0
- package/dist/chunk-ZOSNEO5N.js +77 -0
- package/dist/daemon/server.js +15329 -0
- package/dist/hook-runtime/daemon/server.js +15329 -0
- package/dist/hook-runtime/hooks/post-commit.js +1128 -0
- package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
- package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
- package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
- package/dist/hook-runtime/hooks/pre-tool-use.js +7096 -0
- package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
- package/dist/hook-runtime/hooks/session-end.js +495 -0
- package/dist/hook-runtime/hooks/session-start.js +11313 -0
- package/dist/hook-runtime/index.js +44686 -0
- package/dist/hook-runtime/statusline-main.js +765 -0
- package/dist/hooks/post-commit.js +179 -0
- package/dist/hooks/post-rewrite.js +197 -0
- package/dist/hooks/post-tool-use.js +250 -0
- package/dist/hooks/pre-commit.js +21 -74
- package/dist/hooks/pre-tool-use.js +345 -0
- package/dist/hooks/prim-hook.js +195 -0
- package/dist/hooks/session-end.js +70 -0
- package/dist/hooks/session-start.js +299 -0
- package/dist/index.js +20971 -519
- package/dist/statusline-main.js +765 -0
- package/package.json +35 -13
- package/dist/chunk-3APLWTLB.js +0 -165
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
normalizeEnvelope,
|
|
4
|
+
parseAgent
|
|
5
|
+
} from "../chunk-WB7AUZ4C.js";
|
|
6
|
+
import {
|
|
7
|
+
daemonRequest
|
|
8
|
+
} from "../chunk-CUE2LKLR.js";
|
|
9
|
+
import "../chunk-3EEFL6NJ.js";
|
|
10
|
+
|
|
11
|
+
// src/hooks/session-end.ts
|
|
12
|
+
var STDIN_TIMEOUT_MS = 1e3;
|
|
13
|
+
var DAEMON_TIMEOUT_MS = 250;
|
|
14
|
+
function readStdin() {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
const timer = setTimeout(() => {
|
|
18
|
+
reject(new Error("stdin read timeout"));
|
|
19
|
+
}, STDIN_TIMEOUT_MS);
|
|
20
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
21
|
+
process.stdin.on("end", () => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
24
|
+
});
|
|
25
|
+
process.stdin.on("error", (err) => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
reject(err);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function emit() {
|
|
32
|
+
process.stdout.write("{}\n");
|
|
33
|
+
}
|
|
34
|
+
async function main() {
|
|
35
|
+
const agent = parseAgent(process.argv);
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = await readStdin();
|
|
39
|
+
} catch {
|
|
40
|
+
emit();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let envelope;
|
|
44
|
+
try {
|
|
45
|
+
envelope = normalizeEnvelope(
|
|
46
|
+
JSON.parse(raw),
|
|
47
|
+
agent
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
emit();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (envelope.hook_event_name !== "SessionEnd") {
|
|
54
|
+
emit();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
|
|
58
|
+
emit();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await daemonRequest(
|
|
62
|
+
"session_end",
|
|
63
|
+
{ sessionId: envelope.session_id },
|
|
64
|
+
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
65
|
+
);
|
|
66
|
+
emit();
|
|
67
|
+
}
|
|
68
|
+
main().catch(() => {
|
|
69
|
+
emit();
|
|
70
|
+
});
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
bindRepository,
|
|
4
|
+
ensureEffectivePostCommitHook,
|
|
5
|
+
ensureEffectivePostRewriteHook,
|
|
6
|
+
hasUsableCodexGuidance,
|
|
7
|
+
refreshClaudePlugins,
|
|
8
|
+
resolveRepositoryBinding,
|
|
9
|
+
warmBinCache
|
|
10
|
+
} from "../chunk-2KW7DVFJ.js";
|
|
11
|
+
import {
|
|
12
|
+
buildHookOutput,
|
|
13
|
+
handoffHookOutput
|
|
14
|
+
} from "../chunk-4GLVJLEQ.js";
|
|
15
|
+
import {
|
|
16
|
+
FEEDBACK_DEADLINE_MS,
|
|
17
|
+
acknowledgeDecisionFeedback,
|
|
18
|
+
leaseDecisionFeedback,
|
|
19
|
+
renderFeedback
|
|
20
|
+
} from "../chunk-LMRYZL3D.js";
|
|
21
|
+
import {
|
|
22
|
+
getOrCreateWorkspaceId
|
|
23
|
+
} from "../chunk-EZXEWFDG.js";
|
|
24
|
+
import {
|
|
25
|
+
prepareCodexContext
|
|
26
|
+
} from "../chunk-KKP2NYZ3.js";
|
|
27
|
+
import {
|
|
28
|
+
binFile
|
|
29
|
+
} from "../chunk-4A2XB2PC.js";
|
|
30
|
+
import {
|
|
31
|
+
gitToplevel,
|
|
32
|
+
githubRepositoryFullName,
|
|
33
|
+
isRepoActiveForCapture,
|
|
34
|
+
repoActiveFlag,
|
|
35
|
+
setRepoActive
|
|
36
|
+
} from "../chunk-TJTVHDJ5.js";
|
|
37
|
+
import {
|
|
38
|
+
getSiteUrl,
|
|
39
|
+
isSessionEnded
|
|
40
|
+
} from "../chunk-Z7YPFWAZ.js";
|
|
41
|
+
import {
|
|
42
|
+
normalizeEnvelope,
|
|
43
|
+
parseAgent
|
|
44
|
+
} from "../chunk-WB7AUZ4C.js";
|
|
45
|
+
import {
|
|
46
|
+
daemonRequest
|
|
47
|
+
} from "../chunk-CUE2LKLR.js";
|
|
48
|
+
import "../chunk-3EEFL6NJ.js";
|
|
49
|
+
|
|
50
|
+
// src/daemon/self-heal.ts
|
|
51
|
+
import { spawn } from "child_process";
|
|
52
|
+
function kickDaemonEnsure(options = {}) {
|
|
53
|
+
const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
|
|
54
|
+
if (!primEntry) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const args = [primEntry, "daemon", "ensure"];
|
|
59
|
+
if ((options.platform ?? process.platform) === "darwin") {
|
|
60
|
+
args.push("--latest-bootstrap");
|
|
61
|
+
}
|
|
62
|
+
const child = (options.spawnProcess ?? spawn)(
|
|
63
|
+
options.nodeEntry ?? process.execPath,
|
|
64
|
+
args,
|
|
65
|
+
{ detached: true, stdio: "ignore" }
|
|
66
|
+
);
|
|
67
|
+
child.unref();
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/hooks/reauth-notice.ts
|
|
75
|
+
var REAUTH_NOTICE = "[prim] authentication ended \u2014 run `prim auth login` to resume decision capture";
|
|
76
|
+
function reauthNoticeFields(agent) {
|
|
77
|
+
if (agent === "claude_code") return { systemMessage: REAUTH_NOTICE };
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/hooks/session-start-core.ts
|
|
82
|
+
var DAEMON_TIMEOUT_MS = 250;
|
|
83
|
+
var REPOSITORY_BIND_TIMEOUT_MS = 1e3;
|
|
84
|
+
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";
|
|
85
|
+
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.";
|
|
86
|
+
function primReminder(action, rationaleAction) {
|
|
87
|
+
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.`;
|
|
88
|
+
}
|
|
89
|
+
var PRIM_SKILL_REMINDER = primReminder("invoke the `prim` skill", "invoke the skill");
|
|
90
|
+
var CODEX_PRIM_ACTION = "follow the installed Prim workflow and use the `prim` CLI";
|
|
91
|
+
var CODEX_PRIM_REMINDER = primReminder(CODEX_PRIM_ACTION, CODEX_PRIM_ACTION);
|
|
92
|
+
async function activeProjectRoot(cwd) {
|
|
93
|
+
try {
|
|
94
|
+
const root = gitToplevel(cwd);
|
|
95
|
+
if (!root || !isRepoActiveForCapture(cwd)) return null;
|
|
96
|
+
let shellGateActive = repoActiveFlag(root) === "true";
|
|
97
|
+
if (!shellGateActive) {
|
|
98
|
+
try {
|
|
99
|
+
setRepoActive(root, true);
|
|
100
|
+
shellGateActive = true;
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (shellGateActive) {
|
|
105
|
+
try {
|
|
106
|
+
ensureEffectivePostCommitHook(root);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
ensureEffectivePostRewriteHook(root);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const binding = await bindRepository(root, {
|
|
116
|
+
signal: AbortSignal.timeout(REPOSITORY_BIND_TIMEOUT_MS),
|
|
117
|
+
quietRefresh: true
|
|
118
|
+
});
|
|
119
|
+
return { root, binding };
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
return { root };
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function claudeBindingReminder(binding) {
|
|
128
|
+
if (binding.status === "connected") {
|
|
129
|
+
return `The current GitHub repository is connected to Primitive. In your first response, begin with exactly: "GitHub repository connected to Primitive: ${binding.repositoryFullName}." Then continue the user's request.`;
|
|
130
|
+
}
|
|
131
|
+
return `The current GitHub repository is not connected to Primitive. Before doing task work, ask exactly: "This GitHub repository isn't connected to Primitive yet. Would you like me to start the one-time GitHub connection now?" Wait for the user's answer. If they approve, run \`npx --yes @primitive.ai/prim github connect\`, drive the browser-based GitHub App workflow, report its actual outcome concisely, and resume work. If they decline or the connection does not complete, continue without asking again in this session. Do not use owner/admin escalation or capability-loss warnings.`;
|
|
132
|
+
}
|
|
133
|
+
async function claudeRepositoryBinding(cwd, activeProject) {
|
|
134
|
+
const root = activeProject?.root ?? gitToplevel(cwd);
|
|
135
|
+
if (!root || !githubRepositoryFullName(root)) return void 0;
|
|
136
|
+
if (activeProject) return activeProject.binding;
|
|
137
|
+
try {
|
|
138
|
+
return await resolveRepositoryBinding(root, {
|
|
139
|
+
signal: AbortSignal.timeout(REPOSITORY_BIND_TIMEOUT_MS),
|
|
140
|
+
quietRefresh: true
|
|
141
|
+
});
|
|
142
|
+
} catch {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function processSessionStart(raw, agent, feedbackSignal) {
|
|
147
|
+
let envelope;
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
151
|
+
return { output: buildHookOutput({}) };
|
|
152
|
+
}
|
|
153
|
+
envelope = normalizeEnvelope(parsed, agent);
|
|
154
|
+
} catch {
|
|
155
|
+
return { output: buildHookOutput({}) };
|
|
156
|
+
}
|
|
157
|
+
if (envelope.hook_event_name !== "SessionStart") {
|
|
158
|
+
return { output: buildHookOutput({}) };
|
|
159
|
+
}
|
|
160
|
+
if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
|
|
161
|
+
return { output: buildHookOutput({}) };
|
|
162
|
+
}
|
|
163
|
+
kickDaemonEnsure();
|
|
164
|
+
await daemonRequest(
|
|
165
|
+
"session_start",
|
|
166
|
+
{ sessionId: envelope.session_id, callerEnv: getSiteUrl() },
|
|
167
|
+
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
168
|
+
);
|
|
169
|
+
const cwd = envelope.cwd ?? process.cwd();
|
|
170
|
+
let projectRoot = null;
|
|
171
|
+
let active = false;
|
|
172
|
+
let skillState = { installed: 0, refreshed: 0 };
|
|
173
|
+
let claudeBinding;
|
|
174
|
+
if (agent === "claude_code") {
|
|
175
|
+
const activeProject = await activeProjectRoot(cwd);
|
|
176
|
+
projectRoot = activeProject?.root ?? null;
|
|
177
|
+
active = projectRoot !== null;
|
|
178
|
+
claudeBinding = await claudeRepositoryBinding(cwd, activeProject);
|
|
179
|
+
try {
|
|
180
|
+
skillState = await refreshClaudePlugins(
|
|
181
|
+
cwd,
|
|
182
|
+
active && projectRoot !== null ? { includeProject: true, projectRoot } : { includeProject: false }
|
|
183
|
+
);
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (isSessionEnded() && agent !== "codex") {
|
|
188
|
+
const notice = reauthNoticeFields(agent);
|
|
189
|
+
if (notice) {
|
|
190
|
+
return {
|
|
191
|
+
output: buildHookOutput({ ...notice, reloadSkills: skillState.refreshed > 0 })
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (agent === "codex") {
|
|
196
|
+
let context;
|
|
197
|
+
try {
|
|
198
|
+
context = await prepareCodexContext({
|
|
199
|
+
cwd,
|
|
200
|
+
sessionId: envelope.session_id,
|
|
201
|
+
startup: true,
|
|
202
|
+
includeDigest: false
|
|
203
|
+
});
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
projectRoot = (await activeProjectRoot(cwd))?.root ?? null;
|
|
207
|
+
active = projectRoot !== null;
|
|
208
|
+
let proactive = false;
|
|
209
|
+
try {
|
|
210
|
+
proactive = projectRoot !== null && active && hasUsableCodexGuidance(projectRoot);
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
const additionalContext = [proactive ? CODEX_PRIM_REMINDER : void 0, context?.context].filter((value) => value !== void 0).join("\n\n");
|
|
214
|
+
return {
|
|
215
|
+
output: buildHookOutput({ additionalContext }),
|
|
216
|
+
acknowledge: async () => {
|
|
217
|
+
await context?.acknowledge(true);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (agent === "claude_code") {
|
|
222
|
+
const additionalContext = [
|
|
223
|
+
claudeBinding ? claudeBindingReminder(claudeBinding) : void 0,
|
|
224
|
+
active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0
|
|
225
|
+
].filter((value) => value !== void 0).join("\n\n");
|
|
226
|
+
const reloadSkills = skillState.refreshed > 0;
|
|
227
|
+
if (active) {
|
|
228
|
+
const identity = getOrCreateWorkspaceId(cwd);
|
|
229
|
+
if (identity.status === "ready") {
|
|
230
|
+
const signal = feedbackSignal ?? AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
|
|
231
|
+
const lease = await leaseDecisionFeedback({
|
|
232
|
+
workspaceId: identity.workspaceId,
|
|
233
|
+
currentSessionId: envelope.session_id,
|
|
234
|
+
signal
|
|
235
|
+
});
|
|
236
|
+
const rendered = lease ? renderFeedback(lease) : void 0;
|
|
237
|
+
return {
|
|
238
|
+
output: buildHookOutput({
|
|
239
|
+
systemMessage: rendered?.systemMessage,
|
|
240
|
+
additionalContext,
|
|
241
|
+
reloadSkills
|
|
242
|
+
}),
|
|
243
|
+
acknowledge: rendered ? async () => {
|
|
244
|
+
await acknowledgeDecisionFeedback({
|
|
245
|
+
protocolVersion: rendered.protocolVersion,
|
|
246
|
+
workspaceId: identity.workspaceId,
|
|
247
|
+
deliveries: rendered.deliveries,
|
|
248
|
+
signal
|
|
249
|
+
});
|
|
250
|
+
} : void 0
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { output: buildHookOutput({ additionalContext, reloadSkills }) };
|
|
255
|
+
}
|
|
256
|
+
await activeProjectRoot(cwd);
|
|
257
|
+
return { output: buildHookOutput({}) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/hooks/session-start.ts
|
|
261
|
+
var STDIN_TIMEOUT_MS = 1e3;
|
|
262
|
+
var outputAttempted = false;
|
|
263
|
+
function emitOutput(output, acknowledge) {
|
|
264
|
+
outputAttempted = true;
|
|
265
|
+
return handoffHookOutput(output, acknowledge);
|
|
266
|
+
}
|
|
267
|
+
function readStdin() {
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
const chunks = [];
|
|
270
|
+
const timer = setTimeout(() => {
|
|
271
|
+
reject(new Error("stdin read timeout"));
|
|
272
|
+
}, STDIN_TIMEOUT_MS);
|
|
273
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
274
|
+
process.stdin.on("end", () => {
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
277
|
+
});
|
|
278
|
+
process.stdin.on("error", (err) => {
|
|
279
|
+
clearTimeout(timer);
|
|
280
|
+
reject(err);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async function main() {
|
|
285
|
+
warmBinCache();
|
|
286
|
+
const agent = parseAgent(process.argv);
|
|
287
|
+
let raw;
|
|
288
|
+
try {
|
|
289
|
+
raw = await readStdin();
|
|
290
|
+
} catch {
|
|
291
|
+
await emitOutput(buildHookOutput({}));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const result = await processSessionStart(raw, agent);
|
|
295
|
+
await emitOutput(result.output, result.acknowledge);
|
|
296
|
+
}
|
|
297
|
+
void main().catch(async () => {
|
|
298
|
+
if (!outputAttempted) await emitOutput(buildHookOutput({}));
|
|
299
|
+
});
|