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.
- package/README.md +18 -13
- package/bin/chati.js +28 -4
- package/framework/agents/build/dev.md +5 -1
- package/framework/agents/discover/greenfield-wu.md +16 -1
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +24 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +60 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/manifest.json +1351 -0
- package/framework/manifest.sig +1 -0
- package/framework/orchestrator/chati-router.js +1 -1
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +24 -3
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +87 -1
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -4
- package/src/config/claude-settings-generator.js +9 -6
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +35 -4
- package/src/installer/manifest.js +30 -4
- package/src/installer/signing-public-key.pem +1 -1
- package/src/installer/templates.js +5 -6
- package/src/installer/validator.js +2 -1
- package/src/intelligence/registry-manager.js +3 -2
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +7 -2
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +195 -23
- package/src/orchestrator/deviation-handler.js +5 -3
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +56 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-parallel.js +14 -14
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/backup.js +22 -44
- package/src/upgrade/migrator.js +51 -12
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/constitution-meta.js +12 -0
- package/src/utils/feature-flags.js +26 -2
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- 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
|
-
}
|