@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.80

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-2KW7DVFJ.js +1400 -0
  4. package/dist/chunk-3EEFL6NJ.js +529 -0
  5. package/dist/chunk-4A2XB2PC.js +310 -0
  6. package/dist/chunk-4GLVJLEQ.js +55 -0
  7. package/dist/chunk-BQUJ4AFJ.js +771 -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-KKP2NYZ3.js +505 -0
  12. package/dist/chunk-LEMX3IOG.js +79 -0
  13. package/dist/chunk-LMRYZL3D.js +209 -0
  14. package/dist/chunk-OUXH3UEP.js +247 -0
  15. package/dist/chunk-RO3F6JYV.js +126 -0
  16. package/dist/chunk-TJTVHDJ5.js +321 -0
  17. package/dist/chunk-WB7AUZ4C.js +52 -0
  18. package/dist/chunk-XGADQPCI.js +26 -0
  19. package/dist/chunk-YWO7O6QG.js +327 -0
  20. package/dist/chunk-Z7YPFWAZ.js +917 -0
  21. package/dist/chunk-ZOSNEO5N.js +77 -0
  22. package/dist/daemon/server.js +15329 -0
  23. package/dist/hook-runtime/daemon/server.js +15329 -0
  24. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  25. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  26. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  27. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  28. package/dist/hook-runtime/hooks/pre-tool-use.js +7096 -0
  29. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  30. package/dist/hook-runtime/hooks/session-end.js +495 -0
  31. package/dist/hook-runtime/hooks/session-start.js +11313 -0
  32. package/dist/hook-runtime/index.js +44686 -0
  33. package/dist/hook-runtime/statusline-main.js +765 -0
  34. package/dist/hooks/post-commit.js +179 -0
  35. package/dist/hooks/post-rewrite.js +197 -0
  36. package/dist/hooks/post-tool-use.js +250 -0
  37. package/dist/hooks/pre-commit.js +21 -74
  38. package/dist/hooks/pre-tool-use.js +345 -0
  39. package/dist/hooks/prim-hook.js +195 -0
  40. package/dist/hooks/session-end.js +70 -0
  41. package/dist/hooks/session-start.js +299 -0
  42. package/dist/index.js +20971 -519
  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,321 @@
1
+ // src/lib/git.ts
2
+ import { execFileSync } from "child_process";
3
+ import { createHash } from "crypto";
4
+ import { lstatSync, realpathSync } from "fs";
5
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "path";
6
+ var GIT_TIMEOUT_MS = 1e3;
7
+ var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
8
+ var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
9
+ function gitToplevel(cwd) {
10
+ try {
11
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
12
+ cwd,
13
+ encoding: "utf-8",
14
+ stdio: ["ignore", "pipe", "ignore"],
15
+ timeout: GIT_TIMEOUT_MS
16
+ }).trim();
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function gitValue(cwd, args) {
22
+ try {
23
+ const value = execFileSync("git", args, {
24
+ cwd,
25
+ encoding: "utf-8",
26
+ stdio: ["ignore", "pipe", "ignore"],
27
+ timeout: GIT_TIMEOUT_MS
28
+ }).trim();
29
+ return value || void 0;
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ function githubRepositoryFullName(cwd) {
35
+ const remote = gitValue(cwd, ["config", "--get", "remote.origin.url"]);
36
+ if (!remote || [...remote].some((char) => char < " " || char === "\x7F")) return null;
37
+ const match = /^(?:git@github\.com:([^?#]+)|(?:https|ssh):\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/([^?#]+))$/i.exec(
38
+ remote
39
+ );
40
+ const path = match?.[1] ?? match?.[2];
41
+ if (!path) return null;
42
+ const parts = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "").split("/");
43
+ if (parts.length !== 2 || !GITHUB_OWNER_PATTERN.test(parts[0] ?? "") || !GITHUB_REPOSITORY_PATTERN.test(parts[1] ?? "")) {
44
+ return null;
45
+ }
46
+ return `${parts[0]}/${parts[1]}`;
47
+ }
48
+ function canonicalGitRoot(cwd) {
49
+ try {
50
+ const top = gitToplevel(cwd);
51
+ return top ? realpathSync.native(top) : null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ function normalizeOriginRemote(remote) {
57
+ const value = remote.trim();
58
+ if (!value) return void 0;
59
+ const scp = !value.includes("://") ? /^(?:[^@/\s]+@)?(?<host>[^:/\s]+):(?<path>.+)$/.exec(value)?.groups : void 0;
60
+ if (scp?.host && scp.path) {
61
+ const path = scp.path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
62
+ return path ? `${scp.host.toLowerCase()}/${path}` : void 0;
63
+ }
64
+ try {
65
+ const url = new URL(value);
66
+ url.username = "";
67
+ url.password = "";
68
+ url.search = "";
69
+ url.hash = "";
70
+ const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
71
+ if (url.host && path) return `${url.host.toLowerCase()}/${path}`;
72
+ return void 0;
73
+ } catch {
74
+ return void 0;
75
+ }
76
+ }
77
+ function opaqueRepoKey(source, value) {
78
+ return `repo_v1_${createHash("sha256").update(`${source}\0${value}`).digest("hex")}`;
79
+ }
80
+ function resolveRepositoryContext(cwd = process.cwd()) {
81
+ const top = gitToplevel(cwd);
82
+ if (!top) return null;
83
+ let repoRoot;
84
+ try {
85
+ repoRoot = realpathSync.native(top);
86
+ } catch {
87
+ repoRoot = resolve(top);
88
+ }
89
+ const origin = gitValue(repoRoot, ["config", "--get", "remote.origin.url"]);
90
+ const repoFullName = githubRepositoryFullName(repoRoot) ?? void 0;
91
+ const normalizedOrigin = origin ? normalizeOriginRemote(origin) : void 0;
92
+ if (normalizedOrigin) {
93
+ return {
94
+ repoRoot,
95
+ repoKey: opaqueRepoKey("origin", normalizedOrigin),
96
+ identitySource: "origin",
97
+ repoFullName
98
+ };
99
+ }
100
+ const roots = gitValue(repoRoot, ["rev-list", "--max-parents=0", "HEAD"])?.split(/\s+/).filter(Boolean).sort();
101
+ if (roots && roots.length > 0) {
102
+ return {
103
+ repoRoot,
104
+ repoKey: opaqueRepoKey("root_commit", roots.join("\n")),
105
+ identitySource: "root_commit",
106
+ repoFullName
107
+ };
108
+ }
109
+ return { repoRoot, repoFullName };
110
+ }
111
+ function nearestExistingAncestor(path) {
112
+ const suffix = [];
113
+ let cursor = path;
114
+ while (true) {
115
+ try {
116
+ lstatSync(cursor);
117
+ break;
118
+ } catch (error) {
119
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
120
+ if (code !== "ENOENT" && code !== "ENOTDIR") return null;
121
+ const parent = dirname(cursor);
122
+ if (parent === cursor) return null;
123
+ suffix.unshift(cursor.slice(parent.length + (parent.endsWith(sep) ? 0 : 1)));
124
+ cursor = parent;
125
+ }
126
+ }
127
+ try {
128
+ return { real: realpathSync.native(cursor), suffix };
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ function canonicalRepositoryPathScoped(filePath, context, baseDir) {
134
+ if (!filePath || filePath !== filePath.trim() || filePath.includes("\0")) {
135
+ return { ok: false, reason: "invalid_path" };
136
+ }
137
+ const absolute = resolve(baseDir, filePath);
138
+ const resolved = nearestExistingAncestor(absolute);
139
+ if (!resolved) return { ok: false, reason: "invalid_path" };
140
+ const target = resolve(resolved.real, ...resolved.suffix);
141
+ const rel = relative(context.repoRoot, target);
142
+ if (!rel || rel === ".") return { ok: false, reason: "invalid_path" };
143
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) {
144
+ return { ok: false, reason: "outside_repository" };
145
+ }
146
+ return { ok: true, file: sep === "/" ? rel : rel.split(sep).join("/") };
147
+ }
148
+ function canonicalRepositoryPathLegacy(filePath, cwd, repositoryRoot = canonicalGitRoot(cwd)) {
149
+ if (!repositoryRoot || !filePath || [...filePath].some((char) => char < " " || char === "\x7F" || "~*?[]{}$`\\".includes(char)))
150
+ return null;
151
+ const absolute = resolve(cwd, filePath);
152
+ let target;
153
+ try {
154
+ const stat = lstatSync(absolute);
155
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
156
+ target = realpathSync.native(absolute);
157
+ } catch (error) {
158
+ if (!(error instanceof Error) || error.code !== "ENOENT") {
159
+ return null;
160
+ }
161
+ try {
162
+ const parent = realpathSync.native(dirname(absolute));
163
+ if (!lstatSync(parent).isDirectory()) return null;
164
+ target = resolve(parent, basename(absolute));
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ const rel = relative(repositoryRoot, target);
170
+ if (!rel || isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) {
171
+ return null;
172
+ }
173
+ return sep === "/" ? rel : rel.split(sep).join("/");
174
+ }
175
+ function canonicalRepositoryPath(filePath, contextOrCwd, baseDirOrRoot) {
176
+ if (typeof contextOrCwd === "string") {
177
+ return canonicalRepositoryPathLegacy(filePath, contextOrCwd, baseDirOrRoot);
178
+ }
179
+ return canonicalRepositoryPathScoped(
180
+ filePath,
181
+ contextOrCwd,
182
+ baseDirOrRoot ?? contextOrCwd.repoRoot
183
+ );
184
+ }
185
+
186
+ // src/lib/activation.ts
187
+ import { execFileSync as execFileSync2 } from "child_process";
188
+ import { existsSync, readFileSync } from "fs";
189
+ import { homedir } from "os";
190
+ import { join } from "path";
191
+ var PRIM_ACTIVE_KEY = "prim.active";
192
+ var PRIM_REPO_SYNC_ID_KEY = "prim.repoSyncId";
193
+ var PRIM_REPO_SYNC_REPOSITORY_KEY = "prim.repoSyncRepository";
194
+ var PRIM_REPO_BINDING_STATE_KEY = "prim.repoBindingState";
195
+ var GIT_TIMEOUT_MS2 = 1e3;
196
+ var REPO_SYNC_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
197
+ var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
198
+ var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
199
+ function repoActiveFlag(cwd) {
200
+ try {
201
+ const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
202
+ cwd,
203
+ encoding: "utf-8",
204
+ stdio: ["ignore", "pipe", "ignore"],
205
+ timeout: GIT_TIMEOUT_MS2
206
+ }).trim();
207
+ if (value === "true") return "true";
208
+ if (value === "false") return "false";
209
+ return void 0;
210
+ } catch {
211
+ return void 0;
212
+ }
213
+ }
214
+ function isRepoActive(cwd) {
215
+ return repoActiveFlag(cwd) === "true";
216
+ }
217
+ function setRepoActive(cwd, active) {
218
+ execFileSync2("git", ["config", "--local", PRIM_ACTIVE_KEY, active ? "true" : "false"], {
219
+ cwd,
220
+ stdio: ["ignore", "ignore", "pipe"],
221
+ timeout: GIT_TIMEOUT_MS2
222
+ });
223
+ }
224
+ function isValidRepoSyncId(value) {
225
+ return typeof value === "string" && REPO_SYNC_ID_RE.test(value);
226
+ }
227
+ function localGitConfigValue(cwd, key) {
228
+ try {
229
+ const value = execFileSync2("git", ["config", "--local", "--get", key], {
230
+ cwd,
231
+ encoding: "utf-8",
232
+ stdio: ["ignore", "pipe", "ignore"],
233
+ timeout: GIT_TIMEOUT_MS2
234
+ }).trim();
235
+ return value || void 0;
236
+ } catch {
237
+ return void 0;
238
+ }
239
+ }
240
+ function repoSyncId(cwd) {
241
+ const value = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
242
+ if (!isValidRepoSyncId(value)) return void 0;
243
+ const repositoryFullName = localGitConfigValue(cwd, PRIM_REPO_SYNC_REPOSITORY_KEY);
244
+ return repositoryFullName === githubRepositoryFullName(cwd) ? value : void 0;
245
+ }
246
+ function setRepoSyncId(cwd, value, repositoryFullName) {
247
+ if (!isValidRepoSyncId(value)) {
248
+ throw new Error("invalid repository binding returned by server");
249
+ }
250
+ execFileSync2("git", ["config", "--local", PRIM_REPO_SYNC_ID_KEY, value], {
251
+ cwd,
252
+ stdio: ["ignore", "ignore", "pipe"],
253
+ timeout: GIT_TIMEOUT_MS2
254
+ });
255
+ execFileSync2("git", ["config", "--local", PRIM_REPO_SYNC_REPOSITORY_KEY, repositoryFullName], {
256
+ cwd,
257
+ stdio: ["ignore", "ignore", "pipe"],
258
+ timeout: GIT_TIMEOUT_MS2
259
+ });
260
+ }
261
+ function setRepositoryBindingState(cwd, state) {
262
+ if (state !== "connected" && state !== "unbound" && state !== "invalid") {
263
+ throw new Error("invalid repository binding state");
264
+ }
265
+ execFileSync2("git", ["config", "--local", PRIM_REPO_BINDING_STATE_KEY, state], {
266
+ cwd,
267
+ stdio: ["ignore", "ignore", "pipe"],
268
+ timeout: GIT_TIMEOUT_MS2
269
+ });
270
+ }
271
+ function repositoryBindingState(cwd) {
272
+ const rawId = localGitConfigValue(cwd, PRIM_REPO_SYNC_ID_KEY);
273
+ const marker = localGitConfigValue(cwd, PRIM_REPO_BINDING_STATE_KEY);
274
+ if (rawId !== void 0 && !isValidRepoSyncId(rawId)) return "invalid";
275
+ if (marker === "unbound" || marker === "invalid") return marker;
276
+ if (marker === "connected") return repoSyncId(cwd) ? "connected" : "invalid";
277
+ if (marker !== void 0) return "invalid";
278
+ return rawId === void 0 ? "unbound" : repoSyncId(cwd) ? void 0 : "unbound";
279
+ }
280
+ function hasProjectPrimInstall(cwd) {
281
+ const root = gitToplevel(cwd);
282
+ if (root === null || root === homedir()) return false;
283
+ for (const rel of PROJECT_INSTALL_FILES) {
284
+ const path = join(root, rel);
285
+ if (existsSync(path)) {
286
+ try {
287
+ const content = readFileSync(path, "utf-8");
288
+ if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
289
+ } catch {
290
+ }
291
+ }
292
+ }
293
+ return false;
294
+ }
295
+ function isRepoActiveForCapture(cwd) {
296
+ const flag = repoActiveFlag(cwd);
297
+ if (flag === "true") return true;
298
+ if (flag === "false") return false;
299
+ return hasProjectPrimInstall(cwd);
300
+ }
301
+ function decisionIngestionStatus(cwd) {
302
+ return isRepoActiveForCapture(cwd) ? "enabled" : "disabled";
303
+ }
304
+
305
+ export {
306
+ gitToplevel,
307
+ githubRepositoryFullName,
308
+ canonicalGitRoot,
309
+ resolveRepositoryContext,
310
+ canonicalRepositoryPath,
311
+ repoActiveFlag,
312
+ isRepoActive,
313
+ setRepoActive,
314
+ isValidRepoSyncId,
315
+ repoSyncId,
316
+ setRepoSyncId,
317
+ setRepositoryBindingState,
318
+ repositoryBindingState,
319
+ isRepoActiveForCapture,
320
+ decisionIngestionStatus
321
+ };
@@ -0,0 +1,52 @@
1
+ // src/hooks/agent.ts
2
+ function parseAgent(argv) {
3
+ const i = argv.indexOf("--agent");
4
+ const value = i !== -1 ? argv[i + 1] : void 0;
5
+ if (value === "codex" || value === "hermes") {
6
+ return value;
7
+ }
8
+ return "claude_code";
9
+ }
10
+
11
+ // src/hooks/normalize.ts
12
+ import { createHash } from "crypto";
13
+ var HERMES_EVENT_MAP = {
14
+ on_session_start: "SessionStart",
15
+ on_session_end: "SessionEnd",
16
+ pre_llm_call: "UserPromptSubmit",
17
+ post_llm_call: "Stop",
18
+ pre_tool_call: "PreToolUse",
19
+ post_tool_call: "PostToolUse",
20
+ subagent_stop: "SubagentStop"
21
+ };
22
+ function normalizeEnvelope(parsed, agent) {
23
+ if (agent === "codex") {
24
+ const event = parsed.hook_event_name;
25
+ if ((event === "PreToolUse" || event === "PostToolUse") && (typeof parsed.tool_use_id !== "string" || parsed.tool_use_id.length === 0)) {
26
+ const stableToolIdentity = JSON.stringify([
27
+ parsed.session_id ?? null,
28
+ parsed.turn_id ?? null,
29
+ parsed.tool_name ?? null,
30
+ parsed.tool_input ?? null
31
+ ]);
32
+ return {
33
+ ...parsed,
34
+ tool_use_id: `codex:fallback:v1:${createHash("sha256").update(stableToolIdentity).digest("hex")}`
35
+ };
36
+ }
37
+ return parsed;
38
+ }
39
+ if (agent !== "hermes") {
40
+ return parsed;
41
+ }
42
+ const raw = parsed.hook_event_name;
43
+ if (typeof raw === "string" && raw in HERMES_EVENT_MAP) {
44
+ return { ...parsed, hook_event_name: HERMES_EVENT_MAP[raw] };
45
+ }
46
+ return parsed;
47
+ }
48
+
49
+ export {
50
+ parseAgent,
51
+ normalizeEnvelope
52
+ };
@@ -0,0 +1,26 @@
1
+ // src/ingest-response.ts
2
+ var IngestAcknowledgementError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "IngestAcknowledgementError";
6
+ }
7
+ };
8
+ function requireDurableIngestAcknowledgement(value, expected) {
9
+ if (typeof value !== "object" || value === null) {
10
+ throw new IngestAcknowledgementError("ingest response was not an object");
11
+ }
12
+ const response = value;
13
+ if (response.disposition !== "persisted") {
14
+ throw new IngestAcknowledgementError("ingest response did not confirm persistence");
15
+ }
16
+ if (response.acknowledged !== expected) {
17
+ throw new IngestAcknowledgementError(
18
+ `ingest acknowledged ${String(response.acknowledged)} of ${String(expected)} moves`
19
+ );
20
+ }
21
+ return value;
22
+ }
23
+
24
+ export {
25
+ requireDurableIngestAcknowledgement
26
+ };