@hongmaple0820/scale-engine 0.8.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 +237 -0
- package/README.md +111 -50
- package/dist/adapters/ClaudeCodeAdapter.d.ts +2 -0
- package/dist/adapters/ClaudeCodeAdapter.js +5 -0
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +1 -0
- package/dist/adapters/CodexAdapter.js +5 -0
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/GeminiAdapter.d.ts +1 -0
- package/dist/adapters/GeminiAdapter.js +4 -0
- package/dist/adapters/GeminiAdapter.js.map +1 -1
- package/dist/adapters/HermesAdapter.d.ts +1 -0
- package/dist/adapters/HermesAdapter.js +4 -0
- package/dist/adapters/HermesAdapter.js.map +1 -1
- package/dist/adapters/OpenClawAdapter.d.ts +1 -0
- package/dist/adapters/OpenClawAdapter.js +4 -0
- package/dist/adapters/OpenClawAdapter.js.map +1 -1
- package/dist/adapters/QCoderAdapter.d.ts +1 -0
- package/dist/adapters/QCoderAdapter.js +4 -0
- package/dist/adapters/QCoderAdapter.js.map +1 -1
- package/dist/adapters/TraeAdapter.d.ts +1 -0
- package/dist/adapters/TraeAdapter.js +4 -0
- package/dist/adapters/TraeAdapter.js.map +1 -1
- package/dist/adapters/VSCAdapter.d.ts +1 -0
- package/dist/adapters/VSCAdapter.js +4 -0
- package/dist/adapters/VSCAdapter.js.map +1 -1
- package/dist/adapters/WorkBuddyAdapter.d.ts +1 -0
- package/dist/adapters/WorkBuddyAdapter.js +4 -0
- package/dist/adapters/WorkBuddyAdapter.js.map +1 -1
- 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 +236 -5
- 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 +9 -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.d.ts +81 -0
- package/dist/cli/liteCommands.js +148 -0
- package/dist/cli/liteCommands.js.map +1 -0
- package/dist/cli/phaseCommands.d.ts +159 -0
- package/dist/cli/phaseCommands.js +826 -0
- package/dist/cli/phaseCommands.js.map +1 -0
- 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/SkillDiscovery.d.ts +27 -1
- package/dist/skills/SkillDiscovery.js +106 -11
- package/dist/skills/SkillDiscovery.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
|
@@ -0,0 +1,826 @@
|
|
|
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.
|
|
4
|
+
import { defineCommand } from 'citty';
|
|
5
|
+
// Engine singleton (reuse from cli.ts)
|
|
6
|
+
import { EventBus } from '../core/eventBus.js';
|
|
7
|
+
import { SQLiteArtifactStore } from '../artifact/sqliteStore.js';
|
|
8
|
+
import { FSM } from '../artifact/fsm.js';
|
|
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';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
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
|
+
}
|
|
49
|
+
function getEngine() {
|
|
50
|
+
ensureDir(SCALE_DIR);
|
|
51
|
+
const eventBus = new EventBus({ eventsDir: join(SCALE_DIR, 'events') });
|
|
52
|
+
const store = new SQLiteArtifactStore(eventBus, {
|
|
53
|
+
dbPath: join(SCALE_DIR, 'scale.db'),
|
|
54
|
+
artifactsDir: join(SCALE_DIR, 'artifacts'),
|
|
55
|
+
});
|
|
56
|
+
const fsm = new FSM(store, eventBus);
|
|
57
|
+
registerAllFSMs(fsm);
|
|
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 };
|
|
69
|
+
}
|
|
70
|
+
function ensureDir(dir) {
|
|
71
|
+
if (!existsSync(dir))
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
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
|
+
}
|
|
80
|
+
// Helper: Generate spec markdown file
|
|
81
|
+
function generateSpecMarkdown(id, title, payload) {
|
|
82
|
+
return `# Spec: ${title}
|
|
83
|
+
|
|
84
|
+
**ID**: ${id}
|
|
85
|
+
**Status**: FROZEN
|
|
86
|
+
**Ambiguity Score**: ${payload.ambiguityScore ?? 0.15}
|
|
87
|
+
|
|
88
|
+
## What
|
|
89
|
+
${payload.what}
|
|
90
|
+
|
|
91
|
+
## Success Criteria
|
|
92
|
+
${payload.successCriteria.map(c => `- [ ] ${c}`).join('\n')}
|
|
93
|
+
|
|
94
|
+
## Out of Scope
|
|
95
|
+
${payload.outOfScope.map(o => `- ${o}`).join('\n') || '(none defined)'}
|
|
96
|
+
|
|
97
|
+
## Edge Cases
|
|
98
|
+
${payload.edgeCases.map(e => `- ${e}`).join('\n') || '(none defined)'}
|
|
99
|
+
|
|
100
|
+
## North Star
|
|
101
|
+
${payload.northStar || 'User value delivered'}
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
*Generated by SCALE Engine DEFINE phase*
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
// Helper: Calculate ambiguity score
|
|
108
|
+
function calculateAmbiguityScore(description, successCriteria) {
|
|
109
|
+
let score = 0.2; // Base score (maximum threshold)
|
|
110
|
+
// Reduce score based on completeness
|
|
111
|
+
if (description.length > 50)
|
|
112
|
+
score -= 0.05;
|
|
113
|
+
if (successCriteria.length >= 2)
|
|
114
|
+
score -= 0.03;
|
|
115
|
+
if (successCriteria.length >= 3)
|
|
116
|
+
score -= 0.02;
|
|
117
|
+
return Math.max(0.05, score);
|
|
118
|
+
}
|
|
119
|
+
// DEFINE Phase - AmbiguityScorer + SocraticQuestioner + G1 gate
|
|
120
|
+
export const phaseDefine = defineCommand({
|
|
121
|
+
meta: { name: 'define', description: 'DEFINE: Create Spec with AmbiguityScorer + SocraticQuestioner (/spec)' },
|
|
122
|
+
args: {
|
|
123
|
+
title: { type: 'positional', required: true },
|
|
124
|
+
description: { type: 'string', alias: 'd' },
|
|
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' },
|
|
133
|
+
json: { type: 'boolean', default: false },
|
|
134
|
+
},
|
|
135
|
+
async run({ args }) {
|
|
136
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
137
|
+
const desc = args.description ?? args.title;
|
|
138
|
+
// Parse success criteria
|
|
139
|
+
const successCriteria = args['success-criteria']
|
|
140
|
+
? args['success-criteria'].split(',').map(s => s.trim()).filter(s => s)
|
|
141
|
+
: ['Feature works as described', 'No regression in existing functionality'];
|
|
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;
|
|
210
|
+
// Create Need artifact
|
|
211
|
+
const need = await store.create({
|
|
212
|
+
type: 'Need', title: args.title,
|
|
213
|
+
payload: { rawText: refinedRequirement },
|
|
214
|
+
initialStatus: 'DRAFT',
|
|
215
|
+
createdBy: { kind: 'human', userId: 'cli' },
|
|
216
|
+
});
|
|
217
|
+
// Create Spec artifact with proper payload (use refined requirement if available)
|
|
218
|
+
const specPayload = {
|
|
219
|
+
what: refinedRequirement,
|
|
220
|
+
successCriteria,
|
|
221
|
+
outOfScope: [],
|
|
222
|
+
edgeCases: [],
|
|
223
|
+
northStar: 'Deliver user value',
|
|
224
|
+
ambiguityScore,
|
|
225
|
+
};
|
|
226
|
+
const spec = await store.create({
|
|
227
|
+
type: 'Spec', title: args.title,
|
|
228
|
+
payload: specPayload,
|
|
229
|
+
parents: [need.id],
|
|
230
|
+
initialStatus: 'DRAFT',
|
|
231
|
+
createdBy: { kind: 'human', userId: 'cli' },
|
|
232
|
+
});
|
|
233
|
+
// Generate spec markdown file
|
|
234
|
+
const specsDir = join(SCALE_DIR, 'specs');
|
|
235
|
+
ensureDir(specsDir);
|
|
236
|
+
const specPath = join(specsDir, `${spec.id}.md`);
|
|
237
|
+
writeFileSync(specPath, generateSpecMarkdown(spec.id, args.title, specPayload));
|
|
238
|
+
// FSM transitions: DRAFT -> REVIEWING -> FROZEN
|
|
239
|
+
try {
|
|
240
|
+
await fsm.transition(spec.id, 'refine', { actor: { kind: 'system', component: 'phase-define' } });
|
|
241
|
+
await fsm.transition(spec.id, 'approve', { actor: { kind: 'system', component: 'phase-define' } });
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
// Guard may fail - report reason
|
|
245
|
+
const error = e;
|
|
246
|
+
if (!args.json)
|
|
247
|
+
console.log(` FSM transition: ${error.message}`);
|
|
248
|
+
}
|
|
249
|
+
const result = { phase: 'DEFINE', spec, specPath, ambiguityScore, successCriteria };
|
|
250
|
+
if (args.json)
|
|
251
|
+
console.log(JSON.stringify(result, null, 2));
|
|
252
|
+
else {
|
|
253
|
+
console.log(`\nDEFINE: ${spec.id}`);
|
|
254
|
+
console.log(` Spec file: ${specPath}`);
|
|
255
|
+
console.log(` Ambiguity score: ${ambiguityScore.toFixed(2)}`);
|
|
256
|
+
console.log(` Success criteria: ${successCriteria.length}`);
|
|
257
|
+
console.log(`\n Next: scale plan ${spec.id}\n`);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
// Helper: Generate plan markdown file
|
|
262
|
+
function generatePlanMarkdown(id, specId, payload) {
|
|
263
|
+
return `# Plan: ${id}
|
|
264
|
+
|
|
265
|
+
**Spec**: ${specId}
|
|
266
|
+
**Status**: APPROVED
|
|
267
|
+
|
|
268
|
+
## Approach
|
|
269
|
+
${payload.approach}
|
|
270
|
+
|
|
271
|
+
## Tech Choices
|
|
272
|
+
${payload.techChoices.map(t => `- **${t.decision}**: ${t.rationale}`).join('\n') || '(to be defined)'}
|
|
273
|
+
|
|
274
|
+
## Modules
|
|
275
|
+
${payload.modules.map(m => `- ${m.action} \`${m.path}\`: ${m.reason}`).join('\n') || '(to be defined)'}
|
|
276
|
+
|
|
277
|
+
## Rollback Strategy
|
|
278
|
+
${payload.rollbackStrategy}
|
|
279
|
+
|
|
280
|
+
## Estimated Complexity
|
|
281
|
+
${payload.estimatedComplexity ?? 5}/10
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
*Generated by SCALE Engine PLAN phase*
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
// PLAN Phase - ConsensusPlanner + G2 gate
|
|
288
|
+
export const phasePlan = defineCommand({
|
|
289
|
+
meta: { name: 'plan', description: 'PLAN: Create Plan with ConsensusPlanner (/plan)' },
|
|
290
|
+
args: {
|
|
291
|
+
'spec-id': { type: 'positional', required: true },
|
|
292
|
+
approach: { type: 'string', alias: 'a', description: 'Implementation approach' },
|
|
293
|
+
'rollback': { type: 'string', alias: 'r', description: 'Rollback strategy (required for FSM)' },
|
|
294
|
+
json: { type: 'boolean', default: false },
|
|
295
|
+
},
|
|
296
|
+
async run({ args }) {
|
|
297
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
298
|
+
// Validate spec exists
|
|
299
|
+
const spec = await store.get(args['spec-id']);
|
|
300
|
+
if (!spec || spec.type !== 'Spec') {
|
|
301
|
+
console.error(`\nSpec not found: ${args['spec-id']}\n`);
|
|
302
|
+
process.exit(1);
|
|
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
|
+
}
|
|
313
|
+
// Default rollback strategy (FSM guard requires this)
|
|
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';
|
|
316
|
+
// Create PlanPayload with rollback strategy
|
|
317
|
+
const planPayload = {
|
|
318
|
+
approach,
|
|
319
|
+
techChoices: [],
|
|
320
|
+
modules: [],
|
|
321
|
+
rollbackStrategy,
|
|
322
|
+
estimatedComplexity: 5,
|
|
323
|
+
};
|
|
324
|
+
const plan = await store.create({
|
|
325
|
+
type: 'Plan', title: `Plan for ${spec.title}`,
|
|
326
|
+
payload: planPayload,
|
|
327
|
+
parents: [args['spec-id']],
|
|
328
|
+
initialStatus: 'DRAFT',
|
|
329
|
+
createdBy: { kind: 'human', userId: 'cli' },
|
|
330
|
+
});
|
|
331
|
+
// Generate plan markdown file
|
|
332
|
+
const plansDir = join(SCALE_DIR, 'plans');
|
|
333
|
+
ensureDir(plansDir);
|
|
334
|
+
const planPath = join(plansDir, `${plan.id}.md`);
|
|
335
|
+
writeFileSync(planPath, generatePlanMarkdown(plan.id, args['spec-id'], planPayload));
|
|
336
|
+
// FSM transition: DRAFT -> APPROVED (requires rollbackStrategy)
|
|
337
|
+
try {
|
|
338
|
+
await fsm.transition(plan.id, 'review', { actor: { kind: 'system', component: 'phase-plan' } });
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
const error = e;
|
|
342
|
+
if (!args.json)
|
|
343
|
+
console.log(` FSM transition: ${error.message}`);
|
|
344
|
+
}
|
|
345
|
+
const result = { phase: 'PLAN', plan, planPath, rollbackStrategy };
|
|
346
|
+
if (args.json)
|
|
347
|
+
console.log(JSON.stringify(result, null, 2));
|
|
348
|
+
else {
|
|
349
|
+
console.log(`\nPLAN: ${plan.id}`);
|
|
350
|
+
console.log(` Plan file: ${planPath}`);
|
|
351
|
+
console.log(` Rollback: ${rollbackStrategy}`);
|
|
352
|
+
console.log(`\n Next: scale build ${plan.id}\n`);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
// BUILD Phase
|
|
357
|
+
export const phaseBuild = defineCommand({
|
|
358
|
+
meta: { name: 'build', description: 'BUILD: Create Task (/build)' },
|
|
359
|
+
args: {
|
|
360
|
+
'plan-id': { type: 'positional', required: true },
|
|
361
|
+
description: { type: 'string', alias: 'd', description: 'Task description' },
|
|
362
|
+
json: { type: 'boolean', default: false },
|
|
363
|
+
},
|
|
364
|
+
async run({ args }) {
|
|
365
|
+
const { store, fsm } = getEngine();
|
|
366
|
+
// Validate plan exists
|
|
367
|
+
const plan = await store.get(args['plan-id']);
|
|
368
|
+
if (!plan || plan.type !== 'Plan') {
|
|
369
|
+
console.error(`\nPlan not found: ${args['plan-id']}\n`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
// Create TaskPayload
|
|
373
|
+
const taskPayload = {
|
|
374
|
+
description: args.description ?? `Implement ${plan.title}`,
|
|
375
|
+
filesInvolved: [],
|
|
376
|
+
dependsOn: [],
|
|
377
|
+
requiredRole: 'implementer',
|
|
378
|
+
requiredCapabilities: ['code-generation', 'file-editing'],
|
|
379
|
+
// Initialize quality metrics (FSM guards require these for completion)
|
|
380
|
+
buildStatus: 'pending',
|
|
381
|
+
lintStatus: 'pending',
|
|
382
|
+
testPassed: undefined,
|
|
383
|
+
testCoverage: undefined,
|
|
384
|
+
};
|
|
385
|
+
const task = await store.create({
|
|
386
|
+
type: 'Task', title: `Task for ${plan.title}`,
|
|
387
|
+
payload: taskPayload,
|
|
388
|
+
parents: [args['plan-id']],
|
|
389
|
+
initialStatus: 'PENDING',
|
|
390
|
+
createdBy: { kind: 'human', userId: 'cli' },
|
|
391
|
+
});
|
|
392
|
+
// FSM transitions: PENDING -> READY -> RUNNING
|
|
393
|
+
try {
|
|
394
|
+
await fsm.transition(task.id, 'schedule', { actor: { kind: 'system', component: 'phase-build' } });
|
|
395
|
+
await fsm.transition(task.id, 'start', { actor: { kind: 'human', userId: 'cli' } });
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
const error = e;
|
|
399
|
+
if (!args.json)
|
|
400
|
+
console.log(` FSM transition: ${error.message}`);
|
|
401
|
+
}
|
|
402
|
+
// Update Plan status to IMPLEMENTING
|
|
403
|
+
try {
|
|
404
|
+
await fsm.transition(args['plan-id'], 'implement', { actor: { kind: 'system', component: 'phase-build' } });
|
|
405
|
+
}
|
|
406
|
+
catch { }
|
|
407
|
+
const result = { phase: 'BUILD', task, status: 'RUNNING' };
|
|
408
|
+
if (args.json)
|
|
409
|
+
console.log(JSON.stringify(result, null, 2));
|
|
410
|
+
else {
|
|
411
|
+
console.log(`\nBUILD: ${task.id}`);
|
|
412
|
+
console.log(` Status: RUNNING (ready to implement)`);
|
|
413
|
+
console.log(` Description: ${taskPayload.description}`);
|
|
414
|
+
console.log(`\n Implement now, then run: scale verify ${task.id}\n`);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
// Helper: Run command and capture result (from verify-task)
|
|
419
|
+
async function runVerificationCmd(cmd) {
|
|
420
|
+
const { spawn } = await import('node:child_process');
|
|
421
|
+
return new Promise((resolve) => {
|
|
422
|
+
const child = spawn(cmd, [], { shell: true, stdio: 'pipe' });
|
|
423
|
+
let output = '';
|
|
424
|
+
child.stdout?.on('data', (d) => (output += d.toString()));
|
|
425
|
+
child.stderr?.on('data', (d) => (output += d.toString()));
|
|
426
|
+
child.on('close', (code) => resolve({ exitCode: code ?? 1, output }));
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
// VERIFY Phase - GateSystem quality gates
|
|
430
|
+
export const phaseVerify = defineCommand({
|
|
431
|
+
meta: { name: 'verify', description: 'VERIFY: Run Gates G3-G7 (/test)' },
|
|
432
|
+
args: {
|
|
433
|
+
'task-id': { type: 'positional', required: true },
|
|
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' },
|
|
438
|
+
'skip-build': { type: 'boolean', default: false },
|
|
439
|
+
'skip-lint': { type: 'boolean', default: false },
|
|
440
|
+
'skip-test': { type: 'boolean', default: false },
|
|
441
|
+
json: { type: 'boolean', default: false },
|
|
442
|
+
},
|
|
443
|
+
async run({ args }) {
|
|
444
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
445
|
+
// Validate task exists
|
|
446
|
+
const task = await store.get(args['task-id']);
|
|
447
|
+
if (!task || task.type !== 'Task') {
|
|
448
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
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)}`));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
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]);
|
|
492
|
+
// Update Task payload with verification results
|
|
493
|
+
const currentPayload = task.payload;
|
|
494
|
+
const updatedPayload = {
|
|
495
|
+
...currentPayload,
|
|
496
|
+
buildStatus: results.buildStatus,
|
|
497
|
+
buildExitCode: results.buildExitCode,
|
|
498
|
+
lintStatus: results.lintStatus,
|
|
499
|
+
testPassed: results.testPassed,
|
|
500
|
+
testCoverage: results.testCoverage,
|
|
501
|
+
verificationEvidenceIds,
|
|
502
|
+
verifiedAt: Date.now(),
|
|
503
|
+
};
|
|
504
|
+
await store.update(args['task-id'], { payload: updatedPayload });
|
|
505
|
+
// Attempt FSM transition to COMPLETED
|
|
506
|
+
const allPassed = results.buildStatus === 'success' &&
|
|
507
|
+
(results.buildExitCode ?? 1) === 0 &&
|
|
508
|
+
results.lintStatus === 'success' &&
|
|
509
|
+
results.testPassed === true &&
|
|
510
|
+
(results.testCoverage ?? 0) >= 80 &&
|
|
511
|
+
results.securityPassed === true;
|
|
512
|
+
let transitionResult = null;
|
|
513
|
+
if (allPassed) {
|
|
514
|
+
try {
|
|
515
|
+
transitionResult = await fsm.transition(args['task-id'], 'complete', {
|
|
516
|
+
actor: { kind: 'human', userId: 'cli' }
|
|
517
|
+
});
|
|
518
|
+
if (!args.json)
|
|
519
|
+
console.log('\nTask marked COMPLETED');
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
const error = e;
|
|
523
|
+
if (!args.json)
|
|
524
|
+
console.log('\nFSM transition failed:', error.message);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const passed = allPassed && (transitionResult?.success ?? false);
|
|
528
|
+
const result = { phase: 'VERIFY', taskId: args['task-id'], results, evidenceIds: verificationEvidenceIds, passed };
|
|
529
|
+
if (args.json)
|
|
530
|
+
console.log(JSON.stringify(result, null, 2));
|
|
531
|
+
else {
|
|
532
|
+
console.log(`\nVERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
|
|
533
|
+
if (passed)
|
|
534
|
+
console.log(`\n Next: scale review\n`);
|
|
535
|
+
else
|
|
536
|
+
console.log(`\n Fix issues and re-run: scale verify ${args['task-id']}\n`);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
});
|
|
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
|
|
595
|
+
export const phaseReview = defineCommand({
|
|
596
|
+
meta: { name: 'review', description: 'REVIEW: Code review with Karpathy Principles (/review)' },
|
|
597
|
+
args: {
|
|
598
|
+
'task-id': { type: 'positional', required: false },
|
|
599
|
+
'check-security': { type: 'boolean', default: true },
|
|
600
|
+
'check-style': { type: 'boolean', default: true },
|
|
601
|
+
json: { type: 'boolean', default: false },
|
|
602
|
+
},
|
|
603
|
+
async run({ args }) {
|
|
604
|
+
const { store, workflowEngine } = getEngine();
|
|
605
|
+
const reviewStore = new ReviewStore(SCALE_DIR);
|
|
606
|
+
// If task-id provided, validate task exists
|
|
607
|
+
let task = null;
|
|
608
|
+
let taskPayload;
|
|
609
|
+
if (args['task-id']) {
|
|
610
|
+
task = await store.get(args['task-id']);
|
|
611
|
+
if (!task || task.type !== 'Task') {
|
|
612
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
613
|
+
process.exit(1);
|
|
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 });
|
|
648
|
+
}
|
|
649
|
+
const result = {
|
|
650
|
+
phase: 'REVIEW',
|
|
651
|
+
taskId: args['task-id'],
|
|
652
|
+
reviewId: record.id,
|
|
653
|
+
findings,
|
|
654
|
+
changedFiles: review.changedFiles.map(file => file.path),
|
|
655
|
+
summary,
|
|
656
|
+
passed,
|
|
657
|
+
recommendation: passed ? 'Ready to ship' : 'Fix CRITICAL issues before shipping'
|
|
658
|
+
};
|
|
659
|
+
if (args.json)
|
|
660
|
+
console.log(JSON.stringify(result, null, 2));
|
|
661
|
+
else {
|
|
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}`));
|
|
672
|
+
if (passed) {
|
|
673
|
+
console.log('\nReview passed (no CRITICAL issues)');
|
|
674
|
+
console.log('\n Next: scale ship ' + (args['task-id'] ?? '<task-id>') + '\n');
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
console.log('\nReview blocked by CRITICAL issues');
|
|
678
|
+
console.log('\n Fix critical issues, then: scale review\n');
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
// SHIP Phase - HonestDelivery
|
|
684
|
+
export const phaseShip = defineCommand({
|
|
685
|
+
meta: { name: 'ship', description: 'SHIP: Commit with HonestDelivery Report (/ship)' },
|
|
686
|
+
args: {
|
|
687
|
+
'task-id': { type: 'positional', required: true },
|
|
688
|
+
message: { type: 'string', alias: 'm', description: 'Commit message' },
|
|
689
|
+
'no-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
|
|
690
|
+
'skip-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
|
|
691
|
+
json: { type: 'boolean', default: false },
|
|
692
|
+
},
|
|
693
|
+
async run({ args }) {
|
|
694
|
+
const { store, fsm, workflowEngine } = getEngine();
|
|
695
|
+
// Validate task exists
|
|
696
|
+
const task = await store.get(args['task-id']);
|
|
697
|
+
if (!task || task.type !== 'Task') {
|
|
698
|
+
console.error(`\nTask not found: ${args['task-id']}\n`);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
// Check if task is completed (or attempt transition)
|
|
702
|
+
const payload = task.payload;
|
|
703
|
+
const evidenceValidation = validateVerificationEvidence(payload.verificationEvidenceIds);
|
|
704
|
+
const reviewValidation = validateReviewEvidence(payload.reviewEvidenceIds);
|
|
705
|
+
const verificationPassed = payload.buildStatus === 'success' &&
|
|
706
|
+
(payload.buildExitCode ?? 1) === 0 &&
|
|
707
|
+
payload.lintStatus === 'success' &&
|
|
708
|
+
payload.testPassed === true &&
|
|
709
|
+
(payload.testCoverage ?? 0) >= 80 &&
|
|
710
|
+
evidenceValidation.ok;
|
|
711
|
+
const reviewPassed = payload.reviewPassed === true && reviewValidation.ok;
|
|
712
|
+
if (task.status !== 'COMPLETED') {
|
|
713
|
+
if (!verificationPassed) {
|
|
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
|
+
}
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
// Attempt FSM transition
|
|
724
|
+
try {
|
|
725
|
+
await fsm.transition(args['task-id'], 'complete', {
|
|
726
|
+
actor: { kind: 'human', userId: 'cli' }
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (e) {
|
|
730
|
+
const error = e;
|
|
731
|
+
console.error('\nFSM transition failed:', error.message);
|
|
732
|
+
console.log('\n Run verification first: scale verify ' + args['task-id'] + '\n');
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
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
|
+
}
|
|
746
|
+
// Git operations
|
|
747
|
+
let commitHash = null;
|
|
748
|
+
if (!shouldSkipCommit(args['skip-commit'])) {
|
|
749
|
+
const { execa } = await import('execa');
|
|
750
|
+
const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
|
|
751
|
+
try {
|
|
752
|
+
await execa('git', ['add', '.']);
|
|
753
|
+
const result = await execa('git', ['commit', '-m', commitMessage]);
|
|
754
|
+
commitHash = result.stdout.split('\n')[0]; // First line contains hash
|
|
755
|
+
}
|
|
756
|
+
catch (e) {
|
|
757
|
+
const error = e;
|
|
758
|
+
if (!args.json)
|
|
759
|
+
console.log(' Git commit skipped:', error.message);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Update Plan to DONE if Task completed
|
|
763
|
+
if (task.parents.length > 0) {
|
|
764
|
+
const planId = task.parents[0];
|
|
765
|
+
try {
|
|
766
|
+
await fsm.transition(planId, 'complete', { actor: { kind: 'system', component: 'phase-ship' } });
|
|
767
|
+
}
|
|
768
|
+
catch { }
|
|
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
|
+
}
|
|
805
|
+
const result = {
|
|
806
|
+
phase: 'SHIP',
|
|
807
|
+
taskId: args['task-id'],
|
|
808
|
+
status: 'COMPLETED',
|
|
809
|
+
verificationEvidenceIds: payload.verificationEvidenceIds ?? [],
|
|
810
|
+
evidenceValidation,
|
|
811
|
+
reviewEvidenceIds: payload.reviewEvidenceIds ?? [],
|
|
812
|
+
reviewValidation,
|
|
813
|
+
commitHash,
|
|
814
|
+
};
|
|
815
|
+
if (args.json)
|
|
816
|
+
console.log(JSON.stringify(result, null, 2));
|
|
817
|
+
else {
|
|
818
|
+
console.log('\nSHIP Phase');
|
|
819
|
+
console.log('\nTask COMPLETED: ' + args['task-id']);
|
|
820
|
+
if (commitHash)
|
|
821
|
+
console.log(' Commit: ' + commitHash);
|
|
822
|
+
console.log('\nDone. Feature shipped.\n');
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
//# sourceMappingURL=phaseCommands.js.map
|