@primitive.ai/prim 0.1.0-alpha.94 → 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.
- package/README.md +7 -4
- package/dist/{chunk-UKUAAAXE.js → chunk-27ADT6FQ.js} +2 -2
- package/dist/{chunk-OTHKG76J.js → chunk-35FMRWJH.js} +1 -1
- package/dist/{chunk-DXJQR4QI.js → chunk-4GHANPWQ.js} +2 -2
- package/dist/{chunk-WUXFLE7X.js → chunk-7KABYYWG.js} +64 -66
- package/dist/{chunk-LFFLHSQO.js → chunk-CU7RW7XU.js} +63 -6
- package/dist/{chunk-H4UNYFUC.js → chunk-DHOQGHKQ.js} +184 -1
- package/dist/{chunk-2GP5UFV7.js → chunk-DWRSEC34.js} +5 -3
- package/dist/{chunk-O7YAW7G6.js → chunk-FVDBULGZ.js} +9 -11
- package/dist/{chunk-G2VMA2QO.js → chunk-LY6NJ7KL.js} +6 -4
- package/dist/{chunk-ZB4F75SZ.js → chunk-MXVCP33E.js} +5 -1
- package/dist/{chunk-TYAHC25F.js → chunk-QCBB5HFX.js} +1 -1
- package/dist/{chunk-IMK4JKTA.js → chunk-RK4ZS3HG.js} +2 -2
- package/dist/{chunk-ZU563PQI.js → chunk-ROLYLXJS.js} +2 -2
- package/dist/{chunk-OH3EZXYX.js → chunk-UO2U6IKF.js} +1 -1
- package/dist/{chunk-K7D2EYBS.js → chunk-URLLSALP.js} +2 -2
- package/dist/{chunk-FXF4JWK2.js → chunk-XEDJ5N3M.js} +17 -2
- package/dist/{chunk-AXQQMX2U.js → chunk-Y5W4TZM7.js} +2 -2
- package/dist/{chunk-ICM2XCXS.js → chunk-YYE2NLWX.js} +2 -2
- package/dist/{chunk-DSWT6BCC.js → chunk-Z6NPNLOT.js} +825 -665
- package/dist/daemon/server.js +836 -540
- package/dist/hook-runtime/daemon/server.js +836 -540
- package/dist/hook-runtime/hooks/post-commit.js +836 -540
- package/dist/hook-runtime/hooks/post-rewrite.js +836 -540
- package/dist/hook-runtime/hooks/post-tool-use.js +1160 -791
- package/dist/hook-runtime/hooks/pre-tool-use.js +2060 -1751
- package/dist/hook-runtime/hooks/prim-hook.js +1257 -856
- package/dist/hook-runtime/hooks/session-end.js +179 -129
- package/dist/hook-runtime/hooks/session-start.js +938 -640
- package/dist/hook-runtime/index.js +892 -554
- package/dist/hooks/post-commit.js +11 -11
- package/dist/hooks/post-rewrite.js +10 -10
- package/dist/hooks/post-tool-use.js +20 -20
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +29 -17
- package/dist/hooks/prim-hook.js +22 -18
- package/dist/hooks/session-end.js +11 -5
- package/dist/hooks/session-start.js +26 -22
- package/dist/index.js +79 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,9 +148,12 @@ graph, and presence is reported. Each hook
|
|
|
148
148
|
self-resolves the CLI at run time (PATH, then a local install, then
|
|
149
149
|
`npx --yes @latest`), so it keeps working with no global install.
|
|
150
150
|
|
|
151
|
-
Installs into the current project by default — the repo's `.claude/settings.json
|
|
152
|
-
|
|
153
|
-
pass `--scope user` to install machine-wide.
|
|
151
|
+
Installs into the current project by default — the repo's `.claude/settings.json`
|
|
152
|
+
or `.cursor/hooks.json`, resolved from the git root (so any subdirectory works);
|
|
153
|
+
pass `--scope user` to install machine-wide. Codex defaults to user scope
|
|
154
|
+
(`~/.codex/hooks.json`): Codex reads a project `.codex/hooks.json` only from the
|
|
155
|
+
checkout it lives in and trusts it by absolute path, so a project install covers
|
|
156
|
+
that checkout only, not the repo's other worktrees. Hermes is the exception: it reads
|
|
154
157
|
shell hooks only from the global `~/.hermes/config.yaml`, so `prim hermes install`
|
|
155
158
|
is always user-scoped — and prim merges in place, leaving the rest of that file
|
|
156
159
|
(providers, models, your own hooks) untouched.
|
|
@@ -160,7 +163,7 @@ prim claude install # Install Claude Code hooks (project scope; u
|
|
|
160
163
|
prim claude install --scope user # Install machine-wide instead
|
|
161
164
|
prim cursor install # Install native Cursor hooks (project scope)
|
|
162
165
|
prim cursor install --scope user # Install hooks, skill, and CLI footer machine-wide
|
|
163
|
-
prim codex install # Install OpenAI Codex hooks (project
|
|
166
|
+
prim codex install # Install OpenAI Codex hooks (user scope; --scope project for this checkout only)
|
|
164
167
|
prim hermes install # Install Hermes Agent hooks (global ~/.hermes/config.yaml)
|
|
165
168
|
```
|
|
166
169
|
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getClient,
|
|
3
3
|
getSiteUrl,
|
|
4
4
|
terminalSafeLine
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-LY6NJ7KL.js";
|
|
6
6
|
import {
|
|
7
7
|
daemonRequest
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-35FMRWJH.js";
|
|
9
9
|
|
|
10
10
|
// src/daemon/proxy.ts
|
|
11
11
|
var DAEMON_HTTP_TIMEOUT_MS = 1e4;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSiteUrl
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LY6NJ7KL.js";
|
|
4
4
|
import {
|
|
5
5
|
atomicWriteFile,
|
|
6
6
|
ensureDurableDirectory,
|
|
7
7
|
jwtOrganizationId,
|
|
8
8
|
primConfigDirectory,
|
|
9
9
|
resolveAuthCredential
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DWRSEC34.js";
|
|
11
11
|
|
|
12
12
|
// src/journal.ts
|
|
13
13
|
import {
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
unattendedEnv
|
|
3
3
|
} from "./chunk-OYHYCK4K.js";
|
|
4
|
-
import {
|
|
5
|
-
isValidRepoSyncId,
|
|
6
|
-
setRepoSyncId,
|
|
7
|
-
setRepositoryBindingState
|
|
8
|
-
} from "./chunk-DSWT6BCC.js";
|
|
9
4
|
import {
|
|
10
5
|
HttpError,
|
|
11
6
|
getClient
|
|
12
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LY6NJ7KL.js";
|
|
13
8
|
import {
|
|
14
9
|
GIT_HOOK_CONTRACT_VERSION,
|
|
15
10
|
binFile,
|
|
@@ -22,49 +17,19 @@ import {
|
|
|
22
17
|
managedHookBlock,
|
|
23
18
|
pinnedHookCommand,
|
|
24
19
|
pinnedNpxCommand
|
|
25
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-Y5W4TZM7.js";
|
|
26
21
|
import {
|
|
27
22
|
gitToplevel,
|
|
28
|
-
githubRepositoryFullName
|
|
29
|
-
|
|
23
|
+
githubRepositoryFullName,
|
|
24
|
+
isValidRepoSyncId,
|
|
25
|
+
setRepoSyncId,
|
|
26
|
+
setRepositoryBindingState
|
|
27
|
+
} from "./chunk-DHOQGHKQ.js";
|
|
30
28
|
import {
|
|
31
29
|
atomicWriteFile,
|
|
32
30
|
primConfigDirectory,
|
|
33
31
|
withFileLock
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
|
|
36
|
-
// src/lib/bin-cache.ts
|
|
37
|
-
import { chmodSync, mkdirSync } from "fs";
|
|
38
|
-
import { homedir } from "os";
|
|
39
|
-
import { join } from "path";
|
|
40
|
-
var GIT_HOOK_CACHE_SHELL_DIR = "${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin";
|
|
41
|
-
var GIT_HOOK_CACHE_TTL_MINUTES = 1440;
|
|
42
|
-
var GIT_HOOK_CACHED_BINS = ["prim-post-commit", "prim-post-rewrite"];
|
|
43
|
-
function binCacheDir() {
|
|
44
|
-
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
45
|
-
return join(base, "prim", "bin");
|
|
46
|
-
}
|
|
47
|
-
function writeAtomic(path, content) {
|
|
48
|
-
atomicWriteFile(path, content, { mode: 384 });
|
|
49
|
-
}
|
|
50
|
-
function warmBinCache() {
|
|
51
|
-
try {
|
|
52
|
-
if (process.env.PRIM_BIN_CACHE === "0") {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const dir = binCacheDir();
|
|
56
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
57
|
-
chmodSync(dir, 448);
|
|
58
|
-
writeAtomic(join(dir, "node"), process.execPath);
|
|
59
|
-
for (const bin of GIT_HOOK_CACHED_BINS) {
|
|
60
|
-
const file = binFile(bin);
|
|
61
|
-
if (file) {
|
|
62
|
-
writeAtomic(join(dir, bin), file);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
}
|
|
67
|
-
}
|
|
32
|
+
} from "./chunk-DWRSEC34.js";
|
|
68
33
|
|
|
69
34
|
// src/commands/skill.ts
|
|
70
35
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -81,8 +46,8 @@ import {
|
|
|
81
46
|
rmdirSync as rmdirSync2,
|
|
82
47
|
unlinkSync
|
|
83
48
|
} from "fs";
|
|
84
|
-
import { homedir as
|
|
85
|
-
import { dirname as dirname2, join as
|
|
49
|
+
import { homedir as homedir2 } from "os";
|
|
50
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
86
51
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
87
52
|
import { createPatch } from "diff";
|
|
88
53
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -100,7 +65,7 @@ import {
|
|
|
100
65
|
existsSync,
|
|
101
66
|
fstatSync,
|
|
102
67
|
lstatSync,
|
|
103
|
-
mkdirSync
|
|
68
|
+
mkdirSync,
|
|
104
69
|
openSync,
|
|
105
70
|
readFileSync,
|
|
106
71
|
readSync,
|
|
@@ -109,8 +74,8 @@ import {
|
|
|
109
74
|
rmSync,
|
|
110
75
|
rmdirSync
|
|
111
76
|
} from "fs";
|
|
112
|
-
import { homedir
|
|
113
|
-
import { dirname, isAbsolute, join
|
|
77
|
+
import { homedir } from "os";
|
|
78
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
114
79
|
import { fileURLToPath } from "url";
|
|
115
80
|
import { parse as parseYaml } from "yaml";
|
|
116
81
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -120,8 +85,8 @@ var MAX_SKILL_BYTES = 1024 * 1024;
|
|
|
120
85
|
var REFRESH_LOCK_TIMEOUT_MS = 150;
|
|
121
86
|
var REFRESH_LOCK_POLL_MS = 10;
|
|
122
87
|
function pluginDirFor(cwd, scope) {
|
|
123
|
-
const base = scope === "user" ?
|
|
124
|
-
return
|
|
88
|
+
const base = scope === "user" ? join(homedir(), ".claude") : join(gitToplevel(cwd) ?? cwd, ".claude");
|
|
89
|
+
return join(base, "skills", "prim");
|
|
125
90
|
}
|
|
126
91
|
function resolvePluginDir(cwd, scope) {
|
|
127
92
|
if (scope && scope !== "user" && scope !== "project") {
|
|
@@ -153,8 +118,8 @@ function removeDirIfEmpty(dir) {
|
|
|
153
118
|
}
|
|
154
119
|
function pluginPaths(dir) {
|
|
155
120
|
return {
|
|
156
|
-
manifestPath:
|
|
157
|
-
skillPath:
|
|
121
|
+
manifestPath: join(dir, ".claude-plugin", "plugin.json"),
|
|
122
|
+
skillPath: join(dir, "SKILL.md")
|
|
158
123
|
};
|
|
159
124
|
}
|
|
160
125
|
function pathIsInside(root, path) {
|
|
@@ -219,9 +184,9 @@ function inspectRecognizedInstallation(paths, projectRoot) {
|
|
|
219
184
|
};
|
|
220
185
|
}
|
|
221
186
|
function refreshLockDir(physicalPluginDir) {
|
|
222
|
-
const cacheRoot = process.env.XDG_CACHE_HOME ||
|
|
187
|
+
const cacheRoot = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
223
188
|
const identity = createHash("sha256").update(physicalPluginDir).digest("hex");
|
|
224
|
-
return
|
|
189
|
+
return join(cacheRoot, "prim", "skill-refresh-locks", `${identity}.lock`);
|
|
225
190
|
}
|
|
226
191
|
async function refreshClaudePlugins(cwd, options = {}) {
|
|
227
192
|
const result = { installed: 0, refreshed: 0 };
|
|
@@ -229,7 +194,7 @@ async function refreshClaudePlugins(cwd, options = {}) {
|
|
|
229
194
|
if (options.includeProject !== false) {
|
|
230
195
|
const projectRoot = options.projectRoot ?? gitToplevel(cwd);
|
|
231
196
|
if (projectRoot !== null && isAbsolute(projectRoot)) {
|
|
232
|
-
candidates.push({ dir:
|
|
197
|
+
candidates.push({ dir: join(projectRoot, ".claude", "skills", "prim"), projectRoot });
|
|
233
198
|
}
|
|
234
199
|
}
|
|
235
200
|
candidates.push({ dir: pluginDirFor(cwd, "user") });
|
|
@@ -305,7 +270,7 @@ function installClaudePlugin(cwd, opts) {
|
|
|
305
270
|
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
306
271
|
return 0;
|
|
307
272
|
}
|
|
308
|
-
|
|
273
|
+
mkdirSync(join(dir, ".claude-plugin"), { recursive: true });
|
|
309
274
|
atomicWrite(manifestPath, manifest);
|
|
310
275
|
atomicWrite(skillPath, skill);
|
|
311
276
|
console.log(`Installed prim skill plugin at ${dir}`);
|
|
@@ -325,7 +290,7 @@ function uninstallClaudePlugin(cwd, opts) {
|
|
|
325
290
|
}
|
|
326
291
|
rmSync(manifestPath, { force: true });
|
|
327
292
|
rmSync(skillPath, { force: true });
|
|
328
|
-
removeDirIfEmpty(
|
|
293
|
+
removeDirIfEmpty(join(dir, ".claude-plugin"));
|
|
329
294
|
removeDirIfEmpty(dir);
|
|
330
295
|
console.log(`Removed prim skill plugin from ${dir}`);
|
|
331
296
|
return 0;
|
|
@@ -380,10 +345,10 @@ function readCodexGuidance(path) {
|
|
|
380
345
|
}
|
|
381
346
|
}
|
|
382
347
|
function effectiveCodexGuidance(scope) {
|
|
383
|
-
const override = readCodexGuidance(
|
|
348
|
+
const override = readCodexGuidance(join2(scope, "AGENTS.override.md"));
|
|
384
349
|
if (override === null) return null;
|
|
385
350
|
if (override !== void 0 && override.toString("utf8").trim().length > 0) return override;
|
|
386
|
-
const base = readCodexGuidance(
|
|
351
|
+
const base = readCodexGuidance(join2(scope, "AGENTS.md"));
|
|
387
352
|
return base ?? null;
|
|
388
353
|
}
|
|
389
354
|
function hasUsablePrimBlock(content, visibleBytes) {
|
|
@@ -406,7 +371,7 @@ function hasUsablePrimBlock(content, visibleBytes) {
|
|
|
406
371
|
}
|
|
407
372
|
function hasUsableCodexGuidance(projectRoot) {
|
|
408
373
|
try {
|
|
409
|
-
const codexHome = process.env.CODEX_HOME?.trim() ? resolve2(process.env.CODEX_HOME) :
|
|
374
|
+
const codexHome = process.env.CODEX_HOME?.trim() ? resolve2(process.env.CODEX_HOME) : join2(homedir2(), ".codex");
|
|
410
375
|
const scopes = [
|
|
411
376
|
{ path: codexHome, visibleBytes: CODEX_GUIDANCE_LIMIT_BYTES },
|
|
412
377
|
{ path: projectRoot, visibleBytes: CODEX_DEFAULT_PROJECT_GUIDANCE_BYTES }
|
|
@@ -440,14 +405,14 @@ function sha256(content) {
|
|
|
440
405
|
}
|
|
441
406
|
function cursorSkillDirectory(cwd, scope) {
|
|
442
407
|
const base = scope === "user" ? cursorConfigDirectory() : gitToplevel(cwd) ?? cwd;
|
|
443
|
-
return
|
|
408
|
+
return join2(base, scope === "user" ? "skills" : ".cursor/skills", "prim");
|
|
444
409
|
}
|
|
445
410
|
function cursorSkillPaths(cwd, scope) {
|
|
446
411
|
const directory = cursorSkillDirectory(cwd, scope);
|
|
447
412
|
return {
|
|
448
413
|
directory,
|
|
449
|
-
skill:
|
|
450
|
-
manifest:
|
|
414
|
+
skill: join2(directory, "SKILL.md"),
|
|
415
|
+
manifest: join2(directory, CURSOR_SKILL_MANIFEST)
|
|
451
416
|
};
|
|
452
417
|
}
|
|
453
418
|
function readCursorSkillManifest(path) {
|
|
@@ -535,10 +500,10 @@ function hasUsableCursorSkill(projectRoot) {
|
|
|
535
500
|
return cursorSkillStatus(projectRoot, "project").installed || cursorSkillStatus(projectRoot, "user").installed;
|
|
536
501
|
}
|
|
537
502
|
function userTargetFor(agent) {
|
|
538
|
-
if (agent === "claude") return
|
|
539
|
-
if (agent === "codex") return
|
|
503
|
+
if (agent === "claude") return join2(homedir2(), ".claude", "CLAUDE.md");
|
|
504
|
+
if (agent === "codex") return join2(homedir2(), ".codex", "AGENTS.md");
|
|
540
505
|
if (agent === "hermes") {
|
|
541
|
-
return
|
|
506
|
+
return join2(process.env.HERMES_HOME ?? join2(homedir2(), ".hermes"), ".hermes.md");
|
|
542
507
|
}
|
|
543
508
|
return null;
|
|
544
509
|
}
|
|
@@ -709,6 +674,39 @@ function registerSkillCommands(program) {
|
|
|
709
674
|
});
|
|
710
675
|
}
|
|
711
676
|
|
|
677
|
+
// src/lib/bin-cache.ts
|
|
678
|
+
import { chmodSync, mkdirSync as mkdirSync2 } from "fs";
|
|
679
|
+
import { homedir as homedir3 } from "os";
|
|
680
|
+
import { join as join3 } from "path";
|
|
681
|
+
var GIT_HOOK_CACHE_SHELL_DIR = "${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin";
|
|
682
|
+
var GIT_HOOK_CACHE_TTL_MINUTES = 1440;
|
|
683
|
+
var GIT_HOOK_CACHED_BINS = ["prim-post-commit", "prim-post-rewrite"];
|
|
684
|
+
function binCacheDir() {
|
|
685
|
+
const base = process.env.XDG_CACHE_HOME || join3(homedir3(), ".cache");
|
|
686
|
+
return join3(base, "prim", "bin");
|
|
687
|
+
}
|
|
688
|
+
function writeAtomic(path, content) {
|
|
689
|
+
atomicWriteFile(path, content, { mode: 384 });
|
|
690
|
+
}
|
|
691
|
+
function warmBinCache() {
|
|
692
|
+
try {
|
|
693
|
+
if (process.env.PRIM_BIN_CACHE === "0") {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const dir = binCacheDir();
|
|
697
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
698
|
+
chmodSync(dir, 448);
|
|
699
|
+
writeAtomic(join3(dir, "node"), process.execPath);
|
|
700
|
+
for (const bin of GIT_HOOK_CACHED_BINS) {
|
|
701
|
+
const file = binFile(bin);
|
|
702
|
+
if (file) {
|
|
703
|
+
writeAtomic(join3(dir, bin), file);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
712
710
|
// src/lib/git-hook-legacy.ts
|
|
713
711
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
714
712
|
var PRIM_MANAGED_MARK = "prim-managed-hook";
|
|
@@ -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-
|
|
7
|
+
} from "./chunk-MXVCP33E.js";
|
|
8
|
+
import {
|
|
9
|
+
scrubEnvironmentPaths
|
|
10
|
+
} from "./chunk-URLLSALP.js";
|
|
5
11
|
import {
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
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
|
|
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");
|