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

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