chati-dev 4.3.1 → 4.4.1

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 (158) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +28 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +60 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/manifest.json +1351 -0
  40. package/framework/manifest.sig +1 -0
  41. package/framework/orchestrator/chati-router.js +1 -1
  42. package/framework/orchestrator/chati-update.md +19 -3
  43. package/framework/orchestrator/chati.md +24 -3
  44. package/framework/quality-gates/planning-gate.md +2 -0
  45. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  46. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  47. package/framework/scaffold/saas-dashboard/README.md +58 -0
  48. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  49. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  50. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  51. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  52. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  54. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  55. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  56. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  57. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  58. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  59. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  61. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  62. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  67. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  68. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  77. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  78. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  79. package/framework/schemas/session.schema.json +5 -0
  80. package/framework/schemas/task.schema.json +9 -0
  81. package/framework/scripts/visual-qa.js +87 -1
  82. package/framework/tasks/orchestrator-health.md +4 -4
  83. package/framework/tasks/qa-impl-verdict.md +11 -5
  84. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  85. package/package.json +4 -4
  86. package/src/config/claude-settings-generator.js +9 -6
  87. package/src/config/context-file-generator.js +0 -6
  88. package/src/dashboard/renderer.js +0 -36
  89. package/src/executors/runner.js +204 -0
  90. package/src/installer/core.js +35 -4
  91. package/src/installer/manifest.js +30 -4
  92. package/src/installer/signing-public-key.pem +1 -1
  93. package/src/installer/templates.js +5 -6
  94. package/src/installer/validator.js +2 -1
  95. package/src/intelligence/registry-manager.js +3 -2
  96. package/src/license/client.js +9 -3
  97. package/src/license/commands.js +12 -2
  98. package/src/license/machine-id.js +42 -1
  99. package/src/memory/gotchas.js +58 -40
  100. package/src/memory/magic-docs.js +7 -2
  101. package/src/memory/session-digest.js +9 -4
  102. package/src/orchestrator/cli.js +195 -23
  103. package/src/orchestrator/deviation-handler.js +5 -3
  104. package/src/orchestrator/doctor.js +98 -16
  105. package/src/orchestrator/pipeline-manager.js +44 -22
  106. package/src/orchestrator/session-manager.js +56 -8
  107. package/src/telemetry/sender.js +7 -19
  108. package/src/terminal/run-parallel.js +14 -14
  109. package/src/terminal/run-team.js +3 -3
  110. package/src/upgrade/backup.js +22 -44
  111. package/src/upgrade/migrator.js +51 -12
  112. package/src/upgrade/tracked-files-detector.js +34 -16
  113. package/src/utils/constitution-meta.js +12 -0
  114. package/src/utils/feature-flags.js +26 -2
  115. package/src/utils/flatten-entities.js +4 -40
  116. package/src/utils/schema-validator.js +0 -14
  117. package/src/wizard/i18n.js +3 -3
  118. package/src/api/index.js +0 -120
  119. package/src/autonomy/autonomous-gate.js +0 -294
  120. package/src/autonomy/build-loop.js +0 -281
  121. package/src/autonomy/build-state.js +0 -286
  122. package/src/autonomy/cause-analyzer.js +0 -177
  123. package/src/autonomy/escalation.js +0 -214
  124. package/src/autonomy/index.js +0 -51
  125. package/src/autonomy/mode-manager.js +0 -225
  126. package/src/autonomy/mode-suggester.js +0 -283
  127. package/src/autonomy/progress-reporter.js +0 -275
  128. package/src/autonomy/safety-net.js +0 -370
  129. package/src/config/agent-customizer.js +0 -231
  130. package/src/decision/analyzer.js +0 -291
  131. package/src/decision/engine.js +0 -250
  132. package/src/decision/index.js +0 -38
  133. package/src/decision/registry-healer.js +0 -468
  134. package/src/decision/registry-updater.js +0 -339
  135. package/src/extensions/loader.js +0 -145
  136. package/src/extensions/registry.js +0 -134
  137. package/src/gates/circuit-breaker.js +0 -151
  138. package/src/gates/g1-planning-complete.js +0 -154
  139. package/src/gates/g2-qa-planning.js +0 -156
  140. package/src/gates/g3-implementation.js +0 -215
  141. package/src/gates/g4-qa-implementation.js +0 -240
  142. package/src/gates/g5-deploy-ready.js +0 -181
  143. package/src/gates/gate-base.js +0 -185
  144. package/src/gates/index.js +0 -46
  145. package/src/health/auto-fix.js +0 -216
  146. package/src/health/engine.js +0 -246
  147. package/src/merger/semantic-merger.js +0 -292
  148. package/src/preview/detector.js +0 -238
  149. package/src/preview/index.js +0 -20
  150. package/src/preview/launcher.js +0 -235
  151. package/src/preview/log-buffer.js +0 -103
  152. package/src/quality/metrics-collector.js +0 -281
  153. package/src/quality/test-runner.js +0 -366
  154. package/src/tasks/executor.js +0 -195
  155. package/src/tasks/index.js +0 -4
  156. package/src/tasks/loader.js +0 -210
  157. package/src/tasks/router.js +0 -182
  158. package/src/utils/event-bus.js +0 -126
@@ -1,281 +0,0 @@
1
- /**
2
- * @fileoverview Autonomous build loop (Ralph Wiggum v2).
3
- *
4
- * Executes tasks autonomously with checkpoint-based state management,
5
- * retry logic, and quality gate integration.
6
- *
7
- * Named "Ralph Wiggum" internally — the autonomous execution mode
8
- * that iterates until all tasks are complete or escalation is needed.
9
- *
10
- * Constitution Article XVII — Execution Mode Governance.
11
- */
12
-
13
- import {
14
- createBuildState,
15
- loadBuildState,
16
- saveBuildState,
17
- startBuild,
18
- completeBuild,
19
- failBuild,
20
- updateCheckpoint,
21
- getNextPendingTask,
22
- isTaskExhausted,
23
- isTimedOut,
24
- getProgress,
25
- CheckpointStatus,
26
- BuildStatus,
27
- } from './build-state.js';
28
- import { analyzeCause, buildRetryGuidance } from './cause-analyzer.js';
29
- import { shouldEscalate, getEscalationConfig, buildEscalationSummary } from './escalation.js';
30
-
31
- // ---------------------------------------------------------------------------
32
- // Build Loop
33
- // ---------------------------------------------------------------------------
34
-
35
- /**
36
- * @typedef {object} BuildLoopConfig
37
- * @property {string} projectDir - Project root directory
38
- * @property {string[]} taskIds - Task IDs to execute
39
- * @property {function(string, object?): Promise<{success: boolean, output: string}>} executor - Task execution function (taskId, options?)
40
- * @property {function(object): void} [onProgress] - Progress callback
41
- * @property {boolean} [resume=false] - Whether to resume from existing state
42
- * @property {string} [model='sonnet'] - Current model tier for escalation
43
- */
44
-
45
- /**
46
- * @typedef {object} BuildLoopResult
47
- * @property {string} status - Final build status
48
- * @property {number} completed - Tasks completed
49
- * @property {number} failed - Tasks failed
50
- * @property {number} totalAttempts - Total execution attempts
51
- * @property {string} duration - Human-readable duration
52
- */
53
-
54
- /**
55
- * Run the autonomous build loop.
56
- *
57
- * Loop logic:
58
- * 1. Load or create build state
59
- * 2. Get next pending task
60
- * 3. Execute task
61
- * 4. Save checkpoint
62
- * 5. If task failed and not exhausted, retry
63
- * 6. If task exhausted, mark as failed and continue
64
- * 7. Repeat until all tasks complete or global timeout
65
- *
66
- * @param {BuildLoopConfig} config
67
- * @returns {Promise<BuildLoopResult>}
68
- */
69
- export async function runBuildLoop(config) {
70
- const { projectDir, taskIds, executor, onProgress, resume = false, model = 'sonnet' } = config;
71
-
72
- // Load or create state
73
- let state = resume ? loadBuildState(projectDir) : null;
74
-
75
- if (!state || state.status === BuildStatus.COMPLETED || state.status === BuildStatus.ABANDONED) {
76
- state = createBuildState(taskIds);
77
- }
78
-
79
- state = startBuild(state);
80
- saveBuildState(projectDir, state);
81
-
82
- const startTime = Date.now();
83
-
84
- // Per-task attempt history for cause analysis
85
- /** @type {Map<string, Array<{category: string, output: string}>>} */
86
- const attemptHistory = new Map();
87
-
88
- // Main loop
89
- while (true) {
90
- // Check global timeout
91
- if (isTimedOut(state)) {
92
- state = failBuild(state, 'Global timeout exceeded');
93
- saveBuildState(projectDir, state);
94
- break;
95
- }
96
-
97
- // Get next task
98
- const checkpoint = getNextPendingTask(state);
99
- if (!checkpoint) {
100
- // All tasks processed
101
- const hasFailures = state.checkpoints.some((c) => c.status === CheckpointStatus.FAILED);
102
- if (hasFailures) {
103
- state = failBuild(state, 'Some tasks failed');
104
- } else {
105
- state = completeBuild(state);
106
- }
107
- saveBuildState(projectDir, state);
108
- break;
109
- }
110
-
111
- // Check if task is exhausted
112
- if (isTaskExhausted(checkpoint)) {
113
- state = updateCheckpoint(state, checkpoint.taskId, {
114
- status: CheckpointStatus.FAILED,
115
- error: `Exceeded max iterations (${checkpoint.attempts})`,
116
- });
117
- saveBuildState(projectDir, state);
118
-
119
- if (onProgress) {
120
- onProgress({ type: 'task_exhausted', taskId: checkpoint.taskId, attempts: checkpoint.attempts });
121
- }
122
- continue;
123
- }
124
-
125
- // --- Escalation check (before execution) ---
126
- const previousAttempts = attemptHistory.get(checkpoint.taskId) || [];
127
- const executorOptions = {};
128
-
129
- if (checkpoint.attempts > 0 && checkpoint.error) {
130
- // Analyze the cause of the previous failure
131
- const analysis = analyzeCause(checkpoint.error, previousAttempts);
132
- const escalation = shouldEscalate(checkpoint, analysis);
133
-
134
- if (escalation.escalate) {
135
- const escalationConfig = getEscalationConfig(escalation.newLevel, model);
136
-
137
- // Update checkpoint with escalation level
138
- state = updateCheckpoint(state, checkpoint.taskId, {
139
- escalationLevel: escalation.newLevel,
140
- });
141
-
142
- if (onProgress) {
143
- onProgress({
144
- type: 'escalation',
145
- taskId: checkpoint.taskId,
146
- level: escalation.newLevel,
147
- summary: buildEscalationSummary(escalation.newLevel, escalation.reason),
148
- });
149
- }
150
-
151
- // Pause for human intervention at MAX level
152
- if (escalationConfig.shouldPause) {
153
- state = updateCheckpoint(state, checkpoint.taskId, {
154
- status: CheckpointStatus.FAILED,
155
- error: `Escalation MAX — paused for human intervention: ${escalation.reason}`,
156
- });
157
- saveBuildState(projectDir, state);
158
-
159
- if (onProgress) {
160
- onProgress({ type: 'escalation_pause', taskId: checkpoint.taskId, reason: escalation.reason });
161
- }
162
- continue;
163
- }
164
-
165
- // Build executor options with escalation context
166
- executorOptions.modelOverride = escalationConfig.model;
167
- if (escalationConfig.contextBoost) {
168
- executorOptions.retryGuidance = buildRetryGuidance(analysis, checkpoint.attempts + 1);
169
- }
170
- }
171
- }
172
-
173
- // Mark task as in progress
174
- state = updateCheckpoint(state, checkpoint.taskId, {
175
- status: CheckpointStatus.IN_PROGRESS,
176
- attempts: checkpoint.attempts + 1,
177
- lastAttempt: new Date().toISOString(),
178
- });
179
- saveBuildState(projectDir, state);
180
-
181
- if (onProgress) {
182
- const progress = getProgress(state);
183
- onProgress({ type: 'task_started', taskId: checkpoint.taskId, attempt: checkpoint.attempts + 1, progress });
184
- }
185
-
186
- // Execute task
187
- try {
188
- const result = await executor(checkpoint.taskId, executorOptions);
189
-
190
- if (result.success) {
191
- state = updateCheckpoint(state, checkpoint.taskId, {
192
- status: CheckpointStatus.COMPLETED,
193
- output: result.output?.slice(0, 1000) || 'Completed',
194
- error: null,
195
- escalationLevel: undefined, // Reset on success
196
- });
197
-
198
- if (onProgress) {
199
- onProgress({ type: 'task_completed', taskId: checkpoint.taskId });
200
- }
201
- } else {
202
- // --- Self-critique: analyze failure cause ---
203
- const failureOutput = result.output || 'Task failed';
204
- const analysis = analyzeCause(failureOutput, previousAttempts);
205
-
206
- // Record attempt in history
207
- if (!attemptHistory.has(checkpoint.taskId)) {
208
- attemptHistory.set(checkpoint.taskId, []);
209
- }
210
- attemptHistory.get(checkpoint.taskId).push({
211
- category: analysis.category,
212
- output: failureOutput.slice(0, 500),
213
- });
214
-
215
- state = updateCheckpoint(state, checkpoint.taskId, {
216
- status: CheckpointStatus.IN_PROGRESS, // Will retry
217
- error: failureOutput.slice(0, 500),
218
- });
219
-
220
- if (onProgress) {
221
- onProgress({
222
- type: 'task_failed',
223
- taskId: checkpoint.taskId,
224
- attempt: checkpoint.attempts + 1,
225
- error: failureOutput,
226
- causeAnalysis: analysis,
227
- });
228
- }
229
- }
230
- } catch (err) {
231
- const errorMsg = err.message?.slice(0, 500) || 'Execution error';
232
-
233
- // Record exception in history for cause analysis
234
- const analysis = analyzeCause(errorMsg, previousAttempts);
235
- if (!attemptHistory.has(checkpoint.taskId)) {
236
- attemptHistory.set(checkpoint.taskId, []);
237
- }
238
- attemptHistory.get(checkpoint.taskId).push({
239
- category: analysis.category,
240
- output: errorMsg,
241
- });
242
-
243
- state = updateCheckpoint(state, checkpoint.taskId, {
244
- status: CheckpointStatus.IN_PROGRESS, // Will retry
245
- error: errorMsg,
246
- });
247
- }
248
-
249
- saveBuildState(projectDir, state);
250
- }
251
-
252
- const duration = Date.now() - startTime;
253
- const progress = getProgress(state);
254
-
255
- return {
256
- status: state.status,
257
- completed: progress.completed,
258
- failed: progress.failed,
259
- totalAttempts: state.totalAttempts,
260
- duration: `${Math.round(duration / 1000)}s`,
261
- };
262
- }
263
-
264
- /**
265
- * Get current build loop status without executing.
266
- *
267
- * @param {string} projectDir
268
- * @returns {object|null}
269
- */
270
- export function getBuildStatus(projectDir) {
271
- const state = loadBuildState(projectDir);
272
- if (!state) return null;
273
-
274
- return {
275
- sessionId: state.sessionId,
276
- status: state.status,
277
- progress: getProgress(state),
278
- startedAt: state.startedAt,
279
- lastCheckpoint: state.lastCheckpoint,
280
- };
281
- }
@@ -1,286 +0,0 @@
1
- /**
2
- * @fileoverview Build state manager with checkpoint-based persistence.
3
- *
4
- * Manages the state of autonomous build execution, including
5
- * checkpoints saved after each task completion, abandoned build
6
- * detection, and attempt logging.
7
- *
8
- * Constitution Article XVII — Execution Mode Governance.
9
- */
10
-
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
12
- import { join, dirname } from 'path';
13
-
14
- // ---------------------------------------------------------------------------
15
- // Constants
16
- // ---------------------------------------------------------------------------
17
-
18
- /** Maximum iterations per individual task before giving up */
19
- export const MAX_ITERATIONS_PER_TASK = 10;
20
-
21
- /** Maximum time per individual task before giving up (10 minutes) */
22
- export const MAX_TIME_PER_TASK_MS = 10 * 60 * 1000;
23
-
24
- /** Global timeout for an autonomous build session (30 minutes) */
25
- export const GLOBAL_TIMEOUT_MS = 30 * 60 * 1000;
26
-
27
- /** Time after which a build is considered abandoned (1 hour) */
28
- export const ABANDONED_THRESHOLD_MS = 60 * 60 * 1000;
29
-
30
- /**
31
- * Build status enum.
32
- * @enum {string}
33
- */
34
- export const BuildStatus = {
35
- PENDING: 'pending',
36
- IN_PROGRESS: 'in_progress',
37
- PAUSED: 'paused',
38
- ABANDONED: 'abandoned',
39
- FAILED: 'failed',
40
- COMPLETED: 'completed',
41
- };
42
-
43
- /**
44
- * Task checkpoint status.
45
- * @enum {string}
46
- */
47
- export const CheckpointStatus = {
48
- PENDING: 'pending',
49
- IN_PROGRESS: 'in_progress',
50
- COMPLETED: 'completed',
51
- FAILED: 'failed',
52
- SKIPPED: 'skipped',
53
- };
54
-
55
- // ---------------------------------------------------------------------------
56
- // State Schema
57
- // ---------------------------------------------------------------------------
58
-
59
- /**
60
- * @typedef {object} TaskCheckpoint
61
- * @property {string} taskId
62
- * @property {string} status - CheckpointStatus
63
- * @property {number} attempts - Number of execution attempts
64
- * @property {string|null} firstAttempt - ISO timestamp of first attempt
65
- * @property {string|null} lastAttempt - ISO timestamp of last attempt
66
- * @property {string|null} output - Last output summary
67
- * @property {string|null} error - Last error message (if failed)
68
- */
69
-
70
- /**
71
- * @typedef {object} BuildState
72
- * @property {string} sessionId - Build session identifier
73
- * @property {string} status - BuildStatus
74
- * @property {TaskCheckpoint[]} checkpoints - Per-task checkpoints
75
- * @property {string} startedAt - ISO timestamp
76
- * @property {string|null} lastCheckpoint - ISO timestamp of last checkpoint
77
- * @property {string|null} completedAt - ISO timestamp of completion
78
- * @property {number} totalAttempts - Total execution attempts across all tasks
79
- */
80
-
81
- const BUILD_STATE_FILE = '.chati/build-state.json';
82
-
83
- // ---------------------------------------------------------------------------
84
- // State Management
85
- // ---------------------------------------------------------------------------
86
-
87
- /**
88
- * Initialize a new build state.
89
- *
90
- * @param {string[]} taskIds - Array of task IDs to execute
91
- * @returns {BuildState}
92
- */
93
- export function createBuildState(taskIds) {
94
- return {
95
- sessionId: `build-${Date.now()}`,
96
- status: BuildStatus.PENDING,
97
- checkpoints: taskIds.map((taskId) => ({
98
- taskId,
99
- status: CheckpointStatus.PENDING,
100
- attempts: 0,
101
- firstAttempt: null,
102
- lastAttempt: null,
103
- output: null,
104
- error: null,
105
- })),
106
- startedAt: new Date().toISOString(),
107
- lastCheckpoint: null,
108
- completedAt: null,
109
- totalAttempts: 0,
110
- };
111
- }
112
-
113
- /**
114
- * Load build state from disk.
115
- *
116
- * @param {string} projectDir
117
- * @returns {BuildState|null}
118
- */
119
- export function loadBuildState(projectDir) {
120
- const statePath = join(projectDir, BUILD_STATE_FILE);
121
- if (!existsSync(statePath)) return null;
122
- try {
123
- const state = JSON.parse(readFileSync(statePath, 'utf-8'));
124
-
125
- // Check for abandoned builds
126
- if (state.status === BuildStatus.IN_PROGRESS && state.lastCheckpoint) {
127
- const elapsed = Date.now() - new Date(state.lastCheckpoint).getTime();
128
- if (elapsed > ABANDONED_THRESHOLD_MS) {
129
- state.status = BuildStatus.ABANDONED;
130
- }
131
- }
132
-
133
- return state;
134
- } catch { /* expected: operation may fail gracefully */
135
- return null;
136
- }
137
- }
138
-
139
- /**
140
- * Save build state to disk.
141
- *
142
- * @param {string} projectDir
143
- * @param {BuildState} state
144
- */
145
- export function saveBuildState(projectDir, state) {
146
- const statePath = join(projectDir, BUILD_STATE_FILE);
147
- mkdirSync(dirname(statePath), { recursive: true });
148
- writeFileSync(statePath, JSON.stringify(state, null, 2));
149
- }
150
-
151
- /**
152
- * Update a task checkpoint.
153
- *
154
- * @param {BuildState} state
155
- * @param {string} taskId
156
- * @param {Partial<TaskCheckpoint>} update
157
- * @returns {BuildState}
158
- */
159
- export function updateCheckpoint(state, taskId, update) {
160
- const checkpoint = state.checkpoints.find((c) => c.taskId === taskId);
161
- if (!checkpoint) {
162
- throw new Error(`Task "${taskId}" not found in build state`);
163
- }
164
-
165
- // Set firstAttempt on the first execution attempt
166
- if (update.attempts !== undefined && update.attempts > 0 && !checkpoint.firstAttempt) {
167
- checkpoint.firstAttempt = new Date().toISOString();
168
- }
169
-
170
- Object.assign(checkpoint, update);
171
- state.lastCheckpoint = new Date().toISOString();
172
-
173
- if (update.attempts !== undefined) {
174
- state.totalAttempts = state.checkpoints.reduce((sum, c) => sum + c.attempts, 0);
175
- }
176
-
177
- return state;
178
- }
179
-
180
- /**
181
- * Mark the build as started.
182
- *
183
- * @param {BuildState} state
184
- * @returns {BuildState}
185
- */
186
- export function startBuild(state) {
187
- state.status = BuildStatus.IN_PROGRESS;
188
- state.startedAt = new Date().toISOString();
189
- return state;
190
- }
191
-
192
- /**
193
- * Mark the build as completed.
194
- *
195
- * @param {BuildState} state
196
- * @returns {BuildState}
197
- */
198
- export function completeBuild(state) {
199
- state.status = BuildStatus.COMPLETED;
200
- state.completedAt = new Date().toISOString();
201
- return state;
202
- }
203
-
204
- /**
205
- * Mark the build as failed.
206
- *
207
- * @param {BuildState} state
208
- * @param {string} [reason]
209
- * @returns {BuildState}
210
- */
211
- export function failBuild(state, _reason) {
212
- state.status = BuildStatus.FAILED;
213
- state.completedAt = new Date().toISOString();
214
- return state;
215
- }
216
-
217
- /**
218
- * Get the next pending task checkpoint.
219
- *
220
- * @param {BuildState} state
221
- * @returns {TaskCheckpoint|null}
222
- */
223
- export function getNextPendingTask(state) {
224
- return state.checkpoints.find(
225
- (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
226
- ) || null;
227
- }
228
-
229
- /**
230
- * Check if a task has exceeded max iterations or max time.
231
- *
232
- * Dual check: exhausted if attempts >= MAX_ITERATIONS_PER_TASK
233
- * OR if elapsed time since first attempt >= MAX_TIME_PER_TASK_MS.
234
- *
235
- * @param {TaskCheckpoint} checkpoint
236
- * @returns {boolean}
237
- */
238
- export function isTaskExhausted(checkpoint) {
239
- if (checkpoint.attempts >= MAX_ITERATIONS_PER_TASK) return true;
240
- if (checkpoint.firstAttempt) {
241
- const elapsed = Date.now() - new Date(checkpoint.firstAttempt).getTime();
242
- if (elapsed >= MAX_TIME_PER_TASK_MS) return true;
243
- }
244
- return false;
245
- }
246
-
247
- /**
248
- * Check if the global timeout has been exceeded.
249
- *
250
- * @param {BuildState} state
251
- * @returns {boolean}
252
- */
253
- export function isTimedOut(state) {
254
- const elapsed = Date.now() - new Date(state.startedAt).getTime();
255
- return elapsed > GLOBAL_TIMEOUT_MS;
256
- }
257
-
258
- /**
259
- * Get build progress summary.
260
- *
261
- * @param {BuildState} state
262
- * @returns {{ total: number, completed: number, failed: number, pending: number, progress: number }}
263
- */
264
- export function getProgress(state) {
265
- const total = state.checkpoints.length;
266
- const completed = state.checkpoints.filter((c) => c.status === CheckpointStatus.COMPLETED).length;
267
- const failed = state.checkpoints.filter((c) => c.status === CheckpointStatus.FAILED).length;
268
- const pending = state.checkpoints.filter(
269
- (c) => c.status === CheckpointStatus.PENDING || c.status === CheckpointStatus.IN_PROGRESS,
270
- ).length;
271
- const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
272
-
273
- return { total, completed, failed, pending, progress };
274
- }
275
-
276
- /**
277
- * Clear build state (after completion or manual reset).
278
- *
279
- * @param {string} projectDir
280
- */
281
- export function clearBuildState(projectDir) {
282
- const statePath = join(projectDir, BUILD_STATE_FILE);
283
- if (existsSync(statePath)) {
284
- unlinkSync(statePath);
285
- }
286
- }