@primitive.ai/prim 0.1.0-alpha.41 → 0.1.0-alpha.43

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,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ toCommitMove
4
+ } from "../chunk-F5QCFBJ6.js";
2
5
  import {
3
6
  appendMove,
4
7
  resolveOrg
5
- } from "../chunk-H4OR42TJ.js";
6
- import {
7
- toCommitMove
8
- } from "../chunk-S2O4P4A3.js";
9
- import "../chunk-26VA3ADF.js";
8
+ } from "../chunk-OKUXEBPT.js";
9
+ import "../chunk-W6PAKEDO.js";
10
10
 
11
11
  // src/hooks/post-commit.ts
12
12
  import { execSync, spawn } from "child_process";
@@ -1,23 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  bold,
4
- color
5
- } from "../chunk-AAGJFO7C.js";
4
+ color,
5
+ requireDurableIngestAcknowledgement
6
+ } from "../chunk-QY75BQMF.js";
6
7
  import {
7
8
  scrubFromCwd
8
9
  } from "../chunk-6LAQVM26.js";
9
10
  import {
10
11
  toMove
11
- } from "../chunk-S2O4P4A3.js";
12
+ } from "../chunk-F5QCFBJ6.js";
12
13
  import {
13
- isRepoActiveForCapture
14
- } from "../chunk-LUPD2JSH.js";
14
+ appendMove,
15
+ resolveOrg
16
+ } from "../chunk-OKUXEBPT.js";
15
17
  import {
18
+ getOrCreateWorkspaceId
19
+ } from "../chunk-IMAIBPUC.js";
20
+ import {
21
+ isRepoActiveForCapture,
16
22
  warmBinCache
17
- } from "../chunk-HSZPTVKU.js";
23
+ } from "../chunk-F7O7V6ZM.js";
18
24
  import {
19
25
  getClient
20
- } from "../chunk-26VA3ADF.js";
26
+ } from "../chunk-W6PAKEDO.js";
21
27
  import {
22
28
  normalizeEnvelope,
23
29
  parseAgent
@@ -28,6 +34,18 @@ import { readFileSync } from "fs";
28
34
  import { dirname, join } from "path";
29
35
  import { fileURLToPath } from "url";
30
36
 
37
+ // src/hooks/post-tool-delivery.ts
38
+ var INGEST_TIMEOUT_MS = 4e3;
39
+ async function deliverPostToolMove(move, orgId) {
40
+ appendMove(move, orgId);
41
+ const response = await getClient().post(
42
+ "/api/cli/moves/ingest",
43
+ { batch: [move] },
44
+ { signal: AbortSignal.timeout(INGEST_TIMEOUT_MS) }
45
+ );
46
+ return requireDurableIngestAcknowledgement(response, 1);
47
+ }
48
+
31
49
  // src/hooks/verdict-footer.ts
32
50
  function renderVerdictFooter(ctx) {
33
51
  const successPrefix = color("\u2713 Conflict caught before merge", "green");
@@ -46,7 +64,6 @@ function isVerdictFooterContext(value) {
46
64
 
47
65
  // src/hooks/post-tool-use.ts
48
66
  var STDIN_TIMEOUT_MS = 1e3;
49
- var INGEST_TIMEOUT_MS = 4e3;
50
67
  var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
51
68
  var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch"]);
52
69
  var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
@@ -94,14 +111,6 @@ function debug(msg) {
94
111
  `);
95
112
  }
96
113
  }
97
- async function ingestMove(move) {
98
- const client = getClient();
99
- return await client.post(
100
- "/api/cli/moves/ingest",
101
- { batch: [move] },
102
- { signal: AbortSignal.timeout(INGEST_TIMEOUT_MS) }
103
- );
104
- }
105
114
  async function main() {
106
115
  warmBinCache();
107
116
  const agent = parseAgent(process.argv);
@@ -139,11 +148,14 @@ async function main() {
139
148
  emit();
140
149
  return;
141
150
  }
142
- const base = toMove(parsed, resolveCliVersion(), agent);
151
+ const identity = getOrCreateWorkspaceId(cwd);
152
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
153
+ const base = toMove(parsed, resolveCliVersion(), agent, workspaceId);
143
154
  const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
155
+ const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
144
156
  try {
145
- const result = await ingestMove(move);
146
- debug(`ingested ${move.moveId} (${toolName})`);
157
+ const result = await deliverPostToolMove(move, orgId);
158
+ debug(`durably ingested ${move.moveId} (${toolName})`);
147
159
  if (isVerdictFooterContext(result.verdictFooter)) {
148
160
  process.stderr.write(`${renderVerdictFooter(result.verdictFooter)}
149
161
  `);
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  checkAffectedDecisions,
4
4
  formatDecisionsWarning
5
- } from "../chunk-UWAJCF7P.js";
6
- import "../chunk-26VA3ADF.js";
5
+ } from "../chunk-BSZGI5RL.js";
6
+ import "../chunk-W6PAKEDO.js";
7
7
  import "../chunk-UTKQTZHL.js";
8
8
 
9
9
  // src/hooks/pre-commit.ts
@@ -1,14 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- isRepoActiveForCapture
4
- } from "../chunk-LUPD2JSH.js";
5
- import {
3
+ isRepoActiveForCapture,
6
4
  warmBinCache
7
- } from "../chunk-HSZPTVKU.js";
5
+ } from "../chunk-F7O7V6ZM.js";
8
6
  import {
9
7
  getClient,
10
8
  getSiteUrl
11
- } from "../chunk-26VA3ADF.js";
9
+ } from "../chunk-W6PAKEDO.js";
12
10
  import {
13
11
  daemonRequest
14
12
  } from "../chunk-UTKQTZHL.js";
@@ -1,22 +1,33 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- appendMove,
4
- resolveOrg
5
- } from "../chunk-H4OR42TJ.js";
6
2
  import {
7
3
  scrubFromCwd
8
4
  } from "../chunk-6LAQVM26.js";
9
5
  import {
10
6
  shouldFlushAfter,
11
7
  toMove
12
- } from "../chunk-S2O4P4A3.js";
8
+ } from "../chunk-F5QCFBJ6.js";
9
+ import {
10
+ appendMove,
11
+ resolveOrg
12
+ } from "../chunk-OKUXEBPT.js";
13
+ import {
14
+ buildHookOutput,
15
+ handoffHookOutput
16
+ } from "../chunk-S5NUHFAD.js";
17
+ import {
18
+ FEEDBACK_DEADLINE_MS,
19
+ acknowledgeDecisionFeedback,
20
+ leaseDecisionFeedback,
21
+ renderFeedback
22
+ } from "../chunk-UISU3A7W.js";
13
23
  import {
14
- isRepoActiveForCapture
15
- } from "../chunk-LUPD2JSH.js";
24
+ getOrCreateWorkspaceId
25
+ } from "../chunk-IMAIBPUC.js";
16
26
  import {
27
+ isRepoActiveForCapture,
17
28
  warmBinCache
18
- } from "../chunk-HSZPTVKU.js";
19
- import "../chunk-26VA3ADF.js";
29
+ } from "../chunk-F7O7V6ZM.js";
30
+ import "../chunk-W6PAKEDO.js";
20
31
  import {
21
32
  normalizeEnvelope,
22
33
  parseAgent
@@ -28,6 +39,11 @@ import { readFileSync } from "fs";
28
39
  import { dirname, join } from "path";
29
40
  import { fileURLToPath } from "url";
30
41
  var here = dirname(fileURLToPath(import.meta.url));
42
+ var outputAttempted = false;
43
+ function emitOutput(output, acknowledge) {
44
+ outputAttempted = true;
45
+ return handoffHookOutput(output, acknowledge);
46
+ }
31
47
  function resolveCliVersion() {
32
48
  try {
33
49
  const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
@@ -43,26 +59,67 @@ function spawnBackgroundFlush() {
43
59
  stdio: "ignore"
44
60
  }).unref();
45
61
  }
46
- try {
62
+ function debug(area, error) {
63
+ if (!process.env.PRIM_HOOK_DEBUG) return;
64
+ const detail = error instanceof Error ? error.message : String(error);
65
+ process.stderr.write(`[prim-hook] ${area} failed: ${detail}
66
+ `);
67
+ }
68
+ async function main() {
69
+ const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
47
70
  warmBinCache();
48
71
  const agent = parseAgent(process.argv);
49
- const raw = readFileSync(0, "utf-8");
50
- const parsed = normalizeEnvelope(JSON.parse(raw), agent);
72
+ let raw;
73
+ let parsed;
74
+ try {
75
+ raw = readFileSync(0, "utf-8");
76
+ parsed = normalizeEnvelope(JSON.parse(raw), agent);
77
+ } catch (error) {
78
+ debug("capture", error);
79
+ await emitOutput(buildHookOutput({}));
80
+ return;
81
+ }
51
82
  const cwd = parsed.cwd ?? process.cwd();
52
- if (isRepoActiveForCapture(cwd)) {
53
- const base = toMove(parsed, resolveCliVersion(), agent);
83
+ const isClaudeStop = agent === "claude_code" && parsed.hook_event_name === "Stop";
84
+ if (!isRepoActiveForCapture(cwd)) {
85
+ if (isClaudeStop) await emitOutput(buildHookOutput({}));
86
+ return;
87
+ }
88
+ const identity = getOrCreateWorkspaceId(cwd);
89
+ const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
90
+ try {
91
+ const base = toMove(parsed, resolveCliVersion(), agent, workspaceId);
54
92
  const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
55
93
  const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
56
94
  appendMove(move, orgId);
57
95
  if (shouldFlushAfter(move.eventType)) {
58
96
  spawnBackgroundFlush();
59
97
  }
98
+ } catch (error) {
99
+ debug("capture", error);
60
100
  }
61
- } catch (err) {
62
- if (process.env.PRIM_HOOK_DEBUG) {
63
- const detail = err instanceof Error ? err.message : String(err);
64
- process.stderr.write(`[prim-hook] capture failed: ${detail}
65
- `);
101
+ if (!isClaudeStop) return;
102
+ const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
103
+ if (!workspaceId || !sessionId) {
104
+ await emitOutput(buildHookOutput({}));
105
+ return;
66
106
  }
107
+ const lease = await leaseDecisionFeedback(
108
+ { workspaceId, currentSessionId: sessionId, signal: feedbackSignal },
109
+ { onError: (error) => debug("feedback", error) }
110
+ );
111
+ const rendered = lease ? renderFeedback(lease) : void 0;
112
+ await emitOutput(
113
+ buildHookOutput({ systemMessage: rendered?.systemMessage }),
114
+ rendered ? async () => {
115
+ await acknowledgeDecisionFeedback(
116
+ { workspaceId, deliveries: rendered.deliveries, signal: feedbackSignal },
117
+ { onError: (error) => debug("feedback", error) }
118
+ );
119
+ } : void 0
120
+ );
67
121
  }
68
- process.exit(0);
122
+ void main().catch(async (error) => {
123
+ debug("capture", error);
124
+ if (!outputAttempted) await emitOutput(buildHookOutput({}));
125
+ });
@@ -1,10 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ buildHookOutput,
4
+ handoffHookOutput
5
+ } from "../chunk-S5NUHFAD.js";
6
+ import {
7
+ FEEDBACK_DEADLINE_MS,
8
+ acknowledgeDecisionFeedback,
9
+ leaseDecisionFeedback,
10
+ renderFeedback
11
+ } from "../chunk-UISU3A7W.js";
12
+ import {
13
+ getOrCreateWorkspaceId
14
+ } from "../chunk-IMAIBPUC.js";
15
+ import {
16
+ binFile,
17
+ isRepoActiveForCapture,
3
18
  warmBinCache
4
- } from "../chunk-HSZPTVKU.js";
19
+ } from "../chunk-F7O7V6ZM.js";
5
20
  import {
6
21
  getSiteUrl
7
- } from "../chunk-26VA3ADF.js";
22
+ } from "../chunk-W6PAKEDO.js";
8
23
  import {
9
24
  daemonRequest
10
25
  } from "../chunk-UTKQTZHL.js";
@@ -13,9 +28,34 @@ import {
13
28
  parseAgent
14
29
  } from "../chunk-TCDUH7AN.js";
15
30
 
31
+ // src/daemon/self-heal.ts
32
+ import { spawn } from "child_process";
33
+ function kickDaemonEnsure(options = {}) {
34
+ const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
35
+ if (!primEntry) {
36
+ return false;
37
+ }
38
+ try {
39
+ const child = (options.spawnProcess ?? spawn)(
40
+ options.nodeEntry ?? process.execPath,
41
+ [primEntry, "daemon", "ensure"],
42
+ { detached: true, stdio: "ignore" }
43
+ );
44
+ child.unref();
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
16
51
  // src/hooks/session-start.ts
17
52
  var STDIN_TIMEOUT_MS = 1e3;
18
53
  var DAEMON_TIMEOUT_MS = 250;
54
+ var outputAttempted = false;
55
+ function emitOutput(output, acknowledge) {
56
+ outputAttempted = true;
57
+ return handoffHookOutput(output, acknowledge);
58
+ }
19
59
  function readStdin() {
20
60
  return new Promise((resolve, reject) => {
21
61
  const chunks = [];
@@ -33,28 +73,15 @@ function readStdin() {
33
73
  });
34
74
  });
35
75
  }
36
- function emit(additionalContext) {
37
- if (!additionalContext) {
38
- process.stdout.write("{}\n");
39
- return;
40
- }
41
- const out = {
42
- hookSpecificOutput: {
43
- hookEventName: "SessionStart",
44
- additionalContext
45
- }
46
- };
47
- process.stdout.write(`${JSON.stringify(out)}
48
- `);
49
- }
50
76
  async function main() {
77
+ const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
51
78
  warmBinCache();
52
79
  const agent = parseAgent(process.argv);
53
80
  let raw;
54
81
  try {
55
82
  raw = await readStdin();
56
83
  } catch {
57
- emit();
84
+ await emitOutput(buildHookOutput({}));
58
85
  return;
59
86
  }
60
87
  let envelope;
@@ -64,17 +91,18 @@ async function main() {
64
91
  agent
65
92
  );
66
93
  } catch {
67
- emit();
94
+ await emitOutput(buildHookOutput({}));
68
95
  return;
69
96
  }
70
97
  if (envelope.hook_event_name !== "SessionStart") {
71
- emit();
98
+ await emitOutput(buildHookOutput({}));
72
99
  return;
73
100
  }
74
101
  if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
75
- emit();
102
+ await emitOutput(buildHookOutput({}));
76
103
  return;
77
104
  }
105
+ kickDaemonEnsure();
78
106
  await daemonRequest(
79
107
  "session_start",
80
108
  { sessionId: envelope.session_id },
@@ -89,12 +117,39 @@ async function main() {
89
117
  { timeoutMs: DAEMON_TIMEOUT_MS }
90
118
  );
91
119
  if (snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number") {
92
- emit(`[prim] team: ${snapshot.onlineCount} online`);
120
+ await emitOutput(
121
+ buildHookOutput({ additionalContext: `[prim] team: ${snapshot.onlineCount} online` })
122
+ );
93
123
  return;
94
124
  }
95
125
  }
96
- emit();
126
+ if (agent === "claude_code") {
127
+ const cwd = envelope.cwd ?? process.cwd();
128
+ if (isRepoActiveForCapture(cwd)) {
129
+ const identity = getOrCreateWorkspaceId(cwd);
130
+ if (identity.status === "ready") {
131
+ const lease = await leaseDecisionFeedback({
132
+ workspaceId: identity.workspaceId,
133
+ currentSessionId: envelope.session_id,
134
+ signal: feedbackSignal
135
+ });
136
+ const rendered = lease ? renderFeedback(lease) : void 0;
137
+ await emitOutput(
138
+ buildHookOutput({ systemMessage: rendered?.systemMessage }),
139
+ rendered ? async () => {
140
+ await acknowledgeDecisionFeedback({
141
+ workspaceId: identity.workspaceId,
142
+ deliveries: rendered.deliveries,
143
+ signal: feedbackSignal
144
+ });
145
+ } : void 0
146
+ );
147
+ return;
148
+ }
149
+ }
150
+ }
151
+ await emitOutput(buildHookOutput({}));
97
152
  }
98
- main().catch(() => {
99
- emit();
153
+ void main().catch(async () => {
154
+ if (!outputAttempted) await emitOutput(buildHookOutput({}));
100
155
  });