chati-dev 4.3.0 → 4.4.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 (156) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +32 -4
  3. package/framework/agents/build/dev.md +9 -5
  4. package/framework/agents/discover/brief.md +2 -0
  5. package/framework/agents/discover/brownfield-wu.md +2 -0
  6. package/framework/agents/discover/greenfield-wu.md +18 -1
  7. package/framework/agents/plan/detail.md +2 -0
  8. package/framework/agents/plan/tasks.md +31 -0
  9. package/framework/agents/plan/ux-brand-architect.md +21 -2
  10. package/framework/agents/plan/ux-component-engineer.md +10 -0
  11. package/framework/agents/quality/qa-implementation.md +1 -1
  12. package/framework/agents/quality/qa-planning.md +1 -1
  13. package/framework/agents/quality/qa-visual.md +33 -21
  14. package/framework/config.yaml +5 -4
  15. package/framework/constitution.md +58 -11
  16. package/framework/context/governance.md +12 -10
  17. package/framework/context/root.md +2 -2
  18. package/framework/data/entity-registry.yaml +12 -4
  19. package/framework/data/qa-rubrics.yaml +335 -0
  20. package/framework/domains/constitution.yaml +3 -3
  21. package/framework/executors/json-validate.js +80 -0
  22. package/framework/executors/npm-script.js +72 -0
  23. package/framework/executors/path-check.js +67 -0
  24. package/framework/executors/registry.yaml +36 -0
  25. package/framework/hooks/advance-trigger.js +47 -3
  26. package/framework/hooks/constitution-guard.js +9 -4
  27. package/framework/hooks/git-push-authority.js +113 -0
  28. package/framework/hooks/license-guard.js +59 -9
  29. package/framework/hooks/model-governance.js +40 -17
  30. package/framework/hooks/prism-engine.js +78 -11
  31. package/framework/hooks/session-digest.js +12 -3
  32. package/framework/hooks/settings.json +7 -3
  33. package/framework/hooks/style-guard.js +52 -3
  34. package/framework/hooks/team-quality-gate.js +43 -5
  35. package/framework/i18n/en.yaml +3 -3
  36. package/framework/i18n/es.yaml +3 -3
  37. package/framework/i18n/fr.yaml +3 -3
  38. package/framework/i18n/pt.yaml +3 -3
  39. package/framework/intelligence/confidence.yaml +85 -6
  40. package/framework/intelligence/context-engine.md +9 -5
  41. package/framework/intelligence/decision-engine.md +1 -1
  42. package/framework/orchestrator/chati-router.js +59 -5
  43. package/framework/orchestrator/chati-update.md +19 -3
  44. package/framework/orchestrator/chati.md +20 -1
  45. package/framework/quality-gates/planning-gate.md +2 -0
  46. package/framework/rules/human-writing-style.md +47 -0
  47. package/framework/scaffold/motion-premium/README.md +12 -2
  48. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
  49. package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
  50. package/framework/scaffold/motion-premium-3d/README.md +10 -0
  51. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  52. package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
  53. package/framework/scaffold/saas-dashboard/README.md +58 -0
  54. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  55. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  56. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  57. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  58. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  59. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  60. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  61. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  62. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  63. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  64. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  65. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  66. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  67. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  68. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  69. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  70. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  71. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  72. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  73. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  74. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  77. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  78. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  79. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  80. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  81. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  82. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  83. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  84. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  85. package/framework/schemas/session.schema.json +5 -0
  86. package/framework/schemas/task.schema.json +9 -0
  87. package/framework/scripts/visual-qa.js +101 -3
  88. package/framework/tasks/orchestrator-health.md +4 -4
  89. package/framework/tasks/qa-impl-verdict.md +11 -5
  90. package/framework/templates/brandbook-html-tmpl.md +1 -1
  91. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  92. package/package.json +4 -3
  93. package/src/config/context-file-generator.js +0 -6
  94. package/src/dashboard/renderer.js +0 -36
  95. package/src/executors/runner.js +204 -0
  96. package/src/installer/core.js +13 -3
  97. package/src/installer/templates.js +5 -6
  98. package/src/intelligence/registry-manager.js +1 -1
  99. package/src/license/client.js +9 -3
  100. package/src/license/commands.js +12 -2
  101. package/src/license/machine-id.js +42 -1
  102. package/src/memory/gotchas.js +58 -40
  103. package/src/memory/magic-docs.js +1 -1
  104. package/src/memory/session-digest.js +9 -4
  105. package/src/orchestrator/cli.js +186 -15
  106. package/src/orchestrator/doctor.js +98 -16
  107. package/src/orchestrator/pipeline-manager.js +44 -22
  108. package/src/orchestrator/session-manager.js +64 -8
  109. package/src/telemetry/sender.js +7 -19
  110. package/src/terminal/run-team.js +3 -3
  111. package/src/upgrade/tracked-files-detector.js +34 -16
  112. package/src/utils/feature-flags.js +1 -1
  113. package/src/utils/flatten-entities.js +4 -40
  114. package/src/utils/schema-validator.js +0 -14
  115. package/src/wizard/i18n.js +3 -3
  116. package/src/api/index.js +0 -120
  117. package/src/autonomy/autonomous-gate.js +0 -294
  118. package/src/autonomy/build-loop.js +0 -281
  119. package/src/autonomy/build-state.js +0 -286
  120. package/src/autonomy/cause-analyzer.js +0 -177
  121. package/src/autonomy/escalation.js +0 -214
  122. package/src/autonomy/index.js +0 -51
  123. package/src/autonomy/mode-manager.js +0 -225
  124. package/src/autonomy/mode-suggester.js +0 -283
  125. package/src/autonomy/progress-reporter.js +0 -275
  126. package/src/autonomy/safety-net.js +0 -370
  127. package/src/config/agent-customizer.js +0 -231
  128. package/src/decision/analyzer.js +0 -291
  129. package/src/decision/engine.js +0 -250
  130. package/src/decision/index.js +0 -38
  131. package/src/decision/registry-healer.js +0 -468
  132. package/src/decision/registry-updater.js +0 -339
  133. package/src/extensions/loader.js +0 -145
  134. package/src/extensions/registry.js +0 -134
  135. package/src/gates/circuit-breaker.js +0 -151
  136. package/src/gates/g1-planning-complete.js +0 -154
  137. package/src/gates/g2-qa-planning.js +0 -156
  138. package/src/gates/g3-implementation.js +0 -215
  139. package/src/gates/g4-qa-implementation.js +0 -240
  140. package/src/gates/g5-deploy-ready.js +0 -181
  141. package/src/gates/gate-base.js +0 -185
  142. package/src/gates/index.js +0 -46
  143. package/src/health/auto-fix.js +0 -216
  144. package/src/health/engine.js +0 -246
  145. package/src/merger/semantic-merger.js +0 -292
  146. package/src/preview/detector.js +0 -238
  147. package/src/preview/index.js +0 -20
  148. package/src/preview/launcher.js +0 -235
  149. package/src/preview/log-buffer.js +0 -103
  150. package/src/quality/metrics-collector.js +0 -281
  151. package/src/quality/test-runner.js +0 -366
  152. package/src/tasks/executor.js +0 -195
  153. package/src/tasks/index.js +0 -4
  154. package/src/tasks/loader.js +0 -210
  155. package/src/tasks/router.js +0 -182
  156. package/src/utils/event-bus.js +0 -126
@@ -1,294 +0,0 @@
1
- /**
2
- * @fileoverview Autonomous quality gate evaluation
3
- * Evaluates quality gates without human input using score-based heuristics
4
- */
5
-
6
- import { loadSession, updateSession } from '../orchestrator/session-manager.js';
7
-
8
- /**
9
- * Gate result types.
10
- */
11
- export const GATE_RESULTS = {
12
- PASS: 'pass',
13
- FAIL: 'fail',
14
- REVIEW: 'review', // Borderline — pause for human review even in autonomous mode
15
- };
16
-
17
- // Minimum passing scores per agent (conservative: 90 default)
18
- const GATE_THRESHOLDS = {
19
- 'qa-planning': 95, // Highest — gates planning→build transition
20
- 'qa-implementation': 95, // Gates build→deploy transition
21
- 'qa-visual': 90, // Visual quality gate
22
- 'brief': 90, // Requirements are critical
23
- 'detail': 90,
24
- 'architect': 90,
25
- 'ux': 90,
26
- 'phases': 90,
27
- 'tasks': 90,
28
- 'dev': 90,
29
- 'devops': 90,
30
- 'greenfield-wu': 90,
31
- 'brownfield-wu': 90,
32
- // Default for any other agent
33
- '_default': 90,
34
- };
35
-
36
- // Review range: within 5 points of threshold triggers REVIEW
37
- const REVIEW_RANGE = 5;
38
-
39
- /**
40
- * Evaluate a quality gate autonomously.
41
- *
42
- * @param {object} gateInput
43
- * @param {string} gateInput.agent - Agent that produced the work
44
- * @param {number} gateInput.score - Self-reported quality score (0-100)
45
- * @param {string[]} gateInput.criteriaResults - Which criteria passed
46
- * @param {string[]} gateInput.allCriteria - All criteria for this gate
47
- * @param {number} [gateInput.confidence] - Agent's confidence (0-100)
48
- * @param {string[]} [gateInput.warnings] - Any warnings produced
49
- * @returns {{ result: string, score: number, reasoning: string, canProceed: boolean }}
50
- */
51
- export function evaluateGate(gateInput) {
52
- const {
53
- agent,
54
- score,
55
- criteriaResults = [],
56
- allCriteria = [],
57
- confidence = 100,
58
- warnings = [],
59
- } = gateInput;
60
-
61
- // Validate inputs
62
- if (typeof score !== 'number' || score < 0 || score > 100) {
63
- throw new Error(`Invalid score: ${score}. Must be 0-100.`);
64
- }
65
-
66
- const threshold = getGateThreshold(agent);
67
- const criteriaScore = allCriteria.length > 0
68
- ? (criteriaResults.length / allCriteria.length) * 100
69
- : 100;
70
-
71
- // Combine self-reported score and criteria score
72
- const weightedScore = (score * 0.6) + (criteriaScore * 0.4);
73
-
74
- // Factor in confidence
75
- const confidencePenalty = confidence < 80 ? (80 - confidence) * 0.2 : 0;
76
- const finalScore = Math.max(0, weightedScore - confidencePenalty);
77
-
78
- // Factor in warnings
79
- const warningPenalty = warnings.length * 3;
80
- const adjustedScore = Math.max(0, finalScore - warningPenalty);
81
-
82
- // Determine result
83
- let result;
84
- const reasoning = [];
85
-
86
- if (adjustedScore >= threshold) {
87
- result = GATE_RESULTS.PASS;
88
- reasoning.push(`Score ${Math.round(adjustedScore)}% meets threshold ${threshold}%`);
89
- } else if (adjustedScore >= threshold - REVIEW_RANGE) {
90
- result = GATE_RESULTS.REVIEW;
91
- reasoning.push(`Score ${Math.round(adjustedScore)}% is borderline (threshold: ${threshold}%)`);
92
- reasoning.push('Manual review recommended');
93
- } else {
94
- result = GATE_RESULTS.FAIL;
95
- reasoning.push(`Score ${Math.round(adjustedScore)}% below threshold ${threshold}%`);
96
- }
97
-
98
- // Add details
99
- reasoning.push(`Criteria: ${criteriaResults.length}/${allCriteria.length} passed`);
100
-
101
- if (confidence < 80) {
102
- reasoning.push(`Low confidence (${confidence}%) reduced score by ${Math.round(confidencePenalty)}%`);
103
- }
104
-
105
- if (warnings.length > 0) {
106
- reasoning.push(`${warnings.length} warning(s) reduced score by ${warningPenalty}%`);
107
- }
108
-
109
- return {
110
- result,
111
- score: Math.round(adjustedScore),
112
- reasoning: reasoning.join('. '),
113
- canProceed: result === GATE_RESULTS.PASS,
114
- details: {
115
- rawScore: Math.round(score),
116
- criteriaScore: Math.round(criteriaScore),
117
- weightedScore: Math.round(weightedScore),
118
- confidencePenalty: Math.round(confidencePenalty),
119
- warningPenalty,
120
- finalScore: Math.round(adjustedScore),
121
- threshold,
122
- },
123
- };
124
- }
125
-
126
- /**
127
- * Get the minimum passing score for an agent's gate.
128
- * Different agents have different thresholds.
129
- * @param {string} agentName
130
- * @returns {number} Minimum score (0-100)
131
- */
132
- export function getGateThreshold(agentName) {
133
- return GATE_THRESHOLDS[agentName] || GATE_THRESHOLDS._default;
134
- }
135
-
136
- /**
137
- * Check if gate result allows pipeline to continue.
138
- * @param {string} result - GATE_RESULTS value
139
- * @param {string} executionMode - 'autonomous' | 'human-in-the-loop'
140
- * @returns {{ proceed: boolean, action: string }}
141
- */
142
- export function resolveGateAction(result, executionMode) {
143
- if (result === GATE_RESULTS.PASS) {
144
- return {
145
- proceed: true,
146
- action: 'continue',
147
- };
148
- }
149
-
150
- if (result === GATE_RESULTS.FAIL) {
151
- return {
152
- proceed: false,
153
- action: executionMode === 'autonomous' ? 'pause_for_review' : 'wait_for_user',
154
- };
155
- }
156
-
157
- // REVIEW result
158
- if (executionMode === 'autonomous') {
159
- return {
160
- proceed: false,
161
- action: 'pause_for_review',
162
- };
163
- }
164
-
165
- return {
166
- proceed: false,
167
- action: 'wait_for_user',
168
- };
169
- }
170
-
171
- /**
172
- * Get gate evaluation history.
173
- * @param {string} projectDir
174
- * @returns {object[]}
175
- */
176
- export function getGateHistory(projectDir) {
177
- const result = loadSession(projectDir);
178
- if (!result.loaded || !result.session) {
179
- return [];
180
- }
181
- return result.session.gate_evaluations || [];
182
- }
183
-
184
- /**
185
- * Record a gate evaluation.
186
- * @param {string} projectDir
187
- * @param {object} evaluation
188
- */
189
- export function recordGateEvaluation(projectDir, evaluation) {
190
- const result = loadSession(projectDir);
191
-
192
- if (!result.loaded || !result.session) {
193
- return;
194
- }
195
-
196
- const gate_evaluations = result.session.gate_evaluations || [];
197
- gate_evaluations.push({
198
- ...evaluation,
199
- timestamp: new Date().toISOString(),
200
- });
201
-
202
- updateSession(projectDir, { gate_evaluations });
203
- }
204
-
205
- /**
206
- * Get gate statistics.
207
- * @param {string} projectDir
208
- * @returns {{ total: number, passed: number, failed: number, reviewed: number, passRate: number }}
209
- */
210
- export function getGateStatistics(projectDir) {
211
- const history = getGateHistory(projectDir);
212
-
213
- const stats = {
214
- total: history.length,
215
- passed: 0,
216
- failed: 0,
217
- reviewed: 0,
218
- };
219
-
220
- history.forEach(evaluation => {
221
- if (evaluation.result === GATE_RESULTS.PASS) {
222
- stats.passed++;
223
- } else if (evaluation.result === GATE_RESULTS.FAIL) {
224
- stats.failed++;
225
- } else if (evaluation.result === GATE_RESULTS.REVIEW) {
226
- stats.reviewed++;
227
- }
228
- });
229
-
230
- stats.passRate = stats.total > 0
231
- ? Math.round((stats.passed / stats.total) * 100)
232
- : 0;
233
-
234
- return stats;
235
- }
236
-
237
- /**
238
- * Get agent-specific gate statistics.
239
- * @param {string} projectDir
240
- * @param {string} agentName
241
- * @returns {{ total: number, passed: number, failed: number, reviewed: number, averageScore: number }}
242
- */
243
- export function getAgentGateStatistics(projectDir, agentName) {
244
- const history = getGateHistory(projectDir);
245
- const agentEvaluations = history.filter(e => e.agent === agentName);
246
-
247
- const stats = {
248
- total: agentEvaluations.length,
249
- passed: 0,
250
- failed: 0,
251
- reviewed: 0,
252
- averageScore: 0,
253
- };
254
-
255
- if (stats.total === 0) {
256
- return stats;
257
- }
258
-
259
- let totalScore = 0;
260
-
261
- agentEvaluations.forEach(evaluation => {
262
- if (evaluation.result === GATE_RESULTS.PASS) {
263
- stats.passed++;
264
- } else if (evaluation.result === GATE_RESULTS.FAIL) {
265
- stats.failed++;
266
- } else if (evaluation.result === GATE_RESULTS.REVIEW) {
267
- stats.reviewed++;
268
- }
269
-
270
- totalScore += evaluation.score || 0;
271
- });
272
-
273
- stats.averageScore = Math.round(totalScore / stats.total);
274
-
275
- return stats;
276
- }
277
-
278
- /**
279
- * Clear gate evaluation history.
280
- * @param {string} projectDir
281
- * @returns {{ cleared: number }}
282
- */
283
- export function clearGateHistory(projectDir) {
284
- const result = loadSession(projectDir);
285
- if (!result.loaded || !result.session) {
286
- return { cleared: 0 };
287
- }
288
-
289
- const count = result.session.gate_evaluations?.length || 0;
290
-
291
- updateSession(projectDir, { gate_evaluations: [] });
292
-
293
- return { cleared: count };
294
- }
@@ -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
- }