@primitive.ai/prim 0.1.0-alpha.63 → 0.1.0-alpha.65
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 +5 -4
- package/dist/{chunk-QGVHLP5J.js → chunk-5CKL5EYQ.js} +55 -6
- package/dist/{chunk-DA4BMHEB.js → chunk-BDGQEJA5.js} +3 -3
- package/dist/{chunk-WNIY6Z6N.js → chunk-CPHJUQNE.js} +38 -13
- package/dist/chunk-G5D3FF3D.js +853 -0
- package/dist/{chunk-KBFFVYBC.js → chunk-LNU44OJV.js} +3 -5
- package/dist/{chunk-XTNMSS4C.js → chunk-TDRXHBEX.js} +15 -0
- package/dist/hooks/post-commit.js +29 -5
- package/dist/hooks/post-tool-use.js +31 -19
- package/dist/hooks/pre-tool-use.js +11 -3
- package/dist/hooks/prim-hook.js +5 -5
- package/dist/hooks/session-start.js +9 -12
- package/dist/index.js +103 -20
- package/package.json +1 -1
- package/dist/chunk-GF5QII7T.js +0 -411
package/README.md
CHANGED
|
@@ -71,12 +71,13 @@ so the agent runs it directly — instead of fetching a doc and executing a comm
|
|
|
71
71
|
it, which an auto-mode safety classifier blocks as untrusted-origin code:
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
|
-
Install the Primitive CLI: run `npx --yes @primitive.ai/prim@latest setup` and surface its output.
|
|
74
|
+
Install the Primitive CLI and activate passive decision capture for this repository: run `npx --yes @primitive.ai/prim@latest setup` and surface its output. Drive it yourself; I'll only click Authorize in the browser. This request authorizes setup's built-in activation of this repository and an idempotent retry of that step, but not activation in another repository or creation of a Decision. If enable or health fails, surface the actual error; do not claim fresh activation approval is required. Then show me the welcome.
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
That single command runs the whole install
|
|
78
|
-
daemon → git hooks → skill → welcome). The detailed
|
|
79
|
-
and the manual fallback — live in
|
|
77
|
+
That single command runs the whole install and current-repository activation in one
|
|
78
|
+
process (auth → session hooks → daemon → git hooks → skill → welcome). The detailed
|
|
79
|
+
step-by-step the agent follows — and the manual fallback — live in
|
|
80
|
+
[`setup.md`](./setup.md).
|
|
80
81
|
|
|
81
82
|
## Commands
|
|
82
83
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-UVIWZWM6.js";
|
|
4
4
|
import {
|
|
5
5
|
isValidRepoSyncId
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-TDRXHBEX.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/prim-hook-core.ts
|
|
9
9
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -15,15 +15,61 @@ var AGENT_ENVELOPE_VERSION = 3;
|
|
|
15
15
|
var ENVELOPE_VERSION = LEGACY_ENVELOPE_VERSION;
|
|
16
16
|
|
|
17
17
|
// src/hooks/prim-hook-core.ts
|
|
18
|
+
var CORRELATED_TOOL_EVENTS = /* @__PURE__ */ new Set([
|
|
19
|
+
"PostToolUse",
|
|
20
|
+
"PostToolUseFailure",
|
|
21
|
+
"post_tool_call",
|
|
22
|
+
"post_approval_response"
|
|
23
|
+
]);
|
|
18
24
|
function postToolInvocationId(parsed, agent) {
|
|
19
|
-
if (parsed.hook_event_name !== "
|
|
25
|
+
if (typeof parsed.hook_event_name !== "string" || !CORRELATED_TOOL_EVENTS.has(parsed.hook_event_name)) {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
20
28
|
const raw = agent === "hermes" ? parsed.extra?.tool_call_id : parsed.tool_use_id;
|
|
21
29
|
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
22
30
|
}
|
|
31
|
+
function lowerString(value) {
|
|
32
|
+
return typeof value === "string" ? value.toLowerCase() : void 0;
|
|
33
|
+
}
|
|
34
|
+
function toolOutcomeFor(parsed, agent) {
|
|
35
|
+
const event = parsed.hook_event_name;
|
|
36
|
+
if (agent === "claude_code") {
|
|
37
|
+
if (event === "PostToolUseFailure") {
|
|
38
|
+
return parsed.is_interrupt === true ? "interrupted" : "failed";
|
|
39
|
+
}
|
|
40
|
+
return event === "PostToolUse" ? "succeeded" : void 0;
|
|
41
|
+
}
|
|
42
|
+
if (agent === "codex") {
|
|
43
|
+
return event === "PostToolUse" ? "returned" : void 0;
|
|
44
|
+
}
|
|
45
|
+
const extra = parsed.extra;
|
|
46
|
+
if (event === "post_approval_response") {
|
|
47
|
+
const choice = lowerString(extra?.choice);
|
|
48
|
+
return (choice === "deny" || choice === "smart_deny" || choice === "timeout") && postToolInvocationId(parsed, agent) ? "prevented" : void 0;
|
|
49
|
+
}
|
|
50
|
+
if (event !== "PostToolUse" && event !== "post_tool_call") {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
const status = lowerString(extra?.status);
|
|
54
|
+
if (status === "ok" || status === "success") {
|
|
55
|
+
return "succeeded";
|
|
56
|
+
}
|
|
57
|
+
if (status === "error" || status === "timeout") {
|
|
58
|
+
return "failed";
|
|
59
|
+
}
|
|
60
|
+
if (status === "cancelled") {
|
|
61
|
+
return "interrupted";
|
|
62
|
+
}
|
|
63
|
+
if (status === "blocked") {
|
|
64
|
+
return "prevented";
|
|
65
|
+
}
|
|
66
|
+
return "unknown";
|
|
67
|
+
}
|
|
23
68
|
function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, repository, invocationId) {
|
|
24
69
|
const sessionId = parsed.session_id ?? "";
|
|
25
70
|
const eventType = parsed.hook_event_name ?? "unknown";
|
|
26
|
-
const
|
|
71
|
+
const toolOutcome = toolOutcomeFor(parsed, agent);
|
|
72
|
+
const moveId = CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId ? `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}` : randomUUID();
|
|
27
73
|
return {
|
|
28
74
|
moveId,
|
|
29
75
|
capturedAt: Date.now(),
|
|
@@ -45,7 +91,8 @@ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, reposito
|
|
|
45
91
|
},
|
|
46
92
|
envelopeVersion: AGENT_ENVELOPE_VERSION,
|
|
47
93
|
producer: agent,
|
|
48
|
-
...invocationId ? { invocationId } : {}
|
|
94
|
+
...invocationId ? { invocationId } : {},
|
|
95
|
+
...toolOutcome ? { toolOutcome } : {}
|
|
49
96
|
};
|
|
50
97
|
}
|
|
51
98
|
var MAX_CLASSIFIER_ID_CHARS = 256;
|
|
@@ -63,17 +110,18 @@ function commitAttributionFromEnvironment(env, workspaceId) {
|
|
|
63
110
|
return candidates.length === 1 ? candidates[0] : void 0;
|
|
64
111
|
}
|
|
65
112
|
function toCommitMove(commit, cliVersion, cwd, context = {}) {
|
|
66
|
-
const { repository, repoFullName, repoSyncId, workspaceId, attribution } = context;
|
|
113
|
+
const { repository, repoFullName, repoSyncId, workspaceId, attribution, capturedAt } = context;
|
|
67
114
|
const validatedRepoSyncId = isValidRepoSyncId(repoSyncId) ? repoSyncId : void 0;
|
|
68
115
|
const validatedWorkspaceId = workspaceId && isCanonicalWorkspaceId(workspaceId) ? workspaceId : void 0;
|
|
69
116
|
const validatedAttribution = validatedWorkspaceId ? attribution : void 0;
|
|
117
|
+
const validatedCapturedAt = typeof capturedAt === "number" && Number.isSafeInteger(capturedAt) && capturedAt > 0 ? capturedAt : Date.now();
|
|
70
118
|
const fullSha = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(commit.sha);
|
|
71
119
|
const moveId = validatedRepoSyncId && validatedWorkspaceId && fullSha ? `commit:v2:${createHash("sha256").update(
|
|
72
120
|
JSON.stringify(["commit:v2", validatedRepoSyncId, validatedWorkspaceId, commit.sha])
|
|
73
121
|
).digest("hex")}` : `commit:v2:unscoped:${randomUUID()}`;
|
|
74
122
|
const base = {
|
|
75
123
|
moveId,
|
|
76
|
-
capturedAt:
|
|
124
|
+
capturedAt: validatedCapturedAt,
|
|
77
125
|
sessionId: validatedAttribution?.sessionId ?? "",
|
|
78
126
|
eventType: "git.commit",
|
|
79
127
|
payload: {
|
|
@@ -110,6 +158,7 @@ function shouldFlushAfter(eventType) {
|
|
|
110
158
|
|
|
111
159
|
export {
|
|
112
160
|
postToolInvocationId,
|
|
161
|
+
toolOutcomeFor,
|
|
113
162
|
toMove,
|
|
114
163
|
commitAttributionFromEnvironment,
|
|
115
164
|
toCommitMove,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeShellTargets,
|
|
3
3
|
extractFileTargets
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G5D3FF3D.js";
|
|
5
5
|
import {
|
|
6
6
|
canonicalRepositoryPath
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TDRXHBEX.js";
|
|
8
8
|
|
|
9
9
|
// src/hooks/file-refs.ts
|
|
10
10
|
var MAX_PREFLIGHT_FILE_TARGETS = 25;
|
|
@@ -26,7 +26,7 @@ function preserveHookFileMetadata(payload, resolution) {
|
|
|
26
26
|
return { ...payload, primitive };
|
|
27
27
|
}
|
|
28
28
|
function resolveHookFileRefs(args) {
|
|
29
|
-
const command = args.agent === "claude_code" && args.toolName === "Bash" && args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.command === "string" ? args.toolInput.command : void 0;
|
|
29
|
+
const command = (args.agent === "claude_code" || args.agent === "codex") && args.toolName === "Bash" && args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.command === "string" ? args.toolInput.command : void 0;
|
|
30
30
|
const shell = command === void 0 ? void 0 : analyzeShellTargets(command);
|
|
31
31
|
const shellMutation = shell?.mutation === "none" ? "none" : shell?.coverage === "complete" ? "resolved" : shell ? "unresolved" : void 0;
|
|
32
32
|
const readPath = args.agent === "claude_code" && args.toolName === "Read" && args.toolInput && typeof args.toolInput === "object" && typeof args.toolInput.file_path === "string" ? [args.toolInput.file_path] : void 0;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
clearRepoSyncId,
|
|
2
3
|
gitToplevel,
|
|
3
4
|
githubRepositoryFullName,
|
|
4
5
|
isValidRepoSyncId,
|
|
5
6
|
setRepoSyncId
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TDRXHBEX.js";
|
|
7
8
|
import {
|
|
9
|
+
HttpError,
|
|
8
10
|
getClient,
|
|
9
11
|
withFileLock
|
|
10
12
|
} from "./chunk-CPX3BUOC.js";
|
|
@@ -649,6 +651,7 @@ if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
|
649
651
|
case "$prim_commit_sha" in *[!0-9a-f]*|"") prim_commit_sha= ;; esac
|
|
650
652
|
case "\${#prim_commit_sha}" in 40|64) ;; *) prim_commit_sha= ;; esac
|
|
651
653
|
if [ -n "$prim_commit_sha" ]; then
|
|
654
|
+
prim_commit_observed_file=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_commit_observed_file=
|
|
652
655
|
prim_commit_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_commit_branch=
|
|
653
656
|
prim_cache_dir="\${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin"
|
|
654
657
|
prim_post_commit_ran=0
|
|
@@ -656,18 +659,23 @@ if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
|
656
659
|
prim_node=$(cat "$prim_cache_dir/node")
|
|
657
660
|
prim_entry=$(cat "$prim_cache_dir/prim-post-commit")
|
|
658
661
|
if [ -x "$prim_node" ] && [ -f "$prim_entry" ]; then
|
|
659
|
-
( trap '' HUP; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch"; "$prim_node" "$prim_entry" ) </dev/null >/dev/null 2>&1 &
|
|
662
|
+
( trap '' HUP; trap 'if [ -n "$prim_commit_observed_file" ]; then rm -f "$prim_commit_observed_file"; fi' 0; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch" PRIM_COMMIT_OBSERVED_FILE="$prim_commit_observed_file"; "$prim_node" "$prim_entry" ) </dev/null >/dev/null 2>&1 &
|
|
660
663
|
prim_post_commit_ran=1
|
|
661
664
|
fi
|
|
662
665
|
fi
|
|
663
666
|
if [ "$prim_post_commit_ran" -eq 0 ]; then
|
|
664
667
|
if [ -x "./node_modules/.bin/prim-post-commit" ]; then
|
|
665
|
-
( trap '' HUP; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch"; ./node_modules/.bin/prim-post-commit ) </dev/null >/dev/null 2>&1 &
|
|
668
|
+
( trap '' HUP; trap 'if [ -n "$prim_commit_observed_file" ]; then rm -f "$prim_commit_observed_file"; fi' 0; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch" PRIM_COMMIT_OBSERVED_FILE="$prim_commit_observed_file"; ./node_modules/.bin/prim-post-commit ) </dev/null >/dev/null 2>&1 &
|
|
669
|
+
prim_post_commit_ran=1
|
|
666
670
|
elif command -v npx >/dev/null 2>&1; then
|
|
667
|
-
( trap '' HUP; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch"; npx --yes -p @primitive.ai/prim@latest prim-post-commit ) </dev/null >/dev/null 2>&1 &
|
|
671
|
+
( trap '' HUP; trap 'if [ -n "$prim_commit_observed_file" ]; then rm -f "$prim_commit_observed_file"; fi' 0; export PRIM_COMMIT_SHA="$prim_commit_sha" PRIM_COMMIT_BRANCH="$prim_commit_branch" PRIM_COMMIT_OBSERVED_FILE="$prim_commit_observed_file"; npx --yes -p @primitive.ai/prim@latest prim-post-commit ) </dev/null >/dev/null 2>&1 &
|
|
672
|
+
prim_post_commit_ran=1
|
|
668
673
|
fi
|
|
669
674
|
fi
|
|
670
|
-
|
|
675
|
+
if [ "$prim_post_commit_ran" -eq 0 ] && [ -n "$prim_commit_observed_file" ]; then
|
|
676
|
+
rm -f "$prim_commit_observed_file"
|
|
677
|
+
fi
|
|
678
|
+
unset prim_cache_dir prim_post_commit_ran prim_node prim_entry prim_commit_branch prim_commit_observed_file
|
|
671
679
|
fi
|
|
672
680
|
unset prim_commit_sha
|
|
673
681
|
fi
|
|
@@ -1211,21 +1219,37 @@ function uninstallPostCommitHookAtPath(hookPath) {
|
|
|
1211
1219
|
}
|
|
1212
1220
|
|
|
1213
1221
|
// src/lib/repository-binding.ts
|
|
1214
|
-
async function
|
|
1222
|
+
async function resolveRepositoryBinding(root, options) {
|
|
1215
1223
|
const repositoryFullName = githubRepositoryFullName(root);
|
|
1216
1224
|
if (!repositoryFullName) {
|
|
1217
1225
|
throw new Error("origin must be a GitHub HTTPS/SSH remote in owner/name form");
|
|
1218
1226
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1227
|
+
let response;
|
|
1228
|
+
try {
|
|
1229
|
+
response = await getClient().post(
|
|
1230
|
+
"/api/cli/repositories/bind",
|
|
1231
|
+
{ repositoryFullName },
|
|
1232
|
+
options
|
|
1233
|
+
);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
1236
|
+
return { status: "pending", repositoryFullName };
|
|
1237
|
+
}
|
|
1238
|
+
throw error;
|
|
1239
|
+
}
|
|
1224
1240
|
if (!isValidRepoSyncId(response.repoSyncId)) {
|
|
1225
1241
|
throw new Error("server returned no repository binding");
|
|
1226
1242
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1243
|
+
return { status: "connected", repoSyncId: response.repoSyncId, repositoryFullName };
|
|
1244
|
+
}
|
|
1245
|
+
async function bindRepository(root, options) {
|
|
1246
|
+
const binding = await resolveRepositoryBinding(root, options);
|
|
1247
|
+
if (binding.status === "connected") {
|
|
1248
|
+
setRepoSyncId(root, binding.repoSyncId);
|
|
1249
|
+
} else {
|
|
1250
|
+
clearRepoSyncId(root);
|
|
1251
|
+
}
|
|
1252
|
+
return binding;
|
|
1229
1253
|
}
|
|
1230
1254
|
|
|
1231
1255
|
export {
|
|
@@ -1235,6 +1259,7 @@ export {
|
|
|
1235
1259
|
inspectEffectivePostCommitHook,
|
|
1236
1260
|
uninstallProjectPostCommitHook,
|
|
1237
1261
|
uninstallPostCommitHookAtPath,
|
|
1262
|
+
resolveRepositoryBinding,
|
|
1238
1263
|
bindRepository,
|
|
1239
1264
|
printJson,
|
|
1240
1265
|
compareSemver,
|