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

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-3EEFL6NJ.js +529 -0
  4. package/dist/chunk-4CZTNXIS.js +505 -0
  5. package/dist/chunk-4GLVJLEQ.js +55 -0
  6. package/dist/chunk-BQUJ4AFJ.js +771 -0
  7. package/dist/chunk-CUE2LKLR.js +155 -0
  8. package/dist/chunk-EZXEWFDG.js +210 -0
  9. package/dist/chunk-FI3LIRCB.js +262 -0
  10. package/dist/chunk-HWSS4BV5.js +1400 -0
  11. package/dist/chunk-LEMX3IOG.js +79 -0
  12. package/dist/chunk-LMRYZL3D.js +209 -0
  13. package/dist/chunk-OUXH3UEP.js +247 -0
  14. package/dist/chunk-PMRDE2DA.js +310 -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 +7099 -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 +44696 -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 +348 -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 +20981 -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,79 @@
1
+ import {
2
+ terminalSafeLine
3
+ } from "./chunk-Z7YPFWAZ.js";
4
+
5
+ // src/lib/ansi.ts
6
+ var ANSI_CODES = {
7
+ yellow: "\x1B[33m",
8
+ magenta: "\x1B[35m",
9
+ blue: "\x1B[34m",
10
+ cyan: "\x1B[36m",
11
+ green: "\x1B[32m",
12
+ red: "\x1B[31m",
13
+ // 256-color approximation of the orange trigger marker (xterm 208).
14
+ orange: "\x1B[38;5;208m",
15
+ gray: "\x1B[90m"
16
+ };
17
+ var ANSI_RESET = "\x1B[0m";
18
+ var ANSI_DIM = "\x1B[2m";
19
+ var ANSI_BOLD = "\x1B[1m";
20
+ function supportsColor() {
21
+ if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
22
+ return false;
23
+ }
24
+ return process.stderr.isTTY === true;
25
+ }
26
+ function color(text, c) {
27
+ if (!supportsColor()) {
28
+ return text;
29
+ }
30
+ return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
31
+ }
32
+ function dim(text) {
33
+ if (!supportsColor()) {
34
+ return text;
35
+ }
36
+ return `${ANSI_DIM}${text}${ANSI_RESET}`;
37
+ }
38
+ function bold(text) {
39
+ if (!supportsColor()) {
40
+ return text;
41
+ }
42
+ return `${ANSI_BOLD}${text}${ANSI_RESET}`;
43
+ }
44
+ var AREA_COLORS = {
45
+ auth: "yellow",
46
+ data: "magenta",
47
+ mobile: "blue",
48
+ infra: "cyan",
49
+ ui: "green",
50
+ billing: "orange",
51
+ api: "blue",
52
+ docs: "gray",
53
+ testing: "gray"
54
+ };
55
+ function colorForArea(area) {
56
+ if (!area) {
57
+ return "gray";
58
+ }
59
+ return AREA_COLORS[area] ?? "gray";
60
+ }
61
+ var MAX_HEALTH_ERROR_LENGTH = 240;
62
+ function boundedHealthError(value) {
63
+ if (!value) return void 0;
64
+ const clean = terminalSafeLine(value);
65
+ if (!clean) return void 0;
66
+ return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
67
+ }
68
+ function stripAnsi(text) {
69
+ return text.replace(/\u001b\[[0-9;]*m/g, "");
70
+ }
71
+
72
+ export {
73
+ color,
74
+ dim,
75
+ bold,
76
+ colorForArea,
77
+ boundedHealthError,
78
+ stripAnsi
79
+ };
@@ -0,0 +1,209 @@
1
+ import {
2
+ getClient,
3
+ isTerminalSafeText,
4
+ terminalSafeLine,
5
+ terminalSafeText
6
+ } from "./chunk-Z7YPFWAZ.js";
7
+
8
+ // src/decisions/feedback.ts
9
+ var FEEDBACK_PROTOCOL_VERSION = 2;
10
+ var FEEDBACK_MIN_PROTOCOL_VERSION = 1;
11
+ var FEEDBACK_DEADLINE_MS = 3e3;
12
+ var MAX_FEEDBACK_EVENTS = 40;
13
+ var MAX_FEEDBACK_MESSAGE_CODE_POINTS = 8e3;
14
+ var MAX_FEEDBACK_INTENT_CODE_POINTS = 180;
15
+ var LEASE_PATH = "/api/cli/decisions/feedback/lease";
16
+ var ACK_PATH = "/api/cli/decisions/feedback/ack";
17
+ var STATUS_PATH = "/api/cli/decisions/feedback/status";
18
+ var SHORT_ID = /^[0-9a-f]{8}$/u;
19
+ var MAX_EVENT_ID_CHARS = 128;
20
+ var MAX_RAW_INTENT_CODE_UNITS = 512;
21
+ var MAX_FEEDBACK_WEB_URL_CHARS = 2048;
22
+ var SAFE_PUBLISH_DECISION_ID = /^[A-Za-z0-9_-]+$/u;
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ function isSupportedProtocolVersion(value) {
27
+ return Number.isSafeInteger(value) && Number(value) >= FEEDBACK_MIN_PROTOCOL_VERSION && Number(value) <= FEEDBACK_PROTOCOL_VERSION;
28
+ }
29
+ function normalizeFeedbackIntent(value) {
30
+ const withoutControls = terminalSafeLine(value);
31
+ const points = Array.from(withoutControls);
32
+ if (points.length <= MAX_FEEDBACK_INTENT_CODE_POINTS) {
33
+ return withoutControls;
34
+ }
35
+ return `${points.slice(0, MAX_FEEDBACK_INTENT_CODE_POINTS - 1).join("")}\u2026`;
36
+ }
37
+ function parseFeedbackWebUrl(value) {
38
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_FEEDBACK_WEB_URL_CHARS || !/^https:\/\//iu.test(value) || /\s/u.test(value) || !isTerminalSafeText(value)) {
39
+ return void 0;
40
+ }
41
+ try {
42
+ const url = new URL(value);
43
+ if (url.protocol !== "https:" || !url.hostname || url.username.length > 0 || url.password.length > 0) {
44
+ return void 0;
45
+ }
46
+ } catch {
47
+ return void 0;
48
+ }
49
+ return value;
50
+ }
51
+ function parseFeedbackDecisionId(value) {
52
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_ID_CHARS || terminalSafeText(value) !== value || !SAFE_PUBLISH_DECISION_ID.test(value)) {
53
+ return void 0;
54
+ }
55
+ return value;
56
+ }
57
+ function parseEvent(value, protocolVersion) {
58
+ if (!isRecord(value)) return void 0;
59
+ if (typeof value.eventId !== "string" || value.eventId.length === 0 || value.eventId.length > MAX_EVENT_ID_CHARS || terminalSafeText(value.eventId) !== value.eventId) {
60
+ return void 0;
61
+ }
62
+ if (!Number.isSafeInteger(value.leaseVersion) || Number(value.leaseVersion) < 1) {
63
+ return void 0;
64
+ }
65
+ if (typeof value.shortId !== "string" || !SHORT_ID.test(value.shortId)) {
66
+ return void 0;
67
+ }
68
+ if (typeof value.intent !== "string" || value.intent.length > MAX_RAW_INTENT_CODE_UNITS) {
69
+ return void 0;
70
+ }
71
+ const intent = normalizeFeedbackIntent(value.intent);
72
+ if (!intent) return void 0;
73
+ const webUrl = value.webUrl === void 0 ? void 0 : parseFeedbackWebUrl(value.webUrl);
74
+ if (value.webUrl !== void 0 && webUrl === void 0) return void 0;
75
+ const kind = value.kind === void 0 && protocolVersion === 1 ? "confirm_prompt" : value.kind;
76
+ if (kind !== "confirm_prompt" && kind !== "publish_prompt") return void 0;
77
+ if (protocolVersion === 1 && kind === "publish_prompt") return void 0;
78
+ const decisionId = value.decisionId === void 0 ? void 0 : parseFeedbackDecisionId(value.decisionId);
79
+ if (value.decisionId !== void 0 && decisionId === void 0) return void 0;
80
+ if (decisionId !== void 0 && (protocolVersion !== 2 || kind !== "publish_prompt")) {
81
+ return void 0;
82
+ }
83
+ if (protocolVersion === 2 && kind === "publish_prompt" && decisionId === void 0) {
84
+ return void 0;
85
+ }
86
+ return {
87
+ eventId: value.eventId,
88
+ leaseVersion: Number(value.leaseVersion),
89
+ shortId: value.shortId,
90
+ intent,
91
+ ...webUrl === void 0 ? {} : { webUrl },
92
+ ...decisionId === void 0 ? {} : { decisionId },
93
+ kind
94
+ };
95
+ }
96
+ function parseFeedbackLease(value) {
97
+ if (!isRecord(value) || !isSupportedProtocolVersion(value.protocolVersion)) return void 0;
98
+ const protocolVersion = value.protocolVersion;
99
+ if (value.status === "empty") {
100
+ return value.hasMore === false ? { protocolVersion, events: [], hasMore: false } : void 0;
101
+ }
102
+ if (value.status === "unavailable") {
103
+ return value.reason === "organization_unbound" ? { protocolVersion, events: [], hasMore: false } : void 0;
104
+ }
105
+ if (value.status !== "leased" || typeof value.hasMore !== "boolean") return void 0;
106
+ if (!Array.isArray(value.events) || value.events.length === 0 || value.events.length > MAX_FEEDBACK_EVENTS) {
107
+ return void 0;
108
+ }
109
+ const events = value.events.map((event) => parseEvent(event, protocolVersion));
110
+ if (events.some((event) => event === void 0)) return void 0;
111
+ const parsedEvents = events;
112
+ if (new Set(parsedEvents.map((event) => event.eventId)).size !== parsedEvents.length) {
113
+ return void 0;
114
+ }
115
+ return { protocolVersion, events: parsedEvents, hasMore: value.hasMore };
116
+ }
117
+ function renderFeedback(lease) {
118
+ const lines = [];
119
+ const deliveries = [];
120
+ let pointCount = 0;
121
+ for (const event of lease.events) {
122
+ const decisionId = event.kind === "publish_prompt" ? parseFeedbackDecisionId(event.decisionId) : void 0;
123
+ if (event.kind === "publish_prompt" && decisionId === void 0) continue;
124
+ const identifier = `dec_${event.shortId}`;
125
+ const detail = `${event.intent}${event.webUrl ? ` (${event.webUrl})` : ""}`;
126
+ const line = event.kind === "publish_prompt" ? `[prim] publish this Decision draft (${identifier})? ${detail} Run \`prim decisions publish ${decisionId}\` to share it with your team.` : `[prim] response \u2192 created Decision (${identifier}): ${detail}`;
127
+ const extra = Array.from(line).length + (lines.length === 0 ? 0 : 1);
128
+ if (pointCount + extra > MAX_FEEDBACK_MESSAGE_CODE_POINTS) break;
129
+ lines.push(line);
130
+ deliveries.push({ eventId: event.eventId, leaseVersion: event.leaseVersion });
131
+ pointCount += extra;
132
+ }
133
+ if (lines.length === 0) return void 0;
134
+ return { protocolVersion: lease.protocolVersion, systemMessage: lines.join("\n"), deliveries };
135
+ }
136
+ async function leaseDecisionFeedback(input, dependencies = {}) {
137
+ try {
138
+ const response = await (dependencies.client ?? getClient()).post(
139
+ LEASE_PATH,
140
+ {
141
+ protocolVersion: FEEDBACK_PROTOCOL_VERSION,
142
+ workspaceId: input.workspaceId,
143
+ currentSessionId: input.currentSessionId
144
+ },
145
+ { signal: input.signal, quietRefresh: true }
146
+ );
147
+ const parsed = parseFeedbackLease(response);
148
+ if (!parsed) {
149
+ dependencies.onError?.(
150
+ new Error("server returned an unsupported decision-feedback contract")
151
+ );
152
+ }
153
+ return parsed;
154
+ } catch (error) {
155
+ dependencies.onError?.(error);
156
+ return void 0;
157
+ }
158
+ }
159
+ async function acknowledgeDecisionFeedback(input, dependencies = {}) {
160
+ if (!isSupportedProtocolVersion(input.protocolVersion) || input.deliveries.length === 0 || input.deliveries.length > MAX_FEEDBACK_EVENTS) {
161
+ return false;
162
+ }
163
+ try {
164
+ const response = await (dependencies.client ?? getClient()).post(
165
+ ACK_PATH,
166
+ {
167
+ protocolVersion: input.protocolVersion,
168
+ workspaceId: input.workspaceId,
169
+ deliveries: input.deliveries
170
+ },
171
+ { signal: input.signal, quietRefresh: true }
172
+ );
173
+ const acknowledgedEventIds = isRecord(response) ? response.acknowledgedEventIds : void 0;
174
+ const expectedEventIds = new Set(input.deliveries.map((delivery) => delivery.eventId));
175
+ const valid = isRecord(response) && response.protocolVersion === input.protocolVersion && response.status === "acked" && Array.isArray(acknowledgedEventIds) && acknowledgedEventIds.length <= input.deliveries.length && acknowledgedEventIds.every(
176
+ (id) => typeof id === "string" && id.length > 0 && id.length <= MAX_EVENT_ID_CHARS && expectedEventIds.has(id)
177
+ ) && new Set(acknowledgedEventIds).size === acknowledgedEventIds.length;
178
+ if (!valid) {
179
+ dependencies.onError?.(
180
+ new Error("server returned an unsupported decision-feedback acknowledgement")
181
+ );
182
+ }
183
+ return valid;
184
+ } catch (error) {
185
+ dependencies.onError?.(error);
186
+ return false;
187
+ }
188
+ }
189
+ function parseFeedbackCapability(value) {
190
+ if (!isRecord(value) || !isSupportedProtocolVersion(value.protocolVersion)) return void 0;
191
+ if (value.status === "available") return { status: "available" };
192
+ if (value.status === "unavailable" && value.reason === "organization_unbound") {
193
+ return { status: "unavailable", reason: "organization_unbound" };
194
+ }
195
+ return void 0;
196
+ }
197
+ async function fetchFeedbackCapability(signal, client = getClient()) {
198
+ const result = parseFeedbackCapability(await client.get(STATUS_PATH, { signal }));
199
+ if (!result) throw new Error("server returned an unsupported decision-feedback contract");
200
+ return result;
201
+ }
202
+
203
+ export {
204
+ FEEDBACK_DEADLINE_MS,
205
+ renderFeedback,
206
+ leaseDecisionFeedback,
207
+ acknowledgeDecisionFeedback,
208
+ fetchFeedbackCapability
209
+ };
@@ -0,0 +1,247 @@
1
+ import {
2
+ isCanonicalWorkspaceId
3
+ } from "./chunk-EZXEWFDG.js";
4
+ import {
5
+ scrubEnvironmentPaths
6
+ } from "./chunk-FI3LIRCB.js";
7
+ import {
8
+ isValidRepoSyncId
9
+ } from "./chunk-TJTVHDJ5.js";
10
+
11
+ // src/hooks/prim-hook-core.ts
12
+ import { createHash, randomUUID } from "crypto";
13
+ import { platform } from "os";
14
+
15
+ // src/protocol/move.ts
16
+ var LEGACY_ENVELOPE_VERSION = 1;
17
+ var AGENT_ENVELOPE_VERSION = 3;
18
+ var ENVELOPE_VERSION = LEGACY_ENVELOPE_VERSION;
19
+
20
+ // src/hooks/prim-hook-core.ts
21
+ var CORRELATED_TOOL_EVENTS = /* @__PURE__ */ new Set([
22
+ "PostToolUse",
23
+ "PostToolUseFailure",
24
+ "post_tool_call",
25
+ "post_approval_response"
26
+ ]);
27
+ function postToolInvocationId(parsed, agent) {
28
+ if (typeof parsed.hook_event_name !== "string" || !CORRELATED_TOOL_EVENTS.has(parsed.hook_event_name)) {
29
+ return void 0;
30
+ }
31
+ const raw = agent === "hermes" ? parsed.extra?.tool_call_id : parsed.tool_use_id;
32
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
33
+ }
34
+ function lowerString(value) {
35
+ return typeof value === "string" ? value.toLowerCase() : void 0;
36
+ }
37
+ function toolOutcomeFor(parsed, agent) {
38
+ const event = parsed.hook_event_name;
39
+ if (agent === "claude_code") {
40
+ if (event === "PostToolUseFailure") {
41
+ return parsed.is_interrupt === true ? "interrupted" : "failed";
42
+ }
43
+ return event === "PostToolUse" ? "succeeded" : void 0;
44
+ }
45
+ if (agent === "codex") {
46
+ return event === "PostToolUse" ? "returned" : void 0;
47
+ }
48
+ const extra = parsed.extra;
49
+ if (event === "post_approval_response") {
50
+ const choice = lowerString(extra?.choice);
51
+ return (choice === "deny" || choice === "smart_deny" || choice === "timeout") && postToolInvocationId(parsed, agent) ? "prevented" : void 0;
52
+ }
53
+ if (event !== "PostToolUse" && event !== "post_tool_call") {
54
+ return void 0;
55
+ }
56
+ const status = lowerString(extra?.status);
57
+ if (status === "ok" || status === "success") {
58
+ return "succeeded";
59
+ }
60
+ if (status === "error" || status === "timeout") {
61
+ return "failed";
62
+ }
63
+ if (status === "cancelled") {
64
+ return "interrupted";
65
+ }
66
+ if (status === "blocked") {
67
+ return "prevented";
68
+ }
69
+ return "unknown";
70
+ }
71
+ function toMove(parsed, cliVersion, agent = "claude_code", workspaceId, repository, invocationId) {
72
+ const sessionId = parsed.session_id ?? "";
73
+ const eventType = parsed.hook_event_name ?? "unknown";
74
+ const toolOutcome = toolOutcomeFor(parsed, agent);
75
+ let moveId;
76
+ if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId && invocationId) {
77
+ moveId = `posttool:v1:${createHash("sha256").update(JSON.stringify([agent, sessionId, eventType, invocationId])).digest("hex")}`;
78
+ } else if (CORRELATED_TOOL_EVENTS.has(eventType) && sessionId) {
79
+ moveId = `posttool:fallback:v1:${createHash("sha256").update(
80
+ JSON.stringify([
81
+ agent,
82
+ sessionId,
83
+ eventType,
84
+ parsed.turn_id ?? null,
85
+ parsed.tool_name ?? null,
86
+ parsed.tool_input ?? null
87
+ ])
88
+ ).digest("hex")}`;
89
+ } else {
90
+ moveId = randomUUID();
91
+ }
92
+ return {
93
+ moveId,
94
+ capturedAt: Date.now(),
95
+ sessionId,
96
+ eventType,
97
+ payload: parsed,
98
+ env: scrubEnvironmentPaths({
99
+ cwd: parsed.cwd ?? process.cwd(),
100
+ cliVersion,
101
+ osPlatform: platform(),
102
+ ...repository ? {
103
+ repoRoot: repository.repoRoot,
104
+ repoKey: repository.repoKey,
105
+ gitRoot: repository.repoRoot,
106
+ repoFullName: repository.repoFullName,
107
+ repoSyncId: repository.repoSyncId
108
+ } : {},
109
+ ...workspaceId ? { workspaceId } : {}
110
+ }),
111
+ envelopeVersion: AGENT_ENVELOPE_VERSION,
112
+ producer: agent,
113
+ ...invocationId ? { invocationId } : {},
114
+ ...toolOutcome ? { toolOutcome } : {}
115
+ };
116
+ }
117
+ var MAX_REWRITE_PAIRS_PER_MOVE = 40;
118
+ var MAX_REWRITE_PAIRS_PER_HOOK = 2e3;
119
+ var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
120
+ var MAX_CLASSIFIER_ID_CHARS = 256;
121
+ var INVALID_CLASSIFIER_ID_RE = /[\u0000-\u001f\u007f\uD800-\uDFFF]/u;
122
+ function validSessionHint(value) {
123
+ return typeof value === "string" && value.trim().length > 0 && value === value.trim() && value.length <= MAX_CLASSIFIER_ID_CHARS && !INVALID_CLASSIFIER_ID_RE.test(value);
124
+ }
125
+ function commitAttributionFromEnvironment(env, workspaceId) {
126
+ if (env.HERMES_INTERACTIVE) return void 0;
127
+ if (!workspaceId || !isCanonicalWorkspaceId(workspaceId)) return void 0;
128
+ const candidates = [
129
+ validSessionHint(env.CLAUDE_CODE_SESSION_ID) ? { sessionId: env.CLAUDE_CODE_SESSION_ID, producer: "claude_code" } : void 0,
130
+ validSessionHint(env.CODEX_THREAD_ID) ? { sessionId: env.CODEX_THREAD_ID, producer: "codex" } : void 0
131
+ ].filter((candidate) => candidate !== void 0);
132
+ return candidates.length === 1 ? candidates[0] : void 0;
133
+ }
134
+ function toCommitMove(commit, cliVersion, cwd, context = {}) {
135
+ const { repository, repoFullName, repoSyncId, workspaceId, attribution, capturedAt } = context;
136
+ const validatedRepoSyncId = isValidRepoSyncId(repoSyncId) ? repoSyncId : void 0;
137
+ const validatedWorkspaceId = workspaceId && isCanonicalWorkspaceId(workspaceId) ? workspaceId : void 0;
138
+ const validatedAttribution = validatedWorkspaceId ? attribution : void 0;
139
+ const validatedCapturedAt = typeof capturedAt === "number" && Number.isSafeInteger(capturedAt) && capturedAt > 0 ? capturedAt : Date.now();
140
+ const fullSha = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(commit.sha);
141
+ const moveId = validatedRepoSyncId && validatedWorkspaceId && fullSha ? `commit:v2:${createHash("sha256").update(
142
+ JSON.stringify(["commit:v2", validatedRepoSyncId, validatedWorkspaceId, commit.sha])
143
+ ).digest("hex")}` : `commit:v2:unscoped:${randomUUID()}`;
144
+ const base = {
145
+ moveId,
146
+ capturedAt: validatedCapturedAt,
147
+ sessionId: validatedAttribution?.sessionId ?? "",
148
+ eventType: "git.commit",
149
+ payload: {
150
+ kind: "git.commit",
151
+ sha: commit.sha,
152
+ parentSha: commit.parentSha,
153
+ branch: commit.branch,
154
+ changedFiles: commit.files,
155
+ changedFilesComplete: commit.filesComplete
156
+ },
157
+ env: scrubEnvironmentPaths({
158
+ cwd,
159
+ cliVersion,
160
+ osPlatform: platform(),
161
+ ...repository ? {
162
+ repoRoot: repository.repoRoot,
163
+ repoKey: repository.repoKey,
164
+ gitRoot: repository.repoRoot
165
+ } : {},
166
+ ...repoFullName ? { repoFullName } : {},
167
+ ...validatedRepoSyncId ? { repoSyncId: validatedRepoSyncId } : {},
168
+ ...validatedWorkspaceId ? { workspaceId: validatedWorkspaceId } : {}
169
+ })
170
+ };
171
+ return validatedAttribution ? {
172
+ ...base,
173
+ envelopeVersion: AGENT_ENVELOPE_VERSION,
174
+ producer: validatedAttribution.producer
175
+ } : { ...base, envelopeVersion: ENVELOPE_VERSION };
176
+ }
177
+ function canonicalRewritePairs(pairs) {
178
+ const unique = /* @__PURE__ */ new Map();
179
+ for (const pair of pairs) {
180
+ const oldSha = pair.oldSha.toLowerCase();
181
+ const newSha = pair.newSha.toLowerCase();
182
+ if (!FULL_SHA_RE.test(oldSha) || !FULL_SHA_RE.test(newSha)) return null;
183
+ if (oldSha === newSha) continue;
184
+ unique.set(`${oldSha}\0${newSha}`, { oldSha, newSha });
185
+ }
186
+ return [...unique.values()].sort((left, right) => {
187
+ const leftKey = `${left.oldSha}\0${left.newSha}`;
188
+ const rightKey = `${right.oldSha}\0${right.newSha}`;
189
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
190
+ }).slice(0, MAX_REWRITE_PAIRS_PER_HOOK);
191
+ }
192
+ function toRewriteMove(rewrite, cliVersion, cwd, context) {
193
+ const { repository, repoFullName, repoSyncId, workspaceId, capturedAt } = context;
194
+ if (rewrite.source !== "amend" && rewrite.source !== "rebase" || !isValidRepoSyncId(repoSyncId) || !workspaceId || !isCanonicalWorkspaceId(workspaceId)) {
195
+ return [];
196
+ }
197
+ const pairs = canonicalRewritePairs(rewrite.pairs);
198
+ if (!pairs || pairs.length === 0) return [];
199
+ const branch = rewrite.branch && rewrite.branch === rewrite.branch.trim() && ![...rewrite.branch].some((char) => char < " " || char === "\x7F") ? rewrite.branch : void 0;
200
+ const observedAt = typeof capturedAt === "number" && Number.isSafeInteger(capturedAt) && capturedAt > 0 ? capturedAt : Date.now();
201
+ const moves = [];
202
+ for (let offset = 0; offset < pairs.length; offset += MAX_REWRITE_PAIRS_PER_MOVE) {
203
+ const sortedPairs = pairs.slice(offset, offset + MAX_REWRITE_PAIRS_PER_MOVE);
204
+ const moveId = `rewrite:v1:${createHash("sha256").update(JSON.stringify(["rewrite:v1", repoSyncId, workspaceId, rewrite.source, sortedPairs])).digest("hex")}`;
205
+ moves.push({
206
+ moveId,
207
+ capturedAt: observedAt,
208
+ sessionId: "",
209
+ eventType: "git.rewrite",
210
+ payload: {
211
+ kind: "git.rewrite",
212
+ source: rewrite.source,
213
+ pairs: sortedPairs,
214
+ ...branch ? { branch } : {}
215
+ },
216
+ env: scrubEnvironmentPaths({
217
+ cwd,
218
+ cliVersion,
219
+ osPlatform: platform(),
220
+ ...repository ? {
221
+ repoRoot: repository.repoRoot,
222
+ repoKey: repository.repoKey,
223
+ gitRoot: repository.repoRoot
224
+ } : {},
225
+ ...repoFullName ? { repoFullName } : {},
226
+ repoSyncId,
227
+ workspaceId
228
+ }),
229
+ envelopeVersion: ENVELOPE_VERSION
230
+ });
231
+ }
232
+ return moves;
233
+ }
234
+ function shouldFlushAfter(eventType) {
235
+ return eventType === "SessionEnd";
236
+ }
237
+
238
+ export {
239
+ postToolInvocationId,
240
+ toolOutcomeFor,
241
+ toMove,
242
+ commitAttributionFromEnvironment,
243
+ toCommitMove,
244
+ canonicalRewritePairs,
245
+ toRewriteMove,
246
+ shouldFlushAfter
247
+ };