@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
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ commitAttributionFromEnvironment,
4
+ toCommitMove
5
+ } from "../chunk-NAHHFSLH.js";
6
+ import {
7
+ getOrCreateWorkspaceId
8
+ } from "../chunk-EZXEWFDG.js";
9
+ import {
10
+ appendMove,
11
+ resolveOrg
12
+ } from "../chunk-FBAYCS3X.js";
13
+ import "../chunk-OAAP2MY6.js";
14
+ import "../chunk-BTNNEH2W.js";
15
+ import {
16
+ cachedCollectScopeAdmits,
17
+ isRepoActiveForCapture,
18
+ repoSyncId
19
+ } from "../chunk-M6RIJVRE.js";
20
+ import "../chunk-PCVGBE5B.js";
21
+ import {
22
+ githubRepositoryFullName,
23
+ resolveRepositoryContext
24
+ } from "../chunk-H4UNYFUC.js";
25
+ import "../chunk-67VNLLBN.js";
26
+
27
+ // src/hooks/post-commit.ts
28
+ import { execFileSync, spawn } from "child_process";
29
+ import { lstatSync, readFileSync } from "fs";
30
+ import { basename, dirname, isAbsolute, join } from "path";
31
+ import { fileURLToPath } from "url";
32
+ var here = dirname(fileURLToPath(import.meta.url));
33
+ var GIT_TIMEOUT_MS = 1e3;
34
+ var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
35
+ var OBSERVATION_MARKER_RE = /^prim-post-commit-observed\.[A-Za-z0-9]+$/u;
36
+ var MAX_OBSERVATION_MARKER_AGE_MS = 10 * 60 * 1e3;
37
+ var MAX_OBSERVATION_MARKER_FUTURE_SKEW_MS = 5e3;
38
+ function capturedAtFromLauncher(env = process.env, now = Date.now()) {
39
+ const path = env.PRIM_COMMIT_OBSERVED_FILE;
40
+ if (!path || !isAbsolute(path) || !OBSERVATION_MARKER_RE.test(basename(path))) return;
41
+ try {
42
+ const stat = lstatSync(path);
43
+ const capturedAt = Math.trunc(stat.mtimeMs);
44
+ const uid = process.getuid?.();
45
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== 0 || stat.nlink !== 1 || (stat.mode & 63) !== 0 || uid !== void 0 && stat.uid !== uid || !Number.isSafeInteger(capturedAt) || capturedAt <= 0 || capturedAt < now - MAX_OBSERVATION_MARKER_AGE_MS || capturedAt > now + MAX_OBSERVATION_MARKER_FUTURE_SKEW_MS) {
46
+ return;
47
+ }
48
+ return capturedAt;
49
+ } catch {
50
+ return;
51
+ }
52
+ }
53
+ function gitText(args) {
54
+ try {
55
+ return execFileSync("git", args, {
56
+ encoding: "utf-8",
57
+ stdio: ["ignore", "pipe", "ignore"],
58
+ timeout: GIT_TIMEOUT_MS
59
+ }).trim();
60
+ } catch {
61
+ return;
62
+ }
63
+ }
64
+ function gitBytes(args) {
65
+ try {
66
+ return execFileSync("git", args, {
67
+ encoding: "buffer",
68
+ stdio: ["ignore", "pipe", "ignore"],
69
+ timeout: GIT_TIMEOUT_MS
70
+ });
71
+ } catch {
72
+ return;
73
+ }
74
+ }
75
+ function parseNulPaths(value) {
76
+ if (value.length === 0) return { files: [], complete: true };
77
+ const chunks = [];
78
+ let start = 0;
79
+ for (let i = 0; i < value.length; i += 1) {
80
+ if (value[i] !== 0) continue;
81
+ chunks.push(value.subarray(start, i));
82
+ start = i + 1;
83
+ }
84
+ if (start !== value.length || chunks.some((chunk) => chunk.length === 0)) {
85
+ return { files: [], complete: false };
86
+ }
87
+ const files = [];
88
+ for (const chunk of chunks) {
89
+ const file = chunk.toString("utf8");
90
+ if (!Buffer.from(file, "utf8").equals(chunk)) {
91
+ return { files: [], complete: false };
92
+ }
93
+ files.push(file);
94
+ }
95
+ return { files: [...new Set(files)], complete: true };
96
+ }
97
+ function readCommit() {
98
+ const snapshotProvided = process.env.PRIM_COMMIT_SHA !== void 0;
99
+ const sha = snapshotProvided ? process.env.PRIM_COMMIT_SHA : gitText(["rev-parse", "HEAD"]);
100
+ if (!sha || !FULL_SHA_RE.test(sha)) {
101
+ return null;
102
+ }
103
+ const rawBranch = process.env.PRIM_COMMIT_BRANCH !== void 0 ? process.env.PRIM_COMMIT_BRANCH : gitText(["rev-parse", "--abbrev-ref", "HEAD"]);
104
+ const branch = rawBranch && rawBranch === rawBranch.trim() && ![...rawBranch].some((char) => char < " " || char === "\x7F") ? rawBranch : void 0;
105
+ const rawFiles = gitBytes([
106
+ "diff-tree",
107
+ "--no-commit-id",
108
+ "--name-only",
109
+ "-z",
110
+ "-r",
111
+ "-m",
112
+ "--root",
113
+ sha
114
+ ]);
115
+ const { files, complete } = rawFiles === void 0 ? { files: [], complete: false } : parseNulPaths(rawFiles);
116
+ return {
117
+ sha,
118
+ parentSha: gitText(["rev-parse", "--verify", "--quiet", `${sha}^`]) || void 0,
119
+ branch: branch && branch !== "HEAD" ? branch : void 0,
120
+ files,
121
+ filesComplete: complete
122
+ };
123
+ }
124
+ function resolveCliVersion() {
125
+ try {
126
+ const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
127
+ return pkg.version ?? "unknown";
128
+ } catch {
129
+ return "unknown";
130
+ }
131
+ }
132
+ function spawnBackgroundFlush() {
133
+ const entry = join(here, "..", "index.js");
134
+ spawn(process.execPath, [entry, "moves", "flush"], {
135
+ detached: true,
136
+ stdio: "ignore"
137
+ }).unref();
138
+ }
139
+ function runPostCommit() {
140
+ const cwd = gitText(["rev-parse", "--show-toplevel"]) ?? process.cwd();
141
+ if (!isRepoActiveForCapture(cwd)) {
142
+ return;
143
+ }
144
+ const capturedAt = capturedAtFromLauncher();
145
+ if (process.env.PRIM_COMMIT_OBSERVED_FILE !== void 0 && capturedAt === void 0) {
146
+ return;
147
+ }
148
+ const commit = readCommit();
149
+ if (commit) {
150
+ const repositoryFullName = githubRepositoryFullName(cwd) ?? void 0;
151
+ if (!cachedCollectScopeAdmits(cwd, {
152
+ repository: repositoryFullName,
153
+ branch: commit.branch,
154
+ ...commit.files.length > 0 || !commit.filesComplete ? { paths: commit.files, pathsComplete: commit.filesComplete } : {}
155
+ })) {
156
+ return;
157
+ }
158
+ const repository = resolveRepositoryContext(cwd);
159
+ const identity = getOrCreateWorkspaceId(cwd);
160
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
161
+ const attribution = commitAttributionFromEnvironment(process.env, workspaceId);
162
+ const move = toCommitMove(commit, resolveCliVersion(), cwd, {
163
+ repository,
164
+ repoFullName: repositoryFullName,
165
+ repoSyncId: repoSyncId(cwd),
166
+ workspaceId,
167
+ attribution,
168
+ ...capturedAt === void 0 ? {} : { capturedAt }
169
+ });
170
+ const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd });
171
+ appendMove(move, orgId);
172
+ spawnBackgroundFlush();
173
+ }
174
+ }
175
+ if (!process.env.VITEST) {
176
+ try {
177
+ runPostCommit();
178
+ } catch (err) {
179
+ if (process.env.PRIM_HOOK_DEBUG) {
180
+ const detail = err instanceof Error ? err.message : String(err);
181
+ process.stderr.write(`[prim-post-commit] capture failed: ${detail}
182
+ `);
183
+ }
184
+ }
185
+ process.exit(0);
186
+ }
187
+ export {
188
+ capturedAtFromLauncher,
189
+ runPostCommit
190
+ };
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ canonicalRewritePairs,
4
+ toRewriteMove
5
+ } from "../chunk-NAHHFSLH.js";
6
+ import {
7
+ getOrCreateWorkspaceId
8
+ } from "../chunk-EZXEWFDG.js";
9
+ import {
10
+ appendMove,
11
+ resolveOrg
12
+ } from "../chunk-FBAYCS3X.js";
13
+ import "../chunk-OAAP2MY6.js";
14
+ import "../chunk-BTNNEH2W.js";
15
+ import {
16
+ cachedCollectScopeAdmits,
17
+ isRepoActiveForCapture,
18
+ repoSyncId
19
+ } from "../chunk-M6RIJVRE.js";
20
+ import "../chunk-PCVGBE5B.js";
21
+ import {
22
+ githubRepositoryFullName,
23
+ resolveRepositoryContext
24
+ } from "../chunk-H4UNYFUC.js";
25
+ import "../chunk-67VNLLBN.js";
26
+
27
+ // src/hooks/post-rewrite.ts
28
+ import { execFileSync, spawn } from "child_process";
29
+ import {
30
+ constants,
31
+ closeSync,
32
+ fstatSync,
33
+ lstatSync,
34
+ openSync,
35
+ readFileSync,
36
+ unlinkSync
37
+ } from "fs";
38
+ import { basename, dirname, isAbsolute, join } from "path";
39
+ import { fileURLToPath } from "url";
40
+ var here = dirname(fileURLToPath(import.meta.url));
41
+ var GIT_TIMEOUT_MS = 1e3;
42
+ var MAX_PAIRS_FILE_BYTES = 1048576;
43
+ var MAX_PAIRS_FILE_AGE_MS = 10 * 60 * 1e3;
44
+ var MAX_PAIRS_FILE_FUTURE_SKEW_MS = 5e3;
45
+ var PAIRS_FILE_RE = /^prim-post-rewrite-pairs\.[A-Za-z0-9]+$/u;
46
+ var FULL_SHA_SOURCE = "(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})";
47
+ var REWRITE_PAIR_RE = new RegExp(
48
+ `^(${FULL_SHA_SOURCE}) (${FULL_SHA_SOURCE})(?: ([^\\s]+))?$`,
49
+ "u"
50
+ );
51
+ function parseRewritePairs(value) {
52
+ if (value.length === 0) return [];
53
+ const body = value.endsWith("\n") ? value.slice(0, -1) : value;
54
+ if (body.length === 0) return [];
55
+ const parsed = [];
56
+ for (const line of body.split("\n")) {
57
+ const match = REWRITE_PAIR_RE.exec(line);
58
+ if (!match?.[1] || !match[2]) return;
59
+ parsed.push({ oldSha: match[1], newSha: match[2] });
60
+ }
61
+ return canonicalRewritePairs(parsed) ?? void 0;
62
+ }
63
+ function privatePairsFileStat(stat, now) {
64
+ const capturedAt = Math.trunc(stat.mtimeMs);
65
+ const uid = process.getuid?.();
66
+ return stat.isFile() && !stat.isSymbolicLink() && stat.size <= MAX_PAIRS_FILE_BYTES && stat.nlink === 1 && (stat.mode & 511) === 384 && (uid === void 0 || stat.uid === uid) && Number.isSafeInteger(capturedAt) && capturedAt > 0 && capturedAt >= now - MAX_PAIRS_FILE_AGE_MS && capturedAt <= now + MAX_PAIRS_FILE_FUTURE_SKEW_MS;
67
+ }
68
+ function rewritePairsFromLauncher(env = process.env, now = Date.now()) {
69
+ const path = env.PRIM_REWRITE_PAIRS_FILE;
70
+ if (!path || !isAbsolute(path) || !PAIRS_FILE_RE.test(basename(path))) return;
71
+ let fd;
72
+ try {
73
+ const pathStat = lstatSync(path);
74
+ if (!privatePairsFileStat(pathStat, now)) return;
75
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
76
+ const openStat = fstatSync(fd);
77
+ if (!privatePairsFileStat(openStat, now) || openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
78
+ return;
79
+ }
80
+ const bytes = readFileSync(fd);
81
+ if (bytes.length !== openStat.size) return;
82
+ let text;
83
+ try {
84
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
85
+ } catch {
86
+ return;
87
+ }
88
+ const pairs = parseRewritePairs(text);
89
+ if (!pairs) return;
90
+ return {
91
+ path,
92
+ dev: openStat.dev,
93
+ ino: openStat.ino,
94
+ capturedAt: Math.trunc(openStat.mtimeMs),
95
+ pairs
96
+ };
97
+ } catch {
98
+ return;
99
+ } finally {
100
+ if (fd !== void 0) {
101
+ try {
102
+ closeSync(fd);
103
+ } catch {
104
+ }
105
+ }
106
+ }
107
+ }
108
+ function unlinkSnapshot(snapshot) {
109
+ try {
110
+ const current = lstatSync(snapshot.path);
111
+ if (current.isFile() && !current.isSymbolicLink() && current.dev === snapshot.dev && current.ino === snapshot.ino) {
112
+ unlinkSync(snapshot.path);
113
+ }
114
+ } catch {
115
+ }
116
+ }
117
+ function gitText(args) {
118
+ try {
119
+ return execFileSync("git", args, {
120
+ encoding: "utf-8",
121
+ stdio: ["ignore", "pipe", "ignore"],
122
+ timeout: GIT_TIMEOUT_MS
123
+ }).trim();
124
+ } catch {
125
+ return;
126
+ }
127
+ }
128
+ function validSource(value) {
129
+ return value === "amend" || value === "rebase";
130
+ }
131
+ function validBranch(value) {
132
+ return value && value === value.trim() && ![...value].some((char) => char < " " || char === "\x7F") ? value : void 0;
133
+ }
134
+ function resolveCliVersion() {
135
+ try {
136
+ const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
137
+ return pkg.version ?? "unknown";
138
+ } catch {
139
+ return "unknown";
140
+ }
141
+ }
142
+ function spawnBackgroundFlush() {
143
+ const entry = join(here, "..", "index.js");
144
+ spawn(process.execPath, [entry, "moves", "flush"], {
145
+ detached: true,
146
+ stdio: "ignore"
147
+ }).unref();
148
+ }
149
+ function runPostRewrite() {
150
+ const snapshot = rewritePairsFromLauncher();
151
+ if (!snapshot) return;
152
+ try {
153
+ const source = process.env.PRIM_REWRITE_SOURCE;
154
+ if (!validSource(source) || snapshot.pairs.length === 0) return;
155
+ const cwd = gitText(["rev-parse", "--show-toplevel"]) ?? process.cwd();
156
+ if (!isRepoActiveForCapture(cwd)) return;
157
+ const branch = validBranch(process.env.PRIM_REWRITE_BRANCH);
158
+ const repositoryFullName = githubRepositoryFullName(cwd) ?? void 0;
159
+ if (!cachedCollectScopeAdmits(cwd, { repository: repositoryFullName, branch })) return;
160
+ const repository = resolveRepositoryContext(cwd);
161
+ const identity = getOrCreateWorkspaceId(cwd);
162
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
163
+ const moves = toRewriteMove(
164
+ {
165
+ source,
166
+ pairs: snapshot.pairs,
167
+ branch
168
+ },
169
+ resolveCliVersion(),
170
+ cwd,
171
+ {
172
+ repository,
173
+ repoFullName: repositoryFullName,
174
+ repoSyncId: repoSyncId(cwd),
175
+ workspaceId,
176
+ capturedAt: snapshot.capturedAt
177
+ }
178
+ );
179
+ if (moves.length === 0) return;
180
+ const { orgId } = resolveOrg({ sessionId: "", cwd });
181
+ for (const move of moves) appendMove(move, orgId);
182
+ spawnBackgroundFlush();
183
+ } finally {
184
+ unlinkSnapshot(snapshot);
185
+ }
186
+ }
187
+ if (!process.env.VITEST) {
188
+ try {
189
+ runPostRewrite();
190
+ } catch (error) {
191
+ if (process.env.PRIM_HOOK_DEBUG) {
192
+ const detail = error instanceof Error ? error.message : String(error);
193
+ process.stderr.write(`[prim-post-rewrite] capture failed: ${detail}
194
+ `);
195
+ }
196
+ }
197
+ process.exit(0);
198
+ }
199
+ export {
200
+ parseRewritePairs,
201
+ rewritePairsFromLauncher,
202
+ runPostRewrite
203
+ };
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ requireDurableIngestAcknowledgement
4
+ } from "../chunk-FKXFUUWJ.js";
5
+ import {
6
+ enrichHookPayloadWithFileRefs,
7
+ preserveHookFileMetadata
8
+ } from "../chunk-LFFLHSQO.js";
9
+ import {
10
+ postToolInvocationId,
11
+ toMove,
12
+ toolOutcomeFor
13
+ } from "../chunk-NAHHFSLH.js";
14
+ import {
15
+ getOrCreateWorkspaceId
16
+ } from "../chunk-EZXEWFDG.js";
17
+ import {
18
+ appendMove,
19
+ resolveOrg
20
+ } from "../chunk-FBAYCS3X.js";
21
+ import "../chunk-ZB4F75SZ.js";
22
+ import {
23
+ appendCodexContext,
24
+ prepareCodexContext
25
+ } from "../chunk-VW3FDYRT.js";
26
+ import "../chunk-WX52U7Z2.js";
27
+ import {
28
+ scrubFromCwd
29
+ } from "../chunk-OAAP2MY6.js";
30
+ import {
31
+ bold,
32
+ color
33
+ } from "../chunk-BTNNEH2W.js";
34
+ import {
35
+ cachedCollectScopeAdmits,
36
+ isRepoActiveForCapture,
37
+ repoSyncId
38
+ } from "../chunk-M6RIJVRE.js";
39
+ import {
40
+ getClient,
41
+ terminalSafeLine
42
+ } from "../chunk-PCVGBE5B.js";
43
+ import {
44
+ normalizeEnvelope,
45
+ parseAgent,
46
+ readHookStdin,
47
+ shouldSuppressImportedCursorHandler
48
+ } from "../chunk-HU4PW6I6.js";
49
+ import "../chunk-EWZMDD44.js";
50
+ import {
51
+ currentBranch,
52
+ resolveRepositoryContext
53
+ } from "../chunk-H4UNYFUC.js";
54
+ import "../chunk-KFXF2V5G.js";
55
+ import "../chunk-67VNLLBN.js";
56
+
57
+ // src/hooks/post-tool-use.ts
58
+ import { readFileSync } from "fs";
59
+ import { dirname, join } from "path";
60
+ import { fileURLToPath } from "url";
61
+
62
+ // src/hooks/post-tool-delivery.ts
63
+ var INGEST_TIMEOUT_MS = 4e3;
64
+ async function deliverPostToolMove(move, orgId) {
65
+ appendMove(move, orgId);
66
+ const response = await getClient().post(
67
+ "/api/cli/moves/ingest",
68
+ { batch: [move] },
69
+ { signal: AbortSignal.timeout(INGEST_TIMEOUT_MS) }
70
+ );
71
+ return requireDurableIngestAcknowledgement(response, 1, move.env.gitRoot ?? move.env.cwd);
72
+ }
73
+
74
+ // src/hooks/verdict-footer.ts
75
+ function renderVerdictFooter(ctx) {
76
+ const successPrefix = color("\u2713 Conflict caught before merge", "green");
77
+ const savedCount = `${String(ctx.decisionsSaved)}${ctx.decisionsSavedTruncated ? "+" : ""}`;
78
+ const savedFragment = `${savedCount} decisions saved`;
79
+ const intentFragment = `${bold(terminalSafeLine(ctx.author))}'s intent preserved`;
80
+ return `${successPrefix} \xB7 ${savedFragment} \xB7 ${intentFragment}`;
81
+ }
82
+ function isVerdictFooterContext(value) {
83
+ if (typeof value !== "object" || value === null) {
84
+ return false;
85
+ }
86
+ const v = value;
87
+ return typeof v.author === "string" && typeof v.intent === "string" && typeof v.decisionsSaved === "number" && typeof v.decisionsSavedTruncated === "boolean" && typeof v.decisionId === "string";
88
+ }
89
+
90
+ // src/hooks/post-tool-use.ts
91
+ var STDIN_TIMEOUT_MS = 1e3;
92
+ var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]);
93
+ var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch", "Bash"]);
94
+ var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
95
+ var CURSOR_EDITING_TOOLS = /* @__PURE__ */ new Set(["Write", "Delete", "Shell"]);
96
+ function editingToolsFor(agent) {
97
+ if (agent === "codex") {
98
+ return CODEX_EDITING_TOOLS;
99
+ }
100
+ if (agent === "hermes") {
101
+ return HERMES_EDITING_TOOLS;
102
+ }
103
+ if (agent === "cursor") {
104
+ return CURSOR_EDITING_TOOLS;
105
+ }
106
+ return EDITING_TOOLS;
107
+ }
108
+ var here = dirname(fileURLToPath(import.meta.url));
109
+ function resolveCliVersion() {
110
+ try {
111
+ const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
112
+ return pkg.version ?? "unknown";
113
+ } catch {
114
+ return "unknown";
115
+ }
116
+ }
117
+ async function emit(output = {}) {
118
+ return await new Promise((resolve) => {
119
+ try {
120
+ process.stdout.write(`${JSON.stringify(output)}
121
+ `, (error) => resolve(!error));
122
+ } catch {
123
+ resolve(false);
124
+ }
125
+ });
126
+ }
127
+ async function emitWithAcknowledgment(output, acknowledge) {
128
+ const handedOff = await emit(output);
129
+ await acknowledge?.(handedOff);
130
+ }
131
+ async function emitCursorContext(envelope) {
132
+ if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0 || typeof envelope.cwd !== "string" || envelope.cwd.length === 0) {
133
+ await emit();
134
+ return;
135
+ }
136
+ try {
137
+ const context = await prepareCodexContext({
138
+ cwd: envelope.cwd,
139
+ sessionId: envelope.session_id,
140
+ includeDigest: true,
141
+ namespace: "cursor"
142
+ });
143
+ await emitWithAcknowledgment(
144
+ context.context ? { additional_context: context.context } : {},
145
+ context.acknowledge
146
+ );
147
+ } catch {
148
+ await emit();
149
+ }
150
+ }
151
+ async function finish(agent, envelope) {
152
+ if (agent === "cursor" && envelope) await emitCursorContext(envelope);
153
+ else await emit();
154
+ }
155
+ function debug(msg) {
156
+ if (process.env.PRIM_HOOK_VERBOSE === "1") {
157
+ process.stderr.write(`[prim-post-tool-use] ${msg}
158
+ `);
159
+ }
160
+ }
161
+ async function main() {
162
+ const agent = parseAgent(process.argv);
163
+ let raw;
164
+ try {
165
+ raw = await readHookStdin(STDIN_TIMEOUT_MS);
166
+ } catch {
167
+ await emit();
168
+ return;
169
+ }
170
+ let parsed;
171
+ try {
172
+ const incoming = JSON.parse(raw);
173
+ if (agent !== "cursor" && shouldSuppressImportedCursorHandler(incoming, "prim-post-tool-use")) {
174
+ await emit();
175
+ return;
176
+ }
177
+ parsed = normalizeEnvelope(incoming, agent);
178
+ } catch {
179
+ await emit();
180
+ return;
181
+ }
182
+ let envelope = parsed;
183
+ const isToolResult = envelope.hook_event_name === "PostToolUse" || (agent === "claude_code" || agent === "cursor") && envelope.hook_event_name === "PostToolUseFailure";
184
+ const isHermesDenial = agent === "hermes" && envelope.hook_event_name === "post_approval_response" && toolOutcomeFor(parsed, agent) === "prevented";
185
+ if (!isToolResult && !isHermesDenial) {
186
+ await finish(agent, envelope);
187
+ return;
188
+ }
189
+ const invocationId = postToolInvocationId(parsed, agent);
190
+ if (isHermesDenial && !invocationId) {
191
+ await finish(agent, envelope);
192
+ return;
193
+ }
194
+ const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
195
+ if (!isHermesDenial && !editingToolsFor(agent).has(toolName)) {
196
+ await finish(agent, envelope);
197
+ return;
198
+ }
199
+ if (isHermesDenial && (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) && typeof envelope.extra?.session_key === "string" && envelope.extra.session_key.length > 0) {
200
+ parsed = { ...parsed, session_id: envelope.extra.session_key };
201
+ envelope = parsed;
202
+ }
203
+ if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
204
+ await finish(agent, envelope);
205
+ return;
206
+ }
207
+ const cwd = parsed.cwd ?? process.cwd();
208
+ if (!isRepoActiveForCapture(cwd)) {
209
+ await finish(agent, envelope);
210
+ return;
211
+ }
212
+ const resolvedRepository = resolveRepositoryContext(cwd);
213
+ if (!resolvedRepository) {
214
+ await finish(agent, envelope);
215
+ return;
216
+ }
217
+ const repository = { ...resolvedRepository, repoSyncId: repoSyncId(cwd) };
218
+ const enrichment = enrichHookPayloadWithFileRefs({
219
+ parsed,
220
+ agent,
221
+ cwd,
222
+ repository
223
+ });
224
+ const { resolution } = enrichment;
225
+ if (!isHermesDenial) {
226
+ if (resolution.shellMutation === "none") {
227
+ await finish(agent, envelope);
228
+ return;
229
+ }
230
+ if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
231
+ await finish(agent, envelope);
232
+ return;
233
+ }
234
+ }
235
+ const pathsComplete = !resolution.targetsIncomplete && !resolution.targetsTruncated && resolution.rejected.length === 0 && resolution.shellMutation !== "unresolved";
236
+ const hasPathEvidence = resolution.fileRefs.length > 0 || resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation === "unresolved";
237
+ if (agent === "cursor" && (resolution.rejected.length > 0 || resolution.targetsIncomplete || resolution.targetsTruncated || resolution.shellMutation === "unresolved")) {
238
+ await finish(agent, envelope);
239
+ return;
240
+ }
241
+ if (!cachedCollectScopeAdmits(cwd, {
242
+ repository: resolvedRepository.repoFullName,
243
+ branch: currentBranch(cwd),
244
+ agent,
245
+ ...isHermesDenial || !hasPathEvidence ? {} : { paths: resolution.fileRefs, pathsComplete }
246
+ })) {
247
+ await finish(agent, envelope);
248
+ return;
249
+ }
250
+ const enriched = enrichment.parsed;
251
+ const identity = getOrCreateWorkspaceId(cwd);
252
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
253
+ const base = toMove(enriched, resolveCliVersion(), agent, workspaceId, repository, invocationId);
254
+ const scrubbed = await scrubFromCwd(enriched, cwd);
255
+ const move = {
256
+ ...base,
257
+ payload: preserveHookFileMetadata(scrubbed, resolution)
258
+ };
259
+ const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd });
260
+ let verdictFooter = false;
261
+ try {
262
+ const result = await deliverPostToolMove(move, orgId);
263
+ debug(`durably ingested ${move.moveId} (${toolName})`);
264
+ if (isVerdictFooterContext(result.verdictFooter)) {
265
+ verdictFooter = true;
266
+ process.stderr.write(`${renderVerdictFooter(result.verdictFooter)}
267
+ `);
268
+ }
269
+ } catch (err) {
270
+ debug(`ingest failed: ${err instanceof Error ? err.message : String(err)}`);
271
+ }
272
+ if (agent === "codex" && verdictFooter) {
273
+ try {
274
+ const context = await prepareCodexContext({
275
+ cwd,
276
+ sessionId: envelope.session_id,
277
+ includeDigest: false
278
+ });
279
+ await emitWithAcknowledgment(appendCodexContext({}, context.context), context.acknowledge);
280
+ return;
281
+ } catch {
282
+ }
283
+ }
284
+ await finish(agent, envelope);
285
+ }
286
+ main().catch(async () => {
287
+ await emit();
288
+ });