@hongmaple0820/scale-engine 0.27.1 → 0.29.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.
@@ -7,6 +7,7 @@ import { MemoryFabric, recallMemoryProviders, } from '../memory/index.js';
7
7
  import { createSkillPlan, loadSkillRoutingPolicy, } from '../skills/routing/index.js';
8
8
  import { runSafeCommand } from '../tools/SafeCommandRunner.js';
9
9
  import { SCALE_ENGINE_VERSION } from '../version.js';
10
+ import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
10
11
  import { RuntimeEvidenceLedger } from './RuntimeEvidenceLedger.js';
11
12
  export async function createAiOsPlan(input) {
12
13
  const projectDir = resolve(input.projectDir ?? process.cwd());
@@ -337,6 +338,392 @@ export function createAiOsDoctor(input = {}) {
337
338
  nextActions: aiOsDoctorNextActions({ status, checks, dashboard, benchmark, lang }),
338
339
  };
339
340
  }
341
+ export async function createAiOsAdoption(input) {
342
+ const projectDir = resolve(input.projectDir ?? process.cwd());
343
+ const scaleDir = input.scaleDir ?? '.scale';
344
+ const scaleRoot = resolveScaleRoot(projectDir, scaleDir);
345
+ const generatedAt = new Date().toISOString();
346
+ const migration = createAiOsMigration({ projectDir, scaleDir });
347
+ const run = await createAiOsRun({
348
+ ...input,
349
+ projectDir,
350
+ scaleDir,
351
+ taskId: input.taskId ?? `AIOS-ADOPT-${Date.now()}`,
352
+ mode: 'dry-run',
353
+ });
354
+ const benchmark = await createAiOsBenchmark({
355
+ projectDir,
356
+ scaleDir,
357
+ budget: input.budget,
358
+ });
359
+ const doctor = createAiOsDoctor({
360
+ projectDir,
361
+ scaleDir,
362
+ benchmarkMaxAgeHours: input.benchmarkMaxAgeHours,
363
+ lang: input.lang,
364
+ });
365
+ const phases = [
366
+ {
367
+ id: 'migrate',
368
+ status: migration.status === 'migrated' || migration.status === 'compatible' ? 'passed' : 'blocked',
369
+ summary: migration.status === 'migrated'
370
+ ? `Created ${migration.created.length} AI OS runtime path(s).`
371
+ : 'AI OS runtime paths were already compatible.',
372
+ evidence: [migration.files.migrationReport, ...migration.created, ...migration.existing],
373
+ },
374
+ {
375
+ id: 'first-run',
376
+ status: run.status === 'ready' ? 'passed' : 'blocked',
377
+ summary: `Created first ${run.mode} AI OS run report with ${run.steps.length} step(s).`,
378
+ evidence: [run.artifacts.runReport, ...run.evidence.produced],
379
+ },
380
+ {
381
+ id: 'benchmark',
382
+ status: benchmark.summary.scenarios > 0 ? 'passed' : 'blocked',
383
+ summary: `Created AI OS benchmark report with ${benchmark.summary.scenarios} scenario(s).`,
384
+ evidence: [benchmark.artifacts.benchmarkReport],
385
+ },
386
+ {
387
+ id: 'doctor',
388
+ status: doctor.status === 'ready' ? 'passed' : doctor.status === 'warning' ? 'warning' : 'blocked',
389
+ summary: `AI OS doctor status is ${doctor.status}: ${doctor.summary.passedChecks}/${doctor.summary.totalChecks} checks passed.`,
390
+ evidence: doctor.checks.flatMap(check => check.evidence),
391
+ },
392
+ ];
393
+ const status = phases.some(phase => phase.status === 'blocked')
394
+ ? 'blocked'
395
+ : phases.some(phase => phase.status === 'warning') ? 'warning' : 'ready';
396
+ const adoptionReport = resolveAdoptionReportPath(projectDir, scaleDir);
397
+ const report = {
398
+ version: SCALE_ENGINE_VERSION,
399
+ generatedAt,
400
+ status,
401
+ projectDir,
402
+ scaleRoot,
403
+ phases,
404
+ migration,
405
+ run,
406
+ benchmark,
407
+ doctor,
408
+ artifacts: {
409
+ migrationReport: migration.files.migrationReport,
410
+ runReport: run.artifacts.runReport,
411
+ benchmarkReport: benchmark.artifacts.benchmarkReport,
412
+ adoptionReport,
413
+ },
414
+ warnings: [...migration.warnings, ...doctor.warnings],
415
+ nextActions: aiOsAdoptionNextActions(status, input.lang ?? 'en'),
416
+ };
417
+ writeFileSync(adoptionReport, JSON.stringify(report, null, 2), 'utf-8');
418
+ return report;
419
+ }
420
+ export function createAiOsStatus(input = {}) {
421
+ const projectDir = resolve(input.projectDir ?? process.cwd());
422
+ const scaleDir = input.scaleDir ?? '.scale';
423
+ const lang = input.lang ?? 'en';
424
+ const scaleRoot = resolveScaleRoot(projectDir, scaleDir);
425
+ const warnings = [];
426
+ const runsDir = resolveRunsDir(projectDir, scaleDir);
427
+ const runReports = readAiOsRunReports(runsDir, warnings);
428
+ const dashboard = createAiOsDashboard({ projectDir, scaleDir });
429
+ const doctor = createAiOsDoctor({
430
+ projectDir,
431
+ scaleDir,
432
+ benchmarkMaxAgeHours: input.benchmarkMaxAgeHours,
433
+ lang,
434
+ });
435
+ const requiredDirs = [
436
+ join(scaleRoot, 'ai-os'),
437
+ join(scaleRoot, 'ai-os', 'runs'),
438
+ join(scaleRoot, 'ai-os', 'benchmarks'),
439
+ join(scaleRoot, 'ai-os', 'migrations'),
440
+ ];
441
+ const missingDirs = requiredDirs.filter(dir => !existsSync(dir)).map(dir => normalizeProjectPath(projectDir, dir));
442
+ const runEvidence = runReports.map(report => report.artifacts.runReport);
443
+ const verificationEvidence = runReports
444
+ .filter(report => report.verification.commands.length > 0)
445
+ .flatMap(report => [report.artifacts.runReport, ...report.verification.commands.map(command => command.evidenceId)]);
446
+ const verificationRecommendations = buildVerificationRecommendations(projectDir, scaleDir, lang);
447
+ const benchmarkReport = resolveBenchmarkReportPath(projectDir, scaleDir);
448
+ const adoptionReport = resolveAdoptionReportPath(projectDir, scaleDir);
449
+ const intelligence = buildAiOsIntelligenceReport({
450
+ projectDir,
451
+ scaleDir,
452
+ runReports,
453
+ benchmark: readAiOsBenchmarkReport(benchmarkReport, warnings),
454
+ benchmarkStatus: doctor.benchmark.status,
455
+ benchmarkReport,
456
+ lang,
457
+ });
458
+ const checks = [
459
+ {
460
+ id: 'runtime-dirs',
461
+ title: 'Runtime directories',
462
+ status: missingDirs.length === 0 ? 'ready' : 'blocked',
463
+ summary: missingDirs.length === 0
464
+ ? 'AI OS runtime directories exist.'
465
+ : `${missingDirs.length} AI OS runtime director${missingDirs.length === 1 ? 'y is' : 'ies are'} missing.`,
466
+ evidence: missingDirs.length === 0 ? requiredDirs.map(dir => normalizeProjectPath(projectDir, dir)) : missingDirs,
467
+ },
468
+ {
469
+ id: 'plan-evidence',
470
+ title: 'Plan evidence',
471
+ status: runReports.length > 0 ? 'ready' : 'blocked',
472
+ summary: runReports.length > 0
473
+ ? `${runReports.length} run report(s) include embedded AI OS plans.`
474
+ : 'No AI OS plan evidence is persisted through a run report.',
475
+ evidence: runEvidence,
476
+ },
477
+ {
478
+ id: 'run-evidence',
479
+ title: 'Run evidence',
480
+ status: runReports.length > 0 ? 'ready' : 'blocked',
481
+ summary: runReports.length > 0
482
+ ? `${runReports.length} AI OS run report(s) found.`
483
+ : 'No AI OS run reports found.',
484
+ evidence: runEvidence,
485
+ },
486
+ {
487
+ id: 'verification-evidence',
488
+ title: 'Verification evidence',
489
+ status: verificationEvidence.length > 0 ? 'ready' : 'blocked',
490
+ summary: verificationEvidence.length > 0
491
+ ? `${verificationEvidence.length} guarded verification evidence reference(s) found.`
492
+ : 'No guarded verification evidence found.',
493
+ evidence: verificationEvidence,
494
+ },
495
+ {
496
+ id: 'dashboard-health',
497
+ title: 'Dashboard health',
498
+ status: dashboard.health.status === 'healthy'
499
+ ? 'ready'
500
+ : dashboard.health.status === 'empty' || dashboard.health.status === 'blocked' ? 'blocked' : 'warning',
501
+ summary: `${dashboard.health.status} (${dashboard.health.score}): ${dashboard.health.reasons.join('; ')}`,
502
+ evidence: [runsDir],
503
+ },
504
+ {
505
+ id: 'benchmark-evidence',
506
+ title: 'Benchmark evidence',
507
+ status: doctor.benchmark.status === 'fresh' ? 'ready' : doctor.benchmark.status === 'stale' ? 'warning' : 'blocked',
508
+ summary: summarizeBenchmarkDoctor(doctor.benchmark),
509
+ evidence: [benchmarkReport],
510
+ },
511
+ {
512
+ id: 'adoption-evidence',
513
+ title: 'Adoption evidence',
514
+ status: existsSync(adoptionReport) ? 'ready' : 'blocked',
515
+ summary: existsSync(adoptionReport)
516
+ ? 'AI OS adoption report exists.'
517
+ : 'No AI OS adoption report found.',
518
+ evidence: [adoptionReport],
519
+ },
520
+ ];
521
+ const summary = {
522
+ total: checks.length,
523
+ ready: checks.filter(check => check.status === 'ready').length,
524
+ warning: checks.filter(check => check.status === 'warning').length,
525
+ blocked: checks.filter(check => check.status === 'blocked').length,
526
+ };
527
+ const status = summary.blocked > 0 ? 'blocked' : summary.warning > 0 ? 'warning' : 'ready';
528
+ return {
529
+ version: SCALE_ENGINE_VERSION,
530
+ generatedAt: new Date().toISOString(),
531
+ status,
532
+ projectDir,
533
+ scaleRoot,
534
+ checks,
535
+ summary,
536
+ dashboard,
537
+ doctor,
538
+ intelligence,
539
+ verificationRecommendations,
540
+ nextActions: aiOsStatusNextActions(status, checks, lang, verificationRecommendations),
541
+ warnings: [...warnings, ...doctor.warnings],
542
+ };
543
+ }
544
+ function buildAiOsIntelligenceReport(input) {
545
+ const runMemoryItems = input.runReports.flatMap(report => report.plan.memory.items);
546
+ const benchmarkMemoryItems = input.benchmark?.summary.totalMemoryItems ?? 0;
547
+ const runProviders = input.runReports.flatMap(report => report.plan.memory.selectedProviders);
548
+ const benchmarkProviders = input.benchmark?.scenarios.flatMap(scenario => scenario.metrics.selectedProviders) ?? [];
549
+ const selectedProviders = [...new Set([...runProviders, ...benchmarkProviders])].sort();
550
+ const runTokenSavings = input.runReports.reduce((sum, report) => sum + (report.plan.context.compiler?.estimatedTokenSavings ?? 0), 0);
551
+ const benchmarkTokenSavings = input.benchmark?.summary.totalEstimatedTokenSavings ?? 0;
552
+ const estimatedTokenSavings = runTokenSavings + benchmarkTokenSavings;
553
+ const runSkillSteps = input.runReports.reduce((sum, report) => sum + report.plan.skillPlan.executionPlan.steps.length, 0);
554
+ const benchmarkSkillSteps = input.benchmark?.summary.totalSkillSteps ?? 0;
555
+ const skillSteps = runSkillSteps + benchmarkSkillSteps;
556
+ const totalMemoryItems = runMemoryItems.length + benchmarkMemoryItems;
557
+ const memoryQuality = summarizeMemoryQuality(runMemoryItems);
558
+ const contextQuality = summarizeContextQuality(input.runReports);
559
+ const contextSignalStatus = contextQuality.compressionRisk === 'high'
560
+ ? 'warning'
561
+ : estimatedTokenSavings > 0 ? 'ready' : input.runReports.length > 0 || input.benchmark ? 'warning' : 'blocked';
562
+ const memoryEvidence = [
563
+ ...runMemoryItems.map(item => `${item.provider}:${item.id}`),
564
+ ...(benchmarkMemoryItems > 0 ? [`benchmark:${input.benchmarkReport}:${benchmarkMemoryItems}`] : []),
565
+ ];
566
+ const contextEvidence = [
567
+ ...input.runReports.map(report => `${report.artifacts.runReport}:saved=${report.plan.context.compiler?.estimatedTokenSavings ?? 0}`),
568
+ ...(input.benchmark ? [`${input.benchmarkReport}:saved=${input.benchmark.summary.totalEstimatedTokenSavings}`] : []),
569
+ ];
570
+ const skillEvidence = [
571
+ ...input.runReports.flatMap(report => report.plan.skillPlan.executionPlan.steps.map(step => `${report.artifacts.runReport}:${step.id}`)),
572
+ ...(input.benchmark ? [`${input.benchmarkReport}:steps=${input.benchmark.summary.totalSkillSteps}`] : []),
573
+ ];
574
+ const benchmarkEvidence = input.benchmark ? [
575
+ `${input.benchmarkReport}:scenarios=${input.benchmark.summary.scenarios}`,
576
+ `${input.benchmarkReport}:memory=${input.benchmark.summary.totalMemoryItems}`,
577
+ `${input.benchmarkReport}:skills=${input.benchmark.summary.totalSkillSteps}`,
578
+ ] : [input.benchmarkReport];
579
+ const signals = [
580
+ {
581
+ id: 'memory-recall',
582
+ status: totalMemoryItems > 0 ? 'ready' : selectedProviders.length > 0 ? 'warning' : 'blocked',
583
+ summary: totalMemoryItems > 0
584
+ ? `${totalMemoryItems} memory item(s) recalled through ${selectedProviders.join(', ') || 'configured providers'}; quality ${memoryQuality.score}/100.`
585
+ : selectedProviders.length > 0
586
+ ? `Memory providers were selected (${selectedProviders.join(', ')}) but no relevant item was recalled.`
587
+ : 'No memory recall evidence found in AI OS runs or benchmarks.',
588
+ evidence: memoryEvidence,
589
+ recommendations: totalMemoryItems > 0
590
+ ? ['Keep recording memory item ids with every run so later context assembly can explain recall.']
591
+ : ['Run an AI OS task that should match durable project memory before claiming memory intelligence.'],
592
+ },
593
+ {
594
+ id: 'context-savings',
595
+ status: contextSignalStatus,
596
+ summary: estimatedTokenSavings > 0
597
+ ? `${estimatedTokenSavings} estimated token(s) saved by context compilation evidence; compression risk ${contextQuality.compressionRisk}.`
598
+ : 'Context compiler evidence exists but has not shown measurable token savings yet.',
599
+ evidence: contextEvidence,
600
+ recommendations: contextQuality.evidenceLossWarnings.length > 0
601
+ ? ['Review omitted evidence-bearing context before claiming the task has enough context.']
602
+ : estimatedTokenSavings > 0
603
+ ? ['Track savings deltas across releases before publishing token reduction claims.']
604
+ : ['Add larger representative tasks to benchmark context slicing and token savings.'],
605
+ },
606
+ {
607
+ id: 'skill-routing',
608
+ status: skillSteps > 0 ? 'ready' : input.runReports.length > 0 || input.benchmark ? 'warning' : 'blocked',
609
+ summary: skillSteps > 0
610
+ ? `${skillSteps} skill routing step(s) planned across runs and benchmark scenarios.`
611
+ : 'No skill routing step evidence found.',
612
+ evidence: skillEvidence,
613
+ recommendations: skillSteps > 0
614
+ ? ['Use skill routing evidence in reviews to check why a skill, MCP, or CLI path was selected.']
615
+ : ['Create a task with files or services that should trigger required skill routing.'],
616
+ },
617
+ {
618
+ id: 'benchmark-intelligence',
619
+ status: input.benchmark && input.benchmarkStatus === 'fresh'
620
+ ? 'ready'
621
+ : input.benchmark && input.benchmarkStatus === 'stale' ? 'warning' : 'blocked',
622
+ summary: input.benchmark
623
+ ? `${input.benchmark.summary.scenarios} benchmark scenario(s); benchmark status ${input.benchmarkStatus}.`
624
+ : 'No AI OS benchmark report available for intelligence metrics.',
625
+ evidence: benchmarkEvidence,
626
+ recommendations: input.benchmark && input.benchmarkStatus === 'fresh'
627
+ ? ['Use intelligence signals alongside benchmark deltas for release readiness reviews.']
628
+ : ['Run `scale ai-os benchmark --json` to refresh memory/context/skill intelligence metrics.'],
629
+ },
630
+ ];
631
+ const summary = {
632
+ ready: signals.filter(signal => signal.status === 'ready').length,
633
+ warning: signals.filter(signal => signal.status === 'warning').length,
634
+ blocked: signals.filter(signal => signal.status === 'blocked').length,
635
+ totalMemoryItems,
636
+ selectedProviders,
637
+ memoryQuality,
638
+ contextQuality,
639
+ estimatedTokenSavings,
640
+ skillSteps,
641
+ };
642
+ const status = summary.blocked > 0 ? 'blocked' : summary.warning > 0 ? 'warning' : 'ready';
643
+ const nextActions = aiOsIntelligenceNextActions(status, signals, input.lang);
644
+ return { status, summary, signals, nextActions };
645
+ }
646
+ function summarizeContextQuality(runReports) {
647
+ const omitted = runReports.flatMap(report => report.plan.context.omitted.map(item => {
648
+ const section = report.plan.context.sections.find(candidate => candidate.id === item.id);
649
+ return {
650
+ ...item,
651
+ category: section?.category,
652
+ runReport: report.artifacts.runReport,
653
+ };
654
+ }));
655
+ const totalOmittedTokens = omitted.reduce((sum, item) => sum + item.estimatedTokens, 0);
656
+ const highestOmittedTokens = omitted.reduce((max, item) => Math.max(max, item.estimatedTokens), 0);
657
+ const evidenceLossWarnings = omitted
658
+ .filter(item => item.category === 'evidence' || item.id.includes('evidence'))
659
+ .map(item => `${item.id} omitted from ${item.runReport} (${item.estimatedTokens} tokens; ${item.reason}).`);
660
+ const compressionRisk = evidenceLossWarnings.length > 0
661
+ ? 'high'
662
+ : omitted.length > 0 ? 'medium' : 'low';
663
+ return {
664
+ omittedSections: omitted.length,
665
+ totalOmittedTokens,
666
+ evidenceLossWarnings,
667
+ highestOmittedTokens,
668
+ compressionRisk,
669
+ };
670
+ }
671
+ function summarizeMemoryQuality(items) {
672
+ if (items.length === 0) {
673
+ return {
674
+ score: 0,
675
+ evidenceBackedItems: 0,
676
+ missingEvidenceItems: 0,
677
+ lowConfidenceItems: 0,
678
+ averageConfidence: 0,
679
+ averageRelevance: 0,
680
+ };
681
+ }
682
+ const evidenceBackedItems = items.filter(item => item.evidencePaths.length > 0).length;
683
+ const missingEvidenceItems = items.length - evidenceBackedItems;
684
+ const lowConfidenceItems = items.filter(item => item.confidence < 0.7).length;
685
+ const averageConfidence = average(items.map(item => clampUnit(item.confidence)));
686
+ const averageRelevance = average(items.map(item => clampUnit(item.score)));
687
+ const evidenceRatio = evidenceBackedItems / items.length;
688
+ const lowConfidenceRatio = lowConfidenceItems / items.length;
689
+ const score = Math.max(0, Math.round((averageConfidence * 40) + (averageRelevance * 30) + (evidenceRatio * 30) - (lowConfidenceRatio * 10)));
690
+ return {
691
+ score,
692
+ evidenceBackedItems,
693
+ missingEvidenceItems,
694
+ lowConfidenceItems,
695
+ averageConfidence: roundMetric(averageConfidence),
696
+ averageRelevance: roundMetric(averageRelevance),
697
+ };
698
+ }
699
+ function average(values) {
700
+ if (values.length === 0)
701
+ return 0;
702
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
703
+ }
704
+ function clampUnit(value) {
705
+ if (!Number.isFinite(value))
706
+ return 0;
707
+ return Math.max(0, Math.min(1, value));
708
+ }
709
+ function roundMetric(value) {
710
+ return Number(value.toFixed(3));
711
+ }
712
+ function aiOsIntelligenceNextActions(status, signals, lang) {
713
+ const actions = [];
714
+ if (signals.some(signal => signal.status === 'ready')) {
715
+ actions.push('Use intelligence signals during release review to prove memory, context, and skill routing gains.');
716
+ }
717
+ if (status === 'ready')
718
+ return actions;
719
+ const blocked = signals.filter(signal => signal.status === 'blocked').map(signal => signal.id);
720
+ if (lang === 'zh') {
721
+ actions.push(`Refresh AI OS intelligence evidence for: ${blocked.join(', ') || 'warning signals'}.`);
722
+ return actions;
723
+ }
724
+ actions.push(`Refresh AI OS intelligence evidence for: ${blocked.join(', ') || 'warning signals'}.`);
725
+ return actions;
726
+ }
340
727
  function buildRunSteps(plan) {
341
728
  const steps = new Map();
342
729
  const upsert = (step) => steps.set(step.id, step);
@@ -586,6 +973,144 @@ function resolveRunsDir(projectDir, scaleDir) {
586
973
  function resolveBenchmarkReportPath(projectDir, scaleDir) {
587
974
  return join(resolveScaleRoot(projectDir, scaleDir), 'ai-os', 'benchmarks', 'latest.json');
588
975
  }
976
+ function resolveAdoptionReportPath(projectDir, scaleDir) {
977
+ return join(resolveScaleRoot(projectDir, scaleDir), 'ai-os', 'adoption.json');
978
+ }
979
+ function aiOsAdoptionNextActions(status, lang) {
980
+ if (lang === 'zh') {
981
+ if (status === 'ready')
982
+ return ['AI OS runtime 接入完成;后续真实任务使用 `scale ai-os run --mode guarded`。'];
983
+ if (status === 'warning')
984
+ return ['AI OS runtime 已可用但仍有警告;先运行 `scale ai-os doctor --json --lang zh` 处理剩余项。'];
985
+ return ['AI OS runtime 接入被阻断;查看 adoption report 和 `scale ai-os doctor --json --lang zh` 的失败项。'];
986
+ }
987
+ if (status === 'ready')
988
+ return ['AI OS runtime adoption is complete; use `scale ai-os run --mode guarded` for governed work.'];
989
+ if (status === 'warning')
990
+ return ['AI OS runtime is usable with warnings; run `scale ai-os doctor --json --lang en` and resolve remaining items.'];
991
+ return ['AI OS runtime adoption is blocked; inspect the adoption report and `scale ai-os doctor --json --lang en` failures.'];
992
+ }
993
+ function aiOsStatusNextActions(status, checks, lang, verificationRecommendations) {
994
+ const blocked = new Set(checks.filter(check => check.status === 'blocked').map(check => check.id));
995
+ const firstVerificationCommand = verificationRecommendations[0]?.command ?? '<command>';
996
+ if (lang === 'zh') {
997
+ if (status === 'ready')
998
+ return ['AI OS 闭环已就绪,可使用 `scale ai-os run --mode guarded` 执行受治理任务。'];
999
+ if (blocked.has('runtime-dirs') || blocked.has('adoption-evidence')) {
1000
+ return ['运行 `scale ai-os adopt --task "接入 AI OS runtime" --lang zh` 生成运行态、首份 dry-run、benchmark 和 doctor 报告。'];
1001
+ }
1002
+ if (blocked.has('verification-evidence'))
1003
+ return ['运行 `scale ai-os run --mode guarded --verify "<command>"` 生成受治理验证证据。'];
1004
+ if (blocked.has('benchmark-evidence'))
1005
+ return ['运行 `scale ai-os benchmark --json` 生成闭环 benchmark 证据。'];
1006
+ return ['查看 status checks,补齐 blocked 项后重新运行 `scale ai-os status --lang zh`。'];
1007
+ }
1008
+ if (status === 'ready')
1009
+ return ['AI OS closed loop is ready for guarded project work.'];
1010
+ if (blocked.has('runtime-dirs') || blocked.has('adoption-evidence')) {
1011
+ return ['Run `scale ai-os adopt --task "Adopt AI OS runtime" --lang en` to create runtime state, first dry-run, benchmark, and doctor reports.'];
1012
+ }
1013
+ if (blocked.has('verification-evidence'))
1014
+ return [`Run \`scale ai-os run --mode guarded --verify "${escapeCliDoubleQuoted(firstVerificationCommand)}"\` to produce governed verification evidence.`];
1015
+ if (blocked.has('benchmark-evidence'))
1016
+ return ['Run `scale ai-os benchmark --json` to produce closed-loop benchmark evidence.'];
1017
+ return ['Inspect status checks, resolve blocked items, then rerun `scale ai-os status --lang en`.'];
1018
+ }
1019
+ const VERIFICATION_COMMAND_ORDER = ['build', 'lint', 'test', 'smoke', 'coverage'];
1020
+ function buildVerificationRecommendations(projectDir, scaleDir, lang) {
1021
+ const recommendations = [];
1022
+ const seen = new Set();
1023
+ const add = (recommendation) => {
1024
+ const key = `${recommendation.command}\n${recommendation.service ?? ''}`;
1025
+ if (seen.has(key))
1026
+ return;
1027
+ seen.add(key);
1028
+ recommendations.push(recommendation);
1029
+ };
1030
+ try {
1031
+ const resolved = resolveVerificationTargets({ projectDir, scaleDir, service: 'all' });
1032
+ for (const target of resolved.targets) {
1033
+ for (const name of VERIFICATION_COMMAND_ORDER) {
1034
+ const command = target.config[name];
1035
+ if (!command)
1036
+ continue;
1037
+ add({
1038
+ command,
1039
+ source: 'verification-profile',
1040
+ reason: verificationRecommendationReason(name, lang),
1041
+ profile: resolved.profileName,
1042
+ service: target.service?.name,
1043
+ });
1044
+ }
1045
+ }
1046
+ }
1047
+ catch {
1048
+ // Best effort only. Status should not fail just because verification config is invalid.
1049
+ }
1050
+ if (recommendations.length > 0)
1051
+ return recommendations;
1052
+ for (const item of packageScriptVerificationCommands(projectDir)) {
1053
+ add({
1054
+ command: item.command,
1055
+ source: 'package-script',
1056
+ reason: verificationRecommendationReason(item.name, lang),
1057
+ });
1058
+ }
1059
+ if (recommendations.length > 0)
1060
+ return recommendations;
1061
+ return [{
1062
+ command: 'scale preflight --preflight-profile quick --json',
1063
+ source: 'fallback',
1064
+ reason: lang === 'zh'
1065
+ ? '未找到验证矩阵或 package script,先运行 SCALE 快速预检生成基础验证证据。'
1066
+ : 'No verification matrix or package script was found; run SCALE quick preflight as baseline evidence.',
1067
+ }];
1068
+ }
1069
+ function packageScriptVerificationCommands(projectDir) {
1070
+ const packageJsonPath = join(projectDir, 'package.json');
1071
+ if (!existsSync(packageJsonPath))
1072
+ return [];
1073
+ try {
1074
+ const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
1075
+ const scripts = parsed.scripts ?? {};
1076
+ const commands = [];
1077
+ if (scripts.build)
1078
+ commands.push({ name: 'build', command: 'npm run build' });
1079
+ if (scripts.lint)
1080
+ commands.push({ name: 'lint', command: 'npm run lint' });
1081
+ if (scripts.test)
1082
+ commands.push({ name: 'test', command: 'npm test' });
1083
+ return commands;
1084
+ }
1085
+ catch {
1086
+ return [];
1087
+ }
1088
+ }
1089
+ function verificationRecommendationReason(name, lang) {
1090
+ if (lang === 'zh') {
1091
+ if (name === 'build')
1092
+ return '构建验证可以证明代码仍可编译并生成发布产物。';
1093
+ if (name === 'lint')
1094
+ return 'Lint 验证可以捕获工程规范和静态质量问题。';
1095
+ if (name === 'test')
1096
+ return '测试验证可以证明核心行为没有回归。';
1097
+ if (name === 'smoke')
1098
+ return '冒烟验证可以证明关键产品路径仍可用。';
1099
+ return '覆盖率验证可以补充测试充分性证据。';
1100
+ }
1101
+ if (name === 'build')
1102
+ return 'Build verification proves the code still compiles and produces releasable artifacts.';
1103
+ if (name === 'lint')
1104
+ return 'Lint verification catches engineering-standard and static-quality issues.';
1105
+ if (name === 'test')
1106
+ return 'Test verification proves core behavior did not regress.';
1107
+ if (name === 'smoke')
1108
+ return 'Smoke verification proves critical product paths still work.';
1109
+ return 'Coverage verification adds evidence for test adequacy.';
1110
+ }
1111
+ function escapeCliDoubleQuoted(value) {
1112
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
1113
+ }
589
1114
  function inspectBenchmarkReport(projectDir, scaleDir, maxAgeHours, warnings) {
590
1115
  const reportPath = resolveBenchmarkReportPath(projectDir, scaleDir);
591
1116
  if (!existsSync(reportPath))
@@ -613,6 +1138,22 @@ function inspectBenchmarkReport(projectDir, scaleDir, maxAgeHours, warnings) {
613
1138
  return { status: 'invalid', reportPath };
614
1139
  }
615
1140
  }
1141
+ function readAiOsBenchmarkReport(reportPath, warnings) {
1142
+ if (!existsSync(reportPath))
1143
+ return undefined;
1144
+ try {
1145
+ const parsed = JSON.parse(readFileSync(reportPath, 'utf-8'));
1146
+ if (!parsed || !parsed.summary || !Array.isArray(parsed.scenarios)) {
1147
+ warnings.push(`Ignored invalid AI OS benchmark report: ${reportPath}`);
1148
+ return undefined;
1149
+ }
1150
+ return parsed;
1151
+ }
1152
+ catch (error) {
1153
+ warnings.push(`Ignored unreadable AI OS benchmark report: ${reportPath} (${error instanceof Error ? error.message : String(error)})`);
1154
+ return undefined;
1155
+ }
1156
+ }
616
1157
  function summarizeBenchmarkDoctor(benchmark) {
617
1158
  if (benchmark.status === 'missing')
618
1159
  return 'No AI OS benchmark report found.';