@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.
Files changed (134) hide show
  1. package/README.en.md +237 -0
  2. package/README.md +111 -50
  3. package/dist/adapters/ClaudeCodeAdapter.d.ts +2 -0
  4. package/dist/adapters/ClaudeCodeAdapter.js +5 -0
  5. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
  6. package/dist/adapters/CodexAdapter.d.ts +1 -0
  7. package/dist/adapters/CodexAdapter.js +5 -0
  8. package/dist/adapters/CodexAdapter.js.map +1 -1
  9. package/dist/adapters/GeminiAdapter.d.ts +1 -0
  10. package/dist/adapters/GeminiAdapter.js +4 -0
  11. package/dist/adapters/GeminiAdapter.js.map +1 -1
  12. package/dist/adapters/HermesAdapter.d.ts +1 -0
  13. package/dist/adapters/HermesAdapter.js +4 -0
  14. package/dist/adapters/HermesAdapter.js.map +1 -1
  15. package/dist/adapters/OpenClawAdapter.d.ts +1 -0
  16. package/dist/adapters/OpenClawAdapter.js +4 -0
  17. package/dist/adapters/OpenClawAdapter.js.map +1 -1
  18. package/dist/adapters/QCoderAdapter.d.ts +1 -0
  19. package/dist/adapters/QCoderAdapter.js +4 -0
  20. package/dist/adapters/QCoderAdapter.js.map +1 -1
  21. package/dist/adapters/TraeAdapter.d.ts +1 -0
  22. package/dist/adapters/TraeAdapter.js +4 -0
  23. package/dist/adapters/TraeAdapter.js.map +1 -1
  24. package/dist/adapters/VSCAdapter.d.ts +1 -0
  25. package/dist/adapters/VSCAdapter.js +4 -0
  26. package/dist/adapters/VSCAdapter.js.map +1 -1
  27. package/dist/adapters/WorkBuddyAdapter.d.ts +1 -0
  28. package/dist/adapters/WorkBuddyAdapter.js +4 -0
  29. package/dist/adapters/WorkBuddyAdapter.js.map +1 -1
  30. package/dist/agents/AgentSourceLoader.js +1 -1
  31. package/dist/agents/AgentSourceLoader.js.map +1 -1
  32. package/dist/agents/types.d.ts +1 -1
  33. package/dist/api/cli.js +236 -5
  34. package/dist/api/cli.js.map +1 -1
  35. package/dist/api/quickstart.d.ts +23 -0
  36. package/dist/api/quickstart.js +57 -0
  37. package/dist/api/quickstart.js.map +1 -0
  38. package/dist/artifact/types.d.ts +9 -1
  39. package/dist/artifact/types.js.map +1 -1
  40. package/dist/capabilities/BrowserCapability.d.ts +30 -0
  41. package/dist/capabilities/BrowserCapability.js +73 -0
  42. package/dist/capabilities/BrowserCapability.js.map +1 -0
  43. package/dist/capabilities/CapabilityRegistry.d.ts +17 -0
  44. package/dist/capabilities/CapabilityRegistry.js +65 -0
  45. package/dist/capabilities/CapabilityRegistry.js.map +1 -0
  46. package/dist/capabilities/ComputerCapability.d.ts +28 -0
  47. package/dist/capabilities/ComputerCapability.js +40 -0
  48. package/dist/capabilities/ComputerCapability.js.map +1 -0
  49. package/dist/capabilities/InstalledSkillsIntegration.d.ts +66 -0
  50. package/dist/capabilities/InstalledSkillsIntegration.js +188 -0
  51. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -0
  52. package/dist/capabilities/SearchCapability.d.ts +46 -0
  53. package/dist/capabilities/SearchCapability.js +88 -0
  54. package/dist/capabilities/SearchCapability.js.map +1 -0
  55. package/dist/capabilities/index.d.ts +6 -0
  56. package/dist/capabilities/index.js +9 -0
  57. package/dist/capabilities/index.js.map +1 -0
  58. package/dist/capabilities/types.d.ts +92 -0
  59. package/dist/capabilities/types.js +7 -0
  60. package/dist/capabilities/types.js.map +1 -0
  61. package/dist/cli/liteCommands.d.ts +81 -0
  62. package/dist/cli/liteCommands.js +148 -0
  63. package/dist/cli/liteCommands.js.map +1 -0
  64. package/dist/cli/phaseCommands.d.ts +159 -0
  65. package/dist/cli/phaseCommands.js +826 -0
  66. package/dist/cli/phaseCommands.js.map +1 -0
  67. package/dist/core/logger.js +9 -2
  68. package/dist/core/logger.js.map +1 -1
  69. package/dist/hooks/HookGeneratorEnhanced.js +84 -5
  70. package/dist/hooks/HookGeneratorEnhanced.js.map +1 -1
  71. package/dist/hooks/WorkflowHooksManager.d.ts +30 -0
  72. package/dist/hooks/WorkflowHooksManager.js +117 -0
  73. package/dist/hooks/WorkflowHooksManager.js.map +1 -0
  74. package/dist/hooks/index.d.ts +2 -0
  75. package/dist/hooks/index.js +2 -1
  76. package/dist/hooks/index.js.map +1 -1
  77. package/dist/skills/SkillDiscovery.d.ts +27 -1
  78. package/dist/skills/SkillDiscovery.js +106 -11
  79. package/dist/skills/SkillDiscovery.js.map +1 -1
  80. package/dist/skills/SkillExecutor.d.ts +11 -1
  81. package/dist/skills/SkillExecutor.js +160 -5
  82. package/dist/skills/SkillExecutor.js.map +1 -1
  83. package/dist/workflow/EvidenceStore.d.ts +20 -0
  84. package/dist/workflow/EvidenceStore.js +48 -0
  85. package/dist/workflow/EvidenceStore.js.map +1 -0
  86. package/dist/workflow/ReviewAnalyzer.d.ts +28 -0
  87. package/dist/workflow/ReviewAnalyzer.js +80 -0
  88. package/dist/workflow/ReviewAnalyzer.js.map +1 -0
  89. package/dist/workflow/ReviewStore.d.ts +32 -0
  90. package/dist/workflow/ReviewStore.js +42 -0
  91. package/dist/workflow/ReviewStore.js.map +1 -0
  92. package/dist/workflow/VerificationCommands.d.ts +19 -0
  93. package/dist/workflow/VerificationCommands.js +123 -0
  94. package/dist/workflow/VerificationCommands.js.map +1 -0
  95. package/dist/workflow/WorkflowEngine.d.ts +62 -0
  96. package/dist/workflow/WorkflowEngine.js +151 -0
  97. package/dist/workflow/WorkflowEngine.js.map +1 -0
  98. package/dist/workflow/cognitive/AmbiguityScorer.d.ts +17 -0
  99. package/dist/workflow/cognitive/AmbiguityScorer.js +107 -0
  100. package/dist/workflow/cognitive/AmbiguityScorer.js.map +1 -0
  101. package/dist/workflow/cognitive/ConsensusPlanner.d.ts +26 -0
  102. package/dist/workflow/cognitive/ConsensusPlanner.js +141 -0
  103. package/dist/workflow/cognitive/ConsensusPlanner.js.map +1 -0
  104. package/dist/workflow/cognitive/SocraticQuestioner.d.ts +33 -0
  105. package/dist/workflow/cognitive/SocraticQuestioner.js +276 -0
  106. package/dist/workflow/cognitive/SocraticQuestioner.js.map +1 -0
  107. package/dist/workflow/execution/RalphEngine.d.ts +36 -0
  108. package/dist/workflow/execution/RalphEngine.js +123 -0
  109. package/dist/workflow/execution/RalphEngine.js.map +1 -0
  110. package/dist/workflow/execution/UltraworkEngine.d.ts +43 -0
  111. package/dist/workflow/execution/UltraworkEngine.js +135 -0
  112. package/dist/workflow/execution/UltraworkEngine.js.map +1 -0
  113. package/dist/workflow/gates/GateSystem.d.ts +104 -0
  114. package/dist/workflow/gates/GateSystem.js +579 -0
  115. package/dist/workflow/gates/GateSystem.js.map +1 -0
  116. package/dist/workflow/index.d.ts +12 -0
  117. package/dist/workflow/index.js +14 -0
  118. package/dist/workflow/index.js.map +1 -0
  119. package/dist/workflow/quality/HonestDelivery.d.ts +19 -0
  120. package/dist/workflow/quality/HonestDelivery.js +77 -0
  121. package/dist/workflow/quality/HonestDelivery.js.map +1 -0
  122. package/dist/workflow/quality/KarpathyEvaluator.d.ts +18 -0
  123. package/dist/workflow/quality/KarpathyEvaluator.js +76 -0
  124. package/dist/workflow/quality/KarpathyEvaluator.js.map +1 -0
  125. package/dist/workflow/types.d.ts +139 -0
  126. package/dist/workflow/types.js +4 -0
  127. package/dist/workflow/types.js.map +1 -0
  128. package/dist/workflows/DAGBuilder.js +1 -1
  129. package/dist/workflows/DAGBuilder.js.map +1 -1
  130. package/dist/workflows/WorkflowOrchestrator.js +1 -1
  131. package/dist/workflows/WorkflowOrchestrator.js.map +1 -1
  132. package/dist/workflows/index.js +1 -1
  133. package/dist/workflows/index.js.map +1 -1
  134. 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