@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.81

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 (45) hide show
  1. package/README.md +295 -37
  2. package/SKILL.md +269 -0
  3. package/dist/chunk-3EEFL6NJ.js +529 -0
  4. package/dist/chunk-4CZTNXIS.js +505 -0
  5. package/dist/chunk-4GLVJLEQ.js +55 -0
  6. package/dist/chunk-BQUJ4AFJ.js +771 -0
  7. package/dist/chunk-CUE2LKLR.js +155 -0
  8. package/dist/chunk-EZXEWFDG.js +210 -0
  9. package/dist/chunk-FI3LIRCB.js +262 -0
  10. package/dist/chunk-HWSS4BV5.js +1400 -0
  11. package/dist/chunk-LEMX3IOG.js +79 -0
  12. package/dist/chunk-LMRYZL3D.js +209 -0
  13. package/dist/chunk-OUXH3UEP.js +247 -0
  14. package/dist/chunk-PMRDE2DA.js +310 -0
  15. package/dist/chunk-RO3F6JYV.js +126 -0
  16. package/dist/chunk-TJTVHDJ5.js +321 -0
  17. package/dist/chunk-WB7AUZ4C.js +52 -0
  18. package/dist/chunk-XGADQPCI.js +26 -0
  19. package/dist/chunk-YWO7O6QG.js +327 -0
  20. package/dist/chunk-Z7YPFWAZ.js +917 -0
  21. package/dist/chunk-ZOSNEO5N.js +77 -0
  22. package/dist/daemon/server.js +15329 -0
  23. package/dist/hook-runtime/daemon/server.js +15329 -0
  24. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  25. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  26. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  27. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  28. package/dist/hook-runtime/hooks/pre-tool-use.js +7099 -0
  29. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  30. package/dist/hook-runtime/hooks/session-end.js +495 -0
  31. package/dist/hook-runtime/hooks/session-start.js +11313 -0
  32. package/dist/hook-runtime/index.js +44696 -0
  33. package/dist/hook-runtime/statusline-main.js +765 -0
  34. package/dist/hooks/post-commit.js +179 -0
  35. package/dist/hooks/post-rewrite.js +197 -0
  36. package/dist/hooks/post-tool-use.js +250 -0
  37. package/dist/hooks/pre-commit.js +21 -74
  38. package/dist/hooks/pre-tool-use.js +348 -0
  39. package/dist/hooks/prim-hook.js +195 -0
  40. package/dist/hooks/session-end.js +70 -0
  41. package/dist/hooks/session-start.js +299 -0
  42. package/dist/index.js +20981 -519
  43. package/dist/statusline-main.js +765 -0
  44. package/package.json +35 -13
  45. package/dist/chunk-3APLWTLB.js +0 -165
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getClient
4
- } from "../chunk-3APLWTLB.js";
3
+ checkAffectedDecisions,
4
+ formatDecisionsWarning
5
+ } from "../chunk-RO3F6JYV.js";
6
+ import "../chunk-Z7YPFWAZ.js";
7
+ import "../chunk-CUE2LKLR.js";
8
+ import "../chunk-3EEFL6NJ.js";
5
9
 
6
10
  // src/hooks/pre-commit.ts
7
11
  import { execSync } from "child_process";
@@ -11,81 +15,24 @@ function getStagedFiles() {
11
15
  });
12
16
  return output.trim().split("\n").filter((f) => f.length > 0);
13
17
  }
14
- function matchPattern(filePath, pattern) {
15
- const regexStr = pattern.replaceAll("**", "\xA7GLOBSTAR\xA7").replaceAll("*", "[^/]*").replaceAll("\xA7GLOBSTAR\xA7", ".*");
16
- const regex = new RegExp(`^${regexStr}$`);
17
- return regex.test(filePath);
18
- }
19
- function findAffectedContexts(stagedFiles, specs) {
20
- const affected = /* @__PURE__ */ new Map();
21
- for (const file of stagedFiles) {
22
- for (const spec of specs) {
23
- for (const pattern of spec.filePatterns) {
24
- if (matchPattern(file, pattern)) {
25
- const existing = affected.get(spec._id);
26
- if (existing) {
27
- existing.matchedFiles.push(file);
28
- } else {
29
- affected.set(spec._id, {
30
- contextId: spec._id,
31
- matchedFiles: [file]
32
- });
33
- }
34
- break;
35
- }
36
- }
37
- }
38
- }
39
- return affected;
40
- }
41
- var HOOK_TIMEOUT_MS = 1e4;
42
- async function main() {
18
+ async function runDecisionsCheck() {
43
19
  const stagedFiles = getStagedFiles();
44
20
  if (stagedFiles.length === 0) {
45
- process.exit(0);
46
- }
47
- const client = getClient();
48
- let mappings = [];
49
- try {
50
- mappings = await client.get("/api/cli/specs/mappings", {
51
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
52
- });
53
- } catch {
54
- process.exit(0);
55
- }
56
- if (mappings.length === 0) {
57
- process.exit(0);
21
+ return { decisions: [], truncated: false };
58
22
  }
59
- const affectedContexts = findAffectedContexts(stagedFiles, mappings);
60
- if (affectedContexts.size === 0) {
61
- process.exit(0);
62
- }
63
- console.log(`[prim] ${String(affectedContexts.size)} spec(s) affected by staged changes:`);
64
- for (const [contextId] of affectedContexts) {
65
- try {
66
- const ctx = await client.get(`/api/cli/contexts/${contextId}`, {
67
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
68
- });
69
- if (!ctx._id) {
70
- console.log(` [skip] ${contextId} \u2014 not found`);
71
- continue;
72
- }
73
- if (!ctx.isSpecDocument) {
74
- console.log(` [skip] ${contextId} \u2014 not a spec document`);
75
- continue;
76
- }
77
- await client.post(`/api/cli/contexts/${contextId}/sync`, void 0, {
78
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
79
- });
80
- console.log(` [synced] ${contextId} \u2014 ${ctx.name ?? "(unnamed)"}`);
81
- } catch (error) {
82
- const message = error instanceof Error ? error.message : String(error);
83
- console.error(` [error] ${contextId} \u2014 ${message}`);
84
- }
23
+ return checkAffectedDecisions(stagedFiles);
24
+ }
25
+ async function main() {
26
+ const decisionsResult = await runDecisionsCheck();
27
+ const warning = formatDecisionsWarning(decisionsResult);
28
+ if (warning) {
29
+ console.error(warning);
85
30
  }
86
31
  process.exit(0);
87
32
  }
88
- main().catch((error) => {
89
- console.error("[prim] Pre-commit hook error:", error);
90
- process.exit(0);
91
- });
33
+ if (!process.env.VITEST) {
34
+ main().catch((error) => {
35
+ console.error("[prim] Pre-commit hook error:", error);
36
+ process.exit(0);
37
+ });
38
+ }
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ analyzeShellTargets,
4
+ buildCodexOutput,
5
+ buildHermesOutput,
6
+ buildHookOutput,
7
+ demoteForMode,
8
+ extractFileTargets,
9
+ failOpenCodex,
10
+ failOpenHermes,
11
+ failOpenOutput,
12
+ readHookMode
13
+ } from "../chunk-BQUJ4AFJ.js";
14
+ import {
15
+ appendCodexContext,
16
+ hasVisibleCodexMessage,
17
+ prepareCodexContext
18
+ } from "../chunk-4CZTNXIS.js";
19
+ import {
20
+ packageVersion
21
+ } from "../chunk-PMRDE2DA.js";
22
+ import {
23
+ DEFAULT_RULES,
24
+ scrub
25
+ } from "../chunk-FI3LIRCB.js";
26
+ import {
27
+ boundedHealthError
28
+ } from "../chunk-LEMX3IOG.js";
29
+ import {
30
+ canonicalGitRoot,
31
+ canonicalRepositoryPath,
32
+ isRepoActive,
33
+ repoSyncId
34
+ } from "../chunk-TJTVHDJ5.js";
35
+ import {
36
+ getClient
37
+ } from "../chunk-Z7YPFWAZ.js";
38
+ import {
39
+ normalizeEnvelope,
40
+ parseAgent
41
+ } from "../chunk-WB7AUZ4C.js";
42
+ import "../chunk-CUE2LKLR.js";
43
+ import "../chunk-3EEFL6NJ.js";
44
+
45
+ // src/hooks/preflight-v3.ts
46
+ import { Buffer as Buffer2 } from "buffer";
47
+ var PREFLIGHT_PROTOCOL_VERSION = 3;
48
+ var PREFLIGHT_TIMEOUT_MS = 6500;
49
+ var MAX_PREFLIGHT_PATHS = 32;
50
+ var MAX_PROPOSAL_BYTES = 6144;
51
+ var MAX_CLIENT_VERSION_CHARS = 32;
52
+ var CLIENT_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
53
+ var MAX_DECISION_DISCLOSURES = 16;
54
+ var DISCLOSED_DECISION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
55
+ var DISCLOSED_DECISION_SHORT_ID_RE = /^[0-9a-f]{8}$/u;
56
+ function boundedClientVersion(raw) {
57
+ return typeof raw === "string" && raw.length <= MAX_CLIENT_VERSION_CHARS && CLIENT_VERSION_RE.test(raw) ? raw : "unknown";
58
+ }
59
+ function resolvePreflightTargets(args) {
60
+ let rawPaths;
61
+ let coverage = "complete";
62
+ let mutation = "present";
63
+ if (args.agent === "claude_code" && args.toolName === "Bash") {
64
+ const command = args.toolInput && typeof args.toolInput === "object" ? args.toolInput.command : void 0;
65
+ if (typeof command !== "string") {
66
+ return { paths: [], coverage: "unverified", mutation: "present" };
67
+ }
68
+ const shell = analyzeShellTargets(command);
69
+ rawPaths = shell.paths;
70
+ coverage = shell.coverage;
71
+ mutation = shell.mutation;
72
+ } else {
73
+ const extracted = extractFileTargets(args.toolName, args.toolInput, args.agent);
74
+ if (!extracted) return { paths: [], coverage: "complete", mutation: "none" };
75
+ rawPaths = extracted.paths;
76
+ if (!extracted.complete) coverage = "unverified";
77
+ }
78
+ const root = canonicalGitRoot(args.cwd);
79
+ const paths = /* @__PURE__ */ new Set();
80
+ for (const rawPath of rawPaths) {
81
+ const canonical = canonicalRepositoryPath(rawPath, args.cwd, root);
82
+ if (canonical) paths.add(canonical);
83
+ else coverage = "unverified";
84
+ }
85
+ const bounded = [...paths];
86
+ if (bounded.length > MAX_PREFLIGHT_PATHS) coverage = "unverified";
87
+ return { paths: bounded.slice(0, MAX_PREFLIGHT_PATHS), coverage, mutation };
88
+ }
89
+ function truncateUtf8(value, maxBytes) {
90
+ const encoded = Buffer2.from(value);
91
+ if (encoded.byteLength <= maxBytes) return value;
92
+ return encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "");
93
+ }
94
+ function proposalFor(toolInput) {
95
+ const redacted = scrub(toolInput, DEFAULT_RULES);
96
+ return truncateUtf8(JSON.stringify(redacted) ?? "", MAX_PROPOSAL_BYTES);
97
+ }
98
+ function parsePreflightResponse(value) {
99
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
100
+ const response = value;
101
+ const verdicts = /* @__PURE__ */ new Set(["allow", "warn", "ask", "block", "unavailable"]);
102
+ if (response.protocolVersion !== PREFLIGHT_PROTOCOL_VERSION || typeof response.verdict !== "string" || !verdicts.has(response.verdict) || typeof response.reasonCode !== "string" || typeof response.message !== "string" || !Array.isArray(response.conflicts) || !Array.isArray(response.bypassed)) {
103
+ return null;
104
+ }
105
+ const disclosures = response.decisionDisclosures;
106
+ if (disclosures !== void 0) {
107
+ if (!Array.isArray(disclosures) || disclosures.length === 0 || disclosures.length > MAX_DECISION_DISCLOSURES) {
108
+ return null;
109
+ }
110
+ const seen = /* @__PURE__ */ new Set();
111
+ for (const value2 of disclosures) {
112
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
113
+ const disclosure = value2;
114
+ if (Object.keys(disclosure).length !== 3 || !Object.hasOwn(disclosure, "decisionId") || !Object.hasOwn(disclosure, "shortId") || !Object.hasOwn(disclosure, "participation") || typeof disclosure.decisionId !== "string" || !DISCLOSED_DECISION_ID_RE.test(disclosure.decisionId) || typeof disclosure.shortId !== "string" || !DISCLOSED_DECISION_SHORT_ID_RE.test(disclosure.shortId) || disclosure.participation !== "candidate" && disclosure.participation !== "reconcile_bypass" || seen.has(disclosure.decisionId)) {
115
+ return null;
116
+ }
117
+ seen.add(disclosure.decisionId);
118
+ }
119
+ }
120
+ return response;
121
+ }
122
+ function decisionDisclosureContext(response) {
123
+ return response.decisionDisclosures?.map(({ decisionId }) => `[primitive] hidden Decision: prim decisions show ${decisionId}`).join("\n") ?? "";
124
+ }
125
+ async function requestPreflight(request, signal = AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS)) {
126
+ const response = await getClient().post("/api/cli/decisions/conflict-check", request, {
127
+ signal,
128
+ quietRefresh: true
129
+ });
130
+ return parsePreflightResponse(response);
131
+ }
132
+ function resultForPreflight(response) {
133
+ const verdict = response.verdict === "block" ? "deny" : response.verdict;
134
+ const message = boundedHealthError(response.message) ?? "";
135
+ const unavailable = message || boundedHealthError(response.reasonCode);
136
+ const safe = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
137
+ const directives = response.conflicts.flatMap((value) => {
138
+ if (!value || typeof value !== "object") return [];
139
+ const { decisionId, shortId } = value;
140
+ const id = typeof shortId === "string" && safe.test(shortId) ? `dec_${shortId}` : typeof decisionId === "string" && safe.test(decisionId) ? decisionId : void 0;
141
+ return id ? [`To reconcile, run: prim reconcile ${id}`] : [];
142
+ });
143
+ const reason = [message, ...new Set(directives)].filter(Boolean).join("\n");
144
+ const disclosureContext = decisionDisclosureContext(response);
145
+ return {
146
+ verdict,
147
+ conflicts: response.conflicts,
148
+ reason,
149
+ additionalContext: disclosureContext || (["warn", "ask", "block"].includes(response.verdict) ? reason : ""),
150
+ truncated: false,
151
+ unavailable: response.verdict === "unavailable" ? unavailable : void 0
152
+ };
153
+ }
154
+ function unverifiedResult(message) {
155
+ return {
156
+ verdict: "unavailable",
157
+ conflicts: [],
158
+ reason: "",
159
+ additionalContext: "",
160
+ truncated: false,
161
+ unavailable: message
162
+ };
163
+ }
164
+
165
+ // src/hooks/pre-tool-use.ts
166
+ var STDIN_TIMEOUT_MS = 1e3;
167
+ async function readStdin() {
168
+ return new Promise((resolve, reject) => {
169
+ const chunks = [];
170
+ const timer = setTimeout(() => {
171
+ reject(new Error("stdin read timeout"));
172
+ }, STDIN_TIMEOUT_MS);
173
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
174
+ process.stdin.on("end", () => {
175
+ clearTimeout(timer);
176
+ resolve(Buffer.concat(chunks).toString("utf-8"));
177
+ });
178
+ process.stdin.on("error", (err) => {
179
+ clearTimeout(timer);
180
+ reject(err);
181
+ });
182
+ });
183
+ }
184
+ var agent = parseAgent(process.argv);
185
+ async function emit(output) {
186
+ return await new Promise((resolve) => {
187
+ try {
188
+ process.stdout.write(`${JSON.stringify(output)}
189
+ `, (error) => resolve(!error));
190
+ } catch {
191
+ resolve(false);
192
+ }
193
+ });
194
+ }
195
+ async function emitWithAcknowledgment(output, acknowledge) {
196
+ const handedOff = await emit(output);
197
+ await acknowledge?.(handedOff);
198
+ }
199
+ function failOpen() {
200
+ if (agent === "hermes") return failOpenHermes();
201
+ return agent === "codex" ? failOpenCodex() : failOpenOutput();
202
+ }
203
+ function invocationId(envelope) {
204
+ const value = agent === "hermes" ? envelope.extra?.tool_call_id : envelope.tool_use_id;
205
+ return typeof value === "string" && value.length > 0 ? value : void 0;
206
+ }
207
+ function hermesAdvisory(result, aggregate) {
208
+ const parts = [];
209
+ if (aggregate === "warn") parts.push(result.reason);
210
+ if (result.verdict === "unavailable") {
211
+ parts.push(result.unavailable ?? result.reason);
212
+ }
213
+ if (result.additionalContext && result.additionalContext !== result.reason) {
214
+ parts.push(result.additionalContext);
215
+ }
216
+ const message = [...new Set(parts.filter(Boolean))].join("\n");
217
+ return message.startsWith("[primitive]") ? message : message ? `[primitive] ${message}` : "";
218
+ }
219
+ async function emitUnverified(message, envelope) {
220
+ const result = unverifiedResult(message);
221
+ if (agent === "hermes") {
222
+ process.stderr.write(`[primitive] ${message}
223
+ `);
224
+ await emit(failOpenHermes());
225
+ } else {
226
+ let output = agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result]);
227
+ if (agent === "codex" && typeof envelope?.session_id === "string" && envelope.session_id) {
228
+ try {
229
+ const context = await prepareCodexContext({
230
+ cwd: envelope.cwd ?? process.cwd(),
231
+ sessionId: envelope.session_id,
232
+ includeDigest: false
233
+ });
234
+ output = appendCodexContext(output, context.context);
235
+ await emitWithAcknowledgment(output, context.acknowledge);
236
+ return;
237
+ } catch {
238
+ }
239
+ }
240
+ await emit(output);
241
+ }
242
+ }
243
+ async function main() {
244
+ let raw;
245
+ try {
246
+ raw = await readStdin();
247
+ } catch {
248
+ await emit(failOpen());
249
+ return;
250
+ }
251
+ let envelope;
252
+ try {
253
+ envelope = normalizeEnvelope(
254
+ JSON.parse(raw),
255
+ agent
256
+ );
257
+ } catch {
258
+ await emit(failOpen());
259
+ return;
260
+ }
261
+ if (envelope.hook_event_name !== "PreToolUse") {
262
+ await emit(failOpen());
263
+ return;
264
+ }
265
+ const env = process.env;
266
+ const mode = readHookMode(env);
267
+ if (mode === "off") {
268
+ await emit(failOpen());
269
+ return;
270
+ }
271
+ const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
272
+ const cwd = typeof envelope.cwd === "string" && envelope.cwd.length > 0 ? envelope.cwd : process.cwd();
273
+ const targets = resolvePreflightTargets({
274
+ toolName,
275
+ toolInput: envelope.tool_input,
276
+ agent,
277
+ cwd
278
+ });
279
+ if (targets.mutation === "none") {
280
+ await emit(failOpen());
281
+ return;
282
+ }
283
+ if (!isRepoActive(cwd)) {
284
+ await emit(failOpen());
285
+ return;
286
+ }
287
+ if (targets.paths.length === 0) {
288
+ await emit(failOpen());
289
+ return;
290
+ }
291
+ const binding = repoSyncId(cwd);
292
+ const sessionId = envelope.session_id;
293
+ const callId = invocationId(envelope);
294
+ if (!binding || typeof sessionId !== "string" || !sessionId || !callId) {
295
+ await emitUnverified(
296
+ "GitHub repo connection is required for repository-specific file attribution, Conflict Gate verification, and commit correlation; or tool invocation identity is unavailable",
297
+ envelope
298
+ );
299
+ return;
300
+ }
301
+ const request = {
302
+ protocolVersion: PREFLIGHT_PROTOCOL_VERSION,
303
+ agent,
304
+ clientMode: mode,
305
+ clientVersion: boundedClientVersion(packageVersion()),
306
+ sessionId,
307
+ invocationId: callId,
308
+ repoSyncId: binding,
309
+ paths: targets.paths,
310
+ coverage: targets.coverage,
311
+ proposal: proposalFor(envelope.tool_input)
312
+ };
313
+ let result;
314
+ try {
315
+ const response = await requestPreflight(request);
316
+ if (!response) {
317
+ await emitUnverified("enforcement service returned an incompatible response", envelope);
318
+ return;
319
+ }
320
+ result = resultForPreflight(response);
321
+ } catch {
322
+ await emitUnverified("enforcement service unavailable; change was not verified", envelope);
323
+ return;
324
+ }
325
+ const aggregate = demoteForMode(
326
+ result.verdict === "unavailable" ? "allow" : result.verdict,
327
+ mode
328
+ );
329
+ if (agent === "hermes" && aggregate !== "deny" && aggregate !== "ask") {
330
+ const advisory = hermesAdvisory(result, aggregate);
331
+ if (advisory) process.stderr.write(`${advisory}
332
+ `);
333
+ }
334
+ let output = agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result]);
335
+ if (agent === "codex" && hasVisibleCodexMessage(output) && typeof sessionId === "string" && sessionId.length > 0) {
336
+ try {
337
+ const context = await prepareCodexContext({ cwd, sessionId, includeDigest: false });
338
+ output = appendCodexContext(output, context.context);
339
+ await emitWithAcknowledgment(output, context.acknowledge);
340
+ return;
341
+ } catch {
342
+ }
343
+ }
344
+ await emit(output);
345
+ }
346
+ main().catch(async () => {
347
+ await emit(failOpen());
348
+ });
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildHookOutput,
4
+ handoffHookOutput
5
+ } from "../chunk-4GLVJLEQ.js";
6
+ import {
7
+ FEEDBACK_DEADLINE_MS,
8
+ acknowledgeDecisionFeedback,
9
+ leaseDecisionFeedback,
10
+ renderFeedback
11
+ } from "../chunk-LMRYZL3D.js";
12
+ import {
13
+ enrichHookPayloadWithFileRefs,
14
+ preserveHookFileMetadata
15
+ } from "../chunk-ZOSNEO5N.js";
16
+ import {
17
+ postToolInvocationId,
18
+ shouldFlushAfter,
19
+ toMove
20
+ } from "../chunk-OUXH3UEP.js";
21
+ import {
22
+ getOrCreateWorkspaceId
23
+ } from "../chunk-EZXEWFDG.js";
24
+ import {
25
+ appendMove,
26
+ resolveOrg
27
+ } from "../chunk-YWO7O6QG.js";
28
+ import "../chunk-BQUJ4AFJ.js";
29
+ import {
30
+ prepareCodexContext
31
+ } from "../chunk-4CZTNXIS.js";
32
+ import "../chunk-PMRDE2DA.js";
33
+ import {
34
+ scrubFromCwd,
35
+ writeHookDebug
36
+ } from "../chunk-FI3LIRCB.js";
37
+ import "../chunk-LEMX3IOG.js";
38
+ import {
39
+ isRepoActiveForCapture,
40
+ repoSyncId,
41
+ resolveRepositoryContext
42
+ } from "../chunk-TJTVHDJ5.js";
43
+ import "../chunk-Z7YPFWAZ.js";
44
+ import {
45
+ normalizeEnvelope,
46
+ parseAgent
47
+ } from "../chunk-WB7AUZ4C.js";
48
+ import "../chunk-CUE2LKLR.js";
49
+ import "../chunk-3EEFL6NJ.js";
50
+
51
+ // src/hooks/prim-hook.ts
52
+ import { spawn } from "child_process";
53
+ import { readFileSync } from "fs";
54
+ import { dirname, join } from "path";
55
+ import { fileURLToPath } from "url";
56
+
57
+ // src/hooks/codex-message-context.ts
58
+ var defaultDeps = { prepare: prepareCodexContext };
59
+ async function processCodexMessageContext(envelope, deps = defaultDeps) {
60
+ const event = envelope.hook_event_name;
61
+ if (event === "Stop") return { output: {} };
62
+ if (event !== "UserPromptSubmit") return { output: {} };
63
+ if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
64
+ return { output: {} };
65
+ }
66
+ let prepared;
67
+ try {
68
+ prepared = await deps.prepare({
69
+ cwd: typeof envelope.cwd === "string" ? envelope.cwd : process.cwd(),
70
+ sessionId: envelope.session_id
71
+ });
72
+ } catch {
73
+ return { output: {} };
74
+ }
75
+ return {
76
+ output: prepared.context ? {
77
+ hookSpecificOutput: {
78
+ hookEventName: "UserPromptSubmit",
79
+ additionalContext: prepared.context
80
+ }
81
+ } : {},
82
+ // Even an empty prompt response is a successful handoff. Committing it
83
+ // advances verified feed state and report dedup without inventing output.
84
+ acknowledge: async () => {
85
+ await prepared.acknowledge(true);
86
+ }
87
+ };
88
+ }
89
+
90
+ // src/hooks/prim-hook.ts
91
+ var here = dirname(fileURLToPath(import.meta.url));
92
+ var outputAttempted = false;
93
+ function emitOutput(output, acknowledge) {
94
+ outputAttempted = true;
95
+ return handoffHookOutput(output, acknowledge);
96
+ }
97
+ function resolveCliVersion() {
98
+ try {
99
+ const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
100
+ return pkg.version ?? "unknown";
101
+ } catch {
102
+ return "unknown";
103
+ }
104
+ }
105
+ function spawnBackgroundFlush() {
106
+ const entry = join(here, "..", "index.js");
107
+ spawn(process.execPath, [entry, "moves", "flush"], {
108
+ detached: true,
109
+ stdio: "ignore"
110
+ }).unref();
111
+ }
112
+ async function main() {
113
+ const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
114
+ const agent = parseAgent(process.argv);
115
+ let raw;
116
+ let parsed;
117
+ try {
118
+ raw = readFileSync(0, "utf-8");
119
+ parsed = normalizeEnvelope(JSON.parse(raw), agent);
120
+ } catch (error) {
121
+ writeHookDebug("capture failed", error);
122
+ await emitOutput(buildHookOutput({}));
123
+ return;
124
+ }
125
+ const cwd = parsed.cwd ?? process.cwd();
126
+ const isClaudeStop = agent === "claude_code" && parsed.hook_event_name === "Stop";
127
+ const isCodexContextEvent = agent === "codex" && (parsed.hook_event_name === "UserPromptSubmit" || parsed.hook_event_name === "Stop");
128
+ if (!isRepoActiveForCapture(cwd)) {
129
+ if (isClaudeStop || isCodexContextEvent) await emitOutput(buildHookOutput({}));
130
+ return;
131
+ }
132
+ const identity = getOrCreateWorkspaceId(cwd);
133
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
134
+ const resolvedRepository = resolveRepositoryContext(cwd);
135
+ const repository = resolvedRepository ? { ...resolvedRepository, repoSyncId: repoSyncId(cwd) } : null;
136
+ try {
137
+ const enrichment = repository ? enrichHookPayloadWithFileRefs({ parsed, agent, cwd, repository }) : void 0;
138
+ const enriched = enrichment?.parsed ?? parsed;
139
+ const invocationId = postToolInvocationId(enriched, agent);
140
+ const base = toMove(
141
+ enriched,
142
+ resolveCliVersion(),
143
+ agent,
144
+ workspaceId,
145
+ repository,
146
+ invocationId
147
+ );
148
+ const scrubbed = await scrubFromCwd(enriched, cwd);
149
+ const move = {
150
+ ...base,
151
+ payload: enrichment ? preserveHookFileMetadata(scrubbed, enrichment.resolution) : scrubbed
152
+ };
153
+ const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd });
154
+ appendMove(move, orgId);
155
+ if (shouldFlushAfter(move.eventType)) {
156
+ spawnBackgroundFlush();
157
+ }
158
+ } catch (error) {
159
+ writeHookDebug("capture failed", error);
160
+ }
161
+ if (isCodexContextEvent) {
162
+ const result = await processCodexMessageContext(parsed);
163
+ await emitOutput(result.output, result.acknowledge);
164
+ return;
165
+ }
166
+ if (!isClaudeStop) return;
167
+ const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
168
+ if (!workspaceId || !sessionId) {
169
+ await emitOutput(buildHookOutput({}));
170
+ return;
171
+ }
172
+ const lease = await leaseDecisionFeedback(
173
+ { workspaceId, currentSessionId: sessionId, signal: feedbackSignal },
174
+ { onError: (error) => writeHookDebug("feedback failed", error) }
175
+ );
176
+ const rendered = lease ? renderFeedback(lease) : void 0;
177
+ await emitOutput(
178
+ buildHookOutput({ systemMessage: rendered?.systemMessage }),
179
+ rendered ? async () => {
180
+ await acknowledgeDecisionFeedback(
181
+ {
182
+ protocolVersion: rendered.protocolVersion,
183
+ workspaceId,
184
+ deliveries: rendered.deliveries,
185
+ signal: feedbackSignal
186
+ },
187
+ { onError: (error) => writeHookDebug("feedback failed", error) }
188
+ );
189
+ } : void 0
190
+ );
191
+ }
192
+ void main().catch(async (error) => {
193
+ writeHookDebug("capture failed", error);
194
+ if (!outputAttempted) await emitOutput(buildHookOutput({}));
195
+ });