@hongmaple0820/scale-engine 0.16.0 → 0.18.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 (57) hide show
  1. package/dist/api/cli.js +494 -9
  2. package/dist/api/cli.js.map +1 -1
  3. package/dist/api/doctor.d.ts +1 -0
  4. package/dist/api/doctor.js +45 -0
  5. package/dist/api/doctor.js.map +1 -1
  6. package/dist/api/quickstart.js +5 -2
  7. package/dist/api/quickstart.js.map +1 -1
  8. package/dist/cli/phaseCommands.js +36 -6
  9. package/dist/cli/phaseCommands.js.map +1 -1
  10. package/dist/core/logger.js +1 -1
  11. package/dist/index.d.ts +5 -0
  12. package/dist/index.js +5 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/output/HTMLArtifactLayer.d.ts +97 -0
  15. package/dist/output/HTMLArtifactLayer.js +576 -0
  16. package/dist/output/HTMLArtifactLayer.js.map +1 -0
  17. package/dist/output/index.d.ts +2 -0
  18. package/dist/output/index.js +1 -0
  19. package/dist/output/index.js.map +1 -1
  20. package/dist/skills/routing/SkillGate.js +26 -2
  21. package/dist/skills/routing/SkillGate.js.map +1 -1
  22. package/dist/skills/routing/SkillPolicy.js +2 -2
  23. package/dist/skills/routing/SkillPolicy.js.map +1 -1
  24. package/dist/tools/ToolCapabilityRegistry.d.ts +1 -1
  25. package/dist/tools/ToolCapabilityRegistry.js +4 -4
  26. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  27. package/dist/tools/ToolOrchestrator.js +57 -0
  28. package/dist/tools/ToolOrchestrator.js.map +1 -1
  29. package/dist/workflow/ContextGovernance.d.ts +51 -0
  30. package/dist/workflow/ContextGovernance.js +233 -0
  31. package/dist/workflow/ContextGovernance.js.map +1 -0
  32. package/dist/workflow/DiagnosticLoop.d.ts +40 -0
  33. package/dist/workflow/DiagnosticLoop.js +105 -0
  34. package/dist/workflow/DiagnosticLoop.js.map +1 -0
  35. package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
  36. package/dist/workflow/GovernanceTemplatePacks.js +77 -19
  37. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  38. package/dist/workflow/GovernanceTemplates.js +36 -12
  39. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  40. package/dist/workflow/ResourceGovernance.js +2 -1
  41. package/dist/workflow/ResourceGovernance.js.map +1 -1
  42. package/dist/workflow/TaskArtifactScaffolder.d.ts +22 -0
  43. package/dist/workflow/TaskArtifactScaffolder.js +55 -0
  44. package/dist/workflow/TaskArtifactScaffolder.js.map +1 -1
  45. package/dist/workflow/TddLoop.d.ts +47 -0
  46. package/dist/workflow/TddLoop.js +76 -0
  47. package/dist/workflow/TddLoop.js.map +1 -0
  48. package/dist/workflow/WorkflowGuidance.d.ts +26 -0
  49. package/dist/workflow/WorkflowGuidance.js +173 -0
  50. package/dist/workflow/WorkflowGuidance.js.map +1 -0
  51. package/dist/workflow/WorkflowOpenTasks.d.ts +16 -0
  52. package/dist/workflow/WorkflowOpenTasks.js +37 -0
  53. package/dist/workflow/WorkflowOpenTasks.js.map +1 -0
  54. package/dist/workflow/index.d.ts +5 -0
  55. package/dist/workflow/index.js +5 -0
  56. package/dist/workflow/index.js.map +1 -1
  57. package/package.json +2 -2
package/dist/api/cli.js CHANGED
@@ -30,24 +30,30 @@ import { EvidenceStore } from '../workflow/EvidenceStore.js';
30
30
  import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
31
31
  import { ReviewStore } from '../workflow/ReviewStore.js';
32
32
  import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
33
- import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
33
+ import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
34
34
  import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
35
35
  import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
36
36
  import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
37
37
  import { doctorResourceAssets, scanResourceAssets, settleResourceAssets } from '../workflow/ResourceGovernance.js';
38
+ import { analyzeContextGovernance, renderContextGrillPrompt, writeContextGovernanceTemplates, } from '../workflow/ContextGovernance.js';
39
+ import { createDiagnosticLoop, renderDiagnosticLoopMarkdown, validateDiagnosticLoop, } from '../workflow/DiagnosticLoop.js';
40
+ import { createTddSlice, evaluateTddSlice, renderTddSliceMarkdown, } from '../workflow/TddLoop.js';
41
+ import { nextWorkflowOpenTask, removeWorkflowOpenTask, toolEvidenceRunCompletesOpenTask } from '../workflow/WorkflowOpenTasks.js';
38
42
  import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
39
- import { checkTaskArtifactCompleteness } from '../workflow/TaskArtifactScaffolder.js';
43
+ import { appendContextGrillArtifact, appendDiagnosticLoopArtifact, appendTddSliceArtifact, checkTaskArtifactCompleteness, } from '../workflow/TaskArtifactScaffolder.js';
40
44
  import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
41
45
  import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
42
46
  import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
43
47
  import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
44
48
  import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
45
49
  import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
50
+ import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
46
51
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
47
52
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
48
53
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
49
54
  import { join, resolve } from 'node:path';
50
55
  import { execFileSync } from 'node:child_process';
56
+ import { pathToFileURL } from 'node:url';
51
57
  import { SCALE_ENGINE_VERSION } from '../version.js';
52
58
  // ============================================================================
53
59
  // Engine bootstrap (单例 + lazy init)
@@ -69,6 +75,18 @@ function ensureDir(dir) {
69
75
  function isTruthyFlag(value) {
70
76
  return value === true || value === '' || value === 'true' || value === '1';
71
77
  }
78
+ function commandEvidence(command, exitCode, summary) {
79
+ if (exitCode === undefined || exitCode === null || exitCode === '')
80
+ return undefined;
81
+ const parsed = Number.parseInt(String(exitCode), 10);
82
+ if (Number.isNaN(parsed))
83
+ return undefined;
84
+ return {
85
+ command,
86
+ exitCode: parsed,
87
+ outputSummary: summary ? String(summary) : `Command exited ${parsed}`,
88
+ };
89
+ }
72
90
  function normalizePreflightProfile(value) {
73
91
  const normalized = String(value ?? 'quick').trim().toLowerCase();
74
92
  if (normalized === 'full' || normalized === 'ci')
@@ -80,6 +98,26 @@ function gatesForPreflightProfile(profile) {
80
98
  return ['G3', 'G0', 'G4', 'G5'];
81
99
  return ['G3', 'G0', 'G4', 'G5', 'G6', 'G7'];
82
100
  }
101
+ function shouldSkipPreflightCommandTargets(resolved, args) {
102
+ if (!resolved.matrix)
103
+ return false;
104
+ const requestedService = String(args.service ?? '').trim();
105
+ if (requestedService && requestedService !== 'all')
106
+ return false;
107
+ const hasCommandOverrides = [
108
+ args['build-cmd'],
109
+ args['lint-cmd'],
110
+ args['test-cmd'],
111
+ args['coverage-cmd'],
112
+ ].some(value => typeof value === 'string' && value.trim().length > 0);
113
+ if (hasCommandOverrides)
114
+ return false;
115
+ const profile = resolved.matrix.profiles?.[resolved.profileName];
116
+ const hasProfileCommands = Object.values(profile?.commands ?? {})
117
+ .some(value => typeof value === 'string' && value.trim().length > 0);
118
+ const hasServices = (resolved.matrix.services ?? []).length > 0;
119
+ return !hasServices && !hasProfileCommands;
120
+ }
83
121
  function evaluateEngineeringStandardsGate(options) {
84
122
  const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
85
123
  if (mode === 'off') {
@@ -776,9 +814,239 @@ const contextGlossary = defineCommand({
776
814
  }
777
815
  },
778
816
  });
817
+ const contextInit = defineCommand({
818
+ meta: { name: 'init', description: 'Create CONTEXT.md and CONTEXT-MAP.md starter templates' },
819
+ args: {
820
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
821
+ name: { type: 'string', description: 'Project display name' },
822
+ force: { type: 'boolean', default: false, description: 'Overwrite existing templates' },
823
+ json: { type: 'boolean', default: false },
824
+ },
825
+ run({ args }) {
826
+ const result = writeContextGovernanceTemplates({
827
+ projectDir: resolve(String(args.dir ?? PROJECT_DIR)),
828
+ projectName: args.name ? String(args.name) : undefined,
829
+ force: isTruthyFlag(args.force),
830
+ });
831
+ if (args.json) {
832
+ console.log(JSON.stringify(result, null, 2));
833
+ return;
834
+ }
835
+ console.log('\nSCALE Context Templates');
836
+ for (const file of result.created)
837
+ console.log(` [CREATED] ${file}`);
838
+ for (const file of result.skipped)
839
+ console.log(` [SKIPPED] ${file}`);
840
+ },
841
+ });
842
+ const contextGrill = defineCommand({
843
+ meta: { name: 'grill', description: 'Check project context docs and generate request-specific grill questions' },
844
+ args: {
845
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
846
+ 'task-id': { type: 'string', description: 'Task id for workflow state and artifact linkage' },
847
+ task: { type: 'string', required: true, description: 'Task or requirement description' },
848
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
849
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where explore.md should be updated' },
850
+ write: { type: 'boolean', default: false, description: 'Append context grill output to the task explore artifact' },
851
+ json: { type: 'boolean', default: false },
852
+ },
853
+ run({ args }) {
854
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
855
+ const taskId = String(args['task-id'] ?? `context-${Date.now()}`);
856
+ const changedFiles = parseCommaList(args.files);
857
+ const report = analyzeContextGovernance({
858
+ projectDir,
859
+ request: String(args.task ?? ''),
860
+ changedFiles,
861
+ });
862
+ const artifactPath = isTruthyFlag(args.write)
863
+ ? appendContextGrillArtifact({
864
+ projectDir,
865
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
866
+ report,
867
+ }) ?? undefined
868
+ : undefined;
869
+ if (args['task-id'] || artifactPath) {
870
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
871
+ const current = writer.readCurrentState();
872
+ const currentOpenTasks = current?.taskId === taskId ? current.openTasks : [];
873
+ writer.updateCurrentState({
874
+ taskId,
875
+ phase: 'explore',
876
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
877
+ exploredFiles: changedFiles,
878
+ fileCount: changedFiles.length,
879
+ mainContradiction: report.findings[0]?.message ?? 'context governance ready',
880
+ openTasks: removeWorkflowOpenTask(currentOpenTasks, 'context-grill'),
881
+ });
882
+ }
883
+ if (args.json) {
884
+ console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
885
+ return;
886
+ }
887
+ console.log(renderContextGrillPrompt(report));
888
+ if (artifactPath)
889
+ console.log(`\nArtifact: ${artifactPath}`);
890
+ },
891
+ });
779
892
  const context = defineCommand({
780
893
  meta: { name: 'context', description: 'Context assembly' },
781
- subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary },
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' },
901
+ 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' },
910
+ json: { type: 'boolean', default: false },
911
+ },
912
+ 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),
921
+ });
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
+ }
948
+ if (args.json) {
949
+ console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
950
+ return;
951
+ }
952
+ console.log(renderDiagnosticLoopMarkdown(loop));
953
+ if (!validation.ready) {
954
+ console.log('\nBlockers:');
955
+ for (const blocker of validation.blockers)
956
+ console.log(` - ${blocker}`);
957
+ }
958
+ if (artifactPath)
959
+ console.log(`\nArtifact: ${artifactPath}`);
960
+ },
961
+ });
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' },
971
+ 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' },
986
+ json: { type: 'boolean', default: false },
987
+ },
988
+ 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']),
1000
+ });
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`);
1030
+ }
1031
+ if (args.json) {
1032
+ console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
1033
+ return;
1034
+ }
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}`);
1040
+ }
1041
+ if (artifactPath)
1042
+ console.log(`\nArtifact: ${artifactPath}`);
1043
+ if (tddStatePath)
1044
+ console.log(`TDD state: ${tddStatePath}`);
1045
+ },
1046
+ });
1047
+ const tdd = defineCommand({
1048
+ meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
1049
+ subCommands: { slice: tddSliceCommand },
782
1050
  });
783
1051
  // ============================================================================
784
1052
  // stats
@@ -1132,6 +1400,10 @@ const preflight = defineCommand({
1132
1400
  profile: args.profile,
1133
1401
  service: args.service,
1134
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
+ }
1135
1407
  const engineeringStandards = evaluateEngineeringStandardsGate({
1136
1408
  policy: resolved.policy,
1137
1409
  });
@@ -1151,7 +1423,7 @@ const preflight = defineCommand({
1151
1423
  console.log(' Engineering standards: skipped');
1152
1424
  }
1153
1425
  }
1154
- for (const target of resolved.targets) {
1426
+ for (const target of commandTargetsSkipped ? [] : resolved.targets) {
1155
1427
  if (!args.json) {
1156
1428
  const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
1157
1429
  console.log(`\n Target: ${label}`);
@@ -1181,8 +1453,7 @@ const preflight = defineCommand({
1181
1453
  }
1182
1454
  }
1183
1455
  }
1184
- const passed = targetResults.length > 0 &&
1185
- targetResults.every(target => target.passed) &&
1456
+ const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
1186
1457
  !engineeringStandards.blocked;
1187
1458
  const result = {
1188
1459
  phase: 'PREFLIGHT',
@@ -1193,6 +1464,7 @@ const preflight = defineCommand({
1193
1464
  policy: resolved.policy,
1194
1465
  engineeringStandards,
1195
1466
  targets: targetResults,
1467
+ commandTargetsSkipped,
1196
1468
  passed,
1197
1469
  };
1198
1470
  if (args.json) {
@@ -1224,6 +1496,9 @@ const status = defineCommand({
1224
1496
  const latestReviews = reviewStore.listReviews(5);
1225
1497
  const latestTask = tasks[0];
1226
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);
1227
1502
  const blockers = [];
1228
1503
  const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
1229
1504
  const latestBlockingReview = latestReviews.find(record => !record.passed);
@@ -1244,6 +1519,10 @@ const status = defineCommand({
1244
1519
  return `scale plan ${specs[0].id}`;
1245
1520
  if (!latestTask)
1246
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}`;
1247
1526
  if (!taskPayload?.verificationEvidenceIds?.length)
1248
1527
  return `scale verify ${latestTask.id}`;
1249
1528
  if (latestTask.status !== 'COMPLETED')
@@ -1285,6 +1564,14 @@ const status = defineCommand({
1285
1564
  summary: record.summary,
1286
1565
  createdAt: record.createdAt,
1287
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,
1288
1575
  blockers,
1289
1576
  nextCommand,
1290
1577
  };
@@ -1314,6 +1601,11 @@ const status = defineCommand({
1314
1601
  for (const blocker of blockers)
1315
1602
  console.log(` - ${blocker}`);
1316
1603
  }
1604
+ if (result.workflowState?.openTasks.length) {
1605
+ console.log('\nOpen Tasks:');
1606
+ for (const task of result.workflowState.openTasks)
1607
+ console.log(` - ${task}`);
1608
+ }
1317
1609
  console.log(`\nNext: ${nextCommand}`);
1318
1610
  },
1319
1611
  });
@@ -1423,7 +1715,7 @@ const init = defineCommand({
1423
1715
  const detection = qsResult.success ? undefined : detectPlatform(args.dir);
1424
1716
  console.log(JSON.stringify({
1425
1717
  ok: qsResult.success,
1426
- mode: 'quick',
1718
+ mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
1427
1719
  platform: qsResult.platform,
1428
1720
  created: qsResult.created,
1429
1721
  skipped: qsResult.skipped,
@@ -1435,8 +1727,11 @@ const init = defineCommand({
1435
1727
  }, null, 2));
1436
1728
  return;
1437
1729
  }
1438
- if (qsResult.success && qsResult.platform) {
1439
- console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
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}`);
1440
1735
  console.log(`\n📁 Created (${qsResult.created.length}):`);
1441
1736
  for (const f of qsResult.created)
1442
1737
  console.log(` + ${f}`);
@@ -1790,6 +2085,182 @@ const standards = defineCommand({
1790
2085
  subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
1791
2086
  });
1792
2087
  // ============================================================================
2088
+ // artifact command - Derived HTML artifacts for human review
2089
+ // ============================================================================
2090
+ const artifactRender = defineCommand({
2091
+ meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
2092
+ 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' },
2102
+ },
2103
+ run({ args }) {
2104
+ const result = renderHtmlArtifact({
2105
+ projectDir: args.dir,
2106
+ 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,
2113
+ });
2114
+ if (args.json) {
2115
+ console.log(JSON.stringify(result, null, 2));
2116
+ return;
2117
+ }
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
+ }
2126
+ },
2127
+ });
2128
+ const artifactDoctor = defineCommand({
2129
+ meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
2130
+ 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' },
2136
+ },
2137
+ run({ args }) {
2138
+ const report = doctorHtmlArtifacts({
2139
+ projectDir: args.dir,
2140
+ taskId: args['task-id'],
2141
+ artifactDir: args['artifact-dir'],
2142
+ type: typeof args.type === 'string' ? args.type : undefined,
2143
+ });
2144
+ if (args.json) {
2145
+ console.log(JSON.stringify(report, null, 2));
2146
+ if (!report.ok)
2147
+ process.exitCode = 1;
2148
+ return;
2149
+ }
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
+ }
2163
+ }
2164
+ if (!report.ok)
2165
+ process.exitCode = 1;
2166
+ },
2167
+ });
2168
+ const artifactSettle = defineCommand({
2169
+ meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
2170
+ 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' },
2175
+ },
2176
+ run({ args }) {
2177
+ const report = settleHtmlArtifacts({
2178
+ projectDir: args.dir,
2179
+ taskId: args['task-id'],
2180
+ artifactDir: args['artifact-dir'],
2181
+ });
2182
+ if (args.json) {
2183
+ console.log(JSON.stringify(report, null, 2));
2184
+ if (!report.ok)
2185
+ process.exitCode = 1;
2186
+ return;
2187
+ }
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;
2196
+ },
2197
+ });
2198
+ const artifactOpen = defineCommand({
2199
+ meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
2200
+ 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' },
2207
+ },
2208
+ run({ args }) {
2209
+ const path = resolveHtmlArtifactForOpen({
2210
+ projectDir: args.dir,
2211
+ taskId: args['task-id'],
2212
+ artifactDir: args['artifact-dir'],
2213
+ type: typeof args.type === 'string' ? args.type : undefined,
2214
+ });
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
+ if (args.json) {
2221
+ console.log(JSON.stringify(output, null, 2));
2222
+ if (!exists)
2223
+ process.exitCode = 1;
2224
+ return;
2225
+ }
2226
+ if (!exists) {
2227
+ console.log(`HTML artifact not found: ${path}`);
2228
+ process.exitCode = 1;
2229
+ return;
2230
+ }
2231
+ console.log(url);
2232
+ },
2233
+ });
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' });
2254
+ }
2255
+ else {
2256
+ execFileSync('xdg-open', [path], { stdio: 'ignore' });
2257
+ }
2258
+ }
2259
+ catch {
2260
+ // Opening is convenience-only; artifact doctor/render remains the source of truth.
2261
+ }
2262
+ }
2263
+ // ============================================================================
1793
2264
  // evolve command
1794
2265
  // ============================================================================
1795
2266
  const evolve = defineCommand({
@@ -2187,6 +2658,7 @@ const skillCheckCommand = defineCommand({
2187
2658
  artifactsDir: args.dir ?? state?.artifactsDir,
2188
2659
  level,
2189
2660
  requiredArtifacts: state?.requiredSkillArtifacts,
2661
+ requiredSkills: state?.requiredSkills,
2190
2662
  mode: state?.skillRoutingMode ?? policy.policy.mode,
2191
2663
  enforceLevels: policy.policy.enforceLevels,
2192
2664
  });
@@ -2480,6 +2952,16 @@ const toolRunCommand = defineCommand({
2480
2952
  const report = await result.orchestrator.run(result.plan, {
2481
2953
  dryRun: isTruthyFlag(args['dry-run']),
2482
2954
  });
2955
+ if (toolEvidenceRunCompletesOpenTask(report)) {
2956
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
2957
+ const current = writer.readCurrentState();
2958
+ if (current?.taskId === report.taskId) {
2959
+ writer.updateCurrentState({
2960
+ taskId: report.taskId,
2961
+ openTasks: removeWorkflowOpenTask(current.openTasks, 'tool-evidence'),
2962
+ });
2963
+ }
2964
+ }
2483
2965
  if (args.json) {
2484
2966
  console.log(JSON.stringify(report, null, 2));
2485
2967
  }
@@ -2729,6 +3211,7 @@ const main = defineCommand({
2729
3211
  stats,
2730
3212
  preflight,
2731
3213
  governance,
3214
+ artifact,
2732
3215
  assets,
2733
3216
  standards,
2734
3217
  metrics,
@@ -2737,6 +3220,8 @@ const main = defineCommand({
2737
3220
  status,
2738
3221
  workflow,
2739
3222
  evidence,
3223
+ diagnose,
3224
+ tdd,
2740
3225
  tool,
2741
3226
  skill,
2742
3227
  skills: skill,