ralphy-spec 0.1.1 → 0.3.0

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 (257) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.ja.md +94 -127
  3. package/README.ko.md +94 -127
  4. package/README.md +94 -129
  5. package/README.zh.md +94 -127
  6. package/bin/ralphy-spec.js +0 -0
  7. package/dist/cli/budget.d.ts +3 -0
  8. package/dist/cli/budget.d.ts.map +1 -0
  9. package/dist/cli/budget.js +77 -0
  10. package/dist/cli/budget.js.map +1 -0
  11. package/dist/cli/checkpoint.d.ts +3 -0
  12. package/dist/cli/checkpoint.d.ts.map +1 -0
  13. package/dist/cli/checkpoint.js +23 -0
  14. package/dist/cli/checkpoint.js.map +1 -0
  15. package/dist/cli/init.d.ts +3 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +72 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/cli/report.d.ts +3 -0
  20. package/dist/cli/report.d.ts.map +1 -0
  21. package/dist/cli/report.js +53 -0
  22. package/dist/cli/report.js.map +1 -0
  23. package/dist/cli/run.d.ts +3 -0
  24. package/dist/cli/run.d.ts.map +1 -0
  25. package/dist/cli/run.js +112 -0
  26. package/dist/cli/run.js.map +1 -0
  27. package/dist/cli/status.d.ts +3 -0
  28. package/dist/cli/status.d.ts.map +1 -0
  29. package/dist/cli/status.js +76 -0
  30. package/dist/cli/status.js.map +1 -0
  31. package/dist/cli/tail.d.ts +3 -0
  32. package/dist/cli/tail.d.ts.map +1 -0
  33. package/dist/cli/tail.js +46 -0
  34. package/dist/cli/tail.js.map +1 -0
  35. package/dist/cli/update.d.ts +3 -0
  36. package/dist/cli/update.d.ts.map +1 -0
  37. package/dist/cli/update.js +62 -0
  38. package/dist/cli/update.js.map +1 -0
  39. package/dist/cli/validate.d.ts +3 -0
  40. package/dist/cli/validate.d.ts.map +1 -0
  41. package/dist/cli/validate.js +83 -0
  42. package/dist/cli/validate.js.map +1 -0
  43. package/dist/core/artifacts/budget-writer.d.ts +11 -0
  44. package/dist/core/artifacts/budget-writer.d.ts.map +1 -0
  45. package/dist/core/artifacts/budget-writer.js +28 -0
  46. package/dist/core/artifacts/budget-writer.js.map +1 -0
  47. package/dist/core/artifacts/run-log-writer.d.ts +20 -0
  48. package/dist/core/artifacts/run-log-writer.d.ts.map +1 -0
  49. package/dist/core/artifacts/run-log-writer.js +40 -0
  50. package/dist/core/artifacts/run-log-writer.js.map +1 -0
  51. package/dist/core/artifacts/run-log-writer.test.d.ts +2 -0
  52. package/dist/core/artifacts/run-log-writer.test.d.ts.map +1 -0
  53. package/dist/core/artifacts/run-log-writer.test.js +37 -0
  54. package/dist/core/artifacts/run-log-writer.test.js.map +1 -0
  55. package/dist/core/artifacts/status-writer.d.ts +16 -0
  56. package/dist/core/artifacts/status-writer.d.ts.map +1 -0
  57. package/dist/core/artifacts/status-writer.js +52 -0
  58. package/dist/core/artifacts/status-writer.js.map +1 -0
  59. package/dist/core/artifacts/status-writer.test.d.ts +2 -0
  60. package/dist/core/artifacts/status-writer.test.d.ts.map +1 -0
  61. package/dist/core/artifacts/status-writer.test.js +47 -0
  62. package/dist/core/artifacts/status-writer.test.js.map +1 -0
  63. package/dist/core/artifacts/task-artifacts.d.ts +19 -0
  64. package/dist/core/artifacts/task-artifacts.d.ts.map +1 -0
  65. package/dist/core/artifacts/task-artifacts.js +35 -0
  66. package/dist/core/artifacts/task-artifacts.js.map +1 -0
  67. package/dist/core/artifacts/tasks-writer.d.ts +19 -0
  68. package/dist/core/artifacts/tasks-writer.d.ts.map +1 -0
  69. package/dist/core/artifacts/tasks-writer.js +67 -0
  70. package/dist/core/artifacts/tasks-writer.js.map +1 -0
  71. package/dist/core/artifacts/tasks-writer.test.d.ts +2 -0
  72. package/dist/core/artifacts/tasks-writer.test.d.ts.map +1 -0
  73. package/dist/core/artifacts/tasks-writer.test.js +28 -0
  74. package/dist/core/artifacts/tasks-writer.test.js.map +1 -0
  75. package/dist/core/backends/claude-code.d.ts +17 -0
  76. package/dist/core/backends/claude-code.d.ts.map +1 -0
  77. package/dist/core/backends/claude-code.js +75 -0
  78. package/dist/core/backends/claude-code.js.map +1 -0
  79. package/dist/core/backends/cursor.d.ts +17 -0
  80. package/dist/core/backends/cursor.d.ts.map +1 -0
  81. package/dist/core/backends/cursor.js +75 -0
  82. package/dist/core/backends/cursor.js.map +1 -0
  83. package/dist/core/backends/noop.d.ts +10 -0
  84. package/dist/core/backends/noop.d.ts.map +1 -0
  85. package/dist/core/backends/noop.js +17 -0
  86. package/dist/core/backends/noop.js.map +1 -0
  87. package/dist/core/backends/opencode.d.ts +16 -0
  88. package/dist/core/backends/opencode.d.ts.map +1 -0
  89. package/dist/core/backends/opencode.js +73 -0
  90. package/dist/core/backends/opencode.js.map +1 -0
  91. package/dist/core/backends/types.d.ts +21 -0
  92. package/dist/core/backends/types.d.ts.map +1 -0
  93. package/dist/core/backends/types.js +3 -0
  94. package/dist/core/backends/types.js.map +1 -0
  95. package/dist/core/budgets/errors.d.ts +5 -0
  96. package/dist/core/budgets/errors.d.ts.map +1 -0
  97. package/dist/core/budgets/errors.js +11 -0
  98. package/dist/core/budgets/errors.js.map +1 -0
  99. package/dist/core/budgets/manager.d.ts +21 -0
  100. package/dist/core/budgets/manager.d.ts.map +1 -0
  101. package/dist/core/budgets/manager.js +48 -0
  102. package/dist/core/budgets/manager.js.map +1 -0
  103. package/dist/core/budgets/state.d.ts +25 -0
  104. package/dist/core/budgets/state.d.ts.map +1 -0
  105. package/dist/core/budgets/state.js +33 -0
  106. package/dist/core/budgets/state.js.map +1 -0
  107. package/dist/core/budgets/tiers.d.ts +32 -0
  108. package/dist/core/budgets/tiers.d.ts.map +1 -0
  109. package/dist/core/budgets/tiers.js +67 -0
  110. package/dist/core/budgets/tiers.js.map +1 -0
  111. package/dist/core/engine/constraints.d.ts +16 -0
  112. package/dist/core/engine/constraints.d.ts.map +1 -0
  113. package/dist/core/engine/constraints.js +21 -0
  114. package/dist/core/engine/constraints.js.map +1 -0
  115. package/dist/core/engine/constraints.policy.test.d.ts +2 -0
  116. package/dist/core/engine/constraints.policy.test.d.ts.map +1 -0
  117. package/dist/core/engine/constraints.policy.test.js +85 -0
  118. package/dist/core/engine/constraints.policy.test.js.map +1 -0
  119. package/dist/core/engine/context-pack.d.ts +12 -0
  120. package/dist/core/engine/context-pack.d.ts.map +1 -0
  121. package/dist/core/engine/context-pack.js +45 -0
  122. package/dist/core/engine/context-pack.js.map +1 -0
  123. package/dist/core/engine/loop.d.ts +28 -0
  124. package/dist/core/engine/loop.d.ts.map +1 -0
  125. package/dist/core/engine/loop.hardcap.test.d.ts +2 -0
  126. package/dist/core/engine/loop.hardcap.test.d.ts.map +1 -0
  127. package/dist/core/engine/loop.hardcap.test.js +77 -0
  128. package/dist/core/engine/loop.hardcap.test.js.map +1 -0
  129. package/dist/core/engine/loop.js +864 -0
  130. package/dist/core/engine/loop.js.map +1 -0
  131. package/dist/core/engine/phases.d.ts +2 -0
  132. package/dist/core/engine/phases.d.ts.map +1 -0
  133. package/dist/core/engine/phases.js +3 -0
  134. package/dist/core/engine/phases.js.map +1 -0
  135. package/dist/core/engine/repair.d.ts +6 -0
  136. package/dist/core/engine/repair.d.ts.map +1 -0
  137. package/dist/core/engine/repair.js +23 -0
  138. package/dist/core/engine/repair.js.map +1 -0
  139. package/dist/core/folders.d.ts +26 -0
  140. package/dist/core/folders.d.ts.map +1 -0
  141. package/dist/core/folders.js +58 -0
  142. package/dist/core/folders.js.map +1 -0
  143. package/dist/core/memory/ledger.d.ts +13 -0
  144. package/dist/core/memory/ledger.d.ts.map +1 -0
  145. package/dist/core/memory/ledger.js +24 -0
  146. package/dist/core/memory/ledger.js.map +1 -0
  147. package/dist/core/memory/persistence.d.ts +54 -0
  148. package/dist/core/memory/persistence.d.ts.map +1 -0
  149. package/dist/core/memory/persistence.js +180 -0
  150. package/dist/core/memory/persistence.js.map +1 -0
  151. package/dist/core/reporting/failure-summary.d.ts +23 -0
  152. package/dist/core/reporting/failure-summary.d.ts.map +1 -0
  153. package/dist/core/reporting/failure-summary.js +63 -0
  154. package/dist/core/reporting/failure-summary.js.map +1 -0
  155. package/dist/core/reporting/failure-summary.test.d.ts +2 -0
  156. package/dist/core/reporting/failure-summary.test.d.ts.map +1 -0
  157. package/dist/core/reporting/failure-summary.test.js +22 -0
  158. package/dist/core/reporting/failure-summary.test.js.map +1 -0
  159. package/dist/core/reporting/spend.d.ts +40 -0
  160. package/dist/core/reporting/spend.d.ts.map +1 -0
  161. package/dist/core/reporting/spend.js +157 -0
  162. package/dist/core/reporting/spend.js.map +1 -0
  163. package/dist/core/spec/dag.d.ts +7 -0
  164. package/dist/core/spec/dag.d.ts.map +1 -0
  165. package/dist/core/spec/dag.js +65 -0
  166. package/dist/core/spec/dag.js.map +1 -0
  167. package/dist/core/spec/file-contract.d.ts +13 -0
  168. package/dist/core/spec/file-contract.d.ts.map +1 -0
  169. package/dist/core/spec/file-contract.js +29 -0
  170. package/dist/core/spec/file-contract.js.map +1 -0
  171. package/dist/core/spec/loader.d.ts +8 -0
  172. package/dist/core/spec/loader.d.ts.map +1 -0
  173. package/dist/core/spec/loader.js +62 -0
  174. package/dist/core/spec/loader.js.map +1 -0
  175. package/dist/core/spec/schemas.d.ts +1135 -0
  176. package/dist/core/spec/schemas.d.ts.map +1 -0
  177. package/dist/core/spec/schemas.js +235 -0
  178. package/dist/core/spec/schemas.js.map +1 -0
  179. package/dist/core/spec/sprint-defaults.d.ts +16 -0
  180. package/dist/core/spec/sprint-defaults.d.ts.map +1 -0
  181. package/dist/core/spec/sprint-defaults.js +55 -0
  182. package/dist/core/spec/sprint-defaults.js.map +1 -0
  183. package/dist/core/spec/sprint-defaults.test.d.ts +2 -0
  184. package/dist/core/spec/sprint-defaults.test.d.ts.map +1 -0
  185. package/dist/core/spec/sprint-defaults.test.js +51 -0
  186. package/dist/core/spec/sprint-defaults.test.js.map +1 -0
  187. package/dist/core/spec/types.d.ts +82 -0
  188. package/dist/core/spec/types.d.ts.map +1 -0
  189. package/dist/core/spec/types.js +3 -0
  190. package/dist/core/spec/types.js.map +1 -0
  191. package/dist/core/validators/parsers/eslint.d.ts +3 -0
  192. package/dist/core/validators/parsers/eslint.d.ts.map +1 -0
  193. package/dist/core/validators/parsers/eslint.js +35 -0
  194. package/dist/core/validators/parsers/eslint.js.map +1 -0
  195. package/dist/core/validators/parsers/jest.d.ts +3 -0
  196. package/dist/core/validators/parsers/jest.d.ts.map +1 -0
  197. package/dist/core/validators/parsers/jest.js +16 -0
  198. package/dist/core/validators/parsers/jest.js.map +1 -0
  199. package/dist/core/validators/parsers/tsc.d.ts +3 -0
  200. package/dist/core/validators/parsers/tsc.d.ts.map +1 -0
  201. package/dist/core/validators/parsers/tsc.js +32 -0
  202. package/dist/core/validators/parsers/tsc.js.map +1 -0
  203. package/dist/core/validators/runner.d.ts +8 -0
  204. package/dist/core/validators/runner.d.ts.map +1 -0
  205. package/dist/core/validators/runner.js +85 -0
  206. package/dist/core/validators/runner.js.map +1 -0
  207. package/dist/core/validators/signatures.d.ts +3 -0
  208. package/dist/core/validators/signatures.d.ts.map +1 -0
  209. package/dist/core/validators/signatures.js +10 -0
  210. package/dist/core/validators/signatures.js.map +1 -0
  211. package/dist/core/validators/types.d.ts +27 -0
  212. package/dist/core/validators/types.d.ts.map +1 -0
  213. package/dist/core/validators/types.js +3 -0
  214. package/dist/core/validators/types.js.map +1 -0
  215. package/dist/core/workspace/contract-enforcer.d.ts +54 -0
  216. package/dist/core/workspace/contract-enforcer.d.ts.map +1 -0
  217. package/dist/core/workspace/contract-enforcer.js +128 -0
  218. package/dist/core/workspace/contract-enforcer.js.map +1 -0
  219. package/dist/core/workspace/manager.d.ts +28 -0
  220. package/dist/core/workspace/manager.d.ts.map +1 -0
  221. package/dist/core/workspace/manager.js +3 -0
  222. package/dist/core/workspace/manager.js.map +1 -0
  223. package/dist/core/workspace/merge.d.ts +38 -0
  224. package/dist/core/workspace/merge.d.ts.map +1 -0
  225. package/dist/core/workspace/merge.js +92 -0
  226. package/dist/core/workspace/merge.js.map +1 -0
  227. package/dist/core/workspace/patch-mode.d.ts +22 -0
  228. package/dist/core/workspace/patch-mode.d.ts.map +1 -0
  229. package/dist/core/workspace/patch-mode.js +91 -0
  230. package/dist/core/workspace/patch-mode.js.map +1 -0
  231. package/dist/core/workspace/scope-detector.d.ts +13 -0
  232. package/dist/core/workspace/scope-detector.d.ts.map +1 -0
  233. package/dist/core/workspace/scope-detector.js +34 -0
  234. package/dist/core/workspace/scope-detector.js.map +1 -0
  235. package/dist/core/workspace/worktree-mode.d.ts +28 -0
  236. package/dist/core/workspace/worktree-mode.d.ts.map +1 -0
  237. package/dist/core/workspace/worktree-mode.js +157 -0
  238. package/dist/core/workspace/worktree-mode.js.map +1 -0
  239. package/dist/index.js +16 -4
  240. package/dist/index.js.map +1 -1
  241. package/dist/templates/claude-code/ralphy-archive.md +1 -1
  242. package/dist/templates/claude-code/ralphy-implement.md +1 -1
  243. package/dist/templates/claude-code/ralphy-plan.md +1 -1
  244. package/dist/templates/claude-code/ralphy-validate.md +1 -1
  245. package/dist/templates/cursor/ralphy-archive.md +1 -1
  246. package/dist/templates/cursor/ralphy-implement.md +1 -1
  247. package/dist/templates/cursor/ralphy-plan.md +1 -1
  248. package/dist/templates/cursor/ralphy-validate.md +1 -1
  249. package/dist/templates/shared/openspec-tasks-template.md +23 -3
  250. package/dist/templates/shared/project-template.yml +232 -0
  251. package/dist/utils/installer.d.ts.map +1 -1
  252. package/dist/utils/installer.js +31 -1
  253. package/dist/utils/installer.js.map +1 -1
  254. package/dist/utils/validator.d.ts.map +1 -1
  255. package/dist/utils/validator.js +10 -0
  256. package/dist/utils/validator.js.map +1 -1
  257. package/package.json +15 -5
@@ -0,0 +1,864 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EngineLoop = void 0;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const dag_1 = require("../spec/dag");
9
+ const persistence_1 = require("../memory/persistence");
10
+ const ledger_1 = require("../memory/ledger");
11
+ const runner_1 = require("../validators/runner");
12
+ const signatures_1 = require("../validators/signatures");
13
+ const manager_1 = require("../budgets/manager");
14
+ const state_1 = require("../budgets/state");
15
+ const errors_1 = require("../budgets/errors");
16
+ const context_pack_1 = require("./context-pack");
17
+ const repair_1 = require("./repair");
18
+ const failure_summary_1 = require("../reporting/failure-summary");
19
+ const status_writer_1 = require("../artifacts/status-writer");
20
+ const tasks_writer_1 = require("../artifacts/tasks-writer");
21
+ const budget_writer_1 = require("../artifacts/budget-writer");
22
+ const task_artifacts_1 = require("../artifacts/task-artifacts");
23
+ const run_log_writer_1 = require("../artifacts/run-log-writer");
24
+ const constraints_1 = require("./constraints");
25
+ const scope_detector_1 = require("../workspace/scope-detector");
26
+ const sprint_defaults_1 = require("../spec/sprint-defaults");
27
+ function createRunId() {
28
+ return `run_${new Date().toISOString().replace(/[:.]/g, "-")}_${node_crypto_1.default
29
+ .randomBytes(4)
30
+ .toString("hex")}`;
31
+ }
32
+ class EngineLoop {
33
+ async run(opts) {
34
+ const runId = createRunId();
35
+ const persistence = await persistence_1.PersistenceLayer.openForRepo(opts.repoRoot);
36
+ const ledger = new ledger_1.LedgerLogger(persistence, runId);
37
+ let artifactsEnabled = Boolean(opts.spec.artifacts?.enabled);
38
+ const artifactsRootDir = opts.spec.artifacts?.rootDir;
39
+ const artifactsStatusIcons = opts.spec.artifacts?.statusIcons ?? "emoji";
40
+ const runBudget = opts.spec.budgets?.run;
41
+ const budgetState = new state_1.BudgetState({
42
+ usd: runBudget?.moneyUsd,
43
+ tokens: runBudget?.tokens,
44
+ wallTimeMs: runBudget?.wallTimeMinutes !== undefined
45
+ ? runBudget.wallTimeMinutes * 60_000
46
+ : undefined,
47
+ maxIterations: runBudget?.maxIterationsTotal,
48
+ });
49
+ const runBudgetManager = new manager_1.BudgetManager(budgetState);
50
+ persistence.createRun({
51
+ runId,
52
+ repoRoot: opts.repoRoot,
53
+ backendId: opts.backend.id,
54
+ workspaceMode: opts.workspace.mode,
55
+ });
56
+ ledger.event({ kind: "run_started", message: "Run started", data: { runId } });
57
+ const safeArtifact = async (fn) => {
58
+ if (!artifactsEnabled)
59
+ return;
60
+ try {
61
+ await fn();
62
+ }
63
+ catch (e) {
64
+ artifactsEnabled = false;
65
+ ledger.event({
66
+ kind: "artifact_error",
67
+ message: `Artifact write failed; disabling artifacts for this run: ${e?.message ? String(e.message) : String(e)}`,
68
+ });
69
+ }
70
+ };
71
+ await safeArtifact(async () => {
72
+ await (0, status_writer_1.writeStatus)({
73
+ repoRoot: opts.repoRoot,
74
+ rootDir: artifactsRootDir,
75
+ runId,
76
+ backendId: opts.backend.id,
77
+ workspaceMode: opts.workspace.mode,
78
+ phase: "RUN",
79
+ message: "Run started",
80
+ });
81
+ });
82
+ const finalizeArtifacts = async (outcome) => {
83
+ if (!artifactsEnabled)
84
+ return;
85
+ const ledgerEvents = persistence.listLedger({ runId, limit: 5000 });
86
+ await safeArtifact(async () => {
87
+ await (0, tasks_writer_1.writeTasksBoard)({
88
+ repoRoot: opts.repoRoot,
89
+ rootDir: artifactsRootDir,
90
+ runId,
91
+ statusIcons: artifactsStatusIcons,
92
+ specTasks: opts.spec.tasks ?? [],
93
+ rows: persistence.listTasksForRun({ runId }),
94
+ });
95
+ });
96
+ await safeArtifact(async () => {
97
+ await (0, budget_writer_1.writeBudgetReport)({
98
+ repoRoot: opts.repoRoot,
99
+ rootDir: artifactsRootDir,
100
+ runId,
101
+ ledgerEvents,
102
+ });
103
+ });
104
+ await safeArtifact(async () => {
105
+ await (0, run_log_writer_1.writeRunLogOnce)({
106
+ repoRoot: opts.repoRoot,
107
+ rootDir: artifactsRootDir,
108
+ runId,
109
+ outcome: outcome.ok
110
+ ? { ok: true }
111
+ : { ok: false, exitCode: outcome.exitCode, reason: outcome.reason },
112
+ ledgerEvents: ledgerEvents.map((e) => ({
113
+ ts: e.ts,
114
+ kind: e.kind,
115
+ message: e.message,
116
+ taskId: e.taskId,
117
+ })),
118
+ });
119
+ });
120
+ };
121
+ try {
122
+ const tasks = opts.spec.tasks ?? [];
123
+ const dag = (0, dag_1.buildTaskDAG)(tasks);
124
+ const planOrder = opts.taskId ? [opts.taskId] : dag.order;
125
+ if (opts.dryRun) {
126
+ ledger.event({
127
+ kind: "dry_run",
128
+ message: "Dry run plan generated",
129
+ data: { tasks: planOrder },
130
+ });
131
+ persistence.finishRun({ runId, status: "success" });
132
+ await finalizeArtifacts({ ok: true, runId });
133
+ return { ok: true, runId };
134
+ }
135
+ for (const taskId of planOrder) {
136
+ const task = dag.tasksById.get(taskId) ?? tasks.find((t) => t.id === taskId);
137
+ if (!task) {
138
+ persistence.finishRun({ runId, status: "error" });
139
+ const outcome = {
140
+ ok: false,
141
+ runId,
142
+ exitCode: 4,
143
+ reason: `Unknown task id: ${taskId}`,
144
+ };
145
+ await finalizeArtifacts(outcome);
146
+ return outcome;
147
+ }
148
+ const outcome = await this.runOneTask({
149
+ runId,
150
+ task,
151
+ repoRoot: opts.repoRoot,
152
+ spec: opts.spec,
153
+ backend: opts.backend,
154
+ workspace: opts.workspace,
155
+ persistence,
156
+ ledger,
157
+ runBudgetManager,
158
+ artifacts: artifactsEnabled ? { rootDir: artifactsRootDir } : null,
159
+ });
160
+ if (!outcome.ok) {
161
+ persistence.finishRun({
162
+ runId,
163
+ status: outcome.exitCode === 2 || outcome.exitCode === 3 ? "stopped" : "error",
164
+ });
165
+ await finalizeArtifacts(outcome);
166
+ return outcome;
167
+ }
168
+ }
169
+ ledger.event({ kind: "run_done", message: "All tasks done" });
170
+ persistence.finishRun({ runId, status: "success" });
171
+ const okOutcome = { ok: true, runId };
172
+ await finalizeArtifacts(okOutcome);
173
+ return okOutcome;
174
+ }
175
+ catch (err) {
176
+ ledger.event({
177
+ kind: "run_error",
178
+ message: err?.message ? String(err.message) : String(err),
179
+ });
180
+ persistence.finishRun({ runId, status: "error" });
181
+ const outcome = {
182
+ ok: false,
183
+ runId,
184
+ exitCode: 4,
185
+ reason: err?.message ? String(err.message) : String(err),
186
+ };
187
+ await finalizeArtifacts(outcome);
188
+ return outcome;
189
+ }
190
+ finally {
191
+ persistence.close();
192
+ }
193
+ }
194
+ async runOneTask(args) {
195
+ const { task, runId, persistence, ledger, runBudgetManager } = args;
196
+ let artifacts = args.artifacts;
197
+ let phase = "PLAN";
198
+ persistence.upsertTaskState({ runId, taskId: task.id, status: "running", phase });
199
+ ledger.event({ taskId: task.id, kind: "task_started", message: "Task started" });
200
+ const refreshArtifacts = async () => {
201
+ if (!artifacts)
202
+ return;
203
+ const ledgerEvents = persistence.listLedger({ runId, limit: 5000 });
204
+ try {
205
+ await (0, tasks_writer_1.writeTasksBoard)({
206
+ repoRoot: args.repoRoot,
207
+ rootDir: artifacts.rootDir,
208
+ runId,
209
+ statusIcons: args.spec.artifacts?.statusIcons ?? "emoji",
210
+ specTasks: args.spec.tasks ?? [],
211
+ rows: persistence.listTasksForRun({ runId }),
212
+ });
213
+ await (0, budget_writer_1.writeBudgetReport)({
214
+ repoRoot: args.repoRoot,
215
+ rootDir: artifacts.rootDir,
216
+ runId,
217
+ ledgerEvents,
218
+ });
219
+ }
220
+ catch (e) {
221
+ artifacts = null;
222
+ ledger.event({
223
+ taskId: task.id,
224
+ kind: "artifact_error",
225
+ message: `Artifact write failed; disabling artifacts for this task: ${e?.message ? String(e.message) : String(e)}`,
226
+ });
227
+ }
228
+ };
229
+ await refreshArtifacts();
230
+ if (artifacts) {
231
+ try {
232
+ await (0, status_writer_1.writeStatus)({
233
+ repoRoot: args.repoRoot,
234
+ rootDir: artifacts.rootDir,
235
+ runId,
236
+ backendId: args.backend.id,
237
+ workspaceMode: args.workspace.mode,
238
+ taskId: task.id,
239
+ phase,
240
+ iteration: 0,
241
+ message: "Task started",
242
+ });
243
+ }
244
+ catch (e) {
245
+ artifacts = null;
246
+ ledger.event({
247
+ taskId: task.id,
248
+ kind: "artifact_error",
249
+ message: `STATUS.md write failed; disabling artifacts: ${e?.message ? String(e.message) : String(e)}`,
250
+ });
251
+ }
252
+ }
253
+ const maxIter = task.budget?.hard?.maxIterations ??
254
+ args.spec.budgets?.run?.maxIterationsTotal ??
255
+ 12;
256
+ const lastSignatures = [];
257
+ await args.workspace.prepare(task.id);
258
+ const cwd = args.workspace.getWorkingDir(task.id);
259
+ const taskBudgetConfig = this.toTaskBudgetConfig(task) ?? null;
260
+ const taskBudgetState = new state_1.BudgetState({
261
+ usd: taskBudgetConfig?.hard.usd,
262
+ tokens: taskBudgetConfig?.hard.tokens,
263
+ wallTimeMs: taskBudgetConfig?.hard.timeMinutes !== undefined
264
+ ? taskBudgetConfig.hard.timeMinutes * 60_000
265
+ : undefined,
266
+ maxIterations: taskBudgetConfig?.hard.maxIterations ?? maxIter,
267
+ });
268
+ const taskBudgetManager = new manager_1.BudgetManager(taskBudgetState);
269
+ const checkpointEvery = task.sprint?.intent ? sprint_defaults_1.SPRINT_INTENT_CONSTRAINTS[task.sprint.intent]?.checkpointEveryIterations : undefined;
270
+ let pendingRepairNotes;
271
+ let lastValidatorResults = null;
272
+ let lastIssues = null;
273
+ const blockHardCap = async (iter, reason) => {
274
+ const status = taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null;
275
+ ledger.event({
276
+ taskId: task.id,
277
+ kind: "hard_cap",
278
+ message: `HARD cap reached: blocking task (preserving workspace)`,
279
+ data: status ?? undefined,
280
+ });
281
+ persistence.upsertTaskState({
282
+ runId,
283
+ taskId: task.id,
284
+ status: "blocked",
285
+ phase: "DIAGNOSE",
286
+ iteration: iter,
287
+ lastError: reason,
288
+ });
289
+ if (artifacts) {
290
+ try {
291
+ await (0, status_writer_1.writeStatus)({
292
+ repoRoot: args.repoRoot,
293
+ rootDir: artifacts.rootDir,
294
+ runId,
295
+ backendId: args.backend.id,
296
+ workspaceMode: args.workspace.mode,
297
+ taskId: task.id,
298
+ phase: "DIAGNOSE",
299
+ iteration: iter,
300
+ tier: "hard",
301
+ budgetStatus: status,
302
+ message: `Blocked: ${reason} (workspace preserved)`,
303
+ });
304
+ }
305
+ catch {
306
+ artifacts = null;
307
+ }
308
+ }
309
+ ledger.event({
310
+ taskId: task.id,
311
+ kind: "failure_summary",
312
+ message: "Failure summary (hard cap)",
313
+ data: {
314
+ markdown: (0, failure_summary_1.buildFailureSummary)({
315
+ runId,
316
+ taskId: task.id,
317
+ reason,
318
+ tier: "hard",
319
+ budgetStatus: status,
320
+ lastIssues: lastIssues?.map((i) => ({
321
+ level: i.level,
322
+ kind: i.kind,
323
+ message: i.message,
324
+ file: i.file,
325
+ })),
326
+ ledgerEvents: persistence.listLedger({ runId, limit: 200 }),
327
+ }),
328
+ },
329
+ });
330
+ await refreshArtifacts();
331
+ return { ok: false, runId, exitCode: 2, reason: "Hard cap" };
332
+ };
333
+ for (let iter = 1; iter <= maxIter; iter++) {
334
+ const iterStarted = Date.now();
335
+ try {
336
+ // Run-level hard limits
337
+ runBudgetManager.preflightOrThrow({ estimatedUsd: 0, estimatedTokens: 0 });
338
+ // Task-level hard limits (best-effort)
339
+ taskBudgetManager.preflightOrThrow({ estimatedUsd: 0, estimatedTokens: 0 });
340
+ // Hard-cap: do not attempt another backend call once at cap.
341
+ if (taskBudgetConfig && taskBudgetManager.isAtHardCap(taskBudgetConfig)) {
342
+ // Spec requires that "starting another iteration" throws BudgetExhaustedError.
343
+ throw new errors_1.BudgetExhaustedError("Hard cap reached");
344
+ }
345
+ }
346
+ catch (e) {
347
+ if (e instanceof errors_1.BudgetExhaustedError) {
348
+ return await blockHardCap(iter, "Hard cap reached");
349
+ }
350
+ ledger.event({
351
+ taskId: task.id,
352
+ kind: "budget_exceeded",
353
+ message: e?.message ? String(e.message) : "Budget exceeded",
354
+ });
355
+ persistence.upsertTaskState({
356
+ runId,
357
+ taskId: task.id,
358
+ status: "blocked",
359
+ phase: "DIAGNOSE",
360
+ iteration: iter,
361
+ lastError: "Budget exceeded",
362
+ });
363
+ ledger.event({
364
+ taskId: task.id,
365
+ kind: "failure_summary",
366
+ message: "Failure summary (budget exceeded)",
367
+ data: {
368
+ markdown: (0, failure_summary_1.buildFailureSummary)({
369
+ runId,
370
+ taskId: task.id,
371
+ reason: "Budget exceeded",
372
+ tier: taskBudgetConfig ? taskBudgetManager.getTier(taskBudgetConfig) : "hard",
373
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
374
+ lastIssues: lastIssues?.map((i) => ({
375
+ level: i.level,
376
+ kind: i.kind,
377
+ message: i.message,
378
+ file: i.file,
379
+ })),
380
+ ledgerEvents: persistence.listLedger({ runId, limit: 200 }),
381
+ }),
382
+ },
383
+ });
384
+ await refreshArtifacts();
385
+ return { ok: false, runId, exitCode: 2, reason: "Budget limit" };
386
+ }
387
+ const tier = taskBudgetConfig ? taskBudgetManager.getTier(taskBudgetConfig) : "optimal";
388
+ const shouldDegrade = taskBudgetConfig
389
+ ? taskBudgetManager.shouldApplyDegrade(taskBudgetConfig)
390
+ : false;
391
+ if (shouldDegrade) {
392
+ ledger.event({
393
+ taskId: task.id,
394
+ kind: "degrade",
395
+ message: "WARNING tier: degrade behaviors enabled",
396
+ });
397
+ }
398
+ if (shouldDegrade && lastValidatorResults && lastIssues) {
399
+ const ctx = (0, context_pack_1.buildContextPack)({
400
+ tier: "warning",
401
+ taskId: task.id,
402
+ validatorResults: lastValidatorResults,
403
+ issues: lastIssues,
404
+ });
405
+ ledger.event({
406
+ taskId: task.id,
407
+ kind: "context_pack",
408
+ message: `Context pack built (${ctx.size})`,
409
+ });
410
+ // Optional calls disabling (MVP: only logs, as we don't have optional calls yet).
411
+ ledger.event({
412
+ taskId: task.id,
413
+ kind: "optional_calls_skipped",
414
+ message: "WARNING tier: skipping optional calls (self-review / plan regen)",
415
+ });
416
+ }
417
+ phase = "EXEC";
418
+ persistence.upsertTaskState({
419
+ runId,
420
+ taskId: task.id,
421
+ status: "running",
422
+ phase,
423
+ iteration: iter,
424
+ });
425
+ ledger.event({ taskId: task.id, kind: "exec", message: `EXEC iteration ${iter}` });
426
+ if (artifacts) {
427
+ try {
428
+ await (0, status_writer_1.writeStatus)({
429
+ repoRoot: args.repoRoot,
430
+ rootDir: artifacts.rootDir,
431
+ runId,
432
+ backendId: args.backend.id,
433
+ workspaceMode: args.workspace.mode,
434
+ taskId: task.id,
435
+ phase,
436
+ iteration: iter,
437
+ tier,
438
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
439
+ message: `Executing iteration ${iter}`,
440
+ });
441
+ }
442
+ catch {
443
+ artifacts = null;
444
+ }
445
+ }
446
+ if (artifacts) {
447
+ // Write task artifacts before backend call (best-effort).
448
+ const ctx = lastValidatorResults && lastIssues
449
+ ? (0, context_pack_1.buildContextPack)({
450
+ tier: shouldDegrade ? "warning" : "optimal",
451
+ taskId: task.id,
452
+ validatorResults: lastValidatorResults,
453
+ issues: lastIssues,
454
+ })
455
+ : {
456
+ size: "full",
457
+ text: [`# Context`, ``, `Task: ${task.id}`, `Iteration: ${iter}`, ``].join("\n"),
458
+ };
459
+ try {
460
+ await (0, task_artifacts_1.writeTaskContext)({
461
+ repoRoot: args.repoRoot,
462
+ rootDir: artifacts.rootDir,
463
+ taskId: task.id,
464
+ markdown: ctx.text,
465
+ });
466
+ if (pendingRepairNotes) {
467
+ await (0, task_artifacts_1.writeTaskRepair)({
468
+ repoRoot: args.repoRoot,
469
+ rootDir: artifacts.rootDir,
470
+ taskId: task.id,
471
+ markdown: pendingRepairNotes,
472
+ });
473
+ }
474
+ }
475
+ catch {
476
+ artifacts = null;
477
+ }
478
+ }
479
+ const backendRes = await args.backend.implement({ cwd, backendId: args.backend.id }, {
480
+ task,
481
+ iteration: iter,
482
+ repairNotes: pendingRepairNotes ??
483
+ (shouldDegrade
484
+ ? "WARNING tier active (repair-only mode will be enforced on failures)."
485
+ : undefined),
486
+ });
487
+ if (!backendRes.ok) {
488
+ persistence.upsertTaskState({
489
+ runId,
490
+ taskId: task.id,
491
+ status: "error",
492
+ phase: "EXEC",
493
+ iteration: iter,
494
+ lastError: backendRes.message,
495
+ });
496
+ ledger.event({
497
+ taskId: task.id,
498
+ kind: "backend_error",
499
+ message: backendRes.message,
500
+ });
501
+ await refreshArtifacts();
502
+ return { ok: false, runId, exitCode: 5, reason: "Backend invocation error" };
503
+ }
504
+ // Record backend usage if provided (best-effort; some backends may not estimate).
505
+ if (backendRes.estimatedUsd !== undefined || backendRes.estimatedTokens !== undefined) {
506
+ runBudgetManager.recordBackendUsage({
507
+ usd: backendRes.estimatedUsd,
508
+ tokens: backendRes.estimatedTokens,
509
+ });
510
+ taskBudgetManager.recordBackendUsage({
511
+ usd: backendRes.estimatedUsd,
512
+ tokens: backendRes.estimatedTokens,
513
+ });
514
+ ledger.event({
515
+ taskId: task.id,
516
+ kind: "backend_usage",
517
+ message: "Backend usage recorded",
518
+ data: {
519
+ usd: backendRes.estimatedUsd ?? 0,
520
+ tokens: backendRes.estimatedTokens ?? 0,
521
+ backendId: args.backend.id,
522
+ phase: "EXEC",
523
+ },
524
+ });
525
+ }
526
+ phase = "VALIDATE";
527
+ if (artifacts) {
528
+ try {
529
+ await (0, status_writer_1.writeStatus)({
530
+ repoRoot: args.repoRoot,
531
+ rootDir: artifacts.rootDir,
532
+ runId,
533
+ backendId: args.backend.id,
534
+ workspaceMode: args.workspace.mode,
535
+ taskId: task.id,
536
+ phase,
537
+ iteration: iter,
538
+ tier,
539
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
540
+ message: "Running validators",
541
+ });
542
+ }
543
+ catch {
544
+ artifacts = null;
545
+ }
546
+ }
547
+ const validators = this.resolveValidators(args.spec, task).map((v) => ({
548
+ ...v,
549
+ timeoutMs: v.timeoutMs ?? (args.spec.budgets?.limits?.commandTimeoutSeconds ?? 900) * 1000,
550
+ }));
551
+ const runner = new runner_1.ValidatorRunner({
552
+ cwd,
553
+ commandTimeoutMs: (args.spec.budgets?.limits?.commandTimeoutSeconds ?? 900) * 1000,
554
+ });
555
+ const results = await runner.runAll(validators);
556
+ const allIssues = Object.entries(results).flatMap(([id, r]) => {
557
+ // If a validator failed but produced no parsed issues, create a generic one.
558
+ const issues = !r.ok && r.issues.length === 0
559
+ ? [
560
+ {
561
+ kind: "unknown",
562
+ level: "error",
563
+ message: `Validator "${id}" failed (exit=${r.exitCode ?? "?"})`,
564
+ raw: { validatorId: id },
565
+ },
566
+ ]
567
+ : r.issues;
568
+ return issues.map((i) => ({
569
+ ...i,
570
+ raw: (i.raw ?? { validatorId: id }),
571
+ }));
572
+ });
573
+ // Contract enforcement after EXEC
574
+ if (task.filesContract) {
575
+ const violations = await args.workspace.enforceContract(task.id, task.filesContract);
576
+ if (violations.length) {
577
+ allIssues.push(...violations.map((v) => ({
578
+ kind: "contract_violation",
579
+ level: "error",
580
+ message: `File contract violation: ${v.reason} (${v.file})`,
581
+ file: v.file,
582
+ raw: v,
583
+ })));
584
+ }
585
+ }
586
+ // Sprint constraints & scope detection (best-effort heuristics).
587
+ const changedFiles = await args.workspace.getChangedFiles(task.id);
588
+ const scopePolicy = args.spec.policies?.scopeGuard ?? "warn";
589
+ const scopeLevel = scopePolicy === "block" ? "error" : scopePolicy === "warn" ? "warning" : "warning";
590
+ if (scopePolicy !== "off") {
591
+ for (const v of (0, constraints_1.enforceSprintConstraints)({ task, changedFiles })) {
592
+ allIssues.push({
593
+ kind: "scope_violation",
594
+ level: scopeLevel,
595
+ message: v.message,
596
+ file: v.file,
597
+ raw: v.raw,
598
+ });
599
+ }
600
+ for (const v of (0, scope_detector_1.detectScopeViolations)({ task, changedFiles })) {
601
+ allIssues.push({
602
+ kind: "scope_violation",
603
+ level: scopeLevel,
604
+ message: v.message,
605
+ file: v.file,
606
+ raw: v,
607
+ });
608
+ }
609
+ }
610
+ const ok = Object.values(results).every((r) => r.ok) && allIssues.every((i) => i.level !== "error");
611
+ ledger.event({
612
+ taskId: task.id,
613
+ kind: "validate",
614
+ message: ok ? "VALIDATE passed" : "VALIDATE failed",
615
+ data: { ok, issues: allIssues.length },
616
+ });
617
+ const signatures = allIssues.map(signatures_1.issueSignature);
618
+ lastSignatures.push(...signatures);
619
+ while (lastSignatures.length > 50)
620
+ lastSignatures.shift();
621
+ const recent = lastSignatures.slice(-signatures.length);
622
+ const stuck = signatures.length > 0 &&
623
+ recent.length === signatures.length &&
624
+ iter >= 3 &&
625
+ signatures.every((s) => lastSignatures.slice(-signatures.length * 3).includes(s));
626
+ if (ok) {
627
+ phase = "CHECKPOINT";
628
+ await args.workspace.checkpoint(task.id, "Task completed");
629
+ await args.workspace.merge(task.id);
630
+ await args.workspace.cleanup(task.id);
631
+ persistence.upsertTaskState({
632
+ runId,
633
+ taskId: task.id,
634
+ status: "done",
635
+ phase: "DONE",
636
+ iteration: iter,
637
+ });
638
+ ledger.event({ taskId: task.id, kind: "task_done", message: "Task done" });
639
+ if (artifacts) {
640
+ try {
641
+ await (0, status_writer_1.writeStatus)({
642
+ repoRoot: args.repoRoot,
643
+ rootDir: artifacts.rootDir,
644
+ runId,
645
+ backendId: args.backend.id,
646
+ workspaceMode: args.workspace.mode,
647
+ taskId: task.id,
648
+ phase: "DONE",
649
+ iteration: iter,
650
+ tier,
651
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
652
+ message: "Task done",
653
+ });
654
+ }
655
+ catch {
656
+ artifacts = null;
657
+ }
658
+ }
659
+ const wallTimeMs = Date.now() - iterStarted;
660
+ runBudgetManager.recordIteration(wallTimeMs);
661
+ taskBudgetManager.recordIteration(wallTimeMs);
662
+ ledger.event({
663
+ taskId: task.id,
664
+ kind: "iteration_complete",
665
+ message: "Iteration complete",
666
+ data: { wallTimeMs, phase: "DONE" },
667
+ });
668
+ await refreshArtifacts();
669
+ return { ok: true, runId };
670
+ }
671
+ // Save failure context for next iteration.
672
+ lastValidatorResults = results;
673
+ lastIssues = allIssues;
674
+ pendingRepairNotes = undefined;
675
+ phase = "DIAGNOSE";
676
+ persistence.upsertTaskState({
677
+ runId,
678
+ taskId: task.id,
679
+ status: "running",
680
+ phase,
681
+ iteration: iter,
682
+ lastError: "Validation failed",
683
+ });
684
+ if (artifacts) {
685
+ try {
686
+ await (0, status_writer_1.writeStatus)({
687
+ repoRoot: args.repoRoot,
688
+ rootDir: artifacts.rootDir,
689
+ runId,
690
+ backendId: args.backend.id,
691
+ workspaceMode: args.workspace.mode,
692
+ taskId: task.id,
693
+ phase,
694
+ iteration: iter,
695
+ tier,
696
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
697
+ message: "Validation failed",
698
+ });
699
+ }
700
+ catch {
701
+ artifacts = null;
702
+ }
703
+ }
704
+ if (stuck) {
705
+ ledger.event({
706
+ taskId: task.id,
707
+ kind: "stuck",
708
+ message: "Stuck detected (same issues repeated)",
709
+ });
710
+ await refreshArtifacts();
711
+ return { ok: false, runId, exitCode: 3, reason: "Stuck / max iterations" };
712
+ }
713
+ phase = "REPAIR";
714
+ const tierAfter = taskBudgetConfig ? taskBudgetManager.getTier(taskBudgetConfig) : "optimal";
715
+ const repairNotes = (0, repair_1.buildRepairNotes)({
716
+ tier: tierAfter === "warning" ? "warning" : "optimal",
717
+ issues: allIssues,
718
+ });
719
+ ledger.event({ taskId: task.id, kind: "repair", message: "Retrying (repair loop)" });
720
+ if (artifacts) {
721
+ try {
722
+ await (0, status_writer_1.writeStatus)({
723
+ repoRoot: args.repoRoot,
724
+ rootDir: artifacts.rootDir,
725
+ runId,
726
+ backendId: args.backend.id,
727
+ workspaceMode: args.workspace.mode,
728
+ taskId: task.id,
729
+ phase,
730
+ iteration: iter,
731
+ tier: tierAfter === "warning" ? "warning" : "optimal",
732
+ budgetStatus: taskBudgetConfig ? taskBudgetManager.getStatus(taskBudgetConfig) : null,
733
+ message: "Repair loop",
734
+ });
735
+ }
736
+ catch {
737
+ artifacts = null;
738
+ }
739
+ }
740
+ pendingRepairNotes = repairNotes;
741
+ const wallTimeMs = Date.now() - iterStarted;
742
+ runBudgetManager.recordIteration(wallTimeMs);
743
+ taskBudgetManager.recordIteration(wallTimeMs);
744
+ ledger.event({
745
+ taskId: task.id,
746
+ kind: "iteration_complete",
747
+ message: "Iteration complete",
748
+ data: { wallTimeMs, phase },
749
+ });
750
+ // Intent-based checkpointing (best-effort). Only enabled for worktree mode to avoid committing
751
+ // partial changes directly to the main working directory.
752
+ if (checkpointEvery &&
753
+ args.workspace.mode === "worktree" &&
754
+ iter % checkpointEvery === 0) {
755
+ await args.workspace.checkpoint(task.id, `Checkpoint iteration ${iter}`);
756
+ ledger.event({
757
+ taskId: task.id,
758
+ kind: "checkpoint",
759
+ message: `Checkpoint created (every ${checkpointEvery} iterations)`,
760
+ });
761
+ }
762
+ await refreshArtifacts();
763
+ }
764
+ ledger.event({
765
+ taskId: task.id,
766
+ kind: "max_iterations",
767
+ message: `Max iterations reached (${maxIter})`,
768
+ });
769
+ const treatAsHardCap = Boolean(taskBudgetConfig) && Boolean(taskBudgetManager.isAtHardCap(taskBudgetConfig));
770
+ if (treatAsHardCap) {
771
+ const status = taskBudgetManager.getStatus(taskBudgetConfig);
772
+ persistence.upsertTaskState({
773
+ runId,
774
+ taskId: task.id,
775
+ status: "blocked",
776
+ phase: "DIAGNOSE",
777
+ iteration: maxIter,
778
+ lastError: "Hard cap reached",
779
+ });
780
+ ledger.event({
781
+ taskId: task.id,
782
+ kind: "hard_cap",
783
+ message: "HARD cap reached (max iterations): blocking task (preserving workspace)",
784
+ data: status,
785
+ });
786
+ ledger.event({
787
+ taskId: task.id,
788
+ kind: "failure_summary",
789
+ message: "Failure summary (hard cap / max iterations)",
790
+ data: {
791
+ markdown: (0, failure_summary_1.buildFailureSummary)({
792
+ runId,
793
+ taskId: task.id,
794
+ reason: "Hard cap reached (max iterations)",
795
+ tier: "hard",
796
+ budgetStatus: status,
797
+ lastIssues: lastIssues?.map((i) => ({
798
+ level: i.level,
799
+ kind: i.kind,
800
+ message: i.message,
801
+ file: i.file,
802
+ })),
803
+ ledgerEvents: persistence.listLedger({ runId, limit: 200 }),
804
+ }),
805
+ },
806
+ });
807
+ await refreshArtifacts();
808
+ return { ok: false, runId, exitCode: 2, reason: "Hard cap" };
809
+ }
810
+ persistence.upsertTaskState({
811
+ runId,
812
+ taskId: task.id,
813
+ status: "blocked",
814
+ phase: "DIAGNOSE",
815
+ iteration: maxIter,
816
+ lastError: "Max iterations reached",
817
+ });
818
+ await refreshArtifacts();
819
+ return { ok: false, runId, exitCode: 3, reason: "Max iterations reached" };
820
+ }
821
+ toTaskBudgetConfig(task) {
822
+ const b = task.budget;
823
+ const hardIter = b?.hard?.maxIterations;
824
+ if (!b || hardIter === undefined)
825
+ return null;
826
+ return {
827
+ optimal: {
828
+ usd: b.optimal?.usd,
829
+ tokens: b.optimal?.tokens,
830
+ timeMinutes: b.optimal?.timeMinutes,
831
+ },
832
+ warning: {
833
+ usd: b.warning?.usd,
834
+ tokens: b.warning?.tokens,
835
+ timeMinutes: b.warning?.timeMinutes,
836
+ },
837
+ hard: {
838
+ usd: b.hard?.usd,
839
+ tokens: b.hard?.tokens,
840
+ timeMinutes: b.hard?.timeMinutes,
841
+ maxIterations: hardIter,
842
+ },
843
+ };
844
+ }
845
+ resolveValidators(spec, task) {
846
+ const ids = task.validators ?? spec.defaults.validators ?? [];
847
+ const byId = new Map((spec.validators ?? []).map((v) => [v.id, v]));
848
+ const validators = [];
849
+ for (const id of ids) {
850
+ const v = byId.get(id);
851
+ if (!v)
852
+ continue;
853
+ validators.push({
854
+ id: v.id,
855
+ run: v.run,
856
+ timeoutMs: v.timeoutSeconds ? v.timeoutSeconds * 1000 : undefined,
857
+ parser: v.parser ?? undefined,
858
+ });
859
+ }
860
+ return validators;
861
+ }
862
+ }
863
+ exports.EngineLoop = EngineLoop;
864
+ //# sourceMappingURL=loop.js.map