@hongmaple0820/scale-engine 0.9.0 → 0.10.1

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 +127 -179
  2. package/README.md +168 -1094
  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 +42 -3
  37. package/dist/cli/phaseCommands.js +490 -149
  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 +33 -0
  56. package/dist/workflow/ReviewAnalyzer.js +264 -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 +23 -0
  62. package/dist/workflow/VerificationCommands.js +125 -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 +130 -0
  83. package/dist/workflow/gates/GateSystem.js +788 -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 +146 -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 +3 -3
@@ -1,14 +1,64 @@
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
+ }
49
+ function getValidatedReviewRecords(ids) {
50
+ const reviewStore = new ReviewStore(SCALE_DIR);
51
+ return (ids ?? [])
52
+ .map(id => reviewStore.getReview(id))
53
+ .filter((record) => Boolean(record?.passed));
54
+ }
55
+ function getVerificationEvidenceSummary(ids) {
56
+ const evidenceStore = new EvidenceStore(SCALE_DIR);
57
+ return (ids ?? [])
58
+ .map(id => evidenceStore.getGateResult(id))
59
+ .filter((record) => Boolean(record))
60
+ .map(record => ({ gate: record.gate, passed: record.passed }));
61
+ }
12
62
  function getEngine() {
13
63
  ensureDir(SCALE_DIR);
14
64
  const eventBus = new EventBus({ eventsDir: join(SCALE_DIR, 'events') });
@@ -18,12 +68,31 @@ function getEngine() {
18
68
  });
19
69
  const fsm = new FSM(store, eventBus);
20
70
  registerAllFSMs(fsm);
21
- return { eventBus, store, fsm };
71
+ // Initialize capability registry
72
+ const capabilityRegistry = new CapabilityRegistry(eventBus);
73
+ // Initialize skill registry
74
+ const skillRegistry = new SkillRegistry(eventBus);
75
+ // Initialize workflow engine with cognitive scaffolding and quality gates.
76
+ const workflowEngine = new WorkflowEngine({
77
+ eventBus,
78
+ capabilityRegistry,
79
+ skillRegistry
80
+ });
81
+ return { eventBus, store, fsm, workflowEngine, skillRegistry };
22
82
  }
23
83
  function ensureDir(dir) {
24
84
  if (!existsSync(dir))
25
85
  mkdirSync(dir, { recursive: true });
26
86
  }
87
+ function isTruthyFlag(value) {
88
+ return value === true || value === '' || value === 'true' || value === '1';
89
+ }
90
+ function shouldSkipCommit(value) {
91
+ return isTruthyFlag(value) || process.argv.includes('--no-commit') || process.argv.includes('--skip-commit');
92
+ }
93
+ function normalizeGitPath(path) {
94
+ return path.replace(/\\/g, '/');
95
+ }
27
96
  // Helper: Generate spec markdown file
28
97
  function generateSpecMarkdown(id, title, payload) {
29
98
  return `# Spec: ${title}
@@ -63,34 +132,107 @@ function calculateAmbiguityScore(description, successCriteria) {
63
132
  score -= 0.02;
64
133
  return Math.max(0.05, score);
65
134
  }
66
- // DEFINE Phase
135
+ // DEFINE Phase - AmbiguityScorer + SocraticQuestioner + G1 gate
67
136
  export const phaseDefine = defineCommand({
68
- meta: { name: 'define', description: 'DEFINE: Create Spec (/spec)' },
137
+ meta: { name: 'define', description: 'DEFINE: Create Spec with AmbiguityScorer + SocraticQuestioner (/spec)' },
69
138
  args: {
70
139
  title: { type: 'positional', required: true },
71
140
  description: { type: 'string', alias: 'd' },
72
141
  'success-criteria': { type: 'string', alias: 'c', description: 'Comma-separated criteria' },
142
+ // Socratic refinement answers (optional)
143
+ 'goal': { type: 'string', description: 'Goal answer for Socratic refinement' },
144
+ 'constraint': { type: 'string', description: 'Constraint answer for Socratic refinement' },
145
+ 'acceptance': { type: 'string', description: 'Acceptance criteria answer for Socratic refinement' },
146
+ 'context': { type: 'string', description: 'Context answer for Socratic refinement' },
147
+ 'risk': { type: 'string', description: 'Risk answer for Socratic refinement' },
148
+ 'priority': { type: 'string', description: 'Priority answer for Socratic refinement' },
73
149
  json: { type: 'boolean', default: false },
74
150
  },
75
151
  async run({ args }) {
76
- const { store, fsm } = getEngine();
152
+ const { store, fsm, workflowEngine } = getEngine();
77
153
  const desc = args.description ?? args.title;
78
154
  // Parse success criteria
79
155
  const successCriteria = args['success-criteria']
80
156
  ? args['success-criteria'].split(',').map(s => s.trim()).filter(s => s)
81
157
  : ['Feature works as described', 'No regression in existing functionality'];
82
- // Calculate ambiguity score
83
- const ambiguityScore = calculateAmbiguityScore(desc, successCriteria);
158
+ // === WorkflowEngine Integration ===
159
+ // Step 1: Explore with AmbiguityScorer + SocraticQuestioner
160
+ const exploreResult = await workflowEngine.explore(desc);
161
+ const ambiguityResult = workflowEngine.getAmbiguityScorer().analyzeRequirement(desc);
162
+ // Step 2: Check if requirement needs refinement.
163
+ if (ambiguityResult.blocked) {
164
+ console.error('\nRequirement ambiguity is too high (>40%); refine the requirement first.');
165
+ console.log('\n Refine the requirement by answering:');
166
+ console.log(' - What is the goal?');
167
+ console.log(' - What are the input/output boundaries?');
168
+ console.log(' - What are the acceptance criteria?\n');
169
+ process.exit(1);
170
+ }
171
+ // Step 3: Handle Socratic refinement if ambiguity > 20%
172
+ let refinedRequirement = desc;
173
+ let finalAmbiguityScore = ambiguityResult.totalScore;
174
+ if (ambiguityResult.requiresQuestioning && exploreResult.socraticSession) {
175
+ const session = exploreResult.socraticSession;
176
+ if (!args.json) {
177
+ console.log('\nRequirement ambiguity is >20%; starting Socratic refinement.');
178
+ console.log('\nSix-question refinement framework:');
179
+ console.log(workflowEngine.getSocraticQuestioner().formatSessionReport(session));
180
+ }
181
+ // Check if user provided answers via CLI args
182
+ const answers = [];
183
+ if (args.goal)
184
+ answers.push({ questionId: 'q-goal', answer: args.goal });
185
+ if (args.constraint)
186
+ answers.push({ questionId: 'q-constraint', answer: args.constraint });
187
+ if (args.acceptance)
188
+ answers.push({ questionId: 'q-acceptance', answer: args.acceptance });
189
+ if (args.context)
190
+ answers.push({ questionId: 'q-context', answer: args.context });
191
+ if (args.risk)
192
+ answers.push({ questionId: 'q-risk', answer: args.risk });
193
+ if (args.priority)
194
+ answers.push({ questionId: 'q-priority', answer: args.priority });
195
+ // If answers provided, process them
196
+ if (answers.length > 0) {
197
+ for (const { questionId, answer } of answers) {
198
+ workflowEngine.getSocraticQuestioner().recordAnswer(session.sessionId, questionId, answer);
199
+ }
200
+ const progress = workflowEngine.getSocraticQuestioner().evaluateProgress(session);
201
+ if (progress.refined) {
202
+ refinedRequirement = workflowEngine.getSocraticQuestioner().generateRefinedRequirement(session);
203
+ finalAmbiguityScore = progress.newAmbiguity;
204
+ if (!args.json) {
205
+ console.log('\nRequirement refined; ambiguity reduced to: ' + finalAmbiguityScore.toFixed(2));
206
+ console.log('\nRefined requirement:');
207
+ console.log(refinedRequirement);
208
+ }
209
+ }
210
+ else if (!args.json) {
211
+ console.log('\nMore answers are needed to refine the requirement.');
212
+ console.log(' Current ambiguity: ' + progress.newAmbiguity.toFixed(2));
213
+ }
214
+ }
215
+ else if (!args.json) {
216
+ console.log('\nYou can refine the requirement with:');
217
+ console.log(' --goal "goal description"');
218
+ console.log(' --constraint "constraints and boundaries"');
219
+ console.log(' --acceptance "acceptance criteria"');
220
+ console.log(' --context "context and dependencies"');
221
+ console.log(' --risk "risk scenarios"');
222
+ console.log(' --priority "priority order"\n');
223
+ }
224
+ }
225
+ const ambiguityScore = finalAmbiguityScore;
84
226
  // Create Need artifact
85
227
  const need = await store.create({
86
228
  type: 'Need', title: args.title,
87
- payload: { rawText: desc },
229
+ payload: { rawText: refinedRequirement },
88
230
  initialStatus: 'DRAFT',
89
231
  createdBy: { kind: 'human', userId: 'cli' },
90
232
  });
91
- // Create Spec artifact with proper payload
233
+ // Create Spec artifact with proper payload (use refined requirement if available)
92
234
  const specPayload = {
93
- what: desc,
235
+ what: refinedRequirement,
94
236
  successCriteria,
95
237
  outOfScope: [],
96
238
  edgeCases: [],
@@ -118,13 +260,13 @@ export const phaseDefine = defineCommand({
118
260
  // Guard may fail - report reason
119
261
  const error = e;
120
262
  if (!args.json)
121
- console.log(` ⚠️ FSM transition: ${error.message}`);
263
+ console.log(` FSM transition: ${error.message}`);
122
264
  }
123
265
  const result = { phase: 'DEFINE', spec, specPath, ambiguityScore, successCriteria };
124
266
  if (args.json)
125
267
  console.log(JSON.stringify(result, null, 2));
126
268
  else {
127
- console.log(`\n✅ DEFINE: ${spec.id}`);
269
+ console.log(`\nDEFINE: ${spec.id}`);
128
270
  console.log(` Spec file: ${specPath}`);
129
271
  console.log(` Ambiguity score: ${ambiguityScore.toFixed(2)}`);
130
272
  console.log(` Success criteria: ${successCriteria.length}`);
@@ -158,9 +300,9 @@ ${payload.estimatedComplexity ?? 5}/10
158
300
  *Generated by SCALE Engine PLAN phase*
159
301
  `;
160
302
  }
161
- // PLAN Phase
303
+ // PLAN Phase - ConsensusPlanner + G2 gate
162
304
  export const phasePlan = defineCommand({
163
- meta: { name: 'plan', description: 'PLAN: Create Plan (/plan)' },
305
+ meta: { name: 'plan', description: 'PLAN: Create Plan with ConsensusPlanner (/plan)' },
164
306
  args: {
165
307
  'spec-id': { type: 'positional', required: true },
166
308
  approach: { type: 'string', alias: 'a', description: 'Implementation approach' },
@@ -168,16 +310,25 @@ export const phasePlan = defineCommand({
168
310
  json: { type: 'boolean', default: false },
169
311
  },
170
312
  async run({ args }) {
171
- const { store, fsm } = getEngine();
313
+ const { store, fsm, workflowEngine } = getEngine();
172
314
  // Validate spec exists
173
315
  const spec = await store.get(args['spec-id']);
174
316
  if (!spec || spec.type !== 'Spec') {
175
- console.error(`\n❌ Spec not found: ${args['spec-id']}\n`);
317
+ console.error(`\nSpec not found: ${args['spec-id']}\n`);
176
318
  process.exit(1);
177
319
  }
320
+ // === WorkflowEngine Integration ===
321
+ // Step 1: Run ConsensusPlanner (Planner -> Architect -> Critic).
322
+ const specDesc = spec.payload.what;
323
+ const consensusResult = await workflowEngine.plan(specDesc);
324
+ // Step 2: Display RALPLAN-DR output
325
+ if (!args.json) {
326
+ console.log('\nConsensus Planning Result:');
327
+ console.log(workflowEngine.getConsensusPlanner().formatReport(consensusResult));
328
+ }
178
329
  // 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';
330
+ const rollbackStrategy = args.rollback ?? consensusResult.preMortem.mitigations.join('\n') ?? 'Revert git commits';
331
+ const approach = args.approach ?? consensusResult.viableOptions.find((o) => o.selected)?.description ?? 'Standard implementation';
181
332
  // Create PlanPayload with rollback strategy
182
333
  const planPayload = {
183
334
  approach,
@@ -205,13 +356,13 @@ export const phasePlan = defineCommand({
205
356
  catch (e) {
206
357
  const error = e;
207
358
  if (!args.json)
208
- console.log(` ⚠️ FSM transition: ${error.message}`);
359
+ console.log(` FSM transition: ${error.message}`);
209
360
  }
210
361
  const result = { phase: 'PLAN', plan, planPath, rollbackStrategy };
211
362
  if (args.json)
212
363
  console.log(JSON.stringify(result, null, 2));
213
364
  else {
214
- console.log(`\n✅ PLAN: ${plan.id}`);
365
+ console.log(`\nPLAN: ${plan.id}`);
215
366
  console.log(` Plan file: ${planPath}`);
216
367
  console.log(` Rollback: ${rollbackStrategy}`);
217
368
  console.log(`\n Next: scale build ${plan.id}\n`);
@@ -231,7 +382,7 @@ export const phaseBuild = defineCommand({
231
382
  // Validate plan exists
232
383
  const plan = await store.get(args['plan-id']);
233
384
  if (!plan || plan.type !== 'Plan') {
234
- console.error(`\n❌ Plan not found: ${args['plan-id']}\n`);
385
+ console.error(`\nPlan not found: ${args['plan-id']}\n`);
235
386
  process.exit(1);
236
387
  }
237
388
  // Create TaskPayload
@@ -262,7 +413,7 @@ export const phaseBuild = defineCommand({
262
413
  catch (e) {
263
414
  const error = e;
264
415
  if (!args.json)
265
- console.log(` ⚠️ FSM transition: ${error.message}`);
416
+ console.log(` FSM transition: ${error.message}`);
266
417
  }
267
418
  // Update Plan status to IMPLEMENTING
268
419
  try {
@@ -273,7 +424,7 @@ export const phaseBuild = defineCommand({
273
424
  if (args.json)
274
425
  console.log(JSON.stringify(result, null, 2));
275
426
  else {
276
- console.log(`\n✅ BUILD: ${task.id}`);
427
+ console.log(`\nBUILD: ${task.id}`);
277
428
  console.log(` Status: RUNNING (ready to implement)`);
278
429
  console.log(` Description: ${taskPayload.description}`);
279
430
  console.log(`\n Implement now, then run: scale verify ${task.id}\n`);
@@ -291,81 +442,73 @@ async function runVerificationCmd(cmd) {
291
442
  child.on('close', (code) => resolve({ exitCode: code ?? 1, output }));
292
443
  });
293
444
  }
294
- // VERIFY Phase
445
+ // VERIFY Phase - GateSystem quality gates
295
446
  export const phaseVerify = defineCommand({
296
- meta: { name: 'verify', description: 'VERIFY: Run tests and update Task (/test)' },
447
+ meta: { name: 'verify', description: 'VERIFY: Run Gates G3-G7 (/test)' },
297
448
  args: {
298
449
  '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' },
450
+ 'build-cmd': { type: 'string', description: 'Override build command' },
451
+ 'lint-cmd': { type: 'string', description: 'Override lint command' },
452
+ 'test-cmd': { type: 'string', description: 'Override test command' },
453
+ 'coverage-cmd': { type: 'string', description: 'Override coverage command' },
454
+ 'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
455
+ 'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
302
456
  'skip-build': { type: 'boolean', default: false },
303
457
  'skip-lint': { type: 'boolean', default: false },
304
458
  'skip-test': { type: 'boolean', default: false },
305
459
  json: { type: 'boolean', default: false },
306
460
  },
307
461
  async run({ args }) {
308
- const { store, fsm } = getEngine();
462
+ const { store, fsm, workflowEngine } = getEngine();
309
463
  // Validate task exists
310
464
  const task = await store.get(args['task-id']);
311
465
  if (!task || task.type !== 'Task') {
312
- console.error(`\n❌ Task not found: ${args['task-id']}\n`);
466
+ console.error(`\nTask not found: ${args['task-id']}\n`);
313
467
  process.exit(1);
314
468
  }
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, '%');
469
+ // === WorkflowEngine Integration ===
470
+ // Step 1: Run GateSystem G3-G7
471
+ if (!args.json)
472
+ console.log('\nRunning Quality Gates...');
473
+ const gateResults = await workflowEngine.verify({
474
+ build: args['build-cmd'],
475
+ lint: args['lint-cmd'],
476
+ test: args['test-cmd'],
477
+ coverage: args['coverage-cmd'],
478
+ tddEvidence: args['tdd-evidence'],
479
+ tddStrict: isTruthyFlag(args['tdd-strict']),
480
+ });
481
+ // Step 2: Display gate results
482
+ if (!args.json) {
483
+ console.log('\nGate Results:');
484
+ for (const result of gateResults) {
485
+ console.log(` ${result.passed ? '[PASS]' : '[FAIL]'} ${result.gate}: ${result.evidence.slice(0, 50)}`);
486
+ if (result.blockers.length > 0) {
487
+ result.blockers.forEach((b) => console.log(` [BLOCKER] ${b.slice(0, 80)}`));
364
488
  }
365
- else
366
- console.log(' ❌ Tests failed (exit code:', test.exitCode, ')');
367
489
  }
368
490
  }
491
+ // Extract results from gateResults
492
+ const g0Result = gateResults.find(g => g.gate === 'G0');
493
+ const g4Result = gateResults.find(g => g.gate === 'G4');
494
+ const g5Result = gateResults.find(g => g.gate === 'G5');
495
+ const g6Result = gateResults.find(g => g.gate === 'G6');
496
+ const g7Result = gateResults.find(g => g.gate === 'G7');
497
+ const results = {
498
+ buildStatus: g0Result?.passed ? 'success' : 'failed',
499
+ buildExitCode: g0Result?.evidenceItems?.find(item => item.kind === 'command')?.exitCode,
500
+ lintStatus: g4Result?.passed ? 'success' : 'failed',
501
+ testPassed: g5Result?.passed,
502
+ testCoverage: undefined,
503
+ securityPassed: g7Result?.passed,
504
+ };
505
+ const verificationEvidenceIds = gateResults
506
+ .map(g => g.evidenceRecordId)
507
+ .filter((id) => Boolean(id));
508
+ // Extract coverage from G6 evidence
509
+ const coverageMatch = g6Result?.evidence.match(/Coverage: (\d+\.?\d*)%/);
510
+ if (coverageMatch)
511
+ results.testCoverage = parseFloat(coverageMatch[1]);
369
512
  // Update Task payload with verification results
370
513
  const currentPayload = task.payload;
371
514
  const updatedPayload = {
@@ -375,12 +518,17 @@ export const phaseVerify = defineCommand({
375
518
  lintStatus: results.lintStatus,
376
519
  testPassed: results.testPassed,
377
520
  testCoverage: results.testCoverage,
521
+ verificationEvidenceIds,
522
+ verifiedAt: Date.now(),
378
523
  };
379
524
  await store.update(args['task-id'], { payload: updatedPayload });
380
525
  // Attempt FSM transition to COMPLETED
381
526
  const allPassed = results.buildStatus === 'success' &&
527
+ (results.buildExitCode ?? 1) === 0 &&
382
528
  results.lintStatus === 'success' &&
383
- results.testPassed === true;
529
+ results.testPassed === true &&
530
+ (results.testCoverage ?? 0) >= 80 &&
531
+ results.securityPassed === true;
384
532
  let transitionResult = null;
385
533
  if (allPassed) {
386
534
  try {
@@ -388,20 +536,20 @@ export const phaseVerify = defineCommand({
388
536
  actor: { kind: 'human', userId: 'cli' }
389
537
  });
390
538
  if (!args.json)
391
- console.log('\n✅ Task marked COMPLETED');
539
+ console.log('\nTask marked COMPLETED');
392
540
  }
393
541
  catch (e) {
394
542
  const error = e;
395
543
  if (!args.json)
396
- console.log('\n⚠️ FSM transition failed:', error.message);
544
+ console.log('\nFSM transition failed:', error.message);
397
545
  }
398
546
  }
399
547
  const passed = allPassed && (transitionResult?.success ?? false);
400
- const result = { phase: 'VERIFY', taskId: args['task-id'], results, passed };
548
+ const result = { phase: 'VERIFY', taskId: args['task-id'], results, evidenceIds: verificationEvidenceIds, passed };
401
549
  if (args.json)
402
550
  console.log(JSON.stringify(result, null, 2));
403
551
  else {
404
- console.log(`\n📊 VERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
552
+ console.log(`\nVERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
405
553
  if (passed)
406
554
  console.log(`\n Next: scale review\n`);
407
555
  else
@@ -409,9 +557,107 @@ export const phaseVerify = defineCommand({
409
557
  }
410
558
  },
411
559
  });
412
- // REVIEW Phase
560
+ async function runGit(args) {
561
+ const { execa } = await import('execa');
562
+ const result = await execa('git', args, { reject: false });
563
+ return {
564
+ exitCode: result.exitCode ?? 1,
565
+ stdout: result.stdout ?? '',
566
+ stderr: result.stderr ?? '',
567
+ };
568
+ }
569
+ function mergeUntrackedFilesIntoStatus(statusOutput, untrackedOutput) {
570
+ const existing = new Set(parseChangedFiles(statusOutput).map(file => file.path.replace(/\\/g, '/')));
571
+ const additions = untrackedOutput
572
+ .split('\n')
573
+ .map(line => line.trim())
574
+ .filter(Boolean)
575
+ .filter(path => shouldReviewFile(path))
576
+ .filter(path => !existing.has(path.replace(/\\/g, '/')));
577
+ return [statusOutput.trim(), ...additions].filter(Boolean).join('\n');
578
+ }
579
+ function readUntrackedFileAsDiff(path) {
580
+ try {
581
+ const stat = statSync(path);
582
+ if (!stat.isFile() || stat.size > 250_000)
583
+ return '';
584
+ const content = readFileSync(path, 'utf-8');
585
+ if (content.includes('\u0000'))
586
+ return '';
587
+ return content
588
+ .split('\n')
589
+ .slice(0, 2000)
590
+ .map(line => `+${line}`)
591
+ .join('\n');
592
+ }
593
+ catch {
594
+ return '';
595
+ }
596
+ }
597
+ async function reviewGitChanges(taskPayload) {
598
+ const status = await runGit(['status', '--short']);
599
+ const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
600
+ const statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
601
+ const verificationEvidence = getVerificationEvidenceSummary(taskPayload?.verificationEvidenceIds);
602
+ const changedFiles = analyzeReview({ statusOutput, diffs: [], taskPayload, verificationEvidence }).changedFiles;
603
+ const diffs = [];
604
+ for (const file of changedFiles.slice(0, 50)) {
605
+ if (file.status === '??') {
606
+ diffs.push({ file: file.path, text: readUntrackedFileAsDiff(file.path) });
607
+ }
608
+ else {
609
+ const diff = await runGit(['diff', '--', file.path]);
610
+ diffs.push({ file: file.path, text: diff.stdout });
611
+ }
612
+ }
613
+ return analyzeReview({ statusOutput, diffs, taskPayload, verificationEvidence });
614
+ }
615
+ function collectReviewedFiles(records) {
616
+ const reviewed = new Set();
617
+ for (const record of records) {
618
+ if (!record.passed)
619
+ continue;
620
+ for (const file of record.changedFiles) {
621
+ if (shouldReviewFile(file))
622
+ reviewed.add(normalizeGitPath(file));
623
+ }
624
+ }
625
+ return reviewed;
626
+ }
627
+ async function getReviewableGitChanges() {
628
+ const status = await runGit(['status', '--short']);
629
+ const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
630
+ const statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
631
+ return parseChangedFiles(statusOutput).filter(file => shouldReviewFile(file.path));
632
+ }
633
+ async function stageReviewedFiles(reviewRecords) {
634
+ const reviewedFiles = collectReviewedFiles(reviewRecords);
635
+ const currentChanges = await getReviewableGitChanges();
636
+ const stagedFiles = [];
637
+ const unreviewedFiles = [];
638
+ for (const file of currentChanges) {
639
+ const normalizedPath = normalizeGitPath(file.path);
640
+ if (reviewedFiles.has(normalizedPath)) {
641
+ stagedFiles.push(file.path);
642
+ }
643
+ else {
644
+ unreviewedFiles.push(file.path);
645
+ }
646
+ }
647
+ if (unreviewedFiles.length > 0) {
648
+ return { stagedFiles: [], unreviewedFiles };
649
+ }
650
+ if (stagedFiles.length > 0) {
651
+ const gitAdd = await runGit(['add', '--', ...stagedFiles]);
652
+ if (gitAdd.exitCode !== 0) {
653
+ throw new Error(gitAdd.stderr || 'git add failed');
654
+ }
655
+ }
656
+ return { stagedFiles, unreviewedFiles: [] };
657
+ }
658
+ // REVIEW Phase - KarpathyEvaluator + deterministic review evidence
413
659
  export const phaseReview = defineCommand({
414
- meta: { name: 'review', description: 'REVIEW: Code review (/review)' },
660
+ meta: { name: 'review', description: 'REVIEW: Code review with Karpathy Principles (/review)' },
415
661
  args: {
416
662
  'task-id': { type: 'positional', required: false },
417
663
  'check-security': { type: 'boolean', default: true },
@@ -419,100 +665,123 @@ export const phaseReview = defineCommand({
419
665
  json: { type: 'boolean', default: false },
420
666
  },
421
667
  async run({ args }) {
422
- const { store } = getEngine();
668
+ const { store, workflowEngine } = getEngine();
669
+ const reviewStore = new ReviewStore(SCALE_DIR);
423
670
  // If task-id provided, validate task exists
424
671
  let task = null;
672
+ let taskPayload;
425
673
  if (args['task-id']) {
426
674
  task = await store.get(args['task-id']);
427
675
  if (!task || task.type !== 'Task') {
428
- console.error(`\n❌ Task not found: ${args['task-id']}\n`);
676
+ console.error(`\nTask not found: ${args['task-id']}\n`);
429
677
  process.exit(1);
430
678
  }
679
+ taskPayload = task.payload;
680
+ }
681
+ // === WorkflowEngine Integration ===
682
+ // Step 1: Karpathy Principles Check
683
+ const karpathyResult = workflowEngine.checkKarpathy({
684
+ hypothesesListed: true, // Would be determined from actual context
685
+ hasExtraFeatures: false, // Would be determined from actual context
686
+ changesTraceable: true, // Would be determined from actual context
687
+ hasVerifiableGoal: true // Would be determined from actual context
688
+ });
689
+ if (!args.json) {
690
+ console.log('\nKarpathy Principles Check:');
691
+ console.log(workflowEngine.getKarpathyEvaluator().formatReport());
692
+ }
693
+ const review = await reviewGitChanges(taskPayload);
694
+ const findings = review.findings;
695
+ const summary = summarizeFindings(findings);
696
+ const passed = summary.critical === 0 && summary.high === 0;
697
+ const record = reviewStore.saveReview({
698
+ taskId: args['task-id'],
699
+ passed,
700
+ findings,
701
+ changedFiles: review.changedFiles.map(file => file.path),
702
+ summary,
703
+ });
704
+ if (task && taskPayload) {
705
+ const updatedPayload = {
706
+ ...taskPayload,
707
+ reviewPassed: passed,
708
+ reviewEvidenceIds: [...(taskPayload.reviewEvidenceIds ?? []), record.id],
709
+ reviewedAt: Date.now(),
710
+ };
711
+ await store.update(task.id, { payload: updatedPayload });
431
712
  }
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
713
  const result = {
462
714
  phase: 'REVIEW',
463
715
  taskId: args['task-id'],
716
+ reviewId: record.id,
464
717
  findings,
465
- summary: { critical, high, medium, low },
718
+ changedFiles: review.changedFiles.map(file => file.path),
719
+ summary,
466
720
  passed,
467
721
  recommendation: passed ? 'Ready to ship' : 'Fix CRITICAL issues before shipping'
468
722
  };
469
723
  if (args.json)
470
724
  console.log(JSON.stringify(result, null, 2));
471
725
  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('└─────────────────────────────────────────────┘');
726
+ console.log('\nREVIEW Phase');
727
+ console.log(`\nReview evidence: ${record.id}`);
728
+ console.log('\nReview Findings:');
729
+ console.log('----------------------------------------');
730
+ console.log(`CRITICAL: ${summary.critical} issues ${summary.critical > 0 ? 'BLOCKED' : 'OK'}`);
731
+ console.log(`HIGH: ${summary.high} issues ${summary.high > 0 ? 'BLOCKED' : 'OK'}`);
732
+ console.log(`MEDIUM: ${summary.medium} issues`);
733
+ console.log(`LOW: ${summary.low} issues`);
734
+ console.log('----------------------------------------');
735
+ findings.slice(0, 10).forEach(f => console.log(` [${f.severity}] ${f.file ? `${f.file}: ` : ''}${f.description}`));
480
736
  if (passed) {
481
- console.log('\n✅ Review passed (no CRITICAL issues)');
737
+ console.log('\nReview passed (no CRITICAL issues)');
482
738
  console.log('\n Next: scale ship ' + (args['task-id'] ?? '<task-id>') + '\n');
483
739
  }
484
740
  else {
485
- console.log('\n❌ Review blocked by CRITICAL issues');
741
+ console.log('\nReview blocked by CRITICAL issues');
486
742
  console.log('\n Fix critical issues, then: scale review\n');
487
743
  }
488
744
  }
489
745
  },
490
746
  });
491
- // SHIP Phase
747
+ // SHIP Phase - HonestDelivery
492
748
  export const phaseShip = defineCommand({
493
- meta: { name: 'ship', description: 'SHIP: Commit and complete (/ship)' },
749
+ meta: { name: 'ship', description: 'SHIP: Commit with HonestDelivery Report (/ship)' },
494
750
  args: {
495
751
  'task-id': { type: 'positional', required: true },
496
752
  message: { type: 'string', alias: 'm', description: 'Commit message' },
497
753
  'no-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
754
+ 'skip-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
498
755
  json: { type: 'boolean', default: false },
499
756
  },
500
757
  async run({ args }) {
501
- const { store, fsm } = getEngine();
758
+ const { store, fsm, workflowEngine } = getEngine();
502
759
  // Validate task exists
503
760
  const task = await store.get(args['task-id']);
504
761
  if (!task || task.type !== 'Task') {
505
- console.error(`\n❌ Task not found: ${args['task-id']}\n`);
762
+ console.error(`\nTask not found: ${args['task-id']}\n`);
506
763
  process.exit(1);
507
764
  }
508
765
  // Check if task is completed (or attempt transition)
509
766
  const payload = task.payload;
767
+ const evidenceValidation = validateVerificationEvidence(payload.verificationEvidenceIds);
768
+ const reviewValidation = validateReviewEvidence(payload.reviewEvidenceIds);
510
769
  const verificationPassed = payload.buildStatus === 'success' &&
770
+ (payload.buildExitCode ?? 1) === 0 &&
511
771
  payload.lintStatus === 'success' &&
512
- payload.testPassed === true;
772
+ payload.testPassed === true &&
773
+ (payload.testCoverage ?? 0) >= 80 &&
774
+ evidenceValidation.ok;
775
+ const reviewPassed = payload.reviewPassed === true && reviewValidation.ok;
513
776
  if (task.status !== 'COMPLETED') {
514
777
  if (!verificationPassed) {
515
- console.error('\n❌ Task not verified. Run: scale verify ' + args['task-id'] + '\n');
778
+ console.error('\nTask not verified with persisted evidence. Run: scale verify ' + args['task-id'] + '\n');
779
+ if (evidenceValidation.missing.length > 0) {
780
+ console.error('Missing evidence records: ' + evidenceValidation.missing.join(', '));
781
+ }
782
+ if (evidenceValidation.failed.length > 0) {
783
+ console.error('Failed evidence records: ' + evidenceValidation.failed.join(', '));
784
+ }
516
785
  process.exit(1);
517
786
  }
518
787
  // Attempt FSM transition
@@ -523,25 +792,55 @@ export const phaseShip = defineCommand({
523
792
  }
524
793
  catch (e) {
525
794
  const error = e;
526
- console.error('\n❌ FSM transition failed:', error.message);
795
+ console.error('\nFSM transition failed:', error.message);
527
796
  console.log('\n Run verification first: scale verify ' + args['task-id'] + '\n');
528
797
  process.exit(1);
529
798
  }
530
799
  }
800
+ if (!reviewPassed) {
801
+ console.error('\nTask not reviewed with persisted passing evidence. Run: scale review ' + args['task-id'] + '\n');
802
+ if (reviewValidation.missing.length > 0) {
803
+ console.error('Missing review records: ' + reviewValidation.missing.join(', '));
804
+ }
805
+ if (reviewValidation.failed.length > 0) {
806
+ console.error('Failed review records: ' + reviewValidation.failed.join(', '));
807
+ }
808
+ process.exit(1);
809
+ }
531
810
  // Git operations
532
811
  let commitHash = null;
533
- if (!args['no-commit']) {
534
- const { execa } = await import('execa');
812
+ let stagedFiles = [];
813
+ if (!shouldSkipCommit(args['skip-commit'])) {
535
814
  const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
536
815
  try {
537
- await execa('git', ['add', '.']);
538
- const result = await execa('git', ['commit', '-m', commitMessage]);
539
- commitHash = result.stdout.split('\n')[0]; // First line contains hash
816
+ const reviewRecords = getValidatedReviewRecords(payload.reviewEvidenceIds);
817
+ const stageResult = await stageReviewedFiles(reviewRecords);
818
+ if (stageResult.unreviewedFiles.length > 0) {
819
+ console.error('\nUnreviewed working tree changes detected. Re-run scale review before shipping.');
820
+ stageResult.unreviewedFiles.forEach(file => console.error(' - ' + file));
821
+ console.error('\nUse scale ship ' + args['task-id'] + ' --no-commit to generate the delivery report without committing.\n');
822
+ process.exit(1);
823
+ }
824
+ stagedFiles = stageResult.stagedFiles;
825
+ const result = await runGit(['commit', '-m', commitMessage]);
826
+ if (result.exitCode !== 0) {
827
+ const message = result.stderr || result.stdout || 'git commit failed';
828
+ if (/nothing to commit|no changes added/i.test(message)) {
829
+ if (!args.json)
830
+ console.log(' Git commit skipped: nothing to commit');
831
+ }
832
+ else {
833
+ throw new Error(message);
834
+ }
835
+ }
836
+ else {
837
+ commitHash = result.stdout.split('\n')[0]; // First line contains hash
838
+ }
540
839
  }
541
840
  catch (e) {
542
841
  const error = e;
543
- if (!args.json)
544
- console.log(' ⚠️ Git commit skipped:', error.message);
842
+ console.error('\nGit commit failed:', error.message);
843
+ process.exit(1);
545
844
  }
546
845
  }
547
846
  // Update Plan to DONE if Task completed
@@ -552,20 +851,62 @@ export const phaseShip = defineCommand({
552
851
  }
553
852
  catch { }
554
853
  }
854
+ // === WorkflowEngine Integration ===
855
+ // Generate HonestDelivery report
856
+ if (!args.json) {
857
+ console.log('\nHonest Delivery Report:');
858
+ console.log('-'.repeat(40));
859
+ console.log(`[COMPLETED]`);
860
+ console.log(` - Task: ${args['task-id']}`);
861
+ console.log(` - Status: COMPLETED`);
862
+ if (commitHash)
863
+ console.log(` - Commit: ${commitHash}`);
864
+ if (stagedFiles.length)
865
+ console.log(` - Files committed: ${stagedFiles.length}`);
866
+ console.log('');
867
+ console.log(`[VERIFIED]`);
868
+ console.log(' [PASS] Build: passed');
869
+ console.log(' [PASS] Lint: passed');
870
+ console.log(' [PASS] Tests: passed');
871
+ if (payload.testCoverage)
872
+ console.log(` [PASS] Coverage: ${payload.testCoverage}%`);
873
+ if (payload.verificationEvidenceIds?.length) {
874
+ console.log(` [PASS] Evidence records validated: ${payload.verificationEvidenceIds.join(', ')}`);
875
+ }
876
+ if (payload.reviewEvidenceIds?.length) {
877
+ console.log(` [PASS] Review records validated: ${payload.reviewEvidenceIds.join(', ')}`);
878
+ }
879
+ console.log('');
880
+ // Check for unverified items
881
+ const unverifiedItems = [];
882
+ if (!payload.testCoverage || payload.testCoverage < 80) {
883
+ unverifiedItems.push('Coverage below 80%');
884
+ }
885
+ if (unverifiedItems.length > 0) {
886
+ console.log(`[UNVERIFIED]`);
887
+ unverifiedItems.forEach(item => console.log(` [UNVERIFIED] ${item}`));
888
+ console.log('');
889
+ }
890
+ }
555
891
  const result = {
556
892
  phase: 'SHIP',
557
893
  taskId: args['task-id'],
558
894
  status: 'COMPLETED',
895
+ verificationEvidenceIds: payload.verificationEvidenceIds ?? [],
896
+ evidenceValidation,
897
+ reviewEvidenceIds: payload.reviewEvidenceIds ?? [],
898
+ reviewValidation,
559
899
  commitHash,
900
+ stagedFiles,
560
901
  };
561
902
  if (args.json)
562
903
  console.log(JSON.stringify(result, null, 2));
563
904
  else {
564
- console.log('\n🚀 SHIP Phase');
565
- console.log('\n✅ Task COMPLETED: ' + args['task-id']);
905
+ console.log('\nSHIP Phase');
906
+ console.log('\nTask COMPLETED: ' + args['task-id']);
566
907
  if (commitHash)
567
908
  console.log(' Commit: ' + commitHash);
568
- console.log('\n🎉 Done! Feature shipped.\n');
909
+ console.log('\nDone. Feature shipped.\n');
569
910
  }
570
911
  },
571
912
  });