@neurcode-ai/cli 0.9.48 → 0.9.49

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 (39) hide show
  1. package/LICENSE +201 -0
  2. package/dist/commands/fix.d.ts +12 -0
  3. package/dist/commands/fix.d.ts.map +1 -0
  4. package/dist/commands/fix.js +380 -0
  5. package/dist/commands/fix.js.map +1 -0
  6. package/dist/commands/generate.d.ts +7 -0
  7. package/dist/commands/generate.d.ts.map +1 -0
  8. package/dist/commands/generate.js +117 -0
  9. package/dist/commands/generate.js.map +1 -0
  10. package/dist/commands/plan-show.d.ts +6 -0
  11. package/dist/commands/plan-show.d.ts.map +1 -0
  12. package/dist/commands/plan-show.js +33 -0
  13. package/dist/commands/plan-show.js.map +1 -0
  14. package/dist/commands/start-intent.d.ts +6 -0
  15. package/dist/commands/start-intent.d.ts.map +1 -0
  16. package/dist/commands/start-intent.js +65 -0
  17. package/dist/commands/start-intent.js.map +1 -0
  18. package/dist/commands/verify.d.ts.map +1 -1
  19. package/dist/commands/verify.js +604 -137
  20. package/dist/commands/verify.js.map +1 -1
  21. package/dist/index.js +115 -55
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/context-injector.d.ts +45 -0
  24. package/dist/mcp/context-injector.d.ts.map +1 -0
  25. package/dist/mcp/context-injector.js +587 -0
  26. package/dist/mcp/context-injector.js.map +1 -0
  27. package/dist/mcp/proximity.d.ts +3 -0
  28. package/dist/mcp/proximity.d.ts.map +1 -0
  29. package/dist/mcp/proximity.js +135 -0
  30. package/dist/mcp/proximity.js.map +1 -0
  31. package/dist/utils/git.d.ts +8 -0
  32. package/dist/utils/git.d.ts.map +1 -1
  33. package/dist/utils/git.js +55 -0
  34. package/dist/utils/git.js.map +1 -1
  35. package/dist/utils/plan-sync.d.ts +34 -0
  36. package/dist/utils/plan-sync.d.ts.map +1 -0
  37. package/dist/utils/plan-sync.js +265 -0
  38. package/dist/utils/plan-sync.js.map +1 -0
  39. package/package.json +7 -8
@@ -55,6 +55,7 @@ const ignore_1 = require("../utils/ignore");
55
55
  const project_root_1 = require("../utils/project-root");
56
56
  const brain_context_1 = require("../utils/brain-context");
57
57
  const scope_telemetry_1 = require("../utils/scope-telemetry");
58
+ const plan_sync_1 = require("../utils/plan-sync");
58
59
  const policy_packs_1 = require("../utils/policy-packs");
59
60
  const custom_policy_rules_1 = require("../utils/custom-policy-rules");
60
61
  const policy_exceptions_1 = require("../utils/policy-exceptions");
@@ -672,6 +673,303 @@ function asNumberValue(value) {
672
673
  function asStringValue(value) {
673
674
  return typeof value === 'string' && value.trim().length > 0 ? value : null;
674
675
  }
676
+ const EXPEDITE_FOLLOW_UP_CHECKLIST = [
677
+ 'Add validation back',
678
+ 'Move logic to proper layer',
679
+ 'Remove temporary code',
680
+ ];
681
+ function containsAnyToken(value, tokens) {
682
+ const normalized = value.toLowerCase();
683
+ return tokens.some((token) => normalized.includes(token));
684
+ }
685
+ function isSecurityOrAuthViolation(fileRaw, policyRaw, messageRaw) {
686
+ const combined = `${fileRaw} ${policyRaw} ${messageRaw}`.toLowerCase();
687
+ return containsAnyToken(combined, [
688
+ 'auth',
689
+ 'authentication',
690
+ 'authorization',
691
+ 'security',
692
+ 'permission',
693
+ 'access control',
694
+ 'access_control',
695
+ 'token',
696
+ 'secret',
697
+ 'credential',
698
+ 'encryption',
699
+ 'encrypt',
700
+ 'decrypt',
701
+ 'csrf',
702
+ 'xss',
703
+ 'sql injection',
704
+ 'sqli',
705
+ 'insecure',
706
+ 'vulnerability',
707
+ ]);
708
+ }
709
+ function isCriticalScopeBreach(fileRaw, messageRaw) {
710
+ const combined = `${fileRaw} ${messageRaw}`.toLowerCase();
711
+ return containsAnyToken(combined, [
712
+ 'auth',
713
+ 'security',
714
+ 'secret',
715
+ 'token',
716
+ 'credential',
717
+ 'permission',
718
+ 'infra/terraform',
719
+ 'terraform',
720
+ 'k8s',
721
+ 'helm',
722
+ 'migration',
723
+ 'database/migration',
724
+ 'policy',
725
+ 'contract',
726
+ ]);
727
+ }
728
+ function resolveExpediteModeFromPayload(payload) {
729
+ const explicit = asBooleanFlag(payload.expediteMode);
730
+ if (explicit !== null) {
731
+ return explicit;
732
+ }
733
+ const message = asStringValue(payload.message) || '';
734
+ return containsAnyToken(message, ['hotfix', 'urgent', 'prod down', 'incident', 'expedite']);
735
+ }
736
+ function toVerifySeverity(value) {
737
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
738
+ if (normalized === 'critical' || normalized === 'block')
739
+ return 'critical';
740
+ if (normalized === 'high')
741
+ return 'high';
742
+ if (normalized === 'warn'
743
+ || normalized === 'warning'
744
+ || normalized === 'medium'
745
+ || normalized === 'low') {
746
+ return 'warning';
747
+ }
748
+ return 'info';
749
+ }
750
+ function toVerifyVerdict(value) {
751
+ const normalized = typeof value === 'string' ? value.trim().toUpperCase() : '';
752
+ if (normalized === 'PASS' || normalized === 'WARN' || normalized === 'FAIL') {
753
+ return normalized;
754
+ }
755
+ return 'FAIL';
756
+ }
757
+ function normalizeScopeIssueMessage(rawMessage) {
758
+ const message = asStringValue(rawMessage);
759
+ return message || 'File modified outside intended scope';
760
+ }
761
+ function pushVerifyIssue(target, seen, key, value) {
762
+ if (seen.has(key))
763
+ return;
764
+ seen.add(key);
765
+ target.push(value);
766
+ }
767
+ function dedupeTriageItems(items) {
768
+ const seen = new Set();
769
+ const output = [];
770
+ for (const item of items) {
771
+ const key = `${item.source}|${item.file.toLowerCase()}|${item.policy.toLowerCase()}|${item.message.toLowerCase()}`;
772
+ if (seen.has(key))
773
+ continue;
774
+ seen.add(key);
775
+ output.push(item);
776
+ }
777
+ return output;
778
+ }
779
+ function toCanonicalVerifyOutput(payload) {
780
+ const verdict = toVerifyVerdict(payload.verdict);
781
+ const violations = [];
782
+ const warnings = [];
783
+ const scopeIssues = [];
784
+ const seenViolations = new Set();
785
+ const seenWarnings = new Set();
786
+ const seenScopeIssues = new Set();
787
+ const addScopeIssue = (fileRaw, messageRaw) => {
788
+ const file = asStringValue(fileRaw) || 'unknown';
789
+ const message = normalizeScopeIssueMessage(messageRaw);
790
+ const key = file.toLowerCase();
791
+ pushVerifyIssue(scopeIssues, seenScopeIssues, key, { file, message });
792
+ };
793
+ const addWarning = (fileRaw, messageRaw, policyRaw) => {
794
+ const file = asStringValue(fileRaw) || 'unknown';
795
+ const message = asStringValue(messageRaw) || 'Warning detected';
796
+ const policy = asStringValue(policyRaw) || 'warning';
797
+ const key = `${file.toLowerCase()}|${message.toLowerCase()}|${policy.toLowerCase()}`;
798
+ pushVerifyIssue(warnings, seenWarnings, key, { file, message, policy });
799
+ };
800
+ const addViolation = (fileRaw, messageRaw, policyRaw, severityRaw) => {
801
+ const file = asStringValue(fileRaw) || 'unknown';
802
+ const message = asStringValue(messageRaw) || 'Policy violation detected';
803
+ const policy = asStringValue(policyRaw) || 'unknown_policy';
804
+ const severity = toVerifySeverity(severityRaw);
805
+ const key = `${file.toLowerCase()}|${message.toLowerCase()}|${policy.toLowerCase()}|${severity}`;
806
+ pushVerifyIssue(violations, seenViolations, key, { file, message, policy, severity });
807
+ };
808
+ const rawScopeIssues = Array.isArray(payload.scopeIssues) ? payload.scopeIssues : [];
809
+ for (const item of rawScopeIssues) {
810
+ const record = asObjectRecord(item);
811
+ if (record) {
812
+ addScopeIssue(record.file, record.message);
813
+ }
814
+ else {
815
+ addScopeIssue(item, null);
816
+ }
817
+ }
818
+ const rawBloatFiles = Array.isArray(payload.bloatFiles) ? payload.bloatFiles : [];
819
+ for (const item of rawBloatFiles) {
820
+ addScopeIssue(item, null);
821
+ }
822
+ const rawWarnings = Array.isArray(payload.warnings) ? payload.warnings : [];
823
+ for (const item of rawWarnings) {
824
+ const record = asObjectRecord(item);
825
+ if (record) {
826
+ addWarning(record.file, record.message, record.policy ?? record.rule);
827
+ }
828
+ else if (typeof item === 'string') {
829
+ addWarning('unknown', item, 'warning');
830
+ }
831
+ }
832
+ const rawViolations = Array.isArray(payload.violations) ? payload.violations : [];
833
+ for (const item of rawViolations) {
834
+ const record = asObjectRecord(item);
835
+ if (!record)
836
+ continue;
837
+ const file = record.file;
838
+ const message = record.message;
839
+ const policy = record.policy ?? record.rule;
840
+ const severity = toVerifySeverity(record.severity);
841
+ const combined = `${String(policy || '').toLowerCase()} ${String(message || '').toLowerCase()}`;
842
+ const isScopeIssue = combined.includes('scope_guard')
843
+ || combined.includes('scope')
844
+ || combined.includes('outside the plan')
845
+ || combined.includes('out of scope');
846
+ if (isScopeIssue) {
847
+ addScopeIssue(file, message);
848
+ continue;
849
+ }
850
+ if (severity === 'warning' || severity === 'info') {
851
+ addWarning(file, message, policy);
852
+ continue;
853
+ }
854
+ addViolation(file, message, policy, severity);
855
+ }
856
+ const payloadMessage = asStringValue(payload.message);
857
+ if (payloadMessage
858
+ && violations.length === 0
859
+ && warnings.length === 0
860
+ && scopeIssues.length === 0) {
861
+ addWarning('unknown', payloadMessage, 'verify_result');
862
+ }
863
+ const summaryRecord = asObjectRecord(payload.summary);
864
+ const fileSet = new Set();
865
+ for (const violation of violations)
866
+ fileSet.add(violation.file);
867
+ for (const warning of warnings)
868
+ fileSet.add(warning.file);
869
+ for (const scopeIssue of scopeIssues)
870
+ fileSet.add(scopeIssue.file);
871
+ const totalFilesChanged = (() => {
872
+ const fromSummary = summaryRecord ? asNumberValue(summaryRecord.totalFilesChanged) : null;
873
+ if (fromSummary !== null)
874
+ return Math.max(0, Math.floor(fromSummary));
875
+ const blastRadius = asObjectRecord(payload.blastRadius);
876
+ const fromBlastRadius = blastRadius ? asNumberValue(blastRadius.filesChanged) : null;
877
+ if (fromBlastRadius !== null)
878
+ return Math.max(0, Math.floor(fromBlastRadius));
879
+ return fileSet.size;
880
+ })();
881
+ const driftScoreRaw = asNumberValue(payload.driftScore);
882
+ const driftScore = driftScoreRaw === null
883
+ ? undefined
884
+ : Math.max(0, Math.min(100, Math.round(driftScoreRaw)));
885
+ const expediteModeUsed = resolveExpediteModeFromPayload(payload);
886
+ const scopeTriageItems = scopeIssues.map((item) => ({
887
+ file: item.file,
888
+ message: item.message,
889
+ policy: 'scope_guard',
890
+ severity: 'block',
891
+ source: 'scope',
892
+ }));
893
+ const violationTriageItems = violations.map((item) => ({
894
+ file: item.file,
895
+ message: item.message,
896
+ policy: item.policy,
897
+ severity: item.severity,
898
+ source: 'violation',
899
+ }));
900
+ const warningTriageItems = warnings.map((item) => ({
901
+ file: item.file,
902
+ message: item.message,
903
+ policy: item.policy,
904
+ severity: 'warning',
905
+ source: 'warning',
906
+ }));
907
+ const defaultBlockingItems = dedupeTriageItems([
908
+ ...scopeTriageItems,
909
+ ...violationTriageItems.filter((item) => item.severity === 'critical' || item.severity === 'high'),
910
+ ]);
911
+ const defaultAdvisoryItems = dedupeTriageItems([
912
+ ...warningTriageItems,
913
+ ...violationTriageItems.filter((item) => item.severity === 'warning' || item.severity === 'info'),
914
+ ]);
915
+ const expediteBlockingItems = dedupeTriageItems([
916
+ ...scopeTriageItems.filter((item) => isCriticalScopeBreach(item.file, item.message)),
917
+ ...violationTriageItems.filter((item) => isSecurityOrAuthViolation(item.file, item.policy, item.message)),
918
+ ...warningTriageItems
919
+ .filter((item) => isSecurityOrAuthViolation(item.file, item.policy, item.message))
920
+ .map((item) => ({
921
+ ...item,
922
+ source: 'violation',
923
+ })),
924
+ ]);
925
+ const expediteItems = dedupeTriageItems([
926
+ ...scopeTriageItems
927
+ .filter((item) => !isCriticalScopeBreach(item.file, item.message))
928
+ .map((item) => ({
929
+ ...item,
930
+ source: 'expedite',
931
+ })),
932
+ ...violationTriageItems
933
+ .filter((item) => !isSecurityOrAuthViolation(item.file, item.policy, item.message))
934
+ .map((item) => ({
935
+ ...item,
936
+ source: 'expedite',
937
+ })),
938
+ ...warningTriageItems
939
+ .filter((item) => !isSecurityOrAuthViolation(item.file, item.policy, item.message))
940
+ .map((item) => ({
941
+ ...item,
942
+ source: 'expedite',
943
+ })),
944
+ ]);
945
+ const blockingItems = expediteModeUsed ? expediteBlockingItems : defaultBlockingItems;
946
+ const advisoryItems = expediteModeUsed ? expediteItems : defaultAdvisoryItems;
947
+ return {
948
+ verdict,
949
+ summary: {
950
+ totalFilesChanged,
951
+ totalViolations: violations.length,
952
+ totalWarnings: warnings.length,
953
+ totalScopeIssues: scopeIssues.length,
954
+ },
955
+ violations,
956
+ warnings,
957
+ scopeIssues,
958
+ blockingCount: blockingItems.length,
959
+ advisoryCount: advisoryItems.length,
960
+ blockingItems,
961
+ advisoryItems,
962
+ expediteModeUsed,
963
+ expediteCount: expediteModeUsed ? expediteItems.length : 0,
964
+ expediteItems: expediteModeUsed ? expediteItems : [],
965
+ expediteFollowUpChecklist: expediteModeUsed ? [...EXPEDITE_FOLLOW_UP_CHECKLIST] : [],
966
+ ...(expediteModeUsed ? { expediteNote: 'Expedite Mode used' } : {}),
967
+ ...(typeof driftScore === 'number' ? { driftScore } : {}),
968
+ };
969
+ }
970
+ function emitCanonicalVerifyJson(payload) {
971
+ console.log(JSON.stringify(toCanonicalVerifyOutput(payload), null, 2));
972
+ }
675
973
  function buildDeterministicLayerSummary(payload) {
676
974
  const verdict = asStringValue(payload.verdict) || 'UNKNOWN';
677
975
  const mode = asStringValue(payload.mode) || 'unknown';
@@ -841,6 +1139,13 @@ function isGitRepository(cwd) {
841
1139
  return false;
842
1140
  }
843
1141
  }
1142
+ function resolveVerifyExpediteMode(projectRoot) {
1143
+ if (isEnabledFlag(process.env.NEURCODE_EXPEDITE_MODE) || isEnabledFlag(process.env.NEURCODE_MCP_EXPEDITE_MODE)) {
1144
+ return true;
1145
+ }
1146
+ const branchName = (0, git_1.detectCurrentGitBranch)(projectRoot) || process.env.GITHUB_REF_NAME || '';
1147
+ return containsAnyToken(branchName, ['hotfix', 'urgent', 'prod-down', 'prod_down', 'prod down', 'incident', 'expedite']);
1148
+ }
844
1149
  function isSignedAiLogsRequired(orgGovernanceSettings) {
845
1150
  const explicitRequirement = isEnabledFlag(process.env.NEURCODE_GOVERNANCE_REQUIRE_SIGNED_LOGS) ||
846
1151
  isEnabledFlag(process.env.NEURCODE_AI_LOG_REQUIRE_SIGNED);
@@ -1012,18 +1317,12 @@ async function recordVerificationIfRequested(options, config, payload) {
1012
1317
  * Execute policy-only verification (General Governance mode)
1013
1318
  * Returns the exit code to use
1014
1319
  */
1015
- async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, scopeTelemetry, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyArtifact, compiledPolicyMetadata, changeContractSummary) {
1320
+ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, scopeTelemetry, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, expediteModeEnabled, compiledPolicyArtifact, compiledPolicyMetadata, changeContractSummary) {
1016
1321
  const emitPolicyOnlyJson = (payload) => {
1017
- const enrichedPayload = {
1322
+ emitCanonicalVerifyJson({
1018
1323
  ...payload,
1019
- deterministicLayers: buildDeterministicLayerSummary(payload),
1020
- };
1021
- console.log(JSON.stringify({
1022
- ...enrichedPayload,
1023
- ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1024
- changeContract: changeContractSummary,
1025
- scope: scopeTelemetry,
1026
- }, null, 2));
1324
+ expediteMode: expediteModeEnabled,
1325
+ });
1027
1326
  };
1028
1327
  const policyOnlyVerificationSource = 'policy_only';
1029
1328
  const recordPolicyOnlyVerification = async (payload) => recordVerificationIfRequested(options, config, {
@@ -1582,14 +1881,15 @@ async function verifyCommand(options) {
1582
1881
  try {
1583
1882
  const rootResolution = (0, project_root_1.resolveNeurcodeProjectRootWithTrace)(process.cwd());
1584
1883
  const projectRoot = rootResolution.projectRoot;
1884
+ const localPlanSync = (0, plan_sync_1.ensureLocalPlan)(projectRoot);
1885
+ const localPlanExpectedFiles = [...localPlanSync.expectedFiles];
1886
+ const expediteModeEnabled = resolveVerifyExpediteMode(projectRoot);
1585
1887
  const scopeTelemetry = (0, scope_telemetry_1.buildScopeTelemetryPayload)(rootResolution);
1586
1888
  const emitVerifyJson = (payload) => {
1587
- const jsonPayload = {
1889
+ emitCanonicalVerifyJson({
1588
1890
  ...payload,
1589
- deterministicLayers: buildDeterministicLayerSummary(payload),
1590
- scope: scopeTelemetry,
1591
- };
1592
- console.log(JSON.stringify(jsonPayload, null, 2));
1891
+ expediteMode: expediteModeEnabled,
1892
+ });
1593
1893
  };
1594
1894
  if (!isGitRepository(projectRoot)) {
1595
1895
  const message = 'Verify requires a git repository. Initialize git (`git init`) or run this command inside an existing git project.';
@@ -2069,34 +2369,44 @@ async function verifyCommand(options) {
2069
2369
  });
2070
2370
  process.exit(2);
2071
2371
  }
2072
- // Determine which diff to capture (staged + unstaged for full current work)
2372
+ // Determine which diff to capture.
2073
2373
  let diffText;
2374
+ let diffContextLabel = '';
2074
2375
  if (options.staged) {
2075
2376
  diffText = (0, child_process_1.execSync)('git diff --cached', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2377
+ diffContextLabel = 'staged changes';
2076
2378
  }
2077
2379
  else if (options.base) {
2078
2380
  diffText = (0, git_1.getDiffFromBase)(options.base);
2381
+ diffContextLabel = `working tree vs ${options.base}`;
2079
2382
  }
2080
2383
  else if (options.head) {
2081
2384
  diffText = (0, child_process_1.execSync)('git diff HEAD', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2385
+ diffContextLabel = 'working tree vs HEAD';
2082
2386
  }
2083
2387
  else {
2084
- // Default: combine staged + unstaged to capture all current work
2085
- try {
2086
- const stagedDiff = (0, child_process_1.execSync)('git diff --cached', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2087
- const unstagedDiff = (0, child_process_1.execSync)('git diff', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2088
- diffText = stagedDiff + (stagedDiff && unstagedDiff ? '\n' : '') + unstagedDiff;
2388
+ // Default: resolve a PR-like base context first (origin/main or origin/master).
2389
+ // Fallback to staged diff when base context cannot be resolved.
2390
+ const defaultContext = (0, git_1.resolveDefaultDiffContext)(projectRoot);
2391
+ if (defaultContext.mode === 'base' && defaultContext.baseRef) {
2392
+ diffText = (0, git_1.getDiffFromBase)(defaultContext.baseRef);
2393
+ diffContextLabel = defaultContext.currentBranch
2394
+ ? `${defaultContext.currentBranch} vs ${defaultContext.baseRef}`
2395
+ : `working tree vs ${defaultContext.baseRef}`;
2089
2396
  }
2090
- catch {
2091
- // Fallback to HEAD if git commands fail
2092
- diffText = (0, child_process_1.execSync)('git diff HEAD', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2397
+ else {
2398
+ diffText = (0, child_process_1.execSync)('git diff --cached', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2399
+ diffContextLabel = 'staged changes (fallback)';
2093
2400
  }
2094
2401
  }
2402
+ if (!options.json && diffContextLabel) {
2403
+ console.log(chalk.dim(` Diff context: ${diffContextLabel}`));
2404
+ }
2095
2405
  const untrackedDiffFiles = getUntrackedDiffFiles(projectRoot);
2096
2406
  if (!diffText.trim() && untrackedDiffFiles.length === 0) {
2097
2407
  if (!options.json) {
2098
- console.log(chalk.yellow('⚠️ No changes detected'));
2099
- console.log(chalk.dim(' Make sure you have staged or unstaged changes to verify'));
2408
+ console.log(chalk.yellow('⚠️ No changes detected in current diff context.'));
2409
+ console.log(chalk.dim(' Tip: Ensure changes are staged or run against a base branch.'));
2100
2410
  }
2101
2411
  else {
2102
2412
  emitVerifyJson({
@@ -2109,7 +2419,7 @@ async function verifyCommand(options) {
2109
2419
  bloatFiles: [],
2110
2420
  plannedFilesModified: 0,
2111
2421
  totalPlannedFiles: 0,
2112
- message: 'No changes detected',
2422
+ message: 'No changes detected in current diff context.',
2113
2423
  scopeGuardPassed: false,
2114
2424
  });
2115
2425
  }
@@ -2139,7 +2449,8 @@ async function verifyCommand(options) {
2139
2449
  const summary = (0, diff_parser_1.getDiffSummary)(diffFiles);
2140
2450
  if (diffFiles.length === 0) {
2141
2451
  if (!options.json) {
2142
- console.log(chalk.yellow('⚠️ No file changes detected in diff'));
2452
+ console.log(chalk.yellow('⚠️ No changes detected in current diff context.'));
2453
+ console.log(chalk.dim(' Tip: Ensure changes are staged or run against a base branch.'));
2143
2454
  }
2144
2455
  else {
2145
2456
  emitVerifyJson({
@@ -2152,7 +2463,7 @@ async function verifyCommand(options) {
2152
2463
  bloatFiles: [],
2153
2464
  plannedFilesModified: 0,
2154
2465
  totalPlannedFiles: 0,
2155
- message: 'No file changes detected in diff',
2466
+ message: 'No changes detected in current diff context.',
2156
2467
  scopeGuardPassed: false,
2157
2468
  });
2158
2469
  }
@@ -2382,7 +2693,7 @@ async function verifyCommand(options) {
2382
2693
  }
2383
2694
  }
2384
2695
  const runPolicyOnlyModeAndExit = async (source) => {
2385
- const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyRead.artifact, compiledPolicyMetadata, changeContractSummary);
2696
+ const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, expediteModeEnabled, compiledPolicyRead.artifact, compiledPolicyMetadata, changeContractSummary);
2386
2697
  const changedFiles = diffFiles.map((f) => f.path);
2387
2698
  const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
2388
2699
  recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
@@ -2397,6 +2708,7 @@ async function verifyCommand(options) {
2397
2708
  const requirePlan = options.requirePlan === true
2398
2709
  || process.env.NEURCODE_VERIFY_REQUIRE_PLAN === '1'
2399
2710
  || strictArtifactMode;
2711
+ let useLocalPlanSync = false;
2400
2712
  // Get planId: Priority 1: options flag, Priority 2: state file (.neurcode/config.json), Priority 3: legacy config
2401
2713
  let planId = options.planId;
2402
2714
  if (!planId) {
@@ -2429,6 +2741,19 @@ async function verifyCommand(options) {
2429
2741
  }
2430
2742
  }
2431
2743
  }
2744
+ if (planId === 'local-plan-sync' && localPlanExpectedFiles.length > 0) {
2745
+ useLocalPlanSync = true;
2746
+ if (!options.json) {
2747
+ console.log(chalk.dim(` Using Plan Sync from .neurcode/plan.json (${localPlanExpectedFiles.length} expected file(s))`));
2748
+ }
2749
+ }
2750
+ if (!planId && localPlanExpectedFiles.length > 0) {
2751
+ planId = 'local-plan-sync';
2752
+ useLocalPlanSync = true;
2753
+ if (!options.json) {
2754
+ console.log(chalk.dim(` Using Plan Sync from .neurcode/plan.json (${localPlanExpectedFiles.length} expected file(s))`));
2755
+ }
2756
+ }
2432
2757
  // If no planId found, either enforce strict requirement or fall back to policy-only mode.
2433
2758
  if (!planId) {
2434
2759
  if (requirePlan) {
@@ -2583,37 +2908,60 @@ async function verifyCommand(options) {
2583
2908
  }
2584
2909
  // Track if scope guard passed - this takes priority over AI grading
2585
2910
  let scopeGuardPassed = false;
2911
+ let scopeGuardExpediteBypass = false;
2586
2912
  let governanceResult = null;
2587
2913
  let planFilesForVerification = [];
2588
2914
  let intentConstraintsForVerification;
2589
2915
  try {
2590
2916
  // Step A: Get Modified Files (already have from diffFiles)
2591
2917
  const modifiedFiles = diffFiles.map(f => f.path);
2592
- // Step B: Fetch Plan and Session Data
2593
- const planData = await client.getPlan(finalPlanId);
2594
- // Extract original intent from plan (for constraint checking)
2595
- const originalIntent = planData.intent || '';
2596
- const planTitle = typeof planData.content.title === 'string'
2597
- ? planData.content.title?.trim()
2598
- : '';
2599
- const planSummary = typeof planData.content.summary === 'string' ? planData.content.summary.trim() : '';
2600
- const governanceTask = planTitle || planSummary || originalIntent || 'Plan verification';
2601
- // Get approved files from plan (only files with action CREATE or MODIFY)
2602
- const planFiles = planData.content.files
2603
- .filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
2604
- .map(f => f.path);
2605
- planFilesForVerification = [...planFiles];
2918
+ // Step B: Resolve plan scope from remote plan or local Plan Sync.
2919
+ let originalIntent = '';
2920
+ let governanceTask = 'Plan verification';
2921
+ let planFiles = [];
2922
+ let planDependencies = [];
2923
+ let remotePlanSessionId = null;
2924
+ if (useLocalPlanSync) {
2925
+ const localIntent = (localPlanSync.intent || '').trim();
2926
+ const localConstraintText = localPlanSync.constraints.length > 0
2927
+ ? localPlanSync.constraints.join('; ')
2928
+ : '';
2929
+ planFiles = [...localPlanExpectedFiles];
2930
+ originalIntent = localIntent || localConstraintText;
2931
+ governanceTask = localIntent
2932
+ ? `Local Plan Sync: ${localIntent}`
2933
+ : 'Local Plan Sync verification';
2934
+ if (!options.json) {
2935
+ console.log(chalk.dim(` Plan Sync scope loaded: ${planFiles.length} file(s)`));
2936
+ }
2937
+ }
2938
+ else {
2939
+ const planData = await client.getPlan(finalPlanId);
2940
+ // Extract original intent from plan (for constraint checking)
2941
+ originalIntent = planData.intent || '';
2942
+ const planTitle = typeof planData.content.title === 'string'
2943
+ ? planData.content.title?.trim()
2944
+ : '';
2945
+ const planSummary = typeof planData.content.summary === 'string' ? planData.content.summary.trim() : '';
2946
+ governanceTask = planTitle || planSummary || originalIntent || 'Plan verification';
2947
+ // Get approved files from plan (only files with action CREATE or MODIFY)
2948
+ planFiles = planData.content.files
2949
+ .filter((f) => f.action === 'CREATE' || f.action === 'MODIFY')
2950
+ .map((f) => f.path);
2951
+ planDependencies = Array.isArray(planData.content.dependencies)
2952
+ ? planData.content.dependencies.filter((item) => typeof item === 'string')
2953
+ : [];
2954
+ remotePlanSessionId = planData.sessionId || null;
2955
+ }
2956
+ planFilesForVerification = [...new Set([...planFiles, ...localPlanExpectedFiles])];
2606
2957
  intentConstraintsForVerification = originalIntent || undefined;
2607
- const planDependencies = Array.isArray(planData.content.dependencies)
2608
- ? planData.content.dependencies.filter((item) => typeof item === 'string')
2609
- : [];
2610
2958
  governanceResult = (0, governance_1.evaluateGovernance)({
2611
2959
  projectRoot,
2612
2960
  task: governanceTask,
2613
- expectedFiles: planFiles,
2961
+ expectedFiles: planFilesForVerification,
2614
2962
  expectedDependencies: planDependencies,
2615
2963
  diffFiles,
2616
- contextCandidates: planFiles,
2964
+ contextCandidates: planFilesForVerification,
2617
2965
  orgGovernance: orgGovernanceSettings,
2618
2966
  requireSignedAiLogs: signedLogsRequired,
2619
2967
  signingKey: aiLogSigningKey,
@@ -2626,8 +2974,8 @@ async function verifyCommand(options) {
2626
2974
  // This is the session_id string needed to fetch the session
2627
2975
  let sessionIdString = (0, state_1.getSessionId)() || configData.sessionId || configData.lastSessionId || null;
2628
2976
  // Fallback: Use sessionId from plan if not in config
2629
- if (!sessionIdString && planData.sessionId) {
2630
- sessionIdString = planData.sessionId;
2977
+ if (!sessionIdString && remotePlanSessionId) {
2978
+ sessionIdString = remotePlanSessionId;
2631
2979
  if ((process.env.DEBUG || process.env.VERBOSE) && !options.json) {
2632
2980
  console.log(chalk.dim(` Using sessionId from plan: ${sessionIdString.substring(0, 8)}...`));
2633
2981
  }
@@ -2661,17 +3009,24 @@ async function verifyCommand(options) {
2661
3009
  }
2662
3010
  }
2663
3011
  // Step C: The Intersection Logic
2664
- const approvedSet = new Set([...planFiles, ...allowedFiles]);
3012
+ const approvedSet = new Set([...planFilesForVerification, ...allowedFiles]);
2665
3013
  const violations = modifiedFiles.filter(f => !approvedSet.has(f));
2666
3014
  const filteredViolations = violations.filter((p) => !shouldIgnore(p));
2667
3015
  // Step D: The Block (only report scope violations for non-ignored files)
2668
3016
  if (filteredViolations.length > 0) {
3017
+ const criticalScopeViolations = expediteModeEnabled
3018
+ ? filteredViolations.filter((file) => isCriticalScopeBreach(file, 'File modified outside the plan'))
3019
+ : filteredViolations;
3020
+ const expediteScopeViolations = expediteModeEnabled
3021
+ ? filteredViolations.filter((file) => !criticalScopeViolations.includes(file))
3022
+ : [];
3023
+ const shouldBlockForScope = !expediteModeEnabled || criticalScopeViolations.length > 0;
2669
3024
  const aiDebtSummaryForScope = toAiDebtSummary((0, ai_debt_budget_1.evaluateAiDebtBudget)({
2670
3025
  diffFiles,
2671
3026
  bloatCount: filteredViolations.length,
2672
3027
  config: aiDebtConfig,
2673
3028
  }));
2674
- recordVerifyEvent('FAIL', `scope_violation=${filteredViolations.length}`, modifiedFiles, finalPlanId);
3029
+ recordVerifyEvent(shouldBlockForScope ? 'FAIL' : 'WARN', `${shouldBlockForScope ? 'scope_violation' : 'scope_expedite'}=${filteredViolations.length}`, modifiedFiles, finalPlanId);
2675
3030
  const scopeViolationItems = filteredViolations.map((file) => ({
2676
3031
  file,
2677
3032
  rule: 'scope_guard',
@@ -2683,8 +3038,10 @@ async function verifyCommand(options) {
2683
3038
  ...scopeViolationItems,
2684
3039
  ...aiDebtViolationItems,
2685
3040
  ];
2686
- const scopeViolationMessage = `Scope violation: ${filteredViolations.length} file(s) modified outside the plan`;
2687
- if (options.json) {
3041
+ const scopeViolationMessage = shouldBlockForScope
3042
+ ? `Scope violation: ${criticalScopeViolations.length} critical file(s) modified outside the plan`
3043
+ : `Expedite scope warning: ${expediteScopeViolations.length} non-critical file(s) modified outside the plan`;
3044
+ if (shouldBlockForScope && options.json) {
2688
3045
  // Output JSON for scope violation BEFORE exit. Must include violations for GitHub Action annotations.
2689
3046
  const jsonOutput = {
2690
3047
  grade: 'F',
@@ -2695,12 +3052,13 @@ async function verifyCommand(options) {
2695
3052
  bloatCount: filteredViolations.length,
2696
3053
  bloatFiles: filteredViolations,
2697
3054
  plannedFilesModified: 0,
2698
- totalPlannedFiles: planFiles.length,
3055
+ totalPlannedFiles: planFilesForVerification.length,
2699
3056
  message: scopeViolationMessage,
2700
3057
  scopeGuardPassed: false,
2701
3058
  mode: 'plan_enforced',
2702
3059
  policyOnly: false,
2703
3060
  aiDebt: aiDebtSummaryForScope,
3061
+ ...(expediteModeEnabled ? { expediteMode: true } : {}),
2704
3062
  ...(governanceResult
2705
3063
  ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
2706
3064
  changeContract: changeContractSummary,
@@ -2733,18 +3091,25 @@ async function verifyCommand(options) {
2733
3091
  });
2734
3092
  process.exit(1);
2735
3093
  }
2736
- else {
3094
+ else if (shouldBlockForScope) {
2737
3095
  // Human-readable output only when NOT in json mode
2738
3096
  console.log(chalk.red('\n⛔ SCOPE VIOLATION'));
2739
3097
  console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2740
3098
  console.log(chalk.red('The following files were modified but are not in the plan:'));
2741
3099
  console.log('');
2742
- filteredViolations.forEach(file => {
3100
+ criticalScopeViolations.forEach(file => {
2743
3101
  console.log(chalk.red(` • ${file}`));
2744
3102
  });
3103
+ if (expediteModeEnabled && expediteScopeViolations.length > 0) {
3104
+ console.log('');
3105
+ console.log(chalk.yellow('Non-critical scope files (can be followed up under expedite mode):'));
3106
+ expediteScopeViolations.forEach((file) => {
3107
+ console.log(chalk.yellow(` • ${file}`));
3108
+ });
3109
+ }
2745
3110
  console.log('');
2746
3111
  console.log(chalk.yellow('To unblock these files, run:'));
2747
- filteredViolations.forEach(file => {
3112
+ criticalScopeViolations.forEach(file => {
2748
3113
  console.log(chalk.dim(` neurcode allow ${file}`));
2749
3114
  });
2750
3115
  if (aiDebtSummaryForScope.mode !== 'off') {
@@ -2791,11 +3156,30 @@ async function verifyCommand(options) {
2791
3156
  });
2792
3157
  process.exit(1);
2793
3158
  }
3159
+ else {
3160
+ scopeGuardExpediteBypass = true;
3161
+ if (!options.json) {
3162
+ console.log(chalk.yellow('\n⚠️ Expedite scope relaxation applied (non-critical scope only).'));
3163
+ expediteScopeViolations.forEach((file) => {
3164
+ console.log(chalk.yellow(` • ${file}`));
3165
+ });
3166
+ console.log(chalk.dim(' Follow-up checklist:'));
3167
+ EXPEDITE_FOLLOW_UP_CHECKLIST.forEach((item) => {
3168
+ console.log(chalk.dim(` - ${item}`));
3169
+ });
3170
+ console.log(chalk.dim(' Note: Expedite Mode used\n'));
3171
+ }
3172
+ }
2794
3173
  }
2795
3174
  // Scope guard passed - all files are approved or allowed
2796
3175
  scopeGuardPassed = true;
2797
3176
  if (!options.json) {
2798
- console.log(chalk.green('✅ All modified files are approved or allowed'));
3177
+ if (scopeGuardExpediteBypass) {
3178
+ console.log(chalk.green('✅ Scope guard passed with expedite relaxation for non-critical scope changes'));
3179
+ }
3180
+ else {
3181
+ console.log(chalk.green('✅ All modified files are approved or allowed'));
3182
+ }
2799
3183
  console.log('');
2800
3184
  }
2801
3185
  }
@@ -2879,7 +3263,7 @@ async function verifyCommand(options) {
2879
3263
  const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
2880
3264
  recordVerifyEvent('FAIL', 'policy_lock_mismatch', diffFiles.map((f) => f.path), finalPlanId);
2881
3265
  if (options.json) {
2882
- console.log(JSON.stringify({
3266
+ emitVerifyJson({
2883
3267
  grade: 'F',
2884
3268
  score: 0,
2885
3269
  verdict: 'FAIL',
@@ -2907,7 +3291,7 @@ async function verifyCommand(options) {
2907
3291
  path: policyLockEvaluation.lockPath,
2908
3292
  mismatches: policyLockEvaluation.mismatches,
2909
3293
  },
2910
- }, null, 2));
3294
+ });
2911
3295
  }
2912
3296
  else {
2913
3297
  console.log(chalk.red('\n❌ Policy lock baseline mismatch'));
@@ -3326,35 +3710,20 @@ async function verifyCommand(options) {
3326
3710
  displayChangeContractDrift(changeContractSummary, { advisory: true });
3327
3711
  }
3328
3712
  }
3329
- // Call verify API
3713
+ // Call verify API (or deterministic local evaluation for Plan Sync scope mode)
3330
3714
  if (!options.json) {
3331
- console.log(chalk.dim(' Sending to Neurcode API...\n'));
3332
- if (options.asyncMode) {
3333
- console.log(chalk.dim(' Queue-backed verification enabled (async job mode).'));
3715
+ if (useLocalPlanSync) {
3716
+ console.log(chalk.dim(' Using local Plan Sync deterministic verification (no API plan lookup).\n'));
3717
+ }
3718
+ else {
3719
+ console.log(chalk.dim(' Sending to Neurcode API...\n'));
3720
+ if (options.asyncMode) {
3721
+ console.log(chalk.dim(' Queue-backed verification enabled (async job mode).'));
3722
+ }
3334
3723
  }
3335
3724
  }
3336
3725
  try {
3337
- let verifySource = 'api';
3338
- let verifyResult;
3339
- try {
3340
- verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata, {
3341
- async: options.asyncMode === true,
3342
- pollIntervalMs: Number.isFinite(options.verifyJobPollMs) ? options.verifyJobPollMs : undefined,
3343
- timeoutMs: Number.isFinite(options.verifyJobTimeoutMs) ? options.verifyJobTimeoutMs : undefined,
3344
- idempotencyKey: options.verifyIdempotencyKey,
3345
- maxAttempts: Number.isFinite(options.verifyJobMaxAttempts) ? options.verifyJobMaxAttempts : undefined,
3346
- });
3347
- }
3348
- catch (verifyApiError) {
3349
- if (planFilesForVerification.length === 0) {
3350
- throw verifyApiError;
3351
- }
3352
- verifySource = 'local_fallback';
3353
- if (!options.json) {
3354
- const fallbackReason = verifyApiError instanceof Error ? verifyApiError.message : String(verifyApiError);
3355
- console.log(chalk.yellow('⚠️ Verify API unavailable, using local deterministic fallback.'));
3356
- console.log(chalk.dim(` Reason: ${fallbackReason}`));
3357
- }
3726
+ const runLocalDeterministicVerification = () => {
3358
3727
  const localFileContents = {};
3359
3728
  for (const file of changedFiles) {
3360
3729
  const absolutePath = (0, path_1.join)(projectRoot, file.path);
@@ -3380,7 +3749,7 @@ async function verifyCommand(options) {
3380
3749
  extraConstraintRules: hydratedCompiledPolicyRules.length > 0 ? hydratedCompiledPolicyRules : undefined,
3381
3750
  fileContents: localFileContents,
3382
3751
  });
3383
- verifyResult = {
3752
+ return {
3384
3753
  verificationId: `local-fallback-${Date.now()}`,
3385
3754
  adherenceScore: localEvaluation.adherenceScore,
3386
3755
  bloatCount: localEvaluation.bloatCount,
@@ -3391,6 +3760,35 @@ async function verifyCommand(options) {
3391
3760
  diffSummary: localEvaluation.diffSummary,
3392
3761
  message: localEvaluation.message,
3393
3762
  };
3763
+ };
3764
+ let verifySource = 'api';
3765
+ let verifyResult;
3766
+ if (useLocalPlanSync) {
3767
+ verifySource = 'local_fallback';
3768
+ verifyResult = runLocalDeterministicVerification();
3769
+ }
3770
+ else {
3771
+ try {
3772
+ verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata, {
3773
+ async: options.asyncMode === true,
3774
+ pollIntervalMs: Number.isFinite(options.verifyJobPollMs) ? options.verifyJobPollMs : undefined,
3775
+ timeoutMs: Number.isFinite(options.verifyJobTimeoutMs) ? options.verifyJobTimeoutMs : undefined,
3776
+ idempotencyKey: options.verifyIdempotencyKey,
3777
+ maxAttempts: Number.isFinite(options.verifyJobMaxAttempts) ? options.verifyJobMaxAttempts : undefined,
3778
+ });
3779
+ }
3780
+ catch (verifyApiError) {
3781
+ if (planFilesForVerification.length === 0) {
3782
+ throw verifyApiError;
3783
+ }
3784
+ verifySource = 'local_fallback';
3785
+ if (!options.json) {
3786
+ const fallbackReason = verifyApiError instanceof Error ? verifyApiError.message : String(verifyApiError);
3787
+ console.log(chalk.yellow('⚠️ Verify API unavailable, using local deterministic fallback.'));
3788
+ console.log(chalk.dim(` Reason: ${fallbackReason}`));
3789
+ }
3790
+ verifyResult = runLocalDeterministicVerification();
3791
+ }
3394
3792
  }
3395
3793
  const aiDebtEvaluation = (0, ai_debt_budget_1.evaluateAiDebtBudget)({
3396
3794
  diffFiles,
@@ -3586,7 +3984,7 @@ async function verifyCommand(options) {
3586
3984
  message: effectiveMessage,
3587
3985
  bloatFiles: displayBloatFiles,
3588
3986
  bloatCount: displayBloatFiles.length,
3589
- }, policyViolations);
3987
+ }, policyViolations, expediteModeEnabled);
3590
3988
  if (governanceResult) {
3591
3989
  displayGovernanceInsights(governanceResult, { explain: options.explain });
3592
3990
  }
@@ -3738,6 +4136,10 @@ async function verifyCommand(options) {
3738
4136
  });
3739
4137
  }
3740
4138
  else {
4139
+ console.error(chalk.red('\n❌ Verification failed before completion.'));
4140
+ if (diffFiles.length > 0) {
4141
+ console.log(chalk.dim(` Partial context captured: ${diffFiles.length} changed file(s) in diff.`));
4142
+ }
3741
4143
  if (error instanceof Error) {
3742
4144
  if (error.message.includes('404') || error.message.includes('not found')) {
3743
4145
  console.error(chalk.red(`❌ Error: Plan not found`));
@@ -3758,27 +4160,24 @@ async function verifyCommand(options) {
3758
4160
  catch (error) {
3759
4161
  if (options.json) {
3760
4162
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
3761
- console.log(JSON.stringify({
3762
- grade: 'F',
3763
- score: 0,
4163
+ emitCanonicalVerifyJson({
3764
4164
  verdict: 'FAIL',
3765
- violations: [],
3766
- adherenceScore: 0,
3767
- bloatCount: 0,
3768
- bloatFiles: [],
3769
- plannedFilesModified: 0,
3770
- totalPlannedFiles: 0,
3771
- message: `Unexpected error: ${errorMessage}`,
3772
- scopeGuardPassed: false,
3773
- scope: {
3774
- scanRoot: process.cwd(),
3775
- startDir: process.cwd(),
3776
- gitRoot: null,
3777
- linkedRepoOverrideUsed: false,
3778
- linkedRepos: [],
3779
- blockedOverride: null,
4165
+ summary: {
4166
+ totalFilesChanged: 0,
4167
+ totalViolations: 0,
4168
+ totalWarnings: 1,
4169
+ totalScopeIssues: 0,
3780
4170
  },
3781
- }, null, 2));
4171
+ violations: [],
4172
+ warnings: [
4173
+ {
4174
+ file: 'unknown',
4175
+ message: `Unexpected error: ${errorMessage}`,
4176
+ policy: 'verify_runtime',
4177
+ },
4178
+ ],
4179
+ scopeIssues: [],
4180
+ });
3782
4181
  }
3783
4182
  else {
3784
4183
  console.error(chalk.red('\n❌ Unexpected error:'));
@@ -4115,7 +4514,7 @@ function displayChangeContractDrift(summary, options = { advisory: false }) {
4115
4514
  /**
4116
4515
  * Display verification results in a formatted report card
4117
4516
  */
4118
- function displayVerifyResults(result, policyViolations) {
4517
+ function displayVerifyResults(result, policyViolations, expediteModeUsed = false) {
4119
4518
  const verdictLabel = result.verdict === 'PASS'
4120
4519
  ? chalk.green('PASS ✅')
4121
4520
  : result.verdict === 'WARN'
@@ -4124,42 +4523,110 @@ function displayVerifyResults(result, policyViolations) {
4124
4523
  const plannedText = `${result.plannedFilesModified}/${result.totalPlannedFiles}`;
4125
4524
  console.log(`\n${verdictLabel}`);
4126
4525
  console.log(chalk.dim(`Plan adherence: ${plannedText} files (${result.adherenceScore}%)`));
4127
- const maxItems = 20;
4128
- if (result.bloatCount > 0) {
4129
- console.log(chalk.red('\nOut-of-scope changes:'));
4130
- result.bloatFiles.slice(0, maxItems).forEach((file) => {
4131
- console.log(` - ${file}`);
4526
+ const maxBlockingItems = 20;
4527
+ const maxAdvisoryItems = 8;
4528
+ const maxExpediteItems = 12;
4529
+ const policyItems = policyViolations || [];
4530
+ const isBlockingSeverity = (severityRaw) => {
4531
+ const normalized = String(severityRaw || '').toLowerCase();
4532
+ return normalized === 'block' || normalized === 'critical' || normalized === 'high';
4533
+ };
4534
+ const scopeItems = result.bloatFiles.map((file) => ({
4535
+ file,
4536
+ message: 'File modified outside intended scope',
4537
+ policy: 'scope_guard',
4538
+ }));
4539
+ const policyTriageItems = policyItems.map((item) => ({
4540
+ file: item.file,
4541
+ message: item.message || item.rule,
4542
+ policy: item.rule || 'policy_violation',
4543
+ severity: item.severity,
4544
+ }));
4545
+ let blockingItems = [
4546
+ ...scopeItems.map((item) => ({
4547
+ file: item.file,
4548
+ message: item.message,
4549
+ })),
4550
+ ...policyTriageItems
4551
+ .filter((item) => isBlockingSeverity(item.severity))
4552
+ .map((item) => ({
4553
+ file: item.file,
4554
+ message: item.message,
4555
+ })),
4556
+ ];
4557
+ let advisoryItems = policyTriageItems
4558
+ .filter((item) => !isBlockingSeverity(item.severity))
4559
+ .map((item) => ({
4560
+ file: item.file,
4561
+ message: item.message,
4562
+ }));
4563
+ let expediteItems = [];
4564
+ if (expediteModeUsed) {
4565
+ blockingItems = [
4566
+ ...scopeItems
4567
+ .filter((item) => isCriticalScopeBreach(item.file, item.message))
4568
+ .map((item) => ({ file: item.file, message: item.message })),
4569
+ ...policyTriageItems
4570
+ .filter((item) => isSecurityOrAuthViolation(item.file, item.policy, item.message))
4571
+ .map((item) => ({ file: item.file, message: item.message })),
4572
+ ];
4573
+ expediteItems = [
4574
+ ...scopeItems
4575
+ .filter((item) => !isCriticalScopeBreach(item.file, item.message))
4576
+ .map((item) => ({ file: item.file, message: item.message })),
4577
+ ...policyTriageItems
4578
+ .filter((item) => !isSecurityOrAuthViolation(item.file, item.policy, item.message))
4579
+ .map((item) => ({ file: item.file, message: item.message })),
4580
+ ];
4581
+ advisoryItems = [];
4582
+ }
4583
+ if (blockingItems.length > 0) {
4584
+ console.log(chalk.red(`\nBLOCKING (${blockingItems.length})`));
4585
+ blockingItems.slice(0, maxBlockingItems).forEach((item) => {
4586
+ console.log(` - ${item.file}: ${item.message}`);
4132
4587
  });
4133
- if (result.bloatFiles.length > maxItems) {
4134
- console.log(chalk.dim(` - ... ${result.bloatFiles.length - maxItems} more`));
4588
+ if (blockingItems.length > maxBlockingItems) {
4589
+ console.log(chalk.dim(` - ... ${blockingItems.length - maxBlockingItems} more`));
4135
4590
  }
4136
4591
  }
4137
- if (policyViolations && policyViolations.length > 0) {
4138
- const blocking = policyViolations.filter((item) => item.severity === 'block');
4139
- const warnings = policyViolations.filter((item) => item.severity !== 'block');
4140
- if (blocking.length > 0) {
4141
- console.log(chalk.red('\nBlocking policy violations:'));
4142
- blocking.slice(0, maxItems).forEach((item) => {
4143
- console.log(` - ${item.file}: ${item.message || item.rule}`);
4144
- });
4145
- if (blocking.length > maxItems) {
4146
- console.log(chalk.dim(` - ... ${blocking.length - maxItems} more`));
4147
- }
4592
+ if (advisoryItems.length > 0) {
4593
+ console.log(chalk.yellow(`\nADVISORY (${advisoryItems.length})`));
4594
+ advisoryItems.slice(0, maxAdvisoryItems).forEach((item) => {
4595
+ console.log(` - ${item.file}: ${item.message}`);
4596
+ });
4597
+ if (advisoryItems.length > maxAdvisoryItems) {
4598
+ console.log(chalk.dim(` - ... ${advisoryItems.length - maxAdvisoryItems} more (summarized)`));
4148
4599
  }
4149
- if (warnings.length > 0) {
4150
- console.log(chalk.yellow('\nPolicy warnings:'));
4151
- warnings.slice(0, maxItems).forEach((item) => {
4152
- console.log(` - ${item.file}: ${item.message || item.rule}`);
4153
- });
4154
- if (warnings.length > maxItems) {
4155
- console.log(chalk.dim(` - ... ${warnings.length - maxItems} more`));
4156
- }
4600
+ }
4601
+ if (expediteModeUsed && expediteItems.length > 0) {
4602
+ console.log(chalk.yellow(`\nEXPEDITE (requires follow-up) (${expediteItems.length})`));
4603
+ expediteItems.slice(0, maxExpediteItems).forEach((item) => {
4604
+ console.log(` - ${item.file}: ${item.message}`);
4605
+ });
4606
+ if (expediteItems.length > maxExpediteItems) {
4607
+ console.log(chalk.dim(` - ... ${expediteItems.length - maxExpediteItems} more (summarized)`));
4157
4608
  }
4609
+ console.log(chalk.dim(' Follow-up checklist:'));
4610
+ EXPEDITE_FOLLOW_UP_CHECKLIST.forEach((checkItem) => {
4611
+ console.log(chalk.dim(` - ${checkItem}`));
4612
+ });
4613
+ console.log(chalk.dim(' Note: Expedite Mode used'));
4158
4614
  }
4159
- if (result.bloatCount === 0 && (!policyViolations || policyViolations.length === 0)) {
4615
+ if (blockingItems.length === 0 && advisoryItems.length === 0 && expediteItems.length === 0) {
4160
4616
  console.log(chalk.green('\nNo drift detected.'));
4161
4617
  }
4162
- console.log(chalk.dim(`\nSummary: ${result.message}\n`));
4618
+ const filesTouched = new Set([
4619
+ ...blockingItems.map((item) => item.file),
4620
+ ...advisoryItems.map((item) => item.file),
4621
+ ...expediteItems.map((item) => item.file),
4622
+ ]).size;
4623
+ if (expediteModeUsed) {
4624
+ console.log(chalk.dim(`\nSummary: ${blockingItems.length} blocking issues, ${expediteItems.length} expedite issues across ${filesTouched} files`));
4625
+ }
4626
+ else {
4627
+ console.log(chalk.dim(`\nSummary: ${blockingItems.length} blocking issues, ${advisoryItems.length} advisory issues across ${filesTouched} files`));
4628
+ }
4629
+ console.log(chalk.dim(`Details: ${result.message}\n`));
4163
4630
  }
4164
4631
  function printFirstRunAdvisoryMessage(demoMode) {
4165
4632
  console.log(chalk.cyan('\nNeurcode first-run advisory mode'));