@hongmaple0820/scale-engine 0.17.0 → 0.19.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 (94) hide show
  1. package/README.en.md +296 -237
  2. package/README.md +157 -63
  3. package/dist/api/cli.js +661 -33
  4. package/dist/api/cli.js.map +1 -1
  5. package/dist/api/doctor.d.ts +5 -1
  6. package/dist/api/doctor.js +130 -1
  7. package/dist/api/doctor.js.map +1 -1
  8. package/dist/api/quickstart.d.ts +3 -0
  9. package/dist/api/quickstart.js +12 -4
  10. package/dist/api/quickstart.js.map +1 -1
  11. package/dist/cli/phaseCommands.js +7 -0
  12. package/dist/cli/phaseCommands.js.map +1 -1
  13. package/dist/core/logger.js +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +4 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/memory/MemoryFabric.d.ts +118 -0
  18. package/dist/memory/MemoryFabric.js +281 -0
  19. package/dist/memory/MemoryFabric.js.map +1 -0
  20. package/dist/memory/MemoryLearning.d.ts +61 -0
  21. package/dist/memory/MemoryLearning.js +203 -0
  22. package/dist/memory/MemoryLearning.js.map +1 -0
  23. package/dist/memory/index.d.ts +2 -0
  24. package/dist/memory/index.js +3 -0
  25. package/dist/memory/index.js.map +1 -0
  26. package/dist/output/HTMLArtifactLayer.d.ts +97 -0
  27. package/dist/output/HTMLArtifactLayer.js +576 -0
  28. package/dist/output/HTMLArtifactLayer.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/prompts/VibeTemplateGallery.js +121 -121
  33. package/dist/runtime/FinalReportGuard.d.ts +16 -0
  34. package/dist/runtime/FinalReportGuard.js +14 -0
  35. package/dist/runtime/FinalReportGuard.js.map +1 -0
  36. package/dist/runtime/RuntimeDoctor.d.ts +23 -0
  37. package/dist/runtime/RuntimeDoctor.js +151 -0
  38. package/dist/runtime/RuntimeDoctor.js.map +1 -0
  39. package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
  40. package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
  41. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
  42. package/dist/runtime/SessionLedger.d.ts +53 -0
  43. package/dist/runtime/SessionLedger.js +104 -0
  44. package/dist/runtime/SessionLedger.js.map +1 -0
  45. package/dist/runtime/index.d.ts +4 -0
  46. package/dist/runtime/index.js +5 -0
  47. package/dist/runtime/index.js.map +1 -0
  48. package/dist/skills/routing/SkillGate.js +26 -2
  49. package/dist/skills/routing/SkillGate.js.map +1 -1
  50. package/dist/skills/routing/SkillPolicy.js +2 -2
  51. package/dist/skills/routing/SkillPolicy.js.map +1 -1
  52. package/dist/tools/ToolCapabilityRegistry.d.ts +1 -1
  53. package/dist/tools/ToolCapabilityRegistry.js +4 -4
  54. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  55. package/dist/tools/ToolOrchestrator.js +5 -1
  56. package/dist/tools/ToolOrchestrator.js.map +1 -1
  57. package/dist/workflow/EngineeringStandards.js +69 -66
  58. package/dist/workflow/EngineeringStandards.js.map +1 -1
  59. package/dist/workflow/GovernanceTemplatePacks.d.ts +1 -1
  60. package/dist/workflow/GovernanceTemplatePacks.js +137 -79
  61. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  62. package/dist/workflow/GovernanceTemplates.d.ts +1 -1
  63. package/dist/workflow/GovernanceTemplates.js +494 -199
  64. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  65. package/dist/workflow/ResourceGovernance.js +29 -19
  66. package/dist/workflow/ResourceGovernance.js.map +1 -1
  67. package/dist/workflow/VerificationCommands.d.ts +11 -0
  68. package/dist/workflow/VerificationCommands.js +2 -0
  69. package/dist/workflow/VerificationCommands.js.map +1 -1
  70. package/dist/workflow/VerificationProfile.d.ts +2 -1
  71. package/dist/workflow/VerificationProfile.js +3 -0
  72. package/dist/workflow/VerificationProfile.js.map +1 -1
  73. package/dist/workflow/WorkflowArtifactWriter.js +2 -1
  74. package/dist/workflow/WorkflowArtifactWriter.js.map +1 -1
  75. package/dist/workflow/WorkflowEngine.js +4 -1
  76. package/dist/workflow/WorkflowEngine.js.map +1 -1
  77. package/dist/workflow/WorkspaceSafety.d.ts +9 -0
  78. package/dist/workflow/WorkspaceSafety.js +49 -0
  79. package/dist/workflow/WorkspaceSafety.js.map +1 -0
  80. package/dist/workflow/gates/GateSystem.d.ts +12 -1
  81. package/dist/workflow/gates/GateSystem.js +106 -0
  82. package/dist/workflow/gates/GateSystem.js.map +1 -1
  83. package/dist/workflow/types.d.ts +1 -1
  84. package/docs/MEMORY_FABRIC.md +107 -0
  85. package/docs/README.md +68 -0
  86. package/docs/RUNTIME_EVIDENCE.md +101 -0
  87. package/docs/start/README.md +42 -0
  88. package/docs/start/agent-governance-demo.md +107 -0
  89. package/docs/start/quickstart.md +127 -0
  90. package/examples/demo-projects/agent-governance-demo/README.md +37 -0
  91. package/examples/demo-projects/agent-governance-demo/package.json +16 -0
  92. package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
  93. package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
  94. package/package.json +8 -3
package/dist/api/cli.js CHANGED
@@ -20,7 +20,7 @@ import { createSkillPlan, evaluateSkillGate, loadSkillRoutingPolicy, skillPlanMa
20
20
  import { createAdapter, SUPPORTED_AGENTS } from '../adapters/index.js';
21
21
  import { LessonExtractor, RuleProposer, HookGenerator, EvolutionEngine } from '../evolution/EvolutionEngine.js';
22
22
  import { Doctor } from './doctor.js';
23
- import { quickStart, detectPlatform } from './quickstart.js';
23
+ import { quickStart, detectPlatform, governanceNextSteps } from './quickstart.js';
24
24
  import { SkillDiscovery } from '../skills/SkillDiscovery.js';
25
25
  import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
26
26
  import { evaluateSkillInstallSafety, listSkillRepositoryEntries, recommendSkillWorkflow, renderSkillRepositoryMarkdown, } from '../skills/SkillRepository.js';
@@ -30,7 +30,7 @@ import { EvidenceStore } from '../workflow/EvidenceStore.js';
30
30
  import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
31
31
  import { ReviewStore } from '../workflow/ReviewStore.js';
32
32
  import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
33
- import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
33
+ import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
34
34
  import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
35
35
  import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
36
36
  import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
@@ -47,11 +47,16 @@ import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
47
47
  import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
48
48
  import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
49
49
  import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
50
+ import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
50
51
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
52
+ import { inspectWorkspaceSafety } from '../workflow/WorkspaceSafety.js';
53
+ import { RuntimeEvidenceLedger, SessionLedger, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
54
+ import { MemoryFabric, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, settleMemoryLearning, } from '../memory/index.js';
51
55
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
52
56
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
53
- import { join, resolve } from 'node:path';
57
+ import { isAbsolute, join, resolve } from 'node:path';
54
58
  import { execFileSync } from 'node:child_process';
59
+ import { pathToFileURL } from 'node:url';
55
60
  import { SCALE_ENGINE_VERSION } from '../version.js';
56
61
  // ============================================================================
57
62
  // Engine bootstrap (单例 + lazy init)
@@ -96,6 +101,26 @@ function gatesForPreflightProfile(profile) {
96
101
  return ['G3', 'G0', 'G4', 'G5'];
97
102
  return ['G3', 'G0', 'G4', 'G5', 'G6', 'G7'];
98
103
  }
104
+ function shouldSkipPreflightCommandTargets(resolved, args) {
105
+ if (!resolved.matrix)
106
+ return false;
107
+ const requestedService = String(args.service ?? '').trim();
108
+ if (requestedService && requestedService !== 'all')
109
+ return false;
110
+ const hasCommandOverrides = [
111
+ args['build-cmd'],
112
+ args['lint-cmd'],
113
+ args['test-cmd'],
114
+ args['coverage-cmd'],
115
+ ].some(value => typeof value === 'string' && value.trim().length > 0);
116
+ if (hasCommandOverrides)
117
+ return false;
118
+ const profile = resolved.matrix.profiles?.[resolved.profileName];
119
+ const hasProfileCommands = Object.values(profile?.commands ?? {})
120
+ .some(value => typeof value === 'string' && value.trim().length > 0);
121
+ const hasServices = (resolved.matrix.services ?? []).length > 0;
122
+ return !hasServices && !hasProfileCommands;
123
+ }
99
124
  function evaluateEngineeringStandardsGate(options) {
100
125
  const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
101
126
  if (mode === 'off') {
@@ -109,15 +134,15 @@ function evaluateEngineeringStandardsGate(options) {
109
134
  }
110
135
  const settlement = options.settle && options.artifactsDir
111
136
  ? settleEngineeringStandards({
112
- projectDir: PROJECT_DIR,
113
- scaleDir: SCALE_DIR,
137
+ projectDir: options.projectDir ?? PROJECT_DIR,
138
+ scaleDir: options.scaleDir ?? SCALE_DIR,
114
139
  taskId: options.taskId,
115
140
  artifactsDir: options.artifactsDir,
116
141
  })
117
142
  : undefined;
118
143
  const doctor = settlement?.doctor ?? doctorEngineeringStandards({
119
- projectDir: PROJECT_DIR,
120
- scaleDir: SCALE_DIR,
144
+ projectDir: options.projectDir ?? PROJECT_DIR,
145
+ scaleDir: options.scaleDir ?? SCALE_DIR,
121
146
  });
122
147
  return {
123
148
  mode,
@@ -129,6 +154,16 @@ function evaluateEngineeringStandardsGate(options) {
129
154
  standardsImpactPath: settlement?.standardsImpactPath,
130
155
  };
131
156
  }
157
+ function skippedEngineeringStandardsGate(reason, policy) {
158
+ void reason;
159
+ return {
160
+ mode: normalizeEngineeringStandardsGateMode(policy.engineeringStandardsGate),
161
+ checked: false,
162
+ blocked: false,
163
+ ok: true,
164
+ findings: [],
165
+ };
166
+ }
132
167
  function normalizeEngineeringStandardsGateMode(value) {
133
168
  return value === 'off' || value === 'block' ? value : 'warn';
134
169
  }
@@ -172,6 +207,23 @@ function createEngine() {
172
207
  });
173
208
  return { eventBus, store, fsm, gateway, roleGate, kb, ctx, fsmAgentBridge, workflowEngine };
174
209
  }
210
+ function resolveScaleDirForProject(projectDir) {
211
+ return isAbsolute(SCALE_DIR) ? SCALE_DIR : join(projectDir, SCALE_DIR);
212
+ }
213
+ function createVerificationWorkflowEngine(scaleDir) {
214
+ ensureDir(scaleDir);
215
+ const eventBus = new EventBus({ eventsDir: join(scaleDir, 'events') });
216
+ const capabilityRegistry = new CapabilityRegistry(eventBus);
217
+ const skillRegistry = new SkillRegistry(eventBus);
218
+ registerCoreSkills(skillRegistry);
219
+ registerExternalSkills(skillRegistry, eventBus);
220
+ return new WorkflowEngine({
221
+ eventBus,
222
+ capabilityRegistry,
223
+ skillRegistry,
224
+ scaleDir,
225
+ });
226
+ }
175
227
  // ============================================================================
176
228
  // session commands
177
229
  // ============================================================================
@@ -1357,6 +1409,7 @@ function normalizeWorkspaceTopologyKind(value) {
1357
1409
  const preflight = defineCommand({
1358
1410
  meta: { name: 'preflight', description: 'Run service-aware verification without a task artifact' },
1359
1411
  args: {
1412
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1360
1413
  'build-cmd': { type: 'string', description: 'Override build command' },
1361
1414
  'lint-cmd': { type: 'string', description: 'Override lint command' },
1362
1415
  'test-cmd': { type: 'string', description: 'Override test command' },
@@ -1369,18 +1422,32 @@ const preflight = defineCommand({
1369
1422
  json: { type: 'boolean', default: false },
1370
1423
  },
1371
1424
  async run({ args }) {
1372
- const { workflowEngine } = getEngine();
1425
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1426
+ const scaleDir = resolveScaleDirForProject(projectDir);
1427
+ const workflowEngine = createVerificationWorkflowEngine(scaleDir);
1373
1428
  const preflightProfile = normalizePreflightProfile(args['preflight-profile']);
1374
- const gateStages = gatesForPreflightProfile(preflightProfile);
1375
1429
  const resolved = resolveVerificationTargets({
1376
- projectDir: PROJECT_DIR,
1377
- scaleDir: SCALE_DIR,
1430
+ projectDir,
1431
+ scaleDir,
1378
1432
  profile: args.profile,
1379
1433
  service: args.service,
1380
1434
  });
1381
- const engineeringStandards = evaluateEngineeringStandardsGate({
1382
- policy: resolved.policy,
1383
- });
1435
+ let gateStages = gatesForPreflightProfile(preflightProfile);
1436
+ if (resolved.targets.some(target => target.config.smoke)) {
1437
+ gateStages = ['G8'];
1438
+ }
1439
+ const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
1440
+ if (commandTargetsSkipped) {
1441
+ resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
1442
+ }
1443
+ const workspaceSafety = inspectWorkspaceSafety(projectDir);
1444
+ const engineeringStandards = workspaceSafety.blocked
1445
+ ? skippedEngineeringStandardsGate('Workspace has unresolved git conflicts; resolve them before standards scanning.', resolved.policy)
1446
+ : evaluateEngineeringStandardsGate({
1447
+ policy: resolved.policy,
1448
+ projectDir,
1449
+ scaleDir,
1450
+ });
1384
1451
  const targetResults = [];
1385
1452
  if (!args.json) {
1386
1453
  console.log('\nSCALE Preflight');
@@ -1389,6 +1456,9 @@ const preflight = defineCommand({
1389
1456
  console.log(` Profile: ${resolved.profileName}`);
1390
1457
  console.log(` Preflight profile: ${preflightProfile}`);
1391
1458
  console.log(` Gates: ${gateStages.join(', ')}`);
1459
+ if (workspaceSafety.blocked) {
1460
+ console.log(` Workspace safety: BLOCKED - ${workspaceSafety.message}`);
1461
+ }
1392
1462
  if (engineeringStandards.checked) {
1393
1463
  const status = engineeringStandards.blocked ? 'BLOCKED' : engineeringStandards.ok ? 'OK' : 'WARN';
1394
1464
  console.log(` Engineering standards: ${status} (${engineeringStandards.mode})`);
@@ -1397,7 +1467,7 @@ const preflight = defineCommand({
1397
1467
  console.log(' Engineering standards: skipped');
1398
1468
  }
1399
1469
  }
1400
- for (const target of resolved.targets) {
1470
+ for (const target of commandTargetsSkipped || workspaceSafety.blocked ? [] : resolved.targets) {
1401
1471
  if (!args.json) {
1402
1472
  const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
1403
1473
  console.log(`\n Target: ${label}`);
@@ -1408,6 +1478,12 @@ const preflight = defineCommand({
1408
1478
  lint: args['lint-cmd'] ?? target.config.lint,
1409
1479
  test: args['test-cmd'] ?? target.config.test,
1410
1480
  coverage: args['coverage-cmd'] ?? target.config.coverage,
1481
+ smoke: target.config.smoke,
1482
+ runtimeEvidence: {
1483
+ projectDir,
1484
+ scaleDir,
1485
+ profile: resolved.profileName,
1486
+ },
1411
1487
  tddEvidence: args['tdd-evidence'],
1412
1488
  tddStrict: isTruthyFlag(args['tdd-strict']),
1413
1489
  gates: gateStages,
@@ -1415,7 +1491,7 @@ const preflight = defineCommand({
1415
1491
  const passed = gates.every(gate => gate.passed);
1416
1492
  targetResults.push({
1417
1493
  service: target.service?.name,
1418
- cwd: target.config.cwd ?? PROJECT_DIR,
1494
+ cwd: target.config.cwd ?? projectDir,
1419
1495
  gates,
1420
1496
  passed,
1421
1497
  });
@@ -1427,8 +1503,8 @@ const preflight = defineCommand({
1427
1503
  }
1428
1504
  }
1429
1505
  }
1430
- const passed = targetResults.length > 0 &&
1431
- targetResults.every(target => target.passed) &&
1506
+ const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
1507
+ !workspaceSafety.blocked &&
1432
1508
  !engineeringStandards.blocked;
1433
1509
  const result = {
1434
1510
  phase: 'PREFLIGHT',
@@ -1437,8 +1513,10 @@ const preflight = defineCommand({
1437
1513
  gates: gateStages,
1438
1514
  services: targetResults.map(target => target.service).filter(Boolean),
1439
1515
  policy: resolved.policy,
1516
+ workspaceSafety,
1440
1517
  engineeringStandards,
1441
1518
  targets: targetResults,
1519
+ commandTargetsSkipped,
1442
1520
  passed,
1443
1521
  };
1444
1522
  if (args.json) {
@@ -1471,7 +1549,7 @@ const status = defineCommand({
1471
1549
  const latestTask = tasks[0];
1472
1550
  const taskPayload = latestTask?.payload;
1473
1551
  const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1474
- const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
1552
+ const currentOpenTasks = workflowState?.openTasks ?? [];
1475
1553
  const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
1476
1554
  const blockers = [];
1477
1555
  const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
@@ -1487,16 +1565,16 @@ const status = defineCommand({
1487
1565
  blockers.push(`Task ${latestTask.id} has no persisted review evidence`);
1488
1566
  }
1489
1567
  const nextCommand = (() => {
1568
+ if (nextOpenTask?.kind === 'command')
1569
+ return nextOpenTask.value;
1570
+ if (nextOpenTask?.kind === 'blocker')
1571
+ return `Resolve workflow blocker: ${nextOpenTask.value}`;
1490
1572
  if (!specs[0])
1491
1573
  return 'scale define "<feature>" --description "<what to build>"';
1492
1574
  if (!plans[0])
1493
1575
  return `scale plan ${specs[0].id}`;
1494
1576
  if (!latestTask)
1495
1577
  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}`;
1500
1578
  if (!taskPayload?.verificationEvidenceIds?.length)
1501
1579
  return `scale verify ${latestTask.id}`;
1502
1580
  if (latestTask.status !== 'COMPLETED')
@@ -1543,7 +1621,7 @@ const status = defineCommand({
1543
1621
  level: workflowState.level,
1544
1622
  phase: workflowState.phase,
1545
1623
  artifactsDir: workflowState.artifactsDir,
1546
- openTasks: workflowState.openTasks,
1624
+ openTasks: workflowState.openTasks ?? [],
1547
1625
  skillIntents: workflowState.skillIntents,
1548
1626
  } : null,
1549
1627
  blockers,
@@ -1575,7 +1653,7 @@ const status = defineCommand({
1575
1653
  for (const blocker of blockers)
1576
1654
  console.log(` - ${blocker}`);
1577
1655
  }
1578
- if (result.workflowState?.openTasks.length) {
1656
+ if ((result.workflowState?.openTasks.length ?? 0) > 0) {
1579
1657
  console.log('\nOpen Tasks:');
1580
1658
  for (const task of result.workflowState.openTasks)
1581
1659
  console.log(` - ${task}`);
@@ -1678,8 +1756,8 @@ const init = defineCommand({
1678
1756
  console.log(` Data dir: ${result.scaleDir}`);
1679
1757
  console.log(` Scenario: ${scenarioMode}`);
1680
1758
  console.log(`\n📋 Next steps:`);
1681
- console.log(` → scale doctor`);
1682
- console.log(` → scale create Spec "<feature name>"`);
1759
+ for (const step of governanceNextSteps())
1760
+ console.log(` → ${step}`);
1683
1761
  return;
1684
1762
  }
1685
1763
  // One-click quick start mode
@@ -1689,7 +1767,7 @@ const init = defineCommand({
1689
1767
  const detection = qsResult.success ? undefined : detectPlatform(args.dir);
1690
1768
  console.log(JSON.stringify({
1691
1769
  ok: qsResult.success,
1692
- mode: 'quick',
1770
+ mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
1693
1771
  platform: qsResult.platform,
1694
1772
  created: qsResult.created,
1695
1773
  skipped: qsResult.skipped,
@@ -1701,8 +1779,11 @@ const init = defineCommand({
1701
1779
  }, null, 2));
1702
1780
  return;
1703
1781
  }
1704
- if (qsResult.success && qsResult.platform) {
1705
- console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
1782
+ if (qsResult.success) {
1783
+ if (!qsResult.platform)
1784
+ console.log(`\nSCALE governance templates initialized`);
1785
+ else
1786
+ console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
1706
1787
  console.log(`\n📁 Created (${qsResult.created.length}):`);
1707
1788
  for (const f of qsResult.created)
1708
1789
  console.log(` + ${f}`);
@@ -1748,7 +1829,7 @@ const init = defineCommand({
1748
1829
  scaleDir: result.scaleDir,
1749
1830
  created: result.created,
1750
1831
  skipped: result.skipped,
1751
- nextSteps: ['scale doctor', 'scale create Spec "<feature name>"'],
1832
+ nextSteps: governanceNextSteps(),
1752
1833
  }, null, 2));
1753
1834
  return;
1754
1835
  }
@@ -1765,8 +1846,8 @@ const init = defineCommand({
1765
1846
  console.log(`\n📖 Knowledge: ${result.knowledgeDocPath}`);
1766
1847
  console.log(`\n📂 Data dir: ${result.scaleDir}`);
1767
1848
  console.log(`\n📋 Next steps:`);
1768
- console.log(` → scale doctor`);
1769
- console.log(` → scale create Spec "<feature name>"`);
1849
+ for (const step of governanceNextSteps())
1850
+ console.log(` → ${step}`);
1770
1851
  },
1771
1852
  });
1772
1853
  // ============================================================================
@@ -2056,6 +2137,182 @@ const standards = defineCommand({
2056
2137
  subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
2057
2138
  });
2058
2139
  // ============================================================================
2140
+ // artifact command - Derived HTML artifacts for human review
2141
+ // ============================================================================
2142
+ const artifactRender = defineCommand({
2143
+ meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
2144
+ args: {
2145
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2146
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2147
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2148
+ type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
2149
+ source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
2150
+ theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
2151
+ lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
2152
+ title: { type: 'string', description: 'HTML document title override' },
2153
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2154
+ },
2155
+ run({ args }) {
2156
+ const result = renderHtmlArtifact({
2157
+ projectDir: args.dir,
2158
+ taskId: args['task-id'],
2159
+ artifactDir: args['artifact-dir'],
2160
+ type: String(args.type ?? 'release-report'),
2161
+ sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
2162
+ theme: normalizeThemeArg(args.theme),
2163
+ lang: normalizeLangArg(args.lang),
2164
+ title: typeof args.title === 'string' ? args.title : undefined,
2165
+ });
2166
+ if (args.json) {
2167
+ console.log(JSON.stringify(result, null, 2));
2168
+ return;
2169
+ }
2170
+ console.log('SCALE HTML Artifact Render');
2171
+ console.log(` Type: ${result.type}`);
2172
+ console.log(` HTML: ${result.outputPath}`);
2173
+ console.log(` Index: ${result.indexPath}`);
2174
+ console.log(` Manifest: ${result.manifestPath}`);
2175
+ if (result.missingSources.length > 0) {
2176
+ console.log(` Missing sources: ${result.missingSources.join(', ')}`);
2177
+ }
2178
+ },
2179
+ });
2180
+ const artifactDoctor = defineCommand({
2181
+ meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
2182
+ args: {
2183
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2184
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2185
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2186
+ type: { type: 'string', description: 'Optional HTML artifact type to check' },
2187
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2188
+ },
2189
+ run({ args }) {
2190
+ const report = doctorHtmlArtifacts({
2191
+ projectDir: args.dir,
2192
+ taskId: args['task-id'],
2193
+ artifactDir: args['artifact-dir'],
2194
+ type: typeof args.type === 'string' ? args.type : undefined,
2195
+ });
2196
+ if (args.json) {
2197
+ console.log(JSON.stringify(report, null, 2));
2198
+ if (!report.ok)
2199
+ process.exitCode = 1;
2200
+ return;
2201
+ }
2202
+ console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2203
+ console.log(` Manifest: ${report.manifestPath}`);
2204
+ console.log(` Artifacts: ${report.artifacts.length}`);
2205
+ if (report.findings.length === 0) {
2206
+ console.log(' No HTML artifact findings.');
2207
+ }
2208
+ else {
2209
+ for (const finding of report.findings) {
2210
+ const path = finding.path ? ` ${finding.path}` : '';
2211
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2212
+ if (finding.fix)
2213
+ console.log(` fix: ${finding.fix}`);
2214
+ }
2215
+ }
2216
+ if (!report.ok)
2217
+ process.exitCode = 1;
2218
+ },
2219
+ });
2220
+ const artifactSettle = defineCommand({
2221
+ meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
2222
+ args: {
2223
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2224
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2225
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2226
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2227
+ },
2228
+ run({ args }) {
2229
+ const report = settleHtmlArtifacts({
2230
+ projectDir: args.dir,
2231
+ taskId: args['task-id'],
2232
+ artifactDir: args['artifact-dir'],
2233
+ });
2234
+ if (args.json) {
2235
+ console.log(JSON.stringify(report, null, 2));
2236
+ if (!report.ok)
2237
+ process.exitCode = 1;
2238
+ return;
2239
+ }
2240
+ console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2241
+ console.log(` HTML impact: ${report.htmlImpactPath}`);
2242
+ for (const finding of report.doctor.findings) {
2243
+ const path = finding.path ? ` ${finding.path}` : '';
2244
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2245
+ }
2246
+ if (!report.ok)
2247
+ process.exitCode = 1;
2248
+ },
2249
+ });
2250
+ const artifactOpen = defineCommand({
2251
+ meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
2252
+ args: {
2253
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2254
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2255
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2256
+ type: { type: 'string', description: 'Optional HTML artifact type to open' },
2257
+ 'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
2258
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2259
+ },
2260
+ run({ args }) {
2261
+ const path = resolveHtmlArtifactForOpen({
2262
+ projectDir: args.dir,
2263
+ taskId: args['task-id'],
2264
+ artifactDir: args['artifact-dir'],
2265
+ type: typeof args.type === 'string' ? args.type : undefined,
2266
+ });
2267
+ const url = pathToFileURL(path).toString();
2268
+ const exists = existsSync(path);
2269
+ if (!args['print-only'] && exists)
2270
+ launchLocalFile(path);
2271
+ const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
2272
+ if (args.json) {
2273
+ console.log(JSON.stringify(output, null, 2));
2274
+ if (!exists)
2275
+ process.exitCode = 1;
2276
+ return;
2277
+ }
2278
+ if (!exists) {
2279
+ console.log(`HTML artifact not found: ${path}`);
2280
+ process.exitCode = 1;
2281
+ return;
2282
+ }
2283
+ console.log(url);
2284
+ },
2285
+ });
2286
+ const artifact = defineCommand({
2287
+ meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
2288
+ subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
2289
+ });
2290
+ function normalizeThemeArg(value) {
2291
+ const normalized = String(value ?? 'auto').trim().toLowerCase();
2292
+ if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
2293
+ return normalized;
2294
+ return 'auto';
2295
+ }
2296
+ function normalizeLangArg(value) {
2297
+ return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
2298
+ }
2299
+ function launchLocalFile(path) {
2300
+ try {
2301
+ if (process.platform === 'win32') {
2302
+ execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
2303
+ }
2304
+ else if (process.platform === 'darwin') {
2305
+ execFileSync('open', [path], { stdio: 'ignore' });
2306
+ }
2307
+ else {
2308
+ execFileSync('xdg-open', [path], { stdio: 'ignore' });
2309
+ }
2310
+ }
2311
+ catch {
2312
+ // Opening is convenience-only; artifact doctor/render remains the source of truth.
2313
+ }
2314
+ }
2315
+ // ============================================================================
2059
2316
  // evolve command
2060
2317
  // ============================================================================
2061
2318
  const evolve = defineCommand({
@@ -2221,6 +2478,373 @@ const evidence = defineCommand({
2221
2478
  subCommands: { list: evidenceList, show: evidenceShow },
2222
2479
  });
2223
2480
  // ============================================================================
2481
+ // runtime command - session ledger + completion evidence
2482
+ // ============================================================================
2483
+ function normalizeRuntimeEvidenceKind(value) {
2484
+ const normalized = String(value ?? 'command').trim();
2485
+ const allowed = ['command', 'gate', 'tool', 'skill', 'mcp', 'browser', 'desktop', 'manual', 'final-report'];
2486
+ if (allowed.includes(normalized))
2487
+ return normalized;
2488
+ throw new Error(`Invalid runtime evidence kind "${normalized}"; expected ${allowed.join(', ')}.`);
2489
+ }
2490
+ function normalizeRuntimeEvidenceStatus(value) {
2491
+ const normalized = String(value ?? '').trim();
2492
+ if (normalized === 'passed' || normalized === 'failed' || normalized === 'skipped')
2493
+ return normalized;
2494
+ throw new Error(`Invalid runtime evidence status "${normalized}"; expected passed, failed, or skipped.`);
2495
+ }
2496
+ function normalizeRuntimeSessionStatus(value) {
2497
+ const normalized = String(value ?? 'completed').trim();
2498
+ if (normalized === 'active' || normalized === 'completed' || normalized === 'failed' || normalized === 'abandoned')
2499
+ return normalized;
2500
+ throw new Error(`Invalid runtime session status "${normalized}"; expected active, completed, failed, or abandoned.`);
2501
+ }
2502
+ const runtimeStart = defineCommand({
2503
+ meta: { name: 'start', description: 'Start a runtime session ledger' },
2504
+ args: {
2505
+ 'session-id': { type: 'string', description: 'Session id; generated when omitted' },
2506
+ 'task-id': { type: 'string', description: 'Task id linked to this session' },
2507
+ agent: { type: 'string', description: 'Agent name' },
2508
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2509
+ summary: { type: 'string', description: 'Short session summary' },
2510
+ json: { type: 'boolean', default: false },
2511
+ },
2512
+ run({ args }) {
2513
+ const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
2514
+ const session = ledger.start({
2515
+ sessionId: args['session-id'],
2516
+ taskId: args['task-id'],
2517
+ agent: args.agent,
2518
+ level: normalizeTaskArtifactLevel(args.level),
2519
+ summary: args.summary,
2520
+ });
2521
+ if (args.json) {
2522
+ console.log(JSON.stringify(session, null, 2));
2523
+ return;
2524
+ }
2525
+ console.log(`Runtime session started: ${session.sessionId}`);
2526
+ if (session.taskId)
2527
+ console.log(` Task: ${session.taskId}`);
2528
+ if (session.level)
2529
+ console.log(` Level: ${session.level}`);
2530
+ console.log(` Events: ${ledger.sessionFile(session.sessionId)}`);
2531
+ },
2532
+ });
2533
+ const runtimeEnd = defineCommand({
2534
+ meta: { name: 'end', description: 'End the current or named runtime session' },
2535
+ args: {
2536
+ 'session-id': { type: 'string', description: 'Session id; current session is used when omitted' },
2537
+ status: { type: 'string', default: 'completed', description: 'completed, failed, or abandoned' },
2538
+ summary: { type: 'string', description: 'Completion summary' },
2539
+ json: { type: 'boolean', default: false },
2540
+ },
2541
+ run({ args }) {
2542
+ const ledger = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
2543
+ const sessionId = args['session-id'] ?? ledger.current()?.sessionId;
2544
+ if (!sessionId) {
2545
+ console.error('No runtime session id provided and no current runtime session exists.');
2546
+ process.exit(1);
2547
+ }
2548
+ const session = ledger.end(sessionId, normalizeRuntimeSessionStatus(args.status), args.summary);
2549
+ if (args.json) {
2550
+ console.log(JSON.stringify(session, null, 2));
2551
+ return;
2552
+ }
2553
+ console.log(`Runtime session ended: ${session.sessionId}`);
2554
+ console.log(` Status: ${session.status}`);
2555
+ },
2556
+ });
2557
+ const runtimeRecord = defineCommand({
2558
+ meta: { name: 'record', description: 'Record command, gate, tool, browser, skill, or manual runtime evidence' },
2559
+ args: {
2560
+ 'task-id': { type: 'string', description: 'Task id linked to this evidence' },
2561
+ 'session-id': { type: 'string', description: 'Session id linked to this evidence' },
2562
+ kind: { type: 'string', default: 'command', description: 'command, gate, tool, skill, mcp, browser, desktop, manual, final-report' },
2563
+ title: { type: 'string', required: true, description: 'Evidence title' },
2564
+ status: { type: 'string', required: true, description: 'passed, failed, or skipped' },
2565
+ command: { type: 'string', description: 'Exact command or tool invocation, with secrets redacted by SCALE' },
2566
+ 'exit-code': { type: 'string', description: 'Exit code when applicable' },
2567
+ summary: { type: 'string', required: true, description: 'Short output summary' },
2568
+ artifacts: { type: 'string', description: 'Comma-separated artifact paths' },
2569
+ 'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
2570
+ json: { type: 'boolean', default: false },
2571
+ },
2572
+ run({ args }) {
2573
+ const current = new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).current();
2574
+ let metadata = {};
2575
+ try {
2576
+ metadata = JSON.parse(String(args['metadata-json'] ?? '{}'));
2577
+ }
2578
+ catch {
2579
+ console.error('--metadata-json must be valid JSON.');
2580
+ process.exit(1);
2581
+ }
2582
+ const exitCode = args['exit-code'] === undefined || args['exit-code'] === ''
2583
+ ? undefined
2584
+ : Number.parseInt(String(args['exit-code']), 10);
2585
+ if (exitCode !== undefined && Number.isNaN(exitCode)) {
2586
+ console.error('--exit-code must be a number.');
2587
+ process.exit(1);
2588
+ }
2589
+ const ledger = new RuntimeEvidenceLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR });
2590
+ const record = ledger.record({
2591
+ taskId: args['task-id'] ?? current?.taskId,
2592
+ sessionId: args['session-id'] ?? current?.sessionId,
2593
+ kind: normalizeRuntimeEvidenceKind(args.kind),
2594
+ title: args.title,
2595
+ status: normalizeRuntimeEvidenceStatus(args.status),
2596
+ command: args.command,
2597
+ exitCode,
2598
+ summary: args.summary,
2599
+ artifacts: parseCommaList(args.artifacts),
2600
+ metadata,
2601
+ });
2602
+ if (record.sessionId) {
2603
+ new SessionLedger({ projectDir: PROJECT_DIR, scaleDir: SCALE_DIR }).append(record.sessionId, {
2604
+ type: 'evidence.recorded',
2605
+ message: `${record.status}: ${record.title}`,
2606
+ data: {
2607
+ evidenceId: record.id,
2608
+ kind: record.kind,
2609
+ taskId: record.taskId,
2610
+ },
2611
+ });
2612
+ }
2613
+ if (args.json) {
2614
+ console.log(JSON.stringify(record, null, 2));
2615
+ return;
2616
+ }
2617
+ console.log(`Runtime evidence recorded: ${record.id}`);
2618
+ console.log(` Status: ${record.status}`);
2619
+ console.log(` Kind: ${record.kind}`);
2620
+ if (record.redactionApplied)
2621
+ console.log(' Redaction: applied');
2622
+ },
2623
+ });
2624
+ const runtimeDoctor = defineCommand({
2625
+ meta: { name: 'doctor', description: 'Check runtime session and completion evidence' },
2626
+ args: {
2627
+ 'task-id': { type: 'string', description: 'Task id to inspect' },
2628
+ 'session-id': { type: 'string', description: 'Session id to inspect' },
2629
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2630
+ json: { type: 'boolean', default: false },
2631
+ },
2632
+ run({ args }) {
2633
+ const report = doctorRuntimeEvidence({
2634
+ projectDir: PROJECT_DIR,
2635
+ scaleDir: SCALE_DIR,
2636
+ taskId: args['task-id'],
2637
+ sessionId: args['session-id'],
2638
+ level: normalizeTaskArtifactLevel(args.level),
2639
+ });
2640
+ if (args.json) {
2641
+ console.log(JSON.stringify(report, null, 2));
2642
+ if (report.blocked)
2643
+ process.exitCode = 1;
2644
+ return;
2645
+ }
2646
+ console.log('\nSCALE Runtime Doctor');
2647
+ console.log(` Evidence: ${report.evidence.total} total, ${report.evidence.passed} passed, ${report.evidence.failed} failed, ${report.evidence.skipped} skipped`);
2648
+ for (const check of report.checks) {
2649
+ console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
2650
+ if (check.fix)
2651
+ console.log(` Fix: ${check.fix}`);
2652
+ }
2653
+ if (report.blocked)
2654
+ process.exitCode = 1;
2655
+ },
2656
+ });
2657
+ const runtimeFinalCheck = defineCommand({
2658
+ meta: { name: 'final-check', description: 'Block final delivery claims without passed runtime evidence' },
2659
+ args: {
2660
+ 'task-id': { type: 'string', description: 'Task id to inspect' },
2661
+ 'session-id': { type: 'string', description: 'Session id to inspect' },
2662
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2663
+ json: { type: 'boolean', default: false },
2664
+ },
2665
+ run({ args }) {
2666
+ const readiness = evaluateFinalReportReadiness({
2667
+ projectDir: PROJECT_DIR,
2668
+ scaleDir: SCALE_DIR,
2669
+ taskId: args['task-id'],
2670
+ sessionId: args['session-id'],
2671
+ level: normalizeTaskArtifactLevel(args.level),
2672
+ });
2673
+ if (args.json) {
2674
+ console.log(JSON.stringify(readiness, null, 2));
2675
+ if (readiness.blocked)
2676
+ process.exitCode = 1;
2677
+ return;
2678
+ }
2679
+ console.log('\nSCALE Runtime Final Check');
2680
+ console.log(` Ready: ${readiness.ready}`);
2681
+ for (const reason of readiness.reasons)
2682
+ console.log(` [BLOCKER] ${reason}`);
2683
+ if (readiness.blocked)
2684
+ process.exitCode = 1;
2685
+ },
2686
+ });
2687
+ const runtime = defineCommand({
2688
+ meta: { name: 'runtime', description: 'Runtime session ledger and completion evidence governance' },
2689
+ subCommands: {
2690
+ start: runtimeStart,
2691
+ end: runtimeEnd,
2692
+ record: runtimeRecord,
2693
+ doctor: runtimeDoctor,
2694
+ 'final-check': runtimeFinalCheck,
2695
+ },
2696
+ });
2697
+ // ============================================================================
2698
+ // memory command - runtime evidence + knowledge + graph context packs
2699
+ // ============================================================================
2700
+ function parseMemoryBudget(value) {
2701
+ if (value === undefined || value === null || value === '')
2702
+ return undefined;
2703
+ const parsed = Number.parseInt(String(value), 10);
2704
+ if (Number.isNaN(parsed) || parsed <= 0) {
2705
+ throw new Error('--budget must be a positive integer.');
2706
+ }
2707
+ return parsed;
2708
+ }
2709
+ const memoryPack = defineCommand({
2710
+ meta: { name: 'pack', description: 'Build a compact context pack from runtime evidence, session events, knowledge, and graph status' },
2711
+ args: {
2712
+ task: { type: 'string', required: true, description: 'Current task or question' },
2713
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
2714
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
2715
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2716
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
2717
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
2718
+ json: { type: 'boolean', default: false },
2719
+ },
2720
+ async run({ args }) {
2721
+ let budgetTokens;
2722
+ try {
2723
+ budgetTokens = parseMemoryBudget(args.budget);
2724
+ }
2725
+ catch (e) {
2726
+ console.error(e.message);
2727
+ process.exit(1);
2728
+ }
2729
+ const { kb } = getEngine();
2730
+ const pack = await new MemoryFabric({
2731
+ projectDir: PROJECT_DIR,
2732
+ scaleDir: SCALE_DIR,
2733
+ knowledgeBase: kb,
2734
+ }).createContextPack({
2735
+ task: args.task,
2736
+ taskId: args['task-id'],
2737
+ sessionId: args['session-id'],
2738
+ level: normalizeTaskArtifactLevel(args.level),
2739
+ files: parseCommaList(args.files),
2740
+ budgetTokens,
2741
+ });
2742
+ if (args.json) {
2743
+ console.log(JSON.stringify(pack, null, 2));
2744
+ return;
2745
+ }
2746
+ console.log(renderContextPackMarkdown(pack));
2747
+ },
2748
+ });
2749
+ const memoryDoctor = defineCommand({
2750
+ meta: { name: 'doctor', description: 'Check whether a task context pack is available and within token budget' },
2751
+ args: {
2752
+ task: { type: 'string', required: true, description: 'Current task or question' },
2753
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
2754
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
2755
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2756
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
2757
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
2758
+ json: { type: 'boolean', default: false },
2759
+ },
2760
+ async run({ args }) {
2761
+ let budgetTokens;
2762
+ try {
2763
+ budgetTokens = parseMemoryBudget(args.budget);
2764
+ }
2765
+ catch (e) {
2766
+ console.error(e.message);
2767
+ process.exit(1);
2768
+ }
2769
+ const { kb } = getEngine();
2770
+ const report = await doctorMemoryFabric({
2771
+ projectDir: PROJECT_DIR,
2772
+ scaleDir: SCALE_DIR,
2773
+ knowledgeBase: kb,
2774
+ }, {
2775
+ task: args.task,
2776
+ taskId: args['task-id'],
2777
+ sessionId: args['session-id'],
2778
+ level: normalizeTaskArtifactLevel(args.level),
2779
+ files: parseCommaList(args.files),
2780
+ budgetTokens,
2781
+ });
2782
+ if (args.json) {
2783
+ console.log(JSON.stringify(report, null, 2));
2784
+ if (!report.ok)
2785
+ process.exitCode = 1;
2786
+ return;
2787
+ }
2788
+ console.log('\nSCALE Memory Doctor');
2789
+ console.log(` Budget: ${report.pack.budget.used}/${report.pack.budget.limit} estimated tokens`);
2790
+ for (const check of report.checks) {
2791
+ console.log(` [${check.status.toUpperCase()}] ${check.name}: ${check.message}`);
2792
+ }
2793
+ if (!report.ok)
2794
+ process.exitCode = 1;
2795
+ },
2796
+ });
2797
+ const memorySettle = defineCommand({
2798
+ meta: { name: 'settle', description: 'Settle runtime evidence into a reviewable memory learning candidate' },
2799
+ args: {
2800
+ task: { type: 'string', required: true, description: 'Current task or question' },
2801
+ 'task-id': { type: 'string', description: 'Task id to scope evidence and session data' },
2802
+ 'session-id': { type: 'string', description: 'Session id to scope session events' },
2803
+ level: { type: 'string', default: 'M', description: 'Task level: S, M, L, or CRITICAL' },
2804
+ files: { type: 'string', description: 'Comma-separated files or modules in scope' },
2805
+ budget: { type: 'string', description: 'Maximum estimated tokens for the context pack' },
2806
+ json: { type: 'boolean', default: false },
2807
+ },
2808
+ async run({ args }) {
2809
+ let budgetTokens;
2810
+ try {
2811
+ budgetTokens = parseMemoryBudget(args.budget);
2812
+ }
2813
+ catch (e) {
2814
+ console.error(e.message);
2815
+ process.exit(1);
2816
+ }
2817
+ const { kb } = getEngine();
2818
+ const pack = await new MemoryFabric({
2819
+ projectDir: PROJECT_DIR,
2820
+ scaleDir: SCALE_DIR,
2821
+ knowledgeBase: kb,
2822
+ }).createContextPack({
2823
+ task: args.task,
2824
+ taskId: args['task-id'],
2825
+ sessionId: args['session-id'],
2826
+ level: normalizeTaskArtifactLevel(args.level),
2827
+ files: parseCommaList(args.files),
2828
+ budgetTokens,
2829
+ });
2830
+ const settlement = settleMemoryLearning({
2831
+ projectDir: PROJECT_DIR,
2832
+ scaleDir: SCALE_DIR,
2833
+ pack,
2834
+ });
2835
+ if (args.json) {
2836
+ console.log(JSON.stringify(settlement, null, 2));
2837
+ return;
2838
+ }
2839
+ console.log(renderMemoryLearningCandidateMarkdown(settlement.candidate));
2840
+ console.log(`\nWrote: ${settlement.files.markdown}`);
2841
+ },
2842
+ });
2843
+ const memory = defineCommand({
2844
+ meta: { name: 'memory', description: 'Memory Fabric context packs and budget diagnostics' },
2845
+ subCommands: { pack: memoryPack, doctor: memoryDoctor, settle: memorySettle },
2846
+ });
2847
+ // ============================================================================
2224
2848
  // out-of-scope command — 借鉴 mattpocock/skills 的 .out-of-scope/ 设计
2225
2849
  // ============================================================================
2226
2850
  const outOfScopeAdd = defineCommand({
@@ -2453,6 +3077,7 @@ const skillCheckCommand = defineCommand({
2453
3077
  artifactsDir: args.dir ?? state?.artifactsDir,
2454
3078
  level,
2455
3079
  requiredArtifacts: state?.requiredSkillArtifacts,
3080
+ requiredSkills: state?.requiredSkills,
2456
3081
  mode: state?.skillRoutingMode ?? policy.policy.mode,
2457
3082
  enforceLevels: policy.policy.enforceLevels,
2458
3083
  });
@@ -3005,6 +3630,7 @@ const main = defineCommand({
3005
3630
  stats,
3006
3631
  preflight,
3007
3632
  governance,
3633
+ artifact,
3008
3634
  assets,
3009
3635
  standards,
3010
3636
  metrics,
@@ -3013,6 +3639,8 @@ const main = defineCommand({
3013
3639
  status,
3014
3640
  workflow,
3015
3641
  evidence,
3642
+ runtime,
3643
+ memory,
3016
3644
  diagnose,
3017
3645
  tdd,
3018
3646
  tool,