@hongmaple0820/scale-engine 0.48.0 → 0.50.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 (242) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/agents/evidenceDiscipline.d.ts +7 -0
  4. package/dist/agents/evidenceDiscipline.js +21 -0
  5. package/dist/agents/evidenceDiscipline.js.map +1 -0
  6. package/dist/agents/profiles.js +8 -1
  7. package/dist/agents/profiles.js.map +1 -1
  8. package/dist/agents/types.d.ts +1 -0
  9. package/dist/api/DashboardHttpConfig.d.ts +28 -0
  10. package/dist/api/DashboardHttpConfig.js +110 -0
  11. package/dist/api/DashboardHttpConfig.js.map +1 -0
  12. package/dist/api/cli.js +102 -11
  13. package/dist/api/cli.js.map +1 -1
  14. package/dist/api/http.d.ts +1 -0
  15. package/dist/api/http.js +50 -0
  16. package/dist/api/http.js.map +1 -0
  17. package/dist/artifact/types.d.ts +64 -0
  18. package/dist/artifact/types.js.map +1 -1
  19. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  20. package/dist/bootstrap/DependencyBootstrap.js +14 -3
  21. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  22. package/dist/cli/cortexApplyCommand.d.ts +26 -0
  23. package/dist/cli/cortexApplyCommand.js +74 -0
  24. package/dist/cli/cortexApplyCommand.js.map +1 -0
  25. package/dist/cli/cortexCandidateCommands.d.ts +42 -0
  26. package/dist/cli/cortexCandidateCommands.js +119 -0
  27. package/dist/cli/cortexCandidateCommands.js.map +1 -0
  28. package/dist/cli/cortexCommands.d.ts +51 -0
  29. package/dist/cli/cortexCommands.js +127 -13
  30. package/dist/cli/cortexCommands.js.map +1 -1
  31. package/dist/cli/engineBootstrap.d.ts +1 -1
  32. package/dist/cli/engineBootstrap.js +2 -0
  33. package/dist/cli/engineBootstrap.js.map +1 -1
  34. package/dist/cli/evalCommands.js +13 -1
  35. package/dist/cli/evalCommands.js.map +1 -1
  36. package/dist/cli/phaseCommands.d.ts +81 -1
  37. package/dist/cli/phaseCommands.js +465 -31
  38. package/dist/cli/phaseCommands.js.map +1 -1
  39. package/dist/cli/runtimeSkillCommands.js +12 -2
  40. package/dist/cli/runtimeSkillCommands.js.map +1 -1
  41. package/dist/cli/shieldCommands.d.ts +1 -0
  42. package/dist/cli/shieldCommands.js +20 -7
  43. package/dist/cli/shieldCommands.js.map +1 -1
  44. package/dist/cli/workflowEvidenceCommands.d.ts +120 -0
  45. package/dist/cli/workflowEvidenceCommands.js +228 -2
  46. package/dist/cli/workflowEvidenceCommands.js.map +1 -1
  47. package/dist/cortex/AutoFixEventObservations.d.ts +11 -0
  48. package/dist/cortex/AutoFixEventObservations.js +72 -0
  49. package/dist/cortex/AutoFixEventObservations.js.map +1 -0
  50. package/dist/cortex/GateEvidenceObservations.d.ts +22 -0
  51. package/dist/cortex/GateEvidenceObservations.js +179 -0
  52. package/dist/cortex/GateEvidenceObservations.js.map +1 -0
  53. package/dist/cortex/GovernanceMetrics.d.ts +2 -0
  54. package/dist/cortex/GovernanceMetrics.js +112 -22
  55. package/dist/cortex/GovernanceMetrics.js.map +1 -1
  56. package/dist/cortex/InstinctApplicationRecorder.d.ts +28 -0
  57. package/dist/cortex/InstinctApplicationRecorder.js +145 -0
  58. package/dist/cortex/InstinctApplicationRecorder.js.map +1 -0
  59. package/dist/cortex/InstinctCandidateAudit.d.ts +3 -0
  60. package/dist/cortex/InstinctCandidateAudit.js +39 -0
  61. package/dist/cortex/InstinctCandidateAudit.js.map +1 -0
  62. package/dist/cortex/InstinctCandidateReview.d.ts +32 -0
  63. package/dist/cortex/InstinctCandidateReview.js +125 -0
  64. package/dist/cortex/InstinctCandidateReview.js.map +1 -0
  65. package/dist/cortex/InstinctExtractor.d.ts +1 -0
  66. package/dist/cortex/InstinctExtractor.js +24 -17
  67. package/dist/cortex/InstinctExtractor.js.map +1 -1
  68. package/dist/cortex/InstinctRuntimeEvidence.d.ts +14 -0
  69. package/dist/cortex/InstinctRuntimeEvidence.js +120 -0
  70. package/dist/cortex/InstinctRuntimeEvidence.js.map +1 -0
  71. package/dist/cortex/InstinctStore.d.ts +50 -4
  72. package/dist/cortex/InstinctStore.js +262 -48
  73. package/dist/cortex/InstinctStore.js.map +1 -1
  74. package/dist/cortex/InstinctValidation.d.ts +9 -0
  75. package/dist/cortex/InstinctValidation.js +55 -0
  76. package/dist/cortex/InstinctValidation.js.map +1 -0
  77. package/dist/cortex/SessionInjector.d.ts +1 -0
  78. package/dist/cortex/SessionInjector.js +28 -8
  79. package/dist/cortex/SessionInjector.js.map +1 -1
  80. package/dist/dashboard/DashboardServer.d.ts +79 -0
  81. package/dist/dashboard/DashboardServer.js +330 -6
  82. package/dist/dashboard/DashboardServer.js.map +1 -1
  83. package/dist/dashboard/spa/app.js +515 -0
  84. package/dist/dashboard/spa/components/DataTable.js +53 -0
  85. package/dist/dashboard/spa/components/EventStream.js +66 -0
  86. package/dist/dashboard/spa/components/LoadingState.js +39 -0
  87. package/dist/dashboard/spa/components/MetricCard.js +30 -0
  88. package/dist/dashboard/spa/components/Panel.js +27 -0
  89. package/dist/dashboard/spa/components/StatusBadge.js +51 -0
  90. package/dist/dashboard/spa/i18n.js +767 -0
  91. package/dist/dashboard/spa/index.html +463 -0
  92. package/dist/dashboard/spa/pages/costs.js +522 -0
  93. package/dist/dashboard/spa/pages/documents.js +540 -0
  94. package/dist/dashboard/spa/pages/knowledge.js +457 -0
  95. package/dist/dashboard/spa/pages/monitoring.js +361 -0
  96. package/dist/dashboard/spa/pages/overview.js +301 -0
  97. package/dist/dashboard/spa/pages/topology-renderers.js +251 -0
  98. package/dist/dashboard/spa/pages/topology.js +370 -0
  99. package/dist/dashboard/spa/pages/workflow-renderers.js +239 -0
  100. package/dist/dashboard/spa/pages/workflow.js +217 -0
  101. package/dist/env/EnvironmentDoctor.js +12 -7
  102. package/dist/env/EnvironmentDoctor.js.map +1 -1
  103. package/dist/eval/BenchmarkPublisher.d.ts +2 -0
  104. package/dist/eval/BenchmarkPublisher.js +43 -0
  105. package/dist/eval/BenchmarkPublisher.js.map +1 -1
  106. package/dist/eval/WorkflowEval.d.ts +9 -0
  107. package/dist/eval/WorkflowEval.js +348 -2
  108. package/dist/eval/WorkflowEval.js.map +1 -1
  109. package/dist/guardrails/ast/confirmers.d.ts +18 -0
  110. package/dist/guardrails/ast/confirmers.js +69 -0
  111. package/dist/guardrails/ast/confirmers.js.map +1 -0
  112. package/dist/guardrails/ast/parse.d.ts +20 -0
  113. package/dist/guardrails/ast/parse.js +51 -0
  114. package/dist/guardrails/ast/parse.js.map +1 -0
  115. package/dist/memory/MemoryBrain.d.ts +13 -0
  116. package/dist/memory/MemoryBrain.js +47 -0
  117. package/dist/memory/MemoryBrain.js.map +1 -1
  118. package/dist/memory/MemoryFabric.d.ts +1 -0
  119. package/dist/memory/MemoryFabric.js +12 -8
  120. package/dist/memory/MemoryFabric.js.map +1 -1
  121. package/dist/memory/MemoryLearning.d.ts +1 -0
  122. package/dist/memory/MemoryLearning.js +6 -3
  123. package/dist/memory/MemoryLearning.js.map +1 -1
  124. package/dist/memory/MemoryProviders.d.ts +8 -1
  125. package/dist/memory/MemoryProviders.js +143 -29
  126. package/dist/memory/MemoryProviders.js.map +1 -1
  127. package/dist/output/HTMLDocumentRenderer.d.ts +9 -0
  128. package/dist/output/HTMLDocumentRenderer.js +19 -0
  129. package/dist/output/HTMLDocumentRenderer.js.map +1 -1
  130. package/dist/review/FreshContextVerifier.d.ts +35 -0
  131. package/dist/review/FreshContextVerifier.js +120 -0
  132. package/dist/review/FreshContextVerifier.js.map +1 -0
  133. package/dist/review/JsonLlmClient.d.ts +37 -0
  134. package/dist/review/JsonLlmClient.js +94 -0
  135. package/dist/review/JsonLlmClient.js.map +1 -0
  136. package/dist/review/LlmJudge.d.ts +61 -0
  137. package/dist/review/LlmJudge.js +167 -0
  138. package/dist/review/LlmJudge.js.map +1 -0
  139. package/dist/runtime/AiOsRuntime.d.ts +14 -1
  140. package/dist/runtime/AiOsRuntime.js +59 -3
  141. package/dist/runtime/AiOsRuntime.js.map +1 -1
  142. package/dist/runtime/RuntimeDoctor.js +3 -1
  143. package/dist/runtime/RuntimeDoctor.js.map +1 -1
  144. package/dist/runtime/RuntimeEvidenceLedger.d.ts +6 -0
  145. package/dist/runtime/RuntimeEvidenceLedger.js +52 -1
  146. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -1
  147. package/dist/runtime/SessionLedger.d.ts +2 -0
  148. package/dist/runtime/SessionLedger.js +4 -0
  149. package/dist/runtime/SessionLedger.js.map +1 -1
  150. package/dist/setup/SetupVerification.js +53 -5
  151. package/dist/setup/SetupVerification.js.map +1 -1
  152. package/dist/shield/PolicyCompiler.js +73 -12
  153. package/dist/shield/PolicyCompiler.js.map +1 -1
  154. package/dist/shield/ProtectedPaths.js +4 -2
  155. package/dist/shield/ProtectedPaths.js.map +1 -1
  156. package/dist/skills/SkillCatalog.d.ts +2 -0
  157. package/dist/skills/SkillCatalog.js +8 -0
  158. package/dist/skills/SkillCatalog.js.map +1 -1
  159. package/dist/skills/SkillDoctor.d.ts +19 -2
  160. package/dist/skills/SkillDoctor.js +163 -13
  161. package/dist/skills/SkillDoctor.js.map +1 -1
  162. package/dist/tools/SafeCommandRunner.d.ts +1 -0
  163. package/dist/tools/SafeCommandRunner.js +1 -0
  164. package/dist/tools/SafeCommandRunner.js.map +1 -1
  165. package/dist/tools/ToolCapabilityRegistry.js +25 -3
  166. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  167. package/dist/tools/ToolOrchestrator.js +21 -0
  168. package/dist/tools/ToolOrchestrator.js.map +1 -1
  169. package/dist/version.d.ts +1 -1
  170. package/dist/version.js +1 -1
  171. package/dist/workflow/AgentLoopReadiness.d.ts +103 -0
  172. package/dist/workflow/AgentLoopReadiness.js +371 -0
  173. package/dist/workflow/AgentLoopReadiness.js.map +1 -0
  174. package/dist/workflow/BoundaryEnforcement.d.ts +60 -0
  175. package/dist/workflow/BoundaryEnforcement.js +182 -0
  176. package/dist/workflow/BoundaryEnforcement.js.map +1 -0
  177. package/dist/workflow/EcosystemReadinessGate.d.ts +46 -0
  178. package/dist/workflow/EcosystemReadinessGate.js +126 -0
  179. package/dist/workflow/EcosystemReadinessGate.js.map +1 -0
  180. package/dist/workflow/EngineeringStandards.js +67 -12
  181. package/dist/workflow/EngineeringStandards.js.map +1 -1
  182. package/dist/workflow/GateCatalog.js +21 -2
  183. package/dist/workflow/GateCatalog.js.map +1 -1
  184. package/dist/workflow/GovernanceTemplatePacks.js +2 -26
  185. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  186. package/dist/workflow/GovernanceTemplates.js +8 -1
  187. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  188. package/dist/workflow/ProfileEnforcement.d.ts +7 -0
  189. package/dist/workflow/ProfileEnforcement.js +12 -0
  190. package/dist/workflow/ProfileEnforcement.js.map +1 -0
  191. package/dist/workflow/ReleaseDeploymentLedger.d.ts +63 -0
  192. package/dist/workflow/ReleaseDeploymentLedger.js +154 -0
  193. package/dist/workflow/ReleaseDeploymentLedger.js.map +1 -0
  194. package/dist/workflow/ReviewAnalyzer.js +50 -3
  195. package/dist/workflow/ReviewAnalyzer.js.map +1 -1
  196. package/dist/workflow/ReviewStore.d.ts +10 -0
  197. package/dist/workflow/ReviewStore.js.map +1 -1
  198. package/dist/workflow/SessionPreamble.d.ts +7 -0
  199. package/dist/workflow/SessionPreamble.js +48 -9
  200. package/dist/workflow/SessionPreamble.js.map +1 -1
  201. package/dist/workflow/SurfaceCoverage.d.ts +19 -0
  202. package/dist/workflow/SurfaceCoverage.js +57 -0
  203. package/dist/workflow/SurfaceCoverage.js.map +1 -0
  204. package/dist/workflow/VerificationCommands.d.ts +1 -0
  205. package/dist/workflow/VerificationCommands.js.map +1 -1
  206. package/dist/workflow/VerificationProfile.d.ts +5 -0
  207. package/dist/workflow/VerificationProfile.js +26 -0
  208. package/dist/workflow/VerificationProfile.js.map +1 -1
  209. package/dist/workflow/VerificationSchema.d.ts +3 -0
  210. package/dist/workflow/VerificationSchema.js +6 -0
  211. package/dist/workflow/VerificationSchema.js.map +1 -1
  212. package/dist/workflow/WorkflowEffectiveness.d.ts +97 -0
  213. package/dist/workflow/WorkflowEffectiveness.js +302 -0
  214. package/dist/workflow/WorkflowEffectiveness.js.map +1 -0
  215. package/dist/workflow/WorkflowEffectivenessRenderer.d.ts +2 -0
  216. package/dist/workflow/WorkflowEffectivenessRenderer.js +67 -0
  217. package/dist/workflow/WorkflowEffectivenessRenderer.js.map +1 -0
  218. package/dist/workflow/WorkflowEffectivenessScoring.d.ts +6 -0
  219. package/dist/workflow/WorkflowEffectivenessScoring.js +243 -0
  220. package/dist/workflow/WorkflowEffectivenessScoring.js.map +1 -0
  221. package/dist/workflow/gates/EnhancedGates.js +2 -0
  222. package/dist/workflow/gates/EnhancedGates.js.map +1 -1
  223. package/dist/workflow/gates/GateSystem.d.ts +16 -0
  224. package/dist/workflow/gates/GateSystem.js +208 -41
  225. package/dist/workflow/gates/GateSystem.js.map +1 -1
  226. package/dist/workflow/gates/MetaGovernanceGates.js +269 -8
  227. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -1
  228. package/dist/workflow/gates/TestIntegrityGate.d.ts +51 -0
  229. package/dist/workflow/gates/TestIntegrityGate.js +175 -0
  230. package/dist/workflow/gates/TestIntegrityGate.js.map +1 -0
  231. package/dist/workflow/types.d.ts +1 -1
  232. package/docs/guides/DEVELOPMENT_WORKFLOW.md +28 -0
  233. package/docs/reference/cli.md +2 -1
  234. package/docs/start/agent-governance-demo.md +1 -1
  235. package/docs/workflow/E2E_EXAMPLE.md +133 -0
  236. package/docs/workflow/README.md +7 -1
  237. package/docs/workflow/TEMPLATE_GUIDE.md +162 -0
  238. package/docs/workflow/templates/github-actions-scale-preflight.yml +4 -1
  239. package/docs/workflow/templates/plan.md +26 -0
  240. package/docs/workflow/templates/spec.md +28 -0
  241. package/package.json +7 -3
  242. package/scripts/workflow/run-vitest.mjs +123 -0
@@ -18,7 +18,12 @@ import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
18
18
  import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
19
19
  import { EvidenceStore } from '../workflow/EvidenceStore.js';
20
20
  import { ReviewStore } from '../workflow/ReviewStore.js';
21
+ import { JudgePromptStore, LlmJudge } from '../review/LlmJudge.js';
22
+ import { JsonLlmClient } from '../review/JsonLlmClient.js';
23
+ import { FreshContextVerifier } from '../review/FreshContextVerifier.js';
21
24
  import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
25
+ import { ReleaseDeploymentLedger } from '../workflow/ReleaseDeploymentLedger.js';
26
+ import { recordRuntimeInstinctApplications } from '../cortex/InstinctApplicationRecorder.js';
22
27
  import { appendVerificationArtifact, checkTaskArtifactCompleteness, scaffoldTaskArtifacts } from '../workflow/TaskArtifactScaffolder.js';
23
28
  import { createWorkflowGuidance, renderWorkflowGuidance } from '../workflow/WorkflowGuidance.js';
24
29
  import { blockingWorkflowOpenTasks, removeWorkflowOpenTask } from '../workflow/WorkflowOpenTasks.js';
@@ -33,6 +38,8 @@ import { loadToolPolicy } from '../tools/ToolPolicy.js';
33
38
  import { runSafeCommand } from '../tools/SafeCommandRunner.js';
34
39
  import { join } from 'node:path';
35
40
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
41
+ import { computeSurfaceCoverage, formatSurfaceCoverageWarnings } from '../workflow/SurfaceCoverage.js';
42
+ import { evaluateBoundaries, evaluateConstraints, formatBoundaryWarnings, formatConstraintWarnings, isEnforcedBoundaryProfile, countBoundaryBlockers, } from '../workflow/BoundaryEnforcement.js';
36
43
  import { HTMLDocumentRenderer } from '../output/HTMLDocumentRenderer.js';
37
44
  import { SCALE_ENGINE_VERSION } from '../version.js';
38
45
  import { optimizeCodingPrompt } from '../prompts/PromptOptimizer.js';
@@ -57,6 +64,7 @@ function validateReviewEvidence(ids) {
57
64
  const reviewStore = new ReviewStore(SCALE_DIR);
58
65
  const missing = [];
59
66
  const failed = [];
67
+ const records = [];
60
68
  for (const id of ids ?? []) {
61
69
  const record = reviewStore.getReview(id);
62
70
  if (!record) {
@@ -64,9 +72,14 @@ function validateReviewEvidence(ids) {
64
72
  }
65
73
  else if (!record.passed) {
66
74
  failed.push(id);
75
+ records.push(record);
76
+ }
77
+ else {
78
+ records.push(record);
67
79
  }
68
80
  }
69
- return { ok: (ids?.length ?? 0) > 0 && missing.length === 0 && failed.length === 0, missing, failed };
81
+ const latest = records.sort((a, b) => b.createdAt - a.createdAt)[0];
82
+ return { ok: (ids?.length ?? 0) > 0 && missing.length === 0 && latest?.passed === true, missing, failed };
70
83
  }
71
84
  function getValidatedReviewRecords(ids) {
72
85
  const reviewStore = new ReviewStore(SCALE_DIR);
@@ -321,12 +334,56 @@ async function recordVerificationMetric(options) {
321
334
  metricsStore.writeMarkdownReport(PROJECT_DIR);
322
335
  return record;
323
336
  }
337
+ async function recordShipDeployment(options) {
338
+ const version = optionalString(options.args['deploy-version']) ?? readPackageVersion(PROJECT_DIR);
339
+ const completedAt = optionalString(options.args['deployed-at']);
340
+ const commitTimestamp = await resolveCommitTimestamp(options.commitHash);
341
+ const notes = [
342
+ `task=${options.taskId}`,
343
+ options.taskTitle ? `title=${options.taskTitle}` : undefined,
344
+ options.taskPayload.verificationEvidenceIds?.length ? `verificationEvidence=${options.taskPayload.verificationEvidenceIds.join(',')}` : undefined,
345
+ options.taskPayload.reviewEvidenceIds?.length ? `reviewEvidence=${options.taskPayload.reviewEvidenceIds.join(',')}` : undefined,
346
+ ].filter((value) => Boolean(value)).join('; ');
347
+ return new ReleaseDeploymentLedger(SCALE_DIR).record({
348
+ service: optionalString(options.args['deploy-service']) ?? 'scale-engine',
349
+ environment: optionalString(options.args['deploy-environment']) ?? 'production',
350
+ status: 'succeeded',
351
+ version,
352
+ commitSha: options.commitHash,
353
+ commitTimestamp,
354
+ completedAt,
355
+ source: 'ship',
356
+ notes,
357
+ });
358
+ }
359
+ function optionalString(value) {
360
+ const normalized = typeof value === 'string' ? value.trim() : '';
361
+ return normalized ? normalized : undefined;
362
+ }
363
+ function readPackageVersion(projectDir) {
364
+ const pkgPath = join(projectDir, 'package.json');
365
+ if (!existsSync(pkgPath))
366
+ return undefined;
367
+ try {
368
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
369
+ return optionalString(pkg.version);
370
+ }
371
+ catch {
372
+ return undefined;
373
+ }
374
+ }
375
+ async function resolveCommitTimestamp(commitHash) {
376
+ const result = await runGit(['show', '-s', '--format=%cI', commitHash]);
377
+ if (result.exitCode !== 0)
378
+ return undefined;
379
+ return optionalString(result.stdout);
380
+ }
324
381
  // Helper: Generate spec markdown file
325
- function generateSpecMarkdown(id, title, payload) {
382
+ function generateSpecMarkdown(id, title, payload, status = 'FROZEN') {
326
383
  return `# Spec: ${title}
327
384
 
328
385
  **ID**: ${id}
329
- **Status**: FROZEN
386
+ **Status**: ${status}
330
387
  **Ambiguity Score**: ${payload.ambiguityScore ?? 0.15}
331
388
 
332
389
  ## What
@@ -343,11 +400,34 @@ ${payload.edgeCases.map(e => `- ${e}`).join('\n') || '(none defined)'}
343
400
 
344
401
  ## North Star
345
402
  ${payload.northStar || 'User value delivered'}
346
-
403
+ ${renderSpecContractSections(payload)}
347
404
  ---
348
405
  *Generated by SCALE Engine DEFINE phase*
349
406
  `;
350
407
  }
408
+ // Helper: Render the optional P0 six-element contract sections.
409
+ // Each section is omitted when its field is unset, keeping legacy specs unchanged.
410
+ function renderSpecContractSections(payload) {
411
+ const sections = [];
412
+ if (payload.verificationSurface?.length) {
413
+ sections.push(`\n## Verification Surface\n${payload.verificationSurface.map(s => `- ${s}`).join('\n')}`);
414
+ }
415
+ if (payload.constraints?.length) {
416
+ sections.push(`\n## Constraints\n${payload.constraints.map(c => `- ${c}`).join('\n')}`);
417
+ }
418
+ if (payload.boundaries) {
419
+ const b = payload.boundaries;
420
+ const line = (label, items) => `- ${label}: ${items.length ? items.join(', ') : '(none)'}`;
421
+ sections.push(`\n## Boundaries\n${line('Files', b.files)}\n${line('Tools', b.tools)}\n${line('Forbidden', b.forbidden)}`);
422
+ }
423
+ if (payload.iterationStrategy) {
424
+ sections.push(`\n## Iteration Strategy\n${payload.iterationStrategy}`);
425
+ }
426
+ if (payload.blockedStopCondition) {
427
+ sections.push(`\n## Blocked Stop Condition\n${payload.blockedStopCondition}`);
428
+ }
429
+ return sections.length ? `\n${sections.join('\n')}\n` : '\n';
430
+ }
351
431
  // Helper: Calculate ambiguity score
352
432
  function calculateAmbiguityScore(description, successCriteria) {
353
433
  let score = 0.2; // Base score (maximum threshold)
@@ -364,9 +444,20 @@ function calculateAmbiguityScore(description, successCriteria) {
364
444
  export const phaseDefine = defineCommand({
365
445
  meta: { name: 'define', description: 'DEFINE: Create Spec with AmbiguityScorer + SocraticQuestioner (/spec)' },
366
446
  args: {
367
- title: { type: 'positional', required: true },
447
+ title: { type: 'positional', required: false },
368
448
  description: { type: 'string', alias: 'd' },
369
449
  'success-criteria': { type: 'string', alias: 'c', description: 'Comma-separated criteria' },
450
+ // P0 draft/confirm two-step lifecycle (backward compatible: bare `define` still auto-freezes)
451
+ draft: { type: 'boolean', default: false, description: 'Stop the new Spec at REVIEWING (requires `define --confirm <id>` to FROZEN)' },
452
+ confirm: { type: 'string', description: 'Confirm and freeze an existing draft Spec id (REVIEWING -> FROZEN)' },
453
+ // P0 six-element contract inputs (optional, comma-separated where plural)
454
+ 'verification-surface': { type: 'string', description: 'Comma-separated evidence sources: test names / benchmark commands / artifact paths' },
455
+ 'constraints': { type: 'string', description: 'Comma-separated invariants that must not regress (perf/security/compat)' },
456
+ 'boundary-files': { type: 'string', description: 'Comma-separated files allowed to change' },
457
+ 'boundary-tools': { type: 'string', description: 'Comma-separated tools allowed to use' },
458
+ 'boundary-forbidden': { type: 'string', description: 'Comma-separated scope that must not be touched' },
459
+ 'iteration-strategy': { type: 'string', description: 'How each build iteration decides the next step' },
460
+ 'blocked-stop': { type: 'string', description: 'What to report / what is needed to unblock when no path is viable' },
370
461
  // Socratic refinement answers (optional)
371
462
  'goal': { type: 'string', description: 'Goal answer for Socratic refinement' },
372
463
  'constraint': { type: 'string', description: 'Constraint answer for Socratic refinement' },
@@ -380,6 +471,15 @@ export const phaseDefine = defineCommand({
380
471
  },
381
472
  async run({ args }) {
382
473
  const { store, fsm, workflowEngine } = getEngine();
474
+ // P0: --confirm freezes an existing draft Spec (REVIEWING -> FROZEN) without re-creating it.
475
+ if (args.confirm) {
476
+ await confirmDraftSpec(store, fsm, String(args.confirm), Boolean(args.json));
477
+ return;
478
+ }
479
+ if (!args.title) {
480
+ console.error('\nMissing required argument: title (or pass --confirm <spec-id> to freeze a draft)\n');
481
+ process.exit(1);
482
+ }
383
483
  const rawDesc = String(args.description ?? args.title);
384
484
  // Parse success criteria
385
485
  const successCriteria = args['success-criteria']
@@ -467,6 +567,17 @@ export const phaseDefine = defineCommand({
467
567
  initialStatus: 'DRAFT',
468
568
  createdBy: { kind: 'human', userId: 'cli' },
469
569
  });
570
+ // P0 six-element contract inputs (optional; omitted fields stay undefined)
571
+ const csv = (v) => {
572
+ const items = typeof v === 'string' ? v.split(',').map(s => s.trim()).filter(Boolean) : [];
573
+ return items.length ? items : undefined;
574
+ };
575
+ const boundaryFiles = csv(args['boundary-files']);
576
+ const boundaryTools = csv(args['boundary-tools']);
577
+ const boundaryForbidden = csv(args['boundary-forbidden']);
578
+ const boundaries = (boundaryFiles || boundaryTools || boundaryForbidden)
579
+ ? { files: boundaryFiles ?? [], tools: boundaryTools ?? [], forbidden: boundaryForbidden ?? [] }
580
+ : undefined;
470
581
  // Create Spec artifact with proper payload (use refined requirement if available)
471
582
  const specPayload = {
472
583
  what: refinedRequirement,
@@ -475,6 +586,11 @@ export const phaseDefine = defineCommand({
475
586
  edgeCases: [],
476
587
  northStar: 'Deliver user value',
477
588
  ambiguityScore,
589
+ verificationSurface: csv(args['verification-surface']),
590
+ constraints: csv(args['constraints']),
591
+ boundaries,
592
+ iterationStrategy: typeof args['iteration-strategy'] === 'string' && args['iteration-strategy'] ? String(args['iteration-strategy']) : undefined,
593
+ blockedStopCondition: typeof args['blocked-stop'] === 'string' && args['blocked-stop'] ? String(args['blocked-stop']) : undefined,
478
594
  };
479
595
  const spec = await store.create({
480
596
  type: 'Spec', title: args.title,
@@ -483,11 +599,14 @@ export const phaseDefine = defineCommand({
483
599
  initialStatus: 'DRAFT',
484
600
  createdBy: { kind: 'human', userId: 'cli' },
485
601
  });
602
+ // Draft mode stops at REVIEWING; default mode auto-freezes (FROZEN).
603
+ const isDraft = Boolean(args.draft);
604
+ const finalStatus = isDraft ? 'REVIEWING' : 'FROZEN';
486
605
  // Generate spec markdown file
487
606
  const specsDir = join(SCALE_DIR, 'specs');
488
607
  ensureDir(specsDir);
489
608
  const specPath = join(specsDir, `${spec.id}.md`);
490
- writeFileSync(specPath, generateSpecMarkdown(spec.id, args.title, specPayload));
609
+ writeFileSync(specPath, generateSpecMarkdown(spec.id, args.title, specPayload, finalStatus));
491
610
  // Generate spec HTML file (default format: html)
492
611
  const outputFormat = args.format ?? 'md';
493
612
  let specHtmlPath;
@@ -496,7 +615,7 @@ export const phaseDefine = defineCommand({
496
615
  title: args.title,
497
616
  brand: args.brand,
498
617
  version: SCALE_ENGINE_VERSION,
499
- status: 'FROZEN',
618
+ status: finalStatus,
500
619
  });
501
620
  const html = renderer.renderSpec({
502
621
  id: spec.id,
@@ -507,11 +626,16 @@ export const phaseDefine = defineCommand({
507
626
  edgeCases: specPayload.edgeCases,
508
627
  northStar: specPayload.northStar,
509
628
  ambiguityScore,
629
+ verificationSurface: specPayload.verificationSurface,
630
+ constraints: specPayload.constraints,
631
+ boundaries: specPayload.boundaries,
632
+ iterationStrategy: specPayload.iterationStrategy,
633
+ blockedStopCondition: specPayload.blockedStopCondition,
510
634
  });
511
635
  specHtmlPath = join(specsDir, `${spec.id}.html`);
512
636
  renderer.writeToFile(html, specHtmlPath);
513
637
  }
514
- // FSM transitions: DRAFT -> REVIEWING -> FROZEN
638
+ // FSM transitions: DRAFT -> REVIEWING (-> FROZEN unless --draft)
515
639
  // Phase 1: refine (DRAFT -> REVIEWING) - no guards
516
640
  const refineResult = await fsm.canTransition(spec.id, 'refine');
517
641
  if (!refineResult.allowed) {
@@ -522,22 +646,30 @@ export const phaseDefine = defineCommand({
522
646
  process.exit(1);
523
647
  }
524
648
  await fsm.transition(spec.id, 'refine', { actor: { kind: 'system', component: 'phase-define' } });
525
- // Phase 2: approve (REVIEWING -> FROZEN) - guards: ambiguityScore <= 0.2, has successCriteria
526
- const approveResult = await fsm.canTransition(spec.id, 'approve');
527
- if (!approveResult.allowed) {
649
+ // Phase 2: approve (REVIEWING -> FROZEN) - guards: ambiguityScore <= 0.2, has successCriteria.
650
+ // Skipped in --draft mode: the draft waits for `scale define --confirm <id>`.
651
+ if (!isDraft) {
652
+ const approveResult = await fsm.canTransition(spec.id, 'approve');
653
+ if (!approveResult.allowed) {
654
+ if (!args.json) {
655
+ console.error('\nFSM transition blocked: REVIEWING -> FROZEN');
656
+ console.error(' Spec cannot be frozen due to:');
657
+ approveResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
658
+ console.error('\n Resolve issues before proceeding.');
659
+ }
660
+ process.exit(1);
661
+ }
662
+ await fsm.transition(spec.id, 'approve', { actor: { kind: 'system', component: 'phase-define' } });
528
663
  if (!args.json) {
529
- console.error('\nFSM transition blocked: REVIEWING -> FROZEN');
530
- console.error(' Spec cannot be frozen due to:');
531
- approveResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
532
- console.error('\n Resolve issues before proceeding.');
664
+ console.log(' FSM: DRAFT -> REVIEWING -> FROZEN');
533
665
  }
534
- process.exit(1);
535
666
  }
536
- await fsm.transition(spec.id, 'approve', { actor: { kind: 'system', component: 'phase-define' } });
537
- if (!args.json) {
538
- console.log(' FSM: DRAFT -> REVIEWING -> FROZEN ✓');
667
+ else if (!args.json) {
668
+ console.log(' FSM: DRAFT -> REVIEWING (draft; not yet FROZEN)');
539
669
  }
540
- const result = { phase: 'DEFINE', spec, specPath, specHtmlPath, ambiguityScore, successCriteria, format: outputFormat, promptOptimization };
670
+ // Refresh the spec so the reported status reflects the post-transition state.
671
+ const finalSpec = (await store.get(spec.id)) ?? spec;
672
+ const result = { phase: 'DEFINE', spec: finalSpec, specPath, specHtmlPath, ambiguityScore, successCriteria, format: outputFormat, promptOptimization, status: finalStatus, draft: isDraft };
541
673
  // Write explore artifact for Gate G1 verification
542
674
  const artifactWriter = new WorkflowArtifactWriter(SCALE_DIR);
543
675
  artifactWriter.writeExploreResult({
@@ -557,10 +689,94 @@ export const phaseDefine = defineCommand({
557
689
  console.log(` HTML file: ${specHtmlPath}`);
558
690
  console.log(` Ambiguity score: ${ambiguityScore.toFixed(2)}`);
559
691
  console.log(` Success criteria: ${successCriteria.length}`);
560
- console.log(`\n Next: scale plan ${spec.id}\n`);
692
+ if (isDraft) {
693
+ console.log(`\n Draft created (REVIEWING). Review, then confirm:`);
694
+ console.log(` Next: scale define --confirm ${spec.id}\n`);
695
+ }
696
+ else {
697
+ console.log(`\n Next: scale plan ${spec.id}\n`);
698
+ }
561
699
  }
562
700
  },
563
701
  });
702
+ // Helper: Confirm a draft Spec (REVIEWING -> FROZEN) for the `define --confirm <id>` flow.
703
+ async function confirmDraftSpec(store, fsm, specId, json) {
704
+ const spec = await store.get(specId);
705
+ if (!spec || spec.type !== 'Spec') {
706
+ console.error(`\nSpec not found: ${specId}\n`);
707
+ process.exit(1);
708
+ }
709
+ if (spec.status === 'FROZEN') {
710
+ if (!json)
711
+ console.log(`\nSpec ${specId} is already FROZEN.\n`);
712
+ else
713
+ console.log(JSON.stringify({ phase: 'DEFINE', confirm: true, spec, status: 'FROZEN', alreadyFrozen: true }, null, 2));
714
+ return;
715
+ }
716
+ const approveResult = await fsm.canTransition(specId, 'approve');
717
+ if (!approveResult.allowed) {
718
+ if (!json) {
719
+ console.error('\nFSM transition blocked: REVIEWING -> FROZEN');
720
+ console.error(' Spec cannot be confirmed due to:');
721
+ approveResult.blockedBy?.forEach(b => console.error(` [GUARD] ${b.guard}: ${b.message}`));
722
+ console.error('\n Resolve issues before confirming.');
723
+ }
724
+ process.exit(1);
725
+ }
726
+ await fsm.transition(specId, 'approve', { actor: { kind: 'human', userId: 'cli' } });
727
+ // Refresh persisted markdown status (draft was written as REVIEWING).
728
+ const specPath = join(SCALE_DIR, 'specs', `${specId}.md`);
729
+ if (existsSync(specPath)) {
730
+ writeFileSync(specPath, generateSpecMarkdown(specId, spec.title, spec.payload, 'FROZEN'));
731
+ }
732
+ const confirmed = await store.get(specId);
733
+ if (json) {
734
+ console.log(JSON.stringify({ phase: 'DEFINE', confirm: true, spec: confirmed, status: 'FROZEN' }, null, 2));
735
+ }
736
+ else {
737
+ console.log(`\nCONFIRM: ${specId}`);
738
+ console.log(' FSM: REVIEWING -> FROZEN ✓');
739
+ console.log(`\n Next: scale plan ${specId}\n`);
740
+ }
741
+ }
742
+ // Helper: Resolve the originating Spec for a Task by walking Task -> Plan -> Spec.
743
+ async function resolveSpecForTask(store, task) {
744
+ const planId = task?.parents?.[0];
745
+ if (!planId)
746
+ return undefined;
747
+ const plan = await store.get(planId);
748
+ const specId = plan?.parents?.[0];
749
+ if (!specId)
750
+ return undefined;
751
+ const spec = await store.get(specId);
752
+ if (!spec || spec.type !== 'Spec')
753
+ return undefined;
754
+ return { id: spec.id, payload: spec.payload };
755
+ }
756
+ // Helper: Collect free-form evidence signals (commands run, files, evidence refs/artifacts)
757
+ // used to soft-map a Spec's verificationSurface during verify/ship (P0 Decision C1).
758
+ async function gatherVerificationSignals(store, options) {
759
+ const signals = [];
760
+ for (const command of options.commands ?? [])
761
+ if (command)
762
+ signals.push(command);
763
+ for (const file of options.files ?? [])
764
+ if (file)
765
+ signals.push(file);
766
+ for (const id of options.evidenceIds ?? []) {
767
+ const record = await store.get(id);
768
+ if (!record || record.type !== 'Evidence')
769
+ continue;
770
+ const payload = record.payload;
771
+ if (payload.verificationSurfaceRef)
772
+ signals.push(payload.verificationSurfaceRef);
773
+ if (payload.toolUsed)
774
+ signals.push(payload.toolUsed);
775
+ if (payload.artifacts?.length)
776
+ signals.push(...payload.artifacts);
777
+ }
778
+ return signals;
779
+ }
564
780
  // Helper: Generate plan markdown file
565
781
  function generatePlanMarkdown(id, specId, payload) {
566
782
  return `# Plan: ${id}
@@ -906,10 +1122,15 @@ export const phaseVerify = defineCommand({
906
1122
  'skip-build': { type: 'boolean', default: false },
907
1123
  'skip-lint': { type: 'boolean', default: false },
908
1124
  'skip-test': { type: 'boolean', default: false },
1125
+ progress: { type: 'boolean', default: false, description: 'Emit coarse verify progress events to stderr without changing JSON output' },
909
1126
  json: { type: 'boolean', default: false },
910
1127
  },
911
1128
  async run({ args }) {
912
1129
  const { store, fsm, workflowEngine } = getEngine();
1130
+ const emitProgress = (event, detail) => {
1131
+ if (isTruthyFlag(args.progress))
1132
+ console.error(`[progress] ${event}: ${detail}`);
1133
+ };
913
1134
  // Validate task exists
914
1135
  const task = await store.get(args['task-id']);
915
1136
  if (!task || task.type !== 'Task') {
@@ -932,6 +1153,7 @@ export const phaseVerify = defineCommand({
932
1153
  service: args.service,
933
1154
  services: args.service ? undefined : taskServices,
934
1155
  });
1156
+ emitProgress('verify:start', `task=${args['task-id']} profile=${resolvedVerification.profileName} targets=${resolvedVerification.targets.length}`);
935
1157
  if (!args.json) {
936
1158
  for (const warning of resolvedVerification.warnings)
937
1159
  console.log(` [WARN] ${warning}`);
@@ -947,6 +1169,7 @@ export const phaseVerify = defineCommand({
947
1169
  if (!args.json && resolvedVerification.targets.length > 1) {
948
1170
  console.log(`\n Target: ${target.service?.name ?? 'root'}`);
949
1171
  }
1172
+ emitProgress('target:start', target.service?.name ?? 'root');
950
1173
  const targetResults = await workflowEngine.verify({
951
1174
  cwd: target.config.cwd,
952
1175
  build: args['build-cmd'] ?? target.config.build,
@@ -964,7 +1187,9 @@ export const phaseVerify = defineCommand({
964
1187
  tddStrict: isTruthyFlag(args['tdd-strict']),
965
1188
  });
966
1189
  gateResults.push(...targetResults);
1190
+ emitProgress('target:done', `${target.service?.name ?? 'root'} gates=${targetResults.length}`);
967
1191
  }
1192
+ emitProgress('verify:gates-complete', `gates=${gateResults.length}`);
968
1193
  // Step 2: Display gate results
969
1194
  if (!args.json) {
970
1195
  console.log('\nGate Results:');
@@ -1095,6 +1320,16 @@ export const phaseVerify = defineCommand({
1095
1320
  });
1096
1321
  const workflowOpenTaskBlockers = blockingWorkflowOpenTasks(workflowState.openTasks, args['task-id']);
1097
1322
  const workflowOpenTasksBlocked = workflowOpenTaskBlockers.length > 0;
1323
+ // P0+ (decision E1): resolve the originating Spec up-front so the executional
1324
+ // boundary / constraint checks can gate Task completion. Both reports are
1325
+ // advisory under default/auto and blocking under full/ci/strict; the
1326
+ // detection logic is identical, only the report mode and gating differ.
1327
+ const spec = await resolveSpecForTask(store, task);
1328
+ const boundaryEnforced = isEnforcedBoundaryProfile(resolvedVerification.profileName);
1329
+ const boundaryEnforcement = evaluateBoundaries(taskFiles, spec?.payload.boundaries, boundaryEnforced);
1330
+ const constraintCoverage = evaluateConstraints(spec?.payload.constraints, spec?.payload.verificationSurface, boundaryEnforced);
1331
+ const boundaryBlocked = boundaryEnforced &&
1332
+ countBoundaryBlockers(boundaryEnforcement, constraintCoverage) > 0;
1098
1333
  // Attempt FSM transition to COMPLETED
1099
1334
  // Guards: build_passed, lint_passed, tests_passed, open workflow tasks, and optional artifact policy.
1100
1335
  const codePassed = results.buildStatus === 'success' &&
@@ -1109,9 +1344,15 @@ export const phaseVerify = defineCommand({
1109
1344
  !skillInstallationBlocked &&
1110
1345
  !engineeringStandards.blocked &&
1111
1346
  !(toolEvidenceGate?.blocked ?? false) &&
1347
+ !boundaryBlocked &&
1112
1348
  !workflowOpenTasksBlocked;
1113
1349
  let transitionResult = null;
1114
- if (completionEligible) {
1350
+ const alreadyCompleted = task.status === 'COMPLETED';
1351
+ if (completionEligible && alreadyCompleted) {
1352
+ if (!args.json)
1353
+ console.log('\n FSM: already COMPLETED');
1354
+ }
1355
+ else if (completionEligible) {
1115
1356
  const completeResult = await fsm.canTransition(args['task-id'], 'complete');
1116
1357
  if (!completeResult.allowed) {
1117
1358
  if (!args.json) {
@@ -1147,10 +1388,13 @@ export const phaseVerify = defineCommand({
1147
1388
  else if (!args.json && toolEvidenceGate?.blocked) {
1148
1389
  console.log('\n Tool evidence gate blocked completion - required tools need passed execution evidence');
1149
1390
  }
1391
+ else if (!args.json && boundaryBlocked) {
1392
+ console.log('\n Boundary enforcement blocked completion - keep edits inside Spec boundaries and guard every constraint (enforced profile)');
1393
+ }
1150
1394
  else if (!args.json && workflowOpenTasksBlocked) {
1151
1395
  console.log('\n Workflow open tasks blocked completion - finish required workflow commands first');
1152
1396
  }
1153
- const passed = completionEligible && (transitionResult?.success ?? false);
1397
+ const passed = completionEligible && (alreadyCompleted || (transitionResult?.success ?? false));
1154
1398
  if (passed) {
1155
1399
  workflowState = workflowWriter.updateCurrentState({
1156
1400
  openTasks: removeWorkflowOpenTask(workflowState.openTasks, 'verification'),
@@ -1203,7 +1447,7 @@ export const phaseVerify = defineCommand({
1203
1447
  toolEvidenceGatePassed: finalToolEvidenceGate ? !finalToolEvidenceGate.blocked : true,
1204
1448
  };
1205
1449
  await store.update(args['task-id'], { payload: finalPayload });
1206
- const metricGateStatus = (finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked || engineeringStandards.blocked || finalToolEvidenceGate?.blocked || workflowOpenTasksBlocked)
1450
+ const metricGateStatus = (finalArtifactGate.blocked || finalSkillGate?.blocked || skillInstallationBlocked || engineeringStandards.blocked || finalToolEvidenceGate?.blocked || boundaryBlocked || workflowOpenTasksBlocked)
1207
1451
  ? 'blocked'
1208
1452
  : undefined;
1209
1453
  const metricRecord = await recordVerificationMetric({
@@ -1215,6 +1459,30 @@ export const phaseVerify = defineCommand({
1215
1459
  artifactCheck,
1216
1460
  finalGateStatus: metricGateStatus,
1217
1461
  });
1462
+ const cortexInstinctApplications = recordRuntimeInstinctApplications({
1463
+ projectDir: PROJECT_DIR,
1464
+ scaleDir: SCALE_DIR,
1465
+ phase: 'verify',
1466
+ success: passed,
1467
+ });
1468
+ // P0 (Decision C1): soft-map the Spec's verificationSurface against evidence.
1469
+ // Unmapped items are reported as warnings only — never blocking in P0.
1470
+ // (`spec`, `boundaryEnforcement` and `constraintCoverage` were resolved
1471
+ // above so the boundary checks could gate completion under enforced profiles.)
1472
+ const verificationCommands = resolvedVerification.targets.flatMap(target => [
1473
+ target.config.build, target.config.lint, target.config.test, target.config.coverage,
1474
+ ]);
1475
+ const surfaceSignals = await gatherVerificationSignals(store, {
1476
+ evidenceIds: verificationEvidenceIds,
1477
+ commands: [
1478
+ ...verificationCommands,
1479
+ args['build-cmd'], args['lint-cmd'], args['test-cmd'], args['coverage-cmd'],
1480
+ ],
1481
+ files: taskFiles,
1482
+ });
1483
+ const surfaceCoverage = spec?.payload.verificationSurface?.length
1484
+ ? computeSurfaceCoverage(spec.payload.verificationSurface, surfaceSignals)
1485
+ : undefined;
1218
1486
  const result = {
1219
1487
  phase: 'VERIFY',
1220
1488
  taskId: args['task-id'],
@@ -1240,14 +1508,29 @@ export const phaseVerify = defineCommand({
1240
1508
  blocked: skillInstallationBlocked,
1241
1509
  },
1242
1510
  metric: metricRecord,
1511
+ cortexInstinctApplications,
1512
+ verificationSurfaceCoverage: surfaceCoverage,
1513
+ boundaryEnforcement,
1514
+ constraintCoverage,
1243
1515
  passed
1244
1516
  };
1245
1517
  if (args.json)
1246
1518
  console.log(JSON.stringify(result, null, 2));
1247
1519
  else {
1248
1520
  console.log(`\nVERIFY: ${passed ? 'PASSED' : 'FAILED'}`);
1521
+ if (surfaceCoverage) {
1522
+ for (const line of formatSurfaceCoverageWarnings(surfaceCoverage))
1523
+ console.log(` ${line}`);
1524
+ }
1525
+ for (const line of formatBoundaryWarnings(boundaryEnforcement))
1526
+ console.log(` ${line}`);
1527
+ for (const line of formatConstraintWarnings(constraintCoverage))
1528
+ console.log(` ${line}`);
1249
1529
  if (metricRecord)
1250
1530
  console.log(` Metrics: ${metricRecord.taskId} ${metricRecord.finalGateStatus} (fix iterations: ${metricRecord.fixIterations})`);
1531
+ if (cortexInstinctApplications.recorded.length > 0) {
1532
+ console.log(` Cortex instincts: ${cortexInstinctApplications.recorded.length} outcome(s) recorded`);
1533
+ }
1251
1534
  if (artifactCheck && !artifactCheck.complete) {
1252
1535
  console.log(` Artifact gaps: ${artifactCheck.missing.length} missing, ${artifactCheck.incomplete.length} incomplete`);
1253
1536
  }
@@ -1309,7 +1592,13 @@ function readUntrackedFileAsDiff(path) {
1309
1592
  return '';
1310
1593
  }
1311
1594
  }
1312
- async function reviewGitChanges(taskPayload) {
1595
+ function normalizeMaxDiffFiles(value) {
1596
+ const parsed = Number.parseInt(String(value ?? ''), 10);
1597
+ if (!Number.isFinite(parsed) || parsed <= 0)
1598
+ return 200;
1599
+ return Math.min(parsed, 1000);
1600
+ }
1601
+ async function reviewGitChanges(taskPayload, maxDiffFiles = 200) {
1313
1602
  const status = await runGit(['status', '--short']);
1314
1603
  const untracked = await runGit(['ls-files', '--others', '--exclude-standard']);
1315
1604
  let statusOutput = mergeUntrackedFilesIntoStatus(status.stdout, untracked.stdout);
@@ -1332,7 +1621,7 @@ async function reviewGitChanges(taskPayload) {
1332
1621
  const verificationEvidence = getVerificationEvidenceSummary(taskPayload?.verificationEvidenceIds);
1333
1622
  const changedFiles = analyzeReview({ statusOutput, diffs: [], taskPayload, verificationEvidence }).changedFiles;
1334
1623
  const diffs = [];
1335
- for (const file of changedFiles.slice(0, 50)) {
1624
+ for (const file of changedFiles.slice(0, maxDiffFiles)) {
1336
1625
  if (file.status === '??') {
1337
1626
  diffs.push({ file: file.path, text: readUntrackedFileAsDiff(file.path) });
1338
1627
  }
@@ -1341,7 +1630,23 @@ async function reviewGitChanges(taskPayload) {
1341
1630
  diffs.push({ file: file.path, text: diff.stdout });
1342
1631
  }
1343
1632
  }
1344
- return analyzeReview({ statusOutput, diffs, taskPayload, verificationEvidence });
1633
+ return { ...analyzeReview({ statusOutput, diffs, taskPayload, verificationEvidence }), diffs };
1634
+ }
1635
+ function normalizeReviewMode(value) {
1636
+ return value === 'fresh-subagent' || value === 'hybrid' ? value : 'ai-self';
1637
+ }
1638
+ // Build a compact diff summary (file headers + added lines) for the advisory
1639
+ // LLM-as-Judge (P1.4). Capped so it never blows the model/context budget.
1640
+ function buildJudgeDiffSummary(diffs) {
1641
+ const parts = [];
1642
+ for (const diff of diffs) {
1643
+ const added = diff.text
1644
+ .split('\n')
1645
+ .filter(line => line.startsWith('+') && !line.startsWith('+++'))
1646
+ .map(line => line.slice(1));
1647
+ parts.push(`# ${diff.file}\n${added.join('\n')}`);
1648
+ }
1649
+ return parts.join('\n\n').slice(0, 6000);
1345
1650
  }
1346
1651
  function collectReviewedFiles(records) {
1347
1652
  const reviewed = new Set();
@@ -1553,6 +1858,9 @@ export const phaseReview = defineCommand({
1553
1858
  'check-style': { type: 'boolean', default: true },
1554
1859
  format: { type: 'string', alias: 'f', description: 'Output format: html or md (default: html)' },
1555
1860
  brand: { type: 'string', description: 'Brand theme for HTML output (vercel/stripe/notion/linear/github)' },
1861
+ judge: { type: 'boolean', default: true, description: 'Run the advisory LLM-as-Judge spec-conformance check (P1.4)' },
1862
+ mode: { type: 'string', default: 'ai-self', description: 'Review mode: ai-self (default) | fresh-subagent | hybrid (P2.2)' },
1863
+ 'max-diff-files': { type: 'string', default: '200', description: 'Maximum changed files whose diffs are scanned during deterministic review' },
1556
1864
  json: { type: 'boolean', default: false },
1557
1865
  },
1558
1866
  async run({ args }) {
@@ -1569,7 +1877,7 @@ export const phaseReview = defineCommand({
1569
1877
  }
1570
1878
  taskPayload = task.payload;
1571
1879
  }
1572
- const review = await reviewGitChanges(taskPayload);
1880
+ const review = await reviewGitChanges(taskPayload, normalizeMaxDiffFiles(args['max-diff-files']));
1573
1881
  const karpathyContext = deriveReviewKarpathyContext(review, taskPayload);
1574
1882
  const karpathyResult = workflowEngine.checkKarpathy(karpathyContext);
1575
1883
  const karpathyReport = {
@@ -1586,12 +1894,44 @@ export const phaseReview = defineCommand({
1586
1894
  const findings = review.findings;
1587
1895
  const summary = summarizeFindings(findings);
1588
1896
  const passed = summary.critical === 0 && summary.high === 0;
1897
+ const reviewMode = normalizeReviewMode(args.mode);
1898
+ // Resolve the originating Spec once; both the advisory judge (P1.4) and the
1899
+ // fresh-context verifier (P2.2) read its outcome / verificationSurface.
1900
+ const needsSpec = args.judge || reviewMode !== 'ai-self';
1901
+ const spec = needsSpec ? await resolveSpecForTask(store, task) : undefined;
1902
+ const diffSummary = needsSpec ? buildJudgeDiffSummary(review.diffs) : '';
1903
+ // P1.4 (decision K1): advisory LLM-as-Judge. Never part of `passed`.
1904
+ let judgeVerdict;
1905
+ if (args.judge) {
1906
+ const judge = new LlmJudge(new JsonLlmClient(), new JudgePromptStore(SCALE_DIR));
1907
+ judgeVerdict = await judge.judge({
1908
+ outcome: spec?.payload.what,
1909
+ verificationSurface: spec?.payload.verificationSurface ?? [],
1910
+ diffSummary,
1911
+ reviewFindings: summary,
1912
+ });
1913
+ }
1914
+ // P2.2 (decisions M1/N1/O1): fresh-context verifier runs only for
1915
+ // fresh-subagent / hybrid modes, on isolated input (surface + diff + gate
1916
+ // summary, no build-agent history). Advisory only — never blocks ship.
1917
+ let freshVerifyVerdict;
1918
+ if (reviewMode !== 'ai-self') {
1919
+ freshVerifyVerdict = await new FreshContextVerifier(new JsonLlmClient()).verify({
1920
+ outcome: spec?.payload.what,
1921
+ verificationSurface: spec?.payload.verificationSurface ?? [],
1922
+ diffSummary,
1923
+ gateSummary: `critical=${summary.critical} high=${summary.high} medium=${summary.medium} low=${summary.low}`,
1924
+ });
1925
+ }
1589
1926
  const record = reviewStore.saveReview({
1590
1927
  taskId: args['task-id'],
1591
1928
  passed,
1592
1929
  findings,
1593
1930
  changedFiles: review.changedFiles.map(file => normalizeGitPath(file.path)),
1594
1931
  summary,
1932
+ judge: judgeVerdict,
1933
+ reviewMode,
1934
+ freshVerify: freshVerifyVerdict,
1595
1935
  });
1596
1936
  if (task && taskPayload) {
1597
1937
  const updatedPayload = {
@@ -1638,6 +1978,9 @@ export const phaseReview = defineCommand({
1638
1978
  findings,
1639
1979
  changedFiles: review.changedFiles.map(file => normalizeGitPath(file.path)),
1640
1980
  summary,
1981
+ judge: judgeVerdict,
1982
+ reviewMode,
1983
+ freshVerify: freshVerifyVerdict,
1641
1984
  karpathy: karpathyReport,
1642
1985
  passed,
1643
1986
  format: reviewOutputFormat,
@@ -1660,6 +2003,18 @@ export const phaseReview = defineCommand({
1660
2003
  console.log(`LOW: ${summary.low} issues`);
1661
2004
  console.log('----------------------------------------');
1662
2005
  findings.slice(0, 10).forEach(f => console.log(` [${f.severity}] ${f.file ? `${f.file}: ` : ''}${f.description}`));
2006
+ if (judgeVerdict) {
2007
+ console.log(`\nJudge (advisory, ${judgeVerdict.modelUsed}): ${judgeVerdict.decision.toUpperCase()} (confidence ${judgeVerdict.confidence.toFixed(2)})`);
2008
+ console.log(` ${judgeVerdict.rationale}`);
2009
+ if (judgeVerdict.unmetSurfaces.length)
2010
+ console.log(` Unmet surfaces: ${judgeVerdict.unmetSurfaces.join('; ')}`);
2011
+ }
2012
+ if (freshVerifyVerdict) {
2013
+ console.log(`\nFresh-context verifier (advisory, ${freshVerifyVerdict.modelUsed}): ${freshVerifyVerdict.decision.toUpperCase()} (confidence ${freshVerifyVerdict.confidence.toFixed(2)})`);
2014
+ console.log(` ${freshVerifyVerdict.rationale}`);
2015
+ if (freshVerifyVerdict.unmetSurfaces.length)
2016
+ console.log(` Unmet surfaces: ${freshVerifyVerdict.unmetSurfaces.join('; ')}`);
2017
+ }
1663
2018
  if (passed) {
1664
2019
  console.log('\nReview passed (no CRITICAL issues)');
1665
2020
  console.log('\n Next: scale ship ' + (args['task-id'] ?? '<task-id>') + '\n');
@@ -1679,6 +2034,11 @@ export const phaseShip = defineCommand({
1679
2034
  message: { type: 'string', alias: 'm', description: 'Commit message' },
1680
2035
  'no-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
1681
2036
  'skip-commit': { type: 'boolean', default: false, description: 'Skip git commit' },
2037
+ 'record-deployment': { type: 'boolean', default: false, description: 'Record a deployment event after a successful ship commit' },
2038
+ 'deploy-service': { type: 'string', default: 'scale-engine', description: 'Deployment service name' },
2039
+ 'deploy-environment': { type: 'string', default: 'production', description: 'Deployment environment name' },
2040
+ 'deploy-version': { type: 'string', description: 'Deployment version override' },
2041
+ 'deployed-at': { type: 'string', description: 'Deployment completion timestamp override' },
1682
2042
  json: { type: 'boolean', default: false },
1683
2043
  },
1684
2044
  async run({ args }) {
@@ -1755,10 +2115,12 @@ export const phaseShip = defineCommand({
1755
2115
  process.exit(1);
1756
2116
  }
1757
2117
  // Git operations
2118
+ const skipCommit = shouldSkipCommit(args['skip-commit']);
1758
2119
  let commitHash = null;
1759
2120
  let stagedFiles = [];
1760
2121
  let workspaceBoundary = null;
1761
- if (!shouldSkipCommit(args['skip-commit'])) {
2122
+ let deploymentRecord = null;
2123
+ if (!skipCommit) {
1762
2124
  const commitMessage = args.message ?? `feat: ${task.title ?? args['task-id']}`;
1763
2125
  try {
1764
2126
  workspaceBoundary = await validateWorkspaceShipBoundary();
@@ -1789,7 +2151,11 @@ export const phaseShip = defineCommand({
1789
2151
  }
1790
2152
  }
1791
2153
  else {
1792
- commitHash = result.stdout.split('\n')[0]; // First line contains hash
2154
+ const head = await runGit(['rev-parse', 'HEAD']);
2155
+ if (head.exitCode !== 0 || !head.stdout.trim()) {
2156
+ throw new Error(head.stderr || 'git rev-parse HEAD failed after commit');
2157
+ }
2158
+ commitHash = head.stdout.trim();
1793
2159
  }
1794
2160
  }
1795
2161
  catch (e) {
@@ -1802,12 +2168,64 @@ export const phaseShip = defineCommand({
1802
2168
  if (task.parents.length > 0) {
1803
2169
  const planId = task.parents[0];
1804
2170
  try {
1805
- await fsm.transition(planId, 'complete', { actor: { kind: 'system', component: 'phase-ship' } });
2171
+ const plan = await store.get(planId);
2172
+ if (plan?.status !== 'DONE') {
2173
+ await fsm.transition(planId, 'complete', { actor: { kind: 'system', component: 'phase-ship' } });
2174
+ }
1806
2175
  }
1807
2176
  catch (e) {
1808
2177
  console.error("Warning: Plan completion transition failed:", e.message);
1809
2178
  }
1810
2179
  }
2180
+ // P0 (Decision C1): soft-map the Spec's verificationSurface at ship time too.
2181
+ const shipSpec = await resolveSpecForTask(store, task);
2182
+ const shipSignals = await gatherVerificationSignals(store, {
2183
+ evidenceIds: payload.verificationEvidenceIds,
2184
+ files: payload.filesInvolved,
2185
+ });
2186
+ const shipSurfaceCoverage = shipSpec?.payload.verificationSurface?.length
2187
+ ? computeSurfaceCoverage(shipSpec.payload.verificationSurface, shipSignals)
2188
+ : undefined;
2189
+ if (isTruthyFlag(args['record-deployment'])) {
2190
+ if (!commitHash) {
2191
+ process.stderr.write('\nDeployment recording requires a new ship commit. Re-run without --no-commit/--skip-commit, or record a real deployment explicitly with scale workflow deploy record.\n\n');
2192
+ process.exit(1);
2193
+ }
2194
+ try {
2195
+ deploymentRecord = await recordShipDeployment({
2196
+ taskId: args['task-id'],
2197
+ taskTitle: task.title,
2198
+ taskPayload: payload,
2199
+ commitHash,
2200
+ args,
2201
+ });
2202
+ }
2203
+ catch (error) {
2204
+ process.stderr.write(`\nDeployment record failed: ${error.message}\n`);
2205
+ process.exit(1);
2206
+ }
2207
+ }
2208
+ const cortexInstinctApplications = recordRuntimeInstinctApplications({
2209
+ projectDir: PROJECT_DIR,
2210
+ scaleDir: SCALE_DIR,
2211
+ phase: 'ship',
2212
+ success: true,
2213
+ });
2214
+ const shippedAt = Date.now();
2215
+ const shipMode = skipCommit
2216
+ ? 'no-commit'
2217
+ : commitHash
2218
+ ? 'commit'
2219
+ : 'commit-skipped';
2220
+ const shippedPayload = {
2221
+ ...payload,
2222
+ shipPassed: true,
2223
+ shippedAt,
2224
+ shipMode,
2225
+ shipCommitHash: commitHash ?? undefined,
2226
+ shipDeploymentRecordId: deploymentRecord?.id,
2227
+ };
2228
+ await store.update(task.id, { payload: shippedPayload });
1811
2229
  // === WorkflowEngine Integration ===
1812
2230
  // Generate HonestDelivery report
1813
2231
  if (!args.json) {
@@ -1818,6 +2236,11 @@ export const phaseShip = defineCommand({
1818
2236
  console.log(` - Status: COMPLETED`);
1819
2237
  if (commitHash)
1820
2238
  console.log(` - Commit: ${commitHash}`);
2239
+ if (deploymentRecord)
2240
+ process.stdout.write(` - Deployment evidence: ${deploymentRecord.id}\n`);
2241
+ if (cortexInstinctApplications.recorded.length > 0) {
2242
+ console.log(` - Cortex instinct outcomes: ${cortexInstinctApplications.recorded.length}`);
2243
+ }
1821
2244
  if (stagedFiles.length)
1822
2245
  console.log(` - Files committed: ${stagedFiles.length}`);
1823
2246
  console.log('');
@@ -1844,6 +2267,10 @@ export const phaseShip = defineCommand({
1844
2267
  unverifiedItems.forEach(item => console.log(` [UNVERIFIED] ${item}`));
1845
2268
  console.log('');
1846
2269
  }
2270
+ if (shipSurfaceCoverage) {
2271
+ for (const line of formatSurfaceCoverageWarnings(shipSurfaceCoverage))
2272
+ console.log(line);
2273
+ }
1847
2274
  }
1848
2275
  const result = {
1849
2276
  phase: 'SHIP',
@@ -1854,7 +2281,10 @@ export const phaseShip = defineCommand({
1854
2281
  reviewEvidenceIds: payload.reviewEvidenceIds ?? [],
1855
2282
  reviewValidation,
1856
2283
  commitHash,
2284
+ deploymentRecord,
1857
2285
  stagedFiles,
2286
+ shippedAt,
2287
+ shipMode,
1858
2288
  workspaceBoundary: workspaceBoundary ? {
1859
2289
  topology: workspaceBoundary.report?.topology.topology ?? null,
1860
2290
  configured: workspaceBoundary.report?.topology.configured ?? false,
@@ -1863,6 +2293,8 @@ export const phaseShip = defineCommand({
1863
2293
  blockers: workspaceBoundary.blockers,
1864
2294
  warnings: workspaceBoundary.warnings,
1865
2295
  } : null,
2296
+ cortexInstinctApplications,
2297
+ verificationSurfaceCoverage: shipSurfaceCoverage,
1866
2298
  };
1867
2299
  if (args.json)
1868
2300
  console.log(JSON.stringify(result, null, 2));
@@ -1871,6 +2303,8 @@ export const phaseShip = defineCommand({
1871
2303
  console.log('\nTask COMPLETED: ' + args['task-id']);
1872
2304
  if (commitHash)
1873
2305
  console.log(' Commit: ' + commitHash);
2306
+ if (deploymentRecord)
2307
+ process.stdout.write(' Deployment evidence: ' + deploymentRecord.id + '\n');
1874
2308
  console.log('\nDone. Feature shipped.\n');
1875
2309
  }
1876
2310
  },