@neurcode-ai/cli 0.9.34 → 0.9.35

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 (40) hide show
  1. package/README.md +2 -1
  2. package/dist/api-client.d.ts +14 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +4 -1
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/ask.d.ts.map +1 -1
  7. package/dist/commands/ask.js +15 -2
  8. package/dist/commands/ask.js.map +1 -1
  9. package/dist/commands/plan.d.ts.map +1 -1
  10. package/dist/commands/plan.js +81 -2
  11. package/dist/commands/plan.js.map +1 -1
  12. package/dist/commands/policy.d.ts.map +1 -1
  13. package/dist/commands/policy.js +95 -0
  14. package/dist/commands/policy.js.map +1 -1
  15. package/dist/commands/verify.d.ts +6 -0
  16. package/dist/commands/verify.d.ts.map +1 -1
  17. package/dist/commands/verify.js +365 -56
  18. package/dist/commands/verify.js.map +1 -1
  19. package/dist/index.js +68 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/utils/change-contract.d.ts +55 -0
  22. package/dist/utils/change-contract.d.ts.map +1 -0
  23. package/dist/utils/change-contract.js +158 -0
  24. package/dist/utils/change-contract.js.map +1 -0
  25. package/dist/utils/policy-audit.d.ts +2 -2
  26. package/dist/utils/policy-audit.d.ts.map +1 -1
  27. package/dist/utils/policy-audit.js.map +1 -1
  28. package/dist/utils/policy-compiler.d.ts +68 -0
  29. package/dist/utils/policy-compiler.d.ts.map +1 -0
  30. package/dist/utils/policy-compiler.js +170 -0
  31. package/dist/utils/policy-compiler.js.map +1 -0
  32. package/dist/utils/project-root.d.ts +16 -0
  33. package/dist/utils/project-root.d.ts.map +1 -1
  34. package/dist/utils/project-root.js +123 -9
  35. package/dist/utils/project-root.js.map +1 -1
  36. package/dist/utils/scope-telemetry.d.ts +21 -0
  37. package/dist/utils/scope-telemetry.d.ts.map +1 -0
  38. package/dist/utils/scope-telemetry.js +35 -0
  39. package/dist/utils/scope-telemetry.js.map +1 -0
  40. package/package.json +3 -2
@@ -43,6 +43,7 @@ const child_process_1 = require("child_process");
43
43
  const git_1 = require("../utils/git");
44
44
  const diff_parser_1 = require("@neurcode-ai/diff-parser");
45
45
  const policy_engine_1 = require("@neurcode-ai/policy-engine");
46
+ const governance_runtime_1 = require("@neurcode-ai/governance-runtime");
46
47
  const config_1 = require("../config");
47
48
  const api_client_1 = require("../api-client");
48
49
  const path_1 = require("path");
@@ -53,12 +54,15 @@ const box_1 = require("../utils/box");
53
54
  const ignore_1 = require("../utils/ignore");
54
55
  const project_root_1 = require("../utils/project-root");
55
56
  const brain_context_1 = require("../utils/brain-context");
57
+ const scope_telemetry_1 = require("../utils/scope-telemetry");
56
58
  const policy_packs_1 = require("../utils/policy-packs");
57
59
  const custom_policy_rules_1 = require("../utils/custom-policy-rules");
58
60
  const policy_exceptions_1 = require("../utils/policy-exceptions");
59
61
  const policy_governance_1 = require("../utils/policy-governance");
60
62
  const policy_audit_1 = require("../utils/policy-audit");
61
63
  const governance_1 = require("../utils/governance");
64
+ const policy_compiler_1 = require("../utils/policy-compiler");
65
+ const change_contract_1 = require("../utils/change-contract");
62
66
  const policy_1 = require("@neurcode-ai/policy");
63
67
  // Import chalk with fallback
64
68
  let chalk;
@@ -80,6 +84,7 @@ catch {
80
84
  white: (str) => str,
81
85
  };
82
86
  }
87
+ ;
83
88
  /**
84
89
  * Check if a file path should be excluded from verification analysis
85
90
  * Excludes internal/system files that should not count towards plan adherence
@@ -234,6 +239,43 @@ async function buildEffectivePolicyRules(client, projectRoot, useDashboardPolici
234
239
  includeDashboardPolicies: useDashboardPolicies,
235
240
  };
236
241
  }
242
+ function resolveCompiledPolicyMetadata(artifact, path) {
243
+ if (!artifact || !path) {
244
+ return null;
245
+ }
246
+ return {
247
+ fingerprint: artifact.fingerprint,
248
+ deterministicRuleCount: artifact.compilation.deterministicRuleCount,
249
+ unmatchedStatements: artifact.compilation.unmatchedStatements.length,
250
+ sourcePath: path,
251
+ policyLockFingerprint: artifact.source.policyLockFingerprint,
252
+ };
253
+ }
254
+ function buildCompiledPolicyFromEffectiveRules(input) {
255
+ const policyStatements = [
256
+ ...input.effectiveRules.customPolicies.map((policy) => policy.rule_text),
257
+ ];
258
+ return (0, policy_compiler_1.buildCompiledPolicyArtifact)({
259
+ includeDashboardPolicies: input.effectiveRules.includeDashboardPolicies,
260
+ policyLockPath: input.policyLockEvaluation.lockPath,
261
+ policyLockFingerprint: input.policyLockEvaluation.lockPresent
262
+ ? (0, policy_packs_1.readPolicyLockFile)(input.projectRoot).lock?.effective.fingerprint || null
263
+ : null,
264
+ policyPack: input.effectiveRules.policyPack
265
+ ? {
266
+ id: input.effectiveRules.policyPack.packId,
267
+ name: input.effectiveRules.policyPack.packName,
268
+ version: input.effectiveRules.policyPack.version,
269
+ }
270
+ : null,
271
+ defaultRuleCount: (0, policy_engine_1.createDefaultPolicy)().rules.length,
272
+ policyPackRuleCount: input.effectiveRules.policyPackRules.length,
273
+ customRuleCount: input.effectiveRules.customRules.length,
274
+ effectiveRuleCount: input.effectiveRules.allRules.length,
275
+ intentConstraints: input.intentConstraints,
276
+ policyRules: policyStatements,
277
+ });
278
+ }
237
279
  const POLICY_AUDIT_FILE = 'neurcode.policy.audit.log.jsonl';
238
280
  function isEnabledFlag(value) {
239
281
  if (!value)
@@ -354,13 +396,30 @@ async function recordVerificationIfRequested(options, config, payload) {
354
396
  }
355
397
  return;
356
398
  }
357
- await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance);
399
+ await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance, payload.verificationSource);
358
400
  }
359
401
  /**
360
402
  * Execute policy-only verification (General Governance mode)
361
403
  * Returns the exit code to use
362
404
  */
363
- async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner) {
405
+ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, scopeTelemetry, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary) {
406
+ const emitPolicyOnlyJson = (payload) => {
407
+ console.log(JSON.stringify({
408
+ ...payload,
409
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
410
+ changeContract: changeContractSummary,
411
+ scope: scopeTelemetry,
412
+ }, null, 2));
413
+ };
414
+ const policyOnlyVerificationSource = 'policy_only';
415
+ const recordPolicyOnlyVerification = async (payload) => recordVerificationIfRequested(options, config, {
416
+ ...payload,
417
+ verificationSource: policyOnlyVerificationSource,
418
+ verifyResult: {
419
+ ...payload.verifyResult,
420
+ verificationSource: policyOnlyVerificationSource,
421
+ },
422
+ });
364
423
  if (!options.json) {
365
424
  console.log(chalk.cyan('🛡️ General Governance mode (policy only, no plan linked)\n'));
366
425
  }
@@ -376,13 +435,16 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
376
435
  signingKeys: aiLogSigningKeys,
377
436
  signer: aiLogSigner,
378
437
  });
379
- const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings);
438
+ const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings, {
439
+ compiledPolicy: compiledPolicyMetadata,
440
+ changeContract: changeContractSummary,
441
+ });
380
442
  const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
381
443
  const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
382
444
  if (signedLogsRequired && !governanceAnalysis.aiChangeLogIntegrity.valid) {
383
445
  const message = `AI change-log integrity check failed: ${governanceAnalysis.aiChangeLogIntegrity.issues.join('; ') || 'unknown issue'}`;
384
446
  if (options.json) {
385
- console.log(JSON.stringify({
447
+ emitPolicyOnlyJson({
386
448
  grade: 'F',
387
449
  score: 0,
388
450
  verdict: 'FAIL',
@@ -405,13 +467,13 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
405
467
  policyOnly: true,
406
468
  policyOnlySource: source,
407
469
  ...governancePayload,
408
- }, null, 2));
470
+ });
409
471
  }
410
472
  else {
411
473
  console.log(chalk.red('❌ AI change-log integrity validation failed (policy-only mode).'));
412
474
  console.log(chalk.red(` ${message}`));
413
475
  }
414
- await recordVerificationIfRequested(options, config, {
476
+ await recordPolicyOnlyVerification({
415
477
  grade: 'F',
416
478
  violations: [
417
479
  {
@@ -439,7 +501,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
439
501
  || 'Governance decision matrix returned BLOCK.';
440
502
  const reasonCodes = governanceAnalysis.governanceDecision.reasonCodes || [];
441
503
  if (options.json) {
442
- console.log(JSON.stringify({
504
+ emitPolicyOnlyJson({
443
505
  grade: 'F',
444
506
  score: 0,
445
507
  verdict: 'FAIL',
@@ -462,7 +524,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
462
524
  policyOnly: true,
463
525
  policyOnlySource: source,
464
526
  ...governancePayload,
465
- }, null, 2));
527
+ });
466
528
  }
467
529
  else {
468
530
  console.log(chalk.red('❌ Governance decision blocked this change set (policy-only mode).'));
@@ -471,7 +533,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
471
533
  }
472
534
  console.log(chalk.red(` ${message}`));
473
535
  }
474
- await recordVerificationIfRequested(options, config, {
536
+ await recordPolicyOnlyVerification({
475
537
  grade: 'F',
476
538
  violations: [
477
539
  {
@@ -503,7 +565,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
503
565
  message: item.reason,
504
566
  }));
505
567
  if (options.json) {
506
- console.log(JSON.stringify({
568
+ emitPolicyOnlyJson({
507
569
  grade: 'F',
508
570
  score: 0,
509
571
  verdict: 'FAIL',
@@ -519,7 +581,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
519
581
  policyOnly: true,
520
582
  policyOnlySource: source,
521
583
  ...governancePayload,
522
- }, null, 2));
584
+ });
523
585
  }
524
586
  else {
525
587
  console.log(chalk.red('❌ Context policy violation detected (policy-only mode).'));
@@ -528,7 +590,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
528
590
  });
529
591
  console.log(chalk.dim(`\n${message}`));
530
592
  }
531
- await recordVerificationIfRequested(options, config, {
593
+ await recordPolicyOnlyVerification({
532
594
  grade: 'F',
533
595
  violations: contextPolicyViolationItems,
534
596
  verifyResult: {
@@ -617,7 +679,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
617
679
  const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
618
680
  const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
619
681
  if (options.json) {
620
- console.log(JSON.stringify({
682
+ emitPolicyOnlyJson({
621
683
  grade: 'F',
622
684
  score: 0,
623
685
  verdict: 'FAIL',
@@ -639,7 +701,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
639
701
  path: policyLockEvaluation.lockPath,
640
702
  mismatches: policyLockEvaluation.mismatches,
641
703
  },
642
- }, null, 2));
704
+ });
643
705
  }
644
706
  else {
645
707
  console.log(chalk.red('❌ Policy lock baseline mismatch.'));
@@ -649,7 +711,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
649
711
  });
650
712
  console.log(chalk.dim('\n If drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
651
713
  }
652
- await recordVerificationIfRequested(options, config, {
714
+ await recordPolicyOnlyVerification({
653
715
  grade: 'F',
654
716
  violations: lockViolationItems,
655
717
  verifyResult: {
@@ -763,7 +825,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
763
825
  },
764
826
  };
765
827
  if (options.json) {
766
- console.log(JSON.stringify({
828
+ emitPolicyOnlyJson({
767
829
  grade,
768
830
  score,
769
831
  verdict: effectiveVerdict,
@@ -797,7 +859,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
797
859
  },
798
860
  }
799
861
  : {}),
800
- }, null, 2));
862
+ });
801
863
  }
802
864
  else {
803
865
  if (effectiveVerdict === 'PASS') {
@@ -821,7 +883,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
821
883
  displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
822
884
  console.log(chalk.dim(`\n${message}`));
823
885
  }
824
- await recordVerificationIfRequested(options, config, {
886
+ await recordPolicyOnlyVerification({
825
887
  grade,
826
888
  violations: violationsOutput,
827
889
  verifyResult: {
@@ -839,7 +901,54 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
839
901
  }
840
902
  async function verifyCommand(options) {
841
903
  try {
842
- const projectRoot = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
904
+ const rootResolution = (0, project_root_1.resolveNeurcodeProjectRootWithTrace)(process.cwd());
905
+ const projectRoot = rootResolution.projectRoot;
906
+ const scopeTelemetry = (0, scope_telemetry_1.buildScopeTelemetryPayload)(rootResolution);
907
+ const emitVerifyJson = (payload) => {
908
+ const jsonPayload = {
909
+ ...payload,
910
+ scope: scopeTelemetry,
911
+ };
912
+ console.log(JSON.stringify(jsonPayload, null, 2));
913
+ };
914
+ const enforceChangeContract = options.enforceChangeContract === true ||
915
+ isEnabledFlag(process.env.NEURCODE_VERIFY_ENFORCE_CHANGE_CONTRACT);
916
+ const changeContractRead = (0, change_contract_1.readChangeContract)(projectRoot, options.changeContract);
917
+ const compiledPolicyRead = (0, policy_compiler_1.readCompiledPolicyArtifact)(projectRoot, options.compiledPolicy);
918
+ let compiledPolicyMetadata = resolveCompiledPolicyMetadata(compiledPolicyRead.artifact, compiledPolicyRead.exists ? compiledPolicyRead.path : null);
919
+ let changeContractSummary = {
920
+ path: changeContractRead.path,
921
+ exists: changeContractRead.exists,
922
+ enforced: enforceChangeContract,
923
+ valid: changeContractRead.contract ? null : changeContractRead.exists ? false : null,
924
+ planId: changeContractRead.contract?.planId || null,
925
+ contractId: changeContractRead.contract?.contractId || null,
926
+ violations: changeContractRead.error
927
+ ? [
928
+ {
929
+ code: 'CHANGE_CONTRACT_PARSE_ERROR',
930
+ message: changeContractRead.error,
931
+ },
932
+ ]
933
+ : [],
934
+ };
935
+ if (!options.json) {
936
+ (0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
937
+ includeBlockedWarning: true,
938
+ });
939
+ if (compiledPolicyRead.error) {
940
+ console.log(chalk.yellow(` Compiled policy artifact unavailable (${compiledPolicyRead.error}); falling back to runtime compilation`));
941
+ }
942
+ else if (compiledPolicyRead.artifact) {
943
+ console.log(chalk.dim(` Compiled policy loaded: ${compiledPolicyRead.path} (${compiledPolicyRead.artifact.compilation.deterministicRuleCount} deterministic rules)`));
944
+ }
945
+ if (changeContractRead.error) {
946
+ console.log(chalk.yellow(` Change contract unavailable (${changeContractRead.error})`));
947
+ }
948
+ else if (changeContractRead.contract) {
949
+ console.log(chalk.dim(` Change contract loaded: ${changeContractRead.path}`));
950
+ }
951
+ }
843
952
  // Load configuration
844
953
  const config = (0, config_1.loadConfig)();
845
954
  // 🛑 FORCE PRIORITY: Env Var > Config
@@ -941,7 +1050,7 @@ async function verifyCommand(options) {
941
1050
  const message = 'Signed AI change-logs are required but no signing key is configured. Set NEURCODE_GOVERNANCE_SIGNING_KEY or NEURCODE_GOVERNANCE_SIGNING_KEYS.';
942
1051
  recordVerifyEvent('FAIL', 'missing_signing_key_material');
943
1052
  if (options.json) {
944
- console.log(JSON.stringify({
1053
+ emitVerifyJson({
945
1054
  grade: 'F',
946
1055
  score: 0,
947
1056
  verdict: 'FAIL',
@@ -962,7 +1071,7 @@ async function verifyCommand(options) {
962
1071
  scopeGuardPassed: false,
963
1072
  mode: 'plan_enforced',
964
1073
  policyOnly: false,
965
- }, null, 2));
1074
+ });
966
1075
  }
967
1076
  else {
968
1077
  console.log(chalk.red('\n⛔ Governance Signing Key Missing'));
@@ -1021,7 +1130,7 @@ async function verifyCommand(options) {
1021
1130
  console.log(chalk.dim(' Make sure you have staged or unstaged changes to verify'));
1022
1131
  }
1023
1132
  else {
1024
- console.log(JSON.stringify({
1133
+ emitVerifyJson({
1025
1134
  grade: 'F',
1026
1135
  score: 0,
1027
1136
  verdict: 'FAIL',
@@ -1033,7 +1142,7 @@ async function verifyCommand(options) {
1033
1142
  totalPlannedFiles: 0,
1034
1143
  message: 'No changes detected',
1035
1144
  scopeGuardPassed: false,
1036
- }, null, 2));
1145
+ });
1037
1146
  }
1038
1147
  recordVerifyEvent('NO_CHANGES', 'diff=empty');
1039
1148
  process.exit(0);
@@ -1064,7 +1173,7 @@ async function verifyCommand(options) {
1064
1173
  console.log(chalk.yellow('⚠️ No file changes detected in diff'));
1065
1174
  }
1066
1175
  else {
1067
- console.log(JSON.stringify({
1176
+ emitVerifyJson({
1068
1177
  grade: 'F',
1069
1178
  score: 0,
1070
1179
  verdict: 'FAIL',
@@ -1076,7 +1185,7 @@ async function verifyCommand(options) {
1076
1185
  totalPlannedFiles: 0,
1077
1186
  message: 'No file changes detected in diff',
1078
1187
  scopeGuardPassed: false,
1079
- }, null, 2));
1188
+ });
1080
1189
  }
1081
1190
  recordVerifyEvent('NO_CHANGES', 'diff_files=0');
1082
1191
  process.exit(0);
@@ -1106,7 +1215,10 @@ async function verifyCommand(options) {
1106
1215
  signingKeys: aiLogSigningKeys,
1107
1216
  signer: aiLogSigner,
1108
1217
  });
1109
- const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings);
1218
+ const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings, {
1219
+ changeContract: changeContractSummary,
1220
+ compiledPolicy: compiledPolicyMetadata,
1221
+ });
1110
1222
  const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
1111
1223
  const baselineContextViolationItems = baselineContextViolations.map((item) => ({
1112
1224
  file: item.file,
@@ -1116,7 +1228,7 @@ async function verifyCommand(options) {
1116
1228
  }));
1117
1229
  recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
1118
1230
  if (options.json) {
1119
- console.log(JSON.stringify({
1231
+ emitVerifyJson({
1120
1232
  grade: 'F',
1121
1233
  score: 0,
1122
1234
  verdict: 'FAIL',
@@ -1131,7 +1243,7 @@ async function verifyCommand(options) {
1131
1243
  ...baselineGovernancePayload,
1132
1244
  mode: 'policy_violation',
1133
1245
  policyOnly: false,
1134
- }, null, 2));
1246
+ });
1135
1247
  }
1136
1248
  else {
1137
1249
  console.log(chalk.red('\n⛔ Context Policy Violation'));
@@ -1163,7 +1275,7 @@ async function verifyCommand(options) {
1163
1275
  console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
1164
1276
  }
1165
1277
  const runPolicyOnlyModeAndExit = async (source) => {
1166
- const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner);
1278
+ const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary);
1167
1279
  const changedFiles = diffFiles.map((f) => f.path);
1168
1280
  const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
1169
1281
  recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
@@ -1215,7 +1327,7 @@ async function verifyCommand(options) {
1215
1327
  const message = 'Plan ID is required in strict mode. Run "neurcode plan" first or pass --plan-id.';
1216
1328
  recordVerifyEvent('FAIL', 'missing_plan_id;require_plan=true', changedFiles);
1217
1329
  if (options.json) {
1218
- console.log(JSON.stringify({
1330
+ emitVerifyJson({
1219
1331
  grade: 'F',
1220
1332
  score: 0,
1221
1333
  verdict: 'FAIL',
@@ -1229,7 +1341,7 @@ async function verifyCommand(options) {
1229
1341
  scopeGuardPassed: false,
1230
1342
  mode: 'plan_required',
1231
1343
  policyOnly: false,
1232
- }, null, 2));
1344
+ });
1233
1345
  }
1234
1346
  else {
1235
1347
  console.log(chalk.red('❌ Plan ID is required in strict mode.'));
@@ -1269,6 +1381,8 @@ async function verifyCommand(options) {
1269
1381
  // Track if scope guard passed - this takes priority over AI grading
1270
1382
  let scopeGuardPassed = false;
1271
1383
  let governanceResult = null;
1384
+ let planFilesForVerification = [];
1385
+ let intentConstraintsForVerification;
1272
1386
  try {
1273
1387
  // Step A: Get Modified Files (already have from diffFiles)
1274
1388
  const modifiedFiles = diffFiles.map(f => f.path);
@@ -1285,6 +1399,8 @@ async function verifyCommand(options) {
1285
1399
  const planFiles = planData.content.files
1286
1400
  .filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
1287
1401
  .map(f => f.path);
1402
+ planFilesForVerification = [...planFiles];
1403
+ intentConstraintsForVerification = originalIntent || undefined;
1288
1404
  const planDependencies = Array.isArray(planData.content.dependencies)
1289
1405
  ? planData.content.dependencies.filter((item) => typeof item === 'string')
1290
1406
  : [];
@@ -1371,11 +1487,14 @@ async function verifyCommand(options) {
1371
1487
  mode: 'plan_enforced',
1372
1488
  policyOnly: false,
1373
1489
  ...(governanceResult
1374
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1490
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1491
+ changeContract: changeContractSummary,
1492
+ compiledPolicy: compiledPolicyMetadata,
1493
+ })
1375
1494
  : {}),
1376
1495
  };
1377
1496
  // CRITICAL: Print JSON first, then exit
1378
- console.log(JSON.stringify(jsonOutput, null, 2));
1497
+ emitVerifyJson(jsonOutput);
1379
1498
  await recordVerificationIfRequested(options, config, {
1380
1499
  grade: 'F',
1381
1500
  violations: scopeViolationItems,
@@ -1389,7 +1508,10 @@ async function verifyCommand(options) {
1389
1508
  projectId: projectId || undefined,
1390
1509
  jsonMode: true,
1391
1510
  governance: governanceResult
1392
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1511
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1512
+ changeContract: changeContractSummary,
1513
+ compiledPolicy: compiledPolicyMetadata,
1514
+ })
1393
1515
  : undefined,
1394
1516
  });
1395
1517
  process.exit(1);
@@ -1425,7 +1547,10 @@ async function verifyCommand(options) {
1425
1547
  projectId: projectId || undefined,
1426
1548
  jsonMode: false,
1427
1549
  governance: governanceResult
1428
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1550
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1551
+ changeContract: changeContractSummary,
1552
+ compiledPolicy: compiledPolicyMetadata,
1553
+ })
1429
1554
  : undefined,
1430
1555
  });
1431
1556
  process.exit(1);
@@ -1532,8 +1657,13 @@ async function verifyCommand(options) {
1532
1657
  scopeGuardPassed,
1533
1658
  mode: 'plan_enforced',
1534
1659
  policyOnly: false,
1660
+ changeContract: changeContractSummary,
1661
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1535
1662
  ...(governanceResult
1536
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1663
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1664
+ changeContract: changeContractSummary,
1665
+ compiledPolicy: compiledPolicyMetadata,
1666
+ })
1537
1667
  : {}),
1538
1668
  policyLock: {
1539
1669
  enforced: true,
@@ -1564,12 +1694,35 @@ async function verifyCommand(options) {
1564
1694
  projectId: projectId || undefined,
1565
1695
  jsonMode: Boolean(options.json),
1566
1696
  governance: governanceResult
1567
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1697
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1698
+ changeContract: changeContractSummary,
1699
+ compiledPolicy: compiledPolicyMetadata,
1700
+ })
1568
1701
  : undefined,
1569
1702
  });
1570
1703
  process.exit(2);
1571
1704
  }
1572
1705
  }
1706
+ let effectiveCompiledPolicy = compiledPolicyRead.artifact;
1707
+ if (!effectiveCompiledPolicy) {
1708
+ effectiveCompiledPolicy = buildCompiledPolicyFromEffectiveRules({
1709
+ projectRoot,
1710
+ policyLockEvaluation,
1711
+ effectiveRules,
1712
+ intentConstraints: intentConstraintsForVerification,
1713
+ });
1714
+ compiledPolicyMetadata = resolveCompiledPolicyMetadata(effectiveCompiledPolicy, (0, policy_compiler_1.resolveCompiledPolicyPath)(projectRoot, options.compiledPolicy));
1715
+ }
1716
+ const hydratedCompiledPolicyRules = effectiveCompiledPolicy
1717
+ ? (0, policy_compiler_1.hydrateCompiledPolicyRules)(effectiveCompiledPolicy)
1718
+ : [];
1719
+ if (effectiveCompiledPolicy?.source.policyLockFingerprint &&
1720
+ policyLockEvaluation.lockPresent &&
1721
+ effectiveCompiledPolicy.source.policyLockFingerprint !== (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint) {
1722
+ if (!options.json) {
1723
+ console.log(chalk.yellow(' Compiled policy lock fingerprint differs from current lock; runtime checks will continue with latest lock state'));
1724
+ }
1725
+ }
1573
1726
  // Check user tier - Policy Compliance and A-F Grading are PRO features
1574
1727
  const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
1575
1728
  const auditIntegrity = (0, policy_audit_1.verifyPolicyAuditIntegrity)(projectRoot);
@@ -1589,7 +1742,7 @@ async function verifyCommand(options) {
1589
1742
  console.log(chalk.dim(' Upgrade at: https://www.neurcode.com/dashboard/purchase-plan\n'));
1590
1743
  }
1591
1744
  else {
1592
- console.log(JSON.stringify({
1745
+ emitVerifyJson({
1593
1746
  grade: 'N/A',
1594
1747
  score: 0,
1595
1748
  verdict: 'INFO',
@@ -1604,8 +1757,13 @@ async function verifyCommand(options) {
1604
1757
  mode: 'plan_enforced',
1605
1758
  policyOnly: false,
1606
1759
  tier: 'FREE',
1760
+ changeContract: changeContractSummary,
1761
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1607
1762
  ...(governanceResult
1608
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
1763
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1764
+ changeContract: changeContractSummary,
1765
+ compiledPolicy: compiledPolicyMetadata,
1766
+ })
1609
1767
  : {}),
1610
1768
  policyLock: {
1611
1769
  enforced: policyLockEvaluation.enforced,
@@ -1623,7 +1781,7 @@ async function verifyCommand(options) {
1623
1781
  eventCount: auditIntegrity.count,
1624
1782
  },
1625
1783
  },
1626
- }, null, 2));
1784
+ });
1627
1785
  }
1628
1786
  process.exit(0);
1629
1787
  }
@@ -1729,22 +1887,146 @@ async function verifyCommand(options) {
1729
1887
  })),
1730
1888
  })),
1731
1889
  }));
1890
+ const changeContractEvaluation = changeContractRead.contract
1891
+ ? (0, change_contract_1.evaluateChangeContract)(changeContractRead.contract, {
1892
+ planId: finalPlanId,
1893
+ changedFiles: changedFiles.map((file) => file.path),
1894
+ policyLockFingerprint: (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint || null,
1895
+ compiledPolicyFingerprint: effectiveCompiledPolicy?.fingerprint || null,
1896
+ })
1897
+ : null;
1898
+ if (changeContractEvaluation) {
1899
+ changeContractSummary = {
1900
+ path: changeContractRead.path,
1901
+ exists: true,
1902
+ enforced: enforceChangeContract,
1903
+ valid: changeContractEvaluation.valid,
1904
+ planId: changeContractRead.contract?.planId || null,
1905
+ contractId: changeContractRead.contract?.contractId || null,
1906
+ coverage: changeContractEvaluation.coverage,
1907
+ violations: changeContractEvaluation.violations.map((item) => ({
1908
+ code: item.code,
1909
+ message: item.message,
1910
+ file: item.file,
1911
+ expected: item.expected,
1912
+ actual: item.actual,
1913
+ })),
1914
+ };
1915
+ if (!changeContractEvaluation.valid && enforceChangeContract) {
1916
+ const violations = changeContractEvaluation.violations.map((item) => ({
1917
+ file: item.file || '.neurcode/change-contract.json',
1918
+ rule: `change_contract:${item.code.toLowerCase()}`,
1919
+ severity: 'block',
1920
+ message: item.message,
1921
+ }));
1922
+ const message = `Change contract enforcement failed: ${changeContractEvaluation.violations
1923
+ .map((item) => item.message)
1924
+ .join('; ')}`;
1925
+ if (options.json) {
1926
+ emitVerifyJson({
1927
+ grade: 'F',
1928
+ score: 0,
1929
+ verdict: 'FAIL',
1930
+ violations,
1931
+ adherenceScore: 0,
1932
+ bloatCount: 0,
1933
+ bloatFiles: [],
1934
+ plannedFilesModified: 0,
1935
+ totalPlannedFiles: 0,
1936
+ message,
1937
+ scopeGuardPassed: false,
1938
+ mode: 'plan_enforced',
1939
+ policyOnly: false,
1940
+ changeContract: changeContractSummary,
1941
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1942
+ });
1943
+ }
1944
+ else {
1945
+ console.log(chalk.red('\n⛔ Change contract enforcement failed'));
1946
+ changeContractEvaluation.violations.forEach((item) => {
1947
+ console.log(chalk.red(` • ${item.message}`));
1948
+ });
1949
+ console.log(chalk.dim(` Contract path: ${changeContractRead.path}`));
1950
+ }
1951
+ await recordVerificationIfRequested(options, config, {
1952
+ grade: 'F',
1953
+ violations,
1954
+ verifyResult: {
1955
+ adherenceScore: 0,
1956
+ verdict: 'FAIL',
1957
+ bloatCount: 0,
1958
+ bloatFiles: [],
1959
+ message,
1960
+ },
1961
+ projectId: projectId || undefined,
1962
+ jsonMode: Boolean(options.json),
1963
+ governance: governanceResult
1964
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
1965
+ changeContract: changeContractSummary,
1966
+ compiledPolicy: compiledPolicyMetadata,
1967
+ })
1968
+ : undefined,
1969
+ });
1970
+ process.exit(2);
1971
+ }
1972
+ else if (!changeContractEvaluation.valid && !options.json) {
1973
+ console.log(chalk.yellow('\n⚠️ Change contract drift detected (advisory mode)'));
1974
+ changeContractEvaluation.violations.slice(0, 5).forEach((item) => {
1975
+ console.log(chalk.yellow(` • ${item.message}`));
1976
+ });
1977
+ if (changeContractEvaluation.violations.length > 5) {
1978
+ console.log(chalk.dim(` ... ${changeContractEvaluation.violations.length - 5} more violation(s)`));
1979
+ }
1980
+ }
1981
+ }
1732
1982
  // Call verify API
1733
1983
  if (!options.json) {
1734
1984
  console.log(chalk.dim(' Sending to Neurcode API...\n'));
1735
1985
  }
1736
1986
  try {
1737
- // Extract original intent from plan for constraint checking
1738
- let intentConstraints;
1987
+ let verifySource = 'api';
1988
+ let verifyResult;
1989
+ const deterministicPolicyRules = effectiveCompiledPolicy
1990
+ ? [...effectiveCompiledPolicy.statements.policyRules]
1991
+ : effectiveRules.customPolicies
1992
+ .map((policy) => policy.rule_text)
1993
+ .filter((ruleText) => typeof ruleText === 'string' && ruleText.trim().length > 0);
1739
1994
  try {
1740
- const planData = await client.getPlan(finalPlanId);
1741
- intentConstraints = planData.intent || undefined;
1995
+ verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata);
1742
1996
  }
1743
- catch {
1744
- // If we can't get plan, continue without constraints
1997
+ catch (verifyApiError) {
1998
+ if (planFilesForVerification.length === 0) {
1999
+ throw verifyApiError;
2000
+ }
2001
+ verifySource = 'local_fallback';
2002
+ if (!options.json) {
2003
+ const fallbackReason = verifyApiError instanceof Error ? verifyApiError.message : String(verifyApiError);
2004
+ console.log(chalk.yellow('⚠️ Verify API unavailable, using local deterministic fallback.'));
2005
+ console.log(chalk.dim(` Reason: ${fallbackReason}`));
2006
+ }
2007
+ const localEvaluation = (0, governance_runtime_1.evaluatePlanVerification)({
2008
+ planFiles: planFilesForVerification.map((path) => ({
2009
+ path,
2010
+ action: 'MODIFY',
2011
+ })),
2012
+ changedFiles,
2013
+ diffStats,
2014
+ intentConstraints: intentConstraintsForVerification,
2015
+ policyRules: deterministicPolicyRules,
2016
+ extraConstraintRules: hydratedCompiledPolicyRules.length > 0 ? hydratedCompiledPolicyRules : undefined,
2017
+ });
2018
+ verifyResult = {
2019
+ verificationId: `local-fallback-${Date.now()}`,
2020
+ adherenceScore: localEvaluation.adherenceScore,
2021
+ bloatCount: localEvaluation.bloatCount,
2022
+ bloatFiles: localEvaluation.bloatFiles,
2023
+ plannedFilesModified: localEvaluation.plannedFilesModified,
2024
+ totalPlannedFiles: localEvaluation.totalPlannedFiles,
2025
+ verdict: localEvaluation.verdict,
2026
+ diffSummary: localEvaluation.diffSummary,
2027
+ message: localEvaluation.message,
2028
+ };
1745
2029
  }
1746
- // Call verifyPlan with intentConstraints
1747
- const verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraints);
1748
2030
  // Apply custom policy verdict: block from dashboard overrides API verdict
1749
2031
  const policyBlock = policyDecision === 'block' && policyViolations.length > 0;
1750
2032
  const governanceDecisionBlock = governanceResult?.governanceDecision?.decision === 'block';
@@ -1846,10 +2128,16 @@ async function verifyCommand(options) {
1846
2128
  bloatFiles: filteredBloatFiles,
1847
2129
  plannedFilesModified: verifyResult.plannedFilesModified,
1848
2130
  totalPlannedFiles: verifyResult.totalPlannedFiles,
2131
+ verificationSource: verifySource,
1849
2132
  mode: 'plan_enforced',
1850
2133
  policyOnly: false,
2134
+ changeContract: changeContractSummary,
2135
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1851
2136
  ...(governanceResult
1852
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
2137
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
2138
+ changeContract: changeContractSummary,
2139
+ compiledPolicy: compiledPolicyMetadata,
2140
+ })
1853
2141
  : {}),
1854
2142
  policyLock: {
1855
2143
  enforced: policyLockEvaluation.enforced,
@@ -1871,7 +2159,7 @@ async function verifyCommand(options) {
1871
2159
  }
1872
2160
  : {}),
1873
2161
  };
1874
- console.log(JSON.stringify(jsonOutput, null, 2));
2162
+ emitVerifyJson(jsonOutput);
1875
2163
  await recordVerificationIfRequested(options, config, {
1876
2164
  grade,
1877
2165
  violations: violations,
@@ -1881,11 +2169,16 @@ async function verifyCommand(options) {
1881
2169
  bloatCount: filteredBloatFiles.length,
1882
2170
  bloatFiles: filteredBloatFiles,
1883
2171
  message: effectiveMessage,
2172
+ verificationSource: verifySource,
1884
2173
  },
1885
2174
  projectId: projectId || undefined,
1886
2175
  jsonMode: true,
2176
+ verificationSource: verifySource,
1887
2177
  governance: governanceResult
1888
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
2178
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
2179
+ changeContract: changeContractSummary,
2180
+ compiledPolicy: compiledPolicyMetadata,
2181
+ })
1889
2182
  : undefined,
1890
2183
  });
1891
2184
  // Exit based on effective verdict (same logic as below)
@@ -1960,11 +2253,16 @@ async function verifyCommand(options) {
1960
2253
  bloatCount: filteredBloatForReport.length,
1961
2254
  bloatFiles: filteredBloatForReport,
1962
2255
  message: effectiveMessage,
2256
+ verificationSource: verifySource,
1963
2257
  },
1964
2258
  projectId: projectId || undefined,
1965
2259
  jsonMode: false,
2260
+ verificationSource: verifySource,
1966
2261
  governance: governanceResult
1967
- ? buildGovernancePayload(governanceResult, orgGovernanceSettings)
2262
+ ? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
2263
+ changeContract: changeContractSummary,
2264
+ compiledPolicy: compiledPolicyMetadata,
2265
+ })
1968
2266
  : undefined,
1969
2267
  });
1970
2268
  // Governance override: keep PASS only when scope guard passes and failure is due
@@ -2001,7 +2299,7 @@ async function verifyCommand(options) {
2001
2299
  recordVerifyEvent('FAIL', `verify_api_error=${error instanceof Error ? error.message : 'unknown'}`, changedFiles, finalPlanId);
2002
2300
  if (options.json) {
2003
2301
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2004
- console.log(JSON.stringify({
2302
+ emitVerifyJson({
2005
2303
  grade: 'F',
2006
2304
  score: 0,
2007
2305
  verdict: 'FAIL',
@@ -2013,7 +2311,7 @@ async function verifyCommand(options) {
2013
2311
  totalPlannedFiles: 0,
2014
2312
  message: `Error: ${errorMessage}`,
2015
2313
  scopeGuardPassed: false,
2016
- }, null, 2));
2314
+ });
2017
2315
  }
2018
2316
  else {
2019
2317
  if (error instanceof Error) {
@@ -2048,6 +2346,14 @@ async function verifyCommand(options) {
2048
2346
  totalPlannedFiles: 0,
2049
2347
  message: `Unexpected error: ${errorMessage}`,
2050
2348
  scopeGuardPassed: false,
2349
+ scope: {
2350
+ scanRoot: process.cwd(),
2351
+ startDir: process.cwd(),
2352
+ gitRoot: null,
2353
+ linkedRepoOverrideUsed: false,
2354
+ linkedRepos: [],
2355
+ blockedOverride: null,
2356
+ },
2051
2357
  }, null, 2));
2052
2358
  }
2053
2359
  else {
@@ -2212,7 +2518,7 @@ function buildCompactVerificationPayload(payload) {
2212
2518
  /**
2213
2519
  * Report verification results to Neurcode Cloud
2214
2520
  */
2215
- async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
2521
+ async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance, verificationSource) {
2216
2522
  try {
2217
2523
  const ciContext = collectCIContext();
2218
2524
  const payload = {
@@ -2229,6 +2535,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
2229
2535
  workflowRunId: ciContext.workflowRunId,
2230
2536
  projectId,
2231
2537
  governance,
2538
+ verificationSource: verificationSource || verifyResult.verificationSource || 'api',
2232
2539
  };
2233
2540
  const postPayload = async (requestPayload) => fetch(`${apiUrl}/api/v1/action/verifications`, {
2234
2541
  method: 'POST',
@@ -2271,7 +2578,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
2271
2578
  }
2272
2579
  }
2273
2580
  }
2274
- function buildGovernancePayload(governance, orgGovernanceSettings) {
2581
+ function buildGovernancePayload(governance, orgGovernanceSettings, options) {
2275
2582
  return {
2276
2583
  contextPolicy: governance.contextPolicy,
2277
2584
  blastRadius: governance.blastRadius,
@@ -2292,6 +2599,8 @@ function buildGovernancePayload(governance, orgGovernanceSettings) {
2292
2599
  updatedAt: orgGovernanceSettings.updatedAt || null,
2293
2600
  }
2294
2601
  : null,
2602
+ ...(options?.compiledPolicy ? { policyCompilation: options.compiledPolicy } : {}),
2603
+ ...(options?.changeContract ? { changeContract: options.changeContract } : {}),
2295
2604
  };
2296
2605
  }
2297
2606
  function displayGovernanceInsights(governance, options = {}) {