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.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- 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 +33 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -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/orchestrator/chati-router.js +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- 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 +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -3
- 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 +13 -3
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- 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 +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- 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,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
|
-
}
|