@hongmaple0820/scale-engine 0.15.1 → 0.17.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 (69) hide show
  1. package/dist/agents/LeadershipPresets.d.ts +16 -0
  2. package/dist/agents/LeadershipPresets.js +152 -0
  3. package/dist/agents/LeadershipPresets.js.map +1 -0
  4. package/dist/api/cli.js +774 -8
  5. package/dist/api/cli.js.map +1 -1
  6. package/dist/artifact/types.d.ts +4 -0
  7. package/dist/artifact/types.js.map +1 -1
  8. package/dist/cli/phaseCommands.d.ts +14 -0
  9. package/dist/cli/phaseCommands.js +187 -6
  10. package/dist/cli/phaseCommands.js.map +1 -1
  11. package/dist/cli/vibeCommands.d.ts +20 -0
  12. package/dist/cli/vibeCommands.js +150 -173
  13. package/dist/cli/vibeCommands.js.map +1 -1
  14. package/dist/index.d.ts +9 -0
  15. package/dist/index.js +10 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/prompts/VibeTemplateGallery.d.ts +25 -0
  18. package/dist/prompts/VibeTemplateGallery.js +295 -0
  19. package/dist/prompts/VibeTemplateGallery.js.map +1 -0
  20. package/dist/skills/SkillRepository.d.ts +63 -0
  21. package/dist/skills/SkillRepository.js +365 -0
  22. package/dist/skills/SkillRepository.js.map +1 -0
  23. package/dist/tools/ToolCapabilityRegistry.d.ts +46 -0
  24. package/dist/tools/ToolCapabilityRegistry.js +223 -0
  25. package/dist/tools/ToolCapabilityRegistry.js.map +1 -0
  26. package/dist/tools/ToolEvidenceGate.d.ts +39 -0
  27. package/dist/tools/ToolEvidenceGate.js +117 -0
  28. package/dist/tools/ToolEvidenceGate.js.map +1 -0
  29. package/dist/tools/ToolEvidenceStore.d.ts +58 -0
  30. package/dist/tools/ToolEvidenceStore.js +129 -0
  31. package/dist/tools/ToolEvidenceStore.js.map +1 -0
  32. package/dist/tools/ToolOrchestrator.d.ts +67 -0
  33. package/dist/tools/ToolOrchestrator.js +246 -0
  34. package/dist/tools/ToolOrchestrator.js.map +1 -0
  35. package/dist/tools/ToolPolicy.d.ts +33 -0
  36. package/dist/tools/ToolPolicy.js +157 -0
  37. package/dist/tools/ToolPolicy.js.map +1 -0
  38. package/dist/tools/index.d.ts +5 -0
  39. package/dist/tools/index.js +6 -0
  40. package/dist/tools/index.js.map +1 -0
  41. package/dist/workflow/ContextGovernance.d.ts +51 -0
  42. package/dist/workflow/ContextGovernance.js +233 -0
  43. package/dist/workflow/ContextGovernance.js.map +1 -0
  44. package/dist/workflow/DiagnosticLoop.d.ts +40 -0
  45. package/dist/workflow/DiagnosticLoop.js +105 -0
  46. package/dist/workflow/DiagnosticLoop.js.map +1 -0
  47. package/dist/workflow/EngineeringStandards.d.ts +69 -0
  48. package/dist/workflow/EngineeringStandards.js +348 -6
  49. package/dist/workflow/EngineeringStandards.js.map +1 -1
  50. package/dist/workflow/GovernanceTemplatePacks.js +11 -9
  51. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  52. package/dist/workflow/GovernanceTemplates.js +15 -4
  53. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  54. package/dist/workflow/TaskArtifactScaffolder.d.ts +22 -0
  55. package/dist/workflow/TaskArtifactScaffolder.js +55 -0
  56. package/dist/workflow/TaskArtifactScaffolder.js.map +1 -1
  57. package/dist/workflow/TddLoop.d.ts +47 -0
  58. package/dist/workflow/TddLoop.js +76 -0
  59. package/dist/workflow/TddLoop.js.map +1 -0
  60. package/dist/workflow/WorkflowGuidance.d.ts +26 -0
  61. package/dist/workflow/WorkflowGuidance.js +173 -0
  62. package/dist/workflow/WorkflowGuidance.js.map +1 -0
  63. package/dist/workflow/WorkflowOpenTasks.d.ts +16 -0
  64. package/dist/workflow/WorkflowOpenTasks.js +37 -0
  65. package/dist/workflow/WorkflowOpenTasks.js.map +1 -0
  66. package/dist/workflow/index.d.ts +5 -0
  67. package/dist/workflow/index.js +5 -0
  68. package/dist/workflow/index.js.map +1 -1
  69. package/package.json +2 -2
package/dist/api/cli.js CHANGED
@@ -23,6 +23,8 @@ import { Doctor } from './doctor.js';
23
23
  import { quickStart, detectPlatform } from './quickstart.js';
24
24
  import { SkillDiscovery } from '../skills/SkillDiscovery.js';
25
25
  import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
26
+ import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
27
+ import { listLeadershipPresets, renderLeadershipPresetsMarkdown } from '../agents/LeadershipPresets.js';
26
28
  import { listWorkflowPresets, getPresetsByScenario } from '../workflows/presets.js';
27
29
  import { EvidenceStore } from '../workflow/EvidenceStore.js';
28
30
  import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
@@ -31,15 +33,25 @@ import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
31
33
  import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
32
34
  import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
33
35
  import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
34
- import { doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
36
+ import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
35
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';
36
42
  import { TaskMetricsStore } from '../workflow/TaskMetricsStore.js';
37
- import { checkTaskArtifactCompleteness } from '../workflow/TaskArtifactScaffolder.js';
43
+ import { appendContextGrillArtifact, appendDiagnosticLoopArtifact, appendTddSliceArtifact, checkTaskArtifactCompleteness, } from '../workflow/TaskArtifactScaffolder.js';
38
44
  import { WorkflowArtifactWriter } from '../workflow/WorkflowArtifactWriter.js';
45
+ import { inspectToolCapabilities } from '../tools/ToolCapabilityRegistry.js';
46
+ import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
47
+ import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
48
+ import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
49
+ import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
39
50
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
40
51
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
41
52
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
42
53
  import { join, resolve } from 'node:path';
54
+ import { execFileSync } from 'node:child_process';
43
55
  import { SCALE_ENGINE_VERSION } from '../version.js';
44
56
  // ============================================================================
45
57
  // Engine bootstrap (单例 + lazy init)
@@ -61,6 +73,18 @@ function ensureDir(dir) {
61
73
  function isTruthyFlag(value) {
62
74
  return value === true || value === '' || value === 'true' || value === '1';
63
75
  }
76
+ function commandEvidence(command, exitCode, summary) {
77
+ if (exitCode === undefined || exitCode === null || exitCode === '')
78
+ return undefined;
79
+ const parsed = Number.parseInt(String(exitCode), 10);
80
+ if (Number.isNaN(parsed))
81
+ return undefined;
82
+ return {
83
+ command,
84
+ exitCode: parsed,
85
+ outputSummary: summary ? String(summary) : `Command exited ${parsed}`,
86
+ };
87
+ }
64
88
  function normalizePreflightProfile(value) {
65
89
  const normalized = String(value ?? 'quick').trim().toLowerCase();
66
90
  if (normalized === 'full' || normalized === 'ci')
@@ -768,9 +792,239 @@ const contextGlossary = defineCommand({
768
792
  }
769
793
  },
770
794
  });
795
+ const contextInit = defineCommand({
796
+ meta: { name: 'init', description: 'Create CONTEXT.md and CONTEXT-MAP.md starter templates' },
797
+ args: {
798
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
799
+ name: { type: 'string', description: 'Project display name' },
800
+ force: { type: 'boolean', default: false, description: 'Overwrite existing templates' },
801
+ json: { type: 'boolean', default: false },
802
+ },
803
+ run({ args }) {
804
+ const result = writeContextGovernanceTemplates({
805
+ projectDir: resolve(String(args.dir ?? PROJECT_DIR)),
806
+ projectName: args.name ? String(args.name) : undefined,
807
+ force: isTruthyFlag(args.force),
808
+ });
809
+ if (args.json) {
810
+ console.log(JSON.stringify(result, null, 2));
811
+ return;
812
+ }
813
+ console.log('\nSCALE Context Templates');
814
+ for (const file of result.created)
815
+ console.log(` [CREATED] ${file}`);
816
+ for (const file of result.skipped)
817
+ console.log(` [SKIPPED] ${file}`);
818
+ },
819
+ });
820
+ const contextGrill = defineCommand({
821
+ meta: { name: 'grill', description: 'Check project context docs and generate request-specific grill questions' },
822
+ args: {
823
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
824
+ 'task-id': { type: 'string', description: 'Task id for workflow state and artifact linkage' },
825
+ task: { type: 'string', required: true, description: 'Task or requirement description' },
826
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
827
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where explore.md should be updated' },
828
+ write: { type: 'boolean', default: false, description: 'Append context grill output to the task explore artifact' },
829
+ json: { type: 'boolean', default: false },
830
+ },
831
+ run({ args }) {
832
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
833
+ const taskId = String(args['task-id'] ?? `context-${Date.now()}`);
834
+ const changedFiles = parseCommaList(args.files);
835
+ const report = analyzeContextGovernance({
836
+ projectDir,
837
+ request: String(args.task ?? ''),
838
+ changedFiles,
839
+ });
840
+ const artifactPath = isTruthyFlag(args.write)
841
+ ? appendContextGrillArtifact({
842
+ projectDir,
843
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
844
+ report,
845
+ }) ?? undefined
846
+ : undefined;
847
+ if (args['task-id'] || artifactPath) {
848
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
849
+ const current = writer.readCurrentState();
850
+ const currentOpenTasks = current?.taskId === taskId ? current.openTasks : [];
851
+ writer.updateCurrentState({
852
+ taskId,
853
+ phase: 'explore',
854
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
855
+ exploredFiles: changedFiles,
856
+ fileCount: changedFiles.length,
857
+ mainContradiction: report.findings[0]?.message ?? 'context governance ready',
858
+ openTasks: removeWorkflowOpenTask(currentOpenTasks, 'context-grill'),
859
+ });
860
+ }
861
+ if (args.json) {
862
+ console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
863
+ return;
864
+ }
865
+ console.log(renderContextGrillPrompt(report));
866
+ if (artifactPath)
867
+ console.log(`\nArtifact: ${artifactPath}`);
868
+ },
869
+ });
771
870
  const context = defineCommand({
772
871
  meta: { name: 'context', description: 'Context assembly' },
773
- subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary },
872
+ subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill },
873
+ });
874
+ // ============================================================================
875
+ // diagnose command - evidence-first debugging loop
876
+ // ============================================================================
877
+ const diagnosePlanCommand = defineCommand({
878
+ meta: { name: 'plan', description: 'Create a reproducible diagnostic loop before fixing a bug' },
879
+ args: {
880
+ 'task-id': { type: 'string', required: true },
881
+ symptom: { type: 'string', required: true },
882
+ repro: { type: 'string', description: 'Command that reproduces the current failure' },
883
+ 'expected-failure': { type: 'string', description: 'Expected failing behavior or assertion' },
884
+ files: { type: 'string', description: 'Comma-separated changed or suspicious files' },
885
+ verify: { type: 'string', description: 'Comma-separated verification commands after the fix' },
886
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where plan.md should be updated' },
887
+ write: { type: 'boolean', default: false, description: 'Append diagnostic loop output to the task plan artifact' },
888
+ json: { type: 'boolean', default: false },
889
+ },
890
+ run({ args }) {
891
+ const changedFiles = parseCommaList(args.files);
892
+ const loop = createDiagnosticLoop({
893
+ taskId: String(args['task-id']),
894
+ symptom: String(args.symptom),
895
+ reproductionCommand: args.repro ? String(args.repro) : undefined,
896
+ expectedFailure: args['expected-failure'] ? String(args['expected-failure']) : undefined,
897
+ changedFiles,
898
+ verificationCommands: parseCommaList(args.verify),
899
+ });
900
+ const validation = validateDiagnosticLoop(loop);
901
+ const artifactPath = isTruthyFlag(args.write)
902
+ ? appendDiagnosticLoopArtifact({
903
+ projectDir: PROJECT_DIR,
904
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
905
+ loop,
906
+ validation,
907
+ }) ?? undefined
908
+ : undefined;
909
+ if (artifactPath || args['artifact-dir']) {
910
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
911
+ const current = writer.readCurrentState();
912
+ const currentOpenTasks = current?.taskId === loop.taskId ? current.openTasks : [];
913
+ writer.updateCurrentState({
914
+ taskId: loop.taskId,
915
+ phase: 'plan',
916
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
917
+ filesModified: changedFiles,
918
+ openTasks: validation.ready
919
+ ? removeWorkflowOpenTask(currentOpenTasks.filter(task => task.trim().startsWith('scale ')), 'diagnostic-loop')
920
+ : uniqueStrings([
921
+ ...currentOpenTasks,
922
+ ...validation.blockers,
923
+ ]),
924
+ });
925
+ }
926
+ if (args.json) {
927
+ console.log(JSON.stringify({ loop, validation, artifactPath }, null, 2));
928
+ return;
929
+ }
930
+ console.log(renderDiagnosticLoopMarkdown(loop));
931
+ if (!validation.ready) {
932
+ console.log('\nBlockers:');
933
+ for (const blocker of validation.blockers)
934
+ console.log(` - ${blocker}`);
935
+ }
936
+ if (artifactPath)
937
+ console.log(`\nArtifact: ${artifactPath}`);
938
+ },
939
+ });
940
+ const diagnose = defineCommand({
941
+ meta: { name: 'diagnose', description: 'Evidence-first debugging workflows' },
942
+ subCommands: { plan: diagnosePlanCommand },
943
+ });
944
+ // ============================================================================
945
+ // tdd command - vertical slice RED/GREEN/REFACTOR loop
946
+ // ============================================================================
947
+ const tddSliceCommand = defineCommand({
948
+ meta: { name: 'slice', description: 'Create and evaluate a TDD vertical slice' },
949
+ args: {
950
+ 'task-id': { type: 'string', required: true },
951
+ behavior: { type: 'string', required: true },
952
+ 'public-interface': { type: 'string', required: true },
953
+ 'failing-test': { type: 'string', required: true },
954
+ 'test-file': { type: 'string', required: true },
955
+ 'impl-files': { type: 'string', required: true },
956
+ 'red-exit-code': { type: 'string', description: 'Exit code from the RED command' },
957
+ 'red-summary': { type: 'string', description: 'Short RED output summary' },
958
+ 'green-exit-code': { type: 'string', description: 'Exit code from the GREEN command' },
959
+ 'green-summary': { type: 'string', description: 'Short GREEN output summary' },
960
+ 'refactor-exit-code': { type: 'string', description: 'Exit code from the REFACTOR command' },
961
+ 'refactor-summary': { type: 'string', description: 'Short REFACTOR output summary' },
962
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory where verification.md should be updated' },
963
+ write: { type: 'boolean', default: false, description: 'Append TDD slice output to the task verification artifact' },
964
+ json: { type: 'boolean', default: false },
965
+ },
966
+ run({ args }) {
967
+ const failingTest = String(args['failing-test']);
968
+ const slice = createTddSlice({
969
+ taskId: String(args['task-id']),
970
+ behavior: String(args.behavior),
971
+ publicInterface: String(args['public-interface']),
972
+ failingTestCommand: failingTest,
973
+ testFile: String(args['test-file']),
974
+ implementationFiles: parseCommaList(args['impl-files']),
975
+ redEvidence: commandEvidence(failingTest, args['red-exit-code'], args['red-summary']),
976
+ greenEvidence: commandEvidence(failingTest, args['green-exit-code'], args['green-summary']),
977
+ refactorEvidence: commandEvidence(failingTest, args['refactor-exit-code'], args['refactor-summary']),
978
+ });
979
+ const evaluation = evaluateTddSlice(slice);
980
+ const artifactPath = isTruthyFlag(args.write)
981
+ ? appendTddSliceArtifact({
982
+ projectDir: PROJECT_DIR,
983
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']) : undefined,
984
+ slice,
985
+ }) ?? undefined
986
+ : undefined;
987
+ let tddStatePath;
988
+ if (slice.redEvidence && slice.greenEvidence && slice.refactorEvidence) {
989
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
990
+ writer.writeTDDEvidence({
991
+ timestamp: new Date().toISOString(),
992
+ taskId: slice.taskId,
993
+ red: slice.redEvidence.exitCode !== 0,
994
+ green: slice.greenEvidence.exitCode === 0,
995
+ refactor: slice.refactorEvidence.exitCode === 0,
996
+ testFirst: slice.redEvidence.exitCode !== 0,
997
+ testFile: slice.testFile,
998
+ implFile: slice.implementationFiles[0] ?? '',
999
+ });
1000
+ writer.updateCurrentState({
1001
+ taskId: slice.taskId,
1002
+ phase: 'verify',
1003
+ artifactsDir: args['artifact-dir'] ? String(args['artifact-dir']).replace(/\\/g, '/') : undefined,
1004
+ filesModified: slice.implementationFiles,
1005
+ openTasks: removeWorkflowOpenTask(writer.readCurrentState()?.openTasks, 'tdd-slice'),
1006
+ });
1007
+ tddStatePath = join(writer.getStateDir(), `tdd-${slice.taskId}.json`);
1008
+ }
1009
+ if (args.json) {
1010
+ console.log(JSON.stringify({ slice, evaluation, artifactPath, tddStatePath }, null, 2));
1011
+ return;
1012
+ }
1013
+ console.log(renderTddSliceMarkdown(slice));
1014
+ if (evaluation.blockers.length > 0) {
1015
+ console.log('\nBlockers:');
1016
+ for (const blocker of evaluation.blockers)
1017
+ console.log(` - ${blocker}`);
1018
+ }
1019
+ if (artifactPath)
1020
+ console.log(`\nArtifact: ${artifactPath}`);
1021
+ if (tddStatePath)
1022
+ console.log(`TDD state: ${tddStatePath}`);
1023
+ },
1024
+ });
1025
+ const tdd = defineCommand({
1026
+ meta: { name: 'tdd', description: 'TDD vertical slice workflows' },
1027
+ subCommands: { slice: tddSliceCommand },
774
1028
  });
775
1029
  // ============================================================================
776
1030
  // stats
@@ -893,6 +1147,52 @@ function printWorkspaceLifecycle(report) {
893
1147
  for (const action of report.finish.nextActions)
894
1148
  console.log(` [NEXT] ${action}`);
895
1149
  }
1150
+ function compactList(values, limit = 5) {
1151
+ if (values.length <= limit)
1152
+ return values.join(', ');
1153
+ return `${values.slice(0, limit).join(', ')} (+${values.length - limit} more)`;
1154
+ }
1155
+ function printWorkspaceSummary(report) {
1156
+ const dirtyChildren = report.childRepositories
1157
+ .filter(child => !child.clean)
1158
+ .map(child => child.relativePath);
1159
+ const unpushedChildren = report.childRepositories
1160
+ .filter(child => child.ahead > 0 || (report.topology.finishPolicy.requirePushedBranches && report.topology.topology === 'moe' && !child.upstream && Boolean(child.branch)))
1161
+ .map(child => child.relativePath);
1162
+ const noUpstreamChildren = report.childRepositories
1163
+ .filter(child => !child.upstream && Boolean(child.branch))
1164
+ .map(child => child.relativePath);
1165
+ const rootStatus = report.root.clean
1166
+ ? 'clean'
1167
+ : `dirty (staged=${report.root.staged}, unstaged=${report.root.unstaged}, untracked=${report.root.untracked})`;
1168
+ const status = report.finish.blockers.length > 0 ? 'BLOCKED' : 'READY';
1169
+ console.log('\nSCALE Workspace Summary');
1170
+ console.log(` Status: ${status}`);
1171
+ console.log(` Topology: ${report.topology.topology}${report.topology.configured ? '' : ' (default)'}`);
1172
+ console.log(` Root: ${rootStatus}`);
1173
+ console.log(` Children: ${report.childRepositories.length} total, ${dirtyChildren.length} dirty, ${unpushedChildren.length} unpushed, ${noUpstreamChildren.length} no upstream`);
1174
+ if (dirtyChildren.length > 0)
1175
+ console.log(` Dirty child repositories: ${compactList(dirtyChildren)}`);
1176
+ if (unpushedChildren.length > 0)
1177
+ console.log(` Unpushed child repositories: ${compactList(unpushedChildren)}`);
1178
+ if (report.finish.blockers.length > 0) {
1179
+ console.log('\n Blockers:');
1180
+ for (const blocker of report.finish.blockers.slice(0, 8))
1181
+ console.log(` - ${blocker}`);
1182
+ if (report.finish.blockers.length > 8)
1183
+ console.log(` - ... ${report.finish.blockers.length - 8} more blocker(s)`);
1184
+ }
1185
+ if (report.finish.warnings.length > 0) {
1186
+ console.log(`\n Warnings: ${report.finish.warnings.length} warning(s); run scale workspace finish --json for details`);
1187
+ }
1188
+ console.log('\n Next:');
1189
+ const nextActions = report.finish.blockers.length > 0
1190
+ ? report.finish.nextActions
1191
+ : ['Proceed with scale ship <task-id> or cleanup when the branch policy is satisfied'];
1192
+ for (const action of nextActions.slice(0, 3))
1193
+ console.log(` - ${action}`);
1194
+ console.log(' - Run scale workspace finish --json for full details');
1195
+ }
896
1196
  function printWorkspaceTopology(topology, written) {
897
1197
  console.log('\nSCALE Workspace Topology');
898
1198
  console.log(` Topology: ${topology.topology}${topology.configured ? '' : ' (default)'}`);
@@ -925,6 +1225,7 @@ const workspaceStatus = defineCommand({
925
1225
  meta: { name: 'status', description: 'Inspect root worktree and child repository lifecycle state' },
926
1226
  args: {
927
1227
  dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1228
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
928
1229
  json: { type: 'boolean', default: false },
929
1230
  },
930
1231
  async run({ args }) {
@@ -932,6 +1233,9 @@ const workspaceStatus = defineCommand({
932
1233
  if (args.json) {
933
1234
  console.log(JSON.stringify(report, null, 2));
934
1235
  }
1236
+ else if (isTruthyFlag(args.summary)) {
1237
+ printWorkspaceSummary(report);
1238
+ }
935
1239
  else {
936
1240
  printWorkspaceLifecycle(report);
937
1241
  }
@@ -972,6 +1276,7 @@ const workspaceFinish = defineCommand({
972
1276
  meta: { name: 'finish', description: 'Check whether a temporary worktree can be safely finished or cleaned up' },
973
1277
  args: {
974
1278
  dir: { type: 'string', description: 'Repository or worktree directory; defaults to current project directory' },
1279
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary instead of the full repository listing' },
975
1280
  json: { type: 'boolean', default: false },
976
1281
  },
977
1282
  async run({ args }) {
@@ -985,6 +1290,9 @@ const workspaceFinish = defineCommand({
985
1290
  if (args.json) {
986
1291
  console.log(JSON.stringify(result, null, 2));
987
1292
  }
1293
+ else if (isTruthyFlag(args.summary)) {
1294
+ printWorkspaceSummary(report);
1295
+ }
988
1296
  else {
989
1297
  printWorkspaceLifecycle(report);
990
1298
  }
@@ -999,6 +1307,7 @@ const workspaceCleanup = defineCommand({
999
1307
  'dry-run': { type: 'boolean', default: false, description: 'Preview cleanup; this is the default unless --apply is set' },
1000
1308
  apply: { type: 'boolean', default: false, description: 'Actually run git worktree remove after safety checks' },
1001
1309
  confirm: { type: 'string', description: 'Required confirmation token for --apply, usually the worktree branch name' },
1310
+ summary: { type: 'boolean', default: false, description: 'Print concise human summary before the cleanup plan' },
1002
1311
  json: { type: 'boolean', default: false },
1003
1312
  },
1004
1313
  async run({ args }) {
@@ -1010,6 +1319,14 @@ const workspaceCleanup = defineCommand({
1010
1319
  if (args.json) {
1011
1320
  console.log(JSON.stringify(result, null, 2));
1012
1321
  }
1322
+ else if (isTruthyFlag(args.summary)) {
1323
+ printWorkspaceSummary(result.report);
1324
+ console.log('\n Cleanup:');
1325
+ console.log(` Mode: ${result.mode}`);
1326
+ console.log(` Can apply: ${result.canApply ? 'yes' : 'no'}`);
1327
+ console.log(` Applied: ${result.applied ? 'yes' : 'no'}`);
1328
+ console.log(` Confirmation token: ${result.confirmationToken ?? '(unavailable)'}`);
1329
+ }
1013
1330
  else {
1014
1331
  printWorkspaceCleanup(result);
1015
1332
  }
@@ -1153,6 +1470,9 @@ const status = defineCommand({
1153
1470
  const latestReviews = reviewStore.listReviews(5);
1154
1471
  const latestTask = tasks[0];
1155
1472
  const taskPayload = latestTask?.payload;
1473
+ const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1474
+ const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
1475
+ const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
1156
1476
  const blockers = [];
1157
1477
  const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
1158
1478
  const latestBlockingReview = latestReviews.find(record => !record.passed);
@@ -1173,6 +1493,10 @@ const status = defineCommand({
1173
1493
  return `scale plan ${specs[0].id}`;
1174
1494
  if (!latestTask)
1175
1495
  return `scale build ${plans[0].id}`;
1496
+ if (nextOpenTask?.kind === 'command')
1497
+ return nextOpenTask.value;
1498
+ if (nextOpenTask?.kind === 'blocker')
1499
+ return `Resolve workflow blocker: ${nextOpenTask.value}`;
1176
1500
  if (!taskPayload?.verificationEvidenceIds?.length)
1177
1501
  return `scale verify ${latestTask.id}`;
1178
1502
  if (latestTask.status !== 'COMPLETED')
@@ -1214,6 +1538,14 @@ const status = defineCommand({
1214
1538
  summary: record.summary,
1215
1539
  createdAt: record.createdAt,
1216
1540
  })),
1541
+ workflowState: workflowState ? {
1542
+ taskId: workflowState.taskId,
1543
+ level: workflowState.level,
1544
+ phase: workflowState.phase,
1545
+ artifactsDir: workflowState.artifactsDir,
1546
+ openTasks: workflowState.openTasks,
1547
+ skillIntents: workflowState.skillIntents,
1548
+ } : null,
1217
1549
  blockers,
1218
1550
  nextCommand,
1219
1551
  };
@@ -1243,6 +1575,11 @@ const status = defineCommand({
1243
1575
  for (const blocker of blockers)
1244
1576
  console.log(` - ${blocker}`);
1245
1577
  }
1578
+ if (result.workflowState?.openTasks.length) {
1579
+ console.log('\nOpen Tasks:');
1580
+ for (const task of result.workflowState.openTasks)
1581
+ console.log(` - ${task}`);
1582
+ }
1246
1583
  console.log(`\nNext: ${nextCommand}`);
1247
1584
  },
1248
1585
  });
@@ -1559,14 +1896,48 @@ const assets = defineCommand({
1559
1896
  // ============================================================================
1560
1897
  // standards command - Engineering standards governance
1561
1898
  // ============================================================================
1899
+ function resolveChangedFilesArg(args) {
1900
+ const explicit = splitChangedFiles(args['changed-files']);
1901
+ if (explicit.length > 0)
1902
+ return explicit;
1903
+ if (!args.changed)
1904
+ return undefined;
1905
+ return readGitChangedFiles(args.dir ?? '.');
1906
+ }
1907
+ function splitChangedFiles(value) {
1908
+ if (!value)
1909
+ return [];
1910
+ return value
1911
+ .split(/[\n,]/)
1912
+ .map(item => item.trim())
1913
+ .filter(Boolean);
1914
+ }
1915
+ function readGitChangedFiles(projectDir) {
1916
+ const tracked = readGitPathList(projectDir, ['diff', '--name-only', '--diff-filter=ACMRTUXB', 'HEAD', '--']);
1917
+ const untracked = readGitPathList(projectDir, ['ls-files', '--others', '--exclude-standard']);
1918
+ return Array.from(new Set([...tracked, ...untracked]));
1919
+ }
1920
+ function readGitPathList(projectDir, args) {
1921
+ try {
1922
+ return execFileSync('git', ['-C', projectDir, ...args], { encoding: 'utf-8' })
1923
+ .split(/\r?\n/)
1924
+ .map(item => item.trim())
1925
+ .filter(Boolean);
1926
+ }
1927
+ catch {
1928
+ return [];
1929
+ }
1930
+ }
1562
1931
  const standardsScan = defineCommand({
1563
1932
  meta: { name: 'scan', description: 'Scan source files for engineering standard violations' },
1564
1933
  args: {
1565
1934
  dir: { type: 'string', default: '.', description: 'Project directory' },
1935
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
1936
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
1566
1937
  json: { type: 'boolean', default: false, description: 'Print JSON output' },
1567
1938
  },
1568
1939
  run({ args }) {
1569
- const report = scanEngineeringStandards({ projectDir: args.dir });
1940
+ const report = scanEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
1570
1941
  if (args.json) {
1571
1942
  console.log(JSON.stringify(report, null, 2));
1572
1943
  return;
@@ -1588,10 +1959,12 @@ const standardsDoctor = defineCommand({
1588
1959
  meta: { name: 'doctor', description: 'Find blocking engineering standards problems' },
1589
1960
  args: {
1590
1961
  dir: { type: 'string', default: '.', description: 'Project directory' },
1962
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
1963
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
1591
1964
  json: { type: 'boolean', default: false, description: 'Print JSON output' },
1592
1965
  },
1593
1966
  run({ args }) {
1594
- const report = doctorEngineeringStandards({ projectDir: args.dir });
1967
+ const report = doctorEngineeringStandards({ projectDir: args.dir, changedFiles: resolveChangedFilesArg(args) });
1595
1968
  if (args.json) {
1596
1969
  console.log(JSON.stringify(report, null, 2));
1597
1970
  if (!report.ok)
@@ -1619,6 +1992,8 @@ const standardsSettle = defineCommand({
1619
1992
  dir: { type: 'string', default: '.', description: 'Project directory' },
1620
1993
  'task-id': { type: 'string', description: 'Task id for the settlement record' },
1621
1994
  'artifact-dir': { type: 'string', description: 'Task artifact directory where standards-impact.md should be updated' },
1995
+ changed: { type: 'boolean', default: false, description: 'Scan changed Git files only' },
1996
+ 'changed-files': { type: 'string', description: 'Comma or newline separated file list to scan' },
1622
1997
  json: { type: 'boolean', default: false, description: 'Print JSON output' },
1623
1998
  },
1624
1999
  run({ args }) {
@@ -1626,6 +2001,7 @@ const standardsSettle = defineCommand({
1626
2001
  projectDir: args.dir,
1627
2002
  taskId: args['task-id'],
1628
2003
  artifactsDir: args['artifact-dir'],
2004
+ changedFiles: resolveChangedFilesArg(args),
1629
2005
  });
1630
2006
  if (args.json) {
1631
2007
  console.log(JSON.stringify(report, null, 2));
@@ -1643,9 +2019,41 @@ const standardsSettle = defineCommand({
1643
2019
  process.exitCode = 1;
1644
2020
  },
1645
2021
  });
2022
+ const standardsBaseline = defineCommand({
2023
+ meta: { name: 'baseline', description: 'Generate a legacy standards baseline and classification report' },
2024
+ args: {
2025
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2026
+ write: { type: 'boolean', default: false, description: 'Write .scale/engineering-standards-baseline.json' },
2027
+ 'task-id': { type: 'string', description: 'Task id for the legacy debt report' },
2028
+ 'artifact-dir': { type: 'string', description: 'Directory where standards-legacy-debt.md should be written' },
2029
+ reason: { type: 'string', default: 'legacy standards debt accepted for staged remediation', description: 'Reason recorded on generated baseline entries' },
2030
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2031
+ },
2032
+ run({ args }) {
2033
+ const report = baselineEngineeringStandards({
2034
+ projectDir: args.dir,
2035
+ writeBaseline: args.write,
2036
+ taskId: args['task-id'],
2037
+ artifactsDir: args['artifact-dir'],
2038
+ reason: args.reason,
2039
+ });
2040
+ if (args.json) {
2041
+ console.log(JSON.stringify(report, null, 2));
2042
+ return;
2043
+ }
2044
+ console.log(`Standards baseline: ${report.wroteBaseline ? 'written' : 'dry-run'}`);
2045
+ console.log(` Baseline entries: ${report.baselineEntries.length}`);
2046
+ console.log(` Blocking findings: ${report.debt.blockingFindings}`);
2047
+ console.log(` Baseline path: ${report.baselinePath}`);
2048
+ if (report.legacyDebtPath)
2049
+ console.log(` Legacy debt report: ${report.legacyDebtPath}`);
2050
+ if (!report.wroteBaseline)
2051
+ console.log(' Re-run with --write to update .scale/engineering-standards-baseline.json.');
2052
+ },
2053
+ });
1646
2054
  const standards = defineCommand({
1647
2055
  meta: { name: 'standards', description: 'Engineering standards governance for logs, security, architecture, database, and code quality' },
1648
- subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle },
2056
+ subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
1649
2057
  });
1650
2058
  // ============================================================================
1651
2059
  // evolve command
@@ -2083,9 +2491,341 @@ const skillCheckCommand = defineCommand({
2083
2491
  process.exitCode = 1;
2084
2492
  },
2085
2493
  });
2494
+ const skillRepoCommand = defineCommand({
2495
+ meta: { name: 'repo', description: 'Show SCALE progressive skill repository guide' },
2496
+ args: {
2497
+ category: { type: 'string', description: 'Filter by category: ui/browser/desktop/testing/review/docs/agent-cli/role-library/discovery' },
2498
+ output: { type: 'string', alias: 'o', description: 'Write markdown guide to file' },
2499
+ json: { type: 'boolean', default: false },
2500
+ },
2501
+ run({ args }) {
2502
+ if (args.json) {
2503
+ console.log(JSON.stringify(listSkillRepositoryEntries(args.category ? { category: args.category } : undefined), null, 2));
2504
+ return;
2505
+ }
2506
+ const markdown = renderSkillRepositoryMarkdown();
2507
+ if (args.output) {
2508
+ const outputPath = resolve(PROJECT_DIR, args.output);
2509
+ ensureDir(resolve(outputPath, '..'));
2510
+ writeFileSync(outputPath, markdown, 'utf-8');
2511
+ console.log(`[OK] Skill 仓库指南已生成: ${outputPath}`);
2512
+ return;
2513
+ }
2514
+ console.log(markdown);
2515
+ },
2516
+ });
2517
+ const skillSafetyCommand = defineCommand({
2518
+ meta: { name: 'safety', description: 'Evaluate skill install command and source safety' },
2519
+ args: {
2520
+ source: { type: 'string', description: 'Skill source URL' },
2521
+ command: { type: 'string', description: 'Install command to review' },
2522
+ json: { type: 'boolean', default: false },
2523
+ },
2524
+ run({ args }) {
2525
+ const report = evaluateSkillInstallSafety({
2526
+ sourceUrl: args.source,
2527
+ installCommand: args.command,
2528
+ });
2529
+ if (args.json) {
2530
+ console.log(JSON.stringify(report, null, 2));
2531
+ return;
2532
+ }
2533
+ console.log('\nSCALE Skill Safety');
2534
+ console.log(` Risk: ${report.risk}`);
2535
+ console.log(` Blocked: ${report.blocked}`);
2536
+ for (const finding of report.findings) {
2537
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.rule}: ${finding.message}`);
2538
+ }
2539
+ console.log(' Required checks:');
2540
+ for (const check of report.requiredChecks)
2541
+ console.log(` - ${check}`);
2542
+ if (report.blocked)
2543
+ process.exitCode = 1;
2544
+ },
2545
+ });
2546
+ const skillRecommendCommand = defineCommand({
2547
+ meta: { name: 'recommend', description: 'Recommend a composable skill workflow for a task' },
2548
+ args: {
2549
+ task: { type: 'string', required: true, description: 'Task description' },
2550
+ phase: { type: 'string', description: 'Workflow phase' },
2551
+ json: { type: 'boolean', default: false },
2552
+ },
2553
+ run({ args }) {
2554
+ const plan = recommendSkillWorkflow({
2555
+ description: args.task,
2556
+ phase: args.phase,
2557
+ });
2558
+ if (args.json) {
2559
+ console.log(JSON.stringify(plan, null, 2));
2560
+ return;
2561
+ }
2562
+ console.log('\nSCALE Skill Recommendation');
2563
+ console.log(` Primary: ${plan.primarySkills.join(', ') || 'none'}`);
2564
+ console.log(` Supporting: ${plan.supportingSkills.join(', ') || 'none'}`);
2565
+ console.log(` Safety required: ${plan.safetyRequired}`);
2566
+ console.log(` Evidence: ${plan.requiredEvidence.join(', ') || 'none'}`);
2567
+ for (const reason of plan.rationale)
2568
+ console.log(` - ${reason}`);
2569
+ },
2570
+ });
2086
2571
  const skill = defineCommand({
2087
2572
  meta: { name: 'skill', description: 'Skill discovery and management' },
2088
- subCommands: { scan: skillScan, doctor: skillDoctorCommand, plan: skillPlanCommand, check: skillCheckCommand },
2573
+ subCommands: {
2574
+ scan: skillScan,
2575
+ doctor: skillDoctorCommand,
2576
+ plan: skillPlanCommand,
2577
+ check: skillCheckCommand,
2578
+ repo: skillRepoCommand,
2579
+ safety: skillSafetyCommand,
2580
+ recommend: skillRecommendCommand,
2581
+ },
2582
+ });
2583
+ // ============================================================================
2584
+ // tool command - Skills/MCP/CLI orchestration governance
2585
+ // ============================================================================
2586
+ function normalizeToolMode(value) {
2587
+ const normalized = String(value ?? 'evidence-required');
2588
+ if (normalized === 'off' || normalized === 'advisory' || normalized === 'evidence-required' || normalized === 'block')
2589
+ return normalized;
2590
+ return 'evidence-required';
2591
+ }
2592
+ function parseToolIds(value) {
2593
+ const raw = String(value ?? '').trim();
2594
+ if (!raw)
2595
+ return undefined;
2596
+ return raw.split(',').map(item => item.trim()).filter(Boolean);
2597
+ }
2598
+ function parseCommaList(value) {
2599
+ return parseToolIds(value) ?? [];
2600
+ }
2601
+ function createToolExecutionPlanFromArgs(args) {
2602
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
2603
+ const level = normalizeTaskArtifactLevel(args.level ?? 'M');
2604
+ const skillPolicy = loadSkillRoutingPolicy(projectDir, SCALE_DIR);
2605
+ const skillPlan = createSkillPlan({
2606
+ taskId: String(args['task-id'] ?? `TOOL-${Date.now()}`),
2607
+ taskName: String(args.task ?? 'Tool orchestration task'),
2608
+ description: String(args.task ?? ''),
2609
+ level,
2610
+ files: parseCommaList(args.files),
2611
+ services: parseCommaList(args.services),
2612
+ policy: skillPolicy,
2613
+ });
2614
+ const toolPolicy = loadToolPolicy(projectDir, SCALE_DIR);
2615
+ const toolIds = uniqueStrings([
2616
+ ...skillPlan.requiredSkills,
2617
+ ...skillPlan.recommendedSkills,
2618
+ ...Object.keys(toolPolicy.tools).filter(toolId => {
2619
+ const config = toolPolicy.tools[toolId];
2620
+ const domains = new Set(skillPlan.intents.map(intent => intent.domain));
2621
+ return config.enabled && (config.requiredFor.some(domain => domains.has(domain)) ||
2622
+ (config.recommendedFor ?? []).some(domain => domains.has(domain)));
2623
+ }),
2624
+ ]);
2625
+ const capabilityReport = inspectToolCapabilities({
2626
+ projectDir,
2627
+ toolIds,
2628
+ });
2629
+ const orchestrator = new ToolOrchestrator({
2630
+ projectDir,
2631
+ policy: toolPolicy,
2632
+ capabilityReport,
2633
+ evidenceStore: new ToolEvidenceStore({ projectDir, scaleDir: SCALE_DIR }),
2634
+ });
2635
+ return {
2636
+ projectDir,
2637
+ skillPlan,
2638
+ orchestrator,
2639
+ plan: orchestrator.plan({ skillPlan }),
2640
+ capabilityReport,
2641
+ };
2642
+ }
2643
+ function uniqueStrings(items) {
2644
+ return [...new Set(items)];
2645
+ }
2646
+ const toolPolicyCommand = defineCommand({
2647
+ meta: { name: 'policy', description: 'Show resolved tool orchestration policy' },
2648
+ args: {
2649
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2650
+ mode: { type: 'string', description: 'Render a starter policy mode instead of reading .scale/tools.json' },
2651
+ json: { type: 'boolean', default: false },
2652
+ },
2653
+ run({ args }) {
2654
+ const policy = args.mode
2655
+ ? JSON.parse(toolPolicyTemplate(normalizeToolMode(args.mode)))
2656
+ : loadToolPolicy(args.dir, SCALE_DIR);
2657
+ if (args.json) {
2658
+ console.log(JSON.stringify(policy, null, 2));
2659
+ return;
2660
+ }
2661
+ console.log('\nSCALE Tool Policy');
2662
+ console.log(` Mode: ${policy.mode}`);
2663
+ console.log(` Tools: ${Object.keys(policy.tools).length}`);
2664
+ for (const [id, config] of Object.entries(policy.tools)) {
2665
+ const state = config.enabled ? '[ON]' : '[OFF]';
2666
+ console.log(` ${state} ${id}: requiredFor=${config.requiredFor.join(',') || 'none'}`);
2667
+ }
2668
+ },
2669
+ });
2670
+ const toolDoctorCommand = defineCommand({
2671
+ meta: { name: 'doctor', description: 'Check skill, MCP, and CLI tool availability' },
2672
+ args: {
2673
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2674
+ tools: { type: 'string', description: 'Comma-separated tool ids to check' },
2675
+ json: { type: 'boolean', default: false },
2676
+ },
2677
+ run({ args }) {
2678
+ const report = inspectToolCapabilities({
2679
+ projectDir: args.dir,
2680
+ toolIds: parseToolIds(args.tools),
2681
+ });
2682
+ if (args.json) {
2683
+ console.log(JSON.stringify(report, null, 2));
2684
+ }
2685
+ else {
2686
+ console.log('\nSCALE Tool Doctor');
2687
+ console.log(` Installed: ${report.summary.installed}/${report.summary.total}`);
2688
+ for (const entry of report.tools) {
2689
+ console.log(` ${entry.installed ? '[OK]' : '[MISSING]'} ${entry.id}`);
2690
+ if (entry.detectedPath)
2691
+ console.log(` path: ${entry.detectedPath}`);
2692
+ if (entry.version)
2693
+ console.log(` version: ${entry.version}`);
2694
+ if (entry.missingReason)
2695
+ console.log(` reason: ${entry.missingReason}`);
2696
+ }
2697
+ }
2698
+ if (!report.ok)
2699
+ process.exitCode = 1;
2700
+ },
2701
+ });
2702
+ const toolPlanCommand = defineCommand({
2703
+ meta: { name: 'plan', description: 'Create a tool execution plan from task intent' },
2704
+ args: {
2705
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2706
+ 'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
2707
+ task: { type: 'string', required: true, description: 'Task description' },
2708
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2709
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
2710
+ services: { type: 'string', description: 'Comma-separated affected services' },
2711
+ json: { type: 'boolean', default: false },
2712
+ },
2713
+ run({ args }) {
2714
+ const result = createToolExecutionPlanFromArgs(args);
2715
+ if (args.json) {
2716
+ console.log(JSON.stringify(result.plan, null, 2));
2717
+ return;
2718
+ }
2719
+ console.log('\nSCALE Tool Plan');
2720
+ console.log(` Task: ${result.plan.taskId}`);
2721
+ console.log(` Mode: ${result.plan.mode}`);
2722
+ console.log(` Steps: ${result.plan.steps.length}`);
2723
+ for (const step of result.plan.steps) {
2724
+ console.log(` ${step.status === 'ready' ? '[READY]' : '[MISSING]'} ${step.toolId} (${step.adapter}) required=${step.required}`);
2725
+ }
2726
+ for (const blocker of result.plan.blockers)
2727
+ console.log(` [BLOCKER] ${blocker}`);
2728
+ for (const warning of result.plan.warnings)
2729
+ console.log(` [WARN] ${warning}`);
2730
+ },
2731
+ });
2732
+ const toolRunCommand = defineCommand({
2733
+ meta: { name: 'run', description: 'Run or dry-run a tool execution plan and write tool evidence' },
2734
+ args: {
2735
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2736
+ 'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
2737
+ task: { type: 'string', required: true, description: 'Task description' },
2738
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2739
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
2740
+ services: { type: 'string', description: 'Comma-separated affected services' },
2741
+ 'dry-run': { type: 'boolean', default: false, description: 'Plan and record skipped evidence without executing tools' },
2742
+ json: { type: 'boolean', default: false },
2743
+ },
2744
+ async run({ args }) {
2745
+ const result = createToolExecutionPlanFromArgs(args);
2746
+ const report = await result.orchestrator.run(result.plan, {
2747
+ dryRun: isTruthyFlag(args['dry-run']),
2748
+ });
2749
+ if (toolEvidenceRunCompletesOpenTask(report)) {
2750
+ const writer = new WorkflowArtifactWriter(SCALE_DIR);
2751
+ const current = writer.readCurrentState();
2752
+ if (current?.taskId === report.taskId) {
2753
+ writer.updateCurrentState({
2754
+ taskId: report.taskId,
2755
+ openTasks: removeWorkflowOpenTask(current.openTasks, 'tool-evidence'),
2756
+ });
2757
+ }
2758
+ }
2759
+ if (args.json) {
2760
+ console.log(JSON.stringify(report, null, 2));
2761
+ }
2762
+ else {
2763
+ console.log('\nSCALE Tool Run');
2764
+ console.log(` Task: ${report.taskId}`);
2765
+ console.log(` Dry-run: ${report.dryRun}`);
2766
+ console.log(` Evidence: ${report.evidence.length}`);
2767
+ for (const record of report.evidence) {
2768
+ console.log(` [${record.status.toUpperCase()}] ${record.tool} -> ${record.id}`);
2769
+ }
2770
+ for (const blocker of report.blockers)
2771
+ console.log(` [BLOCKER] ${blocker}`);
2772
+ for (const warning of report.warnings)
2773
+ console.log(` [WARN] ${warning}`);
2774
+ }
2775
+ if (!report.ok)
2776
+ process.exitCode = 1;
2777
+ },
2778
+ });
2779
+ const toolEvidenceCommand = defineCommand({
2780
+ meta: { name: 'evidence', description: 'Check required tool execution evidence for a task' },
2781
+ args: {
2782
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2783
+ 'task-id': { type: 'string', required: true, description: 'Task id for evidence linkage' },
2784
+ task: { type: 'string', required: true, description: 'Task description' },
2785
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2786
+ files: { type: 'string', description: 'Comma-separated changed or target files' },
2787
+ services: { type: 'string', description: 'Comma-separated affected services' },
2788
+ mode: { type: 'string', description: 'Override tool gate mode: off, advisory, evidence-required, or block' },
2789
+ 'allow-skipped': { type: 'boolean', default: false, description: 'Allow skipped/manual fallback evidence to satisfy required tools' },
2790
+ json: { type: 'boolean', default: false },
2791
+ },
2792
+ run({ args }) {
2793
+ const result = createToolExecutionPlanFromArgs(args);
2794
+ const gate = evaluateToolEvidenceGate({
2795
+ projectDir: result.projectDir,
2796
+ level: normalizeTaskArtifactLevel(args.level ?? 'M'),
2797
+ plan: result.plan,
2798
+ evidenceStore: new ToolEvidenceStore({ projectDir: result.projectDir, scaleDir: SCALE_DIR }),
2799
+ mode: args.mode ? normalizeToolMode(args.mode) : result.plan.mode,
2800
+ allowSkipped: isTruthyFlag(args['allow-skipped']),
2801
+ });
2802
+ if (args.json) {
2803
+ console.log(JSON.stringify(gate, null, 2));
2804
+ }
2805
+ else {
2806
+ console.log('\nSCALE Tool Evidence Gate');
2807
+ console.log(` Task: ${gate.taskId ?? args['task-id']}`);
2808
+ console.log(` Mode: ${gate.mode}`);
2809
+ console.log(` Complete: ${gate.complete}`);
2810
+ console.log(` Required tools: ${gate.requiredTools.join(', ') || 'none'}`);
2811
+ for (const item of gate.missing)
2812
+ console.log(` [MISSING] ${item.toolId}: ${item.reason}`);
2813
+ for (const item of gate.failed)
2814
+ console.log(` [FAILED] ${item.toolId}: ${item.reason}`);
2815
+ for (const item of gate.skipped)
2816
+ console.log(` [SKIPPED] ${item.toolId}: ${item.reason}`);
2817
+ for (const item of gate.passed)
2818
+ console.log(` [PASS] ${item.toolId}: ${item.evidenceId ?? 'evidence'}`);
2819
+ for (const warning of gate.warnings)
2820
+ console.log(` [WARN] ${warning}`);
2821
+ }
2822
+ if (gate.blocked)
2823
+ process.exitCode = 1;
2824
+ },
2825
+ });
2826
+ const tool = defineCommand({
2827
+ meta: { name: 'tool', description: 'Skills, MCP, browser, desktop, and external CLI governance' },
2828
+ subCommands: { policy: toolPolicyCommand, doctor: toolDoctorCommand, plan: toolPlanCommand, run: toolRunCommand, evidence: toolEvidenceCommand },
2089
2829
  });
2090
2830
  // ============================================================================
2091
2831
  // agent commands — Multi-Agent 协作系统 (Phase 9)
@@ -2142,9 +2882,32 @@ const agentProfiles = defineCommand({
2142
2882
  }
2143
2883
  },
2144
2884
  });
2885
+ const agentLeaders = defineCommand({
2886
+ meta: { name: 'leaders', description: 'List SCALE leader presets such as CEO and CTO' },
2887
+ args: {
2888
+ output: { type: 'string', alias: 'o', description: 'Write markdown guide to file' },
2889
+ json: { type: 'boolean', default: false },
2890
+ },
2891
+ async run({ args }) {
2892
+ const presets = listLeadershipPresets();
2893
+ if (args.json) {
2894
+ console.log(JSON.stringify(presets, null, 2));
2895
+ return;
2896
+ }
2897
+ const markdown = renderLeadershipPresetsMarkdown();
2898
+ if (args.output) {
2899
+ const outputPath = resolve(PROJECT_DIR, args.output);
2900
+ ensureDir(resolve(outputPath, '..'));
2901
+ writeFileSync(outputPath, markdown, 'utf-8');
2902
+ console.log(`[OK] 领导者角色指南已生成: ${outputPath}`);
2903
+ return;
2904
+ }
2905
+ console.log(markdown);
2906
+ },
2907
+ });
2145
2908
  const agent = defineCommand({
2146
2909
  meta: { name: 'agent', description: 'Multi-Agent system management' },
2147
- subCommands: { spawn: agentSpawn, list: agentList, profiles: agentProfiles },
2910
+ subCommands: { spawn: agentSpawn, list: agentList, profiles: agentProfiles, leaders: agentLeaders },
2148
2911
  });
2149
2912
  // ============================================================================
2150
2913
  // team commands — 团队协作 (Phase 9)
@@ -2250,6 +3013,9 @@ const main = defineCommand({
2250
3013
  status,
2251
3014
  workflow,
2252
3015
  evidence,
3016
+ diagnose,
3017
+ tdd,
3018
+ tool,
2253
3019
  skill,
2254
3020
  skills: skill,
2255
3021
  agent,