@hongmaple0820/scale-engine 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.en.md +310 -237
  2. package/README.md +255 -63
  3. package/dist/api/cli.js +2656 -1258
  4. package/dist/api/cli.js.map +1 -1
  5. package/dist/api/doctor.d.ts +4 -1
  6. package/dist/api/doctor.js +85 -1
  7. package/dist/api/doctor.js.map +1 -1
  8. package/dist/api/quickstart.d.ts +3 -0
  9. package/dist/api/quickstart.js +9 -4
  10. package/dist/api/quickstart.js.map +1 -1
  11. package/dist/cli/phaseCommands.js +7 -0
  12. package/dist/cli/phaseCommands.js.map +1 -1
  13. package/dist/codegraph/CodeIntelligence.d.ts +135 -0
  14. package/dist/codegraph/CodeIntelligence.js +460 -0
  15. package/dist/codegraph/CodeIntelligence.js.map +1 -0
  16. package/dist/context/ContextBudget.d.ts +90 -0
  17. package/dist/context/ContextBudget.js +322 -0
  18. package/dist/context/ContextBudget.js.map +1 -0
  19. package/dist/eval/WorkflowEval.d.ts +161 -0
  20. package/dist/eval/WorkflowEval.js +379 -0
  21. package/dist/eval/WorkflowEval.js.map +1 -0
  22. package/dist/governance/GovernanceRoi.d.ts +25 -0
  23. package/dist/governance/GovernanceRoi.js +70 -0
  24. package/dist/governance/GovernanceRoi.js.map +1 -0
  25. package/dist/governance/ProgressiveGovernance.d.ts +22 -0
  26. package/dist/governance/ProgressiveGovernance.js +159 -0
  27. package/dist/governance/ProgressiveGovernance.js.map +1 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +4 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/memory/MemoryBrain.d.ts +135 -0
  32. package/dist/memory/MemoryBrain.js +635 -0
  33. package/dist/memory/MemoryBrain.js.map +1 -0
  34. package/dist/memory/MemoryFabric.d.ts +118 -0
  35. package/dist/memory/MemoryFabric.js +281 -0
  36. package/dist/memory/MemoryFabric.js.map +1 -0
  37. package/dist/memory/MemoryLearning.d.ts +61 -0
  38. package/dist/memory/MemoryLearning.js +203 -0
  39. package/dist/memory/MemoryLearning.js.map +1 -0
  40. package/dist/memory/index.d.ts +3 -0
  41. package/dist/memory/index.js +4 -0
  42. package/dist/memory/index.js.map +1 -0
  43. package/dist/output/GovernanceDashboard.d.ts +57 -0
  44. package/dist/output/GovernanceDashboard.js +250 -0
  45. package/dist/output/GovernanceDashboard.js.map +1 -0
  46. package/dist/output/HTMLArtifactLayer.js +31 -31
  47. package/dist/output/index.d.ts +2 -0
  48. package/dist/output/index.js +1 -0
  49. package/dist/output/index.js.map +1 -1
  50. package/dist/prompts/VibeTemplateGallery.js +121 -121
  51. package/dist/runtime/FinalReportGuard.d.ts +16 -0
  52. package/dist/runtime/FinalReportGuard.js +14 -0
  53. package/dist/runtime/FinalReportGuard.js.map +1 -0
  54. package/dist/runtime/RuntimeDoctor.d.ts +23 -0
  55. package/dist/runtime/RuntimeDoctor.js +151 -0
  56. package/dist/runtime/RuntimeDoctor.js.map +1 -0
  57. package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
  58. package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
  59. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
  60. package/dist/runtime/SessionLedger.d.ts +53 -0
  61. package/dist/runtime/SessionLedger.js +104 -0
  62. package/dist/runtime/SessionLedger.js.map +1 -0
  63. package/dist/runtime/index.d.ts +4 -0
  64. package/dist/runtime/index.js +5 -0
  65. package/dist/runtime/index.js.map +1 -0
  66. package/dist/skills/SkillRadar.d.ts +83 -0
  67. package/dist/skills/SkillRadar.js +384 -0
  68. package/dist/skills/SkillRadar.js.map +1 -0
  69. package/dist/workflow/EngineeringStandards.js +69 -66
  70. package/dist/workflow/EngineeringStandards.js.map +1 -1
  71. package/dist/workflow/GovernanceTemplatePacks.js +126 -126
  72. package/dist/workflow/GovernanceTemplates.d.ts +1 -1
  73. package/dist/workflow/GovernanceTemplates.js +500 -229
  74. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  75. package/dist/workflow/ResourceGovernance.js +27 -18
  76. package/dist/workflow/ResourceGovernance.js.map +1 -1
  77. package/dist/workflow/VerificationCommands.d.ts +11 -0
  78. package/dist/workflow/VerificationCommands.js +2 -0
  79. package/dist/workflow/VerificationCommands.js.map +1 -1
  80. package/dist/workflow/VerificationProfile.d.ts +2 -1
  81. package/dist/workflow/VerificationProfile.js +3 -0
  82. package/dist/workflow/VerificationProfile.js.map +1 -1
  83. package/dist/workflow/WorkflowArtifactWriter.js +2 -1
  84. package/dist/workflow/WorkflowArtifactWriter.js.map +1 -1
  85. package/dist/workflow/WorkflowEngine.js +4 -1
  86. package/dist/workflow/WorkflowEngine.js.map +1 -1
  87. package/dist/workflow/WorkspaceSafety.d.ts +9 -0
  88. package/dist/workflow/WorkspaceSafety.js +49 -0
  89. package/dist/workflow/WorkspaceSafety.js.map +1 -0
  90. package/dist/workflow/gates/GateSystem.d.ts +12 -1
  91. package/dist/workflow/gates/GateSystem.js +106 -0
  92. package/dist/workflow/gates/GateSystem.js.map +1 -1
  93. package/dist/workflow/types.d.ts +1 -1
  94. package/docs/CODE_INTELLIGENCE.md +138 -0
  95. package/docs/CONTEXT_BUDGET.md +87 -0
  96. package/docs/GOVERNANCE_DASHBOARD.md +69 -0
  97. package/docs/MEMORY_BRAIN.md +104 -0
  98. package/docs/MEMORY_FABRIC.md +107 -0
  99. package/docs/README.md +76 -0
  100. package/docs/RUNTIME_EVIDENCE.md +101 -0
  101. package/docs/SKILL_RADAR.md +115 -0
  102. package/docs/WORKFLOW_EVAL.md +151 -0
  103. package/docs/start/README.md +42 -0
  104. package/docs/start/agent-governance-demo.md +107 -0
  105. package/docs/start/quickstart.md +127 -0
  106. package/examples/demo-projects/agent-governance-demo/README.md +37 -0
  107. package/examples/demo-projects/agent-governance-demo/package.json +16 -0
  108. package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
  109. package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
  110. package/package.json +14 -3
package/dist/api/cli.js CHANGED
@@ -11,6 +11,9 @@ import { BruteRetryDetector, PrematureDoneDetector, BlameShiftDetector } from '.
11
11
  import { DangerousCommandDetector, SecretLeakDetector, RoleGateDetector, ScopeCreepDetector, BUILT_IN_ROLES } from '../guardrails/advancedDetectors.js';
12
12
  import { SQLiteKnowledgeBase } from '../knowledge/SQLiteKnowledgeBase.js';
13
13
  import { ContextBuilder } from '../context/ContextBuilder.js';
14
+ import { buildContextPack, doctorContextBudget, scanContextBudget, writeContextBudgetReport, } from '../context/ContextBudget.js';
15
+ import { buildCodeGraphContext, createCodeGraphRoiReport, impactCodeGraph, inspectCodeIntelligence, queryCodeGraph, writeCodeIntelligenceConfig, } from '../codegraph/CodeIntelligence.js';
16
+ import { WorkflowEvalStore, compareWorkflowEvalRuns, renderWorkflowEvalReport, runWorkflowEvalSuite, } from '../eval/WorkflowEval.js';
14
17
  import { FSMAgentBridge } from '../fsm/FSMAgentBridge.js';
15
18
  import { CapabilityRegistry } from '../capabilities/CapabilityRegistry.js';
16
19
  import { SkillRegistry } from '../skills/SkillRegistry.js';
@@ -20,10 +23,11 @@ import { createSkillPlan, evaluateSkillGate, loadSkillRoutingPolicy, skillPlanMa
20
23
  import { createAdapter, SUPPORTED_AGENTS } from '../adapters/index.js';
21
24
  import { LessonExtractor, RuleProposer, HookGenerator, EvolutionEngine } from '../evolution/EvolutionEngine.js';
22
25
  import { Doctor } from './doctor.js';
23
- import { quickStart, detectPlatform } from './quickstart.js';
26
+ import { quickStart, detectPlatform, governanceNextSteps } from './quickstart.js';
24
27
  import { SkillDiscovery } from '../skills/SkillDiscovery.js';
25
28
  import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
26
29
  import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
30
+ import { evaluateSkillRadar, inspectSkillSupplyChain, renderSkillRadarMarkdown, } from '../skills/SkillRadar.js';
27
31
  import { listLeadershipPresets, renderLeadershipPresetsMarkdown } from '../agents/LeadershipPresets.js';
28
32
  import { listWorkflowPresets, getPresetsByScenario } from '../workflows/presets.js';
29
33
  import { EvidenceStore } from '../workflow/EvidenceStore.js';
@@ -33,6 +37,8 @@ import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
33
37
  import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
34
38
  import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
35
39
  import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
40
+ import { createGovernanceRoiReport } from '../governance/GovernanceRoi.js';
41
+ import { evaluateProgressiveGovernance, normalizeGovernanceMode } from '../governance/ProgressiveGovernance.js';
36
42
  import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
37
43
  import { doctorResourceAssets, scanResourceAssets, settleResourceAssets } from '../workflow/ResourceGovernance.js';
38
44
  import { analyzeContextGovernance, renderContextGrillPrompt, writeContextGovernanceTemplates, } from '../workflow/ContextGovernance.js';
@@ -48,10 +54,14 @@ import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
48
54
  import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
49
55
  import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
50
56
  import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
57
+ import { renderGovernanceDashboard } from '../output/GovernanceDashboard.js';
51
58
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
59
+ import { inspectWorkspaceSafety } from '../workflow/WorkspaceSafety.js';
60
+ import { RuntimeEvidenceLedger, SessionLedger, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
61
+ import { MemoryFabric, MemoryBrain, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, settleMemoryLearning, } from '../memory/index.js';
52
62
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
53
63
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
54
- import { join, resolve } from 'node:path';
64
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
55
65
  import { execFileSync } from 'node:child_process';
56
66
  import { pathToFileURL } from 'node:url';
57
67
  import { SCALE_ENGINE_VERSION } from '../version.js';
@@ -131,15 +141,15 @@ function evaluateEngineeringStandardsGate(options) {
131
141
  }
132
142
  const settlement = options.settle && options.artifactsDir
133
143
  ? settleEngineeringStandards({
134
- projectDir: PROJECT_DIR,
135
- scaleDir: SCALE_DIR,
144
+ projectDir: options.projectDir ?? PROJECT_DIR,
145
+ scaleDir: options.scaleDir ?? SCALE_DIR,
136
146
  taskId: options.taskId,
137
147
  artifactsDir: options.artifactsDir,
138
148
  })
139
149
  : undefined;
140
150
  const doctor = settlement?.doctor ?? doctorEngineeringStandards({
141
- projectDir: PROJECT_DIR,
142
- scaleDir: SCALE_DIR,
151
+ projectDir: options.projectDir ?? PROJECT_DIR,
152
+ scaleDir: options.scaleDir ?? SCALE_DIR,
143
153
  });
144
154
  return {
145
155
  mode,
@@ -151,6 +161,16 @@ function evaluateEngineeringStandardsGate(options) {
151
161
  standardsImpactPath: settlement?.standardsImpactPath,
152
162
  };
153
163
  }
164
+ function skippedEngineeringStandardsGate(reason, policy) {
165
+ void reason;
166
+ return {
167
+ mode: normalizeEngineeringStandardsGateMode(policy.engineeringStandardsGate),
168
+ checked: false,
169
+ blocked: false,
170
+ ok: true,
171
+ findings: [],
172
+ };
173
+ }
154
174
  function normalizeEngineeringStandardsGateMode(value) {
155
175
  return value === 'off' || value === 'block' ? value : 'warn';
156
176
  }
@@ -194,6 +214,23 @@ function createEngine() {
194
214
  });
195
215
  return { eventBus, store, fsm, gateway, roleGate, kb, ctx, fsmAgentBridge, workflowEngine };
196
216
  }
217
+ function resolveScaleDirForProject(projectDir) {
218
+ return isAbsolute(SCALE_DIR) ? SCALE_DIR : join(projectDir, SCALE_DIR);
219
+ }
220
+ function createVerificationWorkflowEngine(scaleDir) {
221
+ ensureDir(scaleDir);
222
+ const eventBus = new EventBus({ eventsDir: join(scaleDir, 'events') });
223
+ const capabilityRegistry = new CapabilityRegistry(eventBus);
224
+ const skillRegistry = new SkillRegistry(eventBus);
225
+ registerCoreSkills(skillRegistry);
226
+ registerExternalSkills(skillRegistry, eventBus);
227
+ return new WorkflowEngine({
228
+ eventBus,
229
+ capabilityRegistry,
230
+ skillRegistry,
231
+ scaleDir,
232
+ });
233
+ }
197
234
  // ============================================================================
198
235
  // session commands
199
236
  // ============================================================================
@@ -889,1257 +926,2541 @@ const contextGrill = defineCommand({
889
926
  console.log(`\nArtifact: ${artifactPath}`);
890
927
  },
891
928
  });
892
- const context = defineCommand({
893
- meta: { name: 'context', description: 'Context assembly' },
894
- subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill },
895
- });
896
- // ============================================================================
897
- // diagnose command - evidence-first debugging loop
898
- // ============================================================================
899
- const diagnosePlanCommand = defineCommand({
900
- meta: { name: 'plan', description: 'Create a reproducible diagnostic loop before fixing a bug' },
929
+ function parsePositiveIntArg(value, name) {
930
+ if (value === undefined || value === null || value === '')
931
+ return undefined;
932
+ const parsed = Number.parseInt(String(value), 10);
933
+ if (Number.isNaN(parsed) || parsed <= 0) {
934
+ throw new Error(`${name} must be a positive integer.`);
935
+ }
936
+ return parsed;
937
+ }
938
+ const contextBudget = defineCommand({
939
+ meta: { name: 'budget', description: 'Report Always/on-demand/evidence/archive/generated context token cost' },
901
940
  args: {
902
- 'task-id': { type: 'string', required: true },
903
- symptom: { type: 'string', required: true },
904
- repro: { type: 'string', description: 'Command that reproduces the current failure' },
905
- 'expected-failure': { type: 'string', description: 'Expected failing behavior or assertion' },
906
- files: { type: 'string', description: 'Comma-separated changed or suspicious files' },
907
- verify: { type: 'string', description: 'Comma-separated verification commands after the fix' },
908
- 'artifact-dir': { type: 'string', description: 'Task artifact directory where plan.md should be updated' },
909
- write: { type: 'boolean', default: false, description: 'Append diagnostic loop output to the task plan artifact' },
941
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
942
+ 'max-always': { type: 'string', description: 'Maximum Always-loaded estimated tokens' },
943
+ 'max-task': { type: 'string', description: 'Maximum task context estimated tokens' },
944
+ write: { type: 'boolean', default: false, description: 'Write .scale/context-budget.json' },
910
945
  json: { type: 'boolean', default: false },
911
946
  },
912
947
  run({ args }) {
913
- const changedFiles = parseCommaList(args.files);
914
- const loop = createDiagnosticLoop({
915
- taskId: String(args['task-id']),
916
- symptom: String(args.symptom),
917
- reproductionCommand: args.repro ? String(args.repro) : undefined,
918
- expectedFailure: args['expected-failure'] ? String(args['expected-failure']) : undefined,
919
- changedFiles,
920
- verificationCommands: parseCommaList(args.verify),
948
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
949
+ const scaleDir = resolveScaleDirForProject(projectDir);
950
+ const report = scanContextBudget({
951
+ projectDir,
952
+ scaleDir,
953
+ maxAlwaysTokens: parsePositiveIntArg(args['max-always'], '--max-always'),
954
+ maxTaskTokens: parsePositiveIntArg(args['max-task'], '--max-task'),
921
955
  });
922
- const validation = validateDiagnosticLoop(loop);
923
- const artifactPath = isTruthyFlag(args.write)
924
- ? appendDiagnosticLoopArtifact({
925
- projectDir: PROJECT_DIR,
926
- artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
927
- loop,
928
- validation,
929
- }) ?? undefined
930
- : undefined;
931
- if (artifactPath || args['artifact-dir']) {
932
- const writer = new WorkflowArtifactWriter(SCALE_DIR);
933
- const current = writer.readCurrentState();
934
- const currentOpenTasks = current?.taskId === loop.taskId ? current.openTasks : [];
935
- writer.updateCurrentState({
936
- taskId: loop.taskId,
937
- phase: 'plan',
938
- artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
939
- filesModified: changedFiles,
940
- openTasks: validation.ready
941
- ? removeWorkflowOpenTask(currentOpenTasks.filter(task => task.trim().startsWith('scale ')), 'diagnostic-loop')
942
- : uniqueStrings([
943
- ...currentOpenTasks,
944
- ...validation.blockers,
945
- ]),
946
- });
947
- }
956
+ const path = isTruthyFlag(args.write) ? writeContextBudgetReport(report) : undefined;
948
957
  if (args.json) {
949
- console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
958
+ console.log(JSON.stringify({ ...report, path }, null, 2));
950
959
  return;
951
960
  }
952
- console.log(renderDiagnosticLoopMarkdown(loop));
953
- if (!validation.ready) {
954
- console.log('\nBlockers:');
955
- for (const blocker of validation.blockers)
956
- console.log(` - ${blocker}`);
961
+ console.log('SCALE Context Budget');
962
+ console.log(` Project: ${report.projectDir}`);
963
+ console.log(` Total: ${report.summary.totalTokens} estimated tokens across ${report.summary.totalFiles} files`);
964
+ console.log(` Always: ${report.summary.alwaysTokens}/${report.thresholds.maxAlwaysTokens}`);
965
+ for (const [category, summary] of Object.entries(report.summary.byCategory)) {
966
+ console.log(` ${category}: ${summary.tokens} tokens in ${summary.files} files`);
957
967
  }
958
- if (artifactPath)
959
- console.log(`\nArtifact: ${artifactPath}`);
968
+ for (const recommendation of report.recommendations)
969
+ console.log(` recommendation: ${recommendation}`);
970
+ if (path)
971
+ console.log(` wrote: ${path}`);
960
972
  },
961
973
  });
962
- const diagnose = defineCommand({
963
- meta: { name: 'diagnose', description: 'Evidence-first debugging workflows' },
964
- subCommands: { plan: diagnosePlanCommand },
965
- });
966
- // ============================================================================
967
- // tdd command - vertical slice RED/GREEN/REFACTOR loop
968
- // ============================================================================
969
- const tddSliceCommand = defineCommand({
970
- meta: { name: 'slice', description: 'Create and evaluate a TDD vertical slice' },
974
+ const contextPack = defineCommand({
975
+ meta: { name: 'pack', description: 'Build a lazy-loaded context pack for a task' },
971
976
  args: {
972
- 'task-id': { type: 'string', required: true },
973
- behavior: { type: 'string', required: true },
974
- 'public-interface': { type: 'string', required: true },
975
- 'failing-test': { type: 'string', required: true },
976
- 'test-file': { type: 'string', required: true },
977
- 'impl-files': { type: 'string', required: true },
978
- 'red-exit-code': { type: 'string', description: 'Exit code from the RED command' },
979
- 'red-summary': { type: 'string', description: 'Short RED output summary' },
980
- 'green-exit-code': { type: 'string', description: 'Exit code from the GREEN command' },
981
- 'green-summary': { type: 'string', description: 'Short GREEN output summary' },
982
- 'refactor-exit-code': { type: 'string', description: 'Exit code from the REFACTOR command' },
983
- 'refactor-summary': { type: 'string', description: 'Short REFACTOR output summary' },
984
- 'artifact-dir': { type: 'string', description: 'Task artifact directory where verification.md should be updated' },
985
- write: { type: 'boolean', default: false, description: 'Append TDD slice output to the task verification artifact' },
977
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
978
+ task: { type: 'string', required: true, description: 'Current task or question' },
979
+ 'task-id': { type: 'string', description: 'Task id' },
980
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
981
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
982
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
986
983
  json: { type: 'boolean', default: false },
987
984
  },
988
985
  run({ args }) {
989
- const failingTest = String(args['failing-test']);
990
- const slice = createTddSlice({
991
- taskId: String(args['task-id']),
992
- behavior: String(args.behavior),
993
- publicInterface: String(args['public-interface']),
994
- failingTestCommand: failingTest,
995
- testFile: String(args['test-file']),
996
- implementationFiles: parseCommaList(args['impl-files']),
997
- redEvidence: commandEvidence(failingTest, args['red-exit-code'], args['red-summary']),
998
- greenEvidence: commandEvidence(failingTest, args['green-exit-code'], args['green-summary']),
999
- refactorEvidence: commandEvidence(failingTest, args['refactor-exit-code'], args['refactor-summary']),
986
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
987
+ const scaleDir = resolveScaleDirForProject(projectDir);
988
+ const pack = buildContextPack({
989
+ projectDir,
990
+ scaleDir,
991
+ task: String(args.task),
992
+ taskId: args['task-id'] ? String(args['task-id']) : undefined,
993
+ level: String(args.level ?? 'M'),
994
+ files: parseCommaList(args.files),
995
+ budget: parsePositiveIntArg(args.budget, '--budget'),
1000
996
  });
1001
- const evaluation = evaluateTddSlice(slice);
1002
- const artifactPath = isTruthyFlag(args.write)
1003
- ? appendTddSliceArtifact({
1004
- projectDir: PROJECT_DIR,
1005
- artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
1006
- slice,
1007
- }) ?? undefined
1008
- : undefined;
1009
- let tddStatePath;
1010
- if (slice.redEvidence && slice.greenEvidence && slice.refactorEvidence) {
1011
- const writer = new WorkflowArtifactWriter(SCALE_DIR);
1012
- writer.writeTDDEvidence({
1013
- timestamp: new Date().toISOString(),
1014
- taskId: slice.taskId,
1015
- red: slice.redEvidence.exitCode !== 0,
1016
- green: slice.greenEvidence.exitCode === 0,
1017
- refactor: slice.refactorEvidence.exitCode === 0,
1018
- testFirst: slice.redEvidence.exitCode !== 0,
1019
- testFile: slice.testFile,
1020
- implFile: slice.implementationFiles[0] ?? '',
1021
- });
1022
- writer.updateCurrentState({
1023
- taskId: slice.taskId,
1024
- phase: 'verify',
1025
- artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
1026
- filesModified: slice.implementationFiles,
1027
- openTasks: removeWorkflowOpenTask(writer.readCurrentState()?.openTasks, 'tdd-slice'),
1028
- });
1029
- tddStatePath = join(writer.getStateDir(), `tdd-${slice.taskId}.json`);
997
+ if (args.json) {
998
+ console.log(JSON.stringify(pack, null, 2));
999
+ return;
1030
1000
  }
1001
+ console.log('SCALE Context Pack');
1002
+ console.log(` Task: ${pack.task.task}`);
1003
+ console.log(` Budget: ${pack.totalEstimatedTokens}/${pack.task.budget}`);
1004
+ for (const section of pack.sections) {
1005
+ console.log(` [${section.included ? 'IN' : 'OUT'}] ${section.id}: ${section.estimatedTokens} tokens`);
1006
+ }
1007
+ },
1008
+ });
1009
+ const contextDoctor = defineCommand({
1010
+ meta: { name: 'doctor', description: 'Check context budget thresholds and generated-artifact loading risk' },
1011
+ args: {
1012
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1013
+ 'max-always': { type: 'string', description: 'Maximum Always-loaded estimated tokens' },
1014
+ 'max-task': { type: 'string', description: 'Maximum task context estimated tokens' },
1015
+ json: { type: 'boolean', default: false },
1016
+ },
1017
+ run({ args }) {
1018
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1019
+ const scaleDir = resolveScaleDirForProject(projectDir);
1020
+ const report = doctorContextBudget({
1021
+ projectDir,
1022
+ scaleDir,
1023
+ maxAlwaysTokens: parsePositiveIntArg(args['max-always'], '--max-always'),
1024
+ maxTaskTokens: parsePositiveIntArg(args['max-task'], '--max-task'),
1025
+ });
1031
1026
  if (args.json) {
1032
- console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
1027
+ console.log(JSON.stringify(report, null, 2));
1028
+ if (!report.ok)
1029
+ process.exitCode = 1;
1033
1030
  return;
1034
1031
  }
1035
- console.log(renderTddSliceMarkdown(slice));
1036
- if (evaluation.blockers.length > 0) {
1037
- console.log('\nBlockers:');
1038
- for (const blocker of evaluation.blockers)
1039
- console.log(` - ${blocker}`);
1032
+ console.log(`SCALE Context Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
1033
+ for (const check of report.checks) {
1034
+ console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
1040
1035
  }
1041
- if (artifactPath)
1042
- console.log(`\nArtifact: ${artifactPath}`);
1043
- if (tddStatePath)
1044
- console.log(`TDD state: ${tddStatePath}`);
1036
+ if (!report.ok)
1037
+ process.exitCode = 1;
1045
1038
  },
1046
1039
  });
1047
- const tdd = defineCommand({
1048
- meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
1049
- subCommands: { slice: tddSliceCommand },
1040
+ const context = defineCommand({
1041
+ meta: { name: 'context', description: 'Context assembly' },
1042
+ subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill, budget: contextBudget, pack: contextPack, doctor: contextDoctor },
1050
1043
  });
1051
1044
  // ============================================================================
1052
- // stats
1045
+ // codegraph command - Adapter-first code intelligence
1053
1046
  // ============================================================================
1054
- const stats = defineCommand({
1055
- meta: { name: 'stats', description: 'Show engine stats' },
1056
- args: {},
1057
- async run() {
1058
- const { store, eventBus } = getEngine();
1059
- const s = store.stats();
1060
- const events = await eventBus.query({ limit: 1000 });
1061
- console.log(JSON.stringify({ ...s, eventCount: events.length }, null, 2));
1062
- },
1063
- });
1064
- const metricsList = defineCommand({
1065
- meta: { name: 'list', description: 'List M/L task workflow metrics' },
1047
+ const codegraphStatus = defineCommand({
1048
+ meta: { name: 'status', description: 'Inspect CodeGraph, Graphify, and fallback code intelligence providers' },
1066
1049
  args: {
1050
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1067
1051
  json: { type: 'boolean', default: false },
1068
1052
  },
1069
- async run({ args }) {
1070
- const store = new TaskMetricsStore(SCALE_DIR);
1071
- const records = store.list();
1072
- const summary = store.summarize();
1053
+ run({ args }) {
1054
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1055
+ const scaleDir = resolveScaleDirForProject(projectDir);
1056
+ const report = inspectCodeIntelligence({
1057
+ projectDir,
1058
+ scaleDir,
1059
+ });
1073
1060
  if (args.json) {
1074
- console.log(JSON.stringify({ summary, records }, null, 2));
1061
+ console.log(JSON.stringify(report, null, 2));
1075
1062
  return;
1076
1063
  }
1077
- console.log('\nWorkflow Metrics');
1078
- console.log(` Total tasks: ${summary.total}`);
1079
- console.log(` First-pass verification rate: ${(summary.firstPassRate * 100).toFixed(1)}%`);
1080
- console.log(` Average fix iterations: ${summary.averageFixIterations.toFixed(2)}`);
1081
- console.log(` Artifact completeness: ${(summary.artifactCompletenessRate * 100).toFixed(1)}%`);
1082
- for (const record of records.slice(-10)) {
1083
- console.log(` - ${record.date} ${record.level} ${record.taskName}: ${record.finalGateStatus}`);
1064
+ console.log('SCALE Code Intelligence Status');
1065
+ console.log(` Config: ${report.configPath} (${report.configExists ? 'found' : 'default'})`);
1066
+ for (const provider of report.providers) {
1067
+ console.log(` [${provider.available ? 'AVAILABLE' : 'UNAVAILABLE'}] ${provider.id} (${provider.type}): ${provider.reason}`);
1084
1068
  }
1069
+ console.log(` Fallback: ${report.fallback.available ? 'available' : 'disabled'} (${report.fallback.tools.join(', ')})`);
1070
+ for (const recommendation of report.recommendations)
1071
+ console.log(` recommendation: ${recommendation}`);
1085
1072
  },
1086
1073
  });
1087
- const metrics = defineCommand({
1088
- meta: { name: 'metrics', description: 'Inspect workflow task metrics' },
1089
- subCommands: { list: metricsList },
1090
- });
1091
- function normalizeTaskArtifactLevel(value) {
1092
- const normalized = String(value ?? 'M').trim().toUpperCase();
1093
- if (normalized === 'S' || normalized === 'M' || normalized === 'L' || normalized === 'CRITICAL') {
1094
- return normalized;
1095
- }
1096
- throw new Error(`Invalid task level "${String(value)}"; expected S, M, L, or CRITICAL.`);
1097
- }
1098
- const taskArtifactsCheck = defineCommand({
1099
- meta: { name: 'check', description: 'Check task artifact completeness' },
1074
+ const codegraphInit = defineCommand({
1075
+ meta: { name: 'init', description: 'Create .scale/code-intelligence.json provider configuration' },
1100
1076
  args: {
1101
- dir: { type: 'string', description: 'Task artifact directory; defaults to .scale/state/current.json artifactsDir' },
1102
- level: { type: 'string', description: 'Task level: S, M, L, or CRITICAL; defaults to current state level or M' },
1103
- 'warn-only': { type: 'boolean', default: false, description: 'Return zero even when artifacts are incomplete' },
1077
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1078
+ force: { type: 'boolean', default: false, description: 'Overwrite existing configuration' },
1104
1079
  json: { type: 'boolean', default: false },
1105
1080
  },
1106
1081
  run({ args }) {
1107
- const state = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1108
- let level;
1109
- try {
1110
- level = normalizeTaskArtifactLevel(args.level ?? state?.level ?? 'M');
1111
- }
1112
- catch (e) {
1113
- console.error(e.message);
1114
- process.exit(1);
1115
- }
1116
- const result = checkTaskArtifactCompleteness({
1117
- projectDir: PROJECT_DIR,
1118
- artifactsDir: args.dir ?? state?.artifactsDir,
1119
- level,
1120
- skillRequiredArtifacts: state?.requiredSkillArtifacts,
1082
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1083
+ const scaleDir = resolveScaleDirForProject(projectDir);
1084
+ const result = writeCodeIntelligenceConfig({
1085
+ projectDir,
1086
+ scaleDir,
1087
+ force: isTruthyFlag(args.force),
1121
1088
  });
1122
1089
  if (args.json) {
1123
1090
  console.log(JSON.stringify(result, null, 2));
1091
+ return;
1124
1092
  }
1125
- else {
1126
- console.log(`\nTask Artifacts: ${result.complete ? 'COMPLETE' : 'INCOMPLETE'}`);
1127
- if (result.artifactsDir)
1128
- console.log(` Directory: ${result.artifactsDir}`);
1129
- console.log(` Required: ${result.required.join(', ') || 'none'}`);
1130
- for (const file of result.missing)
1131
- console.log(` [MISSING] ${file}`);
1132
- for (const item of result.incomplete)
1133
- console.log(` [INCOMPLETE] ${item.file}: ${item.reason}`);
1134
- }
1135
- if (!result.complete && !args['warn-only'])
1136
- process.exitCode = 1;
1093
+ console.log(`SCALE Code Intelligence Config: ${result.written ? 'written' : 'exists'}`);
1094
+ console.log(` ${result.path}`);
1137
1095
  },
1138
1096
  });
1139
- const taskArtifacts = defineCommand({
1140
- meta: { name: 'task-artifacts', description: 'Inspect task artifact completeness' },
1141
- subCommands: { check: taskArtifactsCheck },
1142
- });
1143
- function printWorkspaceLifecycle(report) {
1144
- console.log('\nSCALE Workspace Lifecycle');
1145
- console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
1146
- console.log(` Root: ${report.root.path}`);
1147
- console.log(` Branch: ${report.root.branch ?? '(detached)'}`);
1148
- console.log(` Linked worktree: ${report.root.isLinkedWorktree ? 'yes' : 'no'}`);
1149
- console.log(` Root status: ${report.root.clean ? 'clean' : 'dirty'}`);
1150
- if (!report.root.clean) {
1151
- console.log(` staged=${report.root.staged} unstaged=${report.root.unstaged} untracked=${report.root.untracked}`);
1152
- }
1153
- if (report.childRepositories.length) {
1154
- console.log('\n Child repositories:');
1155
- for (const child of report.childRepositories) {
1156
- console.log(` ${child.clean ? '[CLEAN]' : '[DIRTY]'} ${child.relativePath} (${child.kind}) branch=${child.branch ?? '(detached)'}`);
1157
- if (!child.clean)
1158
- console.log(` staged=${child.staged} unstaged=${child.unstaged} untracked=${child.untracked}`);
1097
+ const codegraphQuery = defineCommand({
1098
+ meta: { name: 'query', description: 'Query code intelligence providers, with explicit fallback when graph data is unavailable' },
1099
+ args: {
1100
+ query: { type: 'positional', required: true, description: 'Symbol, function, class, route, or text query' },
1101
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1102
+ json: { type: 'boolean', default: false },
1103
+ },
1104
+ run({ args }) {
1105
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1106
+ const scaleDir = resolveScaleDirForProject(projectDir);
1107
+ const report = queryCodeGraph({
1108
+ projectDir,
1109
+ scaleDir,
1110
+ query: String(args.query),
1111
+ });
1112
+ if (args.json) {
1113
+ console.log(JSON.stringify(report, null, 2));
1114
+ return;
1159
1115
  }
1160
- }
1161
- else {
1162
- console.log('\n Child repositories: none');
1163
- }
1164
- console.log(`\n Cleanup candidate: ${report.finish.canCleanup ? 'yes' : 'no'}`);
1165
- for (const blocker of report.finish.blockers)
1166
- console.log(` [BLOCKER] ${blocker}`);
1167
- for (const warning of report.finish.warnings)
1168
- console.log(` [WARN] ${warning}`);
1169
- for (const action of report.finish.nextActions)
1170
- console.log(` [NEXT] ${action}`);
1171
- }
1172
- function compactList(values, limit = 5) {
1173
- if (values.length <= limit)
1174
- return values.join(', ');
1175
- return `${values.slice(0, limit).join(', ')} (+${values.length - limit} more)`;
1176
- }
1177
- function printWorkspaceSummary(report) {
1178
- const dirtyChildren = report.childRepositories
1179
- .filter(child => !child.clean)
1180
- .map(child => child.relativePath);
1181
- const unpushedChildren = report.childRepositories
1182
- .filter(child => child.ahead > 0 || (report.topology.finishPolicy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && Boolean(child.branch)))
1183
- .map(child => child.relativePath);
1184
- const noUpstreamChildren = report.childRepositories
1185
- .filter(child => !child.upstream && Boolean(child.branch))
1186
- .map(child => child.relativePath);
1187
- const rootStatus = report.root.clean
1188
- ? 'clean'
1189
- : `dirty (staged=${report.root.staged}, unstaged=${report.root.unstaged}, untracked=${report.root.untracked})`;
1190
- const status = report.finish.blockers.length > 0 ? 'BLOCKED' : 'READY';
1191
- console.log('\nSCALE Workspace Summary');
1192
- console.log(` Status: ${status}`);
1193
- console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
1194
- console.log(` Root: ${rootStatus}`);
1195
- console.log(` Children: ${report.childRepositories.length} total, ${dirtyChildren.length} dirty, ${unpushedChildren.length} unpushed, ${noUpstreamChildren.length} no upstream`);
1196
- if (dirtyChildren.length > 0)
1197
- console.log(` Dirty child repositories: ${compactList(dirtyChildren)}`);
1198
- if (unpushedChildren.length > 0)
1199
- console.log(` Unpushed child repositories: ${compactList(unpushedChildren)}`);
1200
- if (report.finish.blockers.length > 0) {
1201
- console.log('\n Blockers:');
1202
- for (const blocker of report.finish.blockers.slice(0, 8))
1203
- console.log(` - ${blocker}`);
1204
- if (report.finish.blockers.length > 8)
1205
- console.log(` - ... ${report.finish.blockers.length - 8} more blocker(s)`);
1206
- }
1207
- if (report.finish.warnings.length > 0) {
1208
- console.log(`\n Warnings: ${report.finish.warnings.length} warning(s); run scale workspace finish --json for details`);
1209
- }
1210
- console.log('\n Next:');
1211
- const nextActions = report.finish.blockers.length > 0
1212
- ? report.finish.nextActions
1213
- : ['Proceed with scale ship <task-id> or cleanup when the branch policy is satisfied'];
1214
- for (const action of nextActions.slice(0, 3))
1215
- console.log(` - ${action}`);
1216
- console.log(' - Run scale workspace finish --json for full details');
1217
- }
1218
- function printWorkspaceTopology(topology, written) {
1219
- console.log('\nSCALE Workspace Topology');
1220
- console.log(` Topology: ${topology.topology}${topology.configured ? '' : ' (default)'}`);
1221
- console.log(` Config: ${topology.configPath}`);
1222
- if (written)
1223
- console.log(` Written: ${written}`);
1224
- console.log('\n Repositories:');
1225
- for (const repo of topology.repositories) {
1226
- console.log(` - ${repo.name}: ${repo.path} (${repo.role}) required=${repo.required !== false ? 'yes' : 'no'}`);
1227
- }
1228
- for (const warning of topology.warnings)
1229
- console.log(` [WARN] ${warning}`);
1230
- }
1231
- function printWorkspaceCleanup(result) {
1232
- printWorkspaceLifecycle(result.report);
1233
- console.log('\n Cleanup plan:');
1234
- console.log(` Mode: ${result.mode}`);
1235
- console.log(` Target: ${result.targetPath}`);
1236
- console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
1237
- console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
1238
- console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
1239
- for (const command of result.commands)
1240
- console.log(` Command: ${command}`);
1241
- for (const blocker of result.blockers)
1242
- console.log(` [BLOCKER] ${blocker}`);
1243
- for (const warning of result.warnings)
1244
- console.log(` [WARN] ${warning}`);
1245
- }
1246
- const workspaceStatus = defineCommand({
1247
- meta: { name: 'status', description: 'Inspect root worktree and child repository lifecycle state' },
1116
+ printCodeGraphReport(report);
1117
+ },
1118
+ });
1119
+ const codegraphImpact = defineCommand({
1120
+ meta: { name: 'impact', description: 'Find likely impacted files for a symbol' },
1248
1121
  args: {
1249
- dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1250
- summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
1122
+ symbol: { type: 'string', required: true, description: 'Symbol to analyze' },
1123
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1251
1124
  json: { type: 'boolean', default: false },
1252
1125
  },
1253
- async run({ args }) {
1254
- const report = await inspectWorkspaceLifecycle({ projectDir: args.dir ?? PROJECT_DIR });
1126
+ run({ args }) {
1127
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1128
+ const scaleDir = resolveScaleDirForProject(projectDir);
1129
+ const report = impactCodeGraph({
1130
+ projectDir,
1131
+ scaleDir,
1132
+ symbol: String(args.symbol),
1133
+ });
1255
1134
  if (args.json) {
1256
1135
  console.log(JSON.stringify(report, null, 2));
1136
+ return;
1257
1137
  }
1258
- else if (isTruthyFlag(args.summary)) {
1259
- printWorkspaceSummary(report);
1138
+ printCodeGraphReport(report);
1139
+ },
1140
+ });
1141
+ const codegraphContext = defineCommand({
1142
+ meta: { name: 'context', description: 'Build a budgeted file context recommendation from code intelligence' },
1143
+ args: {
1144
+ symbol: { type: 'string', required: true, description: 'Symbol to analyze' },
1145
+ budget: { type: 'string', description: 'Maximum estimated tokens for recommended files' },
1146
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1147
+ json: { type: 'boolean', default: false },
1148
+ },
1149
+ run({ args }) {
1150
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1151
+ const scaleDir = resolveScaleDirForProject(projectDir);
1152
+ const report = buildCodeGraphContext({
1153
+ projectDir,
1154
+ scaleDir,
1155
+ symbol: String(args.symbol),
1156
+ budget: parsePositiveIntArg(args.budget, '--budget'),
1157
+ });
1158
+ if (args.json) {
1159
+ console.log(JSON.stringify(report, null, 2));
1160
+ return;
1260
1161
  }
1261
- else {
1262
- printWorkspaceLifecycle(report);
1162
+ printCodeGraphReport(report);
1163
+ console.log(` Context budget: ${report.totalEstimatedTokens}/${report.budget}`);
1164
+ for (const file of report.contextFiles) {
1165
+ console.log(` [${file.included ? 'IN' : 'OUT'}] ${file.path}: ${file.estimatedTokens} tokens`);
1263
1166
  }
1264
- if (report.finish.blockers.length > 0)
1265
- process.exitCode = 1;
1266
1167
  },
1267
1168
  });
1268
- const workspaceMap = defineCommand({
1269
- meta: { name: 'map', description: 'Resolve or write explicit workspace topology for single, monorepo, polyrepo, submodule, or MOE projects' },
1169
+ const codegraphRoi = defineCommand({
1170
+ meta: { name: 'roi', description: 'Estimate exploration ROI from code intelligence or fallback query results' },
1270
1171
  args: {
1271
- dir: { type: 'string', description: 'Project directory; defaults to current project directory' },
1272
- topology: { type: 'string', default: 'moe', description: 'Starter topology for --write (single/monorepo/polyrepo/submodule-workspace/moe)' },
1273
- write: { type: 'boolean', default: false, description: 'Create .scale/workspace.json when it does not exist' },
1172
+ query: { type: 'string', description: 'Text query' },
1173
+ symbol: { type: 'string', description: 'Symbol to analyze' },
1174
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1274
1175
  json: { type: 'boolean', default: false },
1275
1176
  },
1276
1177
  run({ args }) {
1277
- const projectDir = resolve(args.dir ?? PROJECT_DIR);
1278
- const target = workspaceTopologyPath(projectDir);
1279
- let written = null;
1280
- if (isTruthyFlag(args.write) && !existsSync(target)) {
1281
- ensureDir(join(projectDir, '.scale'));
1282
- writeFileSync(target, workspaceTopologyTemplate({
1283
- topology: normalizeWorkspaceTopologyKind(args.topology),
1284
- }), 'utf-8');
1285
- written = target;
1286
- }
1287
- const topology = resolveWorkspaceTopology({ projectDir });
1288
- const result = { ...topology, written };
1178
+ if (!args.query && !args.symbol)
1179
+ throw new Error('Provide --query or --symbol.');
1180
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1181
+ const scaleDir = resolveScaleDirForProject(projectDir);
1182
+ const report = createCodeGraphRoiReport({
1183
+ projectDir,
1184
+ scaleDir,
1185
+ query: args.query ? String(args.query) : undefined,
1186
+ symbol: args.symbol ? String(args.symbol) : undefined,
1187
+ });
1289
1188
  if (args.json) {
1290
- console.log(JSON.stringify(result, null, 2));
1291
- }
1292
- else {
1293
- printWorkspaceTopology(topology, written);
1189
+ console.log(JSON.stringify(report, null, 2));
1190
+ return;
1294
1191
  }
1192
+ console.log('SCALE Code Intelligence ROI');
1193
+ console.log(` Query: ${report.query}`);
1194
+ console.log(` Provider: ${report.provider ?? 'fallback'}`);
1195
+ console.log(` Fallback: ${report.fallbackUsed}`);
1196
+ console.log(` Graph hits: ${report.metrics.graphHits}`);
1197
+ console.log(` Reads saved: ${report.metrics.fileReadsSaved}`);
1198
+ console.log(` Recommendation: ${report.recommendation}`);
1295
1199
  },
1296
1200
  });
1297
- const workspaceFinish = defineCommand({
1298
- meta: { name: 'finish', description: 'Check whether a temporary worktree can be safely finished or cleaned up' },
1201
+ function printCodeGraphReport(report) {
1202
+ console.log('SCALE Code Intelligence');
1203
+ console.log(` Mode: ${report.mode}`);
1204
+ console.log(` Query: ${report.query}`);
1205
+ console.log(` Provider: ${report.provider ?? 'fallback'}`);
1206
+ console.log(` Fallback used: ${report.fallbackUsed}`);
1207
+ console.log(` Confidence: ${report.confidence}`);
1208
+ console.log(` Files: ${report.files.length}`);
1209
+ console.log(` Estimated reads saved: ${report.roi.fileReadsSaved}`);
1210
+ for (const hit of report.hits.slice(0, 12)) {
1211
+ const line = hit.line ? `:${hit.line}` : '';
1212
+ const symbol = hit.symbol ? ` ${hit.symbol}` : '';
1213
+ console.log(` - ${hit.file}${line}${symbol} (${hit.reason})`);
1214
+ }
1215
+ for (const warning of report.warnings)
1216
+ console.log(` warning: ${warning}`);
1217
+ }
1218
+ const codegraph = defineCommand({
1219
+ meta: { name: 'codegraph', description: 'Adapter-first code intelligence and exploration ROI' },
1220
+ subCommands: { status: codegraphStatus, init: codegraphInit, query: codegraphQuery, impact: codegraphImpact, context: codegraphContext, roi: codegraphRoi },
1221
+ });
1222
+ // ============================================================================
1223
+ // eval command - Workflow eval baseline and failure replay
1224
+ // ============================================================================
1225
+ const evalInit = defineCommand({
1226
+ meta: { name: 'init', description: 'Create a lightweight workflow eval suite under .scale/evals' },
1299
1227
  args: {
1300
- dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1301
- summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
1228
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1229
+ suite: { type: 'string', default: 'workflow-baseline', description: 'Suite id' },
1230
+ force: { type: 'boolean', default: false, description: 'Overwrite the existing suite file' },
1302
1231
  json: { type: 'boolean', default: false },
1303
1232
  },
1304
- async run({ args }) {
1305
- const report = await inspectWorkspaceLifecycle({ projectDir: args.dir ?? PROJECT_DIR });
1306
- const result = {
1307
- root: report.root,
1308
- childRepositories: report.childRepositories,
1309
- topology: report.topology,
1310
- finish: report.finish,
1311
- };
1233
+ run({ args }) {
1234
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1235
+ const scaleDir = resolveScaleDirForProject(projectDir);
1236
+ const store = new WorkflowEvalStore({
1237
+ projectDir,
1238
+ scaleDir,
1239
+ });
1240
+ const result = store.initSuite(String(args.suite ?? 'workflow-baseline'), isTruthyFlag(args.force));
1312
1241
  if (args.json) {
1313
1242
  console.log(JSON.stringify(result, null, 2));
1243
+ return;
1314
1244
  }
1315
- else if (isTruthyFlag(args.summary)) {
1316
- printWorkspaceSummary(report);
1317
- }
1318
- else {
1319
- printWorkspaceLifecycle(report);
1320
- }
1321
- if (report.finish.blockers.length > 0)
1322
- process.exitCode = 1;
1245
+ console.log(`SCALE Workflow Eval Suite: ${result.written ? 'written' : 'exists'}`);
1246
+ console.log(` Suite: ${result.suite.id}`);
1247
+ console.log(` Path: ${result.path}`);
1248
+ console.log(` Cases: ${result.suite.cases.length}`);
1323
1249
  },
1324
1250
  });
1325
- const workspaceCleanup = defineCommand({
1326
- meta: { name: 'cleanup', description: 'Dry-run or apply safe removal of a linked temporary worktree' },
1251
+ const evalRun = defineCommand({
1252
+ meta: { name: 'run', description: 'Run a workflow eval suite and preserve failure replay artifacts' },
1327
1253
  args: {
1328
- dir: { type: 'string', description: 'Linked worktree directory; defaults to current project directory' },
1329
- 'dry-run': { type: 'boolean', default: false, description: 'Preview cleanup; this is the default unless --apply is set' },
1330
- apply: { type: 'boolean', default: false, description: 'Actually run git worktree remove after safety checks' },
1331
- confirm: { type: 'string', description: 'Required confirmation token for --apply, usually the worktree branch name' },
1332
- summary: { type: 'boolean', default: false, description: 'Print concise human summary before the cleanup plan' },
1254
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1255
+ suite: { type: 'string', default: 'workflow-baseline', description: 'Suite id or JSON path' },
1333
1256
  json: { type: 'boolean', default: false },
1334
1257
  },
1335
1258
  async run({ args }) {
1336
- const result = await cleanupWorkspaceLifecycle({
1337
- projectDir: args.dir ?? PROJECT_DIR,
1338
- apply: isTruthyFlag(args.apply),
1339
- confirm: args.confirm,
1259
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1260
+ const scaleDir = resolveScaleDirForProject(projectDir);
1261
+ const result = await runWorkflowEvalSuite({
1262
+ projectDir,
1263
+ scaleDir,
1264
+ suite: String(args.suite ?? 'workflow-baseline'),
1340
1265
  });
1341
1266
  if (args.json) {
1342
1267
  console.log(JSON.stringify(result, null, 2));
1268
+ if (!result.run.ok)
1269
+ process.exitCode = 1;
1270
+ return;
1343
1271
  }
1344
- else if (isTruthyFlag(args.summary)) {
1345
- printWorkspaceSummary(result.report);
1346
- console.log('\n Cleanup:');
1347
- console.log(` Mode: ${result.mode}`);
1348
- console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
1349
- console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
1350
- console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
1351
- }
1352
- else {
1353
- printWorkspaceCleanup(result);
1354
- }
1355
- if (!result.canApply || (isTruthyFlag(args.apply) && !result.applied))
1272
+ console.log(`SCALE Workflow Eval: ${result.run.ok ? 'PASS' : 'FAIL'}`);
1273
+ console.log(` Run: ${result.run.id}`);
1274
+ console.log(` Suite: ${result.run.suiteId}`);
1275
+ console.log(` Pass@1: ${(result.run.metrics.passAt1Rate * 100).toFixed(1)}%`);
1276
+ console.log(` Pass@3: ${(result.run.metrics.passAt3Rate * 100).toFixed(1)}%`);
1277
+ console.log(` Tool calls: ${result.run.metrics.totalToolCalls}`);
1278
+ console.log(` Estimated tokens: ${result.run.metrics.estimatedTokens}`);
1279
+ console.log(` Failures: ${result.run.metrics.failureReplayCount}`);
1280
+ console.log(` Run path: ${result.runPath}`);
1281
+ for (const failurePath of result.failurePaths)
1282
+ console.log(` Failure replay: ${failurePath}`);
1283
+ if (!result.run.ok)
1356
1284
  process.exitCode = 1;
1357
1285
  },
1358
1286
  });
1359
- const workspace = defineCommand({
1360
- meta: { name: 'workspace', description: 'Inspect worktree, branch, and child repository lifecycle safety' },
1361
- subCommands: {
1362
- map: workspaceMap,
1363
- status: workspaceStatus,
1364
- finish: workspaceFinish,
1365
- cleanup: workspaceCleanup,
1287
+ const evalCompare = defineCommand({
1288
+ meta: { name: 'compare', description: 'Compare two workflow eval runs by pass rate, iterations, tool calls, and token estimate' },
1289
+ args: {
1290
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1291
+ baseline: { type: 'string', required: true, description: 'Baseline run id or JSON path' },
1292
+ candidate: { type: 'string', required: true, description: 'Candidate run id or JSON path' },
1293
+ json: { type: 'boolean', default: false },
1366
1294
  },
1367
- });
1368
- function normalizeWorkspaceTopologyKind(value) {
1369
- const normalized = String(value ?? 'moe').trim();
1370
- if (normalized === 'single'
1371
- || normalized === 'monorepo'
1372
- || normalized === 'polyrepo'
1373
- || normalized === 'submodule-workspace'
1374
- || normalized === 'moe') {
1375
- return normalized;
1376
- }
1377
- return 'moe';
1378
- }
1379
- const preflight = defineCommand({
1380
- meta: { name: 'preflight', description: 'Run service-aware verification without a task artifact' },
1295
+ run({ args }) {
1296
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1297
+ const scaleDir = resolveScaleDirForProject(projectDir);
1298
+ const comparison = compareWorkflowEvalRuns({
1299
+ projectDir,
1300
+ scaleDir,
1301
+ baseline: String(args.baseline),
1302
+ candidate: String(args.candidate),
1303
+ });
1304
+ if (args.json) {
1305
+ console.log(JSON.stringify(comparison, null, 2));
1306
+ return;
1307
+ }
1308
+ console.log(`SCALE Workflow Eval Compare: ${comparison.recommendation}`);
1309
+ console.log(` Baseline: ${comparison.baseline.id}`);
1310
+ console.log(` Candidate: ${comparison.candidate.id}`);
1311
+ console.log(` Delta Pass@1: ${(comparison.delta.passAt1Rate * 100).toFixed(1)}%`);
1312
+ console.log(` Delta Pass@3: ${(comparison.delta.passAt3Rate * 100).toFixed(1)}%`);
1313
+ console.log(` Delta fix iterations: ${comparison.delta.averageFixIterations.toFixed(2)}`);
1314
+ console.log(` Delta tool calls: ${comparison.delta.totalToolCalls}`);
1315
+ console.log(` Delta estimated tokens: ${comparison.delta.estimatedTokens}`);
1316
+ console.log(` Delta human corrections: ${comparison.delta.humanCorrections}`);
1317
+ },
1318
+ });
1319
+ const evalReport = defineCommand({
1320
+ meta: { name: 'report', description: 'Render a Markdown workflow eval report from a saved run' },
1381
1321
  args: {
1382
- 'build-cmd': { type: 'string', description: 'Override build command' },
1383
- 'lint-cmd': { type: 'string', description: 'Override lint command' },
1384
- 'test-cmd': { type: 'string', description: 'Override test command' },
1385
- 'coverage-cmd': { type: 'string', description: 'Override coverage command' },
1386
- profile: { type: 'string', description: 'Verification profile from .scale/verification.json' },
1387
- 'preflight-profile': { type: 'string', default: 'quick', description: 'Gate intensity profile (quick/full/ci); quick skips coverage and security' },
1388
- service: { type: 'string', description: 'Service name from .scale/verification.json; use all for required services' },
1389
- 'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
1390
- 'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
1322
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1323
+ run: { type: 'string', required: true, description: 'Run id or JSON path' },
1324
+ output: { type: 'string', alias: 'o', description: 'Write report to a Markdown file' },
1391
1325
  json: { type: 'boolean', default: false },
1392
1326
  },
1393
- async run({ args }) {
1394
- const { workflowEngine } = getEngine();
1395
- const preflightProfile = normalizePreflightProfile(args['preflight-profile']);
1396
- const gateStages = gatesForPreflightProfile(preflightProfile);
1397
- const resolved = resolveVerificationTargets({
1398
- projectDir: PROJECT_DIR,
1399
- scaleDir: SCALE_DIR,
1400
- profile: args.profile,
1401
- service: args.service,
1402
- });
1403
- const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
1404
- if (commandTargetsSkipped) {
1405
- resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
1406
- }
1407
- const engineeringStandards = evaluateEngineeringStandardsGate({
1408
- policy: resolved.policy,
1409
- });
1410
- const targetResults = [];
1411
- if (!args.json) {
1412
- console.log('\nSCALE Preflight');
1413
- for (const warning of resolved.warnings)
1414
- console.log(` [WARN] ${warning}`);
1415
- console.log(` Profile: ${resolved.profileName}`);
1416
- console.log(` Preflight profile: ${preflightProfile}`);
1417
- console.log(` Gates: ${gateStages.join(', ')}`);
1418
- if (engineeringStandards.checked) {
1419
- const status = engineeringStandards.blocked ? 'BLOCKED' : engineeringStandards.ok ? 'OK' : 'WARN';
1420
- console.log(` Engineering standards: ${status} (${engineeringStandards.mode})`);
1421
- }
1422
- else {
1423
- console.log(' Engineering standards: skipped');
1424
- }
1425
- }
1426
- for (const target of commandTargetsSkipped ? [] : resolved.targets) {
1427
- if (!args.json) {
1428
- const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
1429
- console.log(`\n Target: ${label}`);
1430
- }
1431
- const gates = await workflowEngine.verify({
1432
- cwd: target.config.cwd,
1433
- build: args['build-cmd'] ?? target.config.build,
1434
- lint: args['lint-cmd'] ?? target.config.lint,
1435
- test: args['test-cmd'] ?? target.config.test,
1436
- coverage: args['coverage-cmd'] ?? target.config.coverage,
1437
- tddEvidence: args['tdd-evidence'],
1438
- tddStrict: isTruthyFlag(args['tdd-strict']),
1439
- gates: gateStages,
1440
- });
1441
- const passed = gates.every(gate => gate.passed);
1442
- targetResults.push({
1443
- service: target.service?.name,
1444
- cwd: target.config.cwd ?? PROJECT_DIR,
1445
- gates,
1446
- passed,
1447
- });
1448
- if (!args.json) {
1449
- for (const gate of gates) {
1450
- console.log(` ${gate.passed ? '[PASS]' : '[FAIL]'} ${gate.gate}: ${gate.evidence.slice(0, 80)}`);
1451
- for (const blocker of gate.blockers)
1452
- console.log(` [BLOCKER] ${blocker.slice(0, 120)}`);
1453
- }
1454
- }
1327
+ run({ args }) {
1328
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1329
+ const scaleDir = resolveScaleDirForProject(projectDir);
1330
+ const store = new WorkflowEvalStore({ projectDir, scaleDir });
1331
+ const run = store.loadRun(String(args.run));
1332
+ const markdown = renderWorkflowEvalReport(run);
1333
+ const outputPath = args.output ? resolve(projectDir, String(args.output)) : undefined;
1334
+ if (outputPath) {
1335
+ mkdirSync(dirname(outputPath), { recursive: true });
1336
+ writeFileSync(outputPath, markdown, 'utf-8');
1455
1337
  }
1456
- const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
1457
- !engineeringStandards.blocked;
1458
- const result = {
1459
- phase: 'PREFLIGHT',
1460
- profile: resolved.profileName,
1461
- preflightProfile,
1462
- gates: gateStages,
1463
- services: targetResults.map(target => target.service).filter(Boolean),
1464
- policy: resolved.policy,
1465
- engineeringStandards,
1466
- targets: targetResults,
1467
- commandTargetsSkipped,
1468
- passed,
1469
- };
1470
1338
  if (args.json) {
1471
- console.log(JSON.stringify(result, null, 2));
1472
- }
1473
- else {
1474
- console.log(`\nPREFLIGHT: ${passed ? 'PASSED' : 'FAILED'}\n`);
1339
+ console.log(JSON.stringify({ runId: run.id, outputPath, markdown }, null, 2));
1340
+ return;
1475
1341
  }
1476
- if (!passed)
1477
- process.exitCode = 1;
1342
+ if (outputPath)
1343
+ console.log(`Workflow eval report written: ${outputPath}`);
1344
+ else
1345
+ console.log(markdown);
1478
1346
  },
1479
1347
  });
1480
- const status = defineCommand({
1481
- meta: { name: 'status', description: 'Show current SCALE workflow status' },
1348
+ const evalFailures = defineCommand({
1349
+ meta: { name: 'failures', description: 'List failure replay records for workflow improvement' },
1482
1350
  args: {
1351
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1352
+ 'task-id': { type: 'string', description: 'Filter by task/case id' },
1353
+ since: { type: 'string', default: '30d', description: 'Window such as 30d; use all for no date filter' },
1483
1354
  json: { type: 'boolean', default: false },
1484
1355
  },
1485
- async run({ args }) {
1486
- const { store } = getEngine();
1487
- const evidenceStore = new EvidenceStore(SCALE_DIR);
1488
- const reviewStore = new ReviewStore(SCALE_DIR);
1489
- const [specs, plans, tasks, releases] = await Promise.all([
1490
- store.query({ type: 'Spec', limit: 1 }),
1491
- store.query({ type: 'Plan', limit: 1 }),
1492
- store.query({ type: 'Task', limit: 1 }),
1493
- store.query({ type: 'Release', limit: 1 }),
1494
- ]);
1495
- const latestEvidence = evidenceStore.listGateResults(5);
1496
- const latestReviews = reviewStore.listReviews(5);
1497
- const latestTask = tasks[0];
1498
- const taskPayload = latestTask?.payload;
1499
- const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1500
- const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
1501
- const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
1502
- const blockers = [];
1503
- const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
1504
- const latestBlockingReview = latestReviews.find(record => !record.passed);
1505
- if (latestBlockingEvidence)
1506
- blockers.push(`${latestBlockingEvidence.gate}: ${latestBlockingEvidence.blockers.join('; ') || latestBlockingEvidence.status}`);
1507
- if (latestBlockingReview)
1508
- blockers.push(`Review ${latestBlockingReview.id}: ${latestBlockingReview.summary.critical} critical, ${latestBlockingReview.summary.high} high`);
1509
- if (latestTask && (!taskPayload?.verificationEvidenceIds || taskPayload.verificationEvidenceIds.length === 0)) {
1510
- blockers.push(`Task ${latestTask.id} has no persisted verification evidence`);
1511
- }
1512
- if (latestTask?.status === 'COMPLETED' && (!taskPayload?.reviewEvidenceIds || taskPayload.reviewEvidenceIds.length === 0)) {
1513
- blockers.push(`Task ${latestTask.id} has no persisted review evidence`);
1514
- }
1515
- const nextCommand = (() => {
1516
- if (!specs[0])
1517
- return 'scale define "<feature>" --description "<what to build>"';
1518
- if (!plans[0])
1519
- return `scale plan ${specs[0].id}`;
1520
- if (!latestTask)
1521
- return `scale build ${plans[0].id}`;
1522
- if (nextOpenTask?.kind === 'command')
1523
- return nextOpenTask.value;
1524
- if (nextOpenTask?.kind === 'blocker')
1525
- return `Resolve workflow blocker: ${nextOpenTask.value}`;
1526
- if (!taskPayload?.verificationEvidenceIds?.length)
1527
- return `scale verify ${latestTask.id}`;
1528
- if (latestTask.status !== 'COMPLETED')
1529
- return `scale verify ${latestTask.id}`;
1530
- if (!taskPayload.reviewEvidenceIds?.length || taskPayload.reviewPassed !== true)
1531
- return `scale review ${latestTask.id}`;
1532
- if (!releases[0])
1533
- return `scale ship ${latestTask.id}`;
1534
- return 'scale evidence list';
1535
- })();
1536
- const result = {
1537
- artifacts: {
1538
- latestSpec: specs[0] ? { id: specs[0].id, status: specs[0].status, title: specs[0].title } : null,
1539
- latestPlan: plans[0] ? { id: plans[0].id, status: plans[0].status, title: plans[0].title } : null,
1540
- latestTask: latestTask ? {
1541
- id: latestTask.id,
1542
- status: latestTask.status,
1543
- title: latestTask.title,
1544
- lintStatus: taskPayload?.lintStatus,
1545
- testPassed: taskPayload?.testPassed,
1546
- testCoverage: taskPayload?.testCoverage,
1547
- evidenceIds: taskPayload?.verificationEvidenceIds ?? [],
1548
- reviewPassed: taskPayload?.reviewPassed,
1549
- reviewEvidenceIds: taskPayload?.reviewEvidenceIds ?? [],
1550
- } : null,
1551
- },
1552
- recentEvidence: latestEvidence.map(record => ({
1553
- id: record.id,
1554
- gate: record.gate,
1555
- status: record.status,
1556
- passed: record.passed,
1557
- blockers: record.blockers,
1558
- createdAt: record.createdAt,
1559
- })),
1560
- recentReviews: latestReviews.map(record => ({
1561
- id: record.id,
1562
- taskId: record.taskId,
1563
- passed: record.passed,
1564
- summary: record.summary,
1565
- createdAt: record.createdAt,
1566
- })),
1567
- workflowState: workflowState ? {
1568
- taskId: workflowState.taskId,
1569
- level: workflowState.level,
1570
- phase: workflowState.phase,
1571
- artifactsDir: workflowState.artifactsDir,
1572
- openTasks: workflowState.openTasks,
1573
- skillIntents: workflowState.skillIntents,
1574
- } : null,
1575
- blockers,
1576
- nextCommand,
1577
- };
1356
+ run({ args }) {
1357
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1358
+ const scaleDir = resolveScaleDirForProject(projectDir);
1359
+ const store = new WorkflowEvalStore({
1360
+ projectDir,
1361
+ scaleDir,
1362
+ });
1363
+ const failures = store.listFailures({
1364
+ taskId: args['task-id'] ? String(args['task-id']) : undefined,
1365
+ sinceDays: parseSinceDays(args.since),
1366
+ });
1578
1367
  if (args.json) {
1579
- console.log(JSON.stringify(result, null, 2));
1368
+ console.log(JSON.stringify({ count: failures.length, failures }, null, 2));
1580
1369
  return;
1581
1370
  }
1582
- console.log('\nSCALE Status');
1583
- console.log('Artifacts:');
1584
- console.log(` Spec: ${result.artifacts.latestSpec ? `${result.artifacts.latestSpec.id} (${result.artifacts.latestSpec.status})` : 'none'}`);
1585
- console.log(` Plan: ${result.artifacts.latestPlan ? `${result.artifacts.latestPlan.id} (${result.artifacts.latestPlan.status})` : 'none'}`);
1586
- console.log(` Task: ${result.artifacts.latestTask ? `${result.artifacts.latestTask.id} (${result.artifacts.latestTask.status})` : 'none'}`);
1587
- if (result.artifacts.latestTask?.evidenceIds.length) {
1588
- console.log(` Task evidence: ${result.artifacts.latestTask.evidenceIds.join(', ')}`);
1589
- }
1590
- console.log('\nRecent Evidence:');
1591
- if (result.recentEvidence.length === 0) {
1592
- console.log(' none');
1371
+ console.log(`SCALE Failure Replays: ${failures.length}`);
1372
+ for (const failure of failures) {
1373
+ console.log(` [${failure.status}] ${failure.id} ${failure.category} task=${failure.taskId}`);
1374
+ console.log(` prevention: ${failure.prevention}`);
1593
1375
  }
1594
- else {
1595
- for (const record of result.recentEvidence) {
1596
- console.log(` ${record.id} ${record.gate} ${record.passed ? 'PASS' : record.status}`);
1597
- }
1376
+ },
1377
+ });
1378
+ const evalReplay = defineCommand({
1379
+ meta: { name: 'replay', description: 'Show failure replay records by failure id or task id' },
1380
+ args: {
1381
+ id: { type: 'positional', description: 'Failure replay id' },
1382
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1383
+ 'task-id': { type: 'string', description: 'Task/case id to replay' },
1384
+ json: { type: 'boolean', default: false },
1385
+ },
1386
+ run({ args }) {
1387
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1388
+ const scaleDir = resolveScaleDirForProject(projectDir);
1389
+ const store = new WorkflowEvalStore({
1390
+ projectDir,
1391
+ scaleDir,
1392
+ });
1393
+ const failures = args.id
1394
+ ? [store.getFailure(String(args.id))].filter(Boolean)
1395
+ : store.listFailures({ taskId: args['task-id'] ? String(args['task-id']) : undefined });
1396
+ if (args.json) {
1397
+ console.log(JSON.stringify({ count: failures.length, failures }, null, 2));
1398
+ if (failures.length === 0)
1399
+ process.exitCode = 1;
1400
+ return;
1598
1401
  }
1599
- if (blockers.length > 0) {
1600
- console.log('\nBlockers:');
1601
- for (const blocker of blockers)
1602
- console.log(` - ${blocker}`);
1402
+ if (failures.length === 0) {
1403
+ console.log('No failure replay records found.');
1404
+ process.exitCode = 1;
1405
+ return;
1603
1406
  }
1604
- if (result.workflowState?.openTasks.length) {
1605
- console.log('\nOpen Tasks:');
1606
- for (const task of result.workflowState.openTasks)
1607
- console.log(` - ${task}`);
1407
+ for (const failure of failures) {
1408
+ if (!failure)
1409
+ continue;
1410
+ console.log(`Failure Replay: ${failure.id}`);
1411
+ console.log(` Task: ${failure.task}`);
1412
+ console.log(` Category: ${failure.category}`);
1413
+ console.log(` Phase: ${failure.phase}`);
1414
+ console.log(` Wrong turn: ${failure.wrongTurn}`);
1415
+ console.log(` Evidence: ${failure.evidence}`);
1416
+ console.log(` Correction: ${failure.correction}`);
1417
+ console.log(` Prevention: ${failure.prevention}`);
1418
+ if (failure.replayCommand)
1419
+ console.log(` Replay command: ${failure.replayCommand}`);
1608
1420
  }
1609
- console.log(`\nNext: ${nextCommand}`);
1610
1421
  },
1611
1422
  });
1612
- // ============================================================================
1613
- // init command
1614
- // ============================================================================
1615
- const init = defineCommand({
1616
- meta: { name: 'init', description: 'Initialize SCALE Engine in current project (one-click install)' },
1423
+ const evalPromoteFailure = defineCommand({
1424
+ meta: { name: 'promote-failure', description: 'Promote a failure replay into a workflow improvement candidate' },
1617
1425
  args: {
1618
- agent: { type: 'string', default: '', description: `Agent type (${SUPPORTED_AGENTS.join('/')}) - auto-detected if not specified` },
1619
- dir: { type: 'string', default: '.', description: 'Project directory' },
1620
- json: { type: 'boolean', default: false, description: 'Output initialization result as JSON' },
1621
- scenario: { type: 'string', default: 'standard', description: 'Scenario mode (sandbox/standard/critical)' },
1622
- 'governance-pack': {
1623
- type: 'string',
1624
- default: 'standard',
1625
- description: 'Governance template pack (standard/project-scaffold/moe-workspace/resource-governance/go-service-matrix/node-library/frontend-app)',
1626
- },
1627
- quick: { type: 'boolean', default: false, description: 'Quick start with auto-detection' },
1628
- interactive: { type: 'boolean', default: false, description: 'Interactive configuration mode with prompts' },
1629
- 'coverage-threshold': { type: 'string', default: '80', description: 'Coverage threshold (default 80%)' },
1630
- 'retry-threshold': { type: 'string', default: '3', description: 'Brute retry threshold (default 3)' },
1631
- 'block-severity': { type: 'string', default: 'CRITICAL', description: 'Block severity level (CRITICAL/HIGH/MEDIUM)' },
1426
+ id: { type: 'positional', required: true, description: 'Failure replay id' },
1427
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1428
+ json: { type: 'boolean', default: false },
1632
1429
  },
1633
- async run({ args }) {
1634
- // Interactive configuration mode
1635
- if (args.interactive) {
1636
- console.log('\n🔧 SCALE Engine Interactive Configuration\n');
1637
- console.log('='.repeat(50));
1638
- // Step 1: Detect and suggest agent platform
1639
- const detection = detectPlatform(args.dir);
1640
- console.log('\n📋 Step 1: Agent Platform Selection');
1641
- console.log(` Detected suggestions: ${detection.suggestions.join(', ') || 'none'}`);
1642
- const agentType = args.agent || detection.suggestions[0] || 'claude-code';
1643
- console.log(` Using: ${agentType}`);
1644
- // Step 2: Scenario mode
1645
- console.log('\n📋 Step 2: Scenario Mode');
1646
- console.log(' sandbox - No quality gates (POC/prototype)');
1647
- console.log(' standard - Default quality gates');
1648
- console.log(' critical - Hardened gates + manual approval');
1649
- const scenarioMode = args.scenario;
1650
- console.log(` Using: ${scenarioMode}`);
1651
- // Step 3: Quality Gate Thresholds (quantified)
1652
- console.log('\n📋 Step 3: Quality Gate Thresholds');
1653
- const coverageThreshold = parseInt(args['coverage-threshold'], 10) || 80;
1654
- const retryThreshold = parseInt(args['retry-threshold'], 10) || 3;
1655
- const blockSeverity = args['block-severity'] || 'CRITICAL';
1656
- console.log(` Coverage threshold: ${coverageThreshold}%`);
1657
- console.log(` Retry threshold: ${retryThreshold} (brute retry block)`);
1658
- console.log(` Block severity: ${blockSeverity}`);
1659
- // Step 4: Write thresholds to .scale/thresholds.json
1660
- const thresholdsPath = join(args.dir, '.scale', 'thresholds.json');
1661
- ensureDir(join(args.dir, '.scale'));
1662
- writeFileSync(thresholdsPath, JSON.stringify({
1663
- coverage: { minimum: coverageThreshold, unit: 'percent' },
1664
- retry: { bruteMaximum: retryThreshold, unit: 'count' },
1665
- severity: { blockLevel: blockSeverity },
1666
- gates: {
1667
- G3_build: { required: scenarioMode !== 'sandbox', exitCode: 0 },
1668
- G4_lint: { required: scenarioMode !== 'sandbox', exitCode: 0 },
1669
- G5_tests: { required: scenarioMode !== 'sandbox', allPass: true },
1670
- G6_coverage: { required: scenarioMode !== 'sandbox', minimum: coverageThreshold },
1671
- G7_security: { required: scenarioMode === 'critical', noCritical: true },
1672
- },
1673
- }, null, 2));
1674
- console.log(`\n ✓ Thresholds written to: ${thresholdsPath}`);
1675
- // Initialize with adapter
1676
- const adapter = createAdapter(agentType);
1677
- const result = await adapter.init({
1678
- projectDir: args.dir,
1679
- agentType: agentType,
1680
- scenarioMode,
1681
- thresholdsPath,
1430
+ run({ args }) {
1431
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1432
+ const scaleDir = resolveScaleDirForProject(projectDir);
1433
+ const store = new WorkflowEvalStore({
1434
+ projectDir,
1435
+ scaleDir,
1436
+ });
1437
+ const candidate = store.promoteFailure(String(args.id));
1438
+ if (args.json) {
1439
+ console.log(JSON.stringify(candidate, null, 2));
1440
+ return;
1441
+ }
1442
+ console.log(`Workflow improvement candidate: ${candidate.id}`);
1443
+ console.log(` Failure: ${candidate.failureId}`);
1444
+ console.log(` Category: ${candidate.category}`);
1445
+ console.log(` Recommendation: ${candidate.recommendation}`);
1446
+ },
1447
+ });
1448
+ function parseSinceDays(value) {
1449
+ const text = String(value ?? '').trim().toLowerCase();
1450
+ if (!text || text === 'all')
1451
+ return undefined;
1452
+ const match = text.match(/^(\d+)(d|day|days)?$/);
1453
+ if (!match)
1454
+ return undefined;
1455
+ const days = Number.parseInt(match[1], 10);
1456
+ return Number.isFinite(days) && days > 0 ? days : undefined;
1457
+ }
1458
+ const evalCommand = defineCommand({
1459
+ meta: { name: 'eval', description: 'Workflow eval harness, pass@k metrics, and failure replay' },
1460
+ subCommands: {
1461
+ init: evalInit,
1462
+ run: evalRun,
1463
+ compare: evalCompare,
1464
+ report: evalReport,
1465
+ failures: evalFailures,
1466
+ replay: evalReplay,
1467
+ 'promote-failure': evalPromoteFailure,
1468
+ },
1469
+ });
1470
+ // ============================================================================
1471
+ // diagnose command - evidence-first debugging loop
1472
+ // ============================================================================
1473
+ const diagnosePlanCommand = defineCommand({
1474
+ meta: { name: 'plan', description: 'Create a reproducible diagnostic loop before fixing a bug' },
1475
+ args: {
1476
+ 'task-id': { type: 'string', required: true },
1477
+ symptom: { type: 'string', required: true },
1478
+ repro: { type: 'string', description: 'Command that reproduces the current failure' },
1479
+ 'expected-failure': { type: 'string', description: 'Expected failing behavior or assertion' },
1480
+ files: { type: 'string', description: 'Comma-separated changed or suspicious files' },
1481
+ verify: { type: 'string', description: 'Comma-separated verification commands after the fix' },
1482
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where plan.md should be updated' },
1483
+ write: { type: 'boolean', default: false, description: 'Append diagnostic loop output to the task plan artifact' },
1484
+ json: { type: 'boolean', default: false },
1485
+ },
1486
+ run({ args }) {
1487
+ const changedFiles = parseCommaList(args.files);
1488
+ const loop = createDiagnosticLoop({
1489
+ taskId: String(args['task-id']),
1490
+ symptom: String(args.symptom),
1491
+ reproductionCommand: args.repro ? String(args.repro) : undefined,
1492
+ expectedFailure: args['expected-failure'] ? String(args['expected-failure']) : undefined,
1493
+ changedFiles,
1494
+ verificationCommands: parseCommaList(args.verify),
1495
+ });
1496
+ const validation = validateDiagnosticLoop(loop);
1497
+ const artifactPath = isTruthyFlag(args.write)
1498
+ ? appendDiagnosticLoopArtifact({
1499
+ projectDir: PROJECT_DIR,
1500
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
1501
+ loop,
1502
+ validation,
1503
+ }) ?? undefined
1504
+ : undefined;
1505
+ if (artifactPath || args['artifact-dir']) {
1506
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
1507
+ const current = writer.readCurrentState();
1508
+ const currentOpenTasks = current?.taskId === loop.taskId ? current.openTasks : [];
1509
+ writer.updateCurrentState({
1510
+ taskId: loop.taskId,
1511
+ phase: 'plan',
1512
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
1513
+ filesModified: changedFiles,
1514
+ openTasks: validation.ready
1515
+ ? removeWorkflowOpenTask(currentOpenTasks.filter(task => task.trim().startsWith('scale ')), 'diagnostic-loop')
1516
+ : uniqueStrings([
1517
+ ...currentOpenTasks,
1518
+ ...validation.blockers,
1519
+ ]),
1682
1520
  });
1683
- const projectName = args.dir.split(/[/\\]/).pop() || 'Project';
1684
- const governance = writeGovernanceTemplates(args.dir, {
1685
- mode: governanceModeFromScenario(scenarioMode),
1686
- projectName,
1687
- pack: args['governance-pack'],
1521
+ }
1522
+ if (args.json) {
1523
+ console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
1524
+ return;
1525
+ }
1526
+ console.log(renderDiagnosticLoopMarkdown(loop));
1527
+ if (!validation.ready) {
1528
+ console.log('\nBlockers:');
1529
+ for (const blocker of validation.blockers)
1530
+ console.log(` - ${blocker}`);
1531
+ }
1532
+ if (artifactPath)
1533
+ console.log(`\nArtifact: ${artifactPath}`);
1534
+ },
1535
+ });
1536
+ const diagnose = defineCommand({
1537
+ meta: { name: 'diagnose', description: 'Evidence-first debugging workflows' },
1538
+ subCommands: { plan: diagnosePlanCommand },
1539
+ });
1540
+ // ============================================================================
1541
+ // tdd command - vertical slice RED/GREEN/REFACTOR loop
1542
+ // ============================================================================
1543
+ const tddSliceCommand = defineCommand({
1544
+ meta: { name: 'slice', description: 'Create and evaluate a TDD vertical slice' },
1545
+ args: {
1546
+ 'task-id': { type: 'string', required: true },
1547
+ behavior: { type: 'string', required: true },
1548
+ 'public-interface': { type: 'string', required: true },
1549
+ 'failing-test': { type: 'string', required: true },
1550
+ 'test-file': { type: 'string', required: true },
1551
+ 'impl-files': { type: 'string', required: true },
1552
+ 'red-exit-code': { type: 'string', description: 'Exit code from the RED command' },
1553
+ 'red-summary': { type: 'string', description: 'Short RED output summary' },
1554
+ 'green-exit-code': { type: 'string', description: 'Exit code from the GREEN command' },
1555
+ 'green-summary': { type: 'string', description: 'Short GREEN output summary' },
1556
+ 'refactor-exit-code': { type: 'string', description: 'Exit code from the REFACTOR command' },
1557
+ 'refactor-summary': { type: 'string', description: 'Short REFACTOR output summary' },
1558
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where verification.md should be updated' },
1559
+ write: { type: 'boolean', default: false, description: 'Append TDD slice output to the task verification artifact' },
1560
+ json: { type: 'boolean', default: false },
1561
+ },
1562
+ run({ args }) {
1563
+ const failingTest = String(args['failing-test']);
1564
+ const slice = createTddSlice({
1565
+ taskId: String(args['task-id']),
1566
+ behavior: String(args.behavior),
1567
+ publicInterface: String(args['public-interface']),
1568
+ failingTestCommand: failingTest,
1569
+ testFile: String(args['test-file']),
1570
+ implementationFiles: parseCommaList(args['impl-files']),
1571
+ redEvidence: commandEvidence(failingTest, args['red-exit-code'], args['red-summary']),
1572
+ greenEvidence: commandEvidence(failingTest, args['green-exit-code'], args['green-summary']),
1573
+ refactorEvidence: commandEvidence(failingTest, args['refactor-exit-code'], args['refactor-summary']),
1574
+ });
1575
+ const evaluation = evaluateTddSlice(slice);
1576
+ const artifactPath = isTruthyFlag(args.write)
1577
+ ? appendTddSliceArtifact({
1578
+ projectDir: PROJECT_DIR,
1579
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
1580
+ slice,
1581
+ }) ?? undefined
1582
+ : undefined;
1583
+ let tddStatePath;
1584
+ if (slice.redEvidence && slice.greenEvidence && slice.refactorEvidence) {
1585
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
1586
+ writer.writeTDDEvidence({
1587
+ timestamp: new Date().toISOString(),
1588
+ taskId: slice.taskId,
1589
+ red: slice.redEvidence.exitCode !== 0,
1590
+ green: slice.greenEvidence.exitCode === 0,
1591
+ refactor: slice.refactorEvidence.exitCode === 0,
1592
+ testFirst: slice.redEvidence.exitCode !== 0,
1593
+ testFile: slice.testFile,
1594
+ implFile: slice.implementationFiles[0] ?? '',
1688
1595
  });
1689
- result.created.push(...governance.created);
1690
- result.skipped.push(...governance.skipped);
1691
- console.log(`\n✅ SCALE Engine initialized for ${agentType} (interactive mode)`);
1692
- console.log(`\n📁 Created:`);
1693
- for (const f of result.created)
1694
- console.log(` + ${f}`);
1695
- if (result.skipped.length > 0) {
1696
- console.log(`\n⏭️ Skipped (already exist):`);
1697
- for (const f of result.skipped)
1698
- console.log(` - ${f}`);
1699
- }
1700
- console.log(`\n🔧 Configuration Summary:`);
1701
- console.log(` Settings: ${result.settingsPath}`);
1702
- console.log(` Knowledge: ${result.knowledgeDocPath}`);
1703
- console.log(` Thresholds: ${thresholdsPath}`);
1704
- console.log(` Data dir: ${result.scaleDir}`);
1705
- console.log(` Scenario: ${scenarioMode}`);
1706
- console.log(`\n📋 Next steps:`);
1707
- console.log(` → scale doctor`);
1708
- console.log(` → scale create Spec "<feature name>"`);
1596
+ writer.updateCurrentState({
1597
+ taskId: slice.taskId,
1598
+ phase: 'verify',
1599
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
1600
+ filesModified: slice.implementationFiles,
1601
+ openTasks: removeWorkflowOpenTask(writer.readCurrentState()?.openTasks, 'tdd-slice'),
1602
+ });
1603
+ tddStatePath = join(writer.getStateDir(), `tdd-${slice.taskId}.json`);
1604
+ }
1605
+ if (args.json) {
1606
+ console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
1607
+ return;
1608
+ }
1609
+ console.log(renderTddSliceMarkdown(slice));
1610
+ if (evaluation.blockers.length > 0) {
1611
+ console.log('\nBlockers:');
1612
+ for (const blocker of evaluation.blockers)
1613
+ console.log(` - ${blocker}`);
1614
+ }
1615
+ if (artifactPath)
1616
+ console.log(`\nArtifact: ${artifactPath}`);
1617
+ if (tddStatePath)
1618
+ console.log(`TDD state: ${tddStatePath}`);
1619
+ },
1620
+ });
1621
+ const tdd = defineCommand({
1622
+ meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
1623
+ subCommands: { slice: tddSliceCommand },
1624
+ });
1625
+ // ============================================================================
1626
+ // stats
1627
+ // ============================================================================
1628
+ const stats = defineCommand({
1629
+ meta: { name: 'stats', description: 'Show engine stats' },
1630
+ args: {},
1631
+ async run() {
1632
+ const { store, eventBus } = getEngine();
1633
+ const s = store.stats();
1634
+ const events = await eventBus.query({ limit: 1000 });
1635
+ console.log(JSON.stringify({ ...s, eventCount: events.length }, null, 2));
1636
+ },
1637
+ });
1638
+ const metricsList = defineCommand({
1639
+ meta: { name: 'list', description: 'List M/L task workflow metrics' },
1640
+ args: {
1641
+ json: { type: 'boolean', default: false },
1642
+ },
1643
+ async run({ args }) {
1644
+ const store = new TaskMetricsStore(SCALE_DIR);
1645
+ const records = store.list();
1646
+ const summary = store.summarize();
1647
+ if (args.json) {
1648
+ console.log(JSON.stringify({ summary, records }, null, 2));
1649
+ return;
1650
+ }
1651
+ console.log('\nWorkflow Metrics');
1652
+ console.log(` Total tasks: ${summary.total}`);
1653
+ console.log(` First-pass verification rate: ${(summary.firstPassRate * 100).toFixed(1)}%`);
1654
+ console.log(` Average fix iterations: ${summary.averageFixIterations.toFixed(2)}`);
1655
+ console.log(` Artifact completeness: ${(summary.artifactCompletenessRate * 100).toFixed(1)}%`);
1656
+ for (const record of records.slice(-10)) {
1657
+ console.log(` - ${record.date} ${record.level} ${record.taskName}: ${record.finalGateStatus}`);
1658
+ }
1659
+ },
1660
+ });
1661
+ const metrics = defineCommand({
1662
+ meta: { name: 'metrics', description: 'Inspect workflow task metrics' },
1663
+ subCommands: { list: metricsList },
1664
+ });
1665
+ function normalizeTaskArtifactLevel(value) {
1666
+ const normalized = String(value ?? 'M').trim().toUpperCase();
1667
+ if (normalized === 'S' || normalized === 'M' || normalized === 'L' || normalized === 'CRITICAL') {
1668
+ return normalized;
1669
+ }
1670
+ throw new Error(`Invalid task level "${String(value)}"; expected S, M, L, or CRITICAL.`);
1671
+ }
1672
+ const taskArtifactsCheck = defineCommand({
1673
+ meta: { name: 'check', description: 'Check task artifact completeness' },
1674
+ args: {
1675
+ dir: { type: 'string', description: 'Task artifact directory; defaults to .scale/state/current.json artifactsDir' },
1676
+ level: { type: 'string', description: 'Task level: S, M, L, or CRITICAL; defaults to current state level or M' },
1677
+ 'warn-only': { type: 'boolean', default: false, description: 'Return zero even when artifacts are incomplete' },
1678
+ json: { type: 'boolean', default: false },
1679
+ },
1680
+ run({ args }) {
1681
+ const state = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1682
+ let level;
1683
+ try {
1684
+ level = normalizeTaskArtifactLevel(args.level ?? state?.level ?? 'M');
1685
+ }
1686
+ catch (e) {
1687
+ console.error(e.message);
1688
+ process.exit(1);
1689
+ }
1690
+ const result = checkTaskArtifactCompleteness({
1691
+ projectDir: PROJECT_DIR,
1692
+ artifactsDir: args.dir ?? state?.artifactsDir,
1693
+ level,
1694
+ skillRequiredArtifacts: state?.requiredSkillArtifacts,
1695
+ });
1696
+ if (args.json) {
1697
+ console.log(JSON.stringify(result, null, 2));
1698
+ }
1699
+ else {
1700
+ console.log(`\nTask Artifacts: ${result.complete ? 'COMPLETE' : 'INCOMPLETE'}`);
1701
+ if (result.artifactsDir)
1702
+ console.log(` Directory: ${result.artifactsDir}`);
1703
+ console.log(` Required: ${result.required.join(', ') || 'none'}`);
1704
+ for (const file of result.missing)
1705
+ console.log(` [MISSING] ${file}`);
1706
+ for (const item of result.incomplete)
1707
+ console.log(` [INCOMPLETE] ${item.file}: ${item.reason}`);
1708
+ }
1709
+ if (!result.complete && !args['warn-only'])
1710
+ process.exitCode = 1;
1711
+ },
1712
+ });
1713
+ const taskArtifacts = defineCommand({
1714
+ meta: { name: 'task-artifacts', description: 'Inspect task artifact completeness' },
1715
+ subCommands: { check: taskArtifactsCheck },
1716
+ });
1717
+ function printWorkspaceLifecycle(report) {
1718
+ console.log('\nSCALE Workspace Lifecycle');
1719
+ console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
1720
+ console.log(` Root: ${report.root.path}`);
1721
+ console.log(` Branch: ${report.root.branch ?? '(detached)'}`);
1722
+ console.log(` Linked worktree: ${report.root.isLinkedWorktree ? 'yes' : 'no'}`);
1723
+ console.log(` Root status: ${report.root.clean ? 'clean' : 'dirty'}`);
1724
+ if (!report.root.clean) {
1725
+ console.log(` staged=${report.root.staged} unstaged=${report.root.unstaged} untracked=${report.root.untracked}`);
1726
+ }
1727
+ if (report.childRepositories.length) {
1728
+ console.log('\n Child repositories:');
1729
+ for (const child of report.childRepositories) {
1730
+ console.log(` ${child.clean ? '[CLEAN]' : '[DIRTY]'} ${child.relativePath} (${child.kind}) branch=${child.branch ?? '(detached)'}`);
1731
+ if (!child.clean)
1732
+ console.log(` staged=${child.staged} unstaged=${child.unstaged} untracked=${child.untracked}`);
1733
+ }
1734
+ }
1735
+ else {
1736
+ console.log('\n Child repositories: none');
1737
+ }
1738
+ console.log(`\n Cleanup candidate: ${report.finish.canCleanup ? 'yes' : 'no'}`);
1739
+ for (const blocker of report.finish.blockers)
1740
+ console.log(` [BLOCKER] ${blocker}`);
1741
+ for (const warning of report.finish.warnings)
1742
+ console.log(` [WARN] ${warning}`);
1743
+ for (const action of report.finish.nextActions)
1744
+ console.log(` [NEXT] ${action}`);
1745
+ }
1746
+ function compactList(values, limit = 5) {
1747
+ if (values.length <= limit)
1748
+ return values.join(', ');
1749
+ return `${values.slice(0, limit).join(', ')} (+${values.length - limit} more)`;
1750
+ }
1751
+ function printWorkspaceSummary(report) {
1752
+ const dirtyChildren = report.childRepositories
1753
+ .filter(child => !child.clean)
1754
+ .map(child => child.relativePath);
1755
+ const unpushedChildren = report.childRepositories
1756
+ .filter(child => child.ahead > 0 || (report.topology.finishPolicy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && Boolean(child.branch)))
1757
+ .map(child => child.relativePath);
1758
+ const noUpstreamChildren = report.childRepositories
1759
+ .filter(child => !child.upstream && Boolean(child.branch))
1760
+ .map(child => child.relativePath);
1761
+ const rootStatus = report.root.clean
1762
+ ? 'clean'
1763
+ : `dirty (staged=${report.root.staged}, unstaged=${report.root.unstaged}, untracked=${report.root.untracked})`;
1764
+ const status = report.finish.blockers.length > 0 ? 'BLOCKED' : 'READY';
1765
+ console.log('\nSCALE Workspace Summary');
1766
+ console.log(` Status: ${status}`);
1767
+ console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
1768
+ console.log(` Root: ${rootStatus}`);
1769
+ console.log(` Children: ${report.childRepositories.length} total, ${dirtyChildren.length} dirty, ${unpushedChildren.length} unpushed, ${noUpstreamChildren.length} no upstream`);
1770
+ if (dirtyChildren.length > 0)
1771
+ console.log(` Dirty child repositories: ${compactList(dirtyChildren)}`);
1772
+ if (unpushedChildren.length > 0)
1773
+ console.log(` Unpushed child repositories: ${compactList(unpushedChildren)}`);
1774
+ if (report.finish.blockers.length > 0) {
1775
+ console.log('\n Blockers:');
1776
+ for (const blocker of report.finish.blockers.slice(0, 8))
1777
+ console.log(` - ${blocker}`);
1778
+ if (report.finish.blockers.length > 8)
1779
+ console.log(` - ... ${report.finish.blockers.length - 8} more blocker(s)`);
1780
+ }
1781
+ if (report.finish.warnings.length > 0) {
1782
+ console.log(`\n Warnings: ${report.finish.warnings.length} warning(s); run scale workspace finish --json for details`);
1783
+ }
1784
+ console.log('\n Next:');
1785
+ const nextActions = report.finish.blockers.length > 0
1786
+ ? report.finish.nextActions
1787
+ : ['Proceed with scale ship <task-id> or cleanup when the branch policy is satisfied'];
1788
+ for (const action of nextActions.slice(0, 3))
1789
+ console.log(` - ${action}`);
1790
+ console.log(' - Run scale workspace finish --json for full details');
1791
+ }
1792
+ function printWorkspaceTopology(topology, written) {
1793
+ console.log('\nSCALE Workspace Topology');
1794
+ console.log(` Topology: ${topology.topology}${topology.configured ? '' : ' (default)'}`);
1795
+ console.log(` Config: ${topology.configPath}`);
1796
+ if (written)
1797
+ console.log(` Written: ${written}`);
1798
+ console.log('\n Repositories:');
1799
+ for (const repo of topology.repositories) {
1800
+ console.log(` - ${repo.name}: ${repo.path} (${repo.role}) required=${repo.required !== false ? 'yes' : 'no'}`);
1801
+ }
1802
+ for (const warning of topology.warnings)
1803
+ console.log(` [WARN] ${warning}`);
1804
+ }
1805
+ function printWorkspaceCleanup(result) {
1806
+ printWorkspaceLifecycle(result.report);
1807
+ console.log('\n Cleanup plan:');
1808
+ console.log(` Mode: ${result.mode}`);
1809
+ console.log(` Target: ${result.targetPath}`);
1810
+ console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
1811
+ console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
1812
+ console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
1813
+ for (const command of result.commands)
1814
+ console.log(` Command: ${command}`);
1815
+ for (const blocker of result.blockers)
1816
+ console.log(` [BLOCKER] ${blocker}`);
1817
+ for (const warning of result.warnings)
1818
+ console.log(` [WARN] ${warning}`);
1819
+ }
1820
+ const workspaceStatus = defineCommand({
1821
+ meta: { name: 'status', description: 'Inspect root worktree and child repository lifecycle state' },
1822
+ args: {
1823
+ dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1824
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
1825
+ json: { type: 'boolean', default: false },
1826
+ },
1827
+ async run({ args }) {
1828
+ const report = await inspectWorkspaceLifecycle({ projectDir: args.dir ?? PROJECT_DIR });
1829
+ if (args.json) {
1830
+ console.log(JSON.stringify(report, null, 2));
1831
+ }
1832
+ else if (isTruthyFlag(args.summary)) {
1833
+ printWorkspaceSummary(report);
1834
+ }
1835
+ else {
1836
+ printWorkspaceLifecycle(report);
1837
+ }
1838
+ if (report.finish.blockers.length > 0)
1839
+ process.exitCode = 1;
1840
+ },
1841
+ });
1842
+ const workspaceMap = defineCommand({
1843
+ meta: { name: 'map', description: 'Resolve or write explicit workspace topology for single, monorepo, polyrepo, submodule, or MOE projects' },
1844
+ args: {
1845
+ dir: { type: 'string', description: 'Project directory; defaults to current project directory' },
1846
+ topology: { type: 'string', default: 'moe', description: 'Starter topology for --write (single/monorepo/polyrepo/submodule-workspace/moe)' },
1847
+ write: { type: 'boolean', default: false, description: 'Create .scale/workspace.json when it does not exist' },
1848
+ json: { type: 'boolean', default: false },
1849
+ },
1850
+ run({ args }) {
1851
+ const projectDir = resolve(args.dir ?? PROJECT_DIR);
1852
+ const target = workspaceTopologyPath(projectDir);
1853
+ let written = null;
1854
+ if (isTruthyFlag(args.write) && !existsSync(target)) {
1855
+ ensureDir(join(projectDir, '.scale'));
1856
+ writeFileSync(target, workspaceTopologyTemplate({
1857
+ topology: normalizeWorkspaceTopologyKind(args.topology),
1858
+ }), 'utf-8');
1859
+ written = target;
1860
+ }
1861
+ const topology = resolveWorkspaceTopology({ projectDir });
1862
+ const result = { ...topology, written };
1863
+ if (args.json) {
1864
+ console.log(JSON.stringify(result, null, 2));
1865
+ }
1866
+ else {
1867
+ printWorkspaceTopology(topology, written);
1868
+ }
1869
+ },
1870
+ });
1871
+ const workspaceFinish = defineCommand({
1872
+ meta: { name: 'finish', description: 'Check whether a temporary worktree can be safely finished or cleaned up' },
1873
+ args: {
1874
+ dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1875
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
1876
+ json: { type: 'boolean', default: false },
1877
+ },
1878
+ async run({ args }) {
1879
+ const report = await inspectWorkspaceLifecycle({ projectDir: args.dir ?? PROJECT_DIR });
1880
+ const result = {
1881
+ root: report.root,
1882
+ childRepositories: report.childRepositories,
1883
+ topology: report.topology,
1884
+ finish: report.finish,
1885
+ };
1886
+ if (args.json) {
1887
+ console.log(JSON.stringify(result, null, 2));
1888
+ }
1889
+ else if (isTruthyFlag(args.summary)) {
1890
+ printWorkspaceSummary(report);
1891
+ }
1892
+ else {
1893
+ printWorkspaceLifecycle(report);
1894
+ }
1895
+ if (report.finish.blockers.length > 0)
1896
+ process.exitCode = 1;
1897
+ },
1898
+ });
1899
+ const workspaceCleanup = defineCommand({
1900
+ meta: { name: 'cleanup', description: 'Dry-run or apply safe removal of a linked temporary worktree' },
1901
+ args: {
1902
+ dir: { type: 'string', description: 'Linked worktree directory; defaults to current project directory' },
1903
+ 'dry-run': { type: 'boolean', default: false, description: 'Preview cleanup; this is the default unless --apply is set' },
1904
+ apply: { type: 'boolean', default: false, description: 'Actually run git worktree remove after safety checks' },
1905
+ confirm: { type: 'string', description: 'Required confirmation token for --apply, usually the worktree branch name' },
1906
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary before the cleanup plan' },
1907
+ json: { type: 'boolean', default: false },
1908
+ },
1909
+ async run({ args }) {
1910
+ const result = await cleanupWorkspaceLifecycle({
1911
+ projectDir: args.dir ?? PROJECT_DIR,
1912
+ apply: isTruthyFlag(args.apply),
1913
+ confirm: args.confirm,
1914
+ });
1915
+ if (args.json) {
1916
+ console.log(JSON.stringify(result, null, 2));
1917
+ }
1918
+ else if (isTruthyFlag(args.summary)) {
1919
+ printWorkspaceSummary(result.report);
1920
+ console.log('\n Cleanup:');
1921
+ console.log(` Mode: ${result.mode}`);
1922
+ console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
1923
+ console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
1924
+ console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
1925
+ }
1926
+ else {
1927
+ printWorkspaceCleanup(result);
1928
+ }
1929
+ if (!result.canApply || (isTruthyFlag(args.apply) && !result.applied))
1930
+ process.exitCode = 1;
1931
+ },
1932
+ });
1933
+ const workspace = defineCommand({
1934
+ meta: { name: 'workspace', description: 'Inspect worktree, branch, and child repository lifecycle safety' },
1935
+ subCommands: {
1936
+ map: workspaceMap,
1937
+ status: workspaceStatus,
1938
+ finish: workspaceFinish,
1939
+ cleanup: workspaceCleanup,
1940
+ },
1941
+ });
1942
+ function normalizeWorkspaceTopologyKind(value) {
1943
+ const normalized = String(value ?? 'moe').trim();
1944
+ if (normalized === 'single'
1945
+ || normalized === 'monorepo'
1946
+ || normalized === 'polyrepo'
1947
+ || normalized === 'submodule-workspace'
1948
+ || normalized === 'moe') {
1949
+ return normalized;
1950
+ }
1951
+ return 'moe';
1952
+ }
1953
+ const preflight = defineCommand({
1954
+ meta: { name: 'preflight', description: 'Run service-aware verification without a task artifact' },
1955
+ args: {
1956
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1957
+ 'build-cmd': { type: 'string', description: 'Override build command' },
1958
+ 'lint-cmd': { type: 'string', description: 'Override lint command' },
1959
+ 'test-cmd': { type: 'string', description: 'Override test command' },
1960
+ 'coverage-cmd': { type: 'string', description: 'Override coverage command' },
1961
+ profile: { type: 'string', description: 'Verification profile from .scale/verification.json' },
1962
+ 'preflight-profile': { type: 'string', default: 'quick', description: 'Gate intensity profile (quick/full/ci); quick skips coverage and security' },
1963
+ service: { type: 'string', description: 'Service name from .scale/verification.json; use all for required services' },
1964
+ 'tdd-evidence': { type: 'string', description: 'Path to JSON TDD evidence with red/green/refactor/testFirst=true' },
1965
+ 'tdd-strict': { type: 'boolean', default: false, description: 'Require TDD evidence before other gates' },
1966
+ json: { type: 'boolean', default: false },
1967
+ },
1968
+ async run({ args }) {
1969
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1970
+ const scaleDir = resolveScaleDirForProject(projectDir);
1971
+ const workflowEngine = createVerificationWorkflowEngine(scaleDir);
1972
+ const preflightProfile = normalizePreflightProfile(args['preflight-profile']);
1973
+ const resolved = resolveVerificationTargets({
1974
+ projectDir,
1975
+ scaleDir,
1976
+ profile: args.profile,
1977
+ service: args.service,
1978
+ });
1979
+ let gateStages = gatesForPreflightProfile(preflightProfile);
1980
+ if (resolved.targets.some(target => target.config.smoke)) {
1981
+ gateStages = ['G8'];
1982
+ }
1983
+ const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
1984
+ if (commandTargetsSkipped) {
1985
+ resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
1986
+ }
1987
+ const workspaceSafety = inspectWorkspaceSafety(projectDir);
1988
+ const engineeringStandards = workspaceSafety.blocked
1989
+ ? skippedEngineeringStandardsGate('Workspace has unresolved git conflicts; resolve them before standards scanning.', resolved.policy)
1990
+ : evaluateEngineeringStandardsGate({
1991
+ policy: resolved.policy,
1992
+ projectDir,
1993
+ scaleDir,
1994
+ });
1995
+ const targetResults = [];
1996
+ if (!args.json) {
1997
+ console.log('\nSCALE Preflight');
1998
+ for (const warning of resolved.warnings)
1999
+ console.log(` [WARN] ${warning}`);
2000
+ console.log(` Profile: ${resolved.profileName}`);
2001
+ console.log(` Preflight profile: ${preflightProfile}`);
2002
+ console.log(` Gates: ${gateStages.join(', ')}`);
2003
+ if (workspaceSafety.blocked) {
2004
+ console.log(` Workspace safety: BLOCKED - ${workspaceSafety.message}`);
2005
+ }
2006
+ if (engineeringStandards.checked) {
2007
+ const status = engineeringStandards.blocked ? 'BLOCKED' : engineeringStandards.ok ? 'OK' : 'WARN';
2008
+ console.log(` Engineering standards: ${status} (${engineeringStandards.mode})`);
2009
+ }
2010
+ else {
2011
+ console.log(' Engineering standards: skipped');
2012
+ }
2013
+ }
2014
+ for (const target of commandTargetsSkipped || workspaceSafety.blocked ? [] : resolved.targets) {
2015
+ if (!args.json) {
2016
+ const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
2017
+ console.log(`\n Target: ${label}`);
2018
+ }
2019
+ const gates = await workflowEngine.verify({
2020
+ cwd: target.config.cwd,
2021
+ build: args['build-cmd'] ?? target.config.build,
2022
+ lint: args['lint-cmd'] ?? target.config.lint,
2023
+ test: args['test-cmd'] ?? target.config.test,
2024
+ coverage: args['coverage-cmd'] ?? target.config.coverage,
2025
+ smoke: target.config.smoke,
2026
+ runtimeEvidence: {
2027
+ projectDir,
2028
+ scaleDir,
2029
+ profile: resolved.profileName,
2030
+ },
2031
+ tddEvidence: args['tdd-evidence'],
2032
+ tddStrict: isTruthyFlag(args['tdd-strict']),
2033
+ gates: gateStages,
2034
+ });
2035
+ const passed = gates.every(gate => gate.passed);
2036
+ targetResults.push({
2037
+ service: target.service?.name,
2038
+ cwd: target.config.cwd ?? projectDir,
2039
+ gates,
2040
+ passed,
2041
+ });
2042
+ if (!args.json) {
2043
+ for (const gate of gates) {
2044
+ console.log(` ${gate.passed ? '[PASS]' : '[FAIL]'} ${gate.gate}: ${gate.evidence.slice(0, 80)}`);
2045
+ for (const blocker of gate.blockers)
2046
+ console.log(` [BLOCKER] ${blocker.slice(0, 120)}`);
2047
+ }
2048
+ }
2049
+ }
2050
+ const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
2051
+ !workspaceSafety.blocked &&
2052
+ !engineeringStandards.blocked;
2053
+ const result = {
2054
+ phase: 'PREFLIGHT',
2055
+ profile: resolved.profileName,
2056
+ preflightProfile,
2057
+ gates: gateStages,
2058
+ services: targetResults.map(target => target.service).filter(Boolean),
2059
+ policy: resolved.policy,
2060
+ workspaceSafety,
2061
+ engineeringStandards,
2062
+ targets: targetResults,
2063
+ commandTargetsSkipped,
2064
+ passed,
2065
+ };
2066
+ if (args.json) {
2067
+ console.log(JSON.stringify(result, null, 2));
2068
+ }
2069
+ else {
2070
+ console.log(`\nPREFLIGHT: ${passed ? 'PASSED' : 'FAILED'}\n`);
2071
+ }
2072
+ if (!passed)
2073
+ process.exitCode = 1;
2074
+ },
2075
+ });
2076
+ const status = defineCommand({
2077
+ meta: { name: 'status', description: 'Show current SCALE workflow status' },
2078
+ args: {
2079
+ json: { type: 'boolean', default: false },
2080
+ },
2081
+ async run({ args }) {
2082
+ const { store } = getEngine();
2083
+ const evidenceStore = new EvidenceStore(SCALE_DIR);
2084
+ const reviewStore = new ReviewStore(SCALE_DIR);
2085
+ const [specs, plans, tasks, releases] = await Promise.all([
2086
+ store.query({ type: 'Spec', limit: 1 }),
2087
+ store.query({ type: 'Plan', limit: 1 }),
2088
+ store.query({ type: 'Task', limit: 1 }),
2089
+ store.query({ type: 'Release', limit: 1 }),
2090
+ ]);
2091
+ const latestEvidence = evidenceStore.listGateResults(5);
2092
+ const latestReviews = reviewStore.listReviews(5);
2093
+ const latestTask = tasks[0];
2094
+ const taskPayload = latestTask?.payload;
2095
+ const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
2096
+ const currentOpenTasks = workflowState?.openTasks ?? [];
2097
+ const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
2098
+ const blockers = [];
2099
+ const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
2100
+ const latestBlockingReview = latestReviews.find(record => !record.passed);
2101
+ if (latestBlockingEvidence)
2102
+ blockers.push(`${latestBlockingEvidence.gate}: ${latestBlockingEvidence.blockers.join('; ') || latestBlockingEvidence.status}`);
2103
+ if (latestBlockingReview)
2104
+ blockers.push(`Review ${latestBlockingReview.id}: ${latestBlockingReview.summary.critical} critical, ${latestBlockingReview.summary.high} high`);
2105
+ if (latestTask && (!taskPayload?.verificationEvidenceIds || taskPayload.verificationEvidenceIds.length === 0)) {
2106
+ blockers.push(`Task ${latestTask.id} has no persisted verification evidence`);
2107
+ }
2108
+ if (latestTask?.status === 'COMPLETED' && (!taskPayload?.reviewEvidenceIds || taskPayload.reviewEvidenceIds.length === 0)) {
2109
+ blockers.push(`Task ${latestTask.id} has no persisted review evidence`);
2110
+ }
2111
+ const nextCommand = (() => {
2112
+ if (nextOpenTask?.kind === 'command')
2113
+ return nextOpenTask.value;
2114
+ if (nextOpenTask?.kind === 'blocker')
2115
+ return `Resolve workflow blocker: ${nextOpenTask.value}`;
2116
+ if (!specs[0])
2117
+ return 'scale define "<feature>" --description "<what to build>"';
2118
+ if (!plans[0])
2119
+ return `scale plan ${specs[0].id}`;
2120
+ if (!latestTask)
2121
+ return `scale build ${plans[0].id}`;
2122
+ if (!taskPayload?.verificationEvidenceIds?.length)
2123
+ return `scale verify ${latestTask.id}`;
2124
+ if (latestTask.status !== 'COMPLETED')
2125
+ return `scale verify ${latestTask.id}`;
2126
+ if (!taskPayload.reviewEvidenceIds?.length || taskPayload.reviewPassed !== true)
2127
+ return `scale review ${latestTask.id}`;
2128
+ if (!releases[0])
2129
+ return `scale ship ${latestTask.id}`;
2130
+ return 'scale evidence list';
2131
+ })();
2132
+ const result = {
2133
+ artifacts: {
2134
+ latestSpec: specs[0] ? { id: specs[0].id, status: specs[0].status, title: specs[0].title } : null,
2135
+ latestPlan: plans[0] ? { id: plans[0].id, status: plans[0].status, title: plans[0].title } : null,
2136
+ latestTask: latestTask ? {
2137
+ id: latestTask.id,
2138
+ status: latestTask.status,
2139
+ title: latestTask.title,
2140
+ lintStatus: taskPayload?.lintStatus,
2141
+ testPassed: taskPayload?.testPassed,
2142
+ testCoverage: taskPayload?.testCoverage,
2143
+ evidenceIds: taskPayload?.verificationEvidenceIds ?? [],
2144
+ reviewPassed: taskPayload?.reviewPassed,
2145
+ reviewEvidenceIds: taskPayload?.reviewEvidenceIds ?? [],
2146
+ } : null,
2147
+ },
2148
+ recentEvidence: latestEvidence.map(record => ({
2149
+ id: record.id,
2150
+ gate: record.gate,
2151
+ status: record.status,
2152
+ passed: record.passed,
2153
+ blockers: record.blockers,
2154
+ createdAt: record.createdAt,
2155
+ })),
2156
+ recentReviews: latestReviews.map(record => ({
2157
+ id: record.id,
2158
+ taskId: record.taskId,
2159
+ passed: record.passed,
2160
+ summary: record.summary,
2161
+ createdAt: record.createdAt,
2162
+ })),
2163
+ workflowState: workflowState ? {
2164
+ taskId: workflowState.taskId,
2165
+ level: workflowState.level,
2166
+ phase: workflowState.phase,
2167
+ artifactsDir: workflowState.artifactsDir,
2168
+ openTasks: workflowState.openTasks ?? [],
2169
+ skillIntents: workflowState.skillIntents,
2170
+ } : null,
2171
+ blockers,
2172
+ nextCommand,
2173
+ };
2174
+ if (args.json) {
2175
+ console.log(JSON.stringify(result, null, 2));
2176
+ return;
2177
+ }
2178
+ console.log('\nSCALE Status');
2179
+ console.log('Artifacts:');
2180
+ console.log(` Spec: ${result.artifacts.latestSpec ? `${result.artifacts.latestSpec.id} (${result.artifacts.latestSpec.status})` : 'none'}`);
2181
+ console.log(` Plan: ${result.artifacts.latestPlan ? `${result.artifacts.latestPlan.id} (${result.artifacts.latestPlan.status})` : 'none'}`);
2182
+ console.log(` Task: ${result.artifacts.latestTask ? `${result.artifacts.latestTask.id} (${result.artifacts.latestTask.status})` : 'none'}`);
2183
+ if (result.artifacts.latestTask?.evidenceIds.length) {
2184
+ console.log(` Task evidence: ${result.artifacts.latestTask.evidenceIds.join(', ')}`);
2185
+ }
2186
+ console.log('\nRecent Evidence:');
2187
+ if (result.recentEvidence.length === 0) {
2188
+ console.log(' none');
2189
+ }
2190
+ else {
2191
+ for (const record of result.recentEvidence) {
2192
+ console.log(` ${record.id} ${record.gate} ${record.passed ? 'PASS' : record.status}`);
2193
+ }
2194
+ }
2195
+ if (blockers.length > 0) {
2196
+ console.log('\nBlockers:');
2197
+ for (const blocker of blockers)
2198
+ console.log(` - ${blocker}`);
2199
+ }
2200
+ if ((result.workflowState?.openTasks.length ?? 0) > 0) {
2201
+ console.log('\nOpen Tasks:');
2202
+ for (const task of result.workflowState.openTasks)
2203
+ console.log(` - ${task}`);
2204
+ }
2205
+ console.log(`\nNext: ${nextCommand}`);
2206
+ },
2207
+ });
2208
+ // ============================================================================
2209
+ // init command
2210
+ // ============================================================================
2211
+ const init = defineCommand({
2212
+ meta: { name: 'init', description: 'Initialize SCALE Engine in current project (one-click install)' },
2213
+ args: {
2214
+ agent: { type: 'string', default: '', description: `Agent type (${SUPPORTED_AGENTS.join('/')}) - auto-detected if not specified` },
2215
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2216
+ json: { type: 'boolean', default: false, description: 'Output initialization result as JSON' },
2217
+ scenario: { type: 'string', default: 'standard', description: 'Scenario mode (sandbox/standard/critical)' },
2218
+ 'governance-pack': {
2219
+ type: 'string',
2220
+ default: 'standard',
2221
+ description: 'Governance template pack (standard/project-scaffold/moe-workspace/resource-governance/go-service-matrix/node-library/frontend-app)',
2222
+ },
2223
+ quick: { type: 'boolean', default: false, description: 'Quick start with auto-detection' },
2224
+ interactive: { type: 'boolean', default: false, description: 'Interactive configuration mode with prompts' },
2225
+ 'coverage-threshold': { type: 'string', default: '80', description: 'Coverage threshold (default 80%)' },
2226
+ 'retry-threshold': { type: 'string', default: '3', description: 'Brute retry threshold (default 3)' },
2227
+ 'block-severity': { type: 'string', default: 'CRITICAL', description: 'Block severity level (CRITICAL/HIGH/MEDIUM)' },
2228
+ },
2229
+ async run({ args }) {
2230
+ // Interactive configuration mode
2231
+ if (args.interactive) {
2232
+ console.log('\n🔧 SCALE Engine Interactive Configuration\n');
2233
+ console.log('='.repeat(50));
2234
+ // Step 1: Detect and suggest agent platform
2235
+ const detection = detectPlatform(args.dir);
2236
+ console.log('\n📋 Step 1: Agent Platform Selection');
2237
+ console.log(` Detected suggestions: ${detection.suggestions.join(', ') || 'none'}`);
2238
+ const agentType = args.agent || detection.suggestions[0] || 'claude-code';
2239
+ console.log(` Using: ${agentType}`);
2240
+ // Step 2: Scenario mode
2241
+ console.log('\n📋 Step 2: Scenario Mode');
2242
+ console.log(' sandbox - No quality gates (POC/prototype)');
2243
+ console.log(' standard - Default quality gates');
2244
+ console.log(' critical - Hardened gates + manual approval');
2245
+ const scenarioMode = args.scenario;
2246
+ console.log(` Using: ${scenarioMode}`);
2247
+ // Step 3: Quality Gate Thresholds (quantified)
2248
+ console.log('\n📋 Step 3: Quality Gate Thresholds');
2249
+ const coverageThreshold = parseInt(args['coverage-threshold'], 10) || 80;
2250
+ const retryThreshold = parseInt(args['retry-threshold'], 10) || 3;
2251
+ const blockSeverity = args['block-severity'] || 'CRITICAL';
2252
+ console.log(` Coverage threshold: ${coverageThreshold}%`);
2253
+ console.log(` Retry threshold: ${retryThreshold} (brute retry block)`);
2254
+ console.log(` Block severity: ${blockSeverity}`);
2255
+ // Step 4: Write thresholds to .scale/thresholds.json
2256
+ const thresholdsPath = join(args.dir, '.scale', 'thresholds.json');
2257
+ ensureDir(join(args.dir, '.scale'));
2258
+ writeFileSync(thresholdsPath, JSON.stringify({
2259
+ coverage: { minimum: coverageThreshold, unit: 'percent' },
2260
+ retry: { bruteMaximum: retryThreshold, unit: 'count' },
2261
+ severity: { blockLevel: blockSeverity },
2262
+ gates: {
2263
+ G3_build: { required: scenarioMode !== 'sandbox', exitCode: 0 },
2264
+ G4_lint: { required: scenarioMode !== 'sandbox', exitCode: 0 },
2265
+ G5_tests: { required: scenarioMode !== 'sandbox', allPass: true },
2266
+ G6_coverage: { required: scenarioMode !== 'sandbox', minimum: coverageThreshold },
2267
+ G7_security: { required: scenarioMode === 'critical', noCritical: true },
2268
+ },
2269
+ }, null, 2));
2270
+ console.log(`\n ✓ Thresholds written to: ${thresholdsPath}`);
2271
+ // Initialize with adapter
2272
+ const adapter = createAdapter(agentType);
2273
+ const result = await adapter.init({
2274
+ projectDir: args.dir,
2275
+ agentType: agentType,
2276
+ scenarioMode,
2277
+ thresholdsPath,
2278
+ });
2279
+ const projectName = args.dir.split(/[/\\]/).pop() || 'Project';
2280
+ const governance = writeGovernanceTemplates(args.dir, {
2281
+ mode: governanceModeFromScenario(scenarioMode),
2282
+ projectName,
2283
+ pack: args['governance-pack'],
2284
+ });
2285
+ result.created.push(...governance.created);
2286
+ result.skipped.push(...governance.skipped);
2287
+ console.log(`\n✅ SCALE Engine initialized for ${agentType} (interactive mode)`);
2288
+ console.log(`\n📁 Created:`);
2289
+ for (const f of result.created)
2290
+ console.log(` + ${f}`);
2291
+ if (result.skipped.length > 0) {
2292
+ console.log(`\n⏭️ Skipped (already exist):`);
2293
+ for (const f of result.skipped)
2294
+ console.log(` - ${f}`);
2295
+ }
2296
+ console.log(`\n🔧 Configuration Summary:`);
2297
+ console.log(` Settings: ${result.settingsPath}`);
2298
+ console.log(` Knowledge: ${result.knowledgeDocPath}`);
2299
+ console.log(` Thresholds: ${thresholdsPath}`);
2300
+ console.log(` Data dir: ${result.scaleDir}`);
2301
+ console.log(` Scenario: ${scenarioMode}`);
2302
+ console.log(`\n📋 Next steps:`);
2303
+ for (const step of governanceNextSteps())
2304
+ console.log(` → ${step}`);
2305
+ return;
2306
+ }
2307
+ // One-click quick start mode
2308
+ if (!args.agent) {
2309
+ const qsResult = await quickStart(args.dir, { governancePack: args['governance-pack'] });
2310
+ if (args.json) {
2311
+ const detection = qsResult.success ? undefined : detectPlatform(args.dir);
2312
+ console.log(JSON.stringify({
2313
+ ok: qsResult.success,
2314
+ mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
2315
+ platform: qsResult.platform,
2316
+ created: qsResult.created,
2317
+ skipped: qsResult.skipped,
2318
+ constraintsApplied: qsResult.constraintsApplied,
2319
+ capabilitiesEnabled: qsResult.capabilitiesEnabled,
2320
+ knowledgeGraph: qsResult.knowledgeGraph,
2321
+ nextSteps: qsResult.nextSteps,
2322
+ suggestions: detection?.suggestions ?? [],
2323
+ }, null, 2));
2324
+ return;
2325
+ }
2326
+ if (qsResult.success) {
2327
+ if (!qsResult.platform)
2328
+ console.log(`\nSCALE governance templates initialized`);
2329
+ else
2330
+ console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
2331
+ console.log(`\n📁 Created (${qsResult.created.length}):`);
2332
+ for (const f of qsResult.created)
2333
+ console.log(` + ${f}`);
2334
+ if (qsResult.skipped.length > 0) {
2335
+ console.log(`\n⏭️ Skipped (${qsResult.skipped.length}):`);
2336
+ for (const f of qsResult.skipped)
2337
+ console.log(` - ${f}`);
2338
+ }
2339
+ console.log(`\n🔒 Physical constraints applied: ${qsResult.constraintsApplied}`);
2340
+ console.log(`\n🚀 Capabilities enabled: ${qsResult.capabilitiesEnabled.join(', ')}`);
2341
+ console.log(`\n📋 Next steps:`);
2342
+ for (const step of qsResult.nextSteps)
2343
+ console.log(` → ${step}`);
2344
+ }
2345
+ else {
2346
+ console.log(`\n⚠️ No agent platform detected`);
2347
+ const detection = detectPlatform(args.dir);
2348
+ console.log(`\n📋 Suggested platforms: ${detection.suggestions.join(', ')}`);
2349
+ console.log(`\n→ Run: scale init --agent <platform>`);
2350
+ }
2351
+ return;
2352
+ }
2353
+ // Manual agent specification mode
2354
+ const adapter = createAdapter(args.agent);
2355
+ const result = await adapter.init({ projectDir: args.dir, agentType: args.agent, scenarioMode: args.scenario });
2356
+ const projectName = args.dir.split(/[/\\]/).pop() || 'Project';
2357
+ const governance = writeGovernanceTemplates(args.dir, {
2358
+ mode: governanceModeFromScenario(args.scenario),
2359
+ projectName,
2360
+ pack: args['governance-pack'],
2361
+ });
2362
+ result.created.push(...governance.created);
2363
+ result.skipped.push(...governance.skipped);
2364
+ if (args.json) {
2365
+ console.log(JSON.stringify({
2366
+ ok: true,
2367
+ mode: args.quick ? 'quick-agent' : 'manual',
2368
+ agent: args.agent,
2369
+ scenario: args.scenario,
2370
+ governancePack: args['governance-pack'],
2371
+ settingsPath: result.settingsPath,
2372
+ knowledgeDocPath: result.knowledgeDocPath,
2373
+ scaleDir: result.scaleDir,
2374
+ created: result.created,
2375
+ skipped: result.skipped,
2376
+ nextSteps: governanceNextSteps(),
2377
+ }, null, 2));
2378
+ return;
2379
+ }
2380
+ console.log(`\n✅ SCALE Engine initialized for ${args.agent} (scenario: ${args.scenario})`);
2381
+ console.log(`\n📁 Created:`);
2382
+ for (const f of result.created)
2383
+ console.log(` + ${f}`);
2384
+ if (result.skipped.length > 0) {
2385
+ console.log(`\n⏭️ Skipped (already exist):`);
2386
+ for (const f of result.skipped)
2387
+ console.log(` - ${f}`);
2388
+ }
2389
+ console.log(`\n🔧 Settings: ${result.settingsPath}`);
2390
+ console.log(`\n📖 Knowledge: ${result.knowledgeDocPath}`);
2391
+ console.log(`\n📂 Data dir: ${result.scaleDir}`);
2392
+ console.log(`\n📋 Next steps:`);
2393
+ for (const step of governanceNextSteps())
2394
+ console.log(` → ${step}`);
2395
+ },
2396
+ });
2397
+ // ============================================================================
2398
+ // governance command — Generated governance asset tooling
2399
+ // ============================================================================
2400
+ const governanceDiff = defineCommand({
2401
+ meta: { name: 'diff', description: 'Check generated governance files for drift' },
2402
+ args: {
2403
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2404
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2405
+ },
2406
+ run({ args }) {
2407
+ const report = computeGovernanceDrift(args.dir);
2408
+ if (args.json) {
2409
+ console.log(JSON.stringify(report, null, 2));
2410
+ return;
2411
+ }
2412
+ if (!report.lockExists) {
2413
+ console.log('No governance lock found. Run: scale init --governance-pack <pack>');
2414
+ return;
2415
+ }
2416
+ if (report.missing.length === 0 && report.changed.length === 0) {
2417
+ console.log('Governance generated files are clean.');
2418
+ return;
2419
+ }
2420
+ for (const item of report.missing)
2421
+ console.log(`missing: ${item.path}`);
2422
+ for (const item of report.changed)
2423
+ console.log(`changed: ${item.path}`);
2424
+ },
2425
+ });
2426
+ const governanceModeCommand = defineCommand({
2427
+ meta: { name: 'mode', description: 'Evaluate progressive governance mode from task text and changed files' },
2428
+ args: {
2429
+ task: { type: 'string', description: 'Task or requirement description' },
2430
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
2431
+ 'requested-mode': { type: 'string', description: 'Requested governance mode: minimal, standard, expanded, or critical' },
2432
+ json: { type: 'boolean', default: false },
2433
+ },
2434
+ run({ args }) {
2435
+ const report = evaluateProgressiveGovernance({
2436
+ task: args.task ? String(args.task) : undefined,
2437
+ changedFiles: parseCommaList(args.files),
2438
+ requestedMode: normalizeGovernanceMode(args['requested-mode']),
2439
+ });
2440
+ if (args.json) {
2441
+ console.log(JSON.stringify(report, null, 2));
2442
+ return;
2443
+ }
2444
+ console.log('SCALE Progressive Governance');
2445
+ console.log(` Recommended: ${report.recommendedMode}`);
2446
+ console.log(` Effective: ${report.effectiveMode}`);
2447
+ if (report.escalated)
2448
+ console.log(` Escalated from requested mode: ${report.requestedMode}`);
2449
+ for (const signal of report.signals) {
2450
+ console.log(` [${signal.mode}] ${signal.id}: ${signal.reason}`);
2451
+ }
2452
+ for (const behavior of report.requiredBehaviors) {
2453
+ console.log(` behavior: ${behavior}`);
2454
+ }
2455
+ },
2456
+ });
2457
+ const governanceRoiCommand = defineCommand({
2458
+ meta: { name: 'roi', description: 'Report benefit and overhead signals for active governance modules' },
2459
+ args: {
2460
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
2461
+ 'task-id': { type: 'string', description: 'Task id' },
2462
+ task: { type: 'string', description: 'Task or requirement description' },
2463
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
2464
+ 'requested-mode': { type: 'string', description: 'Requested governance mode' },
2465
+ 'code-query': { type: 'string', description: 'Optional code intelligence query to include in ROI' },
2466
+ symbol: { type: 'string', description: 'Optional symbol impact query to include in ROI' },
2467
+ json: { type: 'boolean', default: false },
2468
+ },
2469
+ run({ args }) {
2470
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
2471
+ const scaleDir = resolveScaleDirForProject(projectDir);
2472
+ const governanceReport = evaluateProgressiveGovernance({
2473
+ task: args.task ? String(args.task) : undefined,
2474
+ changedFiles: parseCommaList(args.files),
2475
+ requestedMode: normalizeGovernanceMode(args['requested-mode']),
2476
+ });
2477
+ const contextBudget = scanContextBudget({ projectDir, scaleDir });
2478
+ const codeIntelligence = args.symbol
2479
+ ? impactCodeGraph({ projectDir, scaleDir, symbol: String(args.symbol) })
2480
+ : args['code-query']
2481
+ ? queryCodeGraph({ projectDir, scaleDir, query: String(args['code-query']) })
2482
+ : undefined;
2483
+ const report = createGovernanceRoiReport({
2484
+ taskId: args['task-id'] ? String(args['task-id']) : undefined,
2485
+ contextBudget,
2486
+ governance: governanceReport,
2487
+ codeIntelligence,
2488
+ });
2489
+ if (args.json) {
2490
+ console.log(JSON.stringify(report, null, 2));
2491
+ return;
2492
+ }
2493
+ console.log('SCALE Governance ROI');
2494
+ console.log(` Recommendation: ${report.summary.recommendation}`);
2495
+ for (const module of report.modules) {
2496
+ console.log(` [${module.evidenceLevel}] ${module.module}`);
2497
+ console.log(` benefit: ${module.benefit}`);
2498
+ console.log(` overhead: ${module.overhead}`);
2499
+ console.log(` recommendation: ${module.recommendation}`);
2500
+ }
2501
+ },
2502
+ });
2503
+ const governance = defineCommand({
2504
+ meta: { name: 'governance', description: 'Governance template pack tools' },
2505
+ subCommands: { diff: governanceDiff, mode: governanceModeCommand, roi: governanceRoiCommand },
2506
+ });
2507
+ // ============================================================================
2508
+ // assets command - Resource lifecycle governance
2509
+ // ============================================================================
2510
+ const assetsScan = defineCommand({
2511
+ meta: { name: 'scan', description: 'Classify project docs, reports, media, scripts, and temporary outputs' },
2512
+ args: {
2513
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2514
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2515
+ },
2516
+ run({ args }) {
2517
+ const report = scanResourceAssets({ projectDir: args.dir });
2518
+ if (args.json) {
2519
+ console.log(JSON.stringify(report, null, 2));
2520
+ return;
2521
+ }
2522
+ console.log('SCALE Asset Scan');
2523
+ console.log(` Project: ${report.projectDir}`);
2524
+ console.log(` Total resources: ${report.summary.total}`);
2525
+ console.log(` Tracked forbidden: ${report.summary.trackedForbidden}`);
2526
+ console.log(` Large tracked: ${report.summary.largeTracked}`);
2527
+ console.log(` Expired: ${report.summary.expired}`);
2528
+ console.log('\nBy type:');
2529
+ for (const [type, count] of Object.entries(report.summary.byType)) {
2530
+ if (count > 0)
2531
+ console.log(` ${type}: ${count}`);
2532
+ }
2533
+ },
2534
+ });
2535
+ const assetsDoctor = defineCommand({
2536
+ meta: { name: 'doctor', description: 'Find resource lifecycle and Git policy problems' },
2537
+ args: {
2538
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2539
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2540
+ },
2541
+ run({ args }) {
2542
+ const report = doctorResourceAssets({ projectDir: args.dir });
2543
+ if (args.json) {
2544
+ console.log(JSON.stringify(report, null, 2));
2545
+ return;
2546
+ }
2547
+ console.log(`SCALE Asset Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2548
+ if (report.findings.length === 0) {
2549
+ console.log(' No resource lifecycle findings.');
2550
+ return;
2551
+ }
2552
+ for (const finding of report.findings) {
2553
+ const path = finding.path ? ` ${finding.path}` : '';
2554
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2555
+ if (finding.fix)
2556
+ console.log(` fix: ${finding.fix}`);
2557
+ }
2558
+ if (!report.ok)
2559
+ process.exitCode = 1;
2560
+ },
2561
+ });
2562
+ const assetsSettle = defineCommand({
2563
+ meta: { name: 'settle', description: 'Record resource lifecycle settlement evidence for a task' },
2564
+ args: {
2565
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2566
+ 'task-id': { type: 'string', description: 'Task id for the settlement record' },
2567
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where resource-impact.md should be updated' },
2568
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2569
+ },
2570
+ run({ args }) {
2571
+ const report = settleResourceAssets({
2572
+ projectDir: args.dir,
2573
+ taskId: args['task-id'],
2574
+ artifactsDir: args['artifact-dir'],
2575
+ });
2576
+ if (args.json) {
2577
+ console.log(JSON.stringify(report, null, 2));
2578
+ }
2579
+ else {
2580
+ console.log(`SCALE Asset Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2581
+ if (report.resourceImpactPath)
2582
+ console.log(` Resource impact: ${report.resourceImpactPath}`);
2583
+ if (report.doctor.findings.length > 0) {
2584
+ for (const finding of report.doctor.findings) {
2585
+ const path = finding.path ? ` ${finding.path}` : '';
2586
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2587
+ }
2588
+ }
2589
+ }
2590
+ if (!report.ok)
2591
+ process.exitCode = 1;
2592
+ },
2593
+ });
2594
+ const assets = defineCommand({
2595
+ meta: { name: 'assets', description: 'Resource lifecycle governance for generated and maintained project assets' },
2596
+ subCommands: { scan: assetsScan, doctor: assetsDoctor, settle: assetsSettle },
2597
+ });
2598
+ // ============================================================================
2599
+ // standards command - Engineering standards governance
2600
+ // ============================================================================
2601
+ function resolveChangedFilesArg(args) {
2602
+ const explicit = splitChangedFiles(args['changed-files']);
2603
+ if (explicit.length > 0)
2604
+ return explicit;
2605
+ if (!args.changed)
2606
+ return undefined;
2607
+ return readGitChangedFiles(args.dir ?? '.');
2608
+ }
2609
+ function splitChangedFiles(value) {
2610
+ if (!value)
2611
+ return [];
2612
+ return value
2613
+ .split(/[\n,]/)
2614
+ .map(item => item.trim())
2615
+ .filter(Boolean);
2616
+ }
2617
+ function readGitChangedFiles(projectDir) {
2618
+ const tracked = readGitPathList(projectDir, ['diff', '--name-only', '--diff-filter=ACMRTUXB', 'HEAD', '--']);
2619
+ const untracked = readGitPathList(projectDir, ['ls-files', '--others', '--exclude-standard']);
2620
+ return Array.from(new Set([...tracked, ...untracked]));
2621
+ }
2622
+ function readGitPathList(projectDir, args) {
2623
+ try {
2624
+ return execFileSync('git', ['-C', projectDir, ...args], { encoding: 'utf-8' })
2625
+ .split(/\r?\n/)
2626
+ .map(item => item.trim())
2627
+ .filter(Boolean);
2628
+ }
2629
+ catch {
2630
+ return [];
2631
+ }
2632
+ }
2633
+ const standardsScan = defineCommand({
2634
+ meta: { name: 'scan', description: 'Scan source files for engineering standard violations' },
2635
+ args: {
2636
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2637
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
2638
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
2639
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2640
+ },
2641
+ run({ args }) {
2642
+ const report = scanEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
2643
+ if (args.json) {
2644
+ console.log(JSON.stringify(report, null, 2));
2645
+ return;
2646
+ }
2647
+ console.log('SCALE Standards Scan');
2648
+ console.log(` Project: ${report.projectDir}`);
2649
+ console.log(` Files scanned: ${report.summary.filesScanned}`);
2650
+ console.log(` Findings: ${report.summary.totalFindings}`);
2651
+ console.log(` Blocking findings: ${report.summary.blockingFindings}`);
2652
+ for (const finding of report.findings.slice(0, 20)) {
2653
+ const line = finding.line ? `:${finding.line}` : '';
2654
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
2655
+ }
2656
+ if (report.findings.length > 20)
2657
+ console.log(` ... ${report.findings.length - 20} more finding(s)`);
2658
+ },
2659
+ });
2660
+ const standardsDoctor = defineCommand({
2661
+ meta: { name: 'doctor', description: 'Find blocking engineering standards problems' },
2662
+ args: {
2663
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2664
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
2665
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
2666
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2667
+ },
2668
+ run({ args }) {
2669
+ const report = doctorEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
2670
+ if (args.json) {
2671
+ console.log(JSON.stringify(report, null, 2));
2672
+ if (!report.ok)
2673
+ process.exitCode = 1;
2674
+ return;
2675
+ }
2676
+ console.log(`SCALE Standards Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2677
+ if (report.findings.length === 0) {
2678
+ console.log(' No engineering standards findings.');
2679
+ return;
2680
+ }
2681
+ for (const finding of report.findings) {
2682
+ const line = finding.line ? `:${finding.line}` : '';
2683
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
2684
+ if (finding.fix)
2685
+ console.log(` fix: ${finding.fix}`);
2686
+ }
2687
+ if (!report.ok)
2688
+ process.exitCode = 1;
2689
+ },
2690
+ });
2691
+ const standardsSettle = defineCommand({
2692
+ meta: { name: 'settle', description: 'Record engineering standards settlement evidence for a task' },
2693
+ args: {
2694
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2695
+ 'task-id': { type: 'string', description: 'Task id for the settlement record' },
2696
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where standards-impact.md should be updated' },
2697
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
2698
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
2699
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2700
+ },
2701
+ run({ args }) {
2702
+ const report = settleEngineeringStandards({
2703
+ projectDir: args.dir,
2704
+ taskId: args['task-id'],
2705
+ artifactsDir: args['artifact-dir'],
2706
+ changedFiles: resolveChangedFilesArg(args),
2707
+ });
2708
+ if (args.json) {
2709
+ console.log(JSON.stringify(report, null, 2));
2710
+ }
2711
+ else {
2712
+ console.log(`SCALE Standards Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2713
+ if (report.standardsImpactPath)
2714
+ console.log(` Standards impact: ${report.standardsImpactPath}`);
2715
+ for (const finding of report.doctor.findings) {
2716
+ const line = finding.line ? `:${finding.line}` : '';
2717
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
2718
+ }
2719
+ }
2720
+ if (!report.ok)
2721
+ process.exitCode = 1;
2722
+ },
2723
+ });
2724
+ const standardsBaseline = defineCommand({
2725
+ meta: { name: 'baseline', description: 'Generate a legacy standards baseline and classification report' },
2726
+ args: {
2727
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2728
+ write: { type: 'boolean', default: false, description: 'Write .scale/engineering-standards-baseline.json' },
2729
+ 'task-id': { type: 'string', description: 'Task id for the legacy debt report' },
2730
+ 'artifact-dir': { type: 'string', description: 'Directory where standards-legacy-debt.md should be written' },
2731
+ reason: { type: 'string', default: 'legacy standards debt accepted for staged remediation', description: 'Reason recorded on generated baseline entries' },
2732
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2733
+ },
2734
+ run({ args }) {
2735
+ const report = baselineEngineeringStandards({
2736
+ projectDir: args.dir,
2737
+ writeBaseline: args.write,
2738
+ taskId: args['task-id'],
2739
+ artifactsDir: args['artifact-dir'],
2740
+ reason: args.reason,
2741
+ });
2742
+ if (args.json) {
2743
+ console.log(JSON.stringify(report, null, 2));
2744
+ return;
2745
+ }
2746
+ console.log(`Standards baseline: ${report.wroteBaseline ? 'written' : 'dry-run'}`);
2747
+ console.log(` Baseline entries: ${report.baselineEntries.length}`);
2748
+ console.log(` Blocking findings: ${report.debt.blockingFindings}`);
2749
+ console.log(` Baseline path: ${report.baselinePath}`);
2750
+ if (report.legacyDebtPath)
2751
+ console.log(` Legacy debt report: ${report.legacyDebtPath}`);
2752
+ if (!report.wroteBaseline)
2753
+ console.log(' Re-run with --write to update .scale/engineering-standards-baseline.json.');
2754
+ },
2755
+ });
2756
+ const standards = defineCommand({
2757
+ meta: { name: 'standards', description: 'Engineering standards governance for logs, security, architecture, database, and code quality' },
2758
+ subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
2759
+ });
2760
+ // ============================================================================
2761
+ // artifact command - Derived HTML artifacts for human review
2762
+ // ============================================================================
2763
+ const artifactRender = defineCommand({
2764
+ meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
2765
+ args: {
2766
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2767
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2768
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2769
+ type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
2770
+ source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
2771
+ theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
2772
+ lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
2773
+ title: { type: 'string', description: 'HTML document title override' },
2774
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2775
+ },
2776
+ run({ args }) {
2777
+ const result = renderHtmlArtifact({
2778
+ projectDir: args.dir,
2779
+ taskId: args['task-id'],
2780
+ artifactDir: args['artifact-dir'],
2781
+ type: String(args.type ?? 'release-report'),
2782
+ sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
2783
+ theme: normalizeThemeArg(args.theme),
2784
+ lang: normalizeLangArg(args.lang),
2785
+ title: typeof args.title === 'string' ? args.title : undefined,
2786
+ });
2787
+ if (args.json) {
2788
+ console.log(JSON.stringify(result, null, 2));
1709
2789
  return;
1710
2790
  }
1711
- // One-click quick start mode
1712
- if (!args.agent) {
1713
- const qsResult = await quickStart(args.dir, { governancePack: args['governance-pack'] });
1714
- if (args.json) {
1715
- const detection = qsResult.success ? undefined : detectPlatform(args.dir);
1716
- console.log(JSON.stringify({
1717
- ok: qsResult.success,
1718
- mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
1719
- platform: qsResult.platform,
1720
- created: qsResult.created,
1721
- skipped: qsResult.skipped,
1722
- constraintsApplied: qsResult.constraintsApplied,
1723
- capabilitiesEnabled: qsResult.capabilitiesEnabled,
1724
- knowledgeGraph: qsResult.knowledgeGraph,
1725
- nextSteps: qsResult.nextSteps,
1726
- suggestions: detection?.suggestions ?? [],
1727
- }, null, 2));
1728
- return;
1729
- }
1730
- if (qsResult.success) {
1731
- if (!qsResult.platform)
1732
- console.log(`\nSCALE governance templates initialized`);
1733
- else
1734
- console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
1735
- console.log(`\n📁 Created (${qsResult.created.length}):`);
1736
- for (const f of qsResult.created)
1737
- console.log(` + ${f}`);
1738
- if (qsResult.skipped.length > 0) {
1739
- console.log(`\n⏭️ Skipped (${qsResult.skipped.length}):`);
1740
- for (const f of qsResult.skipped)
1741
- console.log(` - ${f}`);
1742
- }
1743
- console.log(`\n🔒 Physical constraints applied: ${qsResult.constraintsApplied}`);
1744
- console.log(`\n🚀 Capabilities enabled: ${qsResult.capabilitiesEnabled.join(', ')}`);
1745
- console.log(`\n📋 Next steps:`);
1746
- for (const step of qsResult.nextSteps)
1747
- console.log(` → ${step}`);
1748
- }
1749
- else {
1750
- console.log(`\n⚠️ No agent platform detected`);
1751
- const detection = detectPlatform(args.dir);
1752
- console.log(`\n📋 Suggested platforms: ${detection.suggestions.join(', ')}`);
1753
- console.log(`\n→ Run: scale init --agent <platform>`);
2791
+ console.log('SCALE HTML Artifact Render');
2792
+ console.log(` Type: ${result.type}`);
2793
+ console.log(` HTML: ${result.outputPath}`);
2794
+ console.log(` Index: ${result.indexPath}`);
2795
+ console.log(` Manifest: ${result.manifestPath}`);
2796
+ if (result.missingSources.length > 0) {
2797
+ console.log(` Missing sources: ${result.missingSources.join(', ')}`);
2798
+ }
2799
+ },
2800
+ });
2801
+ const artifactDoctor = defineCommand({
2802
+ meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
2803
+ args: {
2804
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2805
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2806
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2807
+ type: { type: 'string', description: 'Optional HTML artifact type to check' },
2808
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2809
+ },
2810
+ run({ args }) {
2811
+ const report = doctorHtmlArtifacts({
2812
+ projectDir: args.dir,
2813
+ taskId: args['task-id'],
2814
+ artifactDir: args['artifact-dir'],
2815
+ type: typeof args.type === 'string' ? args.type : undefined,
2816
+ });
2817
+ if (args.json) {
2818
+ console.log(JSON.stringify(report, null, 2));
2819
+ if (!report.ok)
2820
+ process.exitCode = 1;
2821
+ return;
2822
+ }
2823
+ console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2824
+ console.log(` Manifest: ${report.manifestPath}`);
2825
+ console.log(` Artifacts: ${report.artifacts.length}`);
2826
+ if (report.findings.length === 0) {
2827
+ console.log(' No HTML artifact findings.');
2828
+ }
2829
+ else {
2830
+ for (const finding of report.findings) {
2831
+ const path = finding.path ? ` ${finding.path}` : '';
2832
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2833
+ if (finding.fix)
2834
+ console.log(` fix: ${finding.fix}`);
1754
2835
  }
2836
+ }
2837
+ if (!report.ok)
2838
+ process.exitCode = 1;
2839
+ },
2840
+ });
2841
+ const artifactSettle = defineCommand({
2842
+ meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
2843
+ args: {
2844
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2845
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2846
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2847
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2848
+ },
2849
+ run({ args }) {
2850
+ const report = settleHtmlArtifacts({
2851
+ projectDir: args.dir,
2852
+ taskId: args['task-id'],
2853
+ artifactDir: args['artifact-dir'],
2854
+ });
2855
+ if (args.json) {
2856
+ console.log(JSON.stringify(report, null, 2));
2857
+ if (!report.ok)
2858
+ process.exitCode = 1;
1755
2859
  return;
1756
2860
  }
1757
- // Manual agent specification mode
1758
- const adapter = createAdapter(args.agent);
1759
- const result = await adapter.init({ projectDir: args.dir, agentType: args.agent, scenarioMode: args.scenario });
1760
- const projectName = args.dir.split(/[/\\]/).pop() || 'Project';
1761
- const governance = writeGovernanceTemplates(args.dir, {
1762
- mode: governanceModeFromScenario(args.scenario),
1763
- projectName,
1764
- pack: args['governance-pack'],
2861
+ console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2862
+ console.log(` HTML impact: ${report.htmlImpactPath}`);
2863
+ for (const finding of report.doctor.findings) {
2864
+ const path = finding.path ? ` ${finding.path}` : '';
2865
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2866
+ }
2867
+ if (!report.ok)
2868
+ process.exitCode = 1;
2869
+ },
2870
+ });
2871
+ const artifactOpen = defineCommand({
2872
+ meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
2873
+ args: {
2874
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2875
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2876
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2877
+ type: { type: 'string', description: 'Optional HTML artifact type to open' },
2878
+ 'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
2879
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2880
+ },
2881
+ run({ args }) {
2882
+ const path = resolveHtmlArtifactForOpen({
2883
+ projectDir: args.dir,
2884
+ taskId: args['task-id'],
2885
+ artifactDir: args['artifact-dir'],
2886
+ type: typeof args.type === 'string' ? args.type : undefined,
1765
2887
  });
1766
- result.created.push(...governance.created);
1767
- result.skipped.push(...governance.skipped);
2888
+ const url = pathToFileURL(path).toString();
2889
+ const exists = existsSync(path);
2890
+ if (!args['print-only'] && exists)
2891
+ launchLocalFile(path);
2892
+ const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
1768
2893
  if (args.json) {
1769
- console.log(JSON.stringify({
1770
- ok: true,
1771
- mode: args.quick ? 'quick-agent' : 'manual',
1772
- agent: args.agent,
1773
- scenario: args.scenario,
1774
- governancePack: args['governance-pack'],
1775
- settingsPath: result.settingsPath,
1776
- knowledgeDocPath: result.knowledgeDocPath,
1777
- scaleDir: result.scaleDir,
1778
- created: result.created,
1779
- skipped: result.skipped,
1780
- nextSteps: ['scale doctor', 'scale create Spec "<feature name>"'],
1781
- }, null, 2));
2894
+ console.log(JSON.stringify(output, null, 2));
2895
+ if (!exists)
2896
+ process.exitCode = 1;
1782
2897
  return;
1783
2898
  }
1784
- console.log(`\n✅ SCALE Engine initialized for ${args.agent} (scenario: ${args.scenario})`);
1785
- console.log(`\n📁 Created:`);
1786
- for (const f of result.created)
1787
- console.log(` + ${f}`);
1788
- if (result.skipped.length > 0) {
1789
- console.log(`\n⏭️ Skipped (already exist):`);
1790
- for (const f of result.skipped)
1791
- console.log(` - ${f}`);
2899
+ if (!exists) {
2900
+ console.log(`HTML artifact not found: ${path}`);
2901
+ process.exitCode = 1;
2902
+ return;
1792
2903
  }
1793
- console.log(`\n🔧 Settings: ${result.settingsPath}`);
1794
- console.log(`\n📖 Knowledge: ${result.knowledgeDocPath}`);
1795
- console.log(`\n📂 Data dir: ${result.scaleDir}`);
1796
- console.log(`\n📋 Next steps:`);
1797
- console.log(` → scale doctor`);
1798
- console.log(` → scale create Spec "<feature name>"`);
2904
+ console.log(url);
2905
+ },
2906
+ });
2907
+ const artifactDashboard = defineCommand({
2908
+ meta: { name: 'dashboard', description: 'Render a governance HTML dashboard from runtime, eval, memory, resource, and artifact evidence' },
2909
+ args: {
2910
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2911
+ 'task-id': { type: 'string', description: 'Optional task id to scope runtime/eval evidence and task HTML artifacts' },
2912
+ output: { type: 'string', alias: 'o', description: 'Output HTML path; defaults to .scale/reports/governance-dashboard.html' },
2913
+ theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
2914
+ lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
2915
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2916
+ },
2917
+ run({ args }) {
2918
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
2919
+ const scaleDir = resolveScaleDirForProject(projectDir);
2920
+ const report = renderGovernanceDashboard({
2921
+ projectDir,
2922
+ scaleDir,
2923
+ taskId: typeof args['task-id'] === 'string' ? args['task-id'] : undefined,
2924
+ output: typeof args.output === 'string' ? args.output : undefined,
2925
+ theme: normalizeThemeArg(args.theme),
2926
+ lang: normalizeLangArg(args.lang),
2927
+ });
2928
+ if (args.json) {
2929
+ console.log(JSON.stringify(report, null, 2));
2930
+ if (!report.ok)
2931
+ process.exitCode = 1;
2932
+ return;
2933
+ }
2934
+ console.log(`SCALE Governance Dashboard: ${report.ok ? 'OK' : 'ATTENTION'}`);
2935
+ console.log(` HTML: ${report.outputPath}`);
2936
+ console.log(` Manifest: ${report.manifestPath}`);
2937
+ for (const finding of report.findings) {
2938
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}: ${finding.message}`);
2939
+ }
2940
+ if (!report.ok)
2941
+ process.exitCode = 1;
2942
+ },
2943
+ });
2944
+ const artifact = defineCommand({
2945
+ meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
2946
+ subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen, dashboard: artifactDashboard },
2947
+ });
2948
+ function normalizeThemeArg(value) {
2949
+ const normalized = String(value ?? 'auto').trim().toLowerCase();
2950
+ if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
2951
+ return normalized;
2952
+ return 'auto';
2953
+ }
2954
+ function normalizeLangArg(value) {
2955
+ return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
2956
+ }
2957
+ function launchLocalFile(path) {
2958
+ try {
2959
+ if (process.platform === 'win32') {
2960
+ execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
2961
+ }
2962
+ else if (process.platform === 'darwin') {
2963
+ execFileSync('open', [path], { stdio: 'ignore' });
2964
+ }
2965
+ else {
2966
+ execFileSync('xdg-open', [path], { stdio: 'ignore' });
2967
+ }
2968
+ }
2969
+ catch {
2970
+ // Opening is convenience-only; artifact doctor/render remains the source of truth.
2971
+ }
2972
+ }
2973
+ // ============================================================================
2974
+ // evolve command
2975
+ // ============================================================================
2976
+ const evolve = defineCommand({
2977
+ meta: { name: 'evolve', description: 'Run evolution cycle (Defect→Lesson→Rule→Hook)' },
2978
+ args: {},
2979
+ async run() {
2980
+ const { store, kb, eventBus } = getEngine();
2981
+ const extractor = new LessonExtractor(store, kb, eventBus);
2982
+ const proposer = new RuleProposer(kb, eventBus);
2983
+ const generator = new HookGenerator(eventBus);
2984
+ const engine = new EvolutionEngine(extractor, proposer, generator, eventBus, SCALE_DIR);
2985
+ const stats = await engine.runCycle();
2986
+ console.log(JSON.stringify(stats, null, 2));
1799
2987
  },
1800
2988
  });
1801
2989
  // ============================================================================
1802
- // governance command — Generated governance asset tooling
2990
+ // doctor command
1803
2991
  // ============================================================================
1804
- const governanceDiff = defineCommand({
1805
- meta: { name: 'diff', description: 'Check generated governance files for drift' },
2992
+ const doctor = defineCommand({
2993
+ meta: { name: 'doctor', description: 'Diagnose SCALE Engine health' },
1806
2994
  args: {
1807
2995
  dir: { type: 'string', default: '.', description: 'Project directory' },
1808
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
2996
+ json: { type: 'boolean', default: false, description: 'Output as JSON' },
1809
2997
  },
1810
- run({ args }) {
1811
- const report = computeGovernanceDrift(args.dir);
2998
+ async run({ args }) {
2999
+ const doc = new Doctor(args.dir);
3000
+ const report = await doc.diagnose();
1812
3001
  if (args.json) {
1813
3002
  console.log(JSON.stringify(report, null, 2));
1814
- return;
1815
- }
1816
- if (!report.lockExists) {
1817
- console.log('No governance lock found. Run: scale init --governance-pack <pack>');
1818
- return;
1819
3003
  }
1820
- if (report.missing.length === 0 && report.changed.length === 0) {
1821
- console.log('Governance generated files are clean.');
1822
- return;
3004
+ else {
3005
+ console.log(doc.formatReport(report));
1823
3006
  }
1824
- for (const item of report.missing)
1825
- console.log(`missing: ${item.path}`);
1826
- for (const item of report.changed)
1827
- console.log(`changed: ${item.path}`);
3007
+ process.exitCode = report.overall === 'broken' ? 1 : 0;
1828
3008
  },
1829
3009
  });
1830
- const governance = defineCommand({
1831
- meta: { name: 'governance', description: 'Governance template pack tools' },
1832
- subCommands: { diff: governanceDiff },
1833
- });
1834
3010
  // ============================================================================
1835
- // assets command - Resource lifecycle governance
3011
+ // workflow command 列出/查看工作流预设
1836
3012
  // ============================================================================
1837
- const assetsScan = defineCommand({
1838
- meta: { name: 'scan', description: 'Classify project docs, reports, media, scripts, and temporary outputs' },
3013
+ const workflowList = defineCommand({
3014
+ meta: { name: 'list', description: 'List all workflow presets' },
1839
3015
  args: {
1840
- dir: { type: 'string', default: '.', description: 'Project directory' },
1841
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3016
+ scenario: { type: 'string', description: 'Filter by scenario mode (sandbox/standard/critical)' },
3017
+ json: { type: 'boolean', default: false, description: 'Output workflow presets as JSON' },
1842
3018
  },
1843
- run({ args }) {
1844
- const report = scanResourceAssets({ projectDir: args.dir });
3019
+ async run({ args }) {
3020
+ const presets = args.scenario
3021
+ ? getPresetsByScenario(args.scenario)
3022
+ : listWorkflowPresets();
1845
3023
  if (args.json) {
1846
- console.log(JSON.stringify(report, null, 2));
3024
+ console.log(JSON.stringify({
3025
+ ok: true,
3026
+ scenario: args.scenario ?? null,
3027
+ count: presets.length,
3028
+ presets: presets.map(preset => ({
3029
+ id: preset.id,
3030
+ name: preset.name,
3031
+ nameZh: preset.nameZh,
3032
+ description: preset.description,
3033
+ scenarioMode: preset.scenarioMode,
3034
+ requiredArtifacts: preset.requiredArtifacts,
3035
+ steps: preset.steps,
3036
+ })),
3037
+ }, null, 2));
1847
3038
  return;
1848
3039
  }
1849
- console.log('SCALE Asset Scan');
1850
- console.log(` Project: ${report.projectDir}`);
1851
- console.log(` Total resources: ${report.summary.total}`);
1852
- console.log(` Tracked forbidden: ${report.summary.trackedForbidden}`);
1853
- console.log(` Large tracked: ${report.summary.largeTracked}`);
1854
- console.log(` Expired: ${report.summary.expired}`);
1855
- console.log('\nBy type:');
1856
- for (const [type, count] of Object.entries(report.summary.byType)) {
1857
- if (count > 0)
1858
- console.log(` ${type}: ${count}`);
3040
+ if (presets.length === 0) {
3041
+ console.log('No workflow presets found.');
3042
+ return;
3043
+ }
3044
+ console.log('\n📋 SCALE Engine Workflow Presets');
3045
+ console.log('═══════════════════════════════════════════════════════');
3046
+ for (const preset of presets) {
3047
+ const modeEmoji = { sandbox: '🏖️', standard: '⚙️', critical: '🔒' }[preset.scenarioMode];
3048
+ const mandatorySteps = preset.steps.filter((s) => s.isMandatory).length;
3049
+ const totalSteps = preset.steps.length;
3050
+ console.log(`\n ${preset.nameZh} (${preset.id})`);
3051
+ console.log(` ${preset.description}`);
3052
+ console.log(` Mode: ${modeEmoji} ${preset.scenarioMode} · Steps: ${mandatorySteps}/${totalSteps} mandatory`);
3053
+ if (preset.requiredArtifacts.length > 0) {
3054
+ console.log(` Requires: ${preset.requiredArtifacts.map((a) => `${a.type}${a.status ? `(${a.status})` : ''}`).join(', ')}`);
3055
+ }
3056
+ // Show step summary
3057
+ for (const step of preset.steps) {
3058
+ const marker = step.isMandatory ? '●' : '○';
3059
+ const gate = step.verificationGate ? ` ⊓ ${step.verificationGate}` : '';
3060
+ console.log(` ${marker} ${step.stepId}: ${step.action}${gate}`);
3061
+ }
1859
3062
  }
3063
+ console.log('\n═══════════════════════════════════════════════════════');
3064
+ console.log('\nUsage: scale workflow show <preset-id>');
1860
3065
  },
1861
3066
  });
1862
- const assetsDoctor = defineCommand({
1863
- meta: { name: 'doctor', description: 'Find resource lifecycle and Git policy problems' },
3067
+ const workflow = defineCommand({
3068
+ meta: { name: 'workflow', description: 'Workflow preset management' },
3069
+ subCommands: { list: workflowList },
3070
+ });
3071
+ const evidenceList = defineCommand({
3072
+ meta: { name: 'list', description: 'List persisted gate evidence records' },
1864
3073
  args: {
1865
- dir: { type: 'string', default: '.', description: 'Project directory' },
1866
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3074
+ limit: { type: 'string', default: '20', description: 'Maximum number of records' },
3075
+ json: { type: 'boolean', default: false },
1867
3076
  },
1868
- run({ args }) {
1869
- const report = doctorResourceAssets({ projectDir: args.dir });
3077
+ async run({ args }) {
3078
+ const store = new EvidenceStore(SCALE_DIR);
3079
+ const records = store.listGateResults(parseInt(args.limit, 10) || 20);
1870
3080
  if (args.json) {
1871
- console.log(JSON.stringify(report, null, 2));
3081
+ console.log(JSON.stringify(records, null, 2));
1872
3082
  return;
1873
3083
  }
1874
- console.log(`SCALE Asset Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
1875
- if (report.findings.length === 0) {
1876
- console.log(' No resource lifecycle findings.');
3084
+ if (records.length === 0) {
3085
+ console.log('No evidence records found.');
1877
3086
  return;
1878
3087
  }
1879
- for (const finding of report.findings) {
1880
- const path = finding.path ? ` ${finding.path}` : '';
1881
- console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
1882
- if (finding.fix)
1883
- console.log(` fix: ${finding.fix}`);
3088
+ console.log('\nSCALE Evidence Records');
3089
+ for (const record of records) {
3090
+ const status = record.passed ? 'PASS' : record.status;
3091
+ const blockers = record.blockers.length > 0 ? ` blockers=${record.blockers.length}` : '';
3092
+ console.log(` ${record.id} ${record.gate} ${status} ${new Date(record.createdAt).toISOString()}${blockers}`);
1884
3093
  }
1885
- if (!report.ok)
1886
- process.exitCode = 1;
3094
+ console.log('\nUsage: scale evidence show <id>');
1887
3095
  },
1888
3096
  });
1889
- const assetsSettle = defineCommand({
1890
- meta: { name: 'settle', description: 'Record resource lifecycle settlement evidence for a task' },
3097
+ const evidenceShow = defineCommand({
3098
+ meta: { name: 'show', description: 'Show a persisted gate evidence record' },
1891
3099
  args: {
1892
- dir: { type: 'string', default: '.', description: 'Project directory' },
1893
- 'task-id': { type: 'string', description: 'Task id for the settlement record' },
1894
- 'artifact-dir': { type: 'string', description: 'Task artifact directory where resource-impact.md should be updated' },
1895
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3100
+ id: { type: 'positional', required: true },
3101
+ json: { type: 'boolean', default: false },
1896
3102
  },
1897
- run({ args }) {
1898
- const report = settleResourceAssets({
1899
- projectDir: args.dir,
1900
- taskId: args['task-id'],
1901
- artifactsDir: args['artifact-dir'],
1902
- });
3103
+ async run({ args }) {
3104
+ const store = new EvidenceStore(SCALE_DIR);
3105
+ const record = store.getGateResult(args.id);
3106
+ if (!record) {
3107
+ console.error(`Evidence record not found: ${args.id}`);
3108
+ process.exit(1);
3109
+ }
1903
3110
  if (args.json) {
1904
- console.log(JSON.stringify(report, null, 2));
3111
+ console.log(JSON.stringify(record, null, 2));
3112
+ return;
1905
3113
  }
1906
- else {
1907
- console.log(`SCALE Asset Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
1908
- if (report.resourceImpactPath)
1909
- console.log(` Resource impact: ${report.resourceImpactPath}`);
1910
- if (report.doctor.findings.length > 0) {
1911
- for (const finding of report.doctor.findings) {
1912
- const path = finding.path ? ` ${finding.path}` : '';
1913
- console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
1914
- }
1915
- }
3114
+ console.log(`\nEvidence: ${record.id}`);
3115
+ console.log(`Gate: ${record.gate}`);
3116
+ console.log(`Status: ${record.status}`);
3117
+ console.log(`Passed: ${record.passed}`);
3118
+ console.log(`Created: ${new Date(record.createdAt).toISOString()}`);
3119
+ console.log(`Duration: ${record.durationMs}ms`);
3120
+ if (record.blockers.length > 0) {
3121
+ console.log('\nBlockers:');
3122
+ for (const blocker of record.blockers)
3123
+ console.log(` - ${blocker}`);
3124
+ }
3125
+ console.log('\nEvidence Items:');
3126
+ for (const item of record.evidenceItems) {
3127
+ const status = item.passed ? 'PASS' : 'FAIL';
3128
+ const target = item.command ?? item.path ?? '';
3129
+ console.log(` - [${status}] ${item.label}${target ? ` (${target})` : ''}`);
3130
+ console.log(` ${item.detail}`);
1916
3131
  }
1917
- if (!report.ok)
1918
- process.exitCode = 1;
1919
3132
  },
1920
3133
  });
1921
- const assets = defineCommand({
1922
- meta: { name: 'assets', description: 'Resource lifecycle governance for generated and maintained project assets' },
1923
- subCommands: { scan: assetsScan, doctor: assetsDoctor, settle: assetsSettle },
3134
+ const evidence = defineCommand({
3135
+ meta: { name: 'evidence', description: 'Persisted gate evidence inspection' },
3136
+ subCommands: { list: evidenceList, show: evidenceShow },
1924
3137
  });
1925
3138
  // ============================================================================
1926
- // standards command - Engineering standards governance
3139
+ // runtime command - session ledger + completion evidence
1927
3140
  // ============================================================================
1928
- function resolveChangedFilesArg(args) {
1929
- const explicit = splitChangedFiles(args['changed-files']);
1930
- if (explicit.length > 0)
1931
- return explicit;
1932
- if (!args.changed)
1933
- return undefined;
1934
- return readGitChangedFiles(args.dir ?? '.');
1935
- }
1936
- function splitChangedFiles(value) {
1937
- if (!value)
1938
- return [];
1939
- return value
1940
- .split(/[\n,]/)
1941
- .map(item => item.trim())
1942
- .filter(Boolean);
3141
+ function normalizeRuntimeEvidenceKind(value) {
3142
+ const normalized = String(value ?? 'command').trim();
3143
+ const allowed = ['command', 'gate', 'tool', 'skill', 'mcp', 'browser', 'desktop', 'manual', 'final-report'];
3144
+ if (allowed.includes(normalized))
3145
+ return normalized;
3146
+ throw new Error(`Invalid runtime evidence kind "${normalized}"; expected ${allowed.join(', ')}.`);
1943
3147
  }
1944
- function readGitChangedFiles(projectDir) {
1945
- const tracked = readGitPathList(projectDir, ['diff', '--name-only', '--diff-filter=ACMRTUXB', 'HEAD', '--']);
1946
- const untracked = readGitPathList(projectDir, ['ls-files', '--others', '--exclude-standard']);
1947
- return Array.from(new Set([...tracked, ...untracked]));
3148
+ function normalizeRuntimeEvidenceStatus(value) {
3149
+ const normalized = String(value ?? '').trim();
3150
+ if (normalized === 'passed' || normalized === 'failed' || normalized === 'skipped')
3151
+ return normalized;
3152
+ throw new Error(`Invalid runtime evidence status "${normalized}"; expected passed, failed, or skipped.`);
1948
3153
  }
1949
- function readGitPathList(projectDir, args) {
1950
- try {
1951
- return execFileSync('git', ['-C', projectDir, ...args], { encoding: 'utf-8' })
1952
- .split(/\r?\n/)
1953
- .map(item => item.trim())
1954
- .filter(Boolean);
1955
- }
1956
- catch {
1957
- return [];
1958
- }
3154
+ function normalizeRuntimeSessionStatus(value) {
3155
+ const normalized = String(value ?? 'completed').trim();
3156
+ if (normalized === 'active' || normalized === 'completed' || normalized === 'failed' || normalized === 'abandoned')
3157
+ return normalized;
3158
+ throw new Error(`Invalid runtime session status "${normalized}"; expected active, completed, failed, or abandoned.`);
1959
3159
  }
1960
- const standardsScan = defineCommand({
1961
- meta: { name: 'scan', description: 'Scan source files for engineering standard violations' },
3160
+ const runtimeStart = defineCommand({
3161
+ meta: { name: 'start', description: 'Start a runtime session ledger' },
1962
3162
  args: {
1963
- dir: { type: 'string', default: '.', description: 'Project directory' },
1964
- changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
1965
- 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
1966
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3163
+ 'session-id': { type: 'string', description: 'Session id; generated when omitted' },
3164
+ 'task-id': { type: 'string', description: 'Task id linked to this session' },
3165
+ agent: { type: 'string', description: 'Agent name' },
3166
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3167
+ summary: { type: 'string', description: 'Short session summary' },
3168
+ json: { type: 'boolean', default: false },
1967
3169
  },
1968
3170
  run({ args }) {
1969
- const report = scanEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
1970
- if (args.json) {
1971
- console.log(JSON.stringify(report, null, 2));
1972
- return;
1973
- }
1974
- console.log('SCALE Standards Scan');
1975
- console.log(` Project: ${report.projectDir}`);
1976
- console.log(` Files scanned: ${report.summary.filesScanned}`);
1977
- console.log(` Findings: ${report.summary.totalFindings}`);
1978
- console.log(` Blocking findings: ${report.summary.blockingFindings}`);
1979
- for (const finding of report.findings.slice(0, 20)) {
1980
- const line = finding.line ? `:${finding.line}` : '';
1981
- console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
3171
+ const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
3172
+ const session = ledger.start({
3173
+ sessionId: args['session-id'],
3174
+ taskId: args['task-id'],
3175
+ agent: args.agent,
3176
+ level: normalizeTaskArtifactLevel(args.level),
3177
+ summary: args.summary,
3178
+ });
3179
+ if (args.json) {
3180
+ console.log(JSON.stringify(session, null, 2));
3181
+ return;
1982
3182
  }
1983
- if (report.findings.length > 20)
1984
- console.log(` ... ${report.findings.length - 20} more finding(s)`);
3183
+ console.log(`Runtime session started: ${session.sessionId}`);
3184
+ if (session.taskId)
3185
+ console.log(` Task: ${session.taskId}`);
3186
+ if (session.level)
3187
+ console.log(` Level: ${session.level}`);
3188
+ console.log(` Events: ${ledger.sessionFile(session.sessionId)}`);
1985
3189
  },
1986
3190
  });
1987
- const standardsDoctor = defineCommand({
1988
- meta: { name: 'doctor', description: 'Find blocking engineering standards problems' },
3191
+ const runtimeEnd = defineCommand({
3192
+ meta: { name: 'end', description: 'End the current or named runtime session' },
1989
3193
  args: {
1990
- dir: { type: 'string', default: '.', description: 'Project directory' },
1991
- changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
1992
- 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
1993
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3194
+ 'session-id': { type: 'string', description: 'Session id; current session is used when omitted' },
3195
+ status: { type: 'string', default: 'completed', description: 'completed, failed, or abandoned' },
3196
+ summary: { type: 'string', description: 'Completion summary' },
3197
+ json: { type: 'boolean', default: false },
1994
3198
  },
1995
3199
  run({ args }) {
1996
- const report = doctorEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
3200
+ const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
3201
+ const sessionId = args['session-id'] ?? ledger.current()?.sessionId;
3202
+ if (!sessionId) {
3203
+ console.error('No runtime session id provided and no current runtime session exists.');
3204
+ process.exit(1);
3205
+ }
3206
+ const session = ledger.end(sessionId, normalizeRuntimeSessionStatus(args.status), args.summary);
1997
3207
  if (args.json) {
1998
- console.log(JSON.stringify(report, null, 2));
1999
- if (!report.ok)
2000
- process.exitCode = 1;
3208
+ console.log(JSON.stringify(session, null, 2));
2001
3209
  return;
2002
3210
  }
2003
- console.log(`SCALE Standards Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2004
- if (report.findings.length === 0) {
2005
- console.log(' No engineering standards findings.');
2006
- return;
3211
+ console.log(`Runtime session ended: ${session.sessionId}`);
3212
+ console.log(` Status: ${session.status}`);
3213
+ },
3214
+ });
3215
+ const runtimeRecord = defineCommand({
3216
+ meta: { name: 'record', description: 'Record command, gate, tool, browser, skill, or manual runtime evidence' },
3217
+ args: {
3218
+ 'task-id': { type: 'string', description: 'Task id linked to this evidence' },
3219
+ 'session-id': { type: 'string', description: 'Session id linked to this evidence' },
3220
+ kind: { type: 'string', default: 'command', description: 'command, gate, tool, skill, mcp, browser, desktop, manual, final-report' },
3221
+ title: { type: 'string', required: true, description: 'Evidence title' },
3222
+ status: { type: 'string', required: true, description: 'passed, failed, or skipped' },
3223
+ command: { type: 'string', description: 'Exact command or tool invocation, with secrets redacted by SCALE' },
3224
+ 'exit-code': { type: 'string', description: 'Exit code when applicable' },
3225
+ summary: { type: 'string', required: true, description: 'Short output summary' },
3226
+ artifacts: { type: 'string', description: 'Comma-separated artifact paths' },
3227
+ 'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
3228
+ json: { type: 'boolean', default: false },
3229
+ },
3230
+ run({ args }) {
3231
+ const current = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).current();
3232
+ let metadata = {};
3233
+ try {
3234
+ metadata = JSON.parse(String(args['metadata-json'] ?? '{}'));
2007
3235
  }
2008
- for (const finding of report.findings) {
2009
- const line = finding.line ? `:${finding.line}` : '';
2010
- console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
2011
- if (finding.fix)
2012
- console.log(` fix: ${finding.fix}`);
3236
+ catch {
3237
+ console.error('--metadata-json must be valid JSON.');
3238
+ process.exit(1);
2013
3239
  }
2014
- if (!report.ok)
2015
- process.exitCode = 1;
3240
+ const exitCode = args['exit-code'] === undefined || args['exit-code'] === ''
3241
+ ? undefined
3242
+ : Number.parseInt(String(args['exit-code']), 10);
3243
+ if (exitCode !== undefined && Number.isNaN(exitCode)) {
3244
+ console.error('--exit-code must be a number.');
3245
+ process.exit(1);
3246
+ }
3247
+ const ledger = new RuntimeEvidenceLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
3248
+ const record = ledger.record({
3249
+ taskId: args['task-id'] ?? current?.taskId,
3250
+ sessionId: args['session-id'] ?? current?.sessionId,
3251
+ kind: normalizeRuntimeEvidenceKind(args.kind),
3252
+ title: args.title,
3253
+ status: normalizeRuntimeEvidenceStatus(args.status),
3254
+ command: args.command,
3255
+ exitCode,
3256
+ summary: args.summary,
3257
+ artifacts: parseCommaList(args.artifacts),
3258
+ metadata,
3259
+ });
3260
+ if (record.sessionId) {
3261
+ new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).append(record.sessionId, {
3262
+ type: 'evidence.recorded',
3263
+ message: `${record.status}: ${record.title}`,
3264
+ data: {
3265
+ evidenceId: record.id,
3266
+ kind: record.kind,
3267
+ taskId: record.taskId,
3268
+ },
3269
+ });
3270
+ }
3271
+ if (args.json) {
3272
+ console.log(JSON.stringify(record, null, 2));
3273
+ return;
3274
+ }
3275
+ console.log(`Runtime evidence recorded: ${record.id}`);
3276
+ console.log(` Status: ${record.status}`);
3277
+ console.log(` Kind: ${record.kind}`);
3278
+ if (record.redactionApplied)
3279
+ console.log(' Redaction: applied');
2016
3280
  },
2017
3281
  });
2018
- const standardsSettle = defineCommand({
2019
- meta: { name: 'settle', description: 'Record engineering standards settlement evidence for a task' },
3282
+ const runtimeDoctor = defineCommand({
3283
+ meta: { name: 'doctor', description: 'Check runtime session and completion evidence' },
2020
3284
  args: {
2021
- dir: { type: 'string', default: '.', description: 'Project directory' },
2022
- 'task-id': { type: 'string', description: 'Task id for the settlement record' },
2023
- 'artifact-dir': { type: 'string', description: 'Task artifact directory where standards-impact.md should be updated' },
2024
- changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
2025
- 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
2026
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3285
+ 'task-id': { type: 'string', description: 'Task id to inspect' },
3286
+ 'session-id': { type: 'string', description: 'Session id to inspect' },
3287
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3288
+ json: { type: 'boolean', default: false },
2027
3289
  },
2028
3290
  run({ args }) {
2029
- const report = settleEngineeringStandards({
2030
- projectDir: args.dir,
3291
+ const report = doctorRuntimeEvidence({
3292
+ projectDir: PROJECT_DIR,
3293
+ scaleDir: SCALE_DIR,
2031
3294
  taskId: args['task-id'],
2032
- artifactsDir: args['artifact-dir'],
2033
- changedFiles: resolveChangedFilesArg(args),
3295
+ sessionId: args['session-id'],
3296
+ level: normalizeTaskArtifactLevel(args.level),
2034
3297
  });
2035
3298
  if (args.json) {
2036
3299
  console.log(JSON.stringify(report, null, 2));
3300
+ if (report.blocked)
3301
+ process.exitCode = 1;
3302
+ return;
2037
3303
  }
2038
- else {
2039
- console.log(`SCALE Standards Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2040
- if (report.standardsImpactPath)
2041
- console.log(` Standards impact: ${report.standardsImpactPath}`);
2042
- for (const finding of report.doctor.findings) {
2043
- const line = finding.line ? `:${finding.line}` : '';
2044
- console.log(` [${finding.severity.toUpperCase()}] ${finding.ruleId} ${finding.path}${line}: ${finding.message}`);
2045
- }
3304
+ console.log('\nSCALE Runtime Doctor');
3305
+ console.log(` Evidence: ${report.evidence.total} total, ${report.evidence.passed} passed, ${report.evidence.failed} failed, ${report.evidence.skipped} skipped`);
3306
+ for (const check of report.checks) {
3307
+ console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
3308
+ if (check.fix)
3309
+ console.log(` Fix: ${check.fix}`);
2046
3310
  }
2047
- if (!report.ok)
3311
+ if (report.blocked)
2048
3312
  process.exitCode = 1;
2049
3313
  },
2050
3314
  });
2051
- const standardsBaseline = defineCommand({
2052
- meta: { name: 'baseline', description: 'Generate a legacy standards baseline and classification report' },
3315
+ const runtimeFinalCheck = defineCommand({
3316
+ meta: { name: 'final-check', description: 'Block final delivery claims without passed runtime evidence' },
2053
3317
  args: {
2054
- dir: { type: 'string', default: '.', description: 'Project directory' },
2055
- write: { type: 'boolean', default: false, description: 'Write .scale/engineering-standards-baseline.json' },
2056
- 'task-id': { type: 'string', description: 'Task id for the legacy debt report' },
2057
- 'artifact-dir': { type: 'string', description: 'Directory where standards-legacy-debt.md should be written' },
2058
- reason: { type: 'string', default: 'legacy standards debt accepted for staged remediation', description: 'Reason recorded on generated baseline entries' },
2059
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3318
+ 'task-id': { type: 'string', description: 'Task id to inspect' },
3319
+ 'session-id': { type: 'string', description: 'Session id to inspect' },
3320
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3321
+ json: { type: 'boolean', default: false },
2060
3322
  },
2061
3323
  run({ args }) {
2062
- const report = baselineEngineeringStandards({
2063
- projectDir: args.dir,
2064
- writeBaseline: args.write,
3324
+ const readiness = evaluateFinalReportReadiness({
3325
+ projectDir: PROJECT_DIR,
3326
+ scaleDir: SCALE_DIR,
2065
3327
  taskId: args['task-id'],
2066
- artifactsDir: args['artifact-dir'],
2067
- reason: args.reason,
3328
+ sessionId: args['session-id'],
3329
+ level: normalizeTaskArtifactLevel(args.level),
2068
3330
  });
2069
3331
  if (args.json) {
2070
- console.log(JSON.stringify(report, null, 2));
3332
+ console.log(JSON.stringify(readiness, null, 2));
3333
+ if (readiness.blocked)
3334
+ process.exitCode = 1;
2071
3335
  return;
2072
3336
  }
2073
- console.log(`Standards baseline: ${report.wroteBaseline ? 'written' : 'dry-run'}`);
2074
- console.log(` Baseline entries: ${report.baselineEntries.length}`);
2075
- console.log(` Blocking findings: ${report.debt.blockingFindings}`);
2076
- console.log(` Baseline path: ${report.baselinePath}`);
2077
- if (report.legacyDebtPath)
2078
- console.log(` Legacy debt report: ${report.legacyDebtPath}`);
2079
- if (!report.wroteBaseline)
2080
- console.log(' Re-run with --write to update .scale/engineering-standards-baseline.json.');
3337
+ console.log('\nSCALE Runtime Final Check');
3338
+ console.log(` Ready: ${readiness.ready}`);
3339
+ for (const reason of readiness.reasons)
3340
+ console.log(` [BLOCKER] ${reason}`);
3341
+ if (readiness.blocked)
3342
+ process.exitCode = 1;
2081
3343
  },
2082
3344
  });
2083
- const standards = defineCommand({
2084
- meta: { name: 'standards', description: 'Engineering standards governance for logs, security, architecture, database, and code quality' },
2085
- subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
3345
+ const runtime = defineCommand({
3346
+ meta: { name: 'runtime', description: 'Runtime session ledger and completion evidence governance' },
3347
+ subCommands: {
3348
+ start: runtimeStart,
3349
+ end: runtimeEnd,
3350
+ record: runtimeRecord,
3351
+ doctor: runtimeDoctor,
3352
+ 'final-check': runtimeFinalCheck,
3353
+ },
2086
3354
  });
2087
3355
  // ============================================================================
2088
- // artifact command - Derived HTML artifacts for human review
3356
+ // memory command - runtime evidence + knowledge + graph context packs
2089
3357
  // ============================================================================
2090
- const artifactRender = defineCommand({
2091
- meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
3358
+ function parseMemoryBudget(value) {
3359
+ if (value === undefined || value === null || value === '')
3360
+ return undefined;
3361
+ const parsed = Number.parseInt(String(value), 10);
3362
+ if (Number.isNaN(parsed) || parsed <= 0) {
3363
+ throw new Error('--budget must be a positive integer.');
3364
+ }
3365
+ return parsed;
3366
+ }
3367
+ function normalizeMemorySource(value) {
3368
+ const normalized = String(value ?? 'evidence').trim().toLowerCase();
3369
+ if (normalized === 'evidence' || normalized === 'candidate' || normalized === 'failure')
3370
+ return normalized;
3371
+ throw new Error('--from must be evidence, candidate, or failure.');
3372
+ }
3373
+ function normalizeMemoryNodeType(value) {
3374
+ if (value === undefined || value === null || value === '')
3375
+ return undefined;
3376
+ const normalized = String(value).trim().toLowerCase();
3377
+ if (normalized === 'fact' || normalized === 'decision' || normalized === 'incident' || normalized === 'relation' || normalized === 'contradiction')
3378
+ return normalized;
3379
+ throw new Error('--type must be fact, decision, incident, relation, or contradiction.');
3380
+ }
3381
+ function normalizeMemoryScope(value) {
3382
+ if (value === undefined || value === null || value === '')
3383
+ return undefined;
3384
+ const normalized = String(value).trim().toLowerCase();
3385
+ if (normalized === 'project' || normalized === 'workspace' || normalized === 'global-candidate')
3386
+ return normalized;
3387
+ throw new Error('--scope must be project, workspace, or global-candidate.');
3388
+ }
3389
+ function memoryBrain() {
3390
+ return new MemoryBrain({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
3391
+ }
3392
+ const memoryPack = defineCommand({
3393
+ meta: { name: 'pack', description: 'Build a compact context pack from runtime evidence, session events, knowledge, and graph status' },
2092
3394
  args: {
2093
- dir: { type: 'string', default: '.', description: 'Project directory' },
2094
- 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2095
- 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2096
- type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
2097
- source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
2098
- theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
2099
- lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
2100
- title: { type: 'string', description: 'HTML document title override' },
2101
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3395
+ task: { type: 'string', required: true, description: 'Current task or question' },
3396
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
3397
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
3398
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3399
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
3400
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
3401
+ json: { type: 'boolean', default: false },
2102
3402
  },
2103
- run({ args }) {
2104
- const result = renderHtmlArtifact({
2105
- projectDir: args.dir,
3403
+ async run({ args }) {
3404
+ let budgetTokens;
3405
+ try {
3406
+ budgetTokens = parseMemoryBudget(args.budget);
3407
+ }
3408
+ catch (e) {
3409
+ console.error(e.message);
3410
+ process.exit(1);
3411
+ }
3412
+ const { kb } = getEngine();
3413
+ const pack = await new MemoryFabric({
3414
+ projectDir: PROJECT_DIR,
3415
+ scaleDir: SCALE_DIR,
3416
+ knowledgeBase: kb,
3417
+ }).createContextPack({
3418
+ task: args.task,
2106
3419
  taskId: args['task-id'],
2107
- artifactDir: args['artifact-dir'],
2108
- type: String(args.type ?? 'release-report'),
2109
- sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
2110
- theme: normalizeThemeArg(args.theme),
2111
- lang: normalizeLangArg(args.lang),
2112
- title: typeof args.title === 'string' ? args.title : undefined,
3420
+ sessionId: args['session-id'],
3421
+ level: normalizeTaskArtifactLevel(args.level),
3422
+ files: parseCommaList(args.files),
3423
+ budgetTokens,
2113
3424
  });
2114
3425
  if (args.json) {
2115
- console.log(JSON.stringify(result, null, 2));
3426
+ console.log(JSON.stringify(pack, null, 2));
2116
3427
  return;
2117
3428
  }
2118
- console.log('SCALE HTML Artifact Render');
2119
- console.log(` Type: ${result.type}`);
2120
- console.log(` HTML: ${result.outputPath}`);
2121
- console.log(` Index: ${result.indexPath}`);
2122
- console.log(` Manifest: ${result.manifestPath}`);
2123
- if (result.missingSources.length > 0) {
2124
- console.log(` Missing sources: ${result.missingSources.join(', ')}`);
2125
- }
3429
+ console.log(renderContextPackMarkdown(pack));
2126
3430
  },
2127
3431
  });
2128
- const artifactDoctor = defineCommand({
2129
- meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
3432
+ const memoryDoctor = defineCommand({
3433
+ meta: { name: 'doctor', description: 'Check whether a task context pack is available and within token budget' },
2130
3434
  args: {
2131
- dir: { type: 'string', default: '.', description: 'Project directory' },
2132
- 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2133
- 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2134
- type: { type: 'string', description: 'Optional HTML artifact type to check' },
2135
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3435
+ task: { type: 'string', required: true, description: 'Current task or question' },
3436
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
3437
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
3438
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3439
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
3440
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
3441
+ json: { type: 'boolean', default: false },
2136
3442
  },
2137
- run({ args }) {
2138
- const report = doctorHtmlArtifacts({
2139
- projectDir: args.dir,
3443
+ async run({ args }) {
3444
+ let budgetTokens;
3445
+ try {
3446
+ budgetTokens = parseMemoryBudget(args.budget);
3447
+ }
3448
+ catch (e) {
3449
+ console.error(e.message);
3450
+ process.exit(1);
3451
+ }
3452
+ const { kb } = getEngine();
3453
+ const report = await doctorMemoryFabric({
3454
+ projectDir: PROJECT_DIR,
3455
+ scaleDir: SCALE_DIR,
3456
+ knowledgeBase: kb,
3457
+ }, {
3458
+ task: args.task,
2140
3459
  taskId: args['task-id'],
2141
- artifactDir: args['artifact-dir'],
2142
- type: typeof args.type === 'string' ? args.type : undefined,
3460
+ sessionId: args['session-id'],
3461
+ level: normalizeTaskArtifactLevel(args.level),
3462
+ files: parseCommaList(args.files),
3463
+ budgetTokens,
2143
3464
  });
2144
3465
  if (args.json) {
2145
3466
  console.log(JSON.stringify(report, null, 2));
@@ -2147,283 +3468,288 @@ const artifactDoctor = defineCommand({
2147
3468
  process.exitCode = 1;
2148
3469
  return;
2149
3470
  }
2150
- console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2151
- console.log(` Manifest: ${report.manifestPath}`);
2152
- console.log(` Artifacts: ${report.artifacts.length}`);
2153
- if (report.findings.length === 0) {
2154
- console.log(' No HTML artifact findings.');
2155
- }
2156
- else {
2157
- for (const finding of report.findings) {
2158
- const path = finding.path ? ` ${finding.path}` : '';
2159
- console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2160
- if (finding.fix)
2161
- console.log(` fix: ${finding.fix}`);
2162
- }
3471
+ console.log('\nSCALE Memory Doctor');
3472
+ console.log(` Budget: ${report.pack.budget.used}/${report.pack.budget.limit} estimated tokens`);
3473
+ for (const check of report.checks) {
3474
+ console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
2163
3475
  }
2164
3476
  if (!report.ok)
2165
3477
  process.exitCode = 1;
2166
3478
  },
2167
3479
  });
2168
- const artifactSettle = defineCommand({
2169
- meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
3480
+ const memorySettle = defineCommand({
3481
+ meta: { name: 'settle', description: 'Settle runtime evidence into a reviewable memory learning candidate' },
2170
3482
  args: {
2171
- dir: { type: 'string', default: '.', description: 'Project directory' },
2172
- 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2173
- 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2174
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3483
+ task: { type: 'string', required: true, description: 'Current task or question' },
3484
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
3485
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
3486
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
3487
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
3488
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
3489
+ json: { type: 'boolean', default: false },
2175
3490
  },
2176
- run({ args }) {
2177
- const report = settleHtmlArtifacts({
2178
- projectDir: args.dir,
3491
+ async run({ args }) {
3492
+ let budgetTokens;
3493
+ try {
3494
+ budgetTokens = parseMemoryBudget(args.budget);
3495
+ }
3496
+ catch (e) {
3497
+ console.error(e.message);
3498
+ process.exit(1);
3499
+ }
3500
+ const { kb } = getEngine();
3501
+ const pack = await new MemoryFabric({
3502
+ projectDir: PROJECT_DIR,
3503
+ scaleDir: SCALE_DIR,
3504
+ knowledgeBase: kb,
3505
+ }).createContextPack({
3506
+ task: args.task,
2179
3507
  taskId: args['task-id'],
2180
- artifactDir: args['artifact-dir'],
3508
+ sessionId: args['session-id'],
3509
+ level: normalizeTaskArtifactLevel(args.level),
3510
+ files: parseCommaList(args.files),
3511
+ budgetTokens,
3512
+ });
3513
+ const settlement = settleMemoryLearning({
3514
+ projectDir: PROJECT_DIR,
3515
+ scaleDir: SCALE_DIR,
3516
+ pack,
2181
3517
  });
2182
3518
  if (args.json) {
2183
- console.log(JSON.stringify(report, null, 2));
2184
- if (!report.ok)
2185
- process.exitCode = 1;
3519
+ console.log(JSON.stringify(settlement, null, 2));
2186
3520
  return;
2187
3521
  }
2188
- console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2189
- console.log(` HTML impact: ${report.htmlImpactPath}`);
2190
- for (const finding of report.doctor.findings) {
2191
- const path = finding.path ? ` ${finding.path}` : '';
2192
- console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2193
- }
2194
- if (!report.ok)
2195
- process.exitCode = 1;
3522
+ console.log(renderMemoryLearningCandidateMarkdown(settlement.candidate));
3523
+ console.log(`\nWrote: ${settlement.files.markdown}`);
2196
3524
  },
2197
3525
  });
2198
- const artifactOpen = defineCommand({
2199
- meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
3526
+ const memoryIngest = defineCommand({
3527
+ meta: { name: 'ingest', description: 'Ingest runtime evidence, learning candidates, or failure replays into the project memory brain' },
2200
3528
  args: {
2201
- dir: { type: 'string', default: '.', description: 'Project directory' },
2202
- 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2203
- 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2204
- type: { type: 'string', description: 'Optional HTML artifact type to open' },
2205
- 'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
2206
- json: { type: 'boolean', default: false, description: 'Print JSON output' },
3529
+ from: { type: 'string', default: 'evidence', description: 'Source: evidence, candidate, or failure' },
3530
+ 'task-id': { type: 'string', description: 'Task id to scope runtime evidence' },
3531
+ 'session-id': { type: 'string', description: 'Session id to scope runtime evidence' },
3532
+ 'candidate-id': { type: 'string', description: 'Memory learning candidate id' },
3533
+ 'failure-id': { type: 'string', description: 'Workflow eval failure replay id' },
3534
+ type: { type: 'string', description: 'Memory type override: fact/decision/incident/relation/contradiction' },
3535
+ scope: { type: 'string', description: 'Memory scope: project/workspace/global-candidate' },
3536
+ json: { type: 'boolean', default: false },
2207
3537
  },
2208
3538
  run({ args }) {
2209
- const path = resolveHtmlArtifactForOpen({
2210
- projectDir: args.dir,
3539
+ let from;
3540
+ let type;
3541
+ let scope;
3542
+ try {
3543
+ from = normalizeMemorySource(args.from);
3544
+ type = normalizeMemoryNodeType(args.type);
3545
+ scope = normalizeMemoryScope(args.scope);
3546
+ }
3547
+ catch (error) {
3548
+ console.error(error.message);
3549
+ process.exit(1);
3550
+ return;
3551
+ }
3552
+ const report = memoryBrain().ingest({
3553
+ from,
2211
3554
  taskId: args['task-id'],
2212
- artifactDir: args['artifact-dir'],
2213
- type: typeof args.type === 'string' ? args.type : undefined,
3555
+ sessionId: args['session-id'],
3556
+ candidateId: args['candidate-id'],
3557
+ failureId: args['failure-id'],
3558
+ type,
3559
+ scope,
2214
3560
  });
2215
- const url = pathToFileURL(path).toString();
2216
- const exists = existsSync(path);
2217
- if (!args['print-only'] && exists)
2218
- launchLocalFile(path);
2219
- const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
2220
3561
  if (args.json) {
2221
- console.log(JSON.stringify(output, null, 2));
2222
- if (!exists)
3562
+ console.log(JSON.stringify(report, null, 2));
3563
+ if (!report.ok)
2223
3564
  process.exitCode = 1;
2224
3565
  return;
2225
3566
  }
2226
- if (!exists) {
2227
- console.log(`HTML artifact not found: ${path}`);
3567
+ console.log('\nSCALE Memory Ingest');
3568
+ console.log(` Source: ${report.source}`);
3569
+ console.log(` Created: ${report.created}`);
3570
+ console.log(` Skipped: ${report.skipped}`);
3571
+ for (const node of report.nodes)
3572
+ console.log(` [${node.status}] ${node.id}: ${node.title}`);
3573
+ for (const warning of report.warnings)
3574
+ console.log(` [WARN] ${warning}`);
3575
+ if (!report.ok)
2228
3576
  process.exitCode = 1;
2229
- return;
2230
- }
2231
- console.log(url);
2232
3577
  },
2233
3578
  });
2234
- const artifact = defineCommand({
2235
- meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
2236
- subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
2237
- });
2238
- function normalizeThemeArg(value) {
2239
- const normalized = String(value ?? 'auto').trim().toLowerCase();
2240
- if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
2241
- return normalized;
2242
- return 'auto';
2243
- }
2244
- function normalizeLangArg(value) {
2245
- return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
2246
- }
2247
- function launchLocalFile(path) {
2248
- try {
2249
- if (process.platform === 'win32') {
2250
- execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
2251
- }
2252
- else if (process.platform === 'darwin') {
2253
- execFileSync('open', [path], { stdio: 'ignore' });
3579
+ const memoryQuery = defineCommand({
3580
+ meta: { name: 'query', description: 'Query concise project-scoped long-term memory with evidence references' },
3581
+ args: {
3582
+ query: { type: 'positional', required: true, description: 'Search query' },
3583
+ limit: { type: 'string', default: '8', description: 'Maximum number of memory nodes' },
3584
+ status: { type: 'string', description: 'Filter by status: candidate/active/stale/rejected' },
3585
+ json: { type: 'boolean', default: false },
3586
+ },
3587
+ run({ args }) {
3588
+ const limit = Number.parseInt(String(args.limit ?? '8'), 10);
3589
+ const status = args.status ? String(args.status) : undefined;
3590
+ const report = memoryBrain().query(String(args.query), {
3591
+ limit: Number.isFinite(limit) && limit > 0 ? limit : 8,
3592
+ status,
3593
+ });
3594
+ if (args.json) {
3595
+ console.log(JSON.stringify(report, null, 2));
3596
+ return;
2254
3597
  }
2255
- else {
2256
- execFileSync('xdg-open', [path], { stdio: 'ignore' });
3598
+ console.log('\nSCALE Memory Query');
3599
+ console.log(` Query: ${report.query}`);
3600
+ console.log(` Results: ${report.count}`);
3601
+ for (const node of report.nodes) {
3602
+ console.log(` [${node.status}/${node.type}] ${node.id}: ${node.title}`);
3603
+ console.log(` confidence: ${node.confidence}; evidence: ${node.evidencePaths.join(', ') || 'none'}`);
3604
+ console.log(` ${node.summary}`);
2257
3605
  }
2258
- }
2259
- catch {
2260
- // Opening is convenience-only; artifact doctor/render remains the source of truth.
2261
- }
2262
- }
2263
- // ============================================================================
2264
- // evolve command
2265
- // ============================================================================
2266
- const evolve = defineCommand({
2267
- meta: { name: 'evolve', description: 'Run evolution cycle (Defect→Lesson→Rule→Hook)' },
2268
- args: {},
2269
- async run() {
2270
- const { store, kb, eventBus } = getEngine();
2271
- const extractor = new LessonExtractor(store, kb, eventBus);
2272
- const proposer = new RuleProposer(kb, eventBus);
2273
- const generator = new HookGenerator(eventBus);
2274
- const engine = new EvolutionEngine(extractor, proposer, generator, eventBus, SCALE_DIR);
2275
- const stats = await engine.runCycle();
2276
- console.log(JSON.stringify(stats, null, 2));
2277
3606
  },
2278
3607
  });
2279
- // ============================================================================
2280
- // doctor command
2281
- // ============================================================================
2282
- const doctor = defineCommand({
2283
- meta: { name: 'doctor', description: 'Diagnose SCALE Engine health' },
3608
+ const memoryContradictions = defineCommand({
3609
+ meta: { name: 'contradictions', description: 'Report conflicting project memory instead of silently resolving it' },
2284
3610
  args: {
2285
- dir: { type: 'string', default: '.', description: 'Project directory' },
2286
- json: { type: 'boolean', default: false, description: 'Output as JSON' },
3611
+ json: { type: 'boolean', default: false },
2287
3612
  },
2288
- async run({ args }) {
2289
- const doc = new Doctor(args.dir);
2290
- const report = await doc.diagnose();
3613
+ run({ args }) {
3614
+ const report = memoryBrain().contradictions();
2291
3615
  if (args.json) {
2292
3616
  console.log(JSON.stringify(report, null, 2));
3617
+ if (!report.ok)
3618
+ process.exitCode = 1;
3619
+ return;
2293
3620
  }
2294
- else {
2295
- console.log(doc.formatReport(report));
3621
+ console.log('\nSCALE Memory Contradictions');
3622
+ console.log(` Count: ${report.count}`);
3623
+ for (const item of report.contradictions) {
3624
+ console.log(` [CONFLICT] ${item.title}`);
3625
+ console.log(` nodes: ${item.nodeIds.join(', ')}`);
3626
+ console.log(` evidence: ${item.evidencePaths.join(', ') || 'none'}`);
2296
3627
  }
2297
- process.exitCode = report.overall === 'broken' ? 1 : 0;
3628
+ if (!report.ok)
3629
+ process.exitCode = 1;
2298
3630
  },
2299
3631
  });
2300
- // ============================================================================
2301
- // workflow command 列出/查看工作流预设
2302
- // ============================================================================
2303
- const workflowList = defineCommand({
2304
- meta: { name: 'list', description: 'List all workflow presets' },
3632
+ const memoryDream = defineCommand({
3633
+ meta: { name: 'dream', description: 'Run memory maintenance: duplicates, stale memories, contradictions, and promotion candidates' },
2305
3634
  args: {
2306
- scenario: { type: 'string', description: 'Filter by scenario mode (sandbox/standard/critical)' },
2307
- json: { type: 'boolean', default: false, description: 'Output workflow presets as JSON' },
3635
+ json: { type: 'boolean', default: false },
2308
3636
  },
2309
- async run({ args }) {
2310
- const presets = args.scenario
2311
- ? getPresetsByScenario(args.scenario)
2312
- : listWorkflowPresets();
3637
+ run({ args }) {
3638
+ const report = memoryBrain().dream();
2313
3639
  if (args.json) {
2314
- console.log(JSON.stringify({
2315
- ok: true,
2316
- scenario: args.scenario ?? null,
2317
- count: presets.length,
2318
- presets: presets.map(preset => ({
2319
- id: preset.id,
2320
- name: preset.name,
2321
- nameZh: preset.nameZh,
2322
- description: preset.description,
2323
- scenarioMode: preset.scenarioMode,
2324
- requiredArtifacts: preset.requiredArtifacts,
2325
- steps: preset.steps,
2326
- })),
2327
- }, null, 2));
2328
- return;
2329
- }
2330
- if (presets.length === 0) {
2331
- console.log('No workflow presets found.');
3640
+ console.log(JSON.stringify(report, null, 2));
3641
+ if (!report.ok)
3642
+ process.exitCode = 1;
2332
3643
  return;
2333
3644
  }
2334
- console.log('\n📋 SCALE Engine Workflow Presets');
2335
- console.log('═══════════════════════════════════════════════════════');
2336
- for (const preset of presets) {
2337
- const modeEmoji = { sandbox: '🏖️', standard: '⚙️', critical: '🔒' }[preset.scenarioMode];
2338
- const mandatorySteps = preset.steps.filter((s) => s.isMandatory).length;
2339
- const totalSteps = preset.steps.length;
2340
- console.log(`\n ${preset.nameZh} (${preset.id})`);
2341
- console.log(` ${preset.description}`);
2342
- console.log(` Mode: ${modeEmoji} ${preset.scenarioMode} · Steps: ${mandatorySteps}/${totalSteps} mandatory`);
2343
- if (preset.requiredArtifacts.length > 0) {
2344
- console.log(` Requires: ${preset.requiredArtifacts.map((a) => `${a.type}${a.status ? `(${a.status})` : ''}`).join(', ')}`);
2345
- }
2346
- // Show step summary
2347
- for (const step of preset.steps) {
2348
- const marker = step.isMandatory ? '●' : '○';
2349
- const gate = step.verificationGate ? ` ⊓ ${step.verificationGate}` : '';
2350
- console.log(` ${marker} ${step.stepId}: ${step.action}${gate}`);
2351
- }
2352
- }
2353
- console.log('\n═══════════════════════════════════════════════════════');
2354
- console.log('\nUsage: scale workflow show <preset-id>');
3645
+ console.log('\nSCALE Memory Dream');
3646
+ console.log(` Total: ${report.summary.total}`);
3647
+ console.log(` Active: ${report.summary.active}`);
3648
+ console.log(` Candidates: ${report.summary.candidate}`);
3649
+ console.log(` Contradictions: ${report.summary.contradictions}`);
3650
+ console.log(` Duplicate groups: ${report.summary.duplicateGroups}`);
3651
+ for (const item of report.promotionCandidates)
3652
+ console.log(` [PROMOTE?] ${item.id}: ${item.title}`);
3653
+ for (const item of report.staleCandidates)
3654
+ console.log(` [STALE] ${item.id}: ${item.reason}`);
3655
+ if (!report.ok)
3656
+ process.exitCode = 1;
2355
3657
  },
2356
3658
  });
2357
- const workflow = defineCommand({
2358
- meta: { name: 'workflow', description: 'Workflow preset management' },
2359
- subCommands: { list: workflowList },
2360
- });
2361
- const evidenceList = defineCommand({
2362
- meta: { name: 'list', description: 'List persisted gate evidence records' },
3659
+ const memoryPromote = defineCommand({
3660
+ meta: { name: 'promote', description: 'Promote a memory candidate to active project memory after evidence review' },
2363
3661
  args: {
2364
- limit: { type: 'string', default: '20', description: 'Maximum number of records' },
3662
+ id: { type: 'positional', required: true, description: 'Memory node id or learning candidate id' },
3663
+ scope: { type: 'string', description: 'Scope override: project/workspace/global-candidate' },
2365
3664
  json: { type: 'boolean', default: false },
2366
3665
  },
2367
- async run({ args }) {
2368
- const store = new EvidenceStore(SCALE_DIR);
2369
- const records = store.listGateResults(parseInt(args.limit, 10) || 20);
2370
- if (args.json) {
2371
- console.log(JSON.stringify(records, null, 2));
2372
- return;
3666
+ run({ args }) {
3667
+ let scope;
3668
+ try {
3669
+ scope = normalizeMemoryScope(args.scope);
2373
3670
  }
2374
- if (records.length === 0) {
2375
- console.log('No evidence records found.');
3671
+ catch (error) {
3672
+ console.error(error.message);
3673
+ process.exit(1);
2376
3674
  return;
2377
3675
  }
2378
- console.log('\nSCALE Evidence Records');
2379
- for (const record of records) {
2380
- const status = record.passed ? 'PASS' : record.status;
2381
- const blockers = record.blockers.length > 0 ? ` blockers=${record.blockers.length}` : '';
2382
- console.log(` ${record.id} ${record.gate} ${status} ${new Date(record.createdAt).toISOString()}${blockers}`);
3676
+ const report = memoryBrain().promote(String(args.id), { scope });
3677
+ if (args.json) {
3678
+ console.log(JSON.stringify(report, null, 2));
3679
+ if (!report.ok)
3680
+ process.exitCode = 1;
3681
+ return;
2383
3682
  }
2384
- console.log('\nUsage: scale evidence show <id>');
3683
+ console.log('\nSCALE Memory Promote');
3684
+ console.log(` Status: ${report.ok ? 'promoted' : 'blocked'}`);
3685
+ if (report.node)
3686
+ console.log(` Node: ${report.node.id} (${report.node.status})`);
3687
+ for (const warning of report.warnings)
3688
+ console.log(` [WARN] ${warning}`);
3689
+ if (!report.ok)
3690
+ process.exitCode = 1;
2385
3691
  },
2386
3692
  });
2387
- const evidenceShow = defineCommand({
2388
- meta: { name: 'show', description: 'Show a persisted gate evidence record' },
3693
+ const memoryExport = defineCommand({
3694
+ meta: { name: 'export', description: 'Export project memory as JSONL' },
2389
3695
  args: {
2390
- id: { type: 'positional', required: true },
3696
+ output: { type: 'string', alias: 'o', description: 'Output JSONL file; stdout when omitted' },
2391
3697
  json: { type: 'boolean', default: false },
2392
3698
  },
2393
- async run({ args }) {
2394
- const store = new EvidenceStore(SCALE_DIR);
2395
- const record = store.getGateResult(args.id);
2396
- if (!record) {
2397
- console.error(`Evidence record not found: ${args.id}`);
2398
- process.exit(1);
3699
+ run({ args }) {
3700
+ const jsonl = memoryBrain().exportJsonl();
3701
+ if (args.output) {
3702
+ const outputPath = resolve(PROJECT_DIR, String(args.output));
3703
+ ensureDir(dirname(outputPath));
3704
+ writeFileSync(outputPath, jsonl, 'utf-8');
3705
+ if (args.json) {
3706
+ console.log(JSON.stringify({ ok: true, outputPath, bytes: jsonl.length }, null, 2));
3707
+ return;
3708
+ }
3709
+ console.log(`[OK] Memory JSONL exported: ${outputPath}`);
3710
+ return;
2399
3711
  }
3712
+ console.log(jsonl);
3713
+ },
3714
+ });
3715
+ const memoryImport = defineCommand({
3716
+ meta: { name: 'import', description: 'Import project memory from JSONL' },
3717
+ args: {
3718
+ file: { type: 'positional', required: true, description: 'Input JSONL file' },
3719
+ json: { type: 'boolean', default: false },
3720
+ },
3721
+ run({ args }) {
3722
+ const filePath = resolve(PROJECT_DIR, String(args.file));
3723
+ const report = memoryBrain().importJsonl(filePath);
2400
3724
  if (args.json) {
2401
- console.log(JSON.stringify(record, null, 2));
3725
+ console.log(JSON.stringify(report, null, 2));
3726
+ if (!report.ok)
3727
+ process.exitCode = 1;
2402
3728
  return;
2403
3729
  }
2404
- console.log(`\nEvidence: ${record.id}`);
2405
- console.log(`Gate: ${record.gate}`);
2406
- console.log(`Status: ${record.status}`);
2407
- console.log(`Passed: ${record.passed}`);
2408
- console.log(`Created: ${new Date(record.createdAt).toISOString()}`);
2409
- console.log(`Duration: ${record.durationMs}ms`);
2410
- if (record.blockers.length > 0) {
2411
- console.log('\nBlockers:');
2412
- for (const blocker of record.blockers)
2413
- console.log(` - ${blocker}`);
2414
- }
2415
- console.log('\nEvidence Items:');
2416
- for (const item of record.evidenceItems) {
2417
- const status = item.passed ? 'PASS' : 'FAIL';
2418
- const target = item.command ?? item.path ?? '';
2419
- console.log(` - [${status}] ${item.label}${target ? ` (${target})` : ''}`);
2420
- console.log(` ${item.detail}`);
2421
- }
3730
+ console.log('\nSCALE Memory Import');
3731
+ console.log(` Imported: ${report.imported}`);
3732
+ console.log(` Skipped: ${report.skipped}`);
3733
+ for (const warning of report.warnings)
3734
+ console.log(` [WARN] ${warning}`);
3735
+ if (!report.ok)
3736
+ process.exitCode = 1;
2422
3737
  },
2423
3738
  });
2424
- const evidence = defineCommand({
2425
- meta: { name: 'evidence', description: 'Persisted gate evidence inspection' },
2426
- subCommands: { list: evidenceList, show: evidenceShow },
3739
+ const memory = defineCommand({
3740
+ meta: { name: 'memory', description: 'Memory Fabric context packs and project-scoped long-term memory' },
3741
+ subCommands: {
3742
+ pack: memoryPack,
3743
+ doctor: memoryDoctor,
3744
+ settle: memorySettle,
3745
+ ingest: memoryIngest,
3746
+ query: memoryQuery,
3747
+ contradictions: memoryContradictions,
3748
+ dream: memoryDream,
3749
+ promote: memoryPromote,
3750
+ export: memoryExport,
3751
+ import: memoryImport,
3752
+ },
2427
3753
  });
2428
3754
  // ============================================================================
2429
3755
  // out-of-scope command — 借鉴 mattpocock/skills 的 .out-of-scope/ 设计
@@ -2619,13 +3945,16 @@ const skillPlanCommand = defineCommand({
2619
3945
  const skillDoctorCommand = defineCommand({
2620
3946
  meta: { name: 'doctor', description: 'Check workflow skill installation status' },
2621
3947
  args: {
2622
- dir: { type: 'string', default: '.', description: 'Project directory' },
3948
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
3949
+ 'supply-chain': { type: 'boolean', default: false, description: 'Include supply-chain safety review for known skill sources' },
2623
3950
  json: { type: 'boolean', default: false, description: 'Output skill doctor report as JSON' },
2624
3951
  },
2625
3952
  run({ args }) {
2626
- const report = inspectWorkflowSkills({ projectDir: args.dir });
3953
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
3954
+ const report = inspectWorkflowSkills({ projectDir });
3955
+ const supplyChain = isTruthyFlag(args['supply-chain']) ? inspectSkillSupplyChain({ projectDir }) : undefined;
2627
3956
  if (args.json) {
2628
- console.log(JSON.stringify(report, null, 2));
3957
+ console.log(JSON.stringify(supplyChain ? { installation: report, supplyChain } : report, null, 2));
2629
3958
  return;
2630
3959
  }
2631
3960
  console.log('\nSCALE Skill Doctor');
@@ -2637,7 +3966,18 @@ const skillDoctorCommand = defineCommand({
2637
3966
  if (!skill.installed)
2638
3967
  console.log(` install: ${skill.installCommand}`);
2639
3968
  }
2640
- if (!report.ok)
3969
+ if (supplyChain) {
3970
+ console.log('\nSkill Supply Chain');
3971
+ console.log(` Evaluated: ${supplyChain.evaluated}`);
3972
+ console.log(` Blocked: ${supplyChain.blocked}`);
3973
+ console.log(` Warnings: ${supplyChain.warnings}`);
3974
+ for (const entry of supplyChain.entries.filter(entry => entry.blocked || entry.findings.length > 0)) {
3975
+ console.log(` [${entry.blocked ? 'BLOCKED' : 'WARN'}] ${entry.id}: ${entry.risk}`);
3976
+ for (const finding of entry.findings)
3977
+ console.log(` - ${finding.rule}: ${finding.message}`);
3978
+ }
3979
+ }
3980
+ if (!report.ok || supplyChain?.ok === false)
2641
3981
  process.exitCode = 1;
2642
3982
  },
2643
3983
  });
@@ -2749,6 +4089,59 @@ const skillSafetyCommand = defineCommand({
2749
4089
  process.exitCode = 1;
2750
4090
  },
2751
4091
  });
4092
+ const skillRadarCommand = defineCommand({
4093
+ meta: { name: 'radar', description: 'Recommend skills, MCP, and CLI capabilities with confidence, safety, and evidence requirements' },
4094
+ args: {
4095
+ task: { type: 'string', required: true, description: 'Task description' },
4096
+ phase: { type: 'string', description: 'Workflow phase' },
4097
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
4098
+ files: { type: 'string', description: 'Comma-separated changed or relevant files' },
4099
+ services: { type: 'string', description: 'Comma-separated services or modules' },
4100
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
4101
+ output: { type: 'string', alias: 'o', description: 'Write markdown report to file' },
4102
+ json: { type: 'boolean', default: false, description: 'Output radar report as JSON' },
4103
+ },
4104
+ run({ args }) {
4105
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
4106
+ const scaleDir = resolveScaleDirForProject(projectDir);
4107
+ const report = evaluateSkillRadar({
4108
+ projectDir,
4109
+ scaleDir,
4110
+ task: String(args.task),
4111
+ phase: args.phase ? String(args.phase) : undefined,
4112
+ level: String(args.level ?? 'M'),
4113
+ files: parseCommaList(args.files),
4114
+ services: parseCommaList(args.services),
4115
+ });
4116
+ if (args.output) {
4117
+ const outputPath = resolve(projectDir, String(args.output));
4118
+ ensureDir(dirname(outputPath));
4119
+ writeFileSync(outputPath, renderSkillRadarMarkdown(report), 'utf-8');
4120
+ }
4121
+ if (args.json) {
4122
+ console.log(JSON.stringify(report, null, 2));
4123
+ if (!report.ok)
4124
+ process.exitCode = 1;
4125
+ return;
4126
+ }
4127
+ console.log('\nSCALE Skill Radar');
4128
+ console.log(` Task: ${report.task}`);
4129
+ console.log(` Level: ${report.level}`);
4130
+ console.log(` Domains: ${report.detectedDomains.map(domain => `${domain.domain}:${domain.score}`).join(', ') || 'none'}`);
4131
+ console.log(` Policy: ${report.policyMode}`);
4132
+ console.log(` Tools: ${report.toolSummary.installed}/${report.toolSummary.total} installed`);
4133
+ for (const item of report.recommendations.slice(0, 8)) {
4134
+ console.log(` [${item.action}] ${item.id} confidence=${item.confidence.toFixed(2)} safety=${item.safetyLevel}`);
4135
+ console.log(` evidence: ${item.requiredEvidence.join(', ') || 'none'}`);
4136
+ if (item.safetyLevel === 'blocked' || item.action === 'suggest-fallback')
4137
+ console.log(` fallback: ${item.fallback}`);
4138
+ }
4139
+ if (args.output)
4140
+ console.log(` Report: ${resolve(projectDir, String(args.output))}`);
4141
+ if (!report.ok)
4142
+ process.exitCode = 1;
4143
+ },
4144
+ });
2752
4145
  const skillRecommendCommand = defineCommand({
2753
4146
  meta: { name: 'recommend', description: 'Recommend a composable skill workflow for a task' },
2754
4147
  args: {
@@ -2783,6 +4176,7 @@ const skill = defineCommand({
2783
4176
  check: skillCheckCommand,
2784
4177
  repo: skillRepoCommand,
2785
4178
  safety: skillSafetyCommand,
4179
+ radar: skillRadarCommand,
2786
4180
  recommend: skillRecommendCommand,
2787
4181
  },
2788
4182
  });
@@ -3211,6 +4605,8 @@ const main = defineCommand({
3211
4605
  stats,
3212
4606
  preflight,
3213
4607
  governance,
4608
+ codegraph,
4609
+ eval: evalCommand,
3214
4610
  artifact,
3215
4611
  assets,
3216
4612
  standards,
@@ -3220,6 +4616,8 @@ const main = defineCommand({
3220
4616
  status,
3221
4617
  workflow,
3222
4618
  evidence,
4619
+ runtime,
4620
+ memory,
3223
4621
  diagnose,
3224
4622
  tdd,
3225
4623
  tool,