@hongmaple0820/scale-engine 0.9.0 → 0.10.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 +18 -1
- package/README.md +22 -2
- package/dist/agents/AgentSourceLoader.js +1 -1
- package/dist/agents/AgentSourceLoader.js.map +1 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/api/cli.js +222 -6
- package/dist/api/cli.js.map +1 -1
- package/dist/api/quickstart.d.ts +23 -0
- package/dist/api/quickstart.js +57 -0
- package/dist/api/quickstart.js.map +1 -0
- package/dist/artifact/types.d.ts +5 -1
- package/dist/artifact/types.js.map +1 -1
- package/dist/capabilities/BrowserCapability.d.ts +30 -0
- package/dist/capabilities/BrowserCapability.js +73 -0
- package/dist/capabilities/BrowserCapability.js.map +1 -0
- package/dist/capabilities/CapabilityRegistry.d.ts +17 -0
- package/dist/capabilities/CapabilityRegistry.js +65 -0
- package/dist/capabilities/CapabilityRegistry.js.map +1 -0
- package/dist/capabilities/ComputerCapability.d.ts +28 -0
- package/dist/capabilities/ComputerCapability.js +40 -0
- package/dist/capabilities/ComputerCapability.js.map +1 -0
- package/dist/capabilities/InstalledSkillsIntegration.d.ts +66 -0
- package/dist/capabilities/InstalledSkillsIntegration.js +188 -0
- package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -0
- package/dist/capabilities/SearchCapability.d.ts +46 -0
- package/dist/capabilities/SearchCapability.js +88 -0
- package/dist/capabilities/SearchCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +6 -0
- package/dist/capabilities/index.js +9 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/types.d.ts +92 -0
- package/dist/capabilities/types.js +7 -0
- package/dist/capabilities/types.js.map +1 -0
- package/dist/cli/liteCommands.js +1 -1
- package/dist/cli/liteCommands.js.map +1 -1
- package/dist/cli/phaseCommands.d.ts +33 -3
- package/dist/cli/phaseCommands.js +398 -144
- package/dist/cli/phaseCommands.js.map +1 -1
- package/dist/core/logger.js +9 -2
- package/dist/core/logger.js.map +1 -1
- package/dist/hooks/HookGeneratorEnhanced.js +84 -5
- package/dist/hooks/HookGeneratorEnhanced.js.map +1 -1
- package/dist/hooks/WorkflowHooksManager.d.ts +30 -0
- package/dist/hooks/WorkflowHooksManager.js +117 -0
- package/dist/hooks/WorkflowHooksManager.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/skills/SkillExecutor.d.ts +11 -1
- package/dist/skills/SkillExecutor.js +160 -5
- package/dist/skills/SkillExecutor.js.map +1 -1
- package/dist/workflow/EvidenceStore.d.ts +20 -0
- package/dist/workflow/EvidenceStore.js +48 -0
- package/dist/workflow/EvidenceStore.js.map +1 -0
- package/dist/workflow/ReviewAnalyzer.d.ts +28 -0
- package/dist/workflow/ReviewAnalyzer.js +80 -0
- package/dist/workflow/ReviewAnalyzer.js.map +1 -0
- package/dist/workflow/ReviewStore.d.ts +32 -0
- package/dist/workflow/ReviewStore.js +42 -0
- package/dist/workflow/ReviewStore.js.map +1 -0
- package/dist/workflow/VerificationCommands.d.ts +19 -0
- package/dist/workflow/VerificationCommands.js +123 -0
- package/dist/workflow/VerificationCommands.js.map +1 -0
- package/dist/workflow/WorkflowEngine.d.ts +62 -0
- package/dist/workflow/WorkflowEngine.js +151 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -0
- package/dist/workflow/cognitive/AmbiguityScorer.d.ts +17 -0
- package/dist/workflow/cognitive/AmbiguityScorer.js +107 -0
- package/dist/workflow/cognitive/AmbiguityScorer.js.map +1 -0
- package/dist/workflow/cognitive/ConsensusPlanner.d.ts +26 -0
- package/dist/workflow/cognitive/ConsensusPlanner.js +141 -0
- package/dist/workflow/cognitive/ConsensusPlanner.js.map +1 -0
- package/dist/workflow/cognitive/SocraticQuestioner.d.ts +33 -0
- package/dist/workflow/cognitive/SocraticQuestioner.js +276 -0
- package/dist/workflow/cognitive/SocraticQuestioner.js.map +1 -0
- package/dist/workflow/execution/RalphEngine.d.ts +36 -0
- package/dist/workflow/execution/RalphEngine.js +123 -0
- package/dist/workflow/execution/RalphEngine.js.map +1 -0
- package/dist/workflow/execution/UltraworkEngine.d.ts +43 -0
- package/dist/workflow/execution/UltraworkEngine.js +135 -0
- package/dist/workflow/execution/UltraworkEngine.js.map +1 -0
- package/dist/workflow/gates/GateSystem.d.ts +104 -0
- package/dist/workflow/gates/GateSystem.js +579 -0
- package/dist/workflow/gates/GateSystem.js.map +1 -0
- package/dist/workflow/index.d.ts +12 -0
- package/dist/workflow/index.js +14 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/quality/HonestDelivery.d.ts +19 -0
- package/dist/workflow/quality/HonestDelivery.js +77 -0
- package/dist/workflow/quality/HonestDelivery.js.map +1 -0
- package/dist/workflow/quality/KarpathyEvaluator.d.ts +18 -0
- package/dist/workflow/quality/KarpathyEvaluator.js +76 -0
- package/dist/workflow/quality/KarpathyEvaluator.js.map +1 -0
- package/dist/workflow/types.d.ts +139 -0
- package/dist/workflow/types.js +4 -0
- package/dist/workflow/types.js.map +1 -0
- package/dist/workflows/DAGBuilder.js +1 -1
- package/dist/workflows/DAGBuilder.js.map +1 -1
- package/dist/workflows/WorkflowOrchestrator.js +1 -1
- package/dist/workflows/WorkflowOrchestrator.js.map +1 -1
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,14 +1,51 @@
|
|
|
1
|
-
// SCALE Engine
|
|
2
|
-
// 6
|
|
1
|
+
// SCALE Engine - Phase-Aligned Commands (v0.10.0)
|
|
2
|
+
// 6 phase commands: DEFINE -> PLAN -> BUILD -> VERIFY -> REVIEW -> SHIP
|
|
3
|
+
// Integrates WorkflowEngine cognitive scaffolding and quality gates.
|
|
3
4
|
import { defineCommand } from 'citty';
|
|
4
5
|
// Engine singleton (reuse from cli.ts)
|
|
5
6
|
import { EventBus } from '../core/eventBus.js';
|
|
6
7
|
import { SQLiteArtifactStore } from '../artifact/sqliteStore.js';
|
|
7
8
|
import { FSM } from '../artifact/fsm.js';
|
|
8
9
|
import { registerAllFSMs } from '../artifact/fsmDefinitions.js';
|
|
10
|
+
import { CapabilityRegistry } from '../capabilities/CapabilityRegistry.js';
|
|
11
|
+
import { SkillRegistry } from '../skills/SkillRegistry.js';
|
|
12
|
+
import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
|
|
13
|
+
import { EvidenceStore } from '../workflow/EvidenceStore.js';
|
|
14
|
+
import { ReviewStore } from '../workflow/ReviewStore.js';
|
|
15
|
+
import { analyzeReview, parseChangedFiles, shouldReviewFile, summarizeFindings } from '../workflow/ReviewAnalyzer.js';
|
|
9
16
|
import { join } from 'node:path';
|
|
10
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
11
18
|
const SCALE_DIR = process.env.SCALE_DIR ?? '.scale';
|
|
19
|
+
function validateVerificationEvidence(ids) {
|
|
20
|
+
const evidenceStore = new EvidenceStore(SCALE_DIR);
|
|
21
|
+
const missing = [];
|
|
22
|
+
const failed = [];
|
|
23
|
+
for (const id of ids ?? []) {
|
|
24
|
+
const record = evidenceStore.getGateResult(id);
|
|
25
|
+
if (!record) {
|
|
26
|
+
missing.push(id);
|
|
27
|
+
}
|
|
28
|
+
else if (!record.passed) {
|
|
29
|
+
failed.push(id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { ok: (ids?.length ?? 0) > 0 && missing.length === 0 && failed.length === 0, missing, failed };
|
|
33
|
+
}
|
|
34
|
+
function validateReviewEvidence(ids) {
|
|
35
|
+
const reviewStore = new ReviewStore(SCALE_DIR);
|
|
36
|
+
const missing = [];
|
|
37
|
+
const failed = [];
|
|
38
|
+
for (const id of ids ?? []) {
|
|
39
|
+
const record = reviewStore.getReview(id);
|
|
40
|
+
if (!record) {
|
|
41
|
+
missing.push(id);
|
|
42
|
+
}
|
|
43
|
+
else if (!record.passed) {
|
|
44
|
+
failed.push(id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { ok: (ids?.length ?? 0) > 0 && missing.length === 0 && failed.length === 0, missing, failed };
|
|
48
|
+
}
|
|
12
49
|
function getEngine() {
|
|
13
50
|
ensureDir(SCALE_DIR);
|
|
14
51
|
const eventBus = new EventBus({ eventsDir: join(SCALE_DIR, 'events') });
|
|
@@ -18,12 +55,28 @@ function getEngine() {
|
|
|
18
55
|
});
|
|
19
56
|
const fsm = new FSM(store, eventBus);
|
|
20
57
|
registerAllFSMs(fsm);
|
|
21
|
-
|
|
58
|
+
// Initialize capability registry
|
|
59
|
+
const capabilityRegistry = new CapabilityRegistry(eventBus);
|
|
60
|
+
// Initialize skill registry
|
|
61
|
+
const skillRegistry = new SkillRegistry(eventBus);
|
|
62
|
+
// Initialize workflow engine with cognitive scaffolding and quality gates.
|
|
63
|
+
const workflowEngine = new WorkflowEngine({
|
|
64
|
+
eventBus,
|
|
65
|
+
capabilityRegistry,
|
|
66
|
+
skillRegistry
|
|
67
|
+
});
|
|
68
|
+
return { eventBus, store, fsm, workflowEngine, skillRegistry };
|
|
22
69
|
}
|
|
23
70
|
function ensureDir(dir) {
|
|
24
71
|
if (!existsSync(dir))
|
|
25
72
|
mkdirSync(dir, { recursive: true });
|
|
26
73
|
}
|
|
74
|
+
function isTruthyFlag(value) {
|
|
75
|
+
return value === true || value === '' || value === 'true' || value === '1';
|
|
76
|
+
}
|
|
77
|
+
function shouldSkipCommit(value) {
|
|
78
|
+
return isTruthyFlag(value) || process.argv.includes('--no-commit') || process.argv.includes('--skip-commit');
|
|
79
|
+
}
|
|
27
80
|
// Helper: Generate spec markdown file
|
|
28
81
|
function generateSpecMarkdown(id, title, payload) {
|
|
29
82
|
return `# Spec: ${title}
|
|
@@ -63,34 +116,107 @@ function calculateAmbiguityScore(description, successCriteria) {
|
|
|
63
116
|
score -= 0.02;
|
|
64
117
|
return Math.max(0.05, score);
|
|
65
118
|
}
|
|
66
|
-
// DEFINE Phase
|
|
119
|
+
// DEFINE Phase - AmbiguityScorer + SocraticQuestioner + G1 gate
|
|
67
120
|
export const phaseDefine = defineCommand({
|
|
68
|
-
meta: { name: 'define', description: 'DEFINE: Create Spec (/spec)' },
|
|
121
|
+
meta: { name: 'define', description: 'DEFINE: Create Spec with AmbiguityScorer + SocraticQuestioner (/spec)' },
|
|
69
122
|
args: {
|
|
70
123
|
title: { type: 'positional', required: true },
|
|
71
124
|
description: { type: 'string', alias: 'd' },
|
|
72
125
|
'success-criteria': { type: 'string', alias: 'c', description: 'Comma-separated criteria' },
|
|
126
|
+
// Socratic refinement answers (optional)
|
|
127
|
+
'goal': { type: 'string', description: 'Goal answer for Socratic refinement' },
|
|
128
|
+
'constraint': { type: 'string', description: 'Constraint answer for Socratic refinement' },
|
|
129
|
+
'acceptance': { type: 'string', description: 'Acceptance criteria answer for Socratic refinement' },
|
|
130
|
+
'context': { type: 'string', description: 'Context answer for Socratic refinement' },
|
|
131
|
+
'risk': { type: 'string', description: 'Risk answer for Socratic refinement' },
|
|
132
|
+
'priority': { type: 'string', description: 'Priority answer for Socratic refinement' },
|
|
73
133
|
json: { type: 'boolean', default: false },
|
|
74
134
|
},
|
|
75
135
|
async run({ args }) {
|
|
76
|
-
const { store, fsm } = getEngine();
|
|
136
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
77
137
|
const desc = args.description ?? args.title;
|
|
78
138
|
// Parse success criteria
|
|
79
139
|
const successCriteria = args['success-criteria']
|
|
80
140
|
? args['success-criteria'].split(',').map(s => s.trim()).filter(s => s)
|
|
81
141
|
: ['Feature works as described', 'No regression in existing functionality'];
|
|
82
|
-
//
|
|
83
|
-
|
|
142
|
+
// === WorkflowEngine Integration ===
|
|
143
|
+
// Step 1: Explore with AmbiguityScorer + SocraticQuestioner
|
|
144
|
+
const exploreResult = await workflowEngine.explore(desc);
|
|
145
|
+
const ambiguityResult = workflowEngine.getAmbiguityScorer().analyzeRequirement(desc);
|
|
146
|
+
// Step 2: Check if requirement needs refinement.
|
|
147
|
+
if (ambiguityResult.blocked) {
|
|
148
|
+
console.error('\nRequirement ambiguity is too high (>40%); refine the requirement first.');
|
|
149
|
+
console.log('\n Refine the requirement by answering:');
|
|
150
|
+
console.log(' - What is the goal?');
|
|
151
|
+
console.log(' - What are the input/output boundaries?');
|
|
152
|
+
console.log(' - What are the acceptance criteria?\n');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
// Step 3: Handle Socratic refinement if ambiguity > 20%
|
|
156
|
+
let refinedRequirement = desc;
|
|
157
|
+
let finalAmbiguityScore = ambiguityResult.totalScore;
|
|
158
|
+
if (ambiguityResult.requiresQuestioning && exploreResult.socraticSession) {
|
|
159
|
+
const session = exploreResult.socraticSession;
|
|
160
|
+
if (!args.json) {
|
|
161
|
+
console.log('\nRequirement ambiguity is >20%; starting Socratic refinement.');
|
|
162
|
+
console.log('\nSix-question refinement framework:');
|
|
163
|
+
console.log(workflowEngine.getSocraticQuestioner().formatSessionReport(session));
|
|
164
|
+
}
|
|
165
|
+
// Check if user provided answers via CLI args
|
|
166
|
+
const answers = [];
|
|
167
|
+
if (args.goal)
|
|
168
|
+
answers.push({ questionId: 'q-goal', answer: args.goal });
|
|
169
|
+
if (args.constraint)
|
|
170
|
+
answers.push({ questionId: 'q-constraint', answer: args.constraint });
|
|
171
|
+
if (args.acceptance)
|
|
172
|
+
answers.push({ questionId: 'q-acceptance', answer: args.acceptance });
|
|
173
|
+
if (args.context)
|
|
174
|
+
answers.push({ questionId: 'q-context', answer: args.context });
|
|
175
|
+
if (args.risk)
|
|
176
|
+
answers.push({ questionId: 'q-risk', answer: args.risk });
|
|
177
|
+
if (args.priority)
|
|
178
|
+
answers.push({ questionId: 'q-priority', answer: args.priority });
|
|
179
|
+
// If answers provided, process them
|
|
180
|
+
if (answers.length > 0) {
|
|
181
|
+
for (const { questionId, answer } of answers) {
|
|
182
|
+
workflowEngine.getSocraticQuestioner().recordAnswer(session.sessionId, questionId, answer);
|
|
183
|
+
}
|
|
184
|
+
const progress = workflowEngine.getSocraticQuestioner().evaluateProgress(session);
|
|
185
|
+
if (progress.refined) {
|
|
186
|
+
refinedRequirement = workflowEngine.getSocraticQuestioner().generateRefinedRequirement(session);
|
|
187
|
+
finalAmbiguityScore = progress.newAmbiguity;
|
|
188
|
+
if (!args.json) {
|
|
189
|
+
console.log('\nRequirement refined; ambiguity reduced to: ' + finalAmbiguityScore.toFixed(2));
|
|
190
|
+
console.log('\nRefined requirement:');
|
|
191
|
+
console.log(refinedRequirement);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (!args.json) {
|
|
195
|
+
console.log('\nMore answers are needed to refine the requirement.');
|
|
196
|
+
console.log(' Current ambiguity: ' + progress.newAmbiguity.toFixed(2));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (!args.json) {
|
|
200
|
+
console.log('\nYou can refine the requirement with:');
|
|
201
|
+
console.log(' --goal "goal description"');
|
|
202
|
+
console.log(' --constraint "constraints and boundaries"');
|
|
203
|
+
console.log(' --acceptance "acceptance criteria"');
|
|
204
|
+
console.log(' --context "context and dependencies"');
|
|
205
|
+
console.log(' --risk "risk scenarios"');
|
|
206
|
+
console.log(' --priority "priority order"\n');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const ambiguityScore = finalAmbiguityScore;
|
|
84
210
|
// Create Need artifact
|
|
85
211
|
const need = await store.create({
|
|
86
212
|
type: 'Need', title: args.title,
|
|
87
|
-
payload: { rawText:
|
|
213
|
+
payload: { rawText: refinedRequirement },
|
|
88
214
|
initialStatus: 'DRAFT',
|
|
89
215
|
createdBy: { kind: 'human', userId: 'cli' },
|
|
90
216
|
});
|
|
91
|
-
// Create Spec artifact with proper payload
|
|
217
|
+
// Create Spec artifact with proper payload (use refined requirement if available)
|
|
92
218
|
const specPayload = {
|
|
93
|
-
what:
|
|
219
|
+
what: refinedRequirement,
|
|
94
220
|
successCriteria,
|
|
95
221
|
outOfScope: [],
|
|
96
222
|
edgeCases: [],
|
|
@@ -118,13 +244,13 @@ export const phaseDefine = defineCommand({
|
|
|
118
244
|
// Guard may fail - report reason
|
|
119
245
|
const error = e;
|
|
120
246
|
if (!args.json)
|
|
121
|
-
console.log(`
|
|
247
|
+
console.log(` FSM transition: ${error.message}`);
|
|
122
248
|
}
|
|
123
249
|
const result = { phase: 'DEFINE', spec, specPath, ambiguityScore, successCriteria };
|
|
124
250
|
if (args.json)
|
|
125
251
|
console.log(JSON.stringify(result, null, 2));
|
|
126
252
|
else {
|
|
127
|
-
console.log(`\
|
|
253
|
+
console.log(`\nDEFINE: ${spec.id}`);
|
|
128
254
|
console.log(` Spec file: ${specPath}`);
|
|
129
255
|
console.log(` Ambiguity score: ${ambiguityScore.toFixed(2)}`);
|
|
130
256
|
console.log(` Success criteria: ${successCriteria.length}`);
|
|
@@ -158,9 +284,9 @@ ${payload.estimatedComplexity ?? 5}/10
|
|
|
158
284
|
*Generated by SCALE Engine PLAN phase*
|
|
159
285
|
`;
|
|
160
286
|
}
|
|
161
|
-
// PLAN Phase
|
|
287
|
+
// PLAN Phase - ConsensusPlanner + G2 gate
|
|
162
288
|
export const phasePlan = defineCommand({
|
|
163
|
-
meta: { name: 'plan', description: 'PLAN: Create Plan (/plan)' },
|
|
289
|
+
meta: { name: 'plan', description: 'PLAN: Create Plan with ConsensusPlanner (/plan)' },
|
|
164
290
|
args: {
|
|
165
291
|
'spec-id': { type: 'positional', required: true },
|
|
166
292
|
approach: { type: 'string', alias: 'a', description: 'Implementation approach' },
|
|
@@ -168,16 +294,25 @@ export const phasePlan = defineCommand({
|
|
|
168
294
|
json: { type: 'boolean', default: false },
|
|
169
295
|
},
|
|
170
296
|
async run({ args }) {
|
|
171
|
-
const { store, fsm } = getEngine();
|
|
297
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
172
298
|
// Validate spec exists
|
|
173
299
|
const spec = await store.get(args['spec-id']);
|
|
174
300
|
if (!spec || spec.type !== 'Spec') {
|
|
175
|
-
console.error(`\
|
|
301
|
+
console.error(`\nSpec not found: ${args['spec-id']}\n`);
|
|
176
302
|
process.exit(1);
|
|
177
303
|
}
|
|
304
|
+
// === WorkflowEngine Integration ===
|
|
305
|
+
// Step 1: Run ConsensusPlanner (Planner -> Architect -> Critic).
|
|
306
|
+
const specDesc = spec.payload.what;
|
|
307
|
+
const consensusResult = await workflowEngine.plan(specDesc);
|
|
308
|
+
// Step 2: Display RALPLAN-DR output
|
|
309
|
+
if (!args.json) {
|
|
310
|
+
console.log('\nConsensus Planning Result:');
|
|
311
|
+
console.log(workflowEngine.getConsensusPlanner().formatReport(consensusResult));
|
|
312
|
+
}
|
|
178
313
|
// Default rollback strategy (FSM guard requires this)
|
|
179
|
-
const rollbackStrategy = args.rollback ?? 'Revert git commits
|
|
180
|
-
const approach = args.approach ??
|
|
314
|
+
const rollbackStrategy = args.rollback ?? consensusResult.preMortem.mitigations.join('\n') ?? 'Revert git commits';
|
|
315
|
+
const approach = args.approach ?? consensusResult.viableOptions.find((o) => o.selected)?.description ?? 'Standard implementation';
|
|
181
316
|
// Create PlanPayload with rollback strategy
|
|
182
317
|
const planPayload = {
|
|
183
318
|
approach,
|
|
@@ -205,13 +340,13 @@ export const phasePlan = defineCommand({
|
|
|
205
340
|
catch (e) {
|
|
206
341
|
const error = e;
|
|
207
342
|
if (!args.json)
|
|
208
|
-
console.log(`
|
|
343
|
+
console.log(` FSM transition: ${error.message}`);
|
|
209
344
|
}
|
|
210
345
|
const result = { phase: 'PLAN', plan, planPath, rollbackStrategy };
|
|
211
346
|
if (args.json)
|
|
212
347
|
console.log(JSON.stringify(result, null, 2));
|
|
213
348
|
else {
|
|
214
|
-
console.log(`\
|
|
349
|
+
console.log(`\nPLAN: ${plan.id}`);
|
|
215
350
|
console.log(` Plan file: ${planPath}`);
|
|
216
351
|
console.log(` Rollback: ${rollbackStrategy}`);
|
|
217
352
|
console.log(`\n Next: scale build ${plan.id}\n`);
|
|
@@ -231,7 +366,7 @@ export const phaseBuild = defineCommand({
|
|
|
231
366
|
// Validate plan exists
|
|
232
367
|
const plan = await store.get(args['plan-id']);
|
|
233
368
|
if (!plan || plan.type !== 'Plan') {
|
|
234
|
-
console.error(`\
|
|
369
|
+
console.error(`\nPlan not found: ${args['plan-id']}\n`);
|
|
235
370
|
process.exit(1);
|
|
236
371
|
}
|
|
237
372
|
// Create TaskPayload
|
|
@@ -262,7 +397,7 @@ export const phaseBuild = defineCommand({
|
|
|
262
397
|
catch (e) {
|
|
263
398
|
const error = e;
|
|
264
399
|
if (!args.json)
|
|
265
|
-
console.log(`
|
|
400
|
+
console.log(` FSM transition: ${error.message}`);
|
|
266
401
|
}
|
|
267
402
|
// Update Plan status to IMPLEMENTING
|
|
268
403
|
try {
|
|
@@ -273,7 +408,7 @@ export const phaseBuild = defineCommand({
|
|
|
273
408
|
if (args.json)
|
|
274
409
|
console.log(JSON.stringify(result, null, 2));
|
|
275
410
|
else {
|
|
276
|
-
console.log(`\
|
|
411
|
+
console.log(`\nBUILD: ${task.id}`);
|
|
277
412
|
console.log(` Status: RUNNING (ready to implement)`);
|
|
278
413
|
console.log(` Description: ${taskPayload.description}`);
|
|
279
414
|
console.log(`\n Implement now, then run: scale verify ${task.id}\n`);
|
|
@@ -291,81 +426,69 @@ async function runVerificationCmd(cmd) {
|
|
|
291
426
|
child.on('close', (code) => resolve({ exitCode: code ?? 1, output }));
|
|
292
427
|
});
|
|
293
428
|
}
|
|
294
|
-
// VERIFY Phase
|
|
429
|
+
// VERIFY Phase - GateSystem quality gates
|
|
295
430
|
export const phaseVerify = defineCommand({
|
|
296
|
-
meta: { name: 'verify', description: 'VERIFY: Run
|
|
431
|
+
meta: { name: 'verify', description: 'VERIFY: Run Gates G3-G7 (/test)' },
|
|
297
432
|
args: {
|
|
298
433
|
'task-id': { type: 'positional', required: true },
|
|
299
|
-
'build-cmd': { type: 'string',
|
|
300
|
-
'lint-cmd': { type: 'string',
|
|
301
|
-
'test-cmd': { type: 'string',
|
|
434
|
+
'build-cmd': { type: 'string', description: 'Override build command' },
|
|
435
|
+
'lint-cmd': { type: 'string', description: 'Override lint command' },
|
|
436
|
+
'test-cmd': { type: 'string', description: 'Override test command' },
|
|
437
|
+
'coverage-cmd': { type: 'string', description: 'Override coverage command' },
|
|
302
438
|
'skip-build': { type: 'boolean', default: false },
|
|
303
439
|
'skip-lint': { type: 'boolean', default: false },
|
|
304
440
|
'skip-test': { type: 'boolean', default: false },
|
|
305
441
|
json: { type: 'boolean', default: false },
|
|
306
442
|
},
|
|
307
443
|
async run({ args }) {
|
|
308
|
-
const { store, fsm } = getEngine();
|
|
444
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
309
445
|
// Validate task exists
|
|
310
446
|
const task = await store.get(args['task-id']);
|
|
311
447
|
if (!task || task.type !== 'Task') {
|
|
312
|
-
console.error(`\
|
|
448
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
313
449
|
process.exit(1);
|
|
314
450
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
console.log(
|
|
332
|
-
else
|
|
333
|
-
console.log(' ❌ Build failed (exit code:', build.exitCode, ')');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// Run lint
|
|
337
|
-
if (!args['skip-lint']) {
|
|
338
|
-
if (!args.json)
|
|
339
|
-
console.log('\n🔍 Running lint...');
|
|
340
|
-
const lint = await runVerificationCmd(args['lint-cmd']);
|
|
341
|
-
results.lintStatus = lint.exitCode === 0 ? 'success' : 'failed';
|
|
342
|
-
if (!args.json) {
|
|
343
|
-
if (lint.exitCode === 0)
|
|
344
|
-
console.log(' ✅ Lint passed');
|
|
345
|
-
else
|
|
346
|
-
console.log(' ❌ Lint failed (exit code:', lint.exitCode, ')');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// Run tests
|
|
350
|
-
if (!args['skip-test']) {
|
|
351
|
-
if (!args.json)
|
|
352
|
-
console.log('\n🧪 Running tests...');
|
|
353
|
-
const test = await runVerificationCmd(args['test-cmd']);
|
|
354
|
-
results.testPassed = test.exitCode === 0;
|
|
355
|
-
// Extract coverage (Jest format)
|
|
356
|
-
const coverageMatch = test.output.match(/All files[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|\s*(\d+\.?\d*)/);
|
|
357
|
-
if (coverageMatch)
|
|
358
|
-
results.testCoverage = parseFloat(coverageMatch[1]);
|
|
359
|
-
if (!args.json) {
|
|
360
|
-
if (test.exitCode === 0) {
|
|
361
|
-
console.log(' ✅ Tests passed');
|
|
362
|
-
if (results.testCoverage)
|
|
363
|
-
console.log(' Coverage:', results.testCoverage, '%');
|
|
451
|
+
// === WorkflowEngine Integration ===
|
|
452
|
+
// Step 1: Run GateSystem G3-G7
|
|
453
|
+
if (!args.json)
|
|
454
|
+
console.log('\nRunning Quality Gates...');
|
|
455
|
+
const gateResults = await workflowEngine.verify({
|
|
456
|
+
build: args['build-cmd'],
|
|
457
|
+
lint: args['lint-cmd'],
|
|
458
|
+
test: args['test-cmd'],
|
|
459
|
+
coverage: args['coverage-cmd'],
|
|
460
|
+
});
|
|
461
|
+
// Step 2: Display gate results
|
|
462
|
+
if (!args.json) {
|
|
463
|
+
console.log('\nGate Results:');
|
|
464
|
+
for (const result of gateResults) {
|
|
465
|
+
console.log(` ${result.passed ? '[PASS]' : '[FAIL]'} ${result.gate}: ${result.evidence.slice(0, 50)}`);
|
|
466
|
+
if (result.blockers.length > 0) {
|
|
467
|
+
result.blockers.forEach((b) => console.log(` [BLOCKER] ${b.slice(0, 80)}`));
|
|
364
468
|
}
|
|
365
|
-
else
|
|
366
|
-
console.log(' ❌ Tests failed (exit code:', test.exitCode, ')');
|
|
367
469
|
}
|
|
368
470
|
}
|
|
471
|
+
// Extract results from gateResults
|
|
472
|
+
const g0Result = gateResults.find(g => g.gate === 'G0');
|
|
473
|
+
const g4Result = gateResults.find(g => g.gate === 'G4');
|
|
474
|
+
const g5Result = gateResults.find(g => g.gate === 'G5');
|
|
475
|
+
const g6Result = gateResults.find(g => g.gate === 'G6');
|
|
476
|
+
const g7Result = gateResults.find(g => g.gate === 'G7');
|
|
477
|
+
const results = {
|
|
478
|
+
buildStatus: g0Result?.passed ? 'success' : 'failed',
|
|
479
|
+
buildExitCode: g0Result?.evidenceItems?.find(item => item.kind === 'command')?.exitCode,
|
|
480
|
+
lintStatus: g4Result?.passed ? 'success' : 'failed',
|
|
481
|
+
testPassed: g5Result?.passed,
|
|
482
|
+
testCoverage: undefined,
|
|
483
|
+
securityPassed: g7Result?.passed,
|
|
484
|
+
};
|
|
485
|
+
const verificationEvidenceIds = gateResults
|
|
486
|
+
.map(g => g.evidenceRecordId)
|
|
487
|
+
.filter((id) => Boolean(id));
|
|
488
|
+
// Extract coverage from G6 evidence
|
|
489
|
+
const coverageMatch = g6Result?.evidence.match(/Coverage: (\d+\.?\d*)%/);
|
|
490
|
+
if (coverageMatch)
|
|
491
|
+
results.testCoverage = parseFloat(coverageMatch[1]);
|
|
369
492
|
// Update Task payload with verification results
|
|
370
493
|
const currentPayload = task.payload;
|
|
371
494
|
const updatedPayload = {
|
|
@@ -375,12 +498,17 @@ export const phaseVerify = defineCommand({
|
|
|
375
498
|
lintStatus: results.lintStatus,
|
|
376
499
|
testPassed: results.testPassed,
|
|
377
500
|
testCoverage: results.testCoverage,
|
|
501
|
+
verificationEvidenceIds,
|
|
502
|
+
verifiedAt: Date.now(),
|
|
378
503
|
};
|
|
379
504
|
await store.update(args['task-id'], { payload: updatedPayload });
|
|
380
505
|
// Attempt FSM transition to COMPLETED
|
|
381
506
|
const allPassed = results.buildStatus === 'success' &&
|
|
507
|
+
(results.buildExitCode ?? 1) === 0 &&
|
|
382
508
|
results.lintStatus === 'success' &&
|
|
383
|
-
results.testPassed === true
|
|
509
|
+
results.testPassed === true &&
|
|
510
|
+
(results.testCoverage ?? 0) >= 80 &&
|
|
511
|
+
results.securityPassed === true;
|
|
384
512
|
let transitionResult = null;
|
|
385
513
|
if (allPassed) {
|
|
386
514
|
try {
|
|
@@ -388,20 +516,20 @@ export const phaseVerify = defineCommand({
|
|
|
388
516
|
actor: { kind: 'human', userId: 'cli' }
|
|
389
517
|
});
|
|
390
518
|
if (!args.json)
|
|
391
|
-
console.log('\
|
|
519
|
+
console.log('\nTask marked COMPLETED');
|
|
392
520
|
}
|
|
393
521
|
catch (e) {
|
|
394
522
|
const error = e;
|
|
395
523
|
if (!args.json)
|
|
396
|
-
console.log('\
|
|
524
|
+
console.log('\nFSM transition failed:', error.message);
|
|
397
525
|
}
|
|
398
526
|
}
|
|
399
527
|
const passed = allPassed && (transitionResult?.success ?? false);
|
|
400
|
-
const result = { phase: 'VERIFY', taskId: args['task-id'], results, passed };
|
|
528
|
+
const result = { phase: 'VERIFY', taskId: args['task-id'], results, evidenceIds: verificationEvidenceIds, passed };
|
|
401
529
|
if (args.json)
|
|
402
530
|
console.log(JSON.stringify(result, null, 2));
|
|
403
531
|
else {
|
|
404
|
-
console.log(`\
|
|
532
|
+
console.log(`\nVERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
|
|
405
533
|
if (passed)
|
|
406
534
|
console.log(`\n Next: scale review\n`);
|
|
407
535
|
else
|
|
@@ -409,9 +537,63 @@ export const phaseVerify = defineCommand({
|
|
|
409
537
|
}
|
|
410
538
|
},
|
|
411
539
|
});
|
|
412
|
-
|
|
540
|
+
async function runGit(args) {
|
|
541
|
+
const { execa } = await import('execa');
|
|
542
|
+
const result = await execa('git', args, { reject: false });
|
|
543
|
+
return {
|
|
544
|
+
exitCode: result.exitCode ?? 1,
|
|
545
|
+
stdout: result.stdout ?? '',
|
|
546
|
+
stderr: result.stderr ?? '',
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function mergeUntrackedFilesIntoStatus(statusOutput, untrackedOutput) {
|
|
550
|
+
const existing = new Set(parseChangedFiles(statusOutput).map(file => file.path.replace(/\\/g, '/')));
|
|
551
|
+
const additions = untrackedOutput
|
|
552
|
+
.split('\n')
|
|
553
|
+
.map(line => line.trim())
|
|
554
|
+
.filter(Boolean)
|
|
555
|
+
.filter(path => shouldReviewFile(path))
|
|
556
|
+
.filter(path => !existing.has(path.replace(/\\/g, '/')));
|
|
557
|
+
return [statusOutput.trim(), ...additions].filter(Boolean).join('\n');
|
|
558
|
+
}
|
|
559
|
+
function readUntrackedFileAsDiff(path) {
|
|
560
|
+
try {
|
|
561
|
+
const stat = statSync(path);
|
|
562
|
+
if (!stat.isFile() || stat.size > 250_000)
|
|
563
|
+
return '';
|
|
564
|
+
const content = readFileSync(path, 'utf-8');
|
|
565
|
+
if (content.includes('\u0000'))
|
|
566
|
+
return '';
|
|
567
|
+
return content
|
|
568
|
+
.split('\n')
|
|
569
|
+
.slice(0, 2000)
|
|
570
|
+
.map(line => `+${line}`)
|
|
571
|
+
.join('\n');
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
return '';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function reviewGitChanges(taskPayload) {
|
|
578
|
+
const status = await runGit(['status', '--short']);
|
|
579
|
+
const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
|
|
580
|
+
const statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
|
|
581
|
+
const changedFiles = analyzeReview({ statusOutput, diffs: [], taskPayload }).changedFiles;
|
|
582
|
+
const diffs = [];
|
|
583
|
+
for (const file of changedFiles.slice(0, 50)) {
|
|
584
|
+
if (file.status === '??') {
|
|
585
|
+
diffs.push({ file: file.path, text: readUntrackedFileAsDiff(file.path) });
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
const diff = await runGit(['diff', '--', file.path]);
|
|
589
|
+
diffs.push({ file: file.path, text: diff.stdout });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return analyzeReview({ statusOutput, diffs, taskPayload });
|
|
593
|
+
}
|
|
594
|
+
// REVIEW Phase - KarpathyEvaluator + deterministic review evidence
|
|
413
595
|
export const phaseReview = defineCommand({
|
|
414
|
-
meta: { name: 'review', description: 'REVIEW: Code review (/review)' },
|
|
596
|
+
meta: { name: 'review', description: 'REVIEW: Code review with Karpathy Principles (/review)' },
|
|
415
597
|
args: {
|
|
416
598
|
'task-id': { type: 'positional', required: false },
|
|
417
599
|
'check-security': { type: 'boolean', default: true },
|
|
@@ -419,100 +601,123 @@ export const phaseReview = defineCommand({
|
|
|
419
601
|
json: { type: 'boolean', default: false },
|
|
420
602
|
},
|
|
421
603
|
async run({ args }) {
|
|
422
|
-
const { store } = getEngine();
|
|
604
|
+
const { store, workflowEngine } = getEngine();
|
|
605
|
+
const reviewStore = new ReviewStore(SCALE_DIR);
|
|
423
606
|
// If task-id provided, validate task exists
|
|
424
607
|
let task = null;
|
|
608
|
+
let taskPayload;
|
|
425
609
|
if (args['task-id']) {
|
|
426
610
|
task = await store.get(args['task-id']);
|
|
427
611
|
if (!task || task.type !== 'Task') {
|
|
428
|
-
console.error(`\
|
|
612
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
429
613
|
process.exit(1);
|
|
430
614
|
}
|
|
615
|
+
taskPayload = task.payload;
|
|
616
|
+
}
|
|
617
|
+
// === WorkflowEngine Integration ===
|
|
618
|
+
// Step 1: Karpathy Principles Check
|
|
619
|
+
const karpathyResult = workflowEngine.checkKarpathy({
|
|
620
|
+
hypothesesListed: true, // Would be determined from actual context
|
|
621
|
+
hasExtraFeatures: false, // Would be determined from actual context
|
|
622
|
+
changesTraceable: true, // Would be determined from actual context
|
|
623
|
+
hasVerifiableGoal: true // Would be determined from actual context
|
|
624
|
+
});
|
|
625
|
+
if (!args.json) {
|
|
626
|
+
console.log('\nKarpathy Principles Check:');
|
|
627
|
+
console.log(workflowEngine.getKarpathyEvaluator().formatReport());
|
|
628
|
+
}
|
|
629
|
+
const review = await reviewGitChanges(taskPayload);
|
|
630
|
+
const findings = review.findings;
|
|
631
|
+
const summary = summarizeFindings(findings);
|
|
632
|
+
const passed = summary.critical === 0 && summary.high === 0;
|
|
633
|
+
const record = reviewStore.saveReview({
|
|
634
|
+
taskId: args['task-id'],
|
|
635
|
+
passed,
|
|
636
|
+
findings,
|
|
637
|
+
changedFiles: review.changedFiles.map(file => file.path),
|
|
638
|
+
summary,
|
|
639
|
+
});
|
|
640
|
+
if (task && taskPayload) {
|
|
641
|
+
const updatedPayload = {
|
|
642
|
+
...taskPayload,
|
|
643
|
+
reviewPassed: passed,
|
|
644
|
+
reviewEvidenceIds: [...(taskPayload.reviewEvidenceIds ?? []), record.id],
|
|
645
|
+
reviewedAt: Date.now(),
|
|
646
|
+
};
|
|
647
|
+
await store.update(task.id, { payload: updatedPayload });
|
|
431
648
|
}
|
|
432
|
-
const findings = [];
|
|
433
|
-
// Style checklist
|
|
434
|
-
if (args['check-style']) {
|
|
435
|
-
findings.push({ category: 'style', severity: 'MEDIUM', description: 'Check function length (<50 lines)' });
|
|
436
|
-
findings.push({ category: 'style', severity: 'MEDIUM', description: 'Check file size (<800 lines)' });
|
|
437
|
-
findings.push({ category: 'style', severity: 'LOW', description: 'Check naming conventions' });
|
|
438
|
-
findings.push({ category: 'style', severity: 'LOW', description: 'Check no deep nesting (>4 levels)' });
|
|
439
|
-
}
|
|
440
|
-
// Security checklist
|
|
441
|
-
if (args['check-security']) {
|
|
442
|
-
findings.push({ category: 'security', severity: 'CRITICAL', description: 'No hardcoded secrets/credentials' });
|
|
443
|
-
findings.push({ category: 'security', severity: 'HIGH', description: 'Input validation at boundaries' });
|
|
444
|
-
findings.push({ category: 'security', severity: 'HIGH', description: 'No SQL injection (parameterized queries)' });
|
|
445
|
-
findings.push({ category: 'security', severity: 'HIGH', description: 'No XSS (sanitized output)' });
|
|
446
|
-
findings.push({ category: 'security', severity: 'MEDIUM', description: 'Error messages not leak sensitive data' });
|
|
447
|
-
}
|
|
448
|
-
// Logic checklist
|
|
449
|
-
findings.push({ category: 'logic', severity: 'HIGH', description: 'Edge cases handled' });
|
|
450
|
-
findings.push({ category: 'logic', severity: 'HIGH', description: 'Error handling explicit' });
|
|
451
|
-
findings.push({ category: 'logic', severity: 'MEDIUM', description: 'No N+1 queries' });
|
|
452
|
-
// Performance checklist
|
|
453
|
-
findings.push({ category: 'performance', severity: 'MEDIUM', description: 'No unbounded loops/queries' });
|
|
454
|
-
findings.push({ category: 'performance', severity: 'LOW', description: 'Caching where appropriate' });
|
|
455
|
-
// Count by severity
|
|
456
|
-
const critical = findings.filter(f => f.severity === 'CRITICAL').length;
|
|
457
|
-
const high = findings.filter(f => f.severity === 'HIGH').length;
|
|
458
|
-
const medium = findings.filter(f => f.severity === 'MEDIUM').length;
|
|
459
|
-
const low = findings.filter(f => f.severity === 'LOW').length;
|
|
460
|
-
const passed = critical === 0; // Only block on CRITICAL issues
|
|
461
649
|
const result = {
|
|
462
650
|
phase: 'REVIEW',
|
|
463
651
|
taskId: args['task-id'],
|
|
652
|
+
reviewId: record.id,
|
|
464
653
|
findings,
|
|
465
|
-
|
|
654
|
+
changedFiles: review.changedFiles.map(file => file.path),
|
|
655
|
+
summary,
|
|
466
656
|
passed,
|
|
467
657
|
recommendation: passed ? 'Ready to ship' : 'Fix CRITICAL issues before shipping'
|
|
468
658
|
};
|
|
469
659
|
if (args.json)
|
|
470
660
|
console.log(JSON.stringify(result, null, 2));
|
|
471
661
|
else {
|
|
472
|
-
console.log('\
|
|
473
|
-
console.log(
|
|
474
|
-
console.log('
|
|
475
|
-
console.log(
|
|
476
|
-
console.log(
|
|
477
|
-
console.log(
|
|
478
|
-
console.log(
|
|
479
|
-
console.log(
|
|
662
|
+
console.log('\nREVIEW Phase');
|
|
663
|
+
console.log(`\nReview evidence: ${record.id}`);
|
|
664
|
+
console.log('\nReview Findings:');
|
|
665
|
+
console.log('----------------------------------------');
|
|
666
|
+
console.log(`CRITICAL: ${summary.critical} issues ${summary.critical > 0 ? 'BLOCKED' : 'OK'}`);
|
|
667
|
+
console.log(`HIGH: ${summary.high} issues ${summary.high > 0 ? 'BLOCKED' : 'OK'}`);
|
|
668
|
+
console.log(`MEDIUM: ${summary.medium} issues`);
|
|
669
|
+
console.log(`LOW: ${summary.low} issues`);
|
|
670
|
+
console.log('----------------------------------------');
|
|
671
|
+
findings.slice(0, 10).forEach(f => console.log(` [${f.severity}] ${f.file ? `${f.file}: ` : ''}${f.description}`));
|
|
480
672
|
if (passed) {
|
|
481
|
-
console.log('\
|
|
673
|
+
console.log('\nReview passed (no CRITICAL issues)');
|
|
482
674
|
console.log('\n Next: scale ship ' + (args['task-id'] ?? '<task-id>') + '\n');
|
|
483
675
|
}
|
|
484
676
|
else {
|
|
485
|
-
console.log('\
|
|
677
|
+
console.log('\nReview blocked by CRITICAL issues');
|
|
486
678
|
console.log('\n Fix critical issues, then: scale review\n');
|
|
487
679
|
}
|
|
488
680
|
}
|
|
489
681
|
},
|
|
490
682
|
});
|
|
491
|
-
// SHIP Phase
|
|
683
|
+
// SHIP Phase - HonestDelivery
|
|
492
684
|
export const phaseShip = defineCommand({
|
|
493
|
-
meta: { name: 'ship', description: 'SHIP: Commit
|
|
685
|
+
meta: { name: 'ship', description: 'SHIP: Commit with HonestDelivery Report (/ship)' },
|
|
494
686
|
args: {
|
|
495
687
|
'task-id': { type: 'positional', required: true },
|
|
496
688
|
message: { type: 'string', alias: 'm', description: 'Commit message' },
|
|
497
689
|
'no-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
|
|
690
|
+
'skip-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
|
|
498
691
|
json: { type: 'boolean', default: false },
|
|
499
692
|
},
|
|
500
693
|
async run({ args }) {
|
|
501
|
-
const { store, fsm } = getEngine();
|
|
694
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
502
695
|
// Validate task exists
|
|
503
696
|
const task = await store.get(args['task-id']);
|
|
504
697
|
if (!task || task.type !== 'Task') {
|
|
505
|
-
console.error(`\
|
|
698
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
506
699
|
process.exit(1);
|
|
507
700
|
}
|
|
508
701
|
// Check if task is completed (or attempt transition)
|
|
509
702
|
const payload = task.payload;
|
|
703
|
+
const evidenceValidation = validateVerificationEvidence(payload.verificationEvidenceIds);
|
|
704
|
+
const reviewValidation = validateReviewEvidence(payload.reviewEvidenceIds);
|
|
510
705
|
const verificationPassed = payload.buildStatus === 'success' &&
|
|
706
|
+
(payload.buildExitCode ?? 1) === 0 &&
|
|
511
707
|
payload.lintStatus === 'success' &&
|
|
512
|
-
payload.testPassed === true
|
|
708
|
+
payload.testPassed === true &&
|
|
709
|
+
(payload.testCoverage ?? 0) >= 80 &&
|
|
710
|
+
evidenceValidation.ok;
|
|
711
|
+
const reviewPassed = payload.reviewPassed === true && reviewValidation.ok;
|
|
513
712
|
if (task.status !== 'COMPLETED') {
|
|
514
713
|
if (!verificationPassed) {
|
|
515
|
-
console.error('\
|
|
714
|
+
console.error('\nTask not verified with persisted evidence. Run: scale verify ' + args['task-id'] + '\n');
|
|
715
|
+
if (evidenceValidation.missing.length > 0) {
|
|
716
|
+
console.error('Missing evidence records: ' + evidenceValidation.missing.join(', '));
|
|
717
|
+
}
|
|
718
|
+
if (evidenceValidation.failed.length > 0) {
|
|
719
|
+
console.error('Failed evidence records: ' + evidenceValidation.failed.join(', '));
|
|
720
|
+
}
|
|
516
721
|
process.exit(1);
|
|
517
722
|
}
|
|
518
723
|
// Attempt FSM transition
|
|
@@ -523,14 +728,24 @@ export const phaseShip = defineCommand({
|
|
|
523
728
|
}
|
|
524
729
|
catch (e) {
|
|
525
730
|
const error = e;
|
|
526
|
-
console.error('\
|
|
731
|
+
console.error('\nFSM transition failed:', error.message);
|
|
527
732
|
console.log('\n Run verification first: scale verify ' + args['task-id'] + '\n');
|
|
528
733
|
process.exit(1);
|
|
529
734
|
}
|
|
530
735
|
}
|
|
736
|
+
if (!reviewPassed) {
|
|
737
|
+
console.error('\nTask not reviewed with persisted passing evidence. Run: scale review ' + args['task-id'] + '\n');
|
|
738
|
+
if (reviewValidation.missing.length > 0) {
|
|
739
|
+
console.error('Missing review records: ' + reviewValidation.missing.join(', '));
|
|
740
|
+
}
|
|
741
|
+
if (reviewValidation.failed.length > 0) {
|
|
742
|
+
console.error('Failed review records: ' + reviewValidation.failed.join(', '));
|
|
743
|
+
}
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
531
746
|
// Git operations
|
|
532
747
|
let commitHash = null;
|
|
533
|
-
if (!args['
|
|
748
|
+
if (!shouldSkipCommit(args['skip-commit'])) {
|
|
534
749
|
const { execa } = await import('execa');
|
|
535
750
|
const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
|
|
536
751
|
try {
|
|
@@ -541,7 +756,7 @@ export const phaseShip = defineCommand({
|
|
|
541
756
|
catch (e) {
|
|
542
757
|
const error = e;
|
|
543
758
|
if (!args.json)
|
|
544
|
-
console.log('
|
|
759
|
+
console.log(' Git commit skipped:', error.message);
|
|
545
760
|
}
|
|
546
761
|
}
|
|
547
762
|
// Update Plan to DONE if Task completed
|
|
@@ -552,20 +767,59 @@ export const phaseShip = defineCommand({
|
|
|
552
767
|
}
|
|
553
768
|
catch { }
|
|
554
769
|
}
|
|
770
|
+
// === WorkflowEngine Integration ===
|
|
771
|
+
// Generate HonestDelivery report
|
|
772
|
+
if (!args.json) {
|
|
773
|
+
console.log('\nHonest Delivery Report:');
|
|
774
|
+
console.log('-'.repeat(40));
|
|
775
|
+
console.log(`[COMPLETED]`);
|
|
776
|
+
console.log(` - Task: ${args['task-id']}`);
|
|
777
|
+
console.log(` - Status: COMPLETED`);
|
|
778
|
+
if (commitHash)
|
|
779
|
+
console.log(` - Commit: ${commitHash}`);
|
|
780
|
+
console.log('');
|
|
781
|
+
console.log(`[VERIFIED]`);
|
|
782
|
+
console.log(' [PASS] Build: passed');
|
|
783
|
+
console.log(' [PASS] Lint: passed');
|
|
784
|
+
console.log(' [PASS] Tests: passed');
|
|
785
|
+
if (payload.testCoverage)
|
|
786
|
+
console.log(` [PASS] Coverage: ${payload.testCoverage}%`);
|
|
787
|
+
if (payload.verificationEvidenceIds?.length) {
|
|
788
|
+
console.log(` [PASS] Evidence records validated: ${payload.verificationEvidenceIds.join(', ')}`);
|
|
789
|
+
}
|
|
790
|
+
if (payload.reviewEvidenceIds?.length) {
|
|
791
|
+
console.log(` [PASS] Review records validated: ${payload.reviewEvidenceIds.join(', ')}`);
|
|
792
|
+
}
|
|
793
|
+
console.log('');
|
|
794
|
+
// Check for unverified items
|
|
795
|
+
const unverifiedItems = [];
|
|
796
|
+
if (!payload.testCoverage || payload.testCoverage < 80) {
|
|
797
|
+
unverifiedItems.push('Coverage below 80%');
|
|
798
|
+
}
|
|
799
|
+
if (unverifiedItems.length > 0) {
|
|
800
|
+
console.log(`[UNVERIFIED]`);
|
|
801
|
+
unverifiedItems.forEach(item => console.log(` [UNVERIFIED] ${item}`));
|
|
802
|
+
console.log('');
|
|
803
|
+
}
|
|
804
|
+
}
|
|
555
805
|
const result = {
|
|
556
806
|
phase: 'SHIP',
|
|
557
807
|
taskId: args['task-id'],
|
|
558
808
|
status: 'COMPLETED',
|
|
809
|
+
verificationEvidenceIds: payload.verificationEvidenceIds ?? [],
|
|
810
|
+
evidenceValidation,
|
|
811
|
+
reviewEvidenceIds: payload.reviewEvidenceIds ?? [],
|
|
812
|
+
reviewValidation,
|
|
559
813
|
commitHash,
|
|
560
814
|
};
|
|
561
815
|
if (args.json)
|
|
562
816
|
console.log(JSON.stringify(result, null, 2));
|
|
563
817
|
else {
|
|
564
|
-
console.log('\
|
|
565
|
-
console.log('\
|
|
818
|
+
console.log('\nSHIP Phase');
|
|
819
|
+
console.log('\nTask COMPLETED: ' + args['task-id']);
|
|
566
820
|
if (commitHash)
|
|
567
821
|
console.log(' Commit: ' + commitHash);
|
|
568
|
-
console.log('\
|
|
822
|
+
console.log('\nDone. Feature shipped.\n');
|
|
569
823
|
}
|
|
570
824
|
},
|
|
571
825
|
});
|