codealmanac 0.2.5 → 0.2.7

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 (79) hide show
  1. package/README.md +25 -20
  2. package/dist/{agents-RVTQYE6A.js → agents-V2ZOIACP.js} +6 -5
  3. package/dist/{chunk-P5WGG4FJ.js → chunk-5BWUMAOX.js} +2 -2
  4. package/dist/chunk-5BWUMAOX.js.map +1 -0
  5. package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
  6. package/dist/chunk-BFIG2CXM.js.map +1 -0
  7. package/dist/{chunk-DL5BXZCX.js → chunk-BQY5L3DL.js} +3 -53
  8. package/dist/chunk-BQY5L3DL.js.map +1 -0
  9. package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
  10. package/dist/chunk-FUBE6KCO.js +124 -0
  11. package/dist/chunk-FUBE6KCO.js.map +1 -0
  12. package/dist/chunk-IZBXXAVL.js +524 -0
  13. package/dist/chunk-IZBXXAVL.js.map +1 -0
  14. package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
  15. package/dist/{chunk-SMIK2YLU.js → chunk-JLQZELHQ.js} +82 -88
  16. package/dist/chunk-JLQZELHQ.js.map +1 -0
  17. package/dist/{chunk-TT6ZP4GS.js → chunk-KZXWPG4P.js} +2 -2
  18. package/dist/{chunk-6BJUYZ43.js → chunk-QIA22IAM.js} +8 -16
  19. package/dist/chunk-QIA22IAM.js.map +1 -0
  20. package/dist/{chunk-BGUID5BS.js → chunk-RALBM6HZ.js} +20 -139
  21. package/dist/chunk-RALBM6HZ.js.map +1 -0
  22. package/dist/{chunk-TILAKDN6.js → chunk-U5DLLWIC.js} +3 -3
  23. package/dist/chunk-WL4UE7Q6.js +1386 -0
  24. package/dist/chunk-WL4UE7Q6.js.map +1 -0
  25. package/dist/{chunk-GFUB57IT.js → chunk-ZUQN5Y3K.js} +48 -124
  26. package/dist/chunk-ZUQN5Y3K.js.map +1 -0
  27. package/dist/{chunk-MRRX4UQB.js → chunk-ZZLLOAI6.js} +3 -3
  28. package/dist/{cli-CL4ID7EO.js → cli-XWPNARA6.js} +35 -18
  29. package/dist/cli-XWPNARA6.js.map +1 -0
  30. package/dist/codealmanac.js +1 -1
  31. package/dist/{config-ML2RCR7J.js → config-KH3JUMG6.js} +4 -4
  32. package/dist/doctor-ENJT665Z.js +18 -0
  33. package/dist/paths-O5CZADP2.js +14 -0
  34. package/dist/process-KFSLENL3.js +61 -0
  35. package/dist/{register-commands-FBJ6XQ3L.js → register-commands-LULZUSPO.js} +993 -1015
  36. package/dist/register-commands-LULZUSPO.js.map +1 -0
  37. package/dist/uninstall-BD4MMQ7M.js +16 -0
  38. package/dist/uninstall-BD4MMQ7M.js.map +1 -0
  39. package/dist/update-XSKPDFMJ.js +11 -0
  40. package/dist/update-XSKPDFMJ.js.map +1 -0
  41. package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
  42. package/dist/wiki-O4RWMAE6.js.map +1 -0
  43. package/guides/mini.md +11 -9
  44. package/guides/reference.md +96 -39
  45. package/hooks/almanac-capture.sh +7 -8
  46. package/package.json +1 -1
  47. package/prompts/agents/.gitkeep +1 -0
  48. package/prompts/base/notability.md +139 -0
  49. package/prompts/base/purpose.md +85 -0
  50. package/prompts/base/syntax.md +114 -0
  51. package/prompts/operations/absorb.md +43 -0
  52. package/prompts/operations/build.md +49 -0
  53. package/prompts/operations/garden.md +51 -0
  54. package/dist/chunk-6BJUYZ43.js.map +0 -1
  55. package/dist/chunk-BGUID5BS.js.map +0 -1
  56. package/dist/chunk-DL5BXZCX.js.map +0 -1
  57. package/dist/chunk-GFUB57IT.js.map +0 -1
  58. package/dist/chunk-KQUVMF27.js.map +0 -1
  59. package/dist/chunk-P5WGG4FJ.js.map +0 -1
  60. package/dist/chunk-SMIK2YLU.js.map +0 -1
  61. package/dist/cli-CL4ID7EO.js.map +0 -1
  62. package/dist/doctor-DOLJRGS4.js +0 -17
  63. package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
  64. package/dist/uninstall-DX6LFKMX.js +0 -15
  65. package/dist/update-P2IPG7RO.js +0 -11
  66. package/dist/wiki-IGNRNLUZ.js.map +0 -1
  67. package/prompts/bootstrap.md +0 -176
  68. package/prompts/reviewer.md +0 -129
  69. package/prompts/writer.md +0 -134
  70. /package/dist/{agents-RVTQYE6A.js.map → agents-V2ZOIACP.js.map} +0 -0
  71. /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
  72. /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
  73. /package/dist/{chunk-TT6ZP4GS.js.map → chunk-KZXWPG4P.js.map} +0 -0
  74. /package/dist/{chunk-TILAKDN6.js.map → chunk-U5DLLWIC.js.map} +0 -0
  75. /package/dist/{chunk-MRRX4UQB.js.map → chunk-ZZLLOAI6.js.map} +0 -0
  76. /package/dist/{config-ML2RCR7J.js.map → config-KH3JUMG6.js.map} +0 -0
  77. /package/dist/{doctor-DOLJRGS4.js.map → doctor-ENJT665Z.js.map} +0 -0
  78. /package/dist/{uninstall-DX6LFKMX.js.map → paths-O5CZADP2.js.map} +0 -0
  79. /package/dist/{update-P2IPG7RO.js.map → process-KFSLENL3.js.map} +0 -0
@@ -0,0 +1,1386 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ parseFrontmatter,
4
+ runIndexer
5
+ } from "./chunk-BFIG2CXM.js";
6
+ import {
7
+ checkClaudeAuth,
8
+ resolveClaudeExecutable
9
+ } from "./chunk-FUBE6KCO.js";
10
+ import {
11
+ getRepoAlmanacDir
12
+ } from "./chunk-IZT6RBHS.js";
13
+
14
+ // src/process/ids.ts
15
+ import { randomBytes } from "crypto";
16
+ function createRunId(now = /* @__PURE__ */ new Date()) {
17
+ const stamp = now.toISOString().replace(/\D/g, "").slice(0, 14);
18
+ const suffix = randomBytes(4).toString("hex");
19
+ return `run_${stamp}_${suffix}`;
20
+ }
21
+
22
+ // src/process/background.ts
23
+ import { spawn as spawn2 } from "child_process";
24
+
25
+ // src/process/logs.ts
26
+ import { mkdir, writeFile, appendFile } from "fs/promises";
27
+ import { dirname } from "path";
28
+ async function initializeRunLog(path) {
29
+ await mkdir(dirname(path), { recursive: true });
30
+ await writeFile(path, "", "utf8");
31
+ }
32
+ async function appendRunEvent(path, event, now = /* @__PURE__ */ new Date()) {
33
+ const entry = {
34
+ timestamp: now.toISOString(),
35
+ event
36
+ };
37
+ await appendFile(path, `${JSON.stringify(entry)}
38
+ `, "utf8");
39
+ }
40
+
41
+ // src/process/manager.ts
42
+ import { join as join3 } from "path";
43
+
44
+ // src/harness/providers/claude.ts
45
+ import {
46
+ query
47
+ } from "@anthropic-ai/claude-agent-sdk";
48
+
49
+ // src/harness/providers/metadata.ts
50
+ var BASE_CAPABILITIES = {
51
+ nonInteractive: true,
52
+ streaming: true,
53
+ modelOverride: true,
54
+ modelOptions: false,
55
+ fileRead: true,
56
+ fileWrite: true,
57
+ shell: true
58
+ };
59
+ var HARNESS_PROVIDER_METADATA = {
60
+ claude: {
61
+ id: "claude",
62
+ displayName: "Claude",
63
+ defaultModel: "claude-sonnet-4-6",
64
+ capabilities: {
65
+ ...BASE_CAPABILITIES,
66
+ reasoningEffort: false,
67
+ sessionPersistence: true,
68
+ threadResume: true,
69
+ interrupt: true,
70
+ mcp: true,
71
+ skills: true,
72
+ usage: true,
73
+ cost: true,
74
+ contextUsage: false,
75
+ structuredOutput: false,
76
+ subagents: {
77
+ supported: true,
78
+ programmaticPerRun: true,
79
+ enforcedToolScopes: true
80
+ },
81
+ policy: {
82
+ sandbox: true,
83
+ strictToolAllowlist: true,
84
+ commandApproval: true,
85
+ toolHook: true
86
+ }
87
+ }
88
+ },
89
+ codex: {
90
+ id: "codex",
91
+ displayName: "Codex",
92
+ defaultModel: null,
93
+ capabilities: {
94
+ ...BASE_CAPABILITIES,
95
+ modelOptions: true,
96
+ reasoningEffort: false,
97
+ sessionPersistence: false,
98
+ threadResume: false,
99
+ interrupt: false,
100
+ mcp: false,
101
+ skills: false,
102
+ usage: true,
103
+ cost: false,
104
+ contextUsage: false,
105
+ structuredOutput: true,
106
+ subagents: {
107
+ supported: false,
108
+ programmaticPerRun: false,
109
+ enforcedToolScopes: false
110
+ },
111
+ policy: {
112
+ sandbox: true,
113
+ strictToolAllowlist: false,
114
+ commandApproval: false,
115
+ toolHook: false
116
+ }
117
+ }
118
+ },
119
+ cursor: {
120
+ id: "cursor",
121
+ displayName: "Cursor",
122
+ defaultModel: null,
123
+ capabilities: {
124
+ nonInteractive: false,
125
+ streaming: false,
126
+ modelOverride: false,
127
+ modelOptions: false,
128
+ fileRead: false,
129
+ fileWrite: false,
130
+ shell: false,
131
+ reasoningEffort: false,
132
+ sessionPersistence: false,
133
+ threadResume: false,
134
+ interrupt: false,
135
+ mcp: false,
136
+ skills: false,
137
+ usage: false,
138
+ cost: false,
139
+ contextUsage: false,
140
+ structuredOutput: false,
141
+ subagents: {
142
+ supported: false,
143
+ programmaticPerRun: false,
144
+ enforcedToolScopes: false
145
+ },
146
+ policy: {
147
+ sandbox: false,
148
+ strictToolAllowlist: false,
149
+ commandApproval: false,
150
+ toolHook: false
151
+ }
152
+ }
153
+ }
154
+ };
155
+
156
+ // src/harness/providers/claude.ts
157
+ function createClaudeHarnessProvider(deps = {}) {
158
+ const queryFn = deps.query ?? query;
159
+ const checkAuthFn = deps.checkAuth ?? (() => checkClaudeAuth());
160
+ const resolveExecutable = deps.resolveExecutable ?? resolveClaudeExecutable;
161
+ const metadata = HARNESS_PROVIDER_METADATA.claude;
162
+ return {
163
+ metadata,
164
+ checkStatus: async () => {
165
+ let auth = { loggedIn: false };
166
+ try {
167
+ auth = await checkAuthFn();
168
+ } catch {
169
+ auth = { loggedIn: false };
170
+ }
171
+ const hasApiKey = process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0;
172
+ const installed = resolveExecutable() !== void 0;
173
+ const authenticated = auth.loggedIn || hasApiKey;
174
+ const detail = authenticated ? auth.email ?? (hasApiKey ? "ANTHROPIC_API_KEY set" : "logged in") : installed ? "not logged in" : "claude not found on PATH";
175
+ return { id: metadata.id, installed, authenticated, detail };
176
+ },
177
+ run: async (spec, hooks) => runClaudeHarness(spec, hooks, queryFn, resolveExecutable)
178
+ };
179
+ }
180
+ var claudeHarnessProvider = createClaudeHarnessProvider();
181
+ async function runClaudeHarness(spec, hooks, queryFn, resolveExecutable) {
182
+ const options = buildClaudeOptions(spec, resolveExecutable);
183
+ const stream = queryFn({
184
+ prompt: spec.prompt,
185
+ options
186
+ });
187
+ let costUsd;
188
+ let turns;
189
+ let result = "";
190
+ let providerSessionId;
191
+ let success = false;
192
+ let error;
193
+ let failure;
194
+ let usage;
195
+ try {
196
+ for await (const message of stream) {
197
+ providerSessionId = providerSessionId ?? getSessionId(message);
198
+ for (const event of toHarnessEvents(message)) {
199
+ await hooks?.onEvent?.(event);
200
+ }
201
+ if (message.type === "result") {
202
+ costUsd = message.total_cost_usd;
203
+ turns = message.num_turns;
204
+ usage = mapUsage(message.usage);
205
+ providerSessionId = providerSessionId ?? message.session_id;
206
+ if (message.subtype === "success") {
207
+ success = true;
208
+ result = message.result;
209
+ } else {
210
+ success = false;
211
+ error = message.errors.length > 0 ? message.errors.join("; ") : `agent error: ${message.subtype}`;
212
+ failure = classifyClaudeFailure(error, message.subtype);
213
+ }
214
+ }
215
+ }
216
+ } catch (err) {
217
+ success = false;
218
+ error = err instanceof Error ? err.message : String(err);
219
+ failure = classifyClaudeFailure(error);
220
+ await hooks?.onEvent?.({ type: "error", error, failure });
221
+ }
222
+ await hooks?.onEvent?.({
223
+ type: "done",
224
+ result,
225
+ providerSessionId,
226
+ costUsd,
227
+ turns,
228
+ usage,
229
+ error,
230
+ failure
231
+ });
232
+ return {
233
+ success,
234
+ result,
235
+ providerSessionId,
236
+ costUsd,
237
+ turns,
238
+ usage,
239
+ error,
240
+ failure
241
+ };
242
+ }
243
+ function classifyClaudeFailure(raw, subtype) {
244
+ if (raw.includes("Not logged in") || raw.includes("authentication")) {
245
+ return {
246
+ provider: "claude",
247
+ code: "claude.not_authenticated",
248
+ message: "Claude is not authenticated in this environment.",
249
+ fix: "Run `claude` and log in, or configure ANTHROPIC_API_KEY for this process.",
250
+ raw,
251
+ details: subtype !== void 0 ? { subtype } : void 0
252
+ };
253
+ }
254
+ if (subtype === "error_max_budget_usd") {
255
+ return {
256
+ provider: "claude",
257
+ code: "claude.max_budget_exceeded",
258
+ message: "Claude stopped because the run exceeded its maximum budget.",
259
+ fix: "Raise the budget for this run or use a cheaper model.",
260
+ raw,
261
+ details: { subtype }
262
+ };
263
+ }
264
+ return {
265
+ provider: "claude",
266
+ code: subtype !== void 0 ? `claude.${subtype}` : "claude.process_failed",
267
+ message: raw,
268
+ raw,
269
+ details: subtype !== void 0 ? { subtype } : void 0
270
+ };
271
+ }
272
+ function buildClaudeOptions(spec, resolveExecutable) {
273
+ const tools = toClaudeTools(spec.tools ?? []);
274
+ const agents = toClaudeAgents(spec.agents ?? {});
275
+ if (Object.keys(agents).length > 0 && !tools.includes("Agent")) {
276
+ tools.push("Agent");
277
+ }
278
+ const claudeExecutable = resolveExecutable();
279
+ return pruneUndefined({
280
+ systemPrompt: spec.systemPrompt,
281
+ cwd: spec.cwd,
282
+ model: spec.provider.model ?? HARNESS_PROVIDER_METADATA.claude.defaultModel ?? void 0,
283
+ effort: toClaudeEffort(spec.provider.effort),
284
+ tools,
285
+ allowedTools: tools,
286
+ agents,
287
+ mcpServers: spec.mcpServers,
288
+ maxTurns: spec.limits?.maxTurns ?? 100,
289
+ maxBudgetUsd: spec.limits?.maxCostUsd,
290
+ permissionMode: "dontAsk",
291
+ includePartialMessages: true,
292
+ env: {
293
+ ...process.env,
294
+ CODEALMANAC_INTERNAL_SESSION: "1"
295
+ },
296
+ ...claudeExecutable !== void 0 ? { pathToClaudeCodeExecutable: claudeExecutable } : {}
297
+ });
298
+ }
299
+ function toClaudeAgents(agents) {
300
+ const out = {};
301
+ for (const [name, agent] of Object.entries(agents)) {
302
+ out[name] = pruneUndefined({
303
+ description: agent.description,
304
+ prompt: agent.prompt,
305
+ tools: agent.tools !== void 0 ? toClaudeTools(agent.tools) : void 0,
306
+ model: agent.model,
307
+ maxTurns: agent.maxTurns,
308
+ mcpServers: agent.mcpServers,
309
+ skills: agent.skills
310
+ });
311
+ }
312
+ return out;
313
+ }
314
+ function toClaudeTools(tools) {
315
+ const out = /* @__PURE__ */ new Set();
316
+ for (const tool of tools) {
317
+ switch (tool.id) {
318
+ case "read":
319
+ out.add("Read");
320
+ break;
321
+ case "write":
322
+ out.add("Write");
323
+ break;
324
+ case "edit":
325
+ out.add("Edit");
326
+ break;
327
+ case "search":
328
+ out.add("Glob");
329
+ out.add("Grep");
330
+ break;
331
+ case "shell":
332
+ out.add("Bash");
333
+ break;
334
+ case "web":
335
+ out.add("WebSearch");
336
+ out.add("WebFetch");
337
+ break;
338
+ case "mcp":
339
+ break;
340
+ }
341
+ }
342
+ return [...out];
343
+ }
344
+ function toClaudeEffort(effort) {
345
+ if (effort === "low" || effort === "medium" || effort === "high" || effort === "max") {
346
+ return effort;
347
+ }
348
+ return void 0;
349
+ }
350
+ function toHarnessEvents(message) {
351
+ if (message.type === "stream_event") {
352
+ const text = getTextDelta(message.event);
353
+ return text !== void 0 ? [{ type: "text_delta", content: text }] : [];
354
+ }
355
+ if (message.type === "assistant") {
356
+ const content = message.message.content;
357
+ if (!Array.isArray(content)) return [];
358
+ const events = [];
359
+ for (const block of content) {
360
+ if (block.type === "text") {
361
+ events.push({ type: "text", content: block.text });
362
+ continue;
363
+ }
364
+ if (block.type === "tool_use") {
365
+ events.push({
366
+ type: "tool_use",
367
+ id: block.id,
368
+ tool: block.name,
369
+ input: stringifyInput(block.input)
370
+ });
371
+ }
372
+ }
373
+ return events;
374
+ }
375
+ if (message.type === "user") {
376
+ const content = message.message.content;
377
+ if (!Array.isArray(content)) return [];
378
+ return content.flatMap((block) => {
379
+ if (block.type !== "tool_result") return [];
380
+ return [
381
+ {
382
+ type: "tool_result",
383
+ id: block.tool_use_id,
384
+ content: block.content,
385
+ isError: block.is_error
386
+ }
387
+ ];
388
+ });
389
+ }
390
+ if (message.type === "tool_use_summary") {
391
+ return [{ type: "tool_summary", summary: message.summary }];
392
+ }
393
+ if (message.type === "result" && message.subtype !== "success") {
394
+ return message.errors.map((err) => ({ type: "error", error: err }));
395
+ }
396
+ return [];
397
+ }
398
+ function getTextDelta(event) {
399
+ if (event === null || typeof event !== "object") return void 0;
400
+ const raw = event;
401
+ return raw.type === "content_block_delta" && raw.delta?.type === "text_delta" && typeof raw.delta.text === "string" ? raw.delta.text : void 0;
402
+ }
403
+ function getSessionId(message) {
404
+ return "session_id" in message && typeof message.session_id === "string" ? message.session_id : void 0;
405
+ }
406
+ function stringifyInput(input) {
407
+ if (input === void 0) return void 0;
408
+ if (typeof input === "string") return input;
409
+ try {
410
+ return JSON.stringify(input);
411
+ } catch {
412
+ return String(input);
413
+ }
414
+ }
415
+ function mapUsage(value) {
416
+ if (value === null || typeof value !== "object") return void 0;
417
+ const usage = value;
418
+ const inputTokens = numberField(usage, "input_tokens");
419
+ const cachedInputTokens = numberField(usage, "cache_read_input_tokens");
420
+ const outputTokens = numberField(usage, "output_tokens");
421
+ return pruneUndefined({
422
+ inputTokens,
423
+ cachedInputTokens,
424
+ outputTokens,
425
+ totalTokens: inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0
426
+ });
427
+ }
428
+ function numberField(record, field) {
429
+ const value = record[field];
430
+ return typeof value === "number" ? value : void 0;
431
+ }
432
+ function pruneUndefined(value) {
433
+ for (const key of Object.keys(value)) {
434
+ if (value[key] === void 0) {
435
+ delete value[key];
436
+ }
437
+ }
438
+ return value;
439
+ }
440
+
441
+ // src/harness/providers/codex.ts
442
+ import { spawn, spawnSync } from "child_process";
443
+ function createCodexHarnessProvider(deps = {}) {
444
+ const metadata = HARNESS_PROVIDER_METADATA.codex;
445
+ const commandExists = deps.commandExists ?? defaultCommandExists;
446
+ const runStatus = deps.runStatus ?? defaultRunStatus;
447
+ const runCli = deps.runCli ?? runCodexCli;
448
+ return {
449
+ metadata,
450
+ checkStatus: async () => {
451
+ if (!commandExists("codex")) {
452
+ return {
453
+ id: metadata.id,
454
+ installed: false,
455
+ authenticated: false,
456
+ detail: "codex not found on PATH"
457
+ };
458
+ }
459
+ const auth = await runStatus("codex", ["login", "status"]);
460
+ return {
461
+ id: metadata.id,
462
+ installed: true,
463
+ authenticated: auth.ok,
464
+ detail: auth.detail
465
+ };
466
+ },
467
+ run: async (spec, hooks) => {
468
+ if (spec.agents !== void 0 && Object.keys(spec.agents).length > 0) {
469
+ return {
470
+ success: false,
471
+ result: "",
472
+ error: "Codex exec adapter does not support per-run programmatic agents",
473
+ failure: {
474
+ provider: "codex",
475
+ code: "codex.unsupported_feature",
476
+ message: "Codex exec adapter does not support per-run programmatic agents.",
477
+ fix: "Run this operation with a provider that supports per-run subagents."
478
+ }
479
+ };
480
+ }
481
+ return runCli(buildCodexExecRequest(spec), hooks);
482
+ }
483
+ };
484
+ }
485
+ var codexHarnessProvider = createCodexHarnessProvider();
486
+ function buildCodexExecRequest(spec) {
487
+ const unsupported = unsupportedCodexSpecFields(spec);
488
+ if (unsupported.length > 0) {
489
+ throw new Error(
490
+ `Codex exec adapter does not support: ${unsupported.join(", ")}`
491
+ );
492
+ }
493
+ const args = [
494
+ "exec",
495
+ "--json",
496
+ "--sandbox",
497
+ "workspace-write",
498
+ "--skip-git-repo-check",
499
+ "-C",
500
+ spec.cwd
501
+ ];
502
+ if (spec.provider.model !== void 0 && spec.provider.model.length > 0) {
503
+ args.push("--model", spec.provider.model);
504
+ }
505
+ if (spec.output?.schemaPath !== void 0) {
506
+ args.push("--output-schema", spec.output.schemaPath);
507
+ }
508
+ args.push(combineCodexPrompt(spec));
509
+ return {
510
+ command: "codex",
511
+ args,
512
+ cwd: spec.cwd,
513
+ env: {
514
+ ...process.env,
515
+ CODEALMANAC_INTERNAL_SESSION: "1"
516
+ }
517
+ };
518
+ }
519
+ function unsupportedCodexSpecFields(spec) {
520
+ const unsupported = [];
521
+ if (spec.provider.effort !== void 0) unsupported.push("provider.effort");
522
+ if (spec.skills !== void 0 && spec.skills.length > 0) unsupported.push("skills");
523
+ if (spec.mcpServers !== void 0 && Object.keys(spec.mcpServers).length > 0) {
524
+ unsupported.push("mcpServers");
525
+ }
526
+ if (spec.limits?.maxCostUsd !== void 0) unsupported.push("limits.maxCostUsd");
527
+ return unsupported;
528
+ }
529
+ function combineCodexPrompt(spec) {
530
+ const blocks = [spec.systemPrompt, spec.prompt].filter(
531
+ (block) => block !== void 0 && block.trim().length > 0
532
+ );
533
+ return blocks.join("\n\n---\n\n");
534
+ }
535
+ function runCodexCli(request, hooks) {
536
+ return new Promise((resolve) => {
537
+ const child = spawn(request.command, request.args, {
538
+ cwd: request.cwd,
539
+ env: request.env,
540
+ stdio: ["ignore", "pipe", "pipe"]
541
+ });
542
+ let stdoutBuf = "";
543
+ let stderr = "";
544
+ const state = {
545
+ success: false,
546
+ result: ""
547
+ };
548
+ const eventWrites = [];
549
+ const observe = (msg) => {
550
+ eventWrites.push(applyCodexJsonlEvent(state, msg, hooks));
551
+ };
552
+ const flushLines = () => {
553
+ let idx = stdoutBuf.indexOf("\n");
554
+ while (idx !== -1) {
555
+ const rawLine = stdoutBuf.slice(0, idx);
556
+ stdoutBuf = stdoutBuf.slice(idx + 1);
557
+ const line = rawLine.trim();
558
+ if (line.length > 0) {
559
+ try {
560
+ observe(JSON.parse(line));
561
+ } catch {
562
+ }
563
+ }
564
+ idx = stdoutBuf.indexOf("\n");
565
+ }
566
+ };
567
+ child.stdout.on("data", (chunk) => {
568
+ stdoutBuf += chunk.toString("utf8");
569
+ flushLines();
570
+ });
571
+ child.stderr.on("data", (chunk) => {
572
+ stderr += chunk.toString("utf8");
573
+ });
574
+ child.on("error", (err) => {
575
+ resolve({
576
+ success: false,
577
+ result: state.result,
578
+ providerSessionId: state.providerSessionId,
579
+ turns: state.turns,
580
+ usage: state.usage,
581
+ error: err.code === "ENOENT" ? `${request.command} not found on PATH` : err.message
582
+ });
583
+ });
584
+ child.on("close", async (code) => {
585
+ flushLines();
586
+ if (stdoutBuf.trim().length > 0) {
587
+ try {
588
+ observe(JSON.parse(stdoutBuf.trim()));
589
+ } catch {
590
+ }
591
+ }
592
+ await Promise.allSettled(eventWrites);
593
+ if (code === 0 && state.success) {
594
+ resolve(toHarnessResult(state));
595
+ return;
596
+ }
597
+ const firstStderr = stderr.trim().split("\n")[0];
598
+ const fallbackError = firstStderr !== void 0 && firstStderr.length > 0 ? firstStderr : `${request.command} exited ${code ?? 1}`;
599
+ const failure = state.failure ?? classifyCodexFailure(fallbackError);
600
+ resolve({
601
+ ...toHarnessResult(state),
602
+ success: false,
603
+ error: state.error ?? failure.message,
604
+ failure
605
+ });
606
+ });
607
+ });
608
+ }
609
+ async function applyCodexJsonlEvent(state, input, hooks) {
610
+ const msg = unwrapCodexJsonlEvent(input);
611
+ const sessionId = stringField(msg, "session_id") ?? stringField(msg, "thread_id");
612
+ if (state.providerSessionId === void 0 && sessionId !== void 0) {
613
+ state.providerSessionId = sessionId;
614
+ }
615
+ if (msg.type === "item.completed") {
616
+ const item = objectField(msg, "item");
617
+ if (item?.type === "agent_message") {
618
+ const text = stringField(item, "text");
619
+ if (text !== void 0) {
620
+ state.result = text;
621
+ await hooks?.onEvent?.({ type: "text", content: text });
622
+ }
623
+ }
624
+ if (item?.type === "tool_call") {
625
+ await emitToolUse(item, hooks);
626
+ }
627
+ return;
628
+ }
629
+ if (msg.type === "turn.completed") {
630
+ state.success = true;
631
+ state.turns = 1;
632
+ state.usage = parseCodexUsage(msg.usage);
633
+ await hooks?.onEvent?.({
634
+ type: "done",
635
+ result: state.result,
636
+ providerSessionId: state.providerSessionId,
637
+ turns: state.turns,
638
+ usage: state.usage
639
+ });
640
+ return;
641
+ }
642
+ if (msg.type === "turn.failed" || msg.type === "error") {
643
+ state.success = false;
644
+ const raw = stringField(msg, "message") ?? stringField(msg, "error") ?? "codex turn failed";
645
+ const failure = classifyCodexFailure(raw);
646
+ state.error = failure.message;
647
+ state.failure = failure;
648
+ await hooks?.onEvent?.({
649
+ type: "error",
650
+ error: state.error,
651
+ failure
652
+ });
653
+ }
654
+ }
655
+ function toHarnessResult(state) {
656
+ return {
657
+ success: state.success,
658
+ result: state.result,
659
+ providerSessionId: state.providerSessionId,
660
+ turns: state.turns,
661
+ usage: state.usage,
662
+ error: state.error,
663
+ failure: state.failure
664
+ };
665
+ }
666
+ function unwrapCodexJsonlEvent(input) {
667
+ const msg = objectField(input, "msg");
668
+ return msg ?? input;
669
+ }
670
+ function classifyCodexFailure(raw) {
671
+ const detail = extractJsonDetail(raw);
672
+ const text = detail ?? raw;
673
+ const statusCode = extractStatusCode(raw);
674
+ const model = matchFirst(text, /The '([^']+)' model requires a newer version of Codex/) ?? matchFirst(text, /The '([^']+)' model is not supported/);
675
+ if (text.includes("requires a newer version of Codex") && model !== void 0) {
676
+ return {
677
+ provider: "codex",
678
+ code: "codex.model_requires_newer_cli",
679
+ message: `Codex model ${model} requires a newer Codex CLI.`,
680
+ fix: "Upgrade Codex, or run with --using codex/<supported-model>.",
681
+ raw,
682
+ details: codexFailureDetails({ model, statusCode })
683
+ };
684
+ }
685
+ if (text.includes("model is not supported") && model !== void 0) {
686
+ return {
687
+ provider: "codex",
688
+ code: "codex.model_unavailable",
689
+ message: `Codex model ${model} is not available for this account.`,
690
+ fix: "Choose a supported model with --using codex/<model>, or update the configured Codex model.",
691
+ raw,
692
+ details: codexFailureDetails({ model, statusCode })
693
+ };
694
+ }
695
+ if (text.includes("401 Unauthorized") || text.includes("Unauthorized")) {
696
+ return {
697
+ provider: "codex",
698
+ code: "codex.not_authenticated",
699
+ message: "Codex is not authenticated in this environment.",
700
+ fix: "Run `codex login` in the same environment, or make the existing Codex auth available to this process.",
701
+ raw,
702
+ details: codexFailureDetails({ statusCode: statusCode ?? 401 })
703
+ };
704
+ }
705
+ if (text.includes("not found on PATH")) {
706
+ return {
707
+ provider: "codex",
708
+ code: "codex.not_installed",
709
+ message: "Codex was not found on PATH.",
710
+ fix: "Install Codex or update PATH so the `codex` command is available.",
711
+ raw
712
+ };
713
+ }
714
+ return {
715
+ provider: "codex",
716
+ code: "codex.process_failed",
717
+ message: text,
718
+ raw,
719
+ details: codexFailureDetails({ statusCode })
720
+ };
721
+ }
722
+ function codexFailureDetails(details) {
723
+ const pruned = pruneUndefined2(details);
724
+ return Object.keys(pruned).length > 0 ? pruned : void 0;
725
+ }
726
+ function extractJsonDetail(raw) {
727
+ const start = raw.indexOf("{");
728
+ const end = raw.lastIndexOf("}");
729
+ if (start === -1 || end <= start) return void 0;
730
+ try {
731
+ const parsed = JSON.parse(raw.slice(start, end + 1));
732
+ if (parsed !== null && typeof parsed === "object") {
733
+ const detail = parsed.detail;
734
+ return typeof detail === "string" && detail.length > 0 ? detail : void 0;
735
+ }
736
+ } catch {
737
+ return void 0;
738
+ }
739
+ return void 0;
740
+ }
741
+ function extractStatusCode(raw) {
742
+ const match = raw.match(/status\s+(\d{3})|(\d{3})\s+(?:Bad Request|Unauthorized)/);
743
+ if (match === null) return void 0;
744
+ const value = match[1] ?? match[2];
745
+ return value !== void 0 ? Number.parseInt(value, 10) : void 0;
746
+ }
747
+ function matchFirst(text, pattern) {
748
+ const match = text.match(pattern);
749
+ return match?.[1];
750
+ }
751
+ async function emitToolUse(item, hooks) {
752
+ const tool = stringField(item, "name") ?? stringField(item, "tool_name");
753
+ if (tool === void 0) return;
754
+ await hooks?.onEvent?.({
755
+ type: "tool_use",
756
+ id: stringField(item, "id"),
757
+ tool,
758
+ input: stringifyInput2(item.input ?? item.arguments)
759
+ });
760
+ }
761
+ function parseCodexUsage(value) {
762
+ if (value === null || typeof value !== "object") return void 0;
763
+ const obj = value;
764
+ const inputTokens = numberField2(obj, "input_tokens") ?? numberField2(obj, "inputTokens");
765
+ const cachedInputTokens = numberField2(obj, "cached_input_tokens") ?? numberField2(obj, "cachedInputTokens") ?? numberField2(obj, "cacheReadTokens");
766
+ const outputTokens = numberField2(obj, "output_tokens") ?? numberField2(obj, "outputTokens");
767
+ const reasoningOutputTokens = numberField2(obj, "reasoning_output_tokens") ?? numberField2(obj, "reasoningOutputTokens");
768
+ return pruneUndefined2({
769
+ inputTokens,
770
+ cachedInputTokens,
771
+ outputTokens,
772
+ reasoningOutputTokens,
773
+ totalTokens: inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0
774
+ });
775
+ }
776
+ function objectField(record, field) {
777
+ const value = record[field];
778
+ return value !== null && typeof value === "object" ? value : void 0;
779
+ }
780
+ function stringField(record, field) {
781
+ const value = record[field];
782
+ return typeof value === "string" && value.length > 0 ? value : void 0;
783
+ }
784
+ function numberField2(record, field) {
785
+ const value = record[field];
786
+ return typeof value === "number" ? value : void 0;
787
+ }
788
+ function stringifyInput2(input) {
789
+ if (input === void 0) return void 0;
790
+ if (typeof input === "string") return input;
791
+ try {
792
+ return JSON.stringify(input);
793
+ } catch {
794
+ return String(input);
795
+ }
796
+ }
797
+ function pruneUndefined2(value) {
798
+ for (const key of Object.keys(value)) {
799
+ if (value[key] === void 0) {
800
+ delete value[key];
801
+ }
802
+ }
803
+ return value;
804
+ }
805
+ function defaultCommandExists(command) {
806
+ const result = spawnSync("sh", ["-lc", `command -v ${command}`], {
807
+ encoding: "utf8"
808
+ });
809
+ return result.status === 0 && result.stdout.trim().length > 0;
810
+ }
811
+ function defaultRunStatus(command, args) {
812
+ return new Promise((resolve) => {
813
+ let child;
814
+ let stdout = "";
815
+ let stderr = "";
816
+ try {
817
+ child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
818
+ } catch (err) {
819
+ resolve({
820
+ ok: false,
821
+ detail: err instanceof Error ? err.message : String(err)
822
+ });
823
+ return;
824
+ }
825
+ child.stdout?.on("data", (chunk) => {
826
+ stdout += chunk.toString("utf8");
827
+ });
828
+ child.stderr?.on("data", (chunk) => {
829
+ stderr += chunk.toString("utf8");
830
+ });
831
+ child.on("error", (err) => {
832
+ resolve({ ok: false, detail: err.message });
833
+ });
834
+ child.on("close", (code) => {
835
+ const text = `${stdout}
836
+ ${stderr}`.trim();
837
+ resolve({
838
+ ok: code === 0,
839
+ detail: text.split("\n").find((line) => line.trim().length > 0)?.trim() ?? (code === 0 ? "ready" : `${command} exited ${code ?? 1}`)
840
+ });
841
+ });
842
+ });
843
+ }
844
+
845
+ // src/harness/providers/not-implemented.ts
846
+ function createNotImplementedProvider(metadata) {
847
+ return {
848
+ metadata,
849
+ checkStatus: async () => ({
850
+ id: metadata.id,
851
+ installed: false,
852
+ authenticated: false,
853
+ detail: `${metadata.displayName} harness adapter is not implemented yet`
854
+ }),
855
+ run: async (spec) => ({
856
+ success: false,
857
+ result: "",
858
+ error: `${metadata.displayName} harness adapter is not implemented yet for ${spec.metadata?.operation ?? "unknown"} runs`
859
+ })
860
+ };
861
+ }
862
+
863
+ // src/harness/providers/cursor.ts
864
+ var cursorHarnessProvider = createNotImplementedProvider(
865
+ HARNESS_PROVIDER_METADATA.cursor
866
+ );
867
+
868
+ // src/harness/providers/index.ts
869
+ var HARNESS_PROVIDERS = {
870
+ claude: claudeHarnessProvider,
871
+ codex: codexHarnessProvider,
872
+ cursor: cursorHarnessProvider
873
+ };
874
+ function getHarnessProvider(id) {
875
+ return HARNESS_PROVIDERS[id];
876
+ }
877
+
878
+ // src/process/records.ts
879
+ import { existsSync } from "fs";
880
+ import { mkdir as mkdir2, readFile, readdir, rename, writeFile as writeFile2 } from "fs/promises";
881
+ import { dirname as dirname2, join } from "path";
882
+ function runsDir(repoRoot) {
883
+ return join(getRepoAlmanacDir(repoRoot), "runs");
884
+ }
885
+ function runRecordPath(repoRoot, runId) {
886
+ return join(runsDir(repoRoot), `${runId}.json`);
887
+ }
888
+ function runLogPath(repoRoot, runId) {
889
+ return join(runsDir(repoRoot), `${runId}.jsonl`);
890
+ }
891
+ function runCancelPath(repoRoot, runId) {
892
+ return join(runsDir(repoRoot), `${runId}.cancel`);
893
+ }
894
+ async function markRunCancelled(repoRoot, runId) {
895
+ const path = runCancelPath(repoRoot, runId);
896
+ await mkdir2(dirname2(path), { recursive: true });
897
+ await writeFile2(path, "cancelled\n", "utf8");
898
+ }
899
+ function isRunCancellationRequested(repoRoot, runId) {
900
+ return existsSync(runCancelPath(repoRoot, runId));
901
+ }
902
+ async function writeRunRecord(path, record) {
903
+ await mkdir2(dirname2(path), { recursive: true });
904
+ const tmp = `${path}.tmp-${process.pid}`;
905
+ await writeFile2(tmp, `${JSON.stringify(record, null, 2)}
906
+ `, "utf8");
907
+ await rename(tmp, path);
908
+ }
909
+ function buildStartedRunRecord(args) {
910
+ return {
911
+ version: 1,
912
+ id: args.runId,
913
+ operation: args.spec.metadata?.operation ?? "absorb",
914
+ status: "running",
915
+ repoRoot: args.repoRoot,
916
+ pid: args.pid ?? process.pid,
917
+ provider: args.spec.provider.id,
918
+ model: args.spec.provider.model,
919
+ startedAt: args.startedAt.toISOString(),
920
+ logPath: runLogPath(args.repoRoot, args.runId),
921
+ targetKind: args.spec.metadata?.targetKind,
922
+ targetPaths: args.spec.metadata?.targetPaths
923
+ };
924
+ }
925
+ function buildQueuedRunRecord(args) {
926
+ return {
927
+ ...buildStartedRunRecord({
928
+ runId: args.runId,
929
+ repoRoot: args.repoRoot,
930
+ spec: args.spec,
931
+ startedAt: args.queuedAt,
932
+ pid: 0
933
+ }),
934
+ status: "queued"
935
+ };
936
+ }
937
+ function finishRunRecord(args) {
938
+ const started = Date.parse(args.record.startedAt);
939
+ const finished = args.finishedAt.getTime();
940
+ return {
941
+ ...args.record,
942
+ status: args.status,
943
+ providerSessionId: args.providerSessionId ?? args.record.providerSessionId,
944
+ finishedAt: args.finishedAt.toISOString(),
945
+ durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
946
+ summary: args.summary,
947
+ error: args.error,
948
+ failure: args.failure
949
+ };
950
+ }
951
+ async function readRunRecord(path) {
952
+ try {
953
+ const parsed = JSON.parse(await readFile(path, "utf8"));
954
+ return isRunRecord(parsed) ? parsed : null;
955
+ } catch {
956
+ return null;
957
+ }
958
+ }
959
+ async function listRunRecords(repoRoot) {
960
+ const dir = runsDir(repoRoot);
961
+ if (!existsSync(dir)) return [];
962
+ let entries;
963
+ try {
964
+ entries = await readdir(dir);
965
+ } catch {
966
+ return [];
967
+ }
968
+ const records = [];
969
+ for (const entry of entries) {
970
+ if (!entry.startsWith("run_") || !entry.endsWith(".json")) continue;
971
+ const record = await readRunRecord(join(dir, entry));
972
+ if (record !== null) records.push(record);
973
+ }
974
+ return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
975
+ }
976
+ function toRunView(args) {
977
+ const started = Date.parse(args.record.startedAt);
978
+ const finished = args.record.finishedAt !== void 0 ? Date.parse(args.record.finishedAt) : void 0;
979
+ const elapsedMs = args.record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? args.now.getTime()) - started) : 0);
980
+ const displayStatus = args.record.status === "running" && !args.isPidAlive(args.record.pid) ? "stale" : args.record.status;
981
+ return {
982
+ ...args.record,
983
+ displayStatus,
984
+ elapsedMs,
985
+ error: displayStatus === "stale" ? "process ended without a final status" : args.record.error
986
+ };
987
+ }
988
+ function isRunRecord(value) {
989
+ if (value === null || typeof value !== "object") return false;
990
+ const v = value;
991
+ return v.version === 1 && typeof v.id === "string" && v.id.startsWith("run_") && (v.operation === "build" || v.operation === "absorb" || v.operation === "garden") && (v.status === "queued" || v.status === "running" || v.status === "done" || v.status === "failed" || v.status === "cancelled") && typeof v.repoRoot === "string" && typeof v.pid === "number" && (v.provider === "claude" || v.provider === "codex" || v.provider === "cursor") && typeof v.startedAt === "string" && typeof v.logPath === "string";
992
+ }
993
+
994
+ // src/process/snapshots.ts
995
+ import { createHash } from "crypto";
996
+ import { existsSync as existsSync2, statSync } from "fs";
997
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
998
+ import { join as join2 } from "path";
999
+ async function snapshotPages(pagesDir) {
1000
+ const out = /* @__PURE__ */ new Map();
1001
+ if (!existsSync2(pagesDir)) return out;
1002
+ let entries;
1003
+ try {
1004
+ entries = await readdir2(pagesDir);
1005
+ } catch {
1006
+ return out;
1007
+ }
1008
+ for (const entry of entries) {
1009
+ if (!entry.endsWith(".md")) continue;
1010
+ const slug = entry.slice(0, -3);
1011
+ const full = join2(pagesDir, entry);
1012
+ try {
1013
+ const st = statSync(full);
1014
+ if (!st.isFile()) continue;
1015
+ const content = await readFile2(full, "utf8");
1016
+ out.set(slug, {
1017
+ slug,
1018
+ hash: createHash("sha256").update(content).digest("hex"),
1019
+ archived: parseFrontmatter(content).archived_at !== null
1020
+ });
1021
+ } catch {
1022
+ continue;
1023
+ }
1024
+ }
1025
+ return out;
1026
+ }
1027
+ function diffPageSnapshots(before, after) {
1028
+ let created = 0;
1029
+ let updated = 0;
1030
+ let archived = 0;
1031
+ let deleted = 0;
1032
+ for (const [slug, entry] of after) {
1033
+ const prev = before.get(slug);
1034
+ if (prev === void 0) {
1035
+ created += 1;
1036
+ continue;
1037
+ }
1038
+ if (prev.hash === entry.hash) continue;
1039
+ if (!prev.archived && entry.archived) {
1040
+ archived += 1;
1041
+ } else {
1042
+ updated += 1;
1043
+ }
1044
+ }
1045
+ for (const slug of before.keys()) {
1046
+ if (!after.has(slug)) deleted += 1;
1047
+ }
1048
+ return { created, updated, archived, deleted };
1049
+ }
1050
+ function isNoopPageDelta(delta) {
1051
+ return delta.created === 0 && delta.updated === 0 && delta.archived === 0 && delta.deleted === 0;
1052
+ }
1053
+
1054
+ // src/process/manager.ts
1055
+ async function startForegroundProcess(options) {
1056
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
1057
+ const runId = options.runId ?? createRunId(now());
1058
+ const startedAt = now();
1059
+ const recordPath = runRecordPath(options.repoRoot, runId);
1060
+ const started = buildStartedRunRecord({
1061
+ runId,
1062
+ repoRoot: options.repoRoot,
1063
+ spec: options.spec,
1064
+ startedAt,
1065
+ pid: options.pid
1066
+ });
1067
+ const preStart = await cancelledRecordIfRequested({
1068
+ recordPath,
1069
+ repoRoot: options.repoRoot,
1070
+ runId,
1071
+ fallback: started,
1072
+ finishedAt: now()
1073
+ });
1074
+ if (preStart !== null) {
1075
+ return {
1076
+ runId,
1077
+ record: preStart,
1078
+ result: {
1079
+ success: false,
1080
+ result: "",
1081
+ error: "run cancelled before start"
1082
+ }
1083
+ };
1084
+ }
1085
+ await writeRunRecord(recordPath, started);
1086
+ await initializeRunLog(started.logPath);
1087
+ const afterStart = await cancelledRecordIfRequested({
1088
+ recordPath,
1089
+ repoRoot: options.repoRoot,
1090
+ runId,
1091
+ fallback: started,
1092
+ finishedAt: now()
1093
+ });
1094
+ if (afterStart !== null) {
1095
+ return {
1096
+ runId,
1097
+ record: afterStart,
1098
+ result: {
1099
+ success: false,
1100
+ result: "",
1101
+ error: "run cancelled before start"
1102
+ }
1103
+ };
1104
+ }
1105
+ const harnessRun = options.harnessRun ?? ((spec, hooks) => getHarnessProvider(spec.provider.id).run(spec, hooks));
1106
+ const eventWrites = [];
1107
+ let result;
1108
+ let finalRecord;
1109
+ try {
1110
+ const pagesDir = join3(options.repoRoot, ".almanac", "pages");
1111
+ const before = await snapshotPages(pagesDir);
1112
+ try {
1113
+ result = await harnessRun(options.spec, {
1114
+ onEvent: eventLogger(started.logPath, now, eventWrites, options.onEvent)
1115
+ });
1116
+ } catch (err) {
1117
+ result = {
1118
+ success: false,
1119
+ result: "",
1120
+ error: err instanceof Error ? err.message : String(err)
1121
+ };
1122
+ await appendRunEvent(started.logPath, {
1123
+ type: "error",
1124
+ error: result.error ?? "unknown error"
1125
+ }, now());
1126
+ }
1127
+ await Promise.allSettled(eventWrites);
1128
+ const after = await snapshotPages(pagesDir);
1129
+ const delta = diffPageSnapshots(before, after);
1130
+ if (result.success) {
1131
+ await runIndexer({ repoRoot: options.repoRoot });
1132
+ }
1133
+ const summary = {
1134
+ created: delta.created,
1135
+ updated: delta.updated,
1136
+ archived: delta.archived,
1137
+ costUsd: result.costUsd,
1138
+ turns: result.turns,
1139
+ usage: result.usage
1140
+ };
1141
+ finalRecord = await finishUnlessCancelled({
1142
+ recordPath,
1143
+ fallback: started,
1144
+ status: result.success ? "done" : "failed",
1145
+ finishedAt: now(),
1146
+ providerSessionId: result.providerSessionId,
1147
+ summary,
1148
+ error: result.error,
1149
+ failure: result.failure
1150
+ });
1151
+ } catch (err) {
1152
+ result = {
1153
+ success: false,
1154
+ result: "",
1155
+ error: err instanceof Error ? err.message : String(err)
1156
+ };
1157
+ try {
1158
+ await appendRunEvent(started.logPath, {
1159
+ type: "error",
1160
+ error: result.error ?? "unknown error"
1161
+ }, now());
1162
+ } catch {
1163
+ }
1164
+ await Promise.allSettled(eventWrites);
1165
+ finalRecord = await finishUnlessCancelled({
1166
+ recordPath,
1167
+ fallback: started,
1168
+ status: "failed",
1169
+ finishedAt: now(),
1170
+ error: result.error,
1171
+ failure: result.failure
1172
+ });
1173
+ }
1174
+ if (finalRecord.status === "cancelled" && result.success) {
1175
+ result = {
1176
+ success: false,
1177
+ result: "",
1178
+ error: "run cancelled before final status"
1179
+ };
1180
+ }
1181
+ return { runId, record: finalRecord, result };
1182
+ }
1183
+ async function finishUnlessCancelled(args) {
1184
+ const current = await readRunRecord(args.recordPath);
1185
+ if (current?.status === "cancelled" || isRunCancellationRequested(args.fallback.repoRoot, args.fallback.id)) {
1186
+ return finishCancelled({
1187
+ recordPath: args.recordPath,
1188
+ fallback: current ?? args.fallback,
1189
+ finishedAt: args.finishedAt
1190
+ });
1191
+ }
1192
+ const base = current ?? args.fallback;
1193
+ const finished = finishRunRecord({
1194
+ record: base,
1195
+ status: args.status,
1196
+ finishedAt: args.finishedAt,
1197
+ providerSessionId: args.providerSessionId,
1198
+ summary: args.summary,
1199
+ error: args.error,
1200
+ failure: args.failure
1201
+ });
1202
+ await writeRunRecord(args.recordPath, finished);
1203
+ return finished;
1204
+ }
1205
+ async function cancelledRecordIfRequested(args) {
1206
+ const current = await readRunRecord(args.recordPath);
1207
+ if (current?.status !== "cancelled" && !isRunCancellationRequested(args.repoRoot, args.runId)) {
1208
+ return null;
1209
+ }
1210
+ return finishCancelled({
1211
+ recordPath: args.recordPath,
1212
+ fallback: current ?? args.fallback,
1213
+ finishedAt: args.finishedAt
1214
+ });
1215
+ }
1216
+ async function finishCancelled(args) {
1217
+ const cancelled = args.fallback.status === "cancelled" ? args.fallback : finishRunRecord({
1218
+ record: args.fallback,
1219
+ status: "cancelled",
1220
+ finishedAt: args.finishedAt
1221
+ });
1222
+ await writeRunRecord(args.recordPath, cancelled);
1223
+ return cancelled;
1224
+ }
1225
+ function eventLogger(path, now, writes, observer) {
1226
+ return (event) => {
1227
+ writes.push(appendRunEvent(path, event, now()));
1228
+ if (observer !== void 0) {
1229
+ writes.push(Promise.resolve(observer(event)));
1230
+ }
1231
+ };
1232
+ }
1233
+
1234
+ // src/process/spec.ts
1235
+ import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
1236
+ import { dirname as dirname3, join as join4 } from "path";
1237
+ function runSpecPath(repoRoot, runId) {
1238
+ return join4(runsDir(repoRoot), `${runId}.spec.json`);
1239
+ }
1240
+ async function writeRunSpec(repoRoot, runId, spec) {
1241
+ const path = runSpecPath(repoRoot, runId);
1242
+ await mkdir3(dirname3(path), { recursive: true });
1243
+ const tmp = `${path}.tmp-${process.pid}`;
1244
+ await writeFile3(tmp, `${JSON.stringify(spec, null, 2)}
1245
+ `, "utf8");
1246
+ await rename2(tmp, path);
1247
+ }
1248
+ async function readRunSpec(repoRoot, runId) {
1249
+ const parsed = JSON.parse(
1250
+ await readFile3(runSpecPath(repoRoot, runId), "utf8")
1251
+ );
1252
+ if (!isAgentRunSpec(parsed)) {
1253
+ throw new Error(`invalid run spec for ${runId}`);
1254
+ }
1255
+ return parsed;
1256
+ }
1257
+ function isAgentRunSpec(value) {
1258
+ if (value === null || typeof value !== "object") return false;
1259
+ const spec = value;
1260
+ return spec.provider !== void 0 && typeof spec.provider === "object" && spec.provider !== null && isProviderId(spec.provider.id) && typeof spec.cwd === "string" && typeof spec.prompt === "string" && (spec.metadata === void 0 || typeof spec.metadata === "object" && spec.metadata !== null && (spec.metadata.operation === void 0 || isOperationKind(spec.metadata.operation)));
1261
+ }
1262
+ function isProviderId(value) {
1263
+ return value === "claude" || value === "codex" || value === "cursor";
1264
+ }
1265
+ function isOperationKind(value) {
1266
+ return value === "build" || value === "absorb" || value === "garden";
1267
+ }
1268
+
1269
+ // src/process/background.ts
1270
+ async function startBackgroundProcess(options) {
1271
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
1272
+ const runId = options.runId ?? createRunId(now());
1273
+ await writeRunSpec(options.repoRoot, runId, options.spec);
1274
+ const recordPath = runRecordPath(options.repoRoot, runId);
1275
+ const queued = buildQueuedRunRecord({
1276
+ runId,
1277
+ repoRoot: options.repoRoot,
1278
+ spec: options.spec,
1279
+ queuedAt: now()
1280
+ });
1281
+ await writeRunRecord(recordPath, queued);
1282
+ await initializeRunLog(queued.logPath);
1283
+ const entrypoint = options.entrypoint ?? process.argv[1];
1284
+ if (entrypoint === void 0 || entrypoint.length === 0) {
1285
+ const error = "cannot start background process without an entrypoint";
1286
+ await writeRunRecord(
1287
+ recordPath,
1288
+ finishRunRecord({
1289
+ record: queued,
1290
+ status: "failed",
1291
+ finishedAt: now(),
1292
+ error
1293
+ })
1294
+ );
1295
+ throw new Error(error);
1296
+ }
1297
+ const spawnFn = options.spawnBackground ?? defaultSpawnBackground;
1298
+ let child;
1299
+ try {
1300
+ child = spawnFn({
1301
+ command: process.execPath,
1302
+ args: [entrypoint, "__run-job", runId],
1303
+ cwd: options.repoRoot,
1304
+ env: {
1305
+ ...process.env,
1306
+ CODEALMANAC_INTERNAL_SESSION: "1"
1307
+ }
1308
+ });
1309
+ } catch (err) {
1310
+ await writeRunRecord(
1311
+ recordPath,
1312
+ finishRunRecord({
1313
+ record: queued,
1314
+ status: "failed",
1315
+ finishedAt: now(),
1316
+ error: err instanceof Error ? err.message : String(err)
1317
+ })
1318
+ );
1319
+ throw err;
1320
+ }
1321
+ child.unref?.();
1322
+ const childPid = child.pid ?? 0;
1323
+ return { runId, record: queued, childPid };
1324
+ }
1325
+ async function runBackgroundChild(options) {
1326
+ const existing = await readRunRecord(runRecordPath(options.repoRoot, options.runId));
1327
+ if (existing?.status === "cancelled") {
1328
+ return {
1329
+ runId: options.runId,
1330
+ record: existing,
1331
+ result: {
1332
+ success: false,
1333
+ result: "",
1334
+ error: "run cancelled before start"
1335
+ }
1336
+ };
1337
+ }
1338
+ const spec = await readRunSpec(options.repoRoot, options.runId);
1339
+ return startForegroundProcess({
1340
+ repoRoot: options.repoRoot,
1341
+ spec,
1342
+ runId: options.runId,
1343
+ now: options.now,
1344
+ pid: options.pid,
1345
+ onEvent: options.onEvent,
1346
+ harnessRun: options.harnessRun
1347
+ });
1348
+ }
1349
+ function defaultSpawnBackground(args) {
1350
+ return spawn2(args.command, args.args, {
1351
+ cwd: args.cwd,
1352
+ env: args.env,
1353
+ detached: true,
1354
+ stdio: "ignore"
1355
+ });
1356
+ }
1357
+
1358
+ export {
1359
+ createRunId,
1360
+ initializeRunLog,
1361
+ appendRunEvent,
1362
+ runsDir,
1363
+ runRecordPath,
1364
+ runLogPath,
1365
+ runCancelPath,
1366
+ markRunCancelled,
1367
+ isRunCancellationRequested,
1368
+ writeRunRecord,
1369
+ buildStartedRunRecord,
1370
+ buildQueuedRunRecord,
1371
+ finishRunRecord,
1372
+ readRunRecord,
1373
+ listRunRecords,
1374
+ toRunView,
1375
+ isRunRecord,
1376
+ snapshotPages,
1377
+ diffPageSnapshots,
1378
+ isNoopPageDelta,
1379
+ startForegroundProcess,
1380
+ runSpecPath,
1381
+ writeRunSpec,
1382
+ readRunSpec,
1383
+ startBackgroundProcess,
1384
+ runBackgroundChild
1385
+ };
1386
+ //# sourceMappingURL=chunk-WL4UE7Q6.js.map