@hongmaple0820/scale-engine 0.19.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 (43) hide show
  1. package/README.en.md +17 -3
  2. package/README.md +107 -9
  3. package/dist/api/cli.js +988 -11
  4. package/dist/api/cli.js.map +1 -1
  5. package/dist/codegraph/CodeIntelligence.d.ts +135 -0
  6. package/dist/codegraph/CodeIntelligence.js +460 -0
  7. package/dist/codegraph/CodeIntelligence.js.map +1 -0
  8. package/dist/context/ContextBudget.d.ts +90 -0
  9. package/dist/context/ContextBudget.js +322 -0
  10. package/dist/context/ContextBudget.js.map +1 -0
  11. package/dist/eval/WorkflowEval.d.ts +161 -0
  12. package/dist/eval/WorkflowEval.js +379 -0
  13. package/dist/eval/WorkflowEval.js.map +1 -0
  14. package/dist/governance/GovernanceRoi.d.ts +25 -0
  15. package/dist/governance/GovernanceRoi.js +70 -0
  16. package/dist/governance/GovernanceRoi.js.map +1 -0
  17. package/dist/governance/ProgressiveGovernance.d.ts +22 -0
  18. package/dist/governance/ProgressiveGovernance.js +159 -0
  19. package/dist/governance/ProgressiveGovernance.js.map +1 -0
  20. package/dist/memory/MemoryBrain.d.ts +135 -0
  21. package/dist/memory/MemoryBrain.js +635 -0
  22. package/dist/memory/MemoryBrain.js.map +1 -0
  23. package/dist/memory/index.d.ts +1 -0
  24. package/dist/memory/index.js +1 -0
  25. package/dist/memory/index.js.map +1 -1
  26. package/dist/output/GovernanceDashboard.d.ts +57 -0
  27. package/dist/output/GovernanceDashboard.js +250 -0
  28. package/dist/output/GovernanceDashboard.js.map +1 -0
  29. package/dist/output/index.d.ts +2 -0
  30. package/dist/output/index.js +1 -0
  31. package/dist/output/index.js.map +1 -1
  32. package/dist/skills/SkillRadar.d.ts +83 -0
  33. package/dist/skills/SkillRadar.js +384 -0
  34. package/dist/skills/SkillRadar.js.map +1 -0
  35. package/dist/workflow/GovernanceTemplates.js +194 -194
  36. package/docs/CODE_INTELLIGENCE.md +138 -0
  37. package/docs/CONTEXT_BUDGET.md +87 -0
  38. package/docs/GOVERNANCE_DASHBOARD.md +69 -0
  39. package/docs/MEMORY_BRAIN.md +104 -0
  40. package/docs/README.md +16 -8
  41. package/docs/SKILL_RADAR.md +115 -0
  42. package/docs/WORKFLOW_EVAL.md +151 -0
  43. package/package.json +7 -1
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';
@@ -24,6 +27,7 @@ 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,13 +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';
52
59
  import { inspectWorkspaceSafety } from '../workflow/WorkspaceSafety.js';
53
60
  import { RuntimeEvidenceLedger, SessionLedger, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
54
- import { MemoryFabric, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, settleMemoryLearning, } from '../memory/index.js';
61
+ import { MemoryFabric, MemoryBrain, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, settleMemoryLearning, } from '../memory/index.js';
55
62
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
56
63
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
57
- import { isAbsolute, join, resolve } from 'node:path';
64
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
58
65
  import { execFileSync } from 'node:child_process';
59
66
  import { pathToFileURL } from 'node:url';
60
67
  import { SCALE_ENGINE_VERSION } from '../version.js';
@@ -919,9 +926,546 @@ const contextGrill = defineCommand({
919
926
  console.log(`\nArtifact: ${artifactPath}`);
920
927
  },
921
928
  });
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' },
940
+ args: {
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' },
945
+ json: { type: 'boolean', default: false },
946
+ },
947
+ run({ args }) {
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'),
955
+ });
956
+ const path = isTruthyFlag(args.write) ? writeContextBudgetReport(report) : undefined;
957
+ if (args.json) {
958
+ console.log(JSON.stringify({ ...report, path }, null, 2));
959
+ return;
960
+ }
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`);
967
+ }
968
+ for (const recommendation of report.recommendations)
969
+ console.log(` recommendation: ${recommendation}`);
970
+ if (path)
971
+ console.log(` wrote: ${path}`);
972
+ },
973
+ });
974
+ const contextPack = defineCommand({
975
+ meta: { name: 'pack', description: 'Build a lazy-loaded context pack for a task' },
976
+ args: {
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' },
983
+ json: { type: 'boolean', default: false },
984
+ },
985
+ run({ args }) {
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'),
996
+ });
997
+ if (args.json) {
998
+ console.log(JSON.stringify(pack, null, 2));
999
+ return;
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
+ });
1026
+ if (args.json) {
1027
+ console.log(JSON.stringify(report, null, 2));
1028
+ if (!report.ok)
1029
+ process.exitCode = 1;
1030
+ return;
1031
+ }
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}`);
1035
+ }
1036
+ if (!report.ok)
1037
+ process.exitCode = 1;
1038
+ },
1039
+ });
922
1040
  const context = defineCommand({
923
1041
  meta: { name: 'context', description: 'Context assembly' },
924
- subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill },
1042
+ subCommands: { build: contextBuild, status: contextStatus, inject: contextInject, glossary: contextGlossary, init: contextInit, grill: contextGrill, budget: contextBudget, pack: contextPack, doctor: contextDoctor },
1043
+ });
1044
+ // ============================================================================
1045
+ // codegraph command - Adapter-first code intelligence
1046
+ // ============================================================================
1047
+ const codegraphStatus = defineCommand({
1048
+ meta: { name: 'status', description: 'Inspect CodeGraph, Graphify, and fallback code intelligence providers' },
1049
+ args: {
1050
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1051
+ json: { type: 'boolean', default: false },
1052
+ },
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
+ });
1060
+ if (args.json) {
1061
+ console.log(JSON.stringify(report, null, 2));
1062
+ return;
1063
+ }
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}`);
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}`);
1072
+ },
1073
+ });
1074
+ const codegraphInit = defineCommand({
1075
+ meta: { name: 'init', description: 'Create .scale/code-intelligence.json provider configuration' },
1076
+ args: {
1077
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1078
+ force: { type: 'boolean', default: false, description: 'Overwrite existing configuration' },
1079
+ json: { type: 'boolean', default: false },
1080
+ },
1081
+ run({ args }) {
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),
1088
+ });
1089
+ if (args.json) {
1090
+ console.log(JSON.stringify(result, null, 2));
1091
+ return;
1092
+ }
1093
+ console.log(`SCALE Code Intelligence Config: ${result.written ? 'written' : 'exists'}`);
1094
+ console.log(` ${result.path}`);
1095
+ },
1096
+ });
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;
1115
+ }
1116
+ printCodeGraphReport(report);
1117
+ },
1118
+ });
1119
+ const codegraphImpact = defineCommand({
1120
+ meta: { name: 'impact', description: 'Find likely impacted files for a symbol' },
1121
+ args: {
1122
+ symbol: { type: 'string', required: true, description: 'Symbol to analyze' },
1123
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1124
+ json: { type: 'boolean', default: false },
1125
+ },
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
+ });
1134
+ if (args.json) {
1135
+ console.log(JSON.stringify(report, null, 2));
1136
+ return;
1137
+ }
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;
1161
+ }
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`);
1166
+ }
1167
+ },
1168
+ });
1169
+ const codegraphRoi = defineCommand({
1170
+ meta: { name: 'roi', description: 'Estimate exploration ROI from code intelligence or fallback query results' },
1171
+ args: {
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' },
1175
+ json: { type: 'boolean', default: false },
1176
+ },
1177
+ run({ args }) {
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
+ });
1188
+ if (args.json) {
1189
+ console.log(JSON.stringify(report, null, 2));
1190
+ return;
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}`);
1199
+ },
1200
+ });
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' },
1227
+ args: {
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' },
1231
+ json: { type: 'boolean', default: false },
1232
+ },
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));
1241
+ if (args.json) {
1242
+ console.log(JSON.stringify(result, null, 2));
1243
+ return;
1244
+ }
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}`);
1249
+ },
1250
+ });
1251
+ const evalRun = defineCommand({
1252
+ meta: { name: 'run', description: 'Run a workflow eval suite and preserve failure replay artifacts' },
1253
+ args: {
1254
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1255
+ suite: { type: 'string', default: 'workflow-baseline', description: 'Suite id or JSON path' },
1256
+ json: { type: 'boolean', default: false },
1257
+ },
1258
+ async run({ args }) {
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'),
1265
+ });
1266
+ if (args.json) {
1267
+ console.log(JSON.stringify(result, null, 2));
1268
+ if (!result.run.ok)
1269
+ process.exitCode = 1;
1270
+ return;
1271
+ }
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)
1284
+ process.exitCode = 1;
1285
+ },
1286
+ });
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 },
1294
+ },
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' },
1321
+ args: {
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' },
1325
+ json: { type: 'boolean', default: false },
1326
+ },
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');
1337
+ }
1338
+ if (args.json) {
1339
+ console.log(JSON.stringify({ runId: run.id, outputPath, markdown }, null, 2));
1340
+ return;
1341
+ }
1342
+ if (outputPath)
1343
+ console.log(`Workflow eval report written: ${outputPath}`);
1344
+ else
1345
+ console.log(markdown);
1346
+ },
1347
+ });
1348
+ const evalFailures = defineCommand({
1349
+ meta: { name: 'failures', description: 'List failure replay records for workflow improvement' },
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' },
1354
+ json: { type: 'boolean', default: false },
1355
+ },
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
+ });
1367
+ if (args.json) {
1368
+ console.log(JSON.stringify({ count: failures.length, failures }, null, 2));
1369
+ return;
1370
+ }
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}`);
1375
+ }
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;
1401
+ }
1402
+ if (failures.length === 0) {
1403
+ console.log('No failure replay records found.');
1404
+ process.exitCode = 1;
1405
+ return;
1406
+ }
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}`);
1420
+ }
1421
+ },
1422
+ });
1423
+ const evalPromoteFailure = defineCommand({
1424
+ meta: { name: 'promote-failure', description: 'Promote a failure replay into a workflow improvement candidate' },
1425
+ args: {
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 },
1429
+ },
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
+ },
925
1469
  });
926
1470
  // ============================================================================
927
1471
  // diagnose command - evidence-first debugging loop
@@ -1879,9 +2423,86 @@ const governanceDiff = defineCommand({
1879
2423
  console.log(`changed: ${item.path}`);
1880
2424
  },
1881
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
+ });
1882
2503
  const governance = defineCommand({
1883
2504
  meta: { name: 'governance', description: 'Governance template pack tools' },
1884
- subCommands: { diff: governanceDiff },
2505
+ subCommands: { diff: governanceDiff, mode: governanceModeCommand, roi: governanceRoiCommand },
1885
2506
  });
1886
2507
  // ============================================================================
1887
2508
  // assets command - Resource lifecycle governance
@@ -2283,9 +2904,46 @@ const artifactOpen = defineCommand({
2283
2904
  console.log(url);
2284
2905
  },
2285
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
+ });
2286
2944
  const artifact = defineCommand({
2287
2945
  meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
2288
- subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
2946
+ subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen, dashboard: artifactDashboard },
2289
2947
  });
2290
2948
  function normalizeThemeArg(value) {
2291
2949
  const normalized = String(value ?? 'auto').trim().toLowerCase();
@@ -2706,6 +3364,31 @@ function parseMemoryBudget(value) {
2706
3364
  }
2707
3365
  return parsed;
2708
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
+ }
2709
3392
  const memoryPack = defineCommand({
2710
3393
  meta: { name: 'pack', description: 'Build a compact context pack from runtime evidence, session events, knowledge, and graph status' },
2711
3394
  args: {
@@ -2840,9 +3523,233 @@ const memorySettle = defineCommand({
2840
3523
  console.log(`\nWrote: ${settlement.files.markdown}`);
2841
3524
  },
2842
3525
  });
3526
+ const memoryIngest = defineCommand({
3527
+ meta: { name: 'ingest', description: 'Ingest runtime evidence, learning candidates, or failure replays into the project memory brain' },
3528
+ args: {
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 },
3537
+ },
3538
+ run({ args }) {
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,
3554
+ taskId: args['task-id'],
3555
+ sessionId: args['session-id'],
3556
+ candidateId: args['candidate-id'],
3557
+ failureId: args['failure-id'],
3558
+ type,
3559
+ scope,
3560
+ });
3561
+ if (args.json) {
3562
+ console.log(JSON.stringify(report, null, 2));
3563
+ if (!report.ok)
3564
+ process.exitCode = 1;
3565
+ return;
3566
+ }
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)
3576
+ process.exitCode = 1;
3577
+ },
3578
+ });
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;
3597
+ }
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}`);
3605
+ }
3606
+ },
3607
+ });
3608
+ const memoryContradictions = defineCommand({
3609
+ meta: { name: 'contradictions', description: 'Report conflicting project memory instead of silently resolving it' },
3610
+ args: {
3611
+ json: { type: 'boolean', default: false },
3612
+ },
3613
+ run({ args }) {
3614
+ const report = memoryBrain().contradictions();
3615
+ if (args.json) {
3616
+ console.log(JSON.stringify(report, null, 2));
3617
+ if (!report.ok)
3618
+ process.exitCode = 1;
3619
+ return;
3620
+ }
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'}`);
3627
+ }
3628
+ if (!report.ok)
3629
+ process.exitCode = 1;
3630
+ },
3631
+ });
3632
+ const memoryDream = defineCommand({
3633
+ meta: { name: 'dream', description: 'Run memory maintenance: duplicates, stale memories, contradictions, and promotion candidates' },
3634
+ args: {
3635
+ json: { type: 'boolean', default: false },
3636
+ },
3637
+ run({ args }) {
3638
+ const report = memoryBrain().dream();
3639
+ if (args.json) {
3640
+ console.log(JSON.stringify(report, null, 2));
3641
+ if (!report.ok)
3642
+ process.exitCode = 1;
3643
+ return;
3644
+ }
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;
3657
+ },
3658
+ });
3659
+ const memoryPromote = defineCommand({
3660
+ meta: { name: 'promote', description: 'Promote a memory candidate to active project memory after evidence review' },
3661
+ args: {
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' },
3664
+ json: { type: 'boolean', default: false },
3665
+ },
3666
+ run({ args }) {
3667
+ let scope;
3668
+ try {
3669
+ scope = normalizeMemoryScope(args.scope);
3670
+ }
3671
+ catch (error) {
3672
+ console.error(error.message);
3673
+ process.exit(1);
3674
+ return;
3675
+ }
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;
3682
+ }
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;
3691
+ },
3692
+ });
3693
+ const memoryExport = defineCommand({
3694
+ meta: { name: 'export', description: 'Export project memory as JSONL' },
3695
+ args: {
3696
+ output: { type: 'string', alias: 'o', description: 'Output JSONL file; stdout when omitted' },
3697
+ json: { type: 'boolean', default: false },
3698
+ },
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;
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);
3724
+ if (args.json) {
3725
+ console.log(JSON.stringify(report, null, 2));
3726
+ if (!report.ok)
3727
+ process.exitCode = 1;
3728
+ return;
3729
+ }
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;
3737
+ },
3738
+ });
2843
3739
  const memory = defineCommand({
2844
- meta: { name: 'memory', description: 'Memory Fabric context packs and budget diagnostics' },
2845
- subCommands: { pack: memoryPack, doctor: memoryDoctor, settle: memorySettle },
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
+ },
2846
3753
  });
2847
3754
  // ============================================================================
2848
3755
  // out-of-scope command — 借鉴 mattpocock/skills 的 .out-of-scope/ 设计
@@ -3038,13 +3945,16 @@ const skillPlanCommand = defineCommand({
3038
3945
  const skillDoctorCommand = defineCommand({
3039
3946
  meta: { name: 'doctor', description: 'Check workflow skill installation status' },
3040
3947
  args: {
3041
- 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' },
3042
3950
  json: { type: 'boolean', default: false, description: 'Output skill doctor report as JSON' },
3043
3951
  },
3044
3952
  run({ args }) {
3045
- 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;
3046
3956
  if (args.json) {
3047
- console.log(JSON.stringify(report, null, 2));
3957
+ console.log(JSON.stringify(supplyChain ? { installation: report, supplyChain } : report, null, 2));
3048
3958
  return;
3049
3959
  }
3050
3960
  console.log('\nSCALE Skill Doctor');
@@ -3056,7 +3966,18 @@ const skillDoctorCommand = defineCommand({
3056
3966
  if (!skill.installed)
3057
3967
  console.log(` install: ${skill.installCommand}`);
3058
3968
  }
3059
- 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)
3060
3981
  process.exitCode = 1;
3061
3982
  },
3062
3983
  });
@@ -3168,6 +4089,59 @@ const skillSafetyCommand = defineCommand({
3168
4089
  process.exitCode = 1;
3169
4090
  },
3170
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
+ });
3171
4145
  const skillRecommendCommand = defineCommand({
3172
4146
  meta: { name: 'recommend', description: 'Recommend a composable skill workflow for a task' },
3173
4147
  args: {
@@ -3202,6 +4176,7 @@ const skill = defineCommand({
3202
4176
  check: skillCheckCommand,
3203
4177
  repo: skillRepoCommand,
3204
4178
  safety: skillSafetyCommand,
4179
+ radar: skillRadarCommand,
3205
4180
  recommend: skillRecommendCommand,
3206
4181
  },
3207
4182
  });
@@ -3630,6 +4605,8 @@ const main = defineCommand({
3630
4605
  stats,
3631
4606
  preflight,
3632
4607
  governance,
4608
+ codegraph,
4609
+ eval: evalCommand,
3633
4610
  artifact,
3634
4611
  assets,
3635
4612
  standards,