@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.
@@ -1,8 +1,3 @@
1
- // src/lib/bin-cache.ts
2
- import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
3
- import { homedir } from "os";
4
- import { join as join2 } from "path";
5
-
6
1
  // src/lib/bin-path.ts
7
2
  import { existsSync, readFileSync } from "fs";
8
3
  import { dirname, isAbsolute, join } from "path";
@@ -76,6 +71,9 @@ function commandMatchesBin(command, bin) {
76
71
  }
77
72
 
78
73
  // src/lib/bin-cache.ts
74
+ import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
75
+ import { homedir } from "os";
76
+ import { join as join2 } from "path";
79
77
  var CACHED_BINS = [
80
78
  "prim",
81
79
  "prim-hook",
@@ -241,6 +241,20 @@ function setRepoSyncId(cwd, value) {
241
241
  timeout: GIT_TIMEOUT_MS2
242
242
  });
243
243
  }
244
+ function clearRepoSyncId(cwd) {
245
+ try {
246
+ execFileSync2("git", ["config", "--local", "--unset-all", PRIM_REPO_SYNC_ID_KEY], {
247
+ cwd,
248
+ stdio: ["ignore", "ignore", "pipe"],
249
+ timeout: GIT_TIMEOUT_MS2
250
+ });
251
+ } catch (error) {
252
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 5) {
253
+ return;
254
+ }
255
+ throw error;
256
+ }
257
+ }
244
258
  function hasProjectPrimInstall(cwd) {
245
259
  const root = gitToplevel(cwd);
246
260
  if (root === null || root === homedir()) return false;
@@ -278,6 +292,7 @@ export {
278
292
  isValidRepoSyncId,
279
293
  repoSyncId,
280
294
  setRepoSyncId,
295
+ clearRepoSyncId,
281
296
  isRepoActiveForCapture,
282
297
  decisionIngestionStatus
283
298
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  commitAttributionFromEnvironment,
4
4
  toCommitMove
5
- } from "../chunk-QGVHLP5J.js";
5
+ } from "../chunk-5CKL5EYQ.js";
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg
@@ -15,17 +15,35 @@ import {
15
15
  isRepoActiveForCapture,
16
16
  repoSyncId,
17
17
  resolveRepositoryContext
18
- } from "../chunk-XTNMSS4C.js";
18
+ } from "../chunk-TDRXHBEX.js";
19
19
  import "../chunk-CPX3BUOC.js";
20
20
 
21
21
  // src/hooks/post-commit.ts
22
22
  import { execFileSync, spawn } from "child_process";
23
- import { readFileSync } from "fs";
24
- import { dirname, join } from "path";
23
+ import { lstatSync, readFileSync } from "fs";
24
+ import { basename, dirname, isAbsolute, join } from "path";
25
25
  import { fileURLToPath } from "url";
26
26
  var here = dirname(fileURLToPath(import.meta.url));
27
27
  var GIT_TIMEOUT_MS = 1e3;
28
28
  var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
29
+ var OBSERVATION_MARKER_RE = /^prim-post-commit-observed\.[A-Za-z0-9]+$/u;
30
+ var MAX_OBSERVATION_MARKER_AGE_MS = 10 * 60 * 1e3;
31
+ var MAX_OBSERVATION_MARKER_FUTURE_SKEW_MS = 5e3;
32
+ function capturedAtFromLauncher(env = process.env, now = Date.now()) {
33
+ const path = env.PRIM_COMMIT_OBSERVED_FILE;
34
+ if (!path || !isAbsolute(path) || !OBSERVATION_MARKER_RE.test(basename(path))) return;
35
+ try {
36
+ const stat = lstatSync(path);
37
+ const capturedAt = Math.trunc(stat.mtimeMs);
38
+ const uid = process.getuid?.();
39
+ 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) {
40
+ return;
41
+ }
42
+ return capturedAt;
43
+ } catch {
44
+ return;
45
+ }
46
+ }
29
47
  function gitText(args) {
30
48
  try {
31
49
  return execFileSync("git", args, {
@@ -117,6 +135,10 @@ function runPostCommit() {
117
135
  if (!isRepoActiveForCapture(cwd)) {
118
136
  return;
119
137
  }
138
+ const capturedAt = capturedAtFromLauncher();
139
+ if (process.env.PRIM_COMMIT_OBSERVED_FILE !== void 0 && capturedAt === void 0) {
140
+ return;
141
+ }
120
142
  const commit = readCommit();
121
143
  if (commit) {
122
144
  const repository = resolveRepositoryContext(cwd);
@@ -128,7 +150,8 @@ function runPostCommit() {
128
150
  repoFullName: githubRepositoryFullName(cwd) ?? void 0,
129
151
  repoSyncId: repoSyncId(cwd),
130
152
  workspaceId,
131
- attribution
153
+ attribution,
154
+ ...capturedAt === void 0 ? {} : { capturedAt }
132
155
  });
133
156
  const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd });
134
157
  appendMove(move, orgId);
@@ -148,5 +171,6 @@ if (!process.env.VITEST) {
148
171
  process.exit(0);
149
172
  }
150
173
  export {
174
+ capturedAtFromLauncher,
151
175
  runPostCommit
152
176
  };
@@ -7,29 +7,30 @@ import {
7
7
  import {
8
8
  enrichHookPayloadWithFileRefs,
9
9
  preserveHookFileMetadata
10
- } from "../chunk-DA4BMHEB.js";
10
+ } from "../chunk-BDGQEJA5.js";
11
11
  import {
12
12
  postToolInvocationId,
13
- toMove
14
- } from "../chunk-QGVHLP5J.js";
13
+ toMove,
14
+ toolOutcomeFor
15
+ } from "../chunk-5CKL5EYQ.js";
15
16
  import {
16
17
  appendMove,
17
18
  resolveOrg
18
19
  } from "../chunk-HIMWM526.js";
19
20
  import {
20
21
  scrubFromCwd
21
- } from "../chunk-GF5QII7T.js";
22
+ } from "../chunk-G5D3FF3D.js";
22
23
  import {
23
24
  getOrCreateWorkspaceId
24
25
  } from "../chunk-UVIWZWM6.js";
25
26
  import {
26
27
  warmBinCache
27
- } from "../chunk-KBFFVYBC.js";
28
+ } from "../chunk-LNU44OJV.js";
28
29
  import {
29
30
  isRepoActiveForCapture,
30
31
  repoSyncId,
31
32
  resolveRepositoryContext
32
- } from "../chunk-XTNMSS4C.js";
33
+ } from "../chunk-TDRXHBEX.js";
33
34
  import {
34
35
  getClient
35
36
  } from "../chunk-CPX3BUOC.js";
@@ -74,7 +75,7 @@ function isVerdictFooterContext(value) {
74
75
  // src/hooks/post-tool-use.ts
75
76
  var STDIN_TIMEOUT_MS = 1e3;
76
77
  var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]);
77
- var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch"]);
78
+ var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch", "Bash"]);
78
79
  var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
79
80
  function editingToolsFor(agent) {
80
81
  if (agent === "codex") {
@@ -137,17 +138,27 @@ async function main() {
137
138
  emit();
138
139
  return;
139
140
  }
140
- const envelope = parsed;
141
- if (envelope.hook_event_name !== "PostToolUse") {
141
+ let envelope = parsed;
142
+ const isToolResult = envelope.hook_event_name === "PostToolUse" || agent === "claude_code" && envelope.hook_event_name === "PostToolUseFailure";
143
+ const isHermesDenial = agent === "hermes" && envelope.hook_event_name === "post_approval_response" && toolOutcomeFor(parsed, agent) === "prevented";
144
+ if (!isToolResult && !isHermesDenial) {
145
+ emit();
146
+ return;
147
+ }
148
+ const invocationId = postToolInvocationId(parsed, agent);
149
+ if (isHermesDenial && !invocationId) {
142
150
  emit();
143
151
  return;
144
152
  }
145
153
  const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
146
- const editingTools = editingToolsFor(agent);
147
- if (!editingTools.has(toolName)) {
154
+ if (!isHermesDenial && !editingToolsFor(agent).has(toolName)) {
148
155
  emit();
149
156
  return;
150
157
  }
158
+ if (isHermesDenial && (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) && typeof envelope.extra?.session_key === "string" && envelope.extra.session_key.length > 0) {
159
+ parsed = { ...parsed, session_id: envelope.extra.session_key };
160
+ envelope = parsed;
161
+ }
151
162
  if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
152
163
  emit();
153
164
  return;
@@ -170,18 +181,19 @@ async function main() {
170
181
  repository
171
182
  });
172
183
  const { resolution } = enrichment;
173
- if (resolution.shellMutation === "none") {
174
- emit();
175
- return;
176
- }
177
- if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
178
- emit();
179
- return;
184
+ if (!isHermesDenial) {
185
+ if (resolution.shellMutation === "none") {
186
+ emit();
187
+ return;
188
+ }
189
+ if (resolution.shellMutation === void 0 && resolution.fileRefs.length === 0) {
190
+ emit();
191
+ return;
192
+ }
180
193
  }
181
194
  const enriched = enrichment.parsed;
182
195
  const identity = getOrCreateWorkspaceId(cwd);
183
196
  const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
184
- const invocationId = postToolInvocationId(enriched, agent);
185
197
  const base = toMove(enriched, resolveCliVersion(), agent, workspaceId, repository, invocationId);
186
198
  const scrubbed = scrubFromCwd(enriched, cwd);
187
199
  const move = {
@@ -12,16 +12,17 @@ import {
12
12
  failOpenOutput,
13
13
  readHookMode,
14
14
  scrub
15
- } from "../chunk-GF5QII7T.js";
15
+ } from "../chunk-G5D3FF3D.js";
16
16
  import {
17
+ packageVersion,
17
18
  warmBinCache
18
- } from "../chunk-KBFFVYBC.js";
19
+ } from "../chunk-LNU44OJV.js";
19
20
  import {
20
21
  canonicalGitRoot,
21
22
  canonicalRepositoryPath,
22
23
  isRepoActive,
23
24
  repoSyncId
24
- } from "../chunk-XTNMSS4C.js";
25
+ } from "../chunk-TDRXHBEX.js";
25
26
  import {
26
27
  getClient
27
28
  } from "../chunk-CPX3BUOC.js";
@@ -36,6 +37,11 @@ var PREFLIGHT_PROTOCOL_VERSION = 3;
36
37
  var PREFLIGHT_TIMEOUT_MS = 6500;
37
38
  var MAX_PREFLIGHT_PATHS = 32;
38
39
  var MAX_PROPOSAL_BYTES = 6144;
40
+ var MAX_CLIENT_VERSION_CHARS = 32;
41
+ var CLIENT_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
42
+ function boundedClientVersion(raw) {
43
+ return typeof raw === "string" && raw.length <= MAX_CLIENT_VERSION_CHARS && CLIENT_VERSION_RE.test(raw) ? raw : "unknown";
44
+ }
39
45
  function resolvePreflightTargets(args) {
40
46
  let rawPaths;
41
47
  let coverage = "complete";
@@ -224,6 +230,8 @@ async function main() {
224
230
  const request = {
225
231
  protocolVersion: PREFLIGHT_PROTOCOL_VERSION,
226
232
  agent,
233
+ clientMode: mode,
234
+ clientVersion: boundedClientVersion(packageVersion()),
227
235
  sessionId,
228
236
  invocationId: callId,
229
237
  repoSyncId: binding,
@@ -2,19 +2,19 @@
2
2
  import {
3
3
  enrichHookPayloadWithFileRefs,
4
4
  preserveHookFileMetadata
5
- } from "../chunk-DA4BMHEB.js";
5
+ } from "../chunk-BDGQEJA5.js";
6
6
  import {
7
7
  postToolInvocationId,
8
8
  shouldFlushAfter,
9
9
  toMove
10
- } from "../chunk-QGVHLP5J.js";
10
+ } from "../chunk-5CKL5EYQ.js";
11
11
  import {
12
12
  appendMove,
13
13
  resolveOrg
14
14
  } from "../chunk-HIMWM526.js";
15
15
  import {
16
16
  scrubFromCwd
17
- } from "../chunk-GF5QII7T.js";
17
+ } from "../chunk-G5D3FF3D.js";
18
18
  import {
19
19
  buildHookOutput,
20
20
  handoffHookOutput
@@ -30,12 +30,12 @@ import {
30
30
  } from "../chunk-UVIWZWM6.js";
31
31
  import {
32
32
  warmBinCache
33
- } from "../chunk-KBFFVYBC.js";
33
+ } from "../chunk-LNU44OJV.js";
34
34
  import {
35
35
  isRepoActiveForCapture,
36
36
  repoSyncId,
37
37
  resolveRepositoryContext
38
- } from "../chunk-XTNMSS4C.js";
38
+ } from "../chunk-TDRXHBEX.js";
39
39
  import "../chunk-CPX3BUOC.js";
40
40
  import {
41
41
  normalizeEnvelope,
@@ -4,7 +4,7 @@ import {
4
4
  ensureEffectivePostCommitHook,
5
5
  hasUsableCodexGuidance,
6
6
  refreshClaudePlugins
7
- } from "../chunk-WNIY6Z6N.js";
7
+ } from "../chunk-CPHJUQNE.js";
8
8
  import {
9
9
  buildHookOutput,
10
10
  handoffHookOutput
@@ -21,14 +21,13 @@ import {
21
21
  import {
22
22
  binFile,
23
23
  warmBinCache
24
- } from "../chunk-KBFFVYBC.js";
24
+ } from "../chunk-LNU44OJV.js";
25
25
  import {
26
26
  gitToplevel,
27
27
  isRepoActiveForCapture,
28
28
  repoActiveFlag,
29
- repoSyncId,
30
29
  setRepoActive
31
- } from "../chunk-XTNMSS4C.js";
30
+ } from "../chunk-TDRXHBEX.js";
32
31
  import {
33
32
  getSiteUrl,
34
33
  isSessionEnded
@@ -102,14 +101,12 @@ async function activeProjectRoot(cwd) {
102
101
  } catch {
103
102
  }
104
103
  }
105
- if (!repoSyncId(root)) {
106
- try {
107
- await bindRepository(root, {
108
- signal: AbortSignal.timeout(REPOSITORY_BIND_TIMEOUT_MS),
109
- quietRefresh: true
110
- });
111
- } catch {
112
- }
104
+ try {
105
+ await bindRepository(root, {
106
+ signal: AbortSignal.timeout(REPOSITORY_BIND_TIMEOUT_MS),
107
+ quietRefresh: true
108
+ });
109
+ } catch {
113
110
  }
114
111
  return root;
115
112
  } catch {
package/dist/index.js CHANGED
@@ -32,9 +32,10 @@ import {
32
32
  postCommitHookBlock,
33
33
  printJson,
34
34
  registerSkillCommands,
35
+ resolveRepositoryBinding,
35
36
  uninstallPostCommitHookAtPath,
36
37
  uninstallProjectPostCommitHook
37
- } from "./chunk-WNIY6Z6N.js";
38
+ } from "./chunk-CPHJUQNE.js";
38
39
  import {
39
40
  fetchFeedbackCapability
40
41
  } from "./chunk-KHZNYEUV.js";
@@ -48,7 +49,7 @@ import {
48
49
  packageVersion,
49
50
  pinnedHookCommand,
50
51
  warmBinCache
51
- } from "./chunk-KBFFVYBC.js";
52
+ } from "./chunk-LNU44OJV.js";
52
53
  import {
53
54
  canonicalGitRoot,
54
55
  canonicalRepositoryPath,
@@ -58,7 +59,7 @@ import {
58
59
  isValidRepoSyncId,
59
60
  repoSyncId,
60
61
  setRepoActive
61
- } from "./chunk-XTNMSS4C.js";
62
+ } from "./chunk-TDRXHBEX.js";
62
63
  import {
63
64
  HttpError,
64
65
  REFRESH_TOKEN_PATH,
@@ -96,22 +97,48 @@ async function applyActivation(active) {
96
97
  );
97
98
  process.exit(1);
98
99
  }
100
+ let phase = active ? "post-commit hook coverage" : "local deactivation";
99
101
  try {
100
- let bound;
102
+ let binding;
101
103
  let postCommitHook;
102
104
  if (active) {
103
105
  postCommitHook = ensureEffectivePostCommitHook(root).path;
104
- bound = await bindRepository(root);
106
+ phase = "repository binding";
107
+ binding = await bindRepository(root);
108
+ phase = "local activation";
105
109
  }
106
110
  setRepoActive(root, active);
107
111
  await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
108
- process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
112
+ if (binding?.status === "pending") {
113
+ process.stderr.write(`[prim] Prim is enabled locally in ${root}
109
114
  `);
110
- printJson({ active, repo: root, ...bound, ...postCommitHook ? { postCommitHook } : {} });
115
+ process.stderr.write(
116
+ `[prim] repository ${binding.repositoryFullName} is not connected to Primitive; Moves still ingest into the team graph without repository-specific file attribution, Conflict Gate verification, or commit correlation
117
+ `
118
+ );
119
+ process.stderr.write(
120
+ `[prim] ask an organization owner or administrator to grant Primitive's GitHub App access to this repository through Primitive's GitHub App onboarding; binding retries automatically at the next agent SessionStart
121
+ `
122
+ );
123
+ } else {
124
+ process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
125
+ `);
126
+ }
127
+ printJson({
128
+ active,
129
+ repo: root,
130
+ ...binding ? {
131
+ bindingStatus: binding.status,
132
+ repositoryFullName: binding.repositoryFullName,
133
+ ...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
134
+ } : {},
135
+ ...postCommitHook ? { postCommitHook } : {}
136
+ });
111
137
  } catch (err) {
112
138
  const detail = err instanceof Error ? err.message : String(err);
113
139
  process.stderr.write(
114
- `[prim] failed to ${active ? "cover, bind, and enable" : "disable"} prim: ${detail}
140
+ active ? `[prim] failed to enable prim during ${phase}: ${detail}
141
+ ` : `[prim] failed to disable prim: ${detail}
115
142
  `
116
143
  );
117
144
  process.exit(1);
@@ -1473,6 +1500,7 @@ var CAPTURE_EVENTS = [
1473
1500
  "UserPromptSubmit",
1474
1501
  "PreToolUse",
1475
1502
  "PostToolUse",
1503
+ "PostToolUseFailure",
1476
1504
  "Stop",
1477
1505
  "SessionEnd",
1478
1506
  "SubagentStop"
@@ -1494,6 +1522,11 @@ var REGISTRATIONS = [
1494
1522
  ),
1495
1523
  makeRegistration("PreToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", GATE_BIN),
1496
1524
  makeRegistration("PostToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", POST_TOOL_USE_BIN),
1525
+ makeRegistration(
1526
+ "PostToolUseFailure",
1527
+ "Edit|Write|MultiEdit|NotebookEdit|Bash",
1528
+ POST_TOOL_USE_BIN
1529
+ ),
1497
1530
  makeRegistration("SessionStart", "*", SESSION_START_BIN),
1498
1531
  makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
1499
1532
  ];
@@ -1875,6 +1908,8 @@ var GATE_BIN2 = "prim-pre-tool-use";
1875
1908
  var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
1876
1909
  var SESSION_START_BIN2 = "prim-session-start";
1877
1910
  var CODEX_ARGS = "--agent codex";
1911
+ var CODEX_EDIT_MATCHER = "apply_patch";
1912
+ var CODEX_POST_TOOL_MATCHER = "apply_patch|Bash";
1878
1913
  var JSON_INDENT2 = 2;
1879
1914
  var CODEX_CAPTURE_EVENTS = [
1880
1915
  "SessionStart",
@@ -1887,8 +1922,8 @@ var CODEX_CAPTURE_EVENTS = [
1887
1922
  var PRIM_BINS2 = [CAPTURE_BIN2, GATE_BIN2, POST_TOOL_USE_BIN2, SESSION_START_BIN2];
1888
1923
  var CODEX_REGISTRATIONS = [
1889
1924
  ...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
1890
- makeRegistration("PreToolUse", "apply_patch", GATE_BIN2, CODEX_ARGS),
1891
- makeRegistration("PostToolUse", "apply_patch", POST_TOOL_USE_BIN2, CODEX_ARGS),
1925
+ makeRegistration("PreToolUse", CODEX_EDIT_MATCHER, GATE_BIN2, CODEX_ARGS),
1926
+ makeRegistration("PostToolUse", CODEX_POST_TOOL_MATCHER, POST_TOOL_USE_BIN2, CODEX_ARGS),
1892
1927
  makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
1893
1928
  ];
1894
1929
  var USER_SCOPE_PATH2 = join3(homedir3(), ".codex", "hooks.json");
@@ -3908,19 +3943,66 @@ function checkWorkspaceIdentity() {
3908
3943
  };
3909
3944
  }
3910
3945
  }
3911
- function classifyRepositoryBinding(value) {
3912
- return isValidRepoSyncId(value) ? {
3946
+ function classifyRepositoryBinding(value, current, active) {
3947
+ if (current.status === "pending") {
3948
+ if (value !== void 0) {
3949
+ return {
3950
+ name: "repo-binding",
3951
+ status: "fail",
3952
+ detail: `local repository binding is stale for ${current.repositoryFullName} \u2014 run \`prim enable\``
3953
+ };
3954
+ }
3955
+ if (!active) {
3956
+ return {
3957
+ name: "repo-binding",
3958
+ status: "fail",
3959
+ detail: "passive capture is inactive \u2014 run `prim enable`"
3960
+ };
3961
+ }
3962
+ return {
3963
+ name: "repo-binding",
3964
+ status: "warn",
3965
+ detail: `${current.repositoryFullName} is not connected to Primitive \u2014 local capture is active, but file scoping, Conflict Gate verification, and commit correlation are unavailable; ask an organization owner/admin to grant the Primitive GitHub App access (retried automatically next SessionStart)`
3966
+ };
3967
+ }
3968
+ if (!isValidRepoSyncId(value)) {
3969
+ return {
3970
+ name: "repo-binding",
3971
+ status: "fail",
3972
+ detail: "missing or invalid local prim.repoSyncId \u2014 run `prim enable`"
3973
+ };
3974
+ }
3975
+ if (value !== current.repoSyncId) {
3976
+ return {
3977
+ name: "repo-binding",
3978
+ status: "fail",
3979
+ detail: "local repository binding is stale for the current origin \u2014 run `prim enable`"
3980
+ };
3981
+ }
3982
+ return {
3913
3983
  name: "repo-binding",
3914
3984
  status: "ok",
3915
- detail: "valid local repository binding ready"
3916
- } : {
3917
- name: "repo-binding",
3918
- status: "fail",
3919
- detail: "missing or invalid local prim.repoSyncId \u2014 run `prim enable`"
3985
+ detail: "local repository binding matches the current server binding"
3920
3986
  };
3921
3987
  }
3922
- function checkRepositoryBinding() {
3923
- return classifyRepositoryBinding(repoSyncId(process.cwd()));
3988
+ async function checkRepositoryBinding() {
3989
+ const root = process.cwd();
3990
+ const local = repoSyncId(root);
3991
+ try {
3992
+ const current = await resolveRepositoryBinding(root, {
3993
+ signal: AbortSignal.timeout(CONNECTIVITY_TIMEOUT_MS),
3994
+ quietRefresh: true
3995
+ });
3996
+ return classifyRepositoryBinding(local, current, isRepoActiveForCapture(root));
3997
+ } catch (error) {
3998
+ const message = error instanceof Error ? error.message : String(error);
3999
+ const detail = boundedHealthError(`could not verify the current repository binding \u2014 ${message}`) ?? "could not verify the current repository binding";
4000
+ return {
4001
+ name: "repo-binding",
4002
+ status: "fail",
4003
+ detail
4004
+ };
4005
+ }
3924
4006
  }
3925
4007
  function classifyPostCommitHook(inspection) {
3926
4008
  if (inspection.covered) {
@@ -4067,7 +4149,7 @@ async function collectChecks() {
4067
4149
  checkStranded(),
4068
4150
  checkFeedbackHooks(),
4069
4151
  checkWorkspaceIdentity(),
4070
- checkRepositoryBinding(),
4152
+ await checkRepositoryBinding(),
4071
4153
  checkPostCommitHook(),
4072
4154
  ...backend,
4073
4155
  await checkFeedbackCapability()
@@ -4176,6 +4258,7 @@ var REGISTRATIONS2 = [
4176
4258
  ...CAPTURE_EVENTS2.map((event) => ({ event, bin: CAPTURE_BIN3 })),
4177
4259
  { event: "pre_tool_call", matcher: EDIT_MATCHER, bin: GATE_BIN3, timeout: GATE_TIMEOUT_SECONDS },
4178
4260
  { event: "post_tool_call", matcher: EDIT_MATCHER, bin: POST_TOOL_USE_BIN3 },
4261
+ { event: "post_approval_response", bin: POST_TOOL_USE_BIN3 },
4179
4262
  { event: "on_session_start", bin: SESSION_START_BIN3 },
4180
4263
  { event: "on_session_end", bin: SESSION_END_BIN2 }
4181
4264
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.63",
3
+ "version": "0.1.0-alpha.65",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",