@hongmaple0820/scale-engine 0.40.0 → 0.40.2

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 (47) hide show
  1. package/dist/api/cli.js +421 -26
  2. package/dist/api/cli.js.map +1 -1
  3. package/dist/api/doctor.js +1 -1
  4. package/dist/api/doctor.js.map +1 -1
  5. package/dist/bootstrap/DependencyBootstrap.d.ts +2 -0
  6. package/dist/bootstrap/DependencyBootstrap.js +250 -39
  7. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  8. package/dist/bootstrap/DependencyBootstrapRenderer.js +2 -4
  9. package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -1
  10. package/dist/capabilities/InstalledSkillsIntegration.js +29 -9
  11. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -1
  12. package/dist/context/ContextBudget.js +2 -2
  13. package/dist/core/GbrainRuntime.d.ts +25 -0
  14. package/dist/core/GbrainRuntime.js +270 -0
  15. package/dist/core/GbrainRuntime.js.map +1 -0
  16. package/dist/env/EnvironmentDoctor.js +221 -5
  17. package/dist/env/EnvironmentDoctor.js.map +1 -1
  18. package/dist/memory/MemoryProviders.js +38 -91
  19. package/dist/memory/MemoryProviders.js.map +1 -1
  20. package/dist/runtime/ModelUsageLedger.d.ts +53 -2
  21. package/dist/runtime/ModelUsageLedger.js +243 -39
  22. package/dist/runtime/ModelUsageLedger.js.map +1 -1
  23. package/dist/setup/SetupVerification.d.ts +42 -0
  24. package/dist/setup/SetupVerification.js +180 -0
  25. package/dist/setup/SetupVerification.js.map +1 -0
  26. package/dist/setup/SetupWizard.d.ts +3 -0
  27. package/dist/setup/SetupWizard.js +79 -19
  28. package/dist/setup/SetupWizard.js.map +1 -1
  29. package/dist/skills/SkillDoctor.js +2 -2
  30. package/dist/skills/SkillDoctor.js.map +1 -1
  31. package/dist/skills/SkillRepository.js +2 -2
  32. package/dist/skills/SkillRepository.js.map +1 -1
  33. package/dist/tools/ToolCapabilityRegistry.js +12 -2
  34. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  35. package/dist/workflow/VerificationProfile.js +1 -1
  36. package/dist/workflow/VerificationProfile.js.map +1 -1
  37. package/docs/CONTEXT_BUDGET.md +12 -2
  38. package/docs/GOVERNANCE_DASHBOARD.md +7 -0
  39. package/docs/THIRD_PARTY_SKILLS.md +12 -4
  40. package/docs/start/README.md +2 -2
  41. package/docs/start/quickstart.md +54 -44
  42. package/docs/start/workflow-upgrade.md +8 -1
  43. package/package.json +3 -2
  44. package/scripts/workflow/lib/gbrain-runtime.mjs +185 -0
  45. package/scripts/workflow/lib/report-output.mjs +107 -0
  46. package/scripts/workflow/provider-rehearsal.mjs +129 -48
  47. package/scripts/workflow/setup-smoke.mjs +142 -8
package/dist/api/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // SCALE Engine — CLI 入口 (W6 完整实现)
3
3
  // 所有 Hook 调用入口: session/gate/create/list/transition/context
4
4
  import { defineCommand, runMain } from 'citty';
5
+ import { createInterface } from 'node:readline';
5
6
  import { EventBus } from '../core/eventBus.js';
6
7
  import { SQLiteArtifactStore } from '../artifact/sqliteStore.js';
7
8
  import { FSM } from '../artifact/fsm.js';
@@ -32,6 +33,7 @@ import { quickStart, detectPlatform, governanceNextSteps } from './quickstart.js
32
33
  import { bootstrapDependencies } from '../bootstrap/DependencyBootstrap.js';
33
34
  import { renderDependencyBootstrapReport } from '../bootstrap/DependencyBootstrapRenderer.js';
34
35
  import { runSetupWizard } from '../setup/SetupWizard.js';
36
+ import { verifySetup } from '../setup/SetupVerification.js';
35
37
  import { normalizeLanguage, resolveCliLanguage } from '../i18n/Language.js';
36
38
  import { SkillDiscovery } from '../skills/SkillDiscovery.js';
37
39
  import { inspectRequiredWorkflowSkills, inspectWorkflowSkills } from '../skills/SkillDoctor.js';
@@ -74,7 +76,7 @@ import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, se
74
76
  import { renderGovernanceDashboard } from '../output/GovernanceDashboard.js';
75
77
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
76
78
  import { inspectWorkspaceSafety } from '../workflow/WorkspaceSafety.js';
77
- import { RuntimeEvidenceLedger, SessionLedger, createAiOsAdoption, createAiOsBenchmark, createAiOsDashboard, createAiOsDoctor, createAiOsMigration, createAiOsPlan, createAiOsRun, createAiOsStatus, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
79
+ import { ModelUsageLedger, RuntimeEvidenceLedger, SessionLedger, buildModelUsageInput, createAiOsAdoption, createAiOsBenchmark, createAiOsDashboard, createAiOsDoctor, createAiOsMigration, createAiOsPlan, createAiOsRun, createAiOsStatus, doctorRuntimeEvidence, evaluateFinalReportReadiness, } from '../runtime/index.js';
78
80
  import { MemoryFabric, MemoryBrain, doctorMemoryFabric, renderContextPackMarkdown, renderMemoryLearningCandidateMarkdown, inspectMemoryProviders, recallMemoryProviders, settleMemoryLearning, useMemoryProvider, writeMemoryProvidersConfig, } from '../memory/index.js';
79
81
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
80
82
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
@@ -2816,7 +2818,6 @@ const bootstrap = defineCommand({
2816
2818
  meta: { name: 'bootstrap', description: 'Bootstrap third-party workflow dependencies with explicit install intent' },
2817
2819
  subCommands: { deps: bootstrapDepsCommand },
2818
2820
  });
2819
- // ============================================================================
2820
2821
  const setup = defineCommand({
2821
2822
  meta: { name: 'setup', description: 'Interactive SCALE setup for third-party skills, CLIs, memory, and knowledge providers' },
2822
2823
  args: {
@@ -2827,6 +2828,7 @@ const setup = defineCommand({
2827
2828
  include: { type: 'string', description: 'Additional dependency ids to include explicitly' },
2828
2829
  apply: { type: 'boolean', default: false, description: 'Run install commands for ready dependencies' },
2829
2830
  yes: { type: 'boolean', default: false, description: 'Confirm installation without prompting' },
2831
+ verify: { type: 'boolean', default: false, description: 'Verify governed setup and dependency readiness instead of running the setup wizard' },
2830
2832
  interactive: { type: 'boolean', default: true, description: 'Prompt before installation when dependencies are ready' },
2831
2833
  lang: { type: 'string', description: 'Output language zh/en. Defaults to zh, then SCALE_LANG, then .scale/config.yaml locale.' },
2832
2834
  'memory-provider': { type: 'string', description: 'Switch memory provider during setup: gbrain, agentmemory, or scale-local' },
@@ -2839,15 +2841,30 @@ const setup = defineCommand({
2839
2841
  async run({ args }) {
2840
2842
  const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
2841
2843
  const lang = resolveCliLanguage({ lang: args.lang, projectDir, scaleDir: SCALE_DIR });
2842
- const explicitPacks = parseCommaList(args.pack);
2843
- const recommendedPacks = args.profile
2844
- ? getBootstrapPlanForProfile(String(args.profile), args['governance-pack'] ? String(args['governance-pack']) : undefined).packs
2845
- : [];
2844
+ const { explicitPacks, recommendedPacks } = resolveSetupPacks(args);
2845
+ if (isTruthyFlag(args.verify)) {
2846
+ const verification = await verifySetup({
2847
+ projectDir,
2848
+ scaleDir: SCALE_DIR,
2849
+ packIds: explicitPacks.length > 0 ? uniqueStrings([...recommendedPacks, ...explicitPacks]) : recommendedPacks,
2850
+ includeIds: parseCommaList(args.include),
2851
+ });
2852
+ if (args.json) {
2853
+ console.log(JSON.stringify(verification, null, 2));
2854
+ }
2855
+ else {
2856
+ renderSetupVerifyReport(verification, lang);
2857
+ }
2858
+ if (!verification.ok)
2859
+ process.exitCode = 1;
2860
+ return;
2861
+ }
2846
2862
  const report = await runSetupWizard({
2847
2863
  projectDir,
2848
2864
  scaleDir: SCALE_DIR,
2849
2865
  packIds: explicitPacks.length > 0 ? uniqueStrings([...recommendedPacks, ...explicitPacks]) : recommendedPacks,
2850
2866
  includeIds: parseCommaList(args.include),
2867
+ promptPacks: explicitPacks.length === 0 && recommendedPacks.length === 0 && !args.include,
2851
2868
  apply: isTruthyFlag(args.apply),
2852
2869
  yes: isTruthyFlag(args.yes),
2853
2870
  interactive: isTruthyFlag(args.interactive) && !isTruthyFlag(args.json),
@@ -2859,34 +2876,95 @@ const setup = defineCommand({
2859
2876
  allowExternalWrite: isTruthyFlag(args['allow-external-write']) ? true : undefined,
2860
2877
  promptLanguage: isTruthyFlag(args.interactive) && !args.lang,
2861
2878
  });
2879
+ if (!args.json) {
2880
+ console.log(lang === 'zh' ? '\nSCALE 交互式安装' : '\nSCALE Interactive Setup');
2881
+ console.log(lang === 'zh'
2882
+ ? ` 已执行安装: ${report.applied ? '是' : '否'}`
2883
+ : ` Applied: ${report.applied}`);
2884
+ if (report.memoryProviderSwitch) {
2885
+ const switched = report.memoryProviderSwitch;
2886
+ console.log(lang === 'zh' ? ' 记忆供应商:' : ' Memory provider:');
2887
+ console.log(` provider=${switched.provider}; mode=${switched.mode}; config=${switched.path}`);
2888
+ console.log(` order=${switched.previousOrder.join(' -> ')} => ${switched.nextOrder.join(' -> ')}`);
2889
+ if (switched.providerStatus) {
2890
+ console.log(` status=${switched.providerStatus.available ? 'available' : 'not-ready'}; reason=${switched.providerStatus.reason}`);
2891
+ }
2892
+ for (const warning of switched.warnings)
2893
+ console.log(lang === 'zh' ? ` [警告] ${warning}` : ` [WARN] ${warning}`);
2894
+ }
2895
+ console.log(renderDependencyBootstrapReport(report.final, lang));
2896
+ if (!report.ok)
2897
+ process.exitCode = 1;
2898
+ return;
2899
+ }
2862
2900
  if (args.json) {
2863
2901
  console.log(JSON.stringify(report, null, 2));
2864
2902
  if (!report.ok)
2865
2903
  process.exitCode = 1;
2866
2904
  return;
2867
2905
  }
2868
- console.log(lang === 'zh' ? '\nSCALE 交互式安装' : '\nSCALE Interactive Setup');
2869
- console.log(lang === 'zh'
2870
- ? ` 已执行安装: ${report.applied ? '是' : '否'}`
2871
- : ` Applied: ${report.applied}`);
2872
- if (report.memoryProviderSwitch) {
2873
- const switched = report.memoryProviderSwitch;
2874
- console.log(lang === 'zh' ? ' 记忆供应商:' : ' Memory provider:');
2875
- console.log(` provider=${switched.provider}; mode=${switched.mode}; config=${switched.path}`);
2876
- console.log(` order=${switched.previousOrder.join(' -> ')} => ${switched.nextOrder.join(' -> ')}`);
2877
- if (switched.providerStatus) {
2878
- console.log(` status=${switched.providerStatus.available ? 'available' : 'not-ready'}; reason=${switched.providerStatus.reason}`);
2879
- }
2880
- for (const warning of switched.warnings)
2881
- console.log(lang === 'zh' ? ` [警告] ${warning}` : ` [WARN] ${warning}`);
2882
- }
2883
- console.log(renderDependencyBootstrapReport(report.final, lang));
2884
- if (!report.ok)
2885
- process.exitCode = 1;
2886
2906
  },
2887
2907
  });
2888
2908
  // config command — Configuration profile management
2889
2909
  // ============================================================================
2910
+ function resolveSetupPacks(args) {
2911
+ const explicitPacks = parseCommaList(args.pack);
2912
+ const recommendedPacks = args.profile
2913
+ ? getBootstrapPlanForProfile(String(args.profile), args['governance-pack'] ? String(args['governance-pack']) : undefined).packs
2914
+ : [];
2915
+ return { explicitPacks, recommendedPacks };
2916
+ }
2917
+ function renderSetupVerifyReport(report, lang) {
2918
+ if (lang === 'zh') {
2919
+ console.log('\nSCALE 安装验收');
2920
+ console.log(` 项目: ${report.projectDir}`);
2921
+ console.log(` 依赖包: ${report.packIds.join(', ') || 'full'}`);
2922
+ console.log(` 结论: ${report.ok ? '通过' : '未通过'}`);
2923
+ console.log(` 阻塞项: ${report.summary.blockingIssues.length}`);
2924
+ console.log(` 受管能力: ${report.summary.installedTools}/${report.summary.totalTools}`);
2925
+ console.log(` 记忆供应商: ${report.summary.availableMemoryProviders}`);
2926
+ console.log(` 代码图谱供应商: ${report.summary.availableCodeProviders}`);
2927
+ if (report.summary.blockingIssues.length > 0) {
2928
+ console.log(' 阻塞详情:');
2929
+ for (const issue of report.summary.blockingIssues)
2930
+ console.log(` - ${issue}`);
2931
+ }
2932
+ if (report.warnings.length > 0) {
2933
+ console.log(` 警告${report.warnings.length > 12 ? ` (显示前 12 条,共 ${report.warnings.length} 条)` : ''}:`);
2934
+ for (const warning of report.warnings.slice(0, 12))
2935
+ console.log(` - ${warning}`);
2936
+ }
2937
+ if (report.recommendations.length > 0) {
2938
+ console.log(' 下一步:');
2939
+ for (const command of report.recommendations.slice(0, 12))
2940
+ console.log(` ${command}`);
2941
+ }
2942
+ return;
2943
+ }
2944
+ console.log('\nSCALE Setup Verification');
2945
+ console.log(` Project: ${report.projectDir}`);
2946
+ console.log(` Packs: ${report.packIds.join(', ') || 'full'}`);
2947
+ console.log(` Result: ${report.ok ? 'passed' : 'failed'}`);
2948
+ console.log(` Blocking issues: ${report.summary.blockingIssues.length}`);
2949
+ console.log(` Governed capabilities: ${report.summary.installedTools}/${report.summary.totalTools}`);
2950
+ console.log(` Memory providers: ${report.summary.availableMemoryProviders}`);
2951
+ console.log(` Code providers: ${report.summary.availableCodeProviders}`);
2952
+ if (report.summary.blockingIssues.length > 0) {
2953
+ console.log(' Blockers:');
2954
+ for (const issue of report.summary.blockingIssues)
2955
+ console.log(` - ${issue}`);
2956
+ }
2957
+ if (report.warnings.length > 0) {
2958
+ console.log(` Warnings${report.warnings.length > 12 ? ` (showing first 12 of ${report.warnings.length})` : ''}:`);
2959
+ for (const warning of report.warnings.slice(0, 12))
2960
+ console.log(` - ${warning}`);
2961
+ }
2962
+ if (report.recommendations.length > 0) {
2963
+ console.log(' Next:');
2964
+ for (const command of report.recommendations.slice(0, 12))
2965
+ console.log(` ${command}`);
2966
+ }
2967
+ }
2890
2968
  const configProfile = defineCommand({
2891
2969
  meta: { name: 'profile', description: 'View or switch configuration profile' },
2892
2970
  args: {
@@ -3635,9 +3713,125 @@ const upgradeRollback = defineCommand({
3635
3713
  },
3636
3714
  });
3637
3715
  const upgrade = defineCommand({
3638
- meta: { name: 'upgrade', description: 'SCALE 工作流、模板、skills、MCP、CLI 工具的安全升级规划 / Safe update planning for workflow assets' },
3716
+ meta: { name: 'upgrade', description: 'SCALE 工作流、模板、skills、MCP、CLI 工具的安全升级向导 / Safe update wizard for workflow assets' },
3717
+ args: {
3718
+ dir: { type: 'string', default: PROJECT_DIR, description: '项目目录 / Project directory' },
3719
+ 'target-version': { type: 'string', description: '目标 SCALE Engine 版本,默认使用当前 CLI 版本 / Target SCALE Engine version' },
3720
+ apply: { type: 'boolean', default: false, description: '直接应用安全升级计划 / Apply safe upgrade plan' },
3721
+ yes: { type: 'boolean', default: false, description: '非交互确认 / Confirm without prompting' },
3722
+ html: { type: 'boolean', default: true, description: '写入 HTML 升级计划 / Write HTML plan' },
3723
+ interactive: { type: 'boolean', default: true, description: '启用升级向导交互 / Enable upgrade wizard prompts' },
3724
+ lang: { type: 'string', default: 'zh', description: '输出语言 zh/en / Output language' },
3725
+ json: { type: 'boolean', default: false, description: '输出 JSON / Print JSON output' },
3726
+ },
3639
3727
  subCommands: { check: upgradeCheck, plan: upgradePlan, apply: upgradeApply, rollback: upgradeRollback },
3728
+ async run({ args }) {
3729
+ if (isUpgradeSubcommandInvocation(process.argv))
3730
+ return;
3731
+ const lang = normalizeLangArg(args.lang);
3732
+ const projectDir = resolve(String(args.dir ?? PROJECT_DIR));
3733
+ const scaleDir = resolveScaleDirForProject(projectDir);
3734
+ const targetScaleVersion = args['target-version'] ? String(args['target-version']) : undefined;
3735
+ const plan = createUpgradePlanReport({ projectDir, scaleDir, targetScaleVersion });
3736
+ const htmlPath = args.html ? writeUpgradePlanHtml(plan, undefined, lang) : undefined;
3737
+ const canApply = plan.applyMode === 'safe' && plan.blockers.length === 0;
3738
+ const interactive = isTruthyFlag(args.interactive) && !args.json && Boolean(process.stdin.isTTY) && !isTruthyFlag(args.yes);
3739
+ let apply = isTruthyFlag(args.apply) || isTruthyFlag(args.yes);
3740
+ let cancelled = false;
3741
+ if (interactive && canApply && !apply) {
3742
+ const answer = await askUpgradeWizardQuestion(lang === 'zh'
3743
+ ? '发现可安全应用的升级计划。现在应用吗?1=仅生成计划 2=应用 3=取消,默认 1: '
3744
+ : 'Safe upgrade plan found. Apply now? 1=plan only 2=apply 3=cancel, default 1: ');
3745
+ const normalized = answer.trim().toLowerCase();
3746
+ apply = normalized === '2' || normalized === 'apply' || normalized === 'yes' || normalized === 'y';
3747
+ cancelled = normalized === '3' || normalized === 'cancel' || normalized === 'c';
3748
+ }
3749
+ const applyResult = apply && !cancelled
3750
+ ? applyUpgradePlan({ projectDir, scaleDir, confirm: true })
3751
+ : undefined;
3752
+ const ok = !cancelled && (!applyResult || applyResult.ok);
3753
+ const report = { ok, cancelled, htmlPath, plan, applyResult };
3754
+ if (args.json) {
3755
+ console.log(JSON.stringify(report, null, 2));
3756
+ if (!ok)
3757
+ process.exitCode = 1;
3758
+ return;
3759
+ }
3760
+ renderUpgradeWizardReport(report, lang);
3761
+ if (!ok)
3762
+ process.exitCode = 1;
3763
+ },
3640
3764
  });
3765
+ async function askUpgradeWizardQuestion(question) {
3766
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
3767
+ try {
3768
+ return await new Promise(resolve => rl.question(question, resolve));
3769
+ }
3770
+ finally {
3771
+ rl.close();
3772
+ }
3773
+ }
3774
+ function isUpgradeSubcommandInvocation(argv) {
3775
+ const upgradeIndex = argv.findIndex((value, index) => index > 1 && value === 'upgrade');
3776
+ if (upgradeIndex < 0)
3777
+ return false;
3778
+ const positional = argv.slice(upgradeIndex + 1).find(value => !value.startsWith('-') && !value.includes('='));
3779
+ return positional === 'check' || positional === 'plan' || positional === 'apply' || positional === 'rollback';
3780
+ }
3781
+ function renderUpgradeWizardReport(report, lang) {
3782
+ const plan = report.plan;
3783
+ if (lang === 'zh') {
3784
+ console.log('\nSCALE 升级向导');
3785
+ console.log(` 项目: ${plan.projectDir}`);
3786
+ console.log(` 状态: ${plan.status}`);
3787
+ console.log(` 应用模式: ${plan.applyMode}`);
3788
+ console.log(` 阻塞项: ${plan.blockers.length}`);
3789
+ console.log(` 步骤数: ${plan.steps.length}`);
3790
+ if (report.htmlPath)
3791
+ console.log(` HTML 计划: ${report.htmlPath}`);
3792
+ if (report.cancelled)
3793
+ console.log(' 结果: 已取消');
3794
+ else if (report.applyResult) {
3795
+ console.log(` 结果: ${report.applyResult.applied ? '已应用' : '未应用'}`);
3796
+ console.log(` 原因: ${formatUpgradeApplyReason(report.applyResult.reason, lang)}`);
3797
+ for (const path of report.applyResult.changedFiles)
3798
+ console.log(` 已变更: ${path}`);
3799
+ }
3800
+ else {
3801
+ console.log(' 结果: 已生成计划,未应用变更');
3802
+ }
3803
+ console.log(' 下一步:');
3804
+ for (const command of plan.check.recommendedCommands.slice(0, 5))
3805
+ console.log(` ${formatUpgradeCommand(command, lang)}`);
3806
+ if (plan.blockers.length > 0)
3807
+ console.log(' 先解决阻塞项后再执行 scale upgrade apply --confirm');
3808
+ return;
3809
+ }
3810
+ console.log('\nSCALE Upgrade Wizard');
3811
+ console.log(` Project: ${plan.projectDir}`);
3812
+ console.log(` Status: ${plan.status}`);
3813
+ console.log(` Apply mode: ${plan.applyMode}`);
3814
+ console.log(` Blockers: ${plan.blockers.length}`);
3815
+ console.log(` Steps: ${plan.steps.length}`);
3816
+ if (report.htmlPath)
3817
+ console.log(` HTML plan: ${report.htmlPath}`);
3818
+ if (report.cancelled)
3819
+ console.log(' Result: cancelled');
3820
+ else if (report.applyResult) {
3821
+ console.log(` Result: ${report.applyResult.applied ? 'applied' : 'not applied'}`);
3822
+ console.log(` Reason: ${report.applyResult.reason}`);
3823
+ for (const path of report.applyResult.changedFiles)
3824
+ console.log(` changed: ${path}`);
3825
+ }
3826
+ else {
3827
+ console.log(' Result: plan generated; no changes applied');
3828
+ }
3829
+ console.log(' Next:');
3830
+ for (const command of plan.check.recommendedCommands.slice(0, 5))
3831
+ console.log(` ${formatUpgradeCommand(command, lang)}`);
3832
+ if (plan.blockers.length > 0)
3833
+ console.log(' Resolve blockers before running scale upgrade apply --confirm');
3834
+ }
3641
3835
  function formatUpgradeBlockerMessage(code, fallback, lang) {
3642
3836
  if (lang !== 'zh')
3643
3837
  return fallback;
@@ -4416,6 +4610,169 @@ function normalizeRuntimeSessionStatus(value) {
4416
4610
  return normalized;
4417
4611
  throw new Error(`Invalid runtime session status "${normalized}"; expected active, completed, failed, or abandoned.`);
4418
4612
  }
4613
+ function parseNonNegativeNumberArg(value, name) {
4614
+ if (value === undefined || value === null || value === '')
4615
+ return undefined;
4616
+ const parsed = Number(value);
4617
+ if (!Number.isFinite(parsed) || parsed < 0) {
4618
+ throw new Error(`${name} must be a non-negative number.`);
4619
+ }
4620
+ return parsed;
4621
+ }
4622
+ function parseJsonArg(value, name) {
4623
+ try {
4624
+ return JSON.parse(String(value ?? 'null'));
4625
+ }
4626
+ catch {
4627
+ throw new Error(`${name} must be valid JSON.`);
4628
+ }
4629
+ }
4630
+ function parseMetadataJson(value, name = '--metadata-json') {
4631
+ const parsed = parseJsonArg(value ?? '{}', name);
4632
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
4633
+ throw new Error(`${name} must be a JSON object.`);
4634
+ }
4635
+ return parsed;
4636
+ }
4637
+ function hasModelUsageArgs(args) {
4638
+ return [
4639
+ 'provider',
4640
+ 'model',
4641
+ 'usage-json',
4642
+ 'usage-file',
4643
+ 'input-tokens',
4644
+ 'output-tokens',
4645
+ 'cache-eligible-tokens',
4646
+ 'cache-creation-input-tokens',
4647
+ 'cache-read-input-tokens',
4648
+ 'cached-tokens',
4649
+ 'estimated-cost-usd',
4650
+ ].some(key => args[key] !== undefined && args[key] !== '');
4651
+ }
4652
+ function buildModelUsageRecordInput(args, defaults = {}) {
4653
+ const usagePayload = args['usage-file']
4654
+ ? parseJsonArg(readFileSync(resolve(PROJECT_DIR, String(args['usage-file'])), 'utf-8'), '--usage-file')
4655
+ : args['usage-json']
4656
+ ? parseJsonArg(args['usage-json'], '--usage-json')
4657
+ : undefined;
4658
+ const provider = String(args.provider ?? defaults.provider ?? '').trim();
4659
+ if (!provider)
4660
+ throw new Error('Model usage recording requires --provider.');
4661
+ return buildModelUsageInput({
4662
+ provider,
4663
+ model: args.model ? String(args.model) : undefined,
4664
+ taskId: args['task-id'] ? String(args['task-id']) : defaults.taskId,
4665
+ sessionId: args['session-id'] ? String(args['session-id']) : defaults.sessionId,
4666
+ inputTokens: parseNonNegativeNumberArg(args['input-tokens'], '--input-tokens'),
4667
+ outputTokens: parseNonNegativeNumberArg(args['output-tokens'], '--output-tokens'),
4668
+ cacheEligibleTokens: parseNonNegativeNumberArg(args['cache-eligible-tokens'], '--cache-eligible-tokens'),
4669
+ cacheCreationInputTokens: parseNonNegativeNumberArg(args['cache-creation-input-tokens'], '--cache-creation-input-tokens'),
4670
+ cacheReadInputTokens: parseNonNegativeNumberArg(args['cache-read-input-tokens'], '--cache-read-input-tokens'),
4671
+ cachedTokens: parseNonNegativeNumberArg(args['cached-tokens'], '--cached-tokens'),
4672
+ estimatedCostUsd: parseNonNegativeNumberArg(args['estimated-cost-usd'], '--estimated-cost-usd'),
4673
+ metadata: args['metadata-json'] !== undefined ? parseMetadataJson(args['metadata-json']) : undefined,
4674
+ timestamp: args.timestamp ? String(args.timestamp) : undefined,
4675
+ usagePayload,
4676
+ });
4677
+ }
4678
+ const tokenRecord = defineCommand({
4679
+ meta: { name: 'record', description: 'Record real model usage from provider usage payloads or explicit token counts' },
4680
+ args: {
4681
+ provider: { type: 'string', required: true, description: 'Model provider: anthropic, openai, codex, etc.' },
4682
+ model: { type: 'string', description: 'Optional model id' },
4683
+ 'task-id': { type: 'string', description: 'Task id linked to this model usage' },
4684
+ 'session-id': { type: 'string', description: 'Session id linked to this model usage' },
4685
+ 'usage-json': { type: 'string', description: 'Raw provider response or usage JSON to normalize into the usage ledger' },
4686
+ 'usage-file': { type: 'string', description: 'Path to a JSON file containing a raw provider response or usage payload' },
4687
+ 'input-tokens': { type: 'string', description: 'Explicit input token count; overrides usage JSON when provided' },
4688
+ 'output-tokens': { type: 'string', description: 'Explicit output token count; overrides usage JSON when provided' },
4689
+ 'cache-eligible-tokens': { type: 'string', description: 'Explicit cache-eligible token count' },
4690
+ 'cache-creation-input-tokens': { type: 'string', description: 'Explicit Anthropic cache creation token count' },
4691
+ 'cache-read-input-tokens': { type: 'string', description: 'Explicit Anthropic cache read token count' },
4692
+ 'cached-tokens': { type: 'string', description: 'Explicit OpenAI cached token count' },
4693
+ 'estimated-cost-usd': { type: 'string', description: 'Optional estimated cost in USD' },
4694
+ timestamp: { type: 'string', description: 'Optional ISO timestamp' },
4695
+ 'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
4696
+ json: { type: 'boolean', default: false },
4697
+ },
4698
+ run({ args }) {
4699
+ const record = new ModelUsageLedger(SCALE_DIR).record(buildModelUsageRecordInput(args));
4700
+ if (args.json) {
4701
+ console.log(JSON.stringify(record, null, 2));
4702
+ return;
4703
+ }
4704
+ console.log(`Model usage recorded: ${record.id}`);
4705
+ console.log(` Provider: ${record.provider}`);
4706
+ console.log(` Model: ${record.model ?? 'unknown'}`);
4707
+ console.log(` Tokens: input ${record.inputTokens}, output ${record.outputTokens}, total ${record.totalTokens}`);
4708
+ if (record.cacheSavingsTokens > 0)
4709
+ console.log(` Cache savings: ${record.cacheSavingsTokens} tokens`);
4710
+ },
4711
+ });
4712
+ const tokenReport = defineCommand({
4713
+ meta: { name: 'report', description: 'Summarize recorded model usage by day, provider, model, and task' },
4714
+ args: {
4715
+ day: { type: 'string', description: 'Exact UTC day in YYYY-MM-DD format' },
4716
+ since: { type: 'string', description: 'ISO timestamp lower bound' },
4717
+ until: { type: 'string', description: 'ISO timestamp upper bound' },
4718
+ 'since-days': { type: 'string', default: '7d', description: 'Relative time window when day/since/until are omitted; use all to disable' },
4719
+ provider: { type: 'string', description: 'Filter by provider' },
4720
+ model: { type: 'string', description: 'Filter by model id' },
4721
+ 'task-id': { type: 'string', description: 'Filter by task id' },
4722
+ 'session-id': { type: 'string', description: 'Filter by session id' },
4723
+ limit: { type: 'string', description: 'Maximum recent records to include in the report; defaults to 20' },
4724
+ json: { type: 'boolean', default: false },
4725
+ },
4726
+ run({ args }) {
4727
+ const limit = parsePositiveIntArg(args.limit, '--limit');
4728
+ const sinceDays = args.day || args.since || args.until ? undefined : parseSinceDays(args['since-days']) ?? 7;
4729
+ const since = args.since
4730
+ ? String(args.since)
4731
+ : sinceDays
4732
+ ? new Date(Date.now() - sinceDays * 24 * 60 * 60 * 1000).toISOString()
4733
+ : undefined;
4734
+ const report = new ModelUsageLedger(SCALE_DIR).report({
4735
+ day: args.day ? String(args.day) : undefined,
4736
+ since,
4737
+ until: args.until ? String(args.until) : undefined,
4738
+ provider: args.provider ? String(args.provider) : undefined,
4739
+ model: args.model ? String(args.model) : undefined,
4740
+ taskId: args['task-id'] ? String(args['task-id']) : undefined,
4741
+ sessionId: args['session-id'] ? String(args['session-id']) : undefined,
4742
+ limit,
4743
+ });
4744
+ if (args.json) {
4745
+ console.log(JSON.stringify(report, null, 2));
4746
+ return;
4747
+ }
4748
+ console.log('SCALE Token Report');
4749
+ if (report.filters.day)
4750
+ console.log(` Day: ${report.filters.day}`);
4751
+ else if (report.filters.since || report.filters.until)
4752
+ console.log(` Window: ${report.filters.since ?? '-inf'} -> ${report.filters.until ?? 'now'}`);
4753
+ console.log(` Records: ${report.summary.totalRecords}`);
4754
+ console.log(` Tokens: input ${report.summary.totalInputTokens}, output ${report.summary.totalOutputTokens}, total ${report.summary.totalTokens}`);
4755
+ console.log(` Cache: eligible ${report.summary.cacheEligibleTokens}, create ${report.summary.cacheCreationInputTokens}, read ${report.summary.cacheReadInputTokens}, cached ${report.summary.cachedTokens}, saved ${report.summary.cacheSavingsTokens}`);
4756
+ if (report.summary.estimatedCostUsd !== undefined)
4757
+ console.log(` Estimated cost: $${report.summary.estimatedCostUsd.toFixed(6)}`);
4758
+ for (const row of report.byProvider.slice(0, 5)) {
4759
+ console.log(` Provider ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens, ${row.cacheSavingsTokens} saved`);
4760
+ }
4761
+ for (const row of report.byModel.slice(0, 5)) {
4762
+ console.log(` Model ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens`);
4763
+ }
4764
+ for (const row of report.byTask.slice(0, 5)) {
4765
+ console.log(` Task ${row.key}: ${row.records} record(s), ${row.totalTokens} total tokens`);
4766
+ }
4767
+ for (const row of report.records.slice(0, 10)) {
4768
+ console.log(` Recent ${row.timestamp}: ${row.provider}/${row.model ?? 'unknown'} task=${row.taskId ?? '-'} total=${row.totalTokens}`);
4769
+ }
4770
+ },
4771
+ });
4772
+ const token = defineCommand({
4773
+ meta: { name: 'token', description: 'Record and audit real model token usage' },
4774
+ subCommands: { record: tokenRecord, report: tokenReport },
4775
+ });
4419
4776
  const runtimeStart = defineCommand({
4420
4777
  meta: { name: 'start', description: 'Start a runtime session ledger' },
4421
4778
  args: {
@@ -4483,6 +4840,18 @@ const runtimeRecord = defineCommand({
4483
4840
  'exit-code': { type: 'string', description: 'Exit code when applicable' },
4484
4841
  summary: { type: 'string', required: true, description: 'Short output summary' },
4485
4842
  artifacts: { type: 'string', description: 'Comma-separated artifact paths' },
4843
+ provider: { type: 'string', description: 'Optional model provider when attaching model usage: anthropic, openai, codex, etc.' },
4844
+ model: { type: 'string', description: 'Optional model id when attaching model usage' },
4845
+ 'usage-json': { type: 'string', description: 'Raw provider response or usage JSON to normalize into the usage ledger' },
4846
+ 'usage-file': { type: 'string', description: 'Path to a JSON file containing a raw provider response or usage payload' },
4847
+ 'input-tokens': { type: 'string', description: 'Explicit input token count; overrides usage JSON when provided' },
4848
+ 'output-tokens': { type: 'string', description: 'Explicit output token count; overrides usage JSON when provided' },
4849
+ 'cache-eligible-tokens': { type: 'string', description: 'Explicit cache-eligible token count' },
4850
+ 'cache-creation-input-tokens': { type: 'string', description: 'Explicit Anthropic cache creation token count' },
4851
+ 'cache-read-input-tokens': { type: 'string', description: 'Explicit Anthropic cache read token count' },
4852
+ 'cached-tokens': { type: 'string', description: 'Explicit OpenAI cached token count' },
4853
+ 'estimated-cost-usd': { type: 'string', description: 'Optional estimated cost in USD' },
4854
+ timestamp: { type: 'string', description: 'Optional ISO timestamp for the usage record' },
4486
4855
  'metadata-json': { type: 'string', default: '{}', description: 'Additional JSON metadata' },
4487
4856
  json: { type: 'boolean', default: false },
4488
4857
  },
@@ -4527,13 +4896,24 @@ const runtimeRecord = defineCommand({
4527
4896
  },
4528
4897
  });
4529
4898
  }
4899
+ const usageRecord = hasModelUsageArgs(args)
4900
+ ? new ModelUsageLedger(SCALE_DIR).record(buildModelUsageRecordInput(args, {
4901
+ taskId: record.taskId,
4902
+ sessionId: record.sessionId,
4903
+ }))
4904
+ : undefined;
4530
4905
  if (args.json) {
4531
- console.log(JSON.stringify(record, null, 2));
4906
+ console.log(JSON.stringify(usageRecord ? { evidence: record, usage: usageRecord } : record, null, 2));
4532
4907
  return;
4533
4908
  }
4534
4909
  console.log(`Runtime evidence recorded: ${record.id}`);
4535
4910
  console.log(` Status: ${record.status}`);
4536
4911
  console.log(` Kind: ${record.kind}`);
4912
+ if (usageRecord) {
4913
+ console.log(` Model usage: ${usageRecord.provider}/${usageRecord.model ?? 'unknown'} ${usageRecord.totalTokens} total tokens`);
4914
+ if (usageRecord.cacheSavingsTokens > 0)
4915
+ console.log(` Cache savings: ${usageRecord.cacheSavingsTokens} tokens`);
4916
+ }
4537
4917
  if (record.redactionApplied)
4538
4918
  console.log(' Redaction: applied');
4539
4919
  },
@@ -6153,6 +6533,7 @@ const main = defineCommand({
6153
6533
  workflow,
6154
6534
  evidence,
6155
6535
  runtime,
6536
+ token,
6156
6537
  memory,
6157
6538
  diagnose,
6158
6539
  hunt,
@@ -6169,5 +6550,19 @@ const main = defineCommand({
6169
6550
  config,
6170
6551
  },
6171
6552
  });
6553
+ normalizeUpgradeRootOptionValues(process.argv);
6172
6554
  runMain(main);
6555
+ function normalizeUpgradeRootOptionValues(argv) {
6556
+ const upgradeIndex = argv.findIndex((value, index) => index > 1 && value === 'upgrade');
6557
+ if (upgradeIndex < 0)
6558
+ return;
6559
+ for (let index = upgradeIndex + 1; index < argv.length - 1; index += 1) {
6560
+ if (!['--dir', '--target-version', '--lang'].includes(argv[index]))
6561
+ continue;
6562
+ const value = argv[index + 1];
6563
+ if (!value || value.startsWith('--'))
6564
+ continue;
6565
+ argv.splice(index, 2, `${argv[index]}=${value}`);
6566
+ }
6567
+ }
6173
6568
  //# sourceMappingURL=cli.js.map