@primitive.ai/prim 0.1.0-alpha.93 → 0.1.0-alpha.96

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 (41) hide show
  1. package/README.md +57 -12
  2. package/SKILL.md +5 -2
  3. package/dist/{chunk-UKUAAAXE.js → chunk-27ADT6FQ.js} +2 -2
  4. package/dist/{chunk-OTHKG76J.js → chunk-35FMRWJH.js} +1 -1
  5. package/dist/{chunk-DXJQR4QI.js → chunk-4GHANPWQ.js} +2 -2
  6. package/dist/{chunk-DCJN6LZH.js → chunk-7KABYYWG.js} +747 -430
  7. package/dist/{chunk-LFFLHSQO.js → chunk-CU7RW7XU.js} +63 -6
  8. package/dist/{chunk-H4UNYFUC.js → chunk-DHOQGHKQ.js} +184 -1
  9. package/dist/{chunk-2GP5UFV7.js → chunk-DWRSEC34.js} +5 -3
  10. package/dist/{chunk-36TWVNBE.js → chunk-FVDBULGZ.js} +9 -11
  11. package/dist/{chunk-G2VMA2QO.js → chunk-LY6NJ7KL.js} +6 -4
  12. package/dist/{chunk-ZB4F75SZ.js → chunk-MXVCP33E.js} +5 -1
  13. package/dist/{chunk-TYAHC25F.js → chunk-QCBB5HFX.js} +1 -1
  14. package/dist/{chunk-C3VAA5CG.js → chunk-RK4ZS3HG.js} +2 -2
  15. package/dist/{chunk-ZU563PQI.js → chunk-ROLYLXJS.js} +2 -2
  16. package/dist/{chunk-OH3EZXYX.js → chunk-UO2U6IKF.js} +1 -1
  17. package/dist/{chunk-K7D2EYBS.js → chunk-URLLSALP.js} +2 -2
  18. package/dist/chunk-XEDJ5N3M.js +177 -0
  19. package/dist/{chunk-X64HTHUF.js → chunk-Y5W4TZM7.js} +202 -10
  20. package/dist/{chunk-ICM2XCXS.js → chunk-YYE2NLWX.js} +2 -2
  21. package/dist/{chunk-DSWT6BCC.js → chunk-Z6NPNLOT.js} +825 -665
  22. package/dist/daemon/server.js +836 -540
  23. package/dist/hook-runtime/daemon/server.js +836 -540
  24. package/dist/hook-runtime/hooks/post-commit.js +978 -566
  25. package/dist/hook-runtime/hooks/post-rewrite.js +836 -540
  26. package/dist/hook-runtime/hooks/post-tool-use.js +1215 -796
  27. package/dist/hook-runtime/hooks/pre-tool-use.js +2100 -1741
  28. package/dist/hook-runtime/hooks/prim-hook.js +1312 -861
  29. package/dist/hook-runtime/hooks/session-end.js +232 -134
  30. package/dist/hook-runtime/hooks/session-start.js +1799 -935
  31. package/dist/hook-runtime/index.js +3628 -2461
  32. package/dist/hooks/post-commit.js +14 -10
  33. package/dist/hooks/post-rewrite.js +10 -10
  34. package/dist/hooks/post-tool-use.js +20 -20
  35. package/dist/hooks/pre-commit.js +4 -4
  36. package/dist/hooks/pre-tool-use.js +29 -17
  37. package/dist/hooks/prim-hook.js +22 -18
  38. package/dist/hooks/session-end.js +11 -5
  39. package/dist/hooks/session-start.js +46 -28
  40. package/dist/index.js +623 -390
  41. package/package.json +1 -1
@@ -1,18 +1,32 @@
1
+ import {
2
+ getOrCreateWorkspaceId
3
+ } from "./chunk-EZXEWFDG.js";
1
4
  import {
2
5
  analyzeShellTargets,
3
6
  extractFileTargets
4
- } from "./chunk-ZB4F75SZ.js";
7
+ } from "./chunk-MXVCP33E.js";
8
+ import {
9
+ scrubEnvironmentPaths
10
+ } from "./chunk-URLLSALP.js";
5
11
  import {
6
- canonicalRepositoryPath
7
- } from "./chunk-H4UNYFUC.js";
12
+ cachedCollectScopeAdmits
13
+ } from "./chunk-Z6NPNLOT.js";
14
+ import {
15
+ canonicalRepositoryPath,
16
+ currentBranch,
17
+ isRepoActiveForCapture,
18
+ relatedWorktreeTarget,
19
+ repoSyncId
20
+ } from "./chunk-DHOQGHKQ.js";
8
21
 
9
22
  // src/hooks/file-refs.ts
10
23
  var MAX_PREFLIGHT_FILE_TARGETS = 25;
11
24
  function primitiveMetadataForResolution(resolution) {
12
- const shouldAttach = resolution.fileRefs.length > 0 || resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation !== void 0;
25
+ const shouldAttach = resolution.fileRefs.length > 0 || (resolution.targetCheckouts?.length ?? 0) > 0 || resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation !== void 0;
13
26
  if (!shouldAttach) return;
14
27
  return {
15
28
  fileRefs: [...resolution.fileRefs],
29
+ ...resolution.targetCheckouts ? { targetCheckouts: resolution.targetCheckouts } : {},
16
30
  ...resolution.rejected.length > 0 || resolution.targetsIncomplete ? { fileRefsIncomplete: true } : {},
17
31
  ...resolution.targetsTruncated ? { fileRefsTruncated: true } : {},
18
32
  ...resolution.shellMutation !== void 0 ? { shellMutation: resolution.shellMutation } : {}
@@ -37,13 +51,55 @@ function resolveHookFileRefs(args) {
37
51
  const targetsTruncated = uniqueRawPaths.length > MAX_PREFLIGHT_FILE_TARGETS;
38
52
  const fileRefs = /* @__PURE__ */ new Set();
39
53
  const rejected = [];
54
+ const targets = /* @__PURE__ */ new Map();
40
55
  for (const path of uniqueRawPaths.slice(0, MAX_PREFLIGHT_FILE_TARGETS)) {
41
56
  const canonical = canonicalRepositoryPath(path, args.repository, args.cwd);
42
57
  if (canonical.ok) fileRefs.add(canonical.file);
43
- else rejected.push({ path, reason: canonical.reason });
58
+ else {
59
+ const target = args.captureTargets && canonical.reason === "outside_repository" ? relatedWorktreeTarget(path, args.cwd, args.repository) : null;
60
+ const resolved = target ? canonicalRepositoryPath(path, target, args.cwd) : null;
61
+ if (target && resolved?.ok && isRepoActiveForCapture(target.repoRoot) && repoSyncId(target.repoRoot) === args.repository.repoSyncId && args.repository.repoSyncId && cachedCollectScopeAdmits(target.repoRoot, {
62
+ repository: target.repoFullName,
63
+ branch: currentBranch(target.repoRoot),
64
+ agent: args.agent,
65
+ paths: [resolved.file],
66
+ pathsComplete: true
67
+ })) {
68
+ const group = targets.get(target.repoRoot) ?? { repository: target, fileRefs: [] };
69
+ group.fileRefs.push(resolved.file);
70
+ targets.set(target.repoRoot, group);
71
+ } else rejected.push({ path, reason: canonical.reason });
72
+ }
73
+ }
74
+ const targetCheckouts = [];
75
+ if (targets.size > 0) {
76
+ if (fileRefs.size > 0)
77
+ targets.set(args.repository.repoRoot, {
78
+ repository: args.repository,
79
+ fileRefs: [...fileRefs]
80
+ });
81
+ for (const { repository, fileRefs: paths } of targets.values()) {
82
+ const identity = getOrCreateWorkspaceId(repository.repoRoot);
83
+ const binding = repoSyncId(repository.repoRoot);
84
+ if (identity.status !== "ready" || !binding) {
85
+ rejected.push({ path: repository.repoRoot, reason: "invalid_path" });
86
+ continue;
87
+ }
88
+ const root = scrubEnvironmentPaths({
89
+ cwd: repository.repoRoot,
90
+ gitRoot: repository.repoRoot
91
+ }).gitRoot;
92
+ targetCheckouts.push({
93
+ gitRoot: root,
94
+ workspaceId: identity.workspaceId,
95
+ repoSyncId: binding,
96
+ fileRefs: paths
97
+ });
98
+ }
44
99
  }
45
100
  return {
46
101
  fileRefs: [...fileRefs],
102
+ ...targetCheckouts.length > 0 ? { targetCheckouts } : {},
47
103
  rejected,
48
104
  targetsIncomplete: nativeTargets?.complete === false,
49
105
  targetsTruncated,
@@ -57,7 +113,8 @@ function enrichHookPayloadWithFileRefs(args) {
57
113
  toolInput: args.parsed.tool_input,
58
114
  agent: args.agent,
59
115
  cwd: args.cwd,
60
- repository: args.repository
116
+ repository: args.repository,
117
+ captureTargets: true
61
118
  });
62
119
  const primitive = primitiveMetadataForResolution(resolution);
63
120
  if (!primitive) return { parsed: args.parsed, resolution };
@@ -18,6 +18,24 @@ function gitToplevel(cwd) {
18
18
  return null;
19
19
  }
20
20
  }
21
+ function mainWorktreeRoot(cwd) {
22
+ try {
23
+ const [gitDir, commonDir] = execFileSync(
24
+ "git",
25
+ ["rev-parse", "--git-dir", "--git-common-dir"],
26
+ {
27
+ cwd,
28
+ encoding: "utf-8",
29
+ stdio: ["ignore", "pipe", "ignore"],
30
+ timeout: GIT_TIMEOUT_MS
31
+ }
32
+ ).trim().split("\n").map((value) => resolve(cwd ?? process.cwd(), value));
33
+ if (gitDir === void 0 || commonDir === void 0 || gitDir === commonDir) return null;
34
+ return basename(commonDir) === ".git" ? dirname(commonDir) : null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
21
39
  function gitValue(cwd, args) {
22
40
  try {
23
41
  const value = execFileSync("git", args, {
@@ -56,6 +74,31 @@ function canonicalGitRoot(cwd) {
56
74
  return null;
57
75
  }
58
76
  }
77
+ function relatedWorktreeTarget(path, cwd, origin) {
78
+ let parent = dirname(resolve(cwd, path));
79
+ while (true) {
80
+ try {
81
+ realpathSync.native(parent);
82
+ break;
83
+ } catch {
84
+ const next = dirname(parent);
85
+ if (next === parent) return null;
86
+ parent = next;
87
+ }
88
+ }
89
+ const target = resolveRepositoryContext(parent);
90
+ if (!target || !target.repoKey || target.repoKey !== origin.repoKey) return null;
91
+ const common = (root) => {
92
+ const value = gitValue(root, ["rev-parse", "--git-common-dir"]);
93
+ try {
94
+ return value ? realpathSync.native(resolve(root, value)) : void 0;
95
+ } catch {
96
+ return void 0;
97
+ }
98
+ };
99
+ const originCommon = common(origin.repoRoot);
100
+ return originCommon && originCommon === common(target.repoRoot) ? target : null;
101
+ }
59
102
  function normalizeOriginRemote(remote) {
60
103
  const value = remote.trim();
61
104
  if (!value) return void 0;
@@ -186,11 +229,151 @@ function canonicalRepositoryPath(filePath, contextOrCwd, baseDirOrRoot) {
186
229
  );
187
230
  }
188
231
 
232
+ // src/lib/activation.ts
233
+ import { execFileSync as execFileSync2 } from "child_process";
234
+ import { existsSync, readFileSync } from "fs";
235
+ import { homedir } from "os";
236
+ import { join } from "path";
237
+ var PRIM_ACTIVE_KEY = "prim.active";
238
+ var PRIM_REPO_SYNC_ID_KEY = "prim.repoSyncId";
239
+ var PRIM_REPO_SYNC_REPOSITORY_KEY = "prim.repoSyncRepository";
240
+ var PRIM_REPO_BINDING_STATE_KEY = "prim.repoBindingState";
241
+ var GIT_TIMEOUT_MS2 = 1e3;
242
+ var REPO_SYNC_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
243
+ var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json", ".cursor/hooks.json"];
244
+ var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
245
+ function repoActiveFlag(cwd) {
246
+ try {
247
+ const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
248
+ cwd,
249
+ encoding: "utf-8",
250
+ stdio: ["ignore", "pipe", "ignore"],
251
+ timeout: GIT_TIMEOUT_MS2
252
+ }).trim();
253
+ if (value === "true") return "true";
254
+ if (value === "false") return "false";
255
+ return void 0;
256
+ } catch {
257
+ return void 0;
258
+ }
259
+ }
260
+ function isRepoActive(cwd) {
261
+ return repoActiveFlag(cwd) === "true";
262
+ }
263
+ function setRepoActive(cwd, active) {
264
+ execFileSync2("git", ["config", "--local", PRIM_ACTIVE_KEY, active ? "true" : "false"], {
265
+ cwd,
266
+ stdio: ["ignore", "ignore", "pipe"],
267
+ timeout: GIT_TIMEOUT_MS2
268
+ });
269
+ }
270
+ function isValidRepoSyncId(value) {
271
+ return typeof value === "string" && REPO_SYNC_ID_RE.test(value);
272
+ }
273
+ function localGitConfigValue(cwd, key) {
274
+ try {
275
+ const value = execFileSync2("git", ["config", "--local", "--get", key], {
276
+ cwd,
277
+ encoding: "utf-8",
278
+ stdio: ["ignore", "pipe", "ignore"],
279
+ timeout: GIT_TIMEOUT_MS2
280
+ }).trim();
281
+ return value || void 0;
282
+ } catch {
283
+ return void 0;
284
+ }
285
+ }
286
+ function setLocalGitConfigValue(cwd, key, value) {
287
+ execFileSync2("git", ["config", "--local", key, value], {
288
+ cwd,
289
+ stdio: ["ignore", "ignore", "pipe"],
290
+ timeout: GIT_TIMEOUT_MS2
291
+ });
292
+ }
293
+ function repoSyncId(cwd) {
294
+ const value = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
295
+ if (!isValidRepoSyncId(value)) return void 0;
296
+ const repositoryFullName = localGitConfigValue(cwd, PRIM_REPO_SYNC_REPOSITORY_KEY);
297
+ return repositoryFullName === githubRepositoryFullName(cwd) ? value : void 0;
298
+ }
299
+ function setRepoSyncId(cwd, value, repositoryFullName) {
300
+ if (!isValidRepoSyncId(value)) {
301
+ throw new Error("invalid repository binding returned by server");
302
+ }
303
+ execFileSync2("git", ["config", "--local", PRIM_REPO_SYNC_ID_KEY, value], {
304
+ cwd,
305
+ stdio: ["ignore", "ignore", "pipe"],
306
+ timeout: GIT_TIMEOUT_MS2
307
+ });
308
+ execFileSync2("git", ["config", "--local", PRIM_REPO_SYNC_REPOSITORY_KEY, repositoryFullName], {
309
+ cwd,
310
+ stdio: ["ignore", "ignore", "pipe"],
311
+ timeout: GIT_TIMEOUT_MS2
312
+ });
313
+ }
314
+ function setRepositoryBindingState(cwd, state) {
315
+ if (state !== "connected" && state !== "unbound" && state !== "invalid") {
316
+ throw new Error("invalid repository binding state");
317
+ }
318
+ execFileSync2("git", ["config", "--local", PRIM_REPO_BINDING_STATE_KEY, state], {
319
+ cwd,
320
+ stdio: ["ignore", "ignore", "pipe"],
321
+ timeout: GIT_TIMEOUT_MS2
322
+ });
323
+ }
324
+ function repositoryBindingState(cwd) {
325
+ const rawId = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
326
+ const marker = localGitConfigValue(cwd, PRIM_REPO_BINDING_STATE_KEY);
327
+ if (rawId !== void 0 && !isValidRepoSyncId(rawId)) return "invalid";
328
+ if (marker === "unbound" || marker === "invalid") return marker;
329
+ if (marker === "connected") return repoSyncId(cwd) ? "connected" : "invalid";
330
+ if (marker !== void 0) return "invalid";
331
+ return rawId === void 0 ? "unbound" : repoSyncId(cwd) ? void 0 : "unbound";
332
+ }
333
+ function hasProjectPrimInstall(cwd) {
334
+ const root = gitToplevel(cwd);
335
+ if (root === null || root === homedir()) return false;
336
+ for (const rel of PROJECT_INSTALL_FILES) {
337
+ const path = join(root, rel);
338
+ if (existsSync(path)) {
339
+ try {
340
+ const content = readFileSync(path, "utf-8");
341
+ if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
342
+ } catch {
343
+ }
344
+ }
345
+ }
346
+ return false;
347
+ }
348
+ function isRepoActiveForCapture(cwd) {
349
+ const flag = repoActiveFlag(cwd);
350
+ if (flag === "true") return true;
351
+ if (flag === "false") return false;
352
+ return hasProjectPrimInstall(cwd);
353
+ }
354
+ function decisionIngestionStatus(cwd) {
355
+ return isRepoActiveForCapture(cwd) ? "enabled" : "disabled";
356
+ }
357
+
189
358
  export {
190
359
  gitToplevel,
360
+ mainWorktreeRoot,
191
361
  currentBranch,
192
362
  githubRepositoryFullName,
193
363
  canonicalGitRoot,
364
+ relatedWorktreeTarget,
194
365
  resolveRepositoryContext,
195
- canonicalRepositoryPath
366
+ canonicalRepositoryPath,
367
+ repoActiveFlag,
368
+ isRepoActive,
369
+ setRepoActive,
370
+ isValidRepoSyncId,
371
+ localGitConfigValue,
372
+ setLocalGitConfigValue,
373
+ repoSyncId,
374
+ setRepoSyncId,
375
+ setRepositoryBindingState,
376
+ repositoryBindingState,
377
+ isRepoActiveForCapture,
378
+ decisionIngestionStatus
196
379
  };
@@ -24,6 +24,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  mod
25
25
  ));
26
26
 
27
+ // src/lib/credentials.ts
28
+ import { createHash } from "crypto";
29
+ import { readFileSync } from "fs";
30
+ import { join as join2 } from "path";
31
+
27
32
  // src/lib/paths.ts
28
33
  import { homedir } from "os";
29
34
  import { isAbsolute, join, normalize } from "path";
@@ -353,9 +358,6 @@ async function pollWorkosDeviceAuthorization(configuration, authorization, optio
353
358
  }
354
359
 
355
360
  // src/lib/credentials.ts
356
- import { createHash } from "crypto";
357
- import { readFileSync } from "fs";
358
- import { join as join2 } from "path";
359
361
  var CONFIG_DIRECTORY = primConfigDirectory();
360
362
  var TOKEN_FILE_PATH = join2(CONFIG_DIRECTORY, "token");
361
363
  var REFRESH_TOKEN_PATH = join2(CONFIG_DIRECTORY, "refresh_token");
@@ -1,30 +1,28 @@
1
1
  import {
2
2
  formatStatusline
3
- } from "./chunk-ZU563PQI.js";
4
- import {
5
- decisionIngestionStatus,
6
- repositoryBindingState
7
- } from "./chunk-DSWT6BCC.js";
3
+ } from "./chunk-ROLYLXJS.js";
8
4
  import {
9
5
  getSiteUrl,
10
6
  isSessionEnded,
11
7
  terminalSafeText
12
- } from "./chunk-G2VMA2QO.js";
8
+ } from "./chunk-LY6NJ7KL.js";
13
9
  import {
14
10
  packageVersion
15
- } from "./chunk-X64HTHUF.js";
11
+ } from "./chunk-Y5W4TZM7.js";
16
12
  import {
17
- gitToplevel
18
- } from "./chunk-H4UNYFUC.js";
13
+ decisionIngestionStatus,
14
+ gitToplevel,
15
+ repositoryBindingState
16
+ } from "./chunk-DHOQGHKQ.js";
19
17
  import {
20
18
  daemonPrincipalsMatch,
21
19
  daemonRequest,
22
20
  resolveDaemonPrincipal
23
- } from "./chunk-OTHKG76J.js";
21
+ } from "./chunk-35FMRWJH.js";
24
22
  import {
25
23
  primConfigDirectory,
26
24
  withFileLock
27
- } from "./chunk-2GP5UFV7.js";
25
+ } from "./chunk-DWRSEC34.js";
28
26
 
29
27
  // src/hooks/codex-context.ts
30
28
  import { createHash, randomBytes as randomBytes2 } from "crypto";
@@ -19,7 +19,12 @@ import {
19
19
  resolveAuthCredential,
20
20
  withFileLock,
21
21
  workosConnectTokenEndpoint
22
- } from "./chunk-2GP5UFV7.js";
22
+ } from "./chunk-DWRSEC34.js";
23
+
24
+ // src/client.ts
25
+ import { createHash } from "crypto";
26
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
27
+ import { dirname } from "path";
23
28
 
24
29
  // src/lib/terminal-safe.ts
25
30
  function replaceIsolatedSurrogates(value) {
@@ -67,9 +72,6 @@ function isTerminalSafeText(value) {
67
72
  var stripControlChars = terminalSafeText;
68
73
 
69
74
  // src/client.ts
70
- import { createHash } from "crypto";
71
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
72
- import { dirname } from "path";
73
75
  var CONFIG_DIR_MODE = 448;
74
76
  var CREDENTIAL_FILE_MODE = 384;
75
77
  var REFRESH_THRESHOLD_MS = 6e4;
@@ -253,7 +253,7 @@ var COMMAND_OPTIONS = {
253
253
  longOptionalValues: set("--color")
254
254
  },
255
255
  grep: {
256
- shortFlags: "EFGHIPRTUVabcdhLlnqrsuvwxyZz",
256
+ shortFlags: "EFGHIPRTUVabcdhiLlnoqrsuvwxyZz",
257
257
  shortValues: "ABCDefm",
258
258
  longFlags: set(
259
259
  "--basic-regexp --binary-files-without-match --byte-offset --count --extended-regexp --files-with-matches --files-without-match --fixed-regexp --fixed-strings --help --ignore-case --initial-tab --invert-match --line-buffered --line-number --line-regexp --null --null-data --only-matching --perl-regexp --quiet --recursive --silent --unix-byte-offsets --version --with-filename --word-regexp"
@@ -264,12 +264,14 @@ var COMMAND_OPTIONS = {
264
264
  longOptionalValues: set("--color")
265
265
  },
266
266
  head: {
267
+ numericCount: true,
267
268
  shortFlags: "qvz",
268
269
  shortValues: "cn",
269
270
  longFlags: set("--help --quiet --verbose --version --zero-terminated"),
270
271
  longValues: set("--bytes --lines")
271
272
  },
272
273
  tail: {
274
+ numericCount: true,
273
275
  shortFlags: "Fqsvz",
274
276
  shortValues: "cn",
275
277
  longFlags: set("--help --quiet --retry --silent --verbose --version --zero-terminated"),
@@ -294,6 +296,7 @@ var COMMAND_OPTIONS = {
294
296
  }
295
297
  };
296
298
  var GIT_INSPECTION_OPTIONS = {
299
+ numericCount: true,
297
300
  shortFlags: "abcdfhiklmnprstuvwz",
298
301
  shortValues: "ABCGLMSU",
299
302
  longFlags: set(
@@ -436,6 +439,7 @@ function parseOptions(args, spec) {
436
439
  optionsEnded = true;
437
440
  continue;
438
441
  }
442
+ if (spec.numericCount && /^-\d+$/.test(arg)) continue;
439
443
  if (arg.startsWith("--")) {
440
444
  const separator = arg.indexOf("=");
441
445
  const option = separator === -1 ? arg : arg.slice(0, separator);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scheduleCollectScopeRefresh
3
- } from "./chunk-DSWT6BCC.js";
3
+ } from "./chunk-Z6NPNLOT.js";
4
4
 
5
5
  // src/ingest-response.ts
6
6
  var IngestAcknowledgementError = class extends Error {
@@ -2,10 +2,10 @@ import {
2
2
  cursorHooksPath,
3
3
  hasExactCursorHandler,
4
4
  readCursorHooks
5
- } from "./chunk-X64HTHUF.js";
5
+ } from "./chunk-Y5W4TZM7.js";
6
6
  import {
7
7
  resolveRepositoryContext
8
- } from "./chunk-H4UNYFUC.js";
8
+ } from "./chunk-DHOQGHKQ.js";
9
9
 
10
10
  // src/hooks/agent.ts
11
11
  function parseAgent(argv) {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  boundedHealthError
3
- } from "./chunk-DSWT6BCC.js";
3
+ } from "./chunk-Z6NPNLOT.js";
4
4
  import {
5
5
  stripControlChars
6
- } from "./chunk-G2VMA2QO.js";
6
+ } from "./chunk-LY6NJ7KL.js";
7
7
 
8
8
  // src/lib/delivery-backlog.ts
9
9
  var MS_PER_SECOND = 1e3;
@@ -3,7 +3,7 @@ import {
3
3
  isTerminalSafeText,
4
4
  terminalSafeLine,
5
5
  terminalSafeText
6
- } from "./chunk-G2VMA2QO.js";
6
+ } from "./chunk-LY6NJ7KL.js";
7
7
 
8
8
  // src/decisions/feedback.ts
9
9
  var FEEDBACK_PROTOCOL_VERSION = 2;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  boundedHealthError
3
- } from "./chunk-DSWT6BCC.js";
3
+ } from "./chunk-Z6NPNLOT.js";
4
4
  import {
5
5
  terminalSafeLine
6
- } from "./chunk-G2VMA2QO.js";
6
+ } from "./chunk-LY6NJ7KL.js";
7
7
 
8
8
  // src/hooks/redact.ts
9
9
  import { existsSync, readFileSync, statSync } from "fs";
@@ -0,0 +1,177 @@
1
+ import {
2
+ repoActiveFlag
3
+ } from "./chunk-DHOQGHKQ.js";
4
+ import {
5
+ atomicWriteFile
6
+ } from "./chunk-DWRSEC34.js";
7
+
8
+ // src/lib/commit-heartbeat.ts
9
+ import { execFileSync } from "child_process";
10
+ import { mkdirSync, readFileSync, readdirSync, unlinkSync } from "fs";
11
+ import { dirname, join, resolve } from "path";
12
+ var FIRED_DIR = "prim/post-commit-fired";
13
+ var WIRED_PATH = "prim/git-hooks-wired";
14
+ var GIT_TIMEOUT_MS = 1e3;
15
+ var MAX_STAMP_BYTES = 1024;
16
+ var REFLOG_SCAN_LIMIT = 50;
17
+ var MAX_FIRED_STAMPS = 256;
18
+ var PRUNE_SLACK = 64;
19
+ var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
20
+ var REFLOG_LINE_RE = /^([0-9a-f]{40}|[0-9a-f]{64})\tHEAD@\{([0-9]+)\}\t(.*)$/u;
21
+ var COMMIT_SUBJECT_RE = /^commit(?: \([^)]*\))?:/u;
22
+ var REFLOG_RESOLUTION_MS = 1e3;
23
+ var POST_COMMIT_GRACE_MS = 6e4;
24
+ function commonPath(cwd, relative) {
25
+ const value = execFileSync("git", ["rev-parse", "--git-common-dir"], {
26
+ cwd,
27
+ encoding: "utf8",
28
+ stdio: ["ignore", "pipe", "ignore"],
29
+ timeout: GIT_TIMEOUT_MS
30
+ }).replace(/\r?\n$/u, "");
31
+ if (value === "" || /[\0\r\n]/u.test(value)) throw new Error("Git returned an unsafe path");
32
+ return join(resolve(cwd, value), relative);
33
+ }
34
+ function gitPath(cwd, relative) {
35
+ const value = execFileSync("git", ["rev-parse", "--git-path", relative], {
36
+ cwd,
37
+ encoding: "utf8",
38
+ stdio: ["ignore", "pipe", "ignore"],
39
+ timeout: GIT_TIMEOUT_MS
40
+ }).replace(/\r?\n$/u, "");
41
+ if (value === "" || /[\0\r\n]/u.test(value)) throw new Error("Git returned an unsafe path");
42
+ return resolve(cwd, value);
43
+ }
44
+ function ensurePrivateParent(path) {
45
+ mkdirSync(dirname(dirname(path)), { recursive: true, mode: 448 });
46
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
47
+ }
48
+ function writeStamp(path, at) {
49
+ ensurePrivateParent(path);
50
+ atomicWriteFile(path, `${JSON.stringify({ at })}
51
+ `, { mode: 384 });
52
+ }
53
+ function readStampAt(path) {
54
+ try {
55
+ const raw = readFileSync(path, "utf8");
56
+ if (raw.length > MAX_STAMP_BYTES) return void 0;
57
+ const parsed = JSON.parse(raw);
58
+ return typeof parsed.at === "number" && Number.isSafeInteger(parsed.at) && parsed.at > 0 ? parsed.at : void 0;
59
+ } catch {
60
+ return void 0;
61
+ }
62
+ }
63
+ function recordConversationCaptured(cwd, agent) {
64
+ try {
65
+ writeStamp(gitPath(cwd, `prim/conversation-captured/${agent}`), Date.now());
66
+ } catch {
67
+ }
68
+ }
69
+ function lastConversationCaptured(cwd, agent) {
70
+ try {
71
+ return readStampAt(gitPath(cwd, `prim/conversation-captured/${agent}`));
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ }
76
+ function readFiredStamps(cwd) {
77
+ try {
78
+ const dir = gitPath(cwd, FIRED_DIR);
79
+ return readdirSync(dir).flatMap((sha) => {
80
+ if (!FULL_SHA_RE.test(sha)) return [];
81
+ const at = readStampAt(join(dir, sha));
82
+ return at === void 0 ? [] : [{ sha, at }];
83
+ });
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+ function recordPostCommitFired(cwd, sha, now = Date.now()) {
89
+ if (!sha || !FULL_SHA_RE.test(sha)) return;
90
+ try {
91
+ const dir = gitPath(cwd, FIRED_DIR);
92
+ writeStamp(join(dir, sha), now);
93
+ const names = readdirSync(dir).filter((name) => FULL_SHA_RE.test(name));
94
+ if (names.length <= MAX_FIRED_STAMPS + PRUNE_SLACK) return;
95
+ const stale = names.map((name) => ({ name, at: readStampAt(join(dir, name)) ?? 0 })).sort((left, right) => right.at - left.at || left.name.localeCompare(right.name)).slice(MAX_FIRED_STAMPS);
96
+ for (const { name } of stale) unlinkSync(join(dir, name));
97
+ } catch {
98
+ }
99
+ }
100
+ function recordHooksWired(cwd, options = {}) {
101
+ try {
102
+ const path = commonPath(cwd, WIRED_PATH);
103
+ if (options.onlyIfAbsent && readStampAt(path) !== void 0) return;
104
+ writeStamp(path, options.now ?? Date.now());
105
+ } catch {
106
+ }
107
+ }
108
+ function clearHooksWired(cwd) {
109
+ try {
110
+ unlinkSync(commonPath(cwd, WIRED_PATH));
111
+ } catch {
112
+ }
113
+ }
114
+ function latestLocalCommit(cwd) {
115
+ let output;
116
+ try {
117
+ output = execFileSync(
118
+ "git",
119
+ [
120
+ "log",
121
+ "-g",
122
+ "--no-show-signature",
123
+ "-n",
124
+ String(REFLOG_SCAN_LIMIT),
125
+ "--date=unix",
126
+ "--format=%H%x09%gd%x09%gs",
127
+ "HEAD"
128
+ ],
129
+ { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: GIT_TIMEOUT_MS }
130
+ );
131
+ } catch {
132
+ return void 0;
133
+ }
134
+ for (const line of output.split("\n")) {
135
+ const match = REFLOG_LINE_RE.exec(line);
136
+ if (match?.[1] && match[2] && COMMIT_SUBJECT_RE.test(match[3] ?? "")) {
137
+ return { sha: match[1], at: Number(match[2]) * 1e3 };
138
+ }
139
+ }
140
+ return void 0;
141
+ }
142
+ function inspectPostCommitFiring(cwd, now = Date.now()) {
143
+ if (repoActiveFlag(cwd) !== "true") return { state: "inactive" };
144
+ const commit = latestLocalCommit(cwd);
145
+ if (!commit) return { state: "unverified" };
146
+ const stamps = readFiredStamps(cwd);
147
+ const own = stamps.find((stamp) => stamp.sha === commit.sha);
148
+ if (own) return { state: "fired", commitAt: commit.at, firedAt: own.at };
149
+ if (now - commit.at < POST_COMMIT_GRACE_MS) return { state: "pending", commitAt: commit.at };
150
+ const lastFiredAt = stamps.reduce(
151
+ (latest, stamp) => latest === void 0 || stamp.at > latest ? stamp.at : latest,
152
+ void 0
153
+ );
154
+ let wiredAt;
155
+ try {
156
+ wiredAt = readStampAt(commonPath(cwd, WIRED_PATH));
157
+ } catch {
158
+ wiredAt = void 0;
159
+ }
160
+ if (wiredAt === void 0) return { state: "unverified" };
161
+ const firedSinceWired = lastFiredAt !== void 0 && lastFiredAt >= wiredAt;
162
+ const judged = commit.at > wiredAt || firedSinceWired && commit.at + REFLOG_RESOLUTION_MS > lastFiredAt;
163
+ if (judged) {
164
+ return lastFiredAt === void 0 ? { state: "not_firing", commitAt: commit.at } : { state: "not_firing", commitAt: commit.at, firedAt: lastFiredAt };
165
+ }
166
+ return { state: "unverified" };
167
+ }
168
+
169
+ export {
170
+ POST_COMMIT_GRACE_MS,
171
+ recordConversationCaptured,
172
+ lastConversationCaptured,
173
+ recordPostCommitFired,
174
+ recordHooksWired,
175
+ clearHooksWired,
176
+ inspectPostCommitFiring
177
+ };