@hongmaple0820/scale-engine 0.18.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 (77) hide show
  1. package/README.en.md +296 -237
  2. package/README.md +157 -63
  3. package/dist/api/cli.js +448 -27
  4. package/dist/api/cli.js.map +1 -1
  5. package/dist/api/doctor.d.ts +4 -1
  6. package/dist/api/doctor.js +85 -1
  7. package/dist/api/doctor.js.map +1 -1
  8. package/dist/api/quickstart.d.ts +3 -0
  9. package/dist/api/quickstart.js +9 -4
  10. package/dist/api/quickstart.js.map +1 -1
  11. package/dist/cli/phaseCommands.js +7 -0
  12. package/dist/cli/phaseCommands.js.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +4 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/memory/MemoryFabric.d.ts +118 -0
  17. package/dist/memory/MemoryFabric.js +281 -0
  18. package/dist/memory/MemoryFabric.js.map +1 -0
  19. package/dist/memory/MemoryLearning.d.ts +61 -0
  20. package/dist/memory/MemoryLearning.js +203 -0
  21. package/dist/memory/MemoryLearning.js.map +1 -0
  22. package/dist/memory/index.d.ts +2 -0
  23. package/dist/memory/index.js +3 -0
  24. package/dist/memory/index.js.map +1 -0
  25. package/dist/output/HTMLArtifactLayer.js +31 -31
  26. package/dist/prompts/VibeTemplateGallery.js +121 -121
  27. package/dist/runtime/FinalReportGuard.d.ts +16 -0
  28. package/dist/runtime/FinalReportGuard.js +14 -0
  29. package/dist/runtime/FinalReportGuard.js.map +1 -0
  30. package/dist/runtime/RuntimeDoctor.d.ts +23 -0
  31. package/dist/runtime/RuntimeDoctor.js +151 -0
  32. package/dist/runtime/RuntimeDoctor.js.map +1 -0
  33. package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
  34. package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
  35. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
  36. package/dist/runtime/SessionLedger.d.ts +53 -0
  37. package/dist/runtime/SessionLedger.js +104 -0
  38. package/dist/runtime/SessionLedger.js.map +1 -0
  39. package/dist/runtime/index.d.ts +4 -0
  40. package/dist/runtime/index.js +5 -0
  41. package/dist/runtime/index.js.map +1 -0
  42. package/dist/workflow/EngineeringStandards.js +69 -66
  43. package/dist/workflow/EngineeringStandards.js.map +1 -1
  44. package/dist/workflow/GovernanceTemplatePacks.js +126 -126
  45. package/dist/workflow/GovernanceTemplates.d.ts +1 -1
  46. package/dist/workflow/GovernanceTemplates.js +489 -218
  47. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  48. package/dist/workflow/ResourceGovernance.js +27 -18
  49. package/dist/workflow/ResourceGovernance.js.map +1 -1
  50. package/dist/workflow/VerificationCommands.d.ts +11 -0
  51. package/dist/workflow/VerificationCommands.js +2 -0
  52. package/dist/workflow/VerificationCommands.js.map +1 -1
  53. package/dist/workflow/VerificationProfile.d.ts +2 -1
  54. package/dist/workflow/VerificationProfile.js +3 -0
  55. package/dist/workflow/VerificationProfile.js.map +1 -1
  56. package/dist/workflow/WorkflowArtifactWriter.js +2 -1
  57. package/dist/workflow/WorkflowArtifactWriter.js.map +1 -1
  58. package/dist/workflow/WorkflowEngine.js +4 -1
  59. package/dist/workflow/WorkflowEngine.js.map +1 -1
  60. package/dist/workflow/WorkspaceSafety.d.ts +9 -0
  61. package/dist/workflow/WorkspaceSafety.js +49 -0
  62. package/dist/workflow/WorkspaceSafety.js.map +1 -0
  63. package/dist/workflow/gates/GateSystem.d.ts +12 -1
  64. package/dist/workflow/gates/GateSystem.js +106 -0
  65. package/dist/workflow/gates/GateSystem.js.map +1 -1
  66. package/dist/workflow/types.d.ts +1 -1
  67. package/docs/MEMORY_FABRIC.md +107 -0
  68. package/docs/README.md +68 -0
  69. package/docs/RUNTIME_EVIDENCE.md +101 -0
  70. package/docs/start/README.md +42 -0
  71. package/docs/start/agent-governance-demo.md +107 -0
  72. package/docs/start/quickstart.md +127 -0
  73. package/examples/demo-projects/agent-governance-demo/README.md +37 -0
  74. package/examples/demo-projects/agent-governance-demo/package.json +16 -0
  75. package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
  76. package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
  77. 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';
@@ -49,9 +49,12 @@ import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
49
49
  import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
50
50
  import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
51
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';
52
55
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
53
56
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
54
- import { join, resolve } from 'node:path';
57
+ import { isAbsolute, join, resolve } from 'node:path';
55
58
  import { execFileSync } from 'node:child_process';
56
59
  import { pathToFileURL } from 'node:url';
57
60
  import { SCALE_ENGINE_VERSION } from '../version.js';
@@ -131,15 +134,15 @@ function evaluateEngineeringStandardsGate(options) {
131
134
  }
132
135
  const settlement = options.settle && options.artifactsDir
133
136
  ? settleEngineeringStandards({
134
- projectDir: PROJECT_DIR,
135
- scaleDir: SCALE_DIR,
137
+ projectDir: options.projectDir ?? PROJECT_DIR,
138
+ scaleDir: options.scaleDir ?? SCALE_DIR,
136
139
  taskId: options.taskId,
137
140
  artifactsDir: options.artifactsDir,
138
141
  })
139
142
  : undefined;
140
143
  const doctor = settlement?.doctor ?? doctorEngineeringStandards({
141
- projectDir: PROJECT_DIR,
142
- scaleDir: SCALE_DIR,
144
+ projectDir: options.projectDir ?? PROJECT_DIR,
145
+ scaleDir: options.scaleDir ?? SCALE_DIR,
143
146
  });
144
147
  return {
145
148
  mode,
@@ -151,6 +154,16 @@ function evaluateEngineeringStandardsGate(options) {
151
154
  standardsImpactPath: settlement?.standardsImpactPath,
152
155
  };
153
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
+ }
154
167
  function normalizeEngineeringStandardsGateMode(value) {
155
168
  return value === 'off' || value === 'block' ? value : 'warn';
156
169
  }
@@ -194,6 +207,23 @@ function createEngine() {
194
207
  });
195
208
  return { eventBus, store, fsm, gateway, roleGate, kb, ctx, fsmAgentBridge, workflowEngine };
196
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
+ }
197
227
  // ============================================================================
198
228
  // session commands
199
229
  // ============================================================================
@@ -1379,6 +1409,7 @@ function normalizeWorkspaceTopologyKind(value) {
1379
1409
  const preflight = defineCommand({
1380
1410
  meta: { name: 'preflight', description: 'Run service-aware verification without a task artifact' },
1381
1411
  args: {
1412
+ dir: { type: 'string', default: PROJECT_DIR, description: 'Project directory' },
1382
1413
  'build-cmd': { type: 'string', description: 'Override build command' },
1383
1414
  'lint-cmd': { type: 'string', description: 'Override lint command' },
1384
1415
  'test-cmd': { type: 'string', description: 'Override test command' },
@@ -1391,22 +1422,32 @@ const preflight = defineCommand({
1391
1422
  json: { type: 'boolean', default: false },
1392
1423
  },
1393
1424
  async run({ args }) {
1394
- const { workflowEngine } = getEngine();
1425
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
1426
+ const scaleDir = resolveScaleDirForProject(projectDir);
1427
+ const workflowEngine = createVerificationWorkflowEngine(scaleDir);
1395
1428
  const preflightProfile = normalizePreflightProfile(args['preflight-profile']);
1396
- const gateStages = gatesForPreflightProfile(preflightProfile);
1397
1429
  const resolved = resolveVerificationTargets({
1398
- projectDir: PROJECT_DIR,
1399
- scaleDir: SCALE_DIR,
1430
+ projectDir,
1431
+ scaleDir,
1400
1432
  profile: args.profile,
1401
1433
  service: args.service,
1402
1434
  });
1435
+ let gateStages = gatesForPreflightProfile(preflightProfile);
1436
+ if (resolved.targets.some(target => target.config.smoke)) {
1437
+ gateStages = ['G8'];
1438
+ }
1403
1439
  const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
1404
1440
  if (commandTargetsSkipped) {
1405
1441
  resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
1406
1442
  }
1407
- const engineeringStandards = evaluateEngineeringStandardsGate({
1408
- policy: resolved.policy,
1409
- });
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
+ });
1410
1451
  const targetResults = [];
1411
1452
  if (!args.json) {
1412
1453
  console.log('\nSCALE Preflight');
@@ -1415,6 +1456,9 @@ const preflight = defineCommand({
1415
1456
  console.log(` Profile: ${resolved.profileName}`);
1416
1457
  console.log(` Preflight profile: ${preflightProfile}`);
1417
1458
  console.log(` Gates: ${gateStages.join(', ')}`);
1459
+ if (workspaceSafety.blocked) {
1460
+ console.log(` Workspace safety: BLOCKED - ${workspaceSafety.message}`);
1461
+ }
1418
1462
  if (engineeringStandards.checked) {
1419
1463
  const status = engineeringStandards.blocked ? 'BLOCKED' : engineeringStandards.ok ? 'OK' : 'WARN';
1420
1464
  console.log(` Engineering standards: ${status} (${engineeringStandards.mode})`);
@@ -1423,7 +1467,7 @@ const preflight = defineCommand({
1423
1467
  console.log(' Engineering standards: skipped');
1424
1468
  }
1425
1469
  }
1426
- for (const target of commandTargetsSkipped ? [] : resolved.targets) {
1470
+ for (const target of commandTargetsSkipped || workspaceSafety.blocked ? [] : resolved.targets) {
1427
1471
  if (!args.json) {
1428
1472
  const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
1429
1473
  console.log(`\n Target: ${label}`);
@@ -1434,6 +1478,12 @@ const preflight = defineCommand({
1434
1478
  lint: args['lint-cmd'] ?? target.config.lint,
1435
1479
  test: args['test-cmd'] ?? target.config.test,
1436
1480
  coverage: args['coverage-cmd'] ?? target.config.coverage,
1481
+ smoke: target.config.smoke,
1482
+ runtimeEvidence: {
1483
+ projectDir,
1484
+ scaleDir,
1485
+ profile: resolved.profileName,
1486
+ },
1437
1487
  tddEvidence: args['tdd-evidence'],
1438
1488
  tddStrict: isTruthyFlag(args['tdd-strict']),
1439
1489
  gates: gateStages,
@@ -1441,7 +1491,7 @@ const preflight = defineCommand({
1441
1491
  const passed = gates.every(gate => gate.passed);
1442
1492
  targetResults.push({
1443
1493
  service: target.service?.name,
1444
- cwd: target.config.cwd ?? PROJECT_DIR,
1494
+ cwd: target.config.cwd ?? projectDir,
1445
1495
  gates,
1446
1496
  passed,
1447
1497
  });
@@ -1454,6 +1504,7 @@ const preflight = defineCommand({
1454
1504
  }
1455
1505
  }
1456
1506
  const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
1507
+ !workspaceSafety.blocked &&
1457
1508
  !engineeringStandards.blocked;
1458
1509
  const result = {
1459
1510
  phase: 'PREFLIGHT',
@@ -1462,6 +1513,7 @@ const preflight = defineCommand({
1462
1513
  gates: gateStages,
1463
1514
  services: targetResults.map(target => target.service).filter(Boolean),
1464
1515
  policy: resolved.policy,
1516
+ workspaceSafety,
1465
1517
  engineeringStandards,
1466
1518
  targets: targetResults,
1467
1519
  commandTargetsSkipped,
@@ -1497,7 +1549,7 @@ const status = defineCommand({
1497
1549
  const latestTask = tasks[0];
1498
1550
  const taskPayload = latestTask?.payload;
1499
1551
  const workflowState = new WorkflowArtifactWriter(SCALE_DIR).readCurrentState();
1500
- const currentOpenTasks = workflowState?.taskId === latestTask?.id ? workflowState.openTasks ?? [] : [];
1552
+ const currentOpenTasks = workflowState?.openTasks ?? [];
1501
1553
  const nextOpenTask = nextWorkflowOpenTask(currentOpenTasks);
1502
1554
  const blockers = [];
1503
1555
  const latestBlockingEvidence = latestEvidence.find(record => !record.passed);
@@ -1513,16 +1565,16 @@ const status = defineCommand({
1513
1565
  blockers.push(`Task ${latestTask.id} has no persisted review evidence`);
1514
1566
  }
1515
1567
  const nextCommand = (() => {
1568
+ if (nextOpenTask?.kind === 'command')
1569
+ return nextOpenTask.value;
1570
+ if (nextOpenTask?.kind === 'blocker')
1571
+ return `Resolve workflow blocker: ${nextOpenTask.value}`;
1516
1572
  if (!specs[0])
1517
1573
  return 'scale define "<feature>" --description "<what to build>"';
1518
1574
  if (!plans[0])
1519
1575
  return `scale plan ${specs[0].id}`;
1520
1576
  if (!latestTask)
1521
1577
  return `scale build ${plans[0].id}`;
1522
- if (nextOpenTask?.kind === 'command')
1523
- return nextOpenTask.value;
1524
- if (nextOpenTask?.kind === 'blocker')
1525
- return `Resolve workflow blocker: ${nextOpenTask.value}`;
1526
1578
  if (!taskPayload?.verificationEvidenceIds?.length)
1527
1579
  return `scale verify ${latestTask.id}`;
1528
1580
  if (latestTask.status !== 'COMPLETED')
@@ -1569,7 +1621,7 @@ const status = defineCommand({
1569
1621
  level: workflowState.level,
1570
1622
  phase: workflowState.phase,
1571
1623
  artifactsDir: workflowState.artifactsDir,
1572
- openTasks: workflowState.openTasks,
1624
+ openTasks: workflowState.openTasks ?? [],
1573
1625
  skillIntents: workflowState.skillIntents,
1574
1626
  } : null,
1575
1627
  blockers,
@@ -1601,7 +1653,7 @@ const status = defineCommand({
1601
1653
  for (const blocker of blockers)
1602
1654
  console.log(` - ${blocker}`);
1603
1655
  }
1604
- if (result.workflowState?.openTasks.length) {
1656
+ if ((result.workflowState?.openTasks.length ?? 0) > 0) {
1605
1657
  console.log('\nOpen Tasks:');
1606
1658
  for (const task of result.workflowState.openTasks)
1607
1659
  console.log(` - ${task}`);
@@ -1704,8 +1756,8 @@ const init = defineCommand({
1704
1756
  console.log(` Data dir: ${result.scaleDir}`);
1705
1757
  console.log(` Scenario: ${scenarioMode}`);
1706
1758
  console.log(`\n📋 Next steps:`);
1707
- console.log(` → scale doctor`);
1708
- console.log(` → scale create Spec "<feature name>"`);
1759
+ for (const step of governanceNextSteps())
1760
+ console.log(` → ${step}`);
1709
1761
  return;
1710
1762
  }
1711
1763
  // One-click quick start mode
@@ -1777,7 +1829,7 @@ const init = defineCommand({
1777
1829
  scaleDir: result.scaleDir,
1778
1830
  created: result.created,
1779
1831
  skipped: result.skipped,
1780
- nextSteps: ['scale doctor', 'scale create Spec "<feature name>"'],
1832
+ nextSteps: governanceNextSteps(),
1781
1833
  }, null, 2));
1782
1834
  return;
1783
1835
  }
@@ -1794,8 +1846,8 @@ const init = defineCommand({
1794
1846
  console.log(`\n📖 Knowledge: ${result.knowledgeDocPath}`);
1795
1847
  console.log(`\n📂 Data dir: ${result.scaleDir}`);
1796
1848
  console.log(`\n📋 Next steps:`);
1797
- console.log(` → scale doctor`);
1798
- console.log(` → scale create Spec "<feature name>"`);
1849
+ for (const step of governanceNextSteps())
1850
+ console.log(` → ${step}`);
1799
1851
  },
1800
1852
  });
1801
1853
  // ============================================================================
@@ -2426,6 +2478,373 @@ const evidence = defineCommand({
2426
2478
  subCommands: { list: evidenceList, show: evidenceShow },
2427
2479
  });
2428
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
+ // ============================================================================
2429
2848
  // out-of-scope command — 借鉴 mattpocock/skills 的 .out-of-scope/ 设计
2430
2849
  // ============================================================================
2431
2850
  const outOfScopeAdd = defineCommand({
@@ -3220,6 +3639,8 @@ const main = defineCommand({
3220
3639
  status,
3221
3640
  workflow,
3222
3641
  evidence,
3642
+ runtime,
3643
+ memory,
3223
3644
  diagnose,
3224
3645
  tdd,
3225
3646
  tool,