@primitive.ai/prim 0.1.0-alpha.9 → 0.1.0-alpha.91

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 (47) hide show
  1. package/README.md +303 -37
  2. package/SKILL.md +269 -0
  3. package/dist/chunk-4GLVJLEQ.js +55 -0
  4. package/dist/chunk-5AFRMOPX.js +209 -0
  5. package/dist/chunk-5GYQ4J3D.js +126 -0
  6. package/dist/chunk-67VNLLBN.js +789 -0
  7. package/dist/chunk-BTNNEH2W.js +79 -0
  8. package/dist/chunk-EWZMDD44.js +1474 -0
  9. package/dist/chunk-EZXEWFDG.js +210 -0
  10. package/dist/chunk-FBAYCS3X.js +327 -0
  11. package/dist/chunk-FKXFUUWJ.js +36 -0
  12. package/dist/chunk-FOLKHXLJ.js +1529 -0
  13. package/dist/chunk-H4UNYFUC.js +196 -0
  14. package/dist/chunk-HU4PW6I6.js +264 -0
  15. package/dist/chunk-KFXF2V5G.js +155 -0
  16. package/dist/chunk-LFFLHSQO.js +78 -0
  17. package/dist/chunk-M6RIJVRE.js +18358 -0
  18. package/dist/chunk-NAHHFSLH.js +258 -0
  19. package/dist/chunk-OAAP2MY6.js +262 -0
  20. package/dist/chunk-PCVGBE5B.js +659 -0
  21. package/dist/chunk-VW3FDYRT.js +517 -0
  22. package/dist/chunk-WX52U7Z2.js +112 -0
  23. package/dist/chunk-ZB4F75SZ.js +843 -0
  24. package/dist/daemon/server.js +21633 -0
  25. package/dist/hook-runtime/daemon/server.js +21633 -0
  26. package/dist/hook-runtime/hooks/post-commit.js +19258 -0
  27. package/dist/hook-runtime/hooks/post-rewrite.js +19273 -0
  28. package/dist/hook-runtime/hooks/post-tool-use.js +26365 -0
  29. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  30. package/dist/hook-runtime/hooks/pre-tool-use.js +25832 -0
  31. package/dist/hook-runtime/hooks/prim-hook.js +26386 -0
  32. package/dist/hook-runtime/hooks/session-end.js +1078 -0
  33. package/dist/hook-runtime/hooks/session-start.js +28779 -0
  34. package/dist/hook-runtime/index.js +50987 -0
  35. package/dist/hook-runtime/statusline-main.js +809 -0
  36. package/dist/hooks/post-commit.js +190 -0
  37. package/dist/hooks/post-rewrite.js +203 -0
  38. package/dist/hooks/post-tool-use.js +288 -0
  39. package/dist/hooks/pre-commit.js +16 -101
  40. package/dist/hooks/pre-tool-use.js +369 -0
  41. package/dist/hooks/prim-hook.js +229 -0
  42. package/dist/hooks/session-end.js +59 -0
  43. package/dist/hooks/session-start.js +316 -0
  44. package/dist/index.js +8875 -429
  45. package/dist/statusline-main.js +809 -0
  46. package/package.json +35 -13
  47. 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-5GYQ4J3D.js";
6
+ import "../chunk-PCVGBE5B.js";
7
+ import "../chunk-KFXF2V5G.js";
8
+ import "../chunk-67VNLLBN.js";
5
9
 
6
10
  // src/hooks/pre-commit.ts
7
11
  import { execSync } from "child_process";
@@ -11,113 +15,24 @@ function getStagedFiles() {
11
15
  });
12
16
  return output.trim().split("\n").filter((f) => f.length > 0);
13
17
  }
14
- function getStagedDiff(files) {
15
- return execSync(`git diff --cached -- ${files.map((f) => `"${f}"`).join(" ")}`, {
16
- encoding: "utf-8"
17
- });
18
- }
19
- function matchPattern(filePath, pattern) {
20
- const regexStr = pattern.replaceAll("**", "\xA7GLOBSTAR\xA7").replaceAll("*", "[^/]*").replaceAll("\xA7GLOBSTAR\xA7", ".*");
21
- const regex = new RegExp(`^${regexStr}$`);
22
- return regex.test(filePath);
23
- }
24
- function findAffectedContexts(stagedFiles, specs) {
25
- const affected = /* @__PURE__ */ new Map();
26
- for (const file of stagedFiles) {
27
- for (const spec of specs) {
28
- for (const pattern of spec.filePatterns) {
29
- if (matchPattern(file, pattern)) {
30
- const existing = affected.get(spec._id);
31
- if (existing) {
32
- existing.matchedFiles.push(file);
33
- } else {
34
- affected.set(spec._id, {
35
- contextId: spec._id,
36
- matchedFiles: [file]
37
- });
38
- }
39
- break;
40
- }
41
- }
42
- }
43
- }
44
- return affected;
45
- }
46
- var HOOK_TIMEOUT_MS = 1e4;
47
- var defaultDeps = {
48
- getClient,
49
- getStagedFiles,
50
- getStagedDiff
51
- };
52
- async function syncAffectedSpecs(deps = defaultDeps) {
53
- const stagedFiles = deps.getStagedFiles();
18
+ async function runDecisionsCheck() {
19
+ const stagedFiles = getStagedFiles();
54
20
  if (stagedFiles.length === 0) {
55
- return [];
21
+ return { decisions: [], truncated: false };
56
22
  }
57
- const client = deps.getClient();
58
- let mappings = [];
59
- try {
60
- mappings = await client.get("/api/cli/specs/mappings", {
61
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
62
- });
63
- } catch {
64
- return [];
65
- }
66
- if (mappings.length === 0) {
67
- return [];
68
- }
69
- const affectedContexts = findAffectedContexts(stagedFiles, mappings);
70
- if (affectedContexts.size === 0) {
71
- return [];
72
- }
73
- console.log(`[prim] ${String(affectedContexts.size)} spec(s) affected by staged changes:`);
74
- const synced = [];
75
- for (const [contextId, affected] of affectedContexts) {
76
- try {
77
- const ctx = await client.get(`/api/cli/contexts/${contextId}`, {
78
- signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
79
- });
80
- if (!ctx._id) {
81
- console.log(` [skip] ${contextId} \u2014 not found`);
82
- continue;
83
- }
84
- if (!ctx.isSpecDocument) {
85
- console.log(` [skip] ${contextId} \u2014 not a spec document`);
86
- continue;
87
- }
88
- const diffContent = deps.getStagedDiff(affected.matchedFiles);
89
- if (!diffContent) {
90
- console.log(` [skip] ${contextId} \u2014 no diff content`);
91
- continue;
92
- }
93
- await client.post(
94
- `/api/cli/contexts/${contextId}/sync-diff`,
95
- { diffContent, affectedFiles: affected.matchedFiles },
96
- { signal: AbortSignal.timeout(HOOK_TIMEOUT_MS) }
97
- );
98
- console.log(` [synced] ${contextId} \u2014 ${ctx.name ?? "(unnamed)"}`);
99
- synced.push(contextId);
100
- } catch (error) {
101
- const message = error instanceof Error ? error.message : String(error);
102
- console.error(` [error] ${contextId} \u2014 ${message}`);
103
- }
104
- }
105
- return synced;
23
+ return checkAffectedDecisions(stagedFiles);
106
24
  }
107
25
  async function main() {
108
- await syncAffectedSpecs();
26
+ const decisionsResult = await runDecisionsCheck();
27
+ const warning = formatDecisionsWarning(decisionsResult);
28
+ if (warning) {
29
+ console.error(warning);
30
+ }
109
31
  process.exit(0);
110
32
  }
111
- var isDirectExecution = typeof process !== "undefined" && process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
112
- if (isDirectExecution) {
33
+ if (!process.env.VITEST) {
113
34
  main().catch((error) => {
114
35
  console.error("[prim] Pre-commit hook error:", error);
115
36
  process.exit(0);
116
37
  });
117
38
  }
118
- export {
119
- HOOK_TIMEOUT_MS,
120
- findAffectedContexts,
121
- matchPattern,
122
- syncAffectedSpecs
123
- };
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ analyzeShellTargets,
4
+ buildCodexOutput,
5
+ buildCursorOutput,
6
+ buildHermesOutput,
7
+ buildHookOutput,
8
+ demoteForMode,
9
+ extractFileTargets,
10
+ failOpenCodex,
11
+ failOpenCursor,
12
+ failOpenHermes,
13
+ failOpenOutput,
14
+ readHookMode
15
+ } from "../chunk-ZB4F75SZ.js";
16
+ import {
17
+ appendCodexContext,
18
+ hasVisibleCodexMessage,
19
+ prepareCodexContext
20
+ } from "../chunk-VW3FDYRT.js";
21
+ import "../chunk-WX52U7Z2.js";
22
+ import {
23
+ DEFAULT_RULES,
24
+ scrub
25
+ } from "../chunk-OAAP2MY6.js";
26
+ import {
27
+ boundedHealthError
28
+ } from "../chunk-BTNNEH2W.js";
29
+ import {
30
+ cachedCollectScopeAdmits,
31
+ isRepoActive,
32
+ repoSyncId
33
+ } from "../chunk-M6RIJVRE.js";
34
+ import {
35
+ getClient
36
+ } from "../chunk-PCVGBE5B.js";
37
+ import {
38
+ normalizeEnvelope,
39
+ parseAgent,
40
+ readHookStdin,
41
+ shouldSuppressImportedCursorHandler
42
+ } from "../chunk-HU4PW6I6.js";
43
+ import {
44
+ packageVersion
45
+ } from "../chunk-EWZMDD44.js";
46
+ import {
47
+ canonicalGitRoot,
48
+ canonicalRepositoryPath,
49
+ currentBranch,
50
+ githubRepositoryFullName
51
+ } from "../chunk-H4UNYFUC.js";
52
+ import "../chunk-KFXF2V5G.js";
53
+ import "../chunk-67VNLLBN.js";
54
+
55
+ // src/hooks/preflight-v3.ts
56
+ import { Buffer } from "buffer";
57
+ var PREFLIGHT_PROTOCOL_VERSION = 3;
58
+ var PREFLIGHT_TIMEOUT_MS = 6500;
59
+ var MAX_PREFLIGHT_PATHS = 32;
60
+ var MAX_PROPOSAL_BYTES = 6144;
61
+ var MAX_CLIENT_VERSION_CHARS = 32;
62
+ var CLIENT_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
63
+ var MAX_DECISION_DISCLOSURES = 16;
64
+ var DISCLOSED_DECISION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
65
+ var DISCLOSED_DECISION_SHORT_ID_RE = /^[0-9a-f]{8}$/u;
66
+ function boundedClientVersion(raw) {
67
+ return typeof raw === "string" && raw.length <= MAX_CLIENT_VERSION_CHARS && CLIENT_VERSION_RE.test(raw) ? raw : "unknown";
68
+ }
69
+ function resolvePreflightTargets(args) {
70
+ let rawPaths;
71
+ let coverage = "complete";
72
+ let mutation = "present";
73
+ let definite = false;
74
+ const shellTool = (args.agent === "claude_code" || args.agent === "codex") && args.toolName === "Bash" || args.agent === "cursor" && args.toolName === "Shell";
75
+ if (shellTool) {
76
+ const command = typeof args.toolInput === "string" ? args.toolInput : args.toolInput && typeof args.toolInput === "object" ? args.toolInput.command : void 0;
77
+ if (typeof command !== "string") {
78
+ return { paths: [], coverage: "unverified", mutation: "present" };
79
+ }
80
+ const shell = analyzeShellTargets(command);
81
+ rawPaths = shell.paths;
82
+ coverage = shell.coverage;
83
+ mutation = shell.mutation;
84
+ definite = shell.definiteEdit === true;
85
+ } else {
86
+ const extracted = extractFileTargets(args.toolName, args.toolInput, args.agent);
87
+ if (!extracted) return { paths: [], coverage: "complete", mutation: "none" };
88
+ rawPaths = extracted.paths;
89
+ if (!extracted.complete) coverage = "unverified";
90
+ definite = true;
91
+ }
92
+ const root = canonicalGitRoot(args.cwd);
93
+ const paths = /* @__PURE__ */ new Set();
94
+ for (const rawPath of rawPaths) {
95
+ const canonical = canonicalRepositoryPath(rawPath, args.cwd, root);
96
+ if (canonical) paths.add(canonical);
97
+ else coverage = "unverified";
98
+ }
99
+ const bounded = [...paths];
100
+ if (bounded.length > MAX_PREFLIGHT_PATHS) coverage = "unverified";
101
+ const resolved = bounded.slice(0, MAX_PREFLIGHT_PATHS);
102
+ if (definite && resolved.length === 0 && mutation === "present") {
103
+ return { paths: resolved, coverage: "unverified", mutation, definite: true };
104
+ }
105
+ return { paths: resolved, coverage, mutation };
106
+ }
107
+ function truncateUtf8(value, maxBytes) {
108
+ const encoded = Buffer.from(value);
109
+ if (encoded.byteLength <= maxBytes) return value;
110
+ return encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "");
111
+ }
112
+ function proposalFor(toolInput) {
113
+ const redacted = scrub(toolInput, DEFAULT_RULES);
114
+ return truncateUtf8(JSON.stringify(redacted) ?? "", MAX_PROPOSAL_BYTES);
115
+ }
116
+ function parsePreflightResponse(value) {
117
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
118
+ const response = value;
119
+ const verdicts = /* @__PURE__ */ new Set(["allow", "warn", "ask", "block", "unavailable"]);
120
+ 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)) {
121
+ return null;
122
+ }
123
+ const disclosures = response.decisionDisclosures;
124
+ if (disclosures !== void 0) {
125
+ if (!Array.isArray(disclosures) || disclosures.length === 0 || disclosures.length > MAX_DECISION_DISCLOSURES) {
126
+ return null;
127
+ }
128
+ const seen = /* @__PURE__ */ new Set();
129
+ for (const value2 of disclosures) {
130
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
131
+ const disclosure = value2;
132
+ 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)) {
133
+ return null;
134
+ }
135
+ seen.add(disclosure.decisionId);
136
+ }
137
+ }
138
+ return response;
139
+ }
140
+ function decisionDisclosureContext(response) {
141
+ return response.decisionDisclosures?.map(({ decisionId }) => `[primitive] hidden Decision: prim decisions show ${decisionId}`).join("\n") ?? "";
142
+ }
143
+ async function requestPreflight(request, signal = AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS)) {
144
+ const response = await getClient().post("/api/cli/decisions/conflict-check", request, {
145
+ signal,
146
+ quietRefresh: true
147
+ });
148
+ return parsePreflightResponse(response);
149
+ }
150
+ function resultForPreflight(response) {
151
+ const verdict = response.verdict === "block" ? "deny" : response.verdict;
152
+ const message = boundedHealthError(response.message) ?? "";
153
+ const unavailable = message || boundedHealthError(response.reasonCode);
154
+ const safe = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
155
+ const directives = response.conflicts.flatMap((value) => {
156
+ if (!value || typeof value !== "object") return [];
157
+ const { decisionId, shortId } = value;
158
+ const id = typeof shortId === "string" && safe.test(shortId) ? `dec_${shortId}` : typeof decisionId === "string" && safe.test(decisionId) ? decisionId : void 0;
159
+ return id ? [`To reconcile, run: prim reconcile ${id}`] : [];
160
+ });
161
+ const reason = [message, ...new Set(directives)].filter(Boolean).join("\n");
162
+ const disclosureContext = decisionDisclosureContext(response);
163
+ return {
164
+ verdict,
165
+ conflicts: response.conflicts,
166
+ reason,
167
+ additionalContext: disclosureContext || (["warn", "ask", "block"].includes(response.verdict) ? reason : ""),
168
+ truncated: false,
169
+ unavailable: response.verdict === "unavailable" ? unavailable : void 0
170
+ };
171
+ }
172
+ function unverifiedResult(message) {
173
+ return {
174
+ verdict: "unavailable",
175
+ conflicts: [],
176
+ reason: "",
177
+ additionalContext: "",
178
+ truncated: false,
179
+ unavailable: message
180
+ };
181
+ }
182
+
183
+ // src/hooks/pre-tool-use.ts
184
+ var STDIN_TIMEOUT_MS = 1e3;
185
+ var agent = parseAgent(process.argv);
186
+ async function emit(output) {
187
+ return await new Promise((resolve) => {
188
+ try {
189
+ process.stdout.write(`${JSON.stringify(output)}
190
+ `, (error) => resolve(!error));
191
+ } catch {
192
+ resolve(false);
193
+ }
194
+ });
195
+ }
196
+ async function emitWithAcknowledgment(output, acknowledge) {
197
+ const handedOff = await emit(output);
198
+ await acknowledge?.(handedOff);
199
+ }
200
+ function failOpen() {
201
+ if (agent === "hermes") return failOpenHermes();
202
+ if (agent === "cursor") return failOpenCursor();
203
+ return agent === "codex" ? failOpenCodex() : failOpenOutput();
204
+ }
205
+ function invocationId(envelope) {
206
+ const value = agent === "hermes" ? envelope.extra?.tool_call_id : envelope.tool_use_id;
207
+ return typeof value === "string" && value.length > 0 ? value : void 0;
208
+ }
209
+ function hermesAdvisory(result, aggregate) {
210
+ const parts = [];
211
+ if (aggregate === "warn") parts.push(result.reason);
212
+ if (result.verdict === "unavailable") {
213
+ parts.push(result.unavailable ?? result.reason);
214
+ }
215
+ if (result.additionalContext && result.additionalContext !== result.reason) {
216
+ parts.push(result.additionalContext);
217
+ }
218
+ const message = [...new Set(parts.filter(Boolean))].join("\n");
219
+ return message.startsWith("[primitive]") ? message : message ? `[primitive] ${message}` : "";
220
+ }
221
+ async function emitUnverified(message, envelope) {
222
+ const result = unverifiedResult(message);
223
+ if (agent === "hermes") {
224
+ process.stderr.write(`[primitive] ${message}
225
+ `);
226
+ await emit(failOpenHermes());
227
+ } else if (agent === "cursor") {
228
+ process.stderr.write(`[primitive] ${message}
229
+ `);
230
+ await emit(buildCursorOutput("allow", [result]));
231
+ } else {
232
+ let output = agent === "codex" ? buildCodexOutput("allow", [result]) : buildHookOutput("allow", [result]);
233
+ if (agent === "codex" && typeof envelope?.session_id === "string" && envelope.session_id) {
234
+ try {
235
+ const context = await prepareCodexContext({
236
+ cwd: envelope.cwd ?? process.cwd(),
237
+ sessionId: envelope.session_id,
238
+ includeDigest: false
239
+ });
240
+ output = appendCodexContext(output, context.context);
241
+ await emitWithAcknowledgment(output, context.acknowledge);
242
+ return;
243
+ } catch {
244
+ }
245
+ }
246
+ await emit(output);
247
+ }
248
+ }
249
+ async function main() {
250
+ let raw;
251
+ try {
252
+ raw = await readHookStdin(STDIN_TIMEOUT_MS);
253
+ } catch {
254
+ await emit(failOpen());
255
+ return;
256
+ }
257
+ let envelope;
258
+ try {
259
+ const incoming = JSON.parse(raw);
260
+ if (agent !== "cursor" && shouldSuppressImportedCursorHandler(incoming, "prim-pre-tool-use")) {
261
+ await emit(failOpenCursor());
262
+ return;
263
+ }
264
+ envelope = normalizeEnvelope(incoming, agent);
265
+ } catch {
266
+ await emit(failOpen());
267
+ return;
268
+ }
269
+ if (envelope.hook_event_name !== "PreToolUse") {
270
+ await emit(failOpen());
271
+ return;
272
+ }
273
+ const env = process.env;
274
+ const mode = readHookMode(env);
275
+ if (mode === "off") {
276
+ await emit(failOpen());
277
+ return;
278
+ }
279
+ const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
280
+ const cwd = typeof envelope.cwd === "string" && envelope.cwd.length > 0 ? envelope.cwd : process.cwd();
281
+ const targets = resolvePreflightTargets({
282
+ toolName,
283
+ toolInput: envelope.tool_input,
284
+ agent,
285
+ cwd
286
+ });
287
+ if (targets.mutation === "none") {
288
+ await emit(failOpen());
289
+ return;
290
+ }
291
+ if (!isRepoActive(cwd)) {
292
+ await emit(failOpen());
293
+ return;
294
+ }
295
+ if (targets.paths.length === 0 && !targets.definite) {
296
+ await emit(failOpen());
297
+ return;
298
+ }
299
+ if (agent === "cursor" && targets.coverage === "unverified") {
300
+ await emitUnverified("tool targets were ambiguous or outside the active repository", envelope);
301
+ return;
302
+ }
303
+ const binding = repoSyncId(cwd);
304
+ const branch = currentBranch(cwd);
305
+ const sessionId = envelope.session_id;
306
+ const callId = invocationId(envelope);
307
+ if (!binding || typeof sessionId !== "string" || !sessionId || !callId) {
308
+ await emitUnverified(
309
+ "GitHub repo connection is required for repository-specific file attribution, Conflict Gate verification, and commit correlation; or tool invocation identity is unavailable",
310
+ envelope
311
+ );
312
+ return;
313
+ }
314
+ const collectScopeAdmits = cachedCollectScopeAdmits(cwd, {
315
+ repository: githubRepositoryFullName(cwd) ?? void 0,
316
+ branch,
317
+ agent,
318
+ paths: targets.paths,
319
+ pathsComplete: targets.coverage === "complete"
320
+ });
321
+ const request = {
322
+ protocolVersion: PREFLIGHT_PROTOCOL_VERSION,
323
+ agent,
324
+ clientMode: mode,
325
+ clientVersion: boundedClientVersion(packageVersion()),
326
+ sessionId,
327
+ invocationId: callId,
328
+ repoSyncId: binding,
329
+ paths: targets.paths,
330
+ coverage: targets.coverage,
331
+ ...branch === void 0 ? {} : { branch },
332
+ proposal: collectScopeAdmits ? proposalFor(envelope.tool_input) : ""
333
+ };
334
+ let result;
335
+ try {
336
+ const response = await requestPreflight(request);
337
+ if (!response) {
338
+ await emitUnverified("enforcement service returned an incompatible response", envelope);
339
+ return;
340
+ }
341
+ result = resultForPreflight(response);
342
+ } catch {
343
+ await emitUnverified("enforcement service unavailable; change was not verified", envelope);
344
+ return;
345
+ }
346
+ const aggregate = demoteForMode(
347
+ result.verdict === "unavailable" ? "allow" : result.verdict,
348
+ mode
349
+ );
350
+ if (agent === "hermes" && aggregate !== "deny" && aggregate !== "ask") {
351
+ const advisory = hermesAdvisory(result, aggregate);
352
+ if (advisory) process.stderr.write(`${advisory}
353
+ `);
354
+ }
355
+ let output = agent === "hermes" ? buildHermesOutput(aggregate, [result]) : agent === "cursor" ? buildCursorOutput(aggregate, [result]) : agent === "codex" ? buildCodexOutput(aggregate, [result]) : buildHookOutput(aggregate, [result]);
356
+ if (agent === "codex" && hasVisibleCodexMessage(output) && typeof sessionId === "string" && sessionId.length > 0) {
357
+ try {
358
+ const context = await prepareCodexContext({ cwd, sessionId, includeDigest: false });
359
+ output = appendCodexContext(output, context.context);
360
+ await emitWithAcknowledgment(output, context.acknowledge);
361
+ return;
362
+ } catch {
363
+ }
364
+ }
365
+ await emit(output);
366
+ }
367
+ main().catch(async () => {
368
+ await emit(failOpen());
369
+ });