@neurcode-ai/cli 0.9.28 → 0.9.30

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 (37) hide show
  1. package/LICENSE +201 -0
  2. package/dist/api-client.d.ts +78 -0
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +49 -0
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/apply.d.ts.map +1 -1
  7. package/dist/commands/apply.js +33 -0
  8. package/dist/commands/apply.js.map +1 -1
  9. package/dist/commands/approve.d.ts +11 -0
  10. package/dist/commands/approve.d.ts.map +1 -0
  11. package/dist/commands/approve.js +116 -0
  12. package/dist/commands/approve.js.map +1 -0
  13. package/dist/commands/plan.d.ts.map +1 -1
  14. package/dist/commands/plan.js +244 -64
  15. package/dist/commands/plan.js.map +1 -1
  16. package/dist/commands/prompt.d.ts.map +1 -1
  17. package/dist/commands/prompt.js +71 -4
  18. package/dist/commands/prompt.js.map +1 -1
  19. package/dist/commands/ship.d.ts +2 -0
  20. package/dist/commands/ship.d.ts.map +1 -1
  21. package/dist/commands/ship.js +228 -1
  22. package/dist/commands/ship.js.map +1 -1
  23. package/dist/commands/verify.d.ts +2 -0
  24. package/dist/commands/verify.d.ts.map +1 -1
  25. package/dist/commands/verify.js +253 -18
  26. package/dist/commands/verify.js.map +1 -1
  27. package/dist/index.js +26 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/governance.d.ts +33 -0
  30. package/dist/utils/governance.d.ts.map +1 -0
  31. package/dist/utils/governance.js +68 -0
  32. package/dist/utils/governance.js.map +1 -0
  33. package/dist/utils/manual-approvals.d.ts +20 -0
  34. package/dist/utils/manual-approvals.d.ts.map +1 -0
  35. package/dist/utils/manual-approvals.js +104 -0
  36. package/dist/utils/manual-approvals.js.map +1 -0
  37. package/package.json +12 -9
@@ -58,6 +58,8 @@ const custom_policy_rules_1 = require("../utils/custom-policy-rules");
58
58
  const policy_exceptions_1 = require("../utils/policy-exceptions");
59
59
  const policy_governance_1 = require("../utils/policy-governance");
60
60
  const policy_audit_1 = require("../utils/policy-audit");
61
+ const governance_1 = require("../utils/governance");
62
+ const policy_1 = require("@neurcode-ai/policy");
61
63
  // Import chalk with fallback
62
64
  let chalk;
63
65
  try {
@@ -294,10 +296,56 @@ function resolveAuditIntegrityStatus(requireIntegrity, auditIntegrity) {
294
296
  * Execute policy-only verification (General Governance mode)
295
297
  * Returns the exit code to use
296
298
  */
297
- async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client) {
299
+ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner) {
298
300
  if (!options.json) {
299
301
  console.log(chalk.cyan('šŸ›”ļø General Governance mode (policy only, no plan linked)\n'));
300
302
  }
303
+ const governanceAnalysis = (0, governance_1.evaluateGovernance)({
304
+ projectRoot,
305
+ task: 'Policy-only verification',
306
+ expectedFiles: [],
307
+ diffFiles,
308
+ contextCandidates: diffFiles.map((file) => file.path),
309
+ orgGovernance: orgGovernanceSettings,
310
+ signingKey: aiLogSigningKey,
311
+ signer: aiLogSigner,
312
+ });
313
+ const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
314
+ if (contextPolicyViolations.length > 0) {
315
+ const message = `Context policy violation: ${contextPolicyViolations.map((item) => item.file).join(', ')}`;
316
+ if (options.json) {
317
+ console.log(JSON.stringify({
318
+ grade: 'F',
319
+ score: 0,
320
+ verdict: 'FAIL',
321
+ violations: contextPolicyViolations.map((item) => ({
322
+ file: item.file,
323
+ rule: `context_policy:${item.rule}`,
324
+ severity: 'block',
325
+ message: item.reason,
326
+ })),
327
+ message,
328
+ scopeGuardPassed: false,
329
+ bloatCount: 0,
330
+ bloatFiles: [],
331
+ plannedFilesModified: 0,
332
+ totalPlannedFiles: 0,
333
+ adherenceScore: 0,
334
+ mode: 'policy_only',
335
+ policyOnly: true,
336
+ policyOnlySource: source,
337
+ ...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
338
+ }, null, 2));
339
+ }
340
+ else {
341
+ console.log(chalk.red('āŒ Context policy violation detected (policy-only mode).'));
342
+ contextPolicyViolations.forEach((item) => {
343
+ console.log(chalk.red(` • ${item.file}: ${item.reason}`));
344
+ });
345
+ console.log(chalk.dim(`\n${message}`));
346
+ }
347
+ return 2;
348
+ }
301
349
  let policyViolations = [];
302
350
  let policyDecision = 'allow';
303
351
  const requirePolicyLock = options.requirePolicyLock === true || isEnabledFlag(process.env.NEURCODE_VERIFY_REQUIRE_POLICY_LOCK);
@@ -384,6 +432,8 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
384
432
  adherenceScore: 0,
385
433
  mode: 'policy_only',
386
434
  policyOnly: true,
435
+ policyOnlySource: source,
436
+ ...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
387
437
  policyLock: {
388
438
  enforced: true,
389
439
  matched: false,
@@ -514,6 +564,8 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
514
564
  adherenceScore: score,
515
565
  mode: 'policy_only',
516
566
  policyOnly: true,
567
+ policyOnlySource: source,
568
+ ...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
517
569
  policyLock: {
518
570
  enforced: policyLockEvaluation.enforced,
519
571
  matched: policyLockEvaluation.matched,
@@ -553,6 +605,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
553
605
  if (governance.audit.requireIntegrity && !auditIntegrityStatus.valid) {
554
606
  console.log(chalk.red(' Policy audit integrity check failed'));
555
607
  }
608
+ displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
556
609
  console.log(chalk.dim(`\n${message}`));
557
610
  }
558
611
  return effectiveVerdict === 'FAIL' ? 2 : effectiveVerdict === 'WARN' ? 1 : 0;
@@ -608,6 +661,31 @@ async function verifyCommand(options) {
608
661
  orgId: (0, state_1.getOrgId)(),
609
662
  projectId: projectId || null,
610
663
  };
664
+ const aiLogSigningKey = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY ||
665
+ process.env.NEURCODE_AI_LOG_SIGNING_KEY ||
666
+ null;
667
+ const aiLogSigner = process.env.NEURCODE_GOVERNANCE_SIGNER || process.env.USER || 'neurcode-cli';
668
+ let orgGovernanceSettings = null;
669
+ if (config.apiKey) {
670
+ try {
671
+ const remoteSettings = await client.getOrgGovernanceSettings();
672
+ if (remoteSettings) {
673
+ orgGovernanceSettings = {
674
+ contextPolicy: (0, policy_1.normalizeContextPolicy)(remoteSettings.contextPolicy),
675
+ requireSignedAiLogs: remoteSettings.requireSignedAiLogs === true,
676
+ requireManualApproval: remoteSettings.requireManualApproval !== false,
677
+ minimumManualApprovals: Math.max(1, Math.min(5, Math.floor(remoteSettings.minimumManualApprovals || 1))),
678
+ updatedAt: remoteSettings.updatedAt,
679
+ };
680
+ }
681
+ }
682
+ catch (error) {
683
+ if (!options.json) {
684
+ const message = error instanceof Error ? error.message : 'Unknown error';
685
+ console.log(chalk.dim(` Org governance settings unavailable, using local policy only (${message})`));
686
+ }
687
+ }
688
+ }
611
689
  const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
612
690
  if (!brainScope.orgId || !brainScope.projectId) {
613
691
  return;
@@ -725,20 +803,75 @@ async function verifyCommand(options) {
725
803
  const normalized = toUnixPath(filePath || '');
726
804
  return ignoreFilter(normalized) || runtimeIgnoreSet.has(normalized);
727
805
  };
806
+ const baselineContextPolicyLocal = (0, policy_1.loadContextPolicy)(projectRoot);
807
+ const baselineContextPolicy = orgGovernanceSettings?.contextPolicy
808
+ ? (0, policy_1.mergeContextPolicies)(baselineContextPolicyLocal, orgGovernanceSettings.contextPolicy)
809
+ : baselineContextPolicyLocal;
810
+ const baselineContextPolicyEvaluation = (0, policy_1.evaluateContextPolicyForChanges)(diffFiles.map((file) => file.path), baselineContextPolicy, diffFiles.map((file) => file.path));
811
+ const baselineContextViolations = baselineContextPolicyEvaluation.violations.filter((item) => !shouldIgnore(item.file));
812
+ if (baselineContextViolations.length > 0) {
813
+ const baselineGovernance = (0, governance_1.evaluateGovernance)({
814
+ projectRoot,
815
+ task: 'Context policy validation',
816
+ expectedFiles: [],
817
+ diffFiles,
818
+ contextCandidates: diffFiles.map((file) => file.path),
819
+ orgGovernance: orgGovernanceSettings,
820
+ signingKey: aiLogSigningKey,
821
+ signer: aiLogSigner,
822
+ });
823
+ const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
824
+ recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
825
+ if (options.json) {
826
+ console.log(JSON.stringify({
827
+ grade: 'F',
828
+ score: 0,
829
+ verdict: 'FAIL',
830
+ violations: baselineContextViolations.map((item) => ({
831
+ file: item.file,
832
+ rule: `context_policy:${item.rule}`,
833
+ severity: 'block',
834
+ message: item.reason,
835
+ })),
836
+ adherenceScore: 0,
837
+ bloatCount: 0,
838
+ bloatFiles: [],
839
+ plannedFilesModified: 0,
840
+ totalPlannedFiles: 0,
841
+ message,
842
+ scopeGuardPassed: false,
843
+ ...buildGovernancePayload(baselineGovernance, orgGovernanceSettings),
844
+ mode: 'policy_violation',
845
+ policyOnly: false,
846
+ }, null, 2));
847
+ }
848
+ else {
849
+ console.log(chalk.red('\nā›” Context Policy Violation'));
850
+ baselineContextViolations.forEach((item) => {
851
+ console.log(chalk.red(` • ${item.file}: ${item.reason}`));
852
+ });
853
+ console.log(chalk.dim(`\nRisk level: ${baselineGovernance.blastRadius.riskScore.toUpperCase()}`));
854
+ console.log(chalk.red('\nAction blocked.\n'));
855
+ }
856
+ process.exit(2);
857
+ }
728
858
  if (!options.json) {
729
859
  console.log(chalk.cyan('\nšŸ“Š Analyzing changes against plan...'));
730
860
  console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
731
861
  console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
732
862
  }
863
+ const runPolicyOnlyModeAndExit = async (source) => {
864
+ const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
865
+ const changedFiles = diffFiles.map((f) => f.path);
866
+ const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
867
+ recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
868
+ process.exit(exitCode);
869
+ };
733
870
  // ============================================
734
871
  // --policy-only: General Governance (policy only, no plan enforcement)
735
872
  // ============================================
736
873
  if (options.policyOnly) {
737
- const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client);
738
- const changedFiles = diffFiles.map((f) => f.path);
739
- const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
740
- recordVerifyEvent(verdict, `policy_only=true;exit=${exitCode}`, changedFiles);
741
- process.exit(exitCode);
874
+ await runPolicyOnlyModeAndExit('explicit');
742
875
  }
743
876
  const requirePlan = options.requirePlan === true || process.env.NEURCODE_VERIFY_REQUIRE_PLAN === '1';
744
877
  // Get planId: Priority 1: options flag, Priority 2: state file (.neurcode/config.json), Priority 3: legacy config
@@ -805,14 +938,11 @@ async function verifyCommand(options) {
805
938
  if (!options.json) {
806
939
  console.log(chalk.yellow('āš ļø No Plan ID found. Falling back to General Governance (Policy Only).'));
807
940
  }
808
- options.policyOnly = true;
809
- const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client);
810
- const changedFiles = diffFiles.map((f) => f.path);
811
- const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
812
- recordVerifyEvent(verdict, `policy_only=fallback;exit=${exitCode}`, changedFiles);
813
- process.exit(exitCode);
941
+ await runPolicyOnlyModeAndExit('fallback_missing_plan');
942
+ }
943
+ if (!planId) {
944
+ throw new Error('Plan ID resolution failed unexpectedly');
814
945
  }
815
- // At this point, planId is guaranteed to be defined
816
946
  const finalPlanId = planId;
817
947
  // ============================================
818
948
  // STRICT SCOPE GUARD - Deterministic Check
@@ -822,6 +952,7 @@ async function verifyCommand(options) {
822
952
  }
823
953
  // Track if scope guard passed - this takes priority over AI grading
824
954
  let scopeGuardPassed = false;
955
+ let governanceResult = null;
825
956
  try {
826
957
  // Step A: Get Modified Files (already have from diffFiles)
827
958
  const modifiedFiles = diffFiles.map(f => f.path);
@@ -829,10 +960,29 @@ async function verifyCommand(options) {
829
960
  const planData = await client.getPlan(finalPlanId);
830
961
  // Extract original intent from plan (for constraint checking)
831
962
  const originalIntent = planData.intent || '';
963
+ const planTitle = typeof planData.content.title === 'string'
964
+ ? planData.content.title?.trim()
965
+ : '';
966
+ const planSummary = typeof planData.content.summary === 'string' ? planData.content.summary.trim() : '';
967
+ const governanceTask = planTitle || planSummary || originalIntent || 'Plan verification';
832
968
  // Get approved files from plan (only files with action CREATE or MODIFY)
833
969
  const planFiles = planData.content.files
834
970
  .filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
835
971
  .map(f => f.path);
972
+ const planDependencies = Array.isArray(planData.content.dependencies)
973
+ ? planData.content.dependencies.filter((item) => typeof item === 'string')
974
+ : [];
975
+ governanceResult = (0, governance_1.evaluateGovernance)({
976
+ projectRoot,
977
+ task: governanceTask,
978
+ expectedFiles: planFiles,
979
+ expectedDependencies: planDependencies,
980
+ diffFiles,
981
+ contextCandidates: planFiles,
982
+ orgGovernance: orgGovernanceSettings,
983
+ signingKey: aiLogSigningKey,
984
+ signer: aiLogSigner,
985
+ });
836
986
  // Get sessionId from state file (.neurcode/state.json) first, then fallback to config
837
987
  // Fallback to sessionId from plan if not in state/config
838
988
  // This is the session_id string needed to fetch the session
@@ -901,6 +1051,9 @@ async function verifyCommand(options) {
901
1051
  scopeGuardPassed: false,
902
1052
  mode: 'plan_enforced',
903
1053
  policyOnly: false,
1054
+ ...(governanceResult
1055
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1056
+ : {}),
904
1057
  };
905
1058
  // CRITICAL: Print JSON first, then exit
906
1059
  console.log(JSON.stringify(jsonOutput, null, 2));
@@ -920,6 +1073,9 @@ async function verifyCommand(options) {
920
1073
  filteredViolations.forEach(file => {
921
1074
  console.log(chalk.dim(` neurcode allow ${file}`));
922
1075
  });
1076
+ if (governanceResult) {
1077
+ displayGovernanceInsights(governanceResult, { explain: options.explain });
1078
+ }
923
1079
  console.log('');
924
1080
  process.exit(1);
925
1081
  }
@@ -1024,6 +1180,9 @@ async function verifyCommand(options) {
1024
1180
  scopeGuardPassed,
1025
1181
  mode: 'plan_enforced',
1026
1182
  policyOnly: false,
1183
+ ...(governanceResult
1184
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1185
+ : {}),
1027
1186
  policyLock: {
1028
1187
  enforced: true,
1029
1188
  matched: false,
@@ -1077,6 +1236,9 @@ async function verifyCommand(options) {
1077
1236
  mode: 'plan_enforced',
1078
1237
  policyOnly: false,
1079
1238
  tier: 'FREE',
1239
+ ...(governanceResult
1240
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1241
+ : {}),
1080
1242
  policyLock: {
1081
1243
  enforced: policyLockEvaluation.enforced,
1082
1244
  matched: policyLockEvaluation.matched,
@@ -1307,6 +1469,9 @@ async function verifyCommand(options) {
1307
1469
  totalPlannedFiles: verifyResult.totalPlannedFiles,
1308
1470
  mode: 'plan_enforced',
1309
1471
  policyOnly: false,
1472
+ ...(governanceResult
1473
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1474
+ : {}),
1310
1475
  policyLock: {
1311
1476
  enforced: policyLockEvaluation.enforced,
1312
1477
  matched: policyLockEvaluation.matched,
@@ -1345,8 +1510,10 @@ async function verifyCommand(options) {
1345
1510
  })),
1346
1511
  ];
1347
1512
  // Report in background (don't await to avoid blocking JSON output)
1348
- reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true // jsonMode = true
1349
- ).catch(() => {
1513
+ reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true, // jsonMode = true
1514
+ governanceResult
1515
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1516
+ : undefined).catch(() => {
1350
1517
  // Error already logged in reportVerification
1351
1518
  });
1352
1519
  }
@@ -1374,6 +1541,9 @@ async function verifyCommand(options) {
1374
1541
  bloatFiles: displayBloatFiles,
1375
1542
  bloatCount: displayBloatFiles.length,
1376
1543
  }, policyViolations);
1544
+ if (governanceResult) {
1545
+ displayGovernanceInsights(governanceResult, { explain: options.explain });
1546
+ }
1377
1547
  if (policyExceptionsSummary.suppressed > 0) {
1378
1548
  console.log(chalk.yellow(`\nāš ļø Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
1379
1549
  if (policyExceptionsSummary.matchedExceptionIds.length > 0) {
@@ -1419,8 +1589,10 @@ async function verifyCommand(options) {
1419
1589
  message: v.message,
1420
1590
  })),
1421
1591
  ];
1422
- await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false // jsonMode = false
1423
- );
1592
+ await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false, // jsonMode = false
1593
+ governanceResult
1594
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1595
+ : undefined);
1424
1596
  }
1425
1597
  }
1426
1598
  // Governance override: keep PASS only when scope guard passes and failure is due
@@ -1611,7 +1783,7 @@ function collectCIContext() {
1611
1783
  /**
1612
1784
  * Report verification results to Neurcode Cloud
1613
1785
  */
1614
- async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode) {
1786
+ async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
1615
1787
  try {
1616
1788
  const ciContext = collectCIContext();
1617
1789
  const payload = {
@@ -1627,6 +1799,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
1627
1799
  branch: ciContext.branch,
1628
1800
  workflowRunId: ciContext.workflowRunId,
1629
1801
  projectId,
1802
+ governance,
1630
1803
  };
1631
1804
  const response = await fetch(`${apiUrl}/api/v1/action/verifications`, {
1632
1805
  method: 'POST',
@@ -1655,6 +1828,68 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
1655
1828
  }
1656
1829
  }
1657
1830
  }
1831
+ function buildGovernancePayload(governance, orgGovernanceSettings) {
1832
+ return {
1833
+ contextPolicy: governance.contextPolicy,
1834
+ blastRadius: governance.blastRadius,
1835
+ suspiciousChange: governance.suspiciousChange,
1836
+ changeJustification: governance.changeJustification,
1837
+ governanceDecision: governance.governanceDecision,
1838
+ aiChangeLog: {
1839
+ path: governance.aiChangeLogPath,
1840
+ auditPath: governance.aiChangeLogAuditPath,
1841
+ integrity: governance.aiChangeLogIntegrity,
1842
+ },
1843
+ policySources: governance.policySources,
1844
+ orgGovernance: orgGovernanceSettings
1845
+ ? {
1846
+ requireSignedAiLogs: orgGovernanceSettings.requireSignedAiLogs,
1847
+ requireManualApproval: orgGovernanceSettings.requireManualApproval,
1848
+ minimumManualApprovals: orgGovernanceSettings.minimumManualApprovals,
1849
+ updatedAt: orgGovernanceSettings.updatedAt || null,
1850
+ }
1851
+ : null,
1852
+ };
1853
+ }
1854
+ function displayGovernanceInsights(governance, options = {}) {
1855
+ const maxUnexpectedFiles = options.maxUnexpectedFiles ?? 20;
1856
+ const decision = governance.governanceDecision;
1857
+ console.log(chalk.bold.white('\nBlast Radius:'));
1858
+ console.log(chalk.dim(` Files touched: ${governance.blastRadius.filesChanged}`));
1859
+ console.log(chalk.dim(` Functions impacted: ${governance.blastRadius.functionsAffected}`));
1860
+ console.log(chalk.dim(` Modules impacted: ${governance.blastRadius.modulesAffected.join(', ') || 'none'}`));
1861
+ if (governance.blastRadius.dependenciesAdded.length > 0) {
1862
+ console.log(chalk.dim(` Dependencies added: ${governance.blastRadius.dependenciesAdded.join(', ')}`));
1863
+ }
1864
+ console.log(chalk.dim(` Risk level: ${governance.blastRadius.riskScore.toUpperCase()}`));
1865
+ console.log(chalk.dim(` Governance decision: ${decision.decision.toUpperCase().replace('_', ' ')} | Avg relevance: ${decision.averageRelevanceScore}`));
1866
+ console.log(chalk.dim(` Policy source: ${governance.policySources.mode}${governance.policySources.orgPolicy ? ' (org + local)' : ' (local)'}`));
1867
+ console.log(governance.aiChangeLogIntegrity.valid
1868
+ ? chalk.dim(` AI change-log integrity: valid (${governance.aiChangeLogIntegrity.signed ? 'signed' : 'unsigned'})`)
1869
+ : chalk.red(` AI change-log integrity: invalid (${governance.aiChangeLogIntegrity.issues.join('; ') || 'unknown'})`));
1870
+ if (governance.suspiciousChange.flagged) {
1871
+ console.log(chalk.red('\nSuspicious Change Detected'));
1872
+ console.log(chalk.red(` Plan expected files: ${governance.suspiciousChange.expectedFiles} | AI modified files: ${governance.suspiciousChange.actualFiles}`));
1873
+ governance.suspiciousChange.unexpectedFiles.slice(0, maxUnexpectedFiles).forEach((filePath) => {
1874
+ console.log(chalk.red(` • ${filePath}`));
1875
+ });
1876
+ console.log(chalk.red(` Confidence: ${governance.suspiciousChange.confidence}`));
1877
+ }
1878
+ if (decision.lowRelevanceFiles.length > 0) {
1879
+ console.log(chalk.yellow('\nLow Relevance Files'));
1880
+ decision.lowRelevanceFiles.slice(0, 10).forEach((item) => {
1881
+ console.log(chalk.yellow(` • ${item.file} (score ${item.relevanceScore}, ${item.planLink.replace('_', ' ')})`));
1882
+ });
1883
+ }
1884
+ if (options.explain) {
1885
+ console.log(chalk.bold.white('\nAI Change Justification:'));
1886
+ console.log(chalk.dim(` Task: ${governance.changeJustification.task}`));
1887
+ governance.changeJustification.changes.forEach((item) => {
1888
+ const relevance = typeof item.relevanceScore === 'number' ? ` [score ${item.relevanceScore}]` : '';
1889
+ console.log(chalk.dim(` • ${item.file} — ${item.reason}${relevance}`));
1890
+ });
1891
+ }
1892
+ }
1658
1893
  /**
1659
1894
  * Display verification results in a formatted report card
1660
1895
  */