@primitive.ai/prim 0.1.0-alpha.56 → 0.1.0-alpha.58

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.
@@ -0,0 +1,438 @@
1
+ import {
2
+ DEFAULT_RULES,
3
+ scrub
4
+ } from "./chunk-GUXBUBQ7.js";
5
+ import {
6
+ canonicalGitRoot,
7
+ canonicalRepositoryPath
8
+ } from "./chunk-LDQZASIF.js";
9
+ import {
10
+ getClient
11
+ } from "./chunk-I3HIPAIR.js";
12
+
13
+ // src/hooks/pre-tool-use-scoring.ts
14
+ import { isAbsolute, relative, sep } from "path";
15
+ function anyUnverified(results) {
16
+ return results.some((r) => r.verdict === "unavailable" || r.truncated);
17
+ }
18
+ function unverifiedNote(results) {
19
+ const causes = [];
20
+ const unavailable = results.find((r) => r.verdict === "unavailable");
21
+ if (unavailable) {
22
+ causes.push(
23
+ unavailable.unavailable ? `decision check skipped \u2014 ${unavailable.unavailable}` : "decision check skipped \u2014 not verified"
24
+ );
25
+ }
26
+ if (results.some((r) => r.truncated)) {
27
+ causes.push("decision check partial \u2014 conflict set truncated (per-file cap hit)");
28
+ }
29
+ return causes.map((c) => `[primitive] ${c}`).join("\n");
30
+ }
31
+ function blockingReason(results) {
32
+ const parts = results.filter((result) => result.verdict === "ask" || result.verdict === "deny").flatMap((result) => [result.reason, result.additionalContext]).filter((value) => value.length > 0);
33
+ return [...new Set(parts)].join("\n\n") || "[primitive] conflict detected (no detail available)";
34
+ }
35
+ function advisoryNote(aggregate, results) {
36
+ const details = results.flatMap((result) => [
37
+ ...aggregate === "warn" ? [result.reason] : [],
38
+ result.additionalContext
39
+ ]);
40
+ if (anyUnverified(results)) details.push(unverifiedNote(results));
41
+ return [...new Set(details.filter((value) => value.length > 0))].join("\n\n");
42
+ }
43
+ function buildHookOutput(aggregate, results) {
44
+ if (aggregate === "deny" || aggregate === "ask") {
45
+ return {
46
+ hookSpecificOutput: {
47
+ hookEventName: "PreToolUse",
48
+ permissionDecision: aggregate,
49
+ permissionDecisionReason: blockingReason(results)
50
+ }
51
+ };
52
+ }
53
+ const notes = advisoryNote(aggregate, results);
54
+ if (notes.length > 0) {
55
+ return {
56
+ systemMessage: notes,
57
+ hookSpecificOutput: {
58
+ hookEventName: "PreToolUse",
59
+ permissionDecision: "allow",
60
+ additionalContext: notes
61
+ }
62
+ };
63
+ }
64
+ return {
65
+ hookSpecificOutput: {
66
+ hookEventName: "PreToolUse",
67
+ permissionDecision: "allow"
68
+ }
69
+ };
70
+ }
71
+ function buildCodexOutput(aggregate, results) {
72
+ if (aggregate === "deny" || aggregate === "ask") {
73
+ return {
74
+ hookSpecificOutput: {
75
+ hookEventName: "PreToolUse",
76
+ permissionDecision: "deny",
77
+ permissionDecisionReason: blockingReason(results)
78
+ }
79
+ };
80
+ }
81
+ const notes = advisoryNote(aggregate, results);
82
+ return notes ? {
83
+ systemMessage: notes,
84
+ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: notes }
85
+ } : {};
86
+ }
87
+ function failOpenOutput() {
88
+ return {
89
+ hookSpecificOutput: {
90
+ hookEventName: "PreToolUse",
91
+ permissionDecision: "allow"
92
+ }
93
+ };
94
+ }
95
+ function failOpenCodex() {
96
+ return {};
97
+ }
98
+ function buildHermesOutput(aggregate, results) {
99
+ if (aggregate !== "deny" && aggregate !== "ask") {
100
+ return {};
101
+ }
102
+ const reason = results.filter((r) => r.verdict === "deny" || r.verdict === "ask").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n");
103
+ const directive = results.map((r) => r.additionalContext).filter((s) => s.length > 0 && s !== reason).join("\n");
104
+ const message = [reason, directive].filter((s) => s.length > 0).join("\n\n") || "[primitive] conflict detected (no detail available)";
105
+ return { action: "block", message };
106
+ }
107
+ function failOpenHermes() {
108
+ return {};
109
+ }
110
+ var SUPPORTED_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
111
+ var APPLY_PATCH_FILE_RE = /^\*\*\* (?:Update|Add|Delete) File: (?<path>.+)$/;
112
+ var MOVE_FILE_RE = /^\*\*\* Move File:\s*(?<src>.+?)\s*->\s*(?<dst>.+?)\s*$/;
113
+ var MOVE_TO_RE = /^\*\*\* Move to: (?<dst>.+)$/;
114
+ var LINE_SPLIT_RE = /\r?\n/;
115
+ function parseApplyPatchTargets(command) {
116
+ const paths = /* @__PURE__ */ new Set();
117
+ let activeUpdate = false;
118
+ let complete = true;
119
+ for (const line of command.split(LINE_SPLIT_RE)) {
120
+ const path = APPLY_PATCH_FILE_RE.exec(line)?.groups?.path;
121
+ if (path) {
122
+ paths.add(path);
123
+ activeUpdate = line.startsWith("*** Update File:");
124
+ continue;
125
+ }
126
+ const moveTo = MOVE_TO_RE.exec(line)?.groups?.dst;
127
+ if (moveTo) {
128
+ if (activeUpdate) paths.add(moveTo);
129
+ else complete = false;
130
+ activeUpdate = false;
131
+ continue;
132
+ }
133
+ const move = MOVE_FILE_RE.exec(line)?.groups;
134
+ if (move?.src) {
135
+ paths.add(move.src);
136
+ if (move.dst) {
137
+ paths.add(move.dst);
138
+ }
139
+ activeUpdate = false;
140
+ continue;
141
+ }
142
+ if (line === "*** Begin Patch" || line === "*** End Patch") activeUpdate = false;
143
+ else if (line.startsWith("*** ")) complete = false;
144
+ }
145
+ return { paths: [...paths], complete: complete && paths.size > 0 };
146
+ }
147
+ function extractCodexFileTargets(toolName, toolInput) {
148
+ if (toolName !== "apply_patch") {
149
+ return null;
150
+ }
151
+ if (!toolInput || typeof toolInput !== "object") {
152
+ return { paths: [], complete: false };
153
+ }
154
+ const command = toolInput.command;
155
+ return typeof command === "string" ? parseApplyPatchTargets(command) : { paths: [], complete: false };
156
+ }
157
+ var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
158
+ function extractHermesFileTargets(toolName, toolInput) {
159
+ if (!HERMES_EDITING_TOOLS.has(toolName)) {
160
+ return null;
161
+ }
162
+ if (!toolInput || typeof toolInput !== "object") {
163
+ return { paths: [], complete: false };
164
+ }
165
+ const input = toolInput;
166
+ if (toolName === "patch" && input.mode === "patch") {
167
+ return typeof input.patch === "string" ? parseApplyPatchTargets(input.patch) : { paths: [], complete: false };
168
+ }
169
+ const paths = typeof input.path === "string" && input.path.length > 0 ? [input.path] : [];
170
+ const mode = input.mode;
171
+ return { paths, complete: paths.length > 0 && (mode === void 0 || mode === "replace") };
172
+ }
173
+ function extractFileTargets(toolName, toolInput, agent = "claude_code") {
174
+ if (agent === "codex") {
175
+ return extractCodexFileTargets(toolName, toolInput);
176
+ }
177
+ if (agent === "hermes") {
178
+ return extractHermesFileTargets(toolName, toolInput);
179
+ }
180
+ if (!SUPPORTED_TOOLS.has(toolName)) {
181
+ return null;
182
+ }
183
+ if (!toolInput || typeof toolInput !== "object") {
184
+ return { paths: [], complete: false };
185
+ }
186
+ const input = toolInput;
187
+ if (toolName === "NotebookEdit") {
188
+ const paths2 = typeof input.notebook_path === "string" && input.notebook_path.length > 0 ? [input.notebook_path] : [];
189
+ return { paths: paths2, complete: paths2.length > 0 };
190
+ }
191
+ const paths = typeof input.file_path === "string" && input.file_path.length > 0 ? [input.file_path] : [];
192
+ return { paths, complete: paths.length > 0 };
193
+ }
194
+ function readHookMode(env) {
195
+ if (env.PRIM_BYPASS === "1" || env.PRIM_BYPASS === "true") {
196
+ return "off";
197
+ }
198
+ const mode = env.PRIM_HOOK_MODE;
199
+ if (mode === "off" || mode === "warn") {
200
+ return mode;
201
+ }
202
+ return "block";
203
+ }
204
+ function demoteForMode(verdict, mode) {
205
+ if (mode === "off") {
206
+ return "allow";
207
+ }
208
+ if (mode === "warn" && (verdict === "ask" || verdict === "deny")) {
209
+ return "warn";
210
+ }
211
+ return verdict;
212
+ }
213
+
214
+ // src/hooks/preflight-v3.ts
215
+ import { Buffer } from "buffer";
216
+
217
+ // src/hooks/shell-targets.ts
218
+ import { parse } from "unbash";
219
+ var OUTPUT_REDIRECTS = /* @__PURE__ */ new Set([">", ">>", ">|", "&>", "&>>"]);
220
+ var READ_ONLY_COMMANDS = new Set(": cat echo false printf pwd test true [".split(" "));
221
+ var SINKS = /* @__PURE__ */ new Set(["/dev/null", "/dev/stdout", "/dev/stderr"]);
222
+ var STATIC_PARTS = /* @__PURE__ */ new Set(["Literal", "SingleQuoted", "AnsiCQuoted"]);
223
+ function staticPart(part) {
224
+ return STATIC_PARTS.has(part.type) || part.type === "DoubleQuoted" && part.parts.every(staticPart);
225
+ }
226
+ function staticWord(word) {
227
+ if (!word || !word.value || word.value.includes("\0") || word.value.includes("\n")) return;
228
+ if (word.parts ? !word.parts.every(staticPart) : word.value.startsWith("~") || /[*?\[$`]/.test(word.text))
229
+ return;
230
+ return word.value;
231
+ }
232
+ function unknown(state) {
233
+ state.complete = false;
234
+ state.mutation = true;
235
+ }
236
+ function recordTarget(word, state) {
237
+ const path = staticWord(word);
238
+ if (path === "-" || path && (SINKS.has(path) || path.startsWith("/dev/fd/"))) return;
239
+ state.mutation = true;
240
+ if (path) state.paths.add(path);
241
+ else state.complete = false;
242
+ }
243
+ function visitRedirect(redirect, state) {
244
+ const target = staticWord(redirect.target);
245
+ if (redirect.operator === ">&" && (target === "-" || /^\d+$/.test(target ?? ""))) return;
246
+ if (OUTPUT_REDIRECTS.has(redirect.operator) || redirect.operator === ">&")
247
+ recordTarget(redirect.target, state);
248
+ else unknown(state);
249
+ }
250
+ function operands(words, short = "", long = /* @__PURE__ */ new Set()) {
251
+ const result = [];
252
+ let optionsEnded = false;
253
+ for (const word of words) {
254
+ const value = staticWord(word);
255
+ if (value === void 0) return;
256
+ if (optionsEnded || value === "-" || !value.startsWith("-")) result.push(value);
257
+ else if (value === "--") optionsEnded = true;
258
+ else if (value.startsWith("--") ? !long.has(value) : [...value.slice(1)].some((c) => !short.includes(c)))
259
+ return;
260
+ }
261
+ return result;
262
+ }
263
+ function visitCommand(command, state) {
264
+ for (const redirect of command.redirects) visitRedirect(redirect, state);
265
+ const name = staticWord(command.name);
266
+ if (!name || name.includes("/") || command.suffix.some((word) => !staticWord(word))) {
267
+ unknown(state);
268
+ } else if (!READ_ONLY_COMMANDS.has(name)) {
269
+ state.mutation = true;
270
+ let targets;
271
+ if (name === "tee") {
272
+ targets = operands(command.suffix, "ai", /* @__PURE__ */ new Set(["--append", "--ignore-interrupts"]));
273
+ } else if (name === "touch") {
274
+ targets = operands(command.suffix);
275
+ } else if (name === "rm") {
276
+ targets = operands(
277
+ command.suffix,
278
+ "fivI",
279
+ /* @__PURE__ */ new Set(["--force", "--interactive", "--interactive=once", "--verbose"])
280
+ );
281
+ } else if (name === "cp" || name === "mv") {
282
+ const pair = operands(command.suffix);
283
+ if (pair?.length === 2) targets = name === "cp" ? [pair[1]] : pair;
284
+ }
285
+ if (!targets?.length) state.complete = false;
286
+ else for (const target of targets) state.paths.add(target);
287
+ }
288
+ visitNested([command.name, command.prefix, command.suffix, command.redirects], state);
289
+ }
290
+ var CONTAINERS = /* @__PURE__ */ new Set(["Script", "Pipeline", "AndOr", "CompoundList"]);
291
+ function visitNested(value, state) {
292
+ if (Array.isArray(value)) {
293
+ for (const child of value) visitNested(child, state);
294
+ } else if (value && typeof value === "object") {
295
+ const child = value;
296
+ if (child.type === "Command") {
297
+ visitCommand(child, state);
298
+ } else if (child.type === "Statement") {
299
+ visitStatement(child, state);
300
+ } else if ("parts" in child) {
301
+ for (const part of value.parts ?? []) visitNested(part, state);
302
+ } else {
303
+ const node = typeof child.type === "string" && "pos" in child && "end" in child;
304
+ if (node && !CONTAINERS.has(child.type)) unknown(state);
305
+ if (child.type === "Function") return;
306
+ for (const nested of Object.values(child)) visitNested(nested, state);
307
+ }
308
+ }
309
+ }
310
+ function visitStatement(statement, state) {
311
+ if (statement.background) unknown(state);
312
+ for (const redirect of statement.redirects) visitRedirect(redirect, state);
313
+ visitNested([statement.redirects, statement.command], state);
314
+ }
315
+ function analyzeShellTargets(source) {
316
+ if (!source.trim())
317
+ return { paths: [], coverage: "complete", mutation: "none" };
318
+ try {
319
+ const script = parse(source);
320
+ const state = { paths: /* @__PURE__ */ new Set(), complete: !script.errors?.length, mutation: false };
321
+ for (const statement of script.commands) visitStatement(statement, state);
322
+ if (script.errors?.length) state.mutation = true;
323
+ return {
324
+ paths: [...state.paths],
325
+ coverage: state.complete ? "complete" : "unverified",
326
+ mutation: state.mutation ? "present" : "none"
327
+ };
328
+ } catch {
329
+ return { paths: [], coverage: "unverified", mutation: "present" };
330
+ }
331
+ }
332
+
333
+ // src/hooks/preflight-v3.ts
334
+ var PREFLIGHT_PROTOCOL_VERSION = 3;
335
+ var PREFLIGHT_TIMEOUT_MS = 4500;
336
+ var MAX_PREFLIGHT_PATHS = 32;
337
+ var MAX_PROPOSAL_BYTES = 6144;
338
+ function resolvePreflightTargets(args) {
339
+ let rawPaths;
340
+ let coverage = "complete";
341
+ let mutation = "present";
342
+ if (args.agent === "claude_code" && args.toolName === "Bash") {
343
+ const command = args.toolInput && typeof args.toolInput === "object" ? args.toolInput.command : void 0;
344
+ if (typeof command !== "string") {
345
+ return { paths: [], coverage: "unverified", mutation: "present" };
346
+ }
347
+ const shell = analyzeShellTargets(command);
348
+ rawPaths = shell.paths;
349
+ coverage = shell.coverage;
350
+ mutation = shell.mutation;
351
+ } else {
352
+ const extracted = extractFileTargets(args.toolName, args.toolInput, args.agent);
353
+ if (!extracted) return { paths: [], coverage: "complete", mutation: "none" };
354
+ rawPaths = extracted.paths;
355
+ if (!extracted.complete) coverage = "unverified";
356
+ }
357
+ const root = canonicalGitRoot(args.cwd);
358
+ const paths = /* @__PURE__ */ new Set();
359
+ for (const rawPath of rawPaths) {
360
+ const canonical = canonicalRepositoryPath(rawPath, args.cwd, root);
361
+ if (canonical) paths.add(canonical);
362
+ else coverage = "unverified";
363
+ }
364
+ const bounded = [...paths];
365
+ if (bounded.length > MAX_PREFLIGHT_PATHS) coverage = "unverified";
366
+ return { paths: bounded.slice(0, MAX_PREFLIGHT_PATHS), coverage, mutation };
367
+ }
368
+ function truncateUtf8(value, maxBytes) {
369
+ const encoded = Buffer.from(value);
370
+ if (encoded.byteLength <= maxBytes) return value;
371
+ return encoded.subarray(0, maxBytes).toString("utf8").replace(/\uFFFD$/u, "");
372
+ }
373
+ function proposalFor(toolInput) {
374
+ const redacted = scrub(toolInput, DEFAULT_RULES);
375
+ return truncateUtf8(JSON.stringify(redacted) ?? "", MAX_PROPOSAL_BYTES);
376
+ }
377
+ function parsePreflightResponse(value) {
378
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
379
+ const response = value;
380
+ const verdicts = /* @__PURE__ */ new Set(["allow", "warn", "ask", "block", "unavailable"]);
381
+ 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)) {
382
+ return null;
383
+ }
384
+ return response;
385
+ }
386
+ async function requestPreflight(request, signal = AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS)) {
387
+ const response = await getClient().post("/api/cli/decisions/conflict-check", request, {
388
+ signal,
389
+ quietRefresh: true
390
+ });
391
+ return parsePreflightResponse(response);
392
+ }
393
+ function resultForPreflight(response) {
394
+ const verdict = response.verdict === "block" ? "deny" : response.verdict;
395
+ const safe = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
396
+ const directives = response.conflicts.flatMap((value) => {
397
+ if (!value || typeof value !== "object") return [];
398
+ const { decisionId, shortId } = value;
399
+ const id = typeof shortId === "string" && safe.test(shortId) ? `dec_${shortId}` : typeof decisionId === "string" && safe.test(decisionId) ? decisionId : void 0;
400
+ return id ? [`To reconcile, run: prim reconcile ${id}`] : [];
401
+ });
402
+ const reason = [response.message, ...new Set(directives)].filter(Boolean).join("\n");
403
+ return {
404
+ verdict,
405
+ conflicts: response.conflicts,
406
+ reason,
407
+ additionalContext: ["warn", "ask", "block"].includes(response.verdict) ? reason : "",
408
+ truncated: false,
409
+ unavailable: response.verdict === "unavailable" ? response.message || response.reasonCode : void 0
410
+ };
411
+ }
412
+ function unverifiedResult(message) {
413
+ return {
414
+ verdict: "unavailable",
415
+ conflicts: [],
416
+ reason: "",
417
+ additionalContext: "",
418
+ truncated: false,
419
+ unavailable: message
420
+ };
421
+ }
422
+
423
+ export {
424
+ buildHookOutput,
425
+ buildCodexOutput,
426
+ failOpenOutput,
427
+ failOpenCodex,
428
+ buildHermesOutput,
429
+ failOpenHermes,
430
+ readHookMode,
431
+ demoteForMode,
432
+ PREFLIGHT_PROTOCOL_VERSION,
433
+ resolvePreflightTargets,
434
+ proposalFor,
435
+ requestPreflight,
436
+ resultForPreflight,
437
+ unverifiedResult
438
+ };
@@ -1486,13 +1486,6 @@ async function runIngestionLoop() {
1486
1486
  scheduleIngestion(delay);
1487
1487
  }
1488
1488
  }
1489
- async function handleConflictCheck(params) {
1490
- assertCallerEnvMatches(params.callerEnv, getSiteUrl());
1491
- if (typeof params.file !== "string") {
1492
- throw new Error("conflict_check requires `file: string`");
1493
- }
1494
- return await client.post("/api/cli/decisions/conflict-check", { file: params.file });
1495
- }
1496
1489
  function pathParam(params) {
1497
1490
  if (typeof params.path !== "string" || !params.path.startsWith("/api/cli/")) {
1498
1491
  throw new Error("proxy request requires `path: string` under /api/cli/");
@@ -1555,10 +1548,6 @@ async function dispatchRequest(req) {
1555
1548
  const id = req.id;
1556
1549
  try {
1557
1550
  switch (req.method) {
1558
- case "conflict_check": {
1559
- const result = await handleConflictCheck(req.params ?? {});
1560
- return { id, ok: true, result };
1561
- }
1562
1551
  case "decisions_recent": {
1563
1552
  const result = await proxyGet(req.params ?? {}, "/api/cli/decisions/recent");
1564
1553
  return { id, ok: true, result };
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  toCommitMove
4
- } from "../chunk-F5QCFBJ6.js";
4
+ } from "../chunk-YDBZTFW5.js";
5
5
  import {
6
6
  appendMove,
7
7
  resolveOrg
8
- } from "../chunk-BOTKPLTI.js";
8
+ } from "../chunk-S4LFRVGD.js";
9
9
  import {
10
10
  isRepoActiveForCapture
11
- } from "../chunk-R5ZJRSLV.js";
12
- import "../chunk-DWDEQRWN.js";
11
+ } from "../chunk-LDQZASIF.js";
12
+ import "../chunk-I3HIPAIR.js";
13
13
 
14
14
  // src/hooks/post-commit.ts
15
15
  import { execSync, spawn } from "child_process";
@@ -5,27 +5,32 @@ import {
5
5
  requireDurableIngestAcknowledgement
6
6
  } from "../chunk-QY75BQMF.js";
7
7
  import {
8
- scrubFromCwd
9
- } from "../chunk-6LAQVM26.js";
8
+ resolvePreflightTargets
9
+ } from "../chunk-YWJHWGXH.js";
10
10
  import {
11
+ postToolInvocationId,
11
12
  toMove
12
- } from "../chunk-F5QCFBJ6.js";
13
+ } from "../chunk-YDBZTFW5.js";
13
14
  import {
14
15
  appendMove,
15
16
  resolveOrg
16
- } from "../chunk-BOTKPLTI.js";
17
+ } from "../chunk-S4LFRVGD.js";
18
+ import {
19
+ scrubFromCwd
20
+ } from "../chunk-GUXBUBQ7.js";
17
21
  import {
18
22
  getOrCreateWorkspaceId
19
23
  } from "../chunk-IMAIBPUC.js";
20
24
  import {
21
25
  warmBinCache
22
- } from "../chunk-HSZPTVKU.js";
26
+ } from "../chunk-YIMUIPMD.js";
23
27
  import {
24
- isRepoActiveForCapture
25
- } from "../chunk-R5ZJRSLV.js";
28
+ isRepoActive,
29
+ repoSyncId
30
+ } from "../chunk-LDQZASIF.js";
26
31
  import {
27
32
  getClient
28
- } from "../chunk-DWDEQRWN.js";
33
+ } from "../chunk-I3HIPAIR.js";
29
34
  import {
30
35
  normalizeEnvelope,
31
36
  parseAgent
@@ -66,7 +71,7 @@ function isVerdictFooterContext(value) {
66
71
 
67
72
  // src/hooks/post-tool-use.ts
68
73
  var STDIN_TIMEOUT_MS = 1e3;
69
- var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
74
+ var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]);
70
75
  var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch"]);
71
76
  var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
72
77
  function editingToolsFor(agent) {
@@ -146,13 +151,26 @@ async function main() {
146
151
  return;
147
152
  }
148
153
  const cwd = parsed.cwd ?? process.cwd();
149
- if (!isRepoActiveForCapture(cwd)) {
154
+ if (!isRepoActive(cwd) || !repoSyncId(cwd)) {
150
155
  emit();
151
156
  return;
152
157
  }
158
+ if (agent === "claude_code") {
159
+ const targets = resolvePreflightTargets({
160
+ toolName,
161
+ toolInput: envelope.tool_input,
162
+ agent,
163
+ cwd
164
+ });
165
+ if (targets.mutation === "none") {
166
+ emit();
167
+ return;
168
+ }
169
+ }
153
170
  const identity = getOrCreateWorkspaceId(cwd);
154
171
  const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
155
- const base = toMove(parsed, resolveCliVersion(), agent, workspaceId);
172
+ const invocationId = postToolInvocationId(parsed, agent);
173
+ const base = toMove(parsed, resolveCliVersion(), agent, workspaceId, invocationId);
156
174
  const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
157
175
  const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
158
176
  try {
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-VDLAAUWV.js";
6
- import "../chunk-DWDEQRWN.js";
5
+ } from "../chunk-YITJ2H3B.js";
6
+ import "../chunk-I3HIPAIR.js";
7
7
  import "../chunk-UTKQTZHL.js";
8
8
 
9
9
  // src/hooks/pre-commit.ts