@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.
Files changed (103) hide show
  1. package/README.en.md +18 -1
  2. package/README.md +22 -2
  3. package/dist/agents/AgentSourceLoader.js +1 -1
  4. package/dist/agents/AgentSourceLoader.js.map +1 -1
  5. package/dist/agents/types.d.ts +1 -1
  6. package/dist/api/cli.js +222 -6
  7. package/dist/api/cli.js.map +1 -1
  8. package/dist/api/quickstart.d.ts +23 -0
  9. package/dist/api/quickstart.js +57 -0
  10. package/dist/api/quickstart.js.map +1 -0
  11. package/dist/artifact/types.d.ts +5 -1
  12. package/dist/artifact/types.js.map +1 -1
  13. package/dist/capabilities/BrowserCapability.d.ts +30 -0
  14. package/dist/capabilities/BrowserCapability.js +73 -0
  15. package/dist/capabilities/BrowserCapability.js.map +1 -0
  16. package/dist/capabilities/CapabilityRegistry.d.ts +17 -0
  17. package/dist/capabilities/CapabilityRegistry.js +65 -0
  18. package/dist/capabilities/CapabilityRegistry.js.map +1 -0
  19. package/dist/capabilities/ComputerCapability.d.ts +28 -0
  20. package/dist/capabilities/ComputerCapability.js +40 -0
  21. package/dist/capabilities/ComputerCapability.js.map +1 -0
  22. package/dist/capabilities/InstalledSkillsIntegration.d.ts +66 -0
  23. package/dist/capabilities/InstalledSkillsIntegration.js +188 -0
  24. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -0
  25. package/dist/capabilities/SearchCapability.d.ts +46 -0
  26. package/dist/capabilities/SearchCapability.js +88 -0
  27. package/dist/capabilities/SearchCapability.js.map +1 -0
  28. package/dist/capabilities/index.d.ts +6 -0
  29. package/dist/capabilities/index.js +9 -0
  30. package/dist/capabilities/index.js.map +1 -0
  31. package/dist/capabilities/types.d.ts +92 -0
  32. package/dist/capabilities/types.js +7 -0
  33. package/dist/capabilities/types.js.map +1 -0
  34. package/dist/cli/liteCommands.js +1 -1
  35. package/dist/cli/liteCommands.js.map +1 -1
  36. package/dist/cli/phaseCommands.d.ts +33 -3
  37. package/dist/cli/phaseCommands.js +398 -144
  38. package/dist/cli/phaseCommands.js.map +1 -1
  39. package/dist/core/logger.js +9 -2
  40. package/dist/core/logger.js.map +1 -1
  41. package/dist/hooks/HookGeneratorEnhanced.js +84 -5
  42. package/dist/hooks/HookGeneratorEnhanced.js.map +1 -1
  43. package/dist/hooks/WorkflowHooksManager.d.ts +30 -0
  44. package/dist/hooks/WorkflowHooksManager.js +117 -0
  45. package/dist/hooks/WorkflowHooksManager.js.map +1 -0
  46. package/dist/hooks/index.d.ts +2 -0
  47. package/dist/hooks/index.js +2 -1
  48. package/dist/hooks/index.js.map +1 -1
  49. package/dist/skills/SkillExecutor.d.ts +11 -1
  50. package/dist/skills/SkillExecutor.js +160 -5
  51. package/dist/skills/SkillExecutor.js.map +1 -1
  52. package/dist/workflow/EvidenceStore.d.ts +20 -0
  53. package/dist/workflow/EvidenceStore.js +48 -0
  54. package/dist/workflow/EvidenceStore.js.map +1 -0
  55. package/dist/workflow/ReviewAnalyzer.d.ts +28 -0
  56. package/dist/workflow/ReviewAnalyzer.js +80 -0
  57. package/dist/workflow/ReviewAnalyzer.js.map +1 -0
  58. package/dist/workflow/ReviewStore.d.ts +32 -0
  59. package/dist/workflow/ReviewStore.js +42 -0
  60. package/dist/workflow/ReviewStore.js.map +1 -0
  61. package/dist/workflow/VerificationCommands.d.ts +19 -0
  62. package/dist/workflow/VerificationCommands.js +123 -0
  63. package/dist/workflow/VerificationCommands.js.map +1 -0
  64. package/dist/workflow/WorkflowEngine.d.ts +62 -0
  65. package/dist/workflow/WorkflowEngine.js +151 -0
  66. package/dist/workflow/WorkflowEngine.js.map +1 -0
  67. package/dist/workflow/cognitive/AmbiguityScorer.d.ts +17 -0
  68. package/dist/workflow/cognitive/AmbiguityScorer.js +107 -0
  69. package/dist/workflow/cognitive/AmbiguityScorer.js.map +1 -0
  70. package/dist/workflow/cognitive/ConsensusPlanner.d.ts +26 -0
  71. package/dist/workflow/cognitive/ConsensusPlanner.js +141 -0
  72. package/dist/workflow/cognitive/ConsensusPlanner.js.map +1 -0
  73. package/dist/workflow/cognitive/SocraticQuestioner.d.ts +33 -0
  74. package/dist/workflow/cognitive/SocraticQuestioner.js +276 -0
  75. package/dist/workflow/cognitive/SocraticQuestioner.js.map +1 -0
  76. package/dist/workflow/execution/RalphEngine.d.ts +36 -0
  77. package/dist/workflow/execution/RalphEngine.js +123 -0
  78. package/dist/workflow/execution/RalphEngine.js.map +1 -0
  79. package/dist/workflow/execution/UltraworkEngine.d.ts +43 -0
  80. package/dist/workflow/execution/UltraworkEngine.js +135 -0
  81. package/dist/workflow/execution/UltraworkEngine.js.map +1 -0
  82. package/dist/workflow/gates/GateSystem.d.ts +104 -0
  83. package/dist/workflow/gates/GateSystem.js +579 -0
  84. package/dist/workflow/gates/GateSystem.js.map +1 -0
  85. package/dist/workflow/index.d.ts +12 -0
  86. package/dist/workflow/index.js +14 -0
  87. package/dist/workflow/index.js.map +1 -0
  88. package/dist/workflow/quality/HonestDelivery.d.ts +19 -0
  89. package/dist/workflow/quality/HonestDelivery.js +77 -0
  90. package/dist/workflow/quality/HonestDelivery.js.map +1 -0
  91. package/dist/workflow/quality/KarpathyEvaluator.d.ts +18 -0
  92. package/dist/workflow/quality/KarpathyEvaluator.js +76 -0
  93. package/dist/workflow/quality/KarpathyEvaluator.js.map +1 -0
  94. package/dist/workflow/types.d.ts +139 -0
  95. package/dist/workflow/types.js +4 -0
  96. package/dist/workflow/types.js.map +1 -0
  97. package/dist/workflows/DAGBuilder.js +1 -1
  98. package/dist/workflows/DAGBuilder.js.map +1 -1
  99. package/dist/workflows/WorkflowOrchestrator.js +1 -1
  100. package/dist/workflows/WorkflowOrchestrator.js.map +1 -1
  101. package/dist/workflows/index.js +1 -1
  102. package/dist/workflows/index.js.map +1 -1
  103. package/package.json +2 -2
@@ -1,14 +1,51 @@
1
- // SCALE Engine Phase-Aligned Commands (v0.9.0)
2
- // 6 阶段快捷命令:DEFINE PLAN BUILD VERIFY REVIEW SHIP
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
- return { eventBus, store, 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 };
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
- // Calculate ambiguity score
83
- const ambiguityScore = calculateAmbiguityScore(desc, successCriteria);
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: desc },
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: desc,
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(` ⚠️ FSM transition: ${error.message}`);
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(`\n✅ DEFINE: ${spec.id}`);
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(`\n❌ Spec not found: ${args['spec-id']}\n`);
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 and restore previous version';
180
- const approach = args.approach ?? 'Standard implementation following spec requirements';
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(` ⚠️ FSM transition: ${error.message}`);
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(`\n✅ PLAN: ${plan.id}`);
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(`\n❌ Plan not found: ${args['plan-id']}\n`);
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(` ⚠️ FSM transition: ${error.message}`);
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(`\n✅ BUILD: ${task.id}`);
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 tests and update Task (/test)' },
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', default: 'npm run build', description: 'Build command' },
300
- 'lint-cmd': { type: 'string', default: 'npm run lint', description: 'Lint command' },
301
- 'test-cmd': { type: 'string', default: 'npm test', description: 'Test command' },
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(`\n❌ Task not found: ${args['task-id']}\n`);
448
+ console.error(`\nTask not found: ${args['task-id']}\n`);
313
449
  process.exit(1);
314
450
  }
315
- const results = {
316
- buildStatus: 'pending',
317
- buildExitCode: undefined,
318
- lintStatus: 'pending',
319
- testPassed: undefined,
320
- testCoverage: undefined,
321
- };
322
- // Run build
323
- if (!args['skip-build']) {
324
- if (!args.json)
325
- console.log('\n🔨 Running build...');
326
- const build = await runVerificationCmd(args['build-cmd']);
327
- results.buildStatus = build.exitCode === 0 ? 'success' : 'failed';
328
- results.buildExitCode = build.exitCode;
329
- if (!args.json) {
330
- if (build.exitCode === 0)
331
- console.log(' ✅ Build passed');
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('\n✅ Task marked COMPLETED');
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('\n⚠️ FSM transition failed:', error.message);
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(`\n📊 VERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
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
- // REVIEW Phase
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(`\n❌ Task not found: ${args['task-id']}\n`);
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
- summary: { critical, high, medium, low },
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('\n🔍 REVIEW Phase');
473
- console.log('\nReview Checklist:');
474
- console.log('┌─────────────────────────────────────────────┐');
475
- console.log(`│ CRITICAL: ${critical} issues ${critical > 0 ? '⚠️ BLOCKED' : '✅'} │`);
476
- console.log(`│ HIGH: ${high} issues │`);
477
- console.log(`│ MEDIUM: ${medium} issues │`);
478
- console.log(`│ LOW: ${low} issues │`);
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('\n✅ Review passed (no CRITICAL issues)');
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('\n❌ Review blocked by CRITICAL issues');
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 and complete (/ship)' },
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(`\n❌ Task not found: ${args['task-id']}\n`);
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('\n❌ Task not verified. Run: scale verify ' + args['task-id'] + '\n');
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('\n❌ FSM transition failed:', error.message);
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['no-commit']) {
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(' ⚠️ Git commit skipped:', error.message);
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('\n🚀 SHIP Phase');
565
- console.log('\n✅ Task COMPLETED: ' + args['task-id']);
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('\n🎉 Done! Feature shipped.\n');
822
+ console.log('\nDone. Feature shipped.\n');
569
823
  }
570
824
  },
571
825
  });