@hongmaple0820/scale-engine 0.10.0 → 0.11.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.en.md +127 -196
- package/README.md +168 -1114
- package/dist/api/cli.js +2 -2
- package/dist/api/cli.js.map +1 -1
- package/dist/artifact/types.d.ts +1 -1
- package/dist/artifact/types.js.map +1 -1
- package/dist/capabilities/BrowserQACapability.d.ts +151 -0
- package/dist/capabilities/BrowserQACapability.js +344 -0
- package/dist/capabilities/BrowserQACapability.js.map +1 -0
- package/dist/cli/evolutionCommands.d.ts +112 -0
- package/dist/cli/evolutionCommands.js +246 -0
- package/dist/cli/evolutionCommands.js.map +1 -0
- package/dist/cli/phaseCommands.d.ts +9 -0
- package/dist/cli/phaseCommands.js +169 -48
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/guardrails/OWASPDetector.d.ts +58 -0
- package/dist/guardrails/OWASPDetector.js +508 -0
- package/dist/guardrails/OWASPDetector.js.map +1 -0
- package/dist/workflow/ReviewAnalyzer.d.ts +5 -0
- package/dist/workflow/ReviewAnalyzer.js +194 -10
- package/dist/workflow/ReviewAnalyzer.js.map +1 -1
- package/dist/workflow/VerificationCommands.d.ts +4 -0
- package/dist/workflow/VerificationCommands.js +2 -0
- package/dist/workflow/VerificationCommands.js.map +1 -1
- package/dist/workflow/WorkflowEngine.js +1 -1
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/dist/workflow/evolution/LessonExtractor.d.ts +90 -0
- package/dist/workflow/evolution/LessonExtractor.js +317 -0
- package/dist/workflow/evolution/LessonExtractor.js.map +1 -0
- package/dist/workflow/evolution/SelfImproveEngine.d.ts +156 -0
- package/dist/workflow/evolution/SelfImproveEngine.js +361 -0
- package/dist/workflow/evolution/SelfImproveEngine.js.map +1 -0
- package/dist/workflow/gates/GateSystem.d.ts +28 -2
- package/dist/workflow/gates/GateSystem.js +291 -82
- package/dist/workflow/gates/GateSystem.js.map +1 -1
- package/dist/workflow/qa/E2ETestRunner.d.ts +102 -0
- package/dist/workflow/qa/E2ETestRunner.js +227 -0
- package/dist/workflow/qa/E2ETestRunner.js.map +1 -0
- package/dist/workflow/types.d.ts +7 -0
- package/package.json +3 -3
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// SCALE Engine — Evolution Commands
|
|
2
|
+
// L6 Evolution CLI 命令:Lesson 提取、自改进、规则管理
|
|
3
|
+
// 设计参考:docs/03-CORE-MODULES.md §3.6
|
|
4
|
+
import { defineCommand } from 'citty';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { EventBus } from '../core/eventBus.js';
|
|
8
|
+
import { SQLiteArtifactStore } from '../artifact/sqliteStore.js';
|
|
9
|
+
import { FSM } from '../artifact/fsm.js';
|
|
10
|
+
import { registerAllFSMs } from '../artifact/fsmDefinitions.js';
|
|
11
|
+
import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
|
|
12
|
+
import { CapabilityRegistry } from '../capabilities/CapabilityRegistry.js';
|
|
13
|
+
import { SkillRegistry } from '../skills/SkillRegistry.js';
|
|
14
|
+
import { LessonExtractor } from '../workflow/evolution/LessonExtractor.js';
|
|
15
|
+
import { SelfImproveEngine } from '../workflow/evolution/SelfImproveEngine.js';
|
|
16
|
+
const SCALE_DIR = process.env.SCALE_DIR ?? '.scale';
|
|
17
|
+
function ensureDir(dir) {
|
|
18
|
+
if (!existsSync(dir))
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
// Engine singleton (reuse pattern from phaseCommands.ts)
|
|
22
|
+
function getEngine() {
|
|
23
|
+
ensureDir(SCALE_DIR);
|
|
24
|
+
const eventBus = new EventBus({ eventsDir: join(SCALE_DIR, 'events') });
|
|
25
|
+
const store = new SQLiteArtifactStore(eventBus, {
|
|
26
|
+
dbPath: join(SCALE_DIR, 'scale.db'),
|
|
27
|
+
artifactsDir: join(SCALE_DIR, 'artifacts'),
|
|
28
|
+
});
|
|
29
|
+
const fsm = new FSM(store, eventBus);
|
|
30
|
+
registerAllFSMs(fsm);
|
|
31
|
+
const capabilityRegistry = new CapabilityRegistry(eventBus);
|
|
32
|
+
const skillRegistry = new SkillRegistry(eventBus);
|
|
33
|
+
const workflowEngine = new WorkflowEngine({ eventBus, capabilityRegistry, skillRegistry });
|
|
34
|
+
return { eventBus, store, fsm, workflowEngine, skillRegistry };
|
|
35
|
+
}
|
|
36
|
+
// Evolution extract 命令 - 从会话提取 Lessons
|
|
37
|
+
export const evolutionExtract = defineCommand({
|
|
38
|
+
meta: { name: 'extract', description: 'Extract lessons from session defects' },
|
|
39
|
+
args: {
|
|
40
|
+
'session-id': { type: 'positional', required: true, description: 'Session ID to analyze' },
|
|
41
|
+
'output': { type: 'string', alias: 'o', description: 'Output file for lessons (JSON)' },
|
|
42
|
+
'verbose': { type: 'boolean', alias: 'v', default: false, description: 'Show detailed extraction process' },
|
|
43
|
+
'min': { type: 'string', default: '2', description: 'Minimum occurrences to become lesson' },
|
|
44
|
+
json: { type: 'boolean', default: false },
|
|
45
|
+
},
|
|
46
|
+
async run({ args }) {
|
|
47
|
+
const { eventBus } = getEngine();
|
|
48
|
+
const sessionId = args['session-id'];
|
|
49
|
+
const minOccurrences = parseInt(args.min, 10) || 2;
|
|
50
|
+
const extractor = new LessonExtractor(eventBus, minOccurrences);
|
|
51
|
+
if (!args.verbose && !args.json) {
|
|
52
|
+
console.log(`Analyzing session ${sessionId} for lessons...`);
|
|
53
|
+
}
|
|
54
|
+
const candidates = await extractor.extractFromSession(sessionId);
|
|
55
|
+
if (candidates.length === 0) {
|
|
56
|
+
if (!args.json) {
|
|
57
|
+
console.log('\nNo lessons extracted from this session.');
|
|
58
|
+
console.log('This may mean:');
|
|
59
|
+
console.log(' - No defects were recorded in this session');
|
|
60
|
+
console.log(' - Patterns did not meet minimum occurrence threshold');
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (args.json) {
|
|
65
|
+
const lessons = extractor.toLessonArtifacts(candidates);
|
|
66
|
+
console.log(JSON.stringify(lessons, null, 2));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`\n=== Extracted Lessons (${candidates.length}) ===\n`);
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
console.log(`[${candidate.priority}] ${candidate.pattern}`);
|
|
72
|
+
console.log(` Solution: ${candidate.solution}`);
|
|
73
|
+
console.log(` Occurrences: ${candidate.frequency}`);
|
|
74
|
+
console.log(` Verified: ${candidate.verified ? 'Yes' : 'No (pending)'}`);
|
|
75
|
+
console.log('');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (args.output && !args.json) {
|
|
79
|
+
const lessons = extractor.toLessonArtifacts(candidates);
|
|
80
|
+
console.log(`\nTo save lessons, output to: ${args.output}`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
// Evolution improve 命令 - 运行自改进闭环
|
|
85
|
+
export const evolutionImprove = defineCommand({
|
|
86
|
+
meta: { name: 'improve', description: 'Run self-improve cycle: Defect → Lesson → Rule → Hook' },
|
|
87
|
+
args: {
|
|
88
|
+
'session-id': { type: 'positional', required: true, description: 'Session ID to process' },
|
|
89
|
+
'verbose': { type: 'boolean', alias: 'v', default: false, description: 'Show detailed improvement process' },
|
|
90
|
+
'verify-threshold': { type: 'string', default: '3', description: 'Lesson verification threshold' },
|
|
91
|
+
'rule-threshold': { type: 'string', default: '10', description: 'Rule activation threshold' },
|
|
92
|
+
'hook-threshold': { type: 'string', default: '20', description: 'Hook generation threshold' },
|
|
93
|
+
json: { type: 'boolean', default: false },
|
|
94
|
+
},
|
|
95
|
+
async run({ args }) {
|
|
96
|
+
const { eventBus } = getEngine();
|
|
97
|
+
const sessionId = args['session-id'];
|
|
98
|
+
const thresholds = {
|
|
99
|
+
lessonVerificationThreshold: parseInt(args['verify-threshold'], 10) || 3,
|
|
100
|
+
ruleActivationThreshold: parseInt(args['rule-threshold'], 10) || 10,
|
|
101
|
+
hookGenerationThreshold: parseInt(args['hook-threshold'], 10) || 20,
|
|
102
|
+
maxHooks: 10
|
|
103
|
+
};
|
|
104
|
+
const engine = new SelfImproveEngine(eventBus, thresholds);
|
|
105
|
+
if (args.verbose && !args.json) {
|
|
106
|
+
console.log('Self-improve thresholds:');
|
|
107
|
+
console.log(` Lesson → Rule: ${thresholds.lessonVerificationThreshold} verifications`);
|
|
108
|
+
console.log(` Rule → Active: ${thresholds.ruleActivationThreshold} hits`);
|
|
109
|
+
console.log(` Rule → Hook: ${thresholds.hookGenerationThreshold} hits`);
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
const state = await engine.run(sessionId);
|
|
113
|
+
if (args.json) {
|
|
114
|
+
console.log(JSON.stringify(state, null, 2));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log('\n=== Self-Improve Result ===\n');
|
|
118
|
+
console.log(`Lessons Extracted: ${state.lessonsExtracted}`);
|
|
119
|
+
console.log(`Lessons Verified: ${state.lessonsVerified}`);
|
|
120
|
+
console.log(`Rules Created: ${state.rulesCreated}`);
|
|
121
|
+
console.log(`Rules Active: ${state.rulesActive}`);
|
|
122
|
+
console.log(`Hooks Generated: ${state.hooksGenerated}`);
|
|
123
|
+
if (state.hooksGenerated > 0) {
|
|
124
|
+
console.log('\n[GENERATED HOOKS]');
|
|
125
|
+
const hooks = engine.getGeneratedHooks();
|
|
126
|
+
for (const hook of hooks) {
|
|
127
|
+
console.log(` ${hook.hookType}: ${hook.matcher}`);
|
|
128
|
+
}
|
|
129
|
+
console.log('\nTo register these hooks, add to .claude/settings.json:');
|
|
130
|
+
const hooksConfig = engine.getGeneratedHooksConfig();
|
|
131
|
+
console.log(JSON.stringify({ hooks: hooksConfig }, null, 2));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
// Evolution report 命令 - 显示自改进报告
|
|
137
|
+
export const evolutionReport = defineCommand({
|
|
138
|
+
meta: { name: 'report', description: 'Show self-improve engine report' },
|
|
139
|
+
args: {
|
|
140
|
+
'session-id': { type: 'positional', required: false, description: 'Session ID to analyze' },
|
|
141
|
+
json: { type: 'boolean', default: false },
|
|
142
|
+
},
|
|
143
|
+
async run({ args }) {
|
|
144
|
+
const { eventBus } = getEngine();
|
|
145
|
+
const sessionId = args['session-id'];
|
|
146
|
+
if (sessionId) {
|
|
147
|
+
const engine = new SelfImproveEngine(eventBus);
|
|
148
|
+
await engine.run(sessionId);
|
|
149
|
+
if (args.json) {
|
|
150
|
+
console.log(JSON.stringify({ report: engine.generateReport() }, null, 2));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(engine.generateReport());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log('No session provided. Run `scale evolution improve <session-id>` first.');
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
// Evolution rules 命令 - 管理规则
|
|
162
|
+
export const evolutionRules = defineCommand({
|
|
163
|
+
meta: { name: 'rules', description: 'List or manage rules' },
|
|
164
|
+
args: {
|
|
165
|
+
'list': { type: 'boolean', alias: 'l', default: false, description: 'List all rules' },
|
|
166
|
+
'active': { type: 'boolean', alias: 'a', default: false, description: 'List only active rules' },
|
|
167
|
+
json: { type: 'boolean', default: false },
|
|
168
|
+
},
|
|
169
|
+
async run({ args }) {
|
|
170
|
+
if (!args.json) {
|
|
171
|
+
console.log('\n=== Rules ===\n');
|
|
172
|
+
console.log('Rules are auto-generated from verified lessons.');
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log('To view rules:');
|
|
175
|
+
console.log(' ls knowledge/rules/');
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log('To create a rule:');
|
|
178
|
+
console.log(' scale evolution extract <session> --output knowledge/rules/new-rule.json');
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('To activate a rule:');
|
|
181
|
+
console.log(' scale evolution improve <session> --rule-threshold 5');
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
// Evolution verify 命令 - 手动验证 Lesson
|
|
186
|
+
export const evolutionVerify = defineCommand({
|
|
187
|
+
meta: { name: 'verify', description: 'Manually verify a lesson' },
|
|
188
|
+
args: {
|
|
189
|
+
'pattern': { type: 'positional', required: true, description: 'Lesson pattern to verify' },
|
|
190
|
+
json: { type: 'boolean', default: false },
|
|
191
|
+
},
|
|
192
|
+
async run({ args }) {
|
|
193
|
+
const pattern = args['pattern'];
|
|
194
|
+
if (!args.json) {
|
|
195
|
+
console.log(`\nVerifying lesson: "${pattern}"`);
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log('Manual verification steps:');
|
|
198
|
+
console.log(' 1. Review the pattern and solution');
|
|
199
|
+
console.log(' 2. Apply the solution in a real scenario');
|
|
200
|
+
console.log(' 3. Confirm the solution resolves the pattern');
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log('After verification, run:');
|
|
203
|
+
console.log(` scale evolution improve <session-id> --verify-threshold 1`);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
// Evolution hooks 命令 - 显示生成的 Hooks
|
|
208
|
+
export const evolutionHooks = defineCommand({
|
|
209
|
+
meta: { name: 'hooks', description: 'Show generated hooks configuration' },
|
|
210
|
+
args: {
|
|
211
|
+
'session-id': { type: 'positional', required: false, description: 'Session ID to process' },
|
|
212
|
+
'json': { type: 'boolean', alias: 'j', default: false, description: 'Output as JSON' },
|
|
213
|
+
},
|
|
214
|
+
async run({ args }) {
|
|
215
|
+
const { eventBus } = getEngine();
|
|
216
|
+
const sessionId = args['session-id'];
|
|
217
|
+
if (sessionId) {
|
|
218
|
+
const engine = new SelfImproveEngine(eventBus);
|
|
219
|
+
await engine.run(sessionId);
|
|
220
|
+
if (args.json) {
|
|
221
|
+
const hooksConfig = engine.getGeneratedHooksConfig();
|
|
222
|
+
console.log(JSON.stringify({ hooks: hooksConfig }, null, 2));
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const hooks = engine.getGeneratedHooks();
|
|
226
|
+
if (hooks.length === 0) {
|
|
227
|
+
console.log('No hooks generated.');
|
|
228
|
+
console.log('Run with a session that has enough rule hits.');
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log('\n=== Generated Hooks ===\n');
|
|
232
|
+
for (const hook of hooks) {
|
|
233
|
+
console.log(`[${hook.hookType}] Matcher: ${hook.matcher}`);
|
|
234
|
+
console.log(` Description: ${hook.description}`);
|
|
235
|
+
console.log('');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
console.log('Usage: scale evolution hooks <session-id>');
|
|
242
|
+
console.log(' --json, -j Output as JSON for .claude/settings.json');
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
//# sourceMappingURL=evolutionCommands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evolutionCommands.js","sourceRoot":"","sources":["../../src/cli/evolutionCommands.ts"],"names":[],"mappings":"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"}
|
|
@@ -97,6 +97,15 @@ export declare const phaseVerify: import("citty").CommandDef<{
|
|
|
97
97
|
type: "string";
|
|
98
98
|
description: string;
|
|
99
99
|
};
|
|
100
|
+
'tdd-evidence': {
|
|
101
|
+
type: "string";
|
|
102
|
+
description: string;
|
|
103
|
+
};
|
|
104
|
+
'tdd-strict': {
|
|
105
|
+
type: "boolean";
|
|
106
|
+
default: false;
|
|
107
|
+
description: string;
|
|
108
|
+
};
|
|
100
109
|
'skip-build': {
|
|
101
110
|
type: "boolean";
|
|
102
111
|
default: false;
|
|
@@ -46,6 +46,19 @@ function validateReviewEvidence(ids) {
|
|
|
46
46
|
}
|
|
47
47
|
return { ok: (ids?.length ?? 0) > 0 && missing.length === 0 && failed.length === 0, missing, failed };
|
|
48
48
|
}
|
|
49
|
+
function getValidatedReviewRecords(ids) {
|
|
50
|
+
const reviewStore = new ReviewStore(SCALE_DIR);
|
|
51
|
+
return (ids ?? [])
|
|
52
|
+
.map(id => reviewStore.getReview(id))
|
|
53
|
+
.filter((record) => Boolean(record?.passed));
|
|
54
|
+
}
|
|
55
|
+
function getVerificationEvidenceSummary(ids) {
|
|
56
|
+
const evidenceStore = new EvidenceStore(SCALE_DIR);
|
|
57
|
+
return (ids ?? [])
|
|
58
|
+
.map(id => evidenceStore.getGateResult(id))
|
|
59
|
+
.filter((record) => Boolean(record))
|
|
60
|
+
.map(record => ({ gate: record.gate, passed: record.passed }));
|
|
61
|
+
}
|
|
49
62
|
function getEngine() {
|
|
50
63
|
ensureDir(SCALE_DIR);
|
|
51
64
|
const eventBus = new EventBus({ eventsDir: join(SCALE_DIR, 'events') });
|
|
@@ -77,6 +90,9 @@ function isTruthyFlag(value) {
|
|
|
77
90
|
function shouldSkipCommit(value) {
|
|
78
91
|
return isTruthyFlag(value) || process.argv.includes('--no-commit') || process.argv.includes('--skip-commit');
|
|
79
92
|
}
|
|
93
|
+
function normalizeGitPath(path) {
|
|
94
|
+
return path.replace(/\\/g, '/');
|
|
95
|
+
}
|
|
80
96
|
// Helper: Generate spec markdown file
|
|
81
97
|
function generateSpecMarkdown(id, title, payload) {
|
|
82
98
|
return `# Spec: ${title}
|
|
@@ -236,15 +252,30 @@ export const phaseDefine = defineCommand({
|
|
|
236
252
|
const specPath = join(specsDir, `${spec.id}.md`);
|
|
237
253
|
writeFileSync(specPath, generateSpecMarkdown(spec.id, args.title, specPayload));
|
|
238
254
|
// FSM transitions: DRAFT -> REVIEWING -> FROZEN
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
// Phase 1: refine (DRAFT -> REVIEWING) - no guards
|
|
256
|
+
const refineResult = await fsm.canTransition(spec.id, 'refine');
|
|
257
|
+
if (!refineResult.allowed) {
|
|
258
|
+
if (!args.json) {
|
|
259
|
+
console.error('\nFSM transition blocked: DRAFT -> REVIEWING');
|
|
260
|
+
refineResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
261
|
+
}
|
|
262
|
+
process.exit(1);
|
|
242
263
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
await fsm.transition(spec.id, 'refine', { actor: { kind: 'system', component: 'phase-define' } });
|
|
265
|
+
// Phase 2: approve (REVIEWING -> FROZEN) - guards: ambiguityScore <= 0.2, has successCriteria
|
|
266
|
+
const approveResult = await fsm.canTransition(spec.id, 'approve');
|
|
267
|
+
if (!approveResult.allowed) {
|
|
268
|
+
if (!args.json) {
|
|
269
|
+
console.error('\nFSM transition blocked: REVIEWING -> FROZEN');
|
|
270
|
+
console.error(' Spec cannot be frozen due to:');
|
|
271
|
+
approveResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
272
|
+
console.error('\n Resolve issues before proceeding.');
|
|
273
|
+
}
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
await fsm.transition(spec.id, 'approve', { actor: { kind: 'system', component: 'phase-define' } });
|
|
277
|
+
if (!args.json) {
|
|
278
|
+
console.log(' FSM: DRAFT -> REVIEWING -> FROZEN ✓');
|
|
248
279
|
}
|
|
249
280
|
const result = { phase: 'DEFINE', spec, specPath, ambiguityScore, successCriteria };
|
|
250
281
|
if (args.json)
|
|
@@ -333,14 +364,20 @@ export const phasePlan = defineCommand({
|
|
|
333
364
|
ensureDir(plansDir);
|
|
334
365
|
const planPath = join(plansDir, `${plan.id}.md`);
|
|
335
366
|
writeFileSync(planPath, generatePlanMarkdown(plan.id, args['spec-id'], planPayload));
|
|
336
|
-
// FSM transition: DRAFT -> APPROVED (requires rollbackStrategy)
|
|
337
|
-
|
|
338
|
-
|
|
367
|
+
// FSM transition: DRAFT -> APPROVED (requires rollbackStrategy guard)
|
|
368
|
+
const reviewResult = await fsm.canTransition(plan.id, 'review');
|
|
369
|
+
if (!reviewResult.allowed) {
|
|
370
|
+
if (!args.json) {
|
|
371
|
+
console.error('\nFSM transition blocked: DRAFT -> APPROVED');
|
|
372
|
+
console.error(' Plan cannot be approved due to:');
|
|
373
|
+
reviewResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
374
|
+
console.error('\n Provide rollback strategy: --rollback "Revert strategy description"');
|
|
375
|
+
}
|
|
376
|
+
process.exit(1);
|
|
339
377
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
console.log(` FSM transition: ${error.message}`);
|
|
378
|
+
await fsm.transition(plan.id, 'review', { actor: { kind: 'system', component: 'phase-plan' } });
|
|
379
|
+
if (!args.json) {
|
|
380
|
+
console.log(' FSM: DRAFT -> APPROVED ✓');
|
|
344
381
|
}
|
|
345
382
|
const result = { phase: 'PLAN', plan, planPath, rollbackStrategy };
|
|
346
383
|
if (args.json)
|
|
@@ -390,20 +427,26 @@ export const phaseBuild = defineCommand({
|
|
|
390
427
|
createdBy: { kind: 'human', userId: 'cli' },
|
|
391
428
|
});
|
|
392
429
|
// FSM transitions: PENDING -> READY -> RUNNING
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
430
|
+
// Phase 1: schedule (PENDING -> READY) - no guards
|
|
431
|
+
const scheduleResult = await fsm.canTransition(task.id, 'schedule');
|
|
432
|
+
if (!scheduleResult.allowed) {
|
|
433
|
+
if (!args.json) {
|
|
434
|
+
console.error('\nFSM transition blocked: PENDING -> READY');
|
|
435
|
+
scheduleResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
436
|
+
}
|
|
437
|
+
process.exit(1);
|
|
396
438
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
439
|
+
await fsm.transition(task.id, 'schedule', { actor: { kind: 'system', component: 'phase-build' } });
|
|
440
|
+
// Phase 2: start (READY -> RUNNING) - no guards
|
|
441
|
+
await fsm.transition(task.id, 'start', { actor: { kind: 'human', userId: 'cli' } });
|
|
442
|
+
if (!args.json) {
|
|
443
|
+
console.log(' FSM: PENDING -> READY -> RUNNING ✓');
|
|
401
444
|
}
|
|
402
445
|
// Update Plan status to IMPLEMENTING
|
|
403
|
-
|
|
446
|
+
const implResult = await fsm.canTransition(args['plan-id'], 'implement');
|
|
447
|
+
if (implResult.allowed) {
|
|
404
448
|
await fsm.transition(args['plan-id'], 'implement', { actor: { kind: 'system', component: 'phase-build' } });
|
|
405
449
|
}
|
|
406
|
-
catch { }
|
|
407
450
|
const result = { phase: 'BUILD', task, status: 'RUNNING' };
|
|
408
451
|
if (args.json)
|
|
409
452
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -435,6 +478,8 @@ export const phaseVerify = defineCommand({
|
|
|
435
478
|
'lint-cmd': { type: 'string', description: 'Override lint command' },
|
|
436
479
|
'test-cmd': { type: 'string', description: 'Override test command' },
|
|
437
480
|
'coverage-cmd': { type: 'string', description: 'Override coverage command' },
|
|
481
|
+
'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
|
|
482
|
+
'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
|
|
438
483
|
'skip-build': { type: 'boolean', default: false },
|
|
439
484
|
'skip-lint': { type: 'boolean', default: false },
|
|
440
485
|
'skip-test': { type: 'boolean', default: false },
|
|
@@ -457,6 +502,8 @@ export const phaseVerify = defineCommand({
|
|
|
457
502
|
lint: args['lint-cmd'],
|
|
458
503
|
test: args['test-cmd'],
|
|
459
504
|
coverage: args['coverage-cmd'],
|
|
505
|
+
tddEvidence: args['tdd-evidence'],
|
|
506
|
+
tddStrict: isTruthyFlag(args['tdd-strict']),
|
|
460
507
|
});
|
|
461
508
|
// Step 2: Display gate results
|
|
462
509
|
if (!args.json) {
|
|
@@ -503,6 +550,7 @@ export const phaseVerify = defineCommand({
|
|
|
503
550
|
};
|
|
504
551
|
await store.update(args['task-id'], { payload: updatedPayload });
|
|
505
552
|
// Attempt FSM transition to COMPLETED
|
|
553
|
+
// Guards: build_passed, lint_passed, tests_passed
|
|
506
554
|
const allPassed = results.buildStatus === 'success' &&
|
|
507
555
|
(results.buildExitCode ?? 1) === 0 &&
|
|
508
556
|
results.lintStatus === 'success' &&
|
|
@@ -511,19 +559,26 @@ export const phaseVerify = defineCommand({
|
|
|
511
559
|
results.securityPassed === true;
|
|
512
560
|
let transitionResult = null;
|
|
513
561
|
if (allPassed) {
|
|
514
|
-
|
|
562
|
+
const completeResult = await fsm.canTransition(args['task-id'], 'complete');
|
|
563
|
+
if (!completeResult.allowed) {
|
|
564
|
+
if (!args.json) {
|
|
565
|
+
console.error('\nFSM transition blocked: RUNNING -> COMPLETED');
|
|
566
|
+
console.error(' Task cannot be completed due to:');
|
|
567
|
+
completeResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
568
|
+
}
|
|
569
|
+
// Don't exit - allow user to see what passed and fix issues
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
515
572
|
transitionResult = await fsm.transition(args['task-id'], 'complete', {
|
|
516
573
|
actor: { kind: 'human', userId: 'cli' }
|
|
517
574
|
});
|
|
518
575
|
if (!args.json)
|
|
519
|
-
console.log('\
|
|
520
|
-
}
|
|
521
|
-
catch (e) {
|
|
522
|
-
const error = e;
|
|
523
|
-
if (!args.json)
|
|
524
|
-
console.log('\nFSM transition failed:', error.message);
|
|
576
|
+
console.log('\n FSM: RUNNING -> COMPLETED ✓');
|
|
525
577
|
}
|
|
526
578
|
}
|
|
579
|
+
else if (!args.json) {
|
|
580
|
+
console.log('\n Verification requirements not met - cannot complete Task');
|
|
581
|
+
}
|
|
527
582
|
const passed = allPassed && (transitionResult?.success ?? false);
|
|
528
583
|
const result = { phase: 'VERIFY', taskId: args['task-id'], results, evidenceIds: verificationEvidenceIds, passed };
|
|
529
584
|
if (args.json)
|
|
@@ -578,7 +633,8 @@ async function reviewGitChanges(taskPayload) {
|
|
|
578
633
|
const status = await runGit(['status', '--short']);
|
|
579
634
|
const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
|
|
580
635
|
const statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
|
|
581
|
-
const
|
|
636
|
+
const verificationEvidence = getVerificationEvidenceSummary(taskPayload?.verificationEvidenceIds);
|
|
637
|
+
const changedFiles = analyzeReview({ statusOutput, diffs: [], taskPayload, verificationEvidence }).changedFiles;
|
|
582
638
|
const diffs = [];
|
|
583
639
|
for (const file of changedFiles.slice(0, 50)) {
|
|
584
640
|
if (file.status === '??') {
|
|
@@ -589,7 +645,50 @@ async function reviewGitChanges(taskPayload) {
|
|
|
589
645
|
diffs.push({ file: file.path, text: diff.stdout });
|
|
590
646
|
}
|
|
591
647
|
}
|
|
592
|
-
return analyzeReview({ statusOutput, diffs, taskPayload });
|
|
648
|
+
return analyzeReview({ statusOutput, diffs, taskPayload, verificationEvidence });
|
|
649
|
+
}
|
|
650
|
+
function collectReviewedFiles(records) {
|
|
651
|
+
const reviewed = new Set();
|
|
652
|
+
for (const record of records) {
|
|
653
|
+
if (!record.passed)
|
|
654
|
+
continue;
|
|
655
|
+
for (const file of record.changedFiles) {
|
|
656
|
+
if (shouldReviewFile(file))
|
|
657
|
+
reviewed.add(normalizeGitPath(file));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return reviewed;
|
|
661
|
+
}
|
|
662
|
+
async function getReviewableGitChanges() {
|
|
663
|
+
const status = await runGit(['status', '--short']);
|
|
664
|
+
const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
|
|
665
|
+
const statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
|
|
666
|
+
return parseChangedFiles(statusOutput).filter(file => shouldReviewFile(file.path));
|
|
667
|
+
}
|
|
668
|
+
async function stageReviewedFiles(reviewRecords) {
|
|
669
|
+
const reviewedFiles = collectReviewedFiles(reviewRecords);
|
|
670
|
+
const currentChanges = await getReviewableGitChanges();
|
|
671
|
+
const stagedFiles = [];
|
|
672
|
+
const unreviewedFiles = [];
|
|
673
|
+
for (const file of currentChanges) {
|
|
674
|
+
const normalizedPath = normalizeGitPath(file.path);
|
|
675
|
+
if (reviewedFiles.has(normalizedPath)) {
|
|
676
|
+
stagedFiles.push(file.path);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
unreviewedFiles.push(file.path);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (unreviewedFiles.length > 0) {
|
|
683
|
+
return { stagedFiles: [], unreviewedFiles };
|
|
684
|
+
}
|
|
685
|
+
if (stagedFiles.length > 0) {
|
|
686
|
+
const gitAdd = await runGit(['add', '--', ...stagedFiles]);
|
|
687
|
+
if (gitAdd.exitCode !== 0) {
|
|
688
|
+
throw new Error(gitAdd.stderr || 'git add failed');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { stagedFiles, unreviewedFiles: [] };
|
|
593
692
|
}
|
|
594
693
|
// REVIEW Phase - KarpathyEvaluator + deterministic review evidence
|
|
595
694
|
export const phaseReview = defineCommand({
|
|
@@ -720,18 +819,17 @@ export const phaseShip = defineCommand({
|
|
|
720
819
|
}
|
|
721
820
|
process.exit(1);
|
|
722
821
|
}
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
catch (e) {
|
|
730
|
-
const error = e;
|
|
731
|
-
console.error('\nFSM transition failed:', error.message);
|
|
822
|
+
// FSM transition with guard check
|
|
823
|
+
const completeResult = await fsm.canTransition(args['task-id'], 'complete');
|
|
824
|
+
if (!completeResult.allowed) {
|
|
825
|
+
console.error('\nFSM transition blocked: RUNNING -> COMPLETED');
|
|
826
|
+
completeResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
|
|
732
827
|
console.log('\n Run verification first: scale verify ' + args['task-id'] + '\n');
|
|
733
828
|
process.exit(1);
|
|
734
829
|
}
|
|
830
|
+
await fsm.transition(args['task-id'], 'complete', {
|
|
831
|
+
actor: { kind: 'human', userId: 'cli' }
|
|
832
|
+
});
|
|
735
833
|
}
|
|
736
834
|
if (!reviewPassed) {
|
|
737
835
|
console.error('\nTask not reviewed with persisted passing evidence. Run: scale review ' + args['task-id'] + '\n');
|
|
@@ -745,18 +843,38 @@ export const phaseShip = defineCommand({
|
|
|
745
843
|
}
|
|
746
844
|
// Git operations
|
|
747
845
|
let commitHash = null;
|
|
846
|
+
let stagedFiles = [];
|
|
748
847
|
if (!shouldSkipCommit(args['skip-commit'])) {
|
|
749
|
-
const { execa } = await import('execa');
|
|
750
848
|
const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
|
|
751
849
|
try {
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
850
|
+
const reviewRecords = getValidatedReviewRecords(payload.reviewEvidenceIds);
|
|
851
|
+
const stageResult = await stageReviewedFiles(reviewRecords);
|
|
852
|
+
if (stageResult.unreviewedFiles.length > 0) {
|
|
853
|
+
console.error('\nUnreviewed working tree changes detected. Re-run scale review before shipping.');
|
|
854
|
+
stageResult.unreviewedFiles.forEach(file => console.error(' - ' + file));
|
|
855
|
+
console.error('\nUse scale ship ' + args['task-id'] + ' --no-commit to generate the delivery report without committing.\n');
|
|
856
|
+
process.exit(1);
|
|
857
|
+
}
|
|
858
|
+
stagedFiles = stageResult.stagedFiles;
|
|
859
|
+
const result = await runGit(['commit', '-m', commitMessage]);
|
|
860
|
+
if (result.exitCode !== 0) {
|
|
861
|
+
const message = result.stderr || result.stdout || 'git commit failed';
|
|
862
|
+
if (/nothing to commit|no changes added/i.test(message)) {
|
|
863
|
+
if (!args.json)
|
|
864
|
+
console.log(' Git commit skipped: nothing to commit');
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
throw new Error(message);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
commitHash = result.stdout.split('\n')[0]; // First line contains hash
|
|
872
|
+
}
|
|
755
873
|
}
|
|
756
874
|
catch (e) {
|
|
757
875
|
const error = e;
|
|
758
|
-
|
|
759
|
-
|
|
876
|
+
console.error('\nGit commit failed:', error.message);
|
|
877
|
+
process.exit(1);
|
|
760
878
|
}
|
|
761
879
|
}
|
|
762
880
|
// Update Plan to DONE if Task completed
|
|
@@ -777,6 +895,8 @@ export const phaseShip = defineCommand({
|
|
|
777
895
|
console.log(` - Status: COMPLETED`);
|
|
778
896
|
if (commitHash)
|
|
779
897
|
console.log(` - Commit: ${commitHash}`);
|
|
898
|
+
if (stagedFiles.length)
|
|
899
|
+
console.log(` - Files committed: ${stagedFiles.length}`);
|
|
780
900
|
console.log('');
|
|
781
901
|
console.log(`[VERIFIED]`);
|
|
782
902
|
console.log(' [PASS] Build: passed');
|
|
@@ -811,6 +931,7 @@ export const phaseShip = defineCommand({
|
|
|
811
931
|
reviewEvidenceIds: payload.reviewEvidenceIds ?? [],
|
|
812
932
|
reviewValidation,
|
|
813
933
|
commitHash,
|
|
934
|
+
stagedFiles,
|
|
814
935
|
};
|
|
815
936
|
if (args.json)
|
|
816
937
|
console.log(JSON.stringify(result, null, 2));
|