@neurcode-ai/cli 0.9.44 → 0.9.45

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 (177) hide show
  1. package/dist/commands/contract.js +47 -0
  2. package/dist/commands/plan.js +40 -0
  3. package/dist/commands/verify.d.ts +2 -0
  4. package/dist/commands/verify.js +240 -114
  5. package/dist/index.js +41 -5
  6. package/dist/utils/advisory-signals.d.ts +20 -0
  7. package/dist/utils/advisory-signals.js +177 -0
  8. package/dist/utils/change-contract.d.ts +105 -1
  9. package/dist/utils/change-contract.js +685 -12
  10. package/dist/utils/diff-symbols.d.ts +10 -0
  11. package/dist/utils/diff-symbols.js +218 -0
  12. package/dist/utils/plan-symbols.d.ts +17 -0
  13. package/dist/utils/plan-symbols.js +209 -0
  14. package/package.json +6 -14
  15. package/LICENSE +0 -201
  16. package/dist/api-client.d.ts.map +0 -1
  17. package/dist/api-client.js.map +0 -1
  18. package/dist/commands/allow.d.ts.map +0 -1
  19. package/dist/commands/allow.js.map +0 -1
  20. package/dist/commands/apply.d.ts.map +0 -1
  21. package/dist/commands/apply.js.map +0 -1
  22. package/dist/commands/approve.d.ts.map +0 -1
  23. package/dist/commands/approve.js.map +0 -1
  24. package/dist/commands/ask.d.ts.map +0 -1
  25. package/dist/commands/ask.js.map +0 -1
  26. package/dist/commands/audit.d.ts.map +0 -1
  27. package/dist/commands/audit.js.map +0 -1
  28. package/dist/commands/bootstrap.d.ts.map +0 -1
  29. package/dist/commands/bootstrap.js.map +0 -1
  30. package/dist/commands/brain.d.ts.map +0 -1
  31. package/dist/commands/brain.js.map +0 -1
  32. package/dist/commands/check.d.ts.map +0 -1
  33. package/dist/commands/check.js.map +0 -1
  34. package/dist/commands/config.d.ts.map +0 -1
  35. package/dist/commands/config.js.map +0 -1
  36. package/dist/commands/contract.d.ts.map +0 -1
  37. package/dist/commands/contract.js.map +0 -1
  38. package/dist/commands/doctor.d.ts.map +0 -1
  39. package/dist/commands/doctor.js.map +0 -1
  40. package/dist/commands/feedback.d.ts.map +0 -1
  41. package/dist/commands/feedback.js.map +0 -1
  42. package/dist/commands/guard.d.ts.map +0 -1
  43. package/dist/commands/guard.js.map +0 -1
  44. package/dist/commands/init.d.ts.map +0 -1
  45. package/dist/commands/init.js.map +0 -1
  46. package/dist/commands/login.d.ts.map +0 -1
  47. package/dist/commands/login.js.map +0 -1
  48. package/dist/commands/logout.d.ts.map +0 -1
  49. package/dist/commands/logout.js.map +0 -1
  50. package/dist/commands/map.d.ts.map +0 -1
  51. package/dist/commands/map.js.map +0 -1
  52. package/dist/commands/plan-slo.d.ts.map +0 -1
  53. package/dist/commands/plan-slo.js.map +0 -1
  54. package/dist/commands/plan.d.ts.map +0 -1
  55. package/dist/commands/plan.js.map +0 -1
  56. package/dist/commands/policy.d.ts.map +0 -1
  57. package/dist/commands/policy.js.map +0 -1
  58. package/dist/commands/prompt.d.ts.map +0 -1
  59. package/dist/commands/prompt.js.map +0 -1
  60. package/dist/commands/refactor.d.ts.map +0 -1
  61. package/dist/commands/refactor.js.map +0 -1
  62. package/dist/commands/remediate.d.ts.map +0 -1
  63. package/dist/commands/remediate.js.map +0 -1
  64. package/dist/commands/repo.d.ts.map +0 -1
  65. package/dist/commands/repo.js.map +0 -1
  66. package/dist/commands/revert.d.ts.map +0 -1
  67. package/dist/commands/revert.js.map +0 -1
  68. package/dist/commands/security.d.ts.map +0 -1
  69. package/dist/commands/security.js.map +0 -1
  70. package/dist/commands/session.d.ts.map +0 -1
  71. package/dist/commands/session.js.map +0 -1
  72. package/dist/commands/ship.d.ts.map +0 -1
  73. package/dist/commands/ship.js.map +0 -1
  74. package/dist/commands/simulate.d.ts.map +0 -1
  75. package/dist/commands/simulate.js.map +0 -1
  76. package/dist/commands/verify.d.ts.map +0 -1
  77. package/dist/commands/verify.js.map +0 -1
  78. package/dist/commands/watch.d.ts.map +0 -1
  79. package/dist/commands/watch.js.map +0 -1
  80. package/dist/commands/whoami.d.ts.map +0 -1
  81. package/dist/commands/whoami.js.map +0 -1
  82. package/dist/config.d.ts.map +0 -1
  83. package/dist/config.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/rules.d.ts.map +0 -1
  87. package/dist/rules.js.map +0 -1
  88. package/dist/services/integrations/TicketService.d.ts.map +0 -1
  89. package/dist/services/integrations/TicketService.js.map +0 -1
  90. package/dist/services/mapper/ProjectScanner.d.ts.map +0 -1
  91. package/dist/services/mapper/ProjectScanner.js.map +0 -1
  92. package/dist/services/project-knowledge-service.d.ts.map +0 -1
  93. package/dist/services/project-knowledge-service.js.map +0 -1
  94. package/dist/services/security/SecurityGuard.d.ts.map +0 -1
  95. package/dist/services/security/SecurityGuard.js.map +0 -1
  96. package/dist/services/toolbox-service.d.ts.map +0 -1
  97. package/dist/services/toolbox-service.js.map +0 -1
  98. package/dist/services/watch/BlobStore.d.ts.map +0 -1
  99. package/dist/services/watch/BlobStore.js.map +0 -1
  100. package/dist/services/watch/CommandPoller.d.ts.map +0 -1
  101. package/dist/services/watch/CommandPoller.js.map +0 -1
  102. package/dist/services/watch/Journal.d.ts.map +0 -1
  103. package/dist/services/watch/Journal.js.map +0 -1
  104. package/dist/services/watch/Sentinel.d.ts.map +0 -1
  105. package/dist/services/watch/Sentinel.js.map +0 -1
  106. package/dist/services/watch/Syncer.d.ts.map +0 -1
  107. package/dist/services/watch/Syncer.js.map +0 -1
  108. package/dist/utils/ROILogger.d.ts.map +0 -1
  109. package/dist/utils/ROILogger.js.map +0 -1
  110. package/dist/utils/RelevanceScorer.d.ts.map +0 -1
  111. package/dist/utils/RelevanceScorer.js.map +0 -1
  112. package/dist/utils/ai-debt-budget.d.ts.map +0 -1
  113. package/dist/utils/ai-debt-budget.js.map +0 -1
  114. package/dist/utils/artifact-signature.d.ts.map +0 -1
  115. package/dist/utils/artifact-signature.js.map +0 -1
  116. package/dist/utils/ask-cache.d.ts.map +0 -1
  117. package/dist/utils/ask-cache.js.map +0 -1
  118. package/dist/utils/box.d.ts.map +0 -1
  119. package/dist/utils/box.js.map +0 -1
  120. package/dist/utils/brain-context.d.ts.map +0 -1
  121. package/dist/utils/brain-context.js.map +0 -1
  122. package/dist/utils/breakage-simulator.d.ts.map +0 -1
  123. package/dist/utils/breakage-simulator.js.map +0 -1
  124. package/dist/utils/change-contract.d.ts.map +0 -1
  125. package/dist/utils/change-contract.js.map +0 -1
  126. package/dist/utils/cli-json.d.ts.map +0 -1
  127. package/dist/utils/cli-json.js.map +0 -1
  128. package/dist/utils/custom-policy-rules.d.ts.map +0 -1
  129. package/dist/utils/custom-policy-rules.js.map +0 -1
  130. package/dist/utils/git.d.ts.map +0 -1
  131. package/dist/utils/git.js.map +0 -1
  132. package/dist/utils/gitignore.d.ts.map +0 -1
  133. package/dist/utils/gitignore.js.map +0 -1
  134. package/dist/utils/governance.d.ts.map +0 -1
  135. package/dist/utils/governance.js.map +0 -1
  136. package/dist/utils/ignore.d.ts.map +0 -1
  137. package/dist/utils/ignore.js.map +0 -1
  138. package/dist/utils/manual-approvals.d.ts.map +0 -1
  139. package/dist/utils/manual-approvals.js.map +0 -1
  140. package/dist/utils/messages.d.ts.map +0 -1
  141. package/dist/utils/messages.js.map +0 -1
  142. package/dist/utils/neurcode-context.d.ts.map +0 -1
  143. package/dist/utils/neurcode-context.js.map +0 -1
  144. package/dist/utils/plan-cache.d.ts.map +0 -1
  145. package/dist/utils/plan-cache.js.map +0 -1
  146. package/dist/utils/plan-slo.d.ts.map +0 -1
  147. package/dist/utils/plan-slo.js.map +0 -1
  148. package/dist/utils/policy-audit.d.ts.map +0 -1
  149. package/dist/utils/policy-audit.js.map +0 -1
  150. package/dist/utils/policy-compiler.d.ts.map +0 -1
  151. package/dist/utils/policy-compiler.js.map +0 -1
  152. package/dist/utils/policy-exceptions.d.ts.map +0 -1
  153. package/dist/utils/policy-exceptions.js.map +0 -1
  154. package/dist/utils/policy-governance.d.ts.map +0 -1
  155. package/dist/utils/policy-governance.js.map +0 -1
  156. package/dist/utils/policy-packs.d.ts.map +0 -1
  157. package/dist/utils/policy-packs.js.map +0 -1
  158. package/dist/utils/project-detector.d.ts.map +0 -1
  159. package/dist/utils/project-detector.js.map +0 -1
  160. package/dist/utils/project-root.d.ts.map +0 -1
  161. package/dist/utils/project-root.js.map +0 -1
  162. package/dist/utils/repo-links.d.ts.map +0 -1
  163. package/dist/utils/repo-links.js.map +0 -1
  164. package/dist/utils/restore.d.ts.map +0 -1
  165. package/dist/utils/restore.js.map +0 -1
  166. package/dist/utils/runtime-guard.d.ts.map +0 -1
  167. package/dist/utils/runtime-guard.js.map +0 -1
  168. package/dist/utils/scope-telemetry.d.ts.map +0 -1
  169. package/dist/utils/scope-telemetry.js.map +0 -1
  170. package/dist/utils/secret-masking.d.ts.map +0 -1
  171. package/dist/utils/secret-masking.js.map +0 -1
  172. package/dist/utils/state.d.ts.map +0 -1
  173. package/dist/utils/state.js.map +0 -1
  174. package/dist/utils/tier.d.ts.map +0 -1
  175. package/dist/utils/tier.js.map +0 -1
  176. package/dist/utils/user-context.d.ts.map +0 -1
  177. package/dist/utils/user-context.js.map +0 -1
@@ -12,6 +12,7 @@ const policy_packs_1 = require("../utils/policy-packs");
12
12
  const policy_compiler_1 = require("../utils/policy-compiler");
13
13
  const change_contract_1 = require("../utils/change-contract");
14
14
  const artifact_signature_1 = require("../utils/artifact-signature");
15
+ const plan_symbols_1 = require("../utils/plan-symbols");
15
16
  let chalk;
16
17
  try {
17
18
  chalk = require('chalk');
@@ -60,6 +61,49 @@ function parseAsJsonIfPossible(raw) {
60
61
  function normalizeProvider(provider) {
61
62
  return String(provider || 'generic').trim().toLowerCase();
62
63
  }
64
+ function parseBooleanFlag(raw, fallback) {
65
+ if (!raw || !raw.trim())
66
+ return fallback;
67
+ const normalized = raw.trim().toLowerCase();
68
+ if (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on')
69
+ return true;
70
+ if (normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off')
71
+ return false;
72
+ return fallback;
73
+ }
74
+ function parseNonNegativeInt(raw) {
75
+ if (!raw || !raw.trim())
76
+ return undefined;
77
+ const parsed = Number(raw);
78
+ if (!Number.isFinite(parsed))
79
+ return undefined;
80
+ const rounded = Math.floor(parsed);
81
+ return rounded >= 0 ? rounded : undefined;
82
+ }
83
+ function resolveChangeContractOptionsFromEnv() {
84
+ const maxUnexpectedFiles = parseNonNegativeInt(process.env.NEURCODE_CHANGE_CONTRACT_MAX_UNEXPECTED_FILES);
85
+ const maxMissingExpectedSymbols = parseNonNegativeInt(process.env.NEURCODE_CHANGE_CONTRACT_MAX_MISSING_EXPECTED_SYMBOLS);
86
+ return {
87
+ enforceExpectedFiles: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_EXPECTED_FILES, false),
88
+ enforceActionMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_ACTION_MATCHING, true),
89
+ allowRenameForModify: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ALLOW_RENAME_FOR_MODIFY, true),
90
+ enforceExpectedSymbols: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_EXPECTED_SYMBOLS, false),
91
+ enforceSymbolActionMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_SYMBOL_ACTION_MATCHING, false),
92
+ symbolTypeRelaxedMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_SYMBOL_TYPE_RELAXED_MATCHING, true),
93
+ symbolFileBasenameFallback: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_SYMBOL_FILE_BASENAME_FALLBACK, false),
94
+ ...(maxUnexpectedFiles !== undefined ? { maxUnexpectedFiles } : {}),
95
+ ...(maxMissingExpectedSymbols !== undefined ? { maxMissingExpectedSymbols } : {}),
96
+ };
97
+ }
98
+ function mapPlanFilesForChangeContract(files) {
99
+ return files
100
+ .map((file) => ({
101
+ path: file.path,
102
+ action: file.action,
103
+ reason: file.reason,
104
+ }))
105
+ .filter((file) => typeof file.path === 'string' && file.path.trim().length > 0);
106
+ }
63
107
  function normalizeCandidateLimit(value) {
64
108
  if (!Number.isFinite(value))
65
109
  return 8;
@@ -407,6 +451,9 @@ function contractCommand(program) {
407
451
  projectId: options.projectId || null,
408
452
  intent: options.intent || response.plan.summary || 'imported-plan',
409
453
  expectedFiles,
454
+ planFiles: mapPlanFilesForChangeContract(response.plan.files),
455
+ expectedSymbols: (0, plan_symbols_1.mapPlanSymbolsForChangeContract)(response.plan),
456
+ options: resolveChangeContractOptionsFromEnv(),
410
457
  policyLockFingerprint: policyLock.lock?.effective.fingerprint || null,
411
458
  compiledPolicyFingerprint: compiledPolicy.artifact?.fingerprint || null,
412
459
  });
@@ -55,6 +55,7 @@ const change_contract_1 = require("../utils/change-contract");
55
55
  const policy_packs_1 = require("../utils/policy-packs");
56
56
  const policy_compiler_1 = require("../utils/policy-compiler");
57
57
  const artifact_signature_1 = require("../utils/artifact-signature");
58
+ const plan_symbols_1 = require("../utils/plan-symbols");
58
59
  // Import chalk with fallback for plain strings if not available
59
60
  let chalk;
60
61
  try {
@@ -591,6 +592,39 @@ function parseBooleanFlag(raw, fallback) {
591
592
  }
592
593
  return fallback;
593
594
  }
595
+ function parseNonNegativeInt(raw) {
596
+ if (!raw || !raw.trim())
597
+ return undefined;
598
+ const parsed = Number(raw);
599
+ if (!Number.isFinite(parsed))
600
+ return undefined;
601
+ const rounded = Math.floor(parsed);
602
+ return rounded >= 0 ? rounded : undefined;
603
+ }
604
+ function resolveChangeContractOptionsFromEnv() {
605
+ const maxUnexpectedFiles = parseNonNegativeInt(process.env.NEURCODE_CHANGE_CONTRACT_MAX_UNEXPECTED_FILES);
606
+ const maxMissingExpectedSymbols = parseNonNegativeInt(process.env.NEURCODE_CHANGE_CONTRACT_MAX_MISSING_EXPECTED_SYMBOLS);
607
+ return {
608
+ enforceExpectedFiles: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_EXPECTED_FILES, false),
609
+ enforceActionMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_ACTION_MATCHING, true),
610
+ allowRenameForModify: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ALLOW_RENAME_FOR_MODIFY, true),
611
+ enforceExpectedSymbols: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_EXPECTED_SYMBOLS, false),
612
+ enforceSymbolActionMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_ENFORCE_SYMBOL_ACTION_MATCHING, false),
613
+ symbolTypeRelaxedMatching: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_SYMBOL_TYPE_RELAXED_MATCHING, true),
614
+ symbolFileBasenameFallback: parseBooleanFlag(process.env.NEURCODE_CHANGE_CONTRACT_SYMBOL_FILE_BASENAME_FALLBACK, false),
615
+ ...(maxUnexpectedFiles !== undefined ? { maxUnexpectedFiles } : {}),
616
+ ...(maxMissingExpectedSymbols !== undefined ? { maxMissingExpectedSymbols } : {}),
617
+ };
618
+ }
619
+ function mapPlanFilesForChangeContract(files) {
620
+ return files
621
+ .map((file) => ({
622
+ path: file.path,
623
+ action: file.action,
624
+ reason: file.reason,
625
+ }))
626
+ .filter((file) => typeof file.path === 'string' && file.path.trim().length > 0);
627
+ }
594
628
  function parseConfidenceScoreThreshold(raw) {
595
629
  if (!raw)
596
630
  return null;
@@ -854,6 +888,9 @@ function emitCachedPlanHit(input) {
854
888
  projectId: input.projectId || null,
855
889
  intent: input.response.plan.summary || 'cached-plan',
856
890
  expectedFiles,
891
+ planFiles: mapPlanFilesForChangeContract(input.response.plan.files),
892
+ expectedSymbols: (0, plan_symbols_1.mapPlanSymbolsForChangeContract)(input.response.plan),
893
+ options: resolveChangeContractOptionsFromEnv(),
857
894
  policyLockFingerprint: lockRead.lock?.effective.fingerprint || null,
858
895
  compiledPolicyFingerprint: compiledPolicyRead.artifact?.fingerprint || null,
859
896
  });
@@ -2284,6 +2321,9 @@ async function planCommand(intent, options) {
2284
2321
  projectId: finalProjectId || null,
2285
2322
  intent,
2286
2323
  expectedFiles,
2324
+ planFiles: mapPlanFilesForChangeContract(response.plan.files),
2325
+ expectedSymbols: (0, plan_symbols_1.mapPlanSymbolsForChangeContract)(response.plan),
2326
+ options: resolveChangeContractOptionsFromEnv(),
2287
2327
  policyLockFingerprint: lockRead.lock?.effective.fingerprint || null,
2288
2328
  compiledPolicyFingerprint: compiledPolicyRead.artifact?.fingerprint || null,
2289
2329
  });
@@ -37,6 +37,8 @@ interface VerifyOptions {
37
37
  runtimeGuard?: string;
38
38
  /** Print detailed AI change justification reasoning. */
39
39
  explain?: boolean;
40
+ /** Print extra explanatory output for demos/onboarding. */
41
+ demo?: boolean;
40
42
  /** Use queue-backed async verification mode on the API. */
41
43
  asyncMode?: boolean;
42
44
  /** Poll interval for async verification job status. */
@@ -51,7 +51,6 @@ const path_1 = require("path");
51
51
  const fs_1 = require("fs");
52
52
  const state_1 = require("../utils/state");
53
53
  const ROILogger_1 = require("../utils/ROILogger");
54
- const box_1 = require("../utils/box");
55
54
  const ignore_1 = require("../utils/ignore");
56
55
  const project_root_1 = require("../utils/project-root");
57
56
  const brain_context_1 = require("../utils/brain-context");
@@ -64,6 +63,8 @@ const policy_audit_1 = require("../utils/policy-audit");
64
63
  const governance_1 = require("../utils/governance");
65
64
  const policy_compiler_1 = require("../utils/policy-compiler");
66
65
  const change_contract_1 = require("../utils/change-contract");
66
+ const diff_symbols_1 = require("../utils/diff-symbols");
67
+ const advisory_signals_1 = require("../utils/advisory-signals");
67
68
  const runtime_guard_1 = require("../utils/runtime-guard");
68
69
  const artifact_signature_1 = require("../utils/artifact-signature");
69
70
  const policy_1 = require("@neurcode-ai/policy");
@@ -2308,6 +2309,9 @@ async function verifyCommand(options) {
2308
2309
  console.log(chalk.cyan('\n📊 Analyzing changes against plan...'));
2309
2310
  console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
2310
2311
  console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
2312
+ if (options.demo) {
2313
+ console.log(chalk.dim(' Demo mode enabled: showing extra context while keeping drift output short and grouped.\n'));
2314
+ }
2311
2315
  }
2312
2316
  const runPolicyOnlyModeAndExit = async (source) => {
2313
2317
  const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary);
@@ -2400,10 +2404,104 @@ async function verifyCommand(options) {
2400
2404
  });
2401
2405
  process.exit(1);
2402
2406
  }
2403
- if (!options.json) {
2404
- console.log(chalk.yellow('⚠️ No Plan ID found. Falling back to General Governance (Policy Only).'));
2407
+ let autoContractPath = null;
2408
+ if (!changeContractRead.contract && !strictArtifactMode) {
2409
+ try {
2410
+ const fallbackPlanId = `advisory_${Date.now()}`;
2411
+ const advisoryContract = buildMinimalAdvisoryContractFromDiff(diffFiles, fallbackPlanId);
2412
+ autoContractPath = (0, change_contract_1.writeChangeContract)(projectRoot, advisoryContract, options.changeContract);
2413
+ changeContractSummary = {
2414
+ path: autoContractPath,
2415
+ exists: true,
2416
+ enforced: false,
2417
+ valid: true,
2418
+ planId: advisoryContract.planId,
2419
+ contractId: advisoryContract.contractId,
2420
+ coverage: {
2421
+ expectedFiles: advisoryContract.expectedFiles.length,
2422
+ changedFiles: diffFiles.length,
2423
+ outOfContractFiles: 0,
2424
+ missingExpectedFiles: 0,
2425
+ blockedFilesTouched: 0,
2426
+ actionMismatches: 0,
2427
+ expectedSymbols: advisoryContract.expectedSymbols?.length || 0,
2428
+ changedSymbols: 0,
2429
+ missingExpectedSymbols: 0,
2430
+ blockedSymbolsTouched: 0,
2431
+ symbolActionMismatches: 0,
2432
+ symbolRenameMatches: 0,
2433
+ toleratedUnexpectedFiles: 0,
2434
+ toleratedMissingExpectedSymbols: 0,
2435
+ },
2436
+ signature: changeContractSummary.signature,
2437
+ violations: [],
2438
+ };
2439
+ }
2440
+ catch {
2441
+ autoContractPath = null;
2442
+ }
2405
2443
  }
2406
- await runPolicyOnlyModeAndExit('fallback_missing_plan');
2444
+ const message = 'No plan linked yet. Ran advisory verification for quick first-run experience. ' +
2445
+ 'Use `neurcode plan` and `neurcode contract import --auto-detect --write-change-contract` for full enforcement.';
2446
+ const advisorySignals = (0, advisory_signals_1.evaluateAdvisorySignals)({
2447
+ diffFiles,
2448
+ summary,
2449
+ });
2450
+ const advisoryWarnCount = advisorySignals.filter((item) => item.severity === 'warn').length;
2451
+ const advisoryVerdict = advisoryWarnCount > 0 ? 'WARN' : 'PASS';
2452
+ const advisoryGrade = advisoryWarnCount > 0 ? 'C' : 'B';
2453
+ const advisoryScore = advisoryWarnCount > 0 ? 60 : 70;
2454
+ const advisoryViolations = advisorySignals.map((item) => ({
2455
+ file: item.files[0] || '.',
2456
+ rule: `advisory:${item.code.toLowerCase()}`,
2457
+ severity: item.severity === 'warn' ? 'warn' : 'allow',
2458
+ message: `${item.title}: ${item.detail}`,
2459
+ }));
2460
+ recordVerifyEvent(advisoryVerdict, `advisory_missing_plan;signals=${advisorySignals.length};warn=${advisoryWarnCount}`, diffFiles.map((f) => f.path));
2461
+ if (options.json) {
2462
+ emitVerifyJson({
2463
+ grade: advisoryGrade,
2464
+ score: advisoryScore,
2465
+ verdict: advisoryVerdict,
2466
+ violations: advisoryViolations,
2467
+ adherenceScore: advisoryScore,
2468
+ bloatCount: 0,
2469
+ bloatFiles: [],
2470
+ plannedFilesModified: 0,
2471
+ totalPlannedFiles: 0,
2472
+ message,
2473
+ scopeGuardPassed: true,
2474
+ mode: 'advisory_missing_plan',
2475
+ advisoryMode: true,
2476
+ advisorySignals,
2477
+ policyOnly: true,
2478
+ policyOnlySource: 'fallback_missing_plan',
2479
+ ...(autoContractPath
2480
+ ? {
2481
+ changeContract: {
2482
+ ...changeContractSummary,
2483
+ path: autoContractPath,
2484
+ },
2485
+ }
2486
+ : {
2487
+ changeContract: changeContractSummary,
2488
+ }),
2489
+ });
2490
+ }
2491
+ else {
2492
+ printFirstRunAdvisoryMessage(options.demo === true);
2493
+ printAdvisorySignals(advisorySignals, options.demo === true);
2494
+ if (autoContractPath) {
2495
+ console.log(chalk.green(`✅ Auto-generated minimal advisory contract: ${autoContractPath}`));
2496
+ }
2497
+ else if (!changeContractRead.contract) {
2498
+ console.log(chalk.yellow('⚠️ Could not auto-generate advisory contract; continuing without contract.'));
2499
+ }
2500
+ console.log(chalk.dim('Next steps: neurcode plan "<intent>"'));
2501
+ console.log(chalk.dim(' neurcode contract import --auto-detect --write-change-contract'));
2502
+ console.log(chalk.dim(`\nSummary: ${message}\n`));
2503
+ }
2504
+ process.exit(0);
2407
2505
  }
2408
2506
  if (!planId) {
2409
2507
  throw new Error('Plan ID resolution failed unexpectedly');
@@ -2997,6 +3095,7 @@ async function verifyCommand(options) {
2997
3095
  })),
2998
3096
  })),
2999
3097
  }));
3098
+ const changedSymbols = (0, diff_symbols_1.extractDeclaredSymbolsFromDiff)(diffFiles);
3000
3099
  const compiledIntentProof = (0, governance_runtime_1.compileDeterministicConstraints)({
3001
3100
  intentConstraints: intentConstraintsForVerification,
3002
3101
  policyRules: deterministicPolicyRules,
@@ -3068,6 +3167,16 @@ async function verifyCommand(options) {
3068
3167
  ? (0, change_contract_1.evaluateChangeContract)(changeContractRead.contract, {
3069
3168
  planId: finalPlanId,
3070
3169
  changedFiles: changedFiles.map((file) => file.path),
3170
+ changedFileEntries: changedFiles.map((file) => ({
3171
+ path: file.path,
3172
+ changeType: file.changeType,
3173
+ })),
3174
+ changedSymbols: changedSymbols.map((symbol) => ({
3175
+ name: symbol.name,
3176
+ type: symbol.type,
3177
+ action: symbol.action,
3178
+ file: symbol.file,
3179
+ })),
3071
3180
  policyLockFingerprint: (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint || null,
3072
3181
  compiledPolicyFingerprint: effectiveCompiledPolicy?.fingerprint || null,
3073
3182
  })
@@ -3086,6 +3195,8 @@ async function verifyCommand(options) {
3086
3195
  code: item.code,
3087
3196
  message: item.message,
3088
3197
  file: item.file,
3198
+ symbol: item.symbol,
3199
+ symbolType: item.symbolType,
3089
3200
  expected: item.expected,
3090
3201
  actual: item.actual,
3091
3202
  })),
@@ -3097,9 +3208,8 @@ async function verifyCommand(options) {
3097
3208
  severity: 'block',
3098
3209
  message: item.message,
3099
3210
  }));
3100
- const message = `Change contract enforcement failed: ${changeContractEvaluation.violations
3101
- .map((item) => item.message)
3102
- .join('; ')}`;
3211
+ const message = `Implementation deviates from intended contract (` +
3212
+ `${changeContractEvaluation.violations.length} violation(s)).`;
3103
3213
  if (options.json) {
3104
3214
  emitVerifyJson({
3105
3215
  grade: 'F',
@@ -3120,11 +3230,7 @@ async function verifyCommand(options) {
3120
3230
  });
3121
3231
  }
3122
3232
  else {
3123
- console.log(chalk.red('\n⛔ Change contract enforcement failed'));
3124
- changeContractEvaluation.violations.forEach((item) => {
3125
- console.log(chalk.red(` • ${item.message}`));
3126
- });
3127
- console.log(chalk.dim(` Contract path: ${changeContractRead.path}`));
3233
+ displayChangeContractDrift(changeContractSummary, { advisory: false });
3128
3234
  }
3129
3235
  await recordVerificationIfRequested(options, config, {
3130
3236
  grade: 'F',
@@ -3148,13 +3254,7 @@ async function verifyCommand(options) {
3148
3254
  process.exit(2);
3149
3255
  }
3150
3256
  else if (!changeContractEvaluation.valid && !options.json) {
3151
- console.log(chalk.yellow('\n⚠️ Change contract drift detected (advisory mode)'));
3152
- changeContractEvaluation.violations.slice(0, 5).forEach((item) => {
3153
- console.log(chalk.yellow(` • ${item.message}`));
3154
- });
3155
- if (changeContractEvaluation.violations.length > 5) {
3156
- console.log(chalk.dim(` ... ${changeContractEvaluation.violations.length - 5} more violation(s)`));
3157
- }
3257
+ displayChangeContractDrift(changeContractSummary, { advisory: true });
3158
3258
  }
3159
3259
  }
3160
3260
  // Call verify API
@@ -3912,111 +4012,137 @@ function displayGovernanceInsights(governance, options = {}) {
3912
4012
  });
3913
4013
  }
3914
4014
  }
4015
+ function displayChangeContractDrift(summary, options = { advisory: false }) {
4016
+ const groups = (0, change_contract_1.groupChangeContractViolations)(summary.violations.map((item) => ({
4017
+ code: item.code,
4018
+ message: item.message,
4019
+ ...(item.file ? { file: item.file } : {}),
4020
+ ...(item.symbol ? { symbol: item.symbol } : {}),
4021
+ ...(item.symbolType ? { symbolType: item.symbolType } : {}),
4022
+ ...(item.expected ? { expected: item.expected } : {}),
4023
+ ...(item.actual ? { actual: item.actual } : {}),
4024
+ })));
4025
+ if (groups.length === 0)
4026
+ return;
4027
+ const maxItemsPerGroup = options.maxItemsPerGroup ?? 12;
4028
+ const header = options.advisory
4029
+ ? chalk.yellow('\nWARN ⚠️ Change contract drift detected')
4030
+ : chalk.red('\nFAIL ❌ Change contract enforcement failed');
4031
+ console.log(header);
4032
+ for (const group of groups) {
4033
+ console.log(chalk.white(`\n${group.title}:`));
4034
+ group.items.slice(0, maxItemsPerGroup).forEach((entry) => {
4035
+ console.log(` - ${entry}`);
4036
+ });
4037
+ if (group.items.length > maxItemsPerGroup) {
4038
+ console.log(chalk.dim(` - ... ${group.items.length - maxItemsPerGroup} more`));
4039
+ }
4040
+ console.log(chalk.dim(` Why it matters: ${group.impact}`));
4041
+ }
4042
+ console.log(chalk.dim('\nSummary:'));
4043
+ console.log(chalk.dim('Implementation deviates from intended contract.'));
4044
+ console.log(chalk.dim(`Contract path: ${summary.path}`));
4045
+ }
3915
4046
  /**
3916
4047
  * Display verification results in a formatted report card
3917
4048
  */
3918
4049
  function displayVerifyResults(result, policyViolations) {
3919
- // Calculate grade/score
3920
- // CRITICAL: 0/0 planned files = 'F' (Incomplete)
3921
- // Bloat automatically drops grade by at least one letter
3922
- let grade;
3923
- let gradeColor;
3924
- if (result.totalPlannedFiles === 0 && result.plannedFilesModified === 0) {
3925
- // Special case: No planned files = Incomplete (F)
3926
- grade = 'F';
3927
- gradeColor = chalk.red;
3928
- }
3929
- else if (result.verdict === 'PASS') {
3930
- grade = 'A';
3931
- gradeColor = chalk.green;
3932
- }
3933
- else if (result.verdict === 'WARN') {
3934
- // Base grade calculation
3935
- let baseGrade = result.adherenceScore >= 70 ? 'B' : result.adherenceScore >= 50 ? 'C' : 'D';
3936
- // Bloat drops grade by one letter (B -> C, C -> D, D -> F)
3937
- if (result.bloatCount > 0) {
3938
- if (baseGrade === 'B')
3939
- baseGrade = 'C';
3940
- else if (baseGrade === 'C')
3941
- baseGrade = 'D';
3942
- else if (baseGrade === 'D')
3943
- baseGrade = 'F';
3944
- }
3945
- grade = baseGrade;
3946
- gradeColor = chalk.yellow;
3947
- }
3948
- else {
3949
- grade = 'F';
3950
- gradeColor = chalk.red;
3951
- }
3952
- // Calculate estimated time saved (5 minutes per VERIFY_PASS)
3953
- const estimatedMinutesSaved = result.verdict === 'PASS' ? 5 : 0;
3954
- // Calculate policy compliance percentage
3955
- const policyCompliance = result.bloatCount === 0 ? 100 : Math.max(0, 100 - (result.bloatCount * 10));
3956
- // Display Governance Badge for PASS and FAIL verdicts (high visibility)
3957
- if (result.verdict === 'PASS' || result.verdict === 'FAIL') {
3958
- console.log('\n');
3959
- const borderColor = result.verdict === 'PASS' ? 'green' : 'red';
3960
- const gradeColorFunc = result.verdict === 'PASS' ? chalk.green.bold : chalk.red.bold;
3961
- const badgeContent = [
3962
- `${chalk.bold.white('Governance Badge')}`,
3963
- '',
3964
- `${chalk.cyan('Grade:')} ${gradeColorFunc(grade)} ${chalk.dim(`(${result.adherenceScore}%)`)}`,
3965
- result.verdict === 'PASS' ? `${chalk.cyan('Estimated Time Saved:')} ${chalk.green.bold(`${estimatedMinutesSaved}m`)}` : '',
3966
- `${chalk.cyan('Policy Compliance:')} ${result.verdict === 'PASS' ? chalk.green.bold(`${policyCompliance}%`) : chalk.red.bold(`${policyCompliance}%`)}`,
3967
- ].filter(line => line !== '').join('\n');
3968
- console.log((0, box_1.createBox)(badgeContent, {
3969
- borderColor,
3970
- titleColor: 'white',
3971
- padding: 2,
3972
- }));
3973
- console.log('');
3974
- }
3975
- console.log(chalk.bold.cyan('📋 Plan Adherence Report\n'));
3976
- console.log('━'.repeat(50));
3977
- const scoreDisplay = gradeColor(`Grade: ${grade} (${result.adherenceScore}%)`);
3978
- if (result.verdict === 'PASS') {
3979
- console.log(chalk.green('✅'), scoreDisplay);
4050
+ const verdictLabel = result.verdict === 'PASS'
4051
+ ? chalk.green('PASS ')
4052
+ : result.verdict === 'WARN'
4053
+ ? chalk.yellow('WARN ⚠️')
4054
+ : chalk.red('FAIL ❌');
4055
+ const plannedText = `${result.plannedFilesModified}/${result.totalPlannedFiles}`;
4056
+ console.log(`\n${verdictLabel}`);
4057
+ console.log(chalk.dim(`Plan adherence: ${plannedText} files (${result.adherenceScore}%)`));
4058
+ const maxItems = 20;
4059
+ if (result.bloatCount > 0) {
4060
+ console.log(chalk.red('\nOut-of-scope changes:'));
4061
+ result.bloatFiles.slice(0, maxItems).forEach((file) => {
4062
+ console.log(` - ${file}`);
4063
+ });
4064
+ if (result.bloatFiles.length > maxItems) {
4065
+ console.log(chalk.dim(` - ... ${result.bloatFiles.length - maxItems} more`));
4066
+ }
3980
4067
  }
3981
- else if (result.verdict === 'WARN') {
3982
- console.log(chalk.yellow('⚠️ '), scoreDisplay);
4068
+ if (policyViolations && policyViolations.length > 0) {
4069
+ const blocking = policyViolations.filter((item) => item.severity === 'block');
4070
+ const warnings = policyViolations.filter((item) => item.severity !== 'block');
4071
+ if (blocking.length > 0) {
4072
+ console.log(chalk.red('\nBlocking policy violations:'));
4073
+ blocking.slice(0, maxItems).forEach((item) => {
4074
+ console.log(` - ${item.file}: ${item.message || item.rule}`);
4075
+ });
4076
+ if (blocking.length > maxItems) {
4077
+ console.log(chalk.dim(` - ... ${blocking.length - maxItems} more`));
4078
+ }
4079
+ }
4080
+ if (warnings.length > 0) {
4081
+ console.log(chalk.yellow('\nPolicy warnings:'));
4082
+ warnings.slice(0, maxItems).forEach((item) => {
4083
+ console.log(` - ${item.file}: ${item.message || item.rule}`);
4084
+ });
4085
+ if (warnings.length > maxItems) {
4086
+ console.log(chalk.dim(` - ... ${warnings.length - maxItems} more`));
4087
+ }
4088
+ }
3983
4089
  }
3984
- else {
3985
- console.log(chalk.red(''), scoreDisplay);
4090
+ if (result.bloatCount === 0 && (!policyViolations || policyViolations.length === 0)) {
4091
+ console.log(chalk.green('\nNo drift detected.'));
3986
4092
  }
3987
- console.log('');
3988
- console.log(chalk.bold.white('Adherence:'));
3989
- console.log(` ${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified`);
3990
- console.log(` ${result.adherenceScore}% adherence to plan`);
3991
- // Display bloat
3992
- if (result.bloatCount > 0) {
3993
- console.log('');
3994
- console.log(chalk.bold.red(`🚫 Bloat Detected: ${result.bloatCount} unexpected file(s)`));
3995
- console.log(chalk.red(' Blocked Bloat:'));
3996
- result.bloatFiles.forEach(file => {
3997
- console.log(chalk.red(` • ${file}`));
3998
- });
4093
+ console.log(chalk.dim(`\nSummary: ${result.message}\n`));
4094
+ }
4095
+ function printFirstRunAdvisoryMessage(demoMode) {
4096
+ console.log(chalk.cyan('\nNeurcode first-run advisory mode'));
4097
+ console.log(chalk.dim('Neurcode checks if your AI-generated code matches your intended plan.'));
4098
+ console.log(chalk.dim('To get full enforcement:'));
4099
+ console.log(chalk.dim('1. Define a plan'));
4100
+ console.log(chalk.dim('2. Generate a contract'));
4101
+ console.log(chalk.dim('Running in advisory mode for now.\n'));
4102
+ if (demoMode) {
4103
+ console.log(chalk.dim('Demo mode: this run is intentionally non-blocking to make evaluation easy.'));
3999
4104
  }
4000
- else {
4001
- console.log('');
4002
- console.log(chalk.green('✅ No bloat detected - all changes match the plan'));
4105
+ }
4106
+ function printAdvisorySignals(signals, demoMode) {
4107
+ if (signals.length === 0) {
4108
+ if (demoMode) {
4109
+ console.log(chalk.dim('No high-signal advisory findings detected for this diff.'));
4110
+ }
4111
+ return;
4003
4112
  }
4004
- // Display custom policy violations from dashboard
4005
- if (policyViolations && policyViolations.length > 0) {
4006
- console.log('');
4007
- const blockCount = policyViolations.filter(v => v.severity === 'block').length;
4008
- const label = blockCount > 0
4009
- ? chalk.bold.red(`🚫 Custom Policy Violations: ${policyViolations.length} (${blockCount} blocking)`)
4010
- : chalk.bold.yellow(`⚠️ Custom Policy Warnings: ${policyViolations.length}`);
4011
- console.log(label);
4012
- policyViolations.forEach(v => {
4013
- const lineColor = v.severity === 'block' ? chalk.red : chalk.yellow;
4014
- console.log(lineColor(` • ${v.file}: ${v.message || v.rule}`));
4113
+ console.log(chalk.yellow('\nAdvisory findings (non-blocking):'));
4114
+ for (const signal of signals) {
4115
+ const severityLabel = signal.severity === 'warn' ? chalk.yellow('[warn]') : chalk.dim('[info]');
4116
+ console.log(`${severityLabel} ${signal.title}`);
4117
+ console.log(chalk.dim(` ${signal.detail}`));
4118
+ signal.files.forEach((file) => {
4119
+ console.log(chalk.dim(` - ${file}`));
4015
4120
  });
4016
4121
  }
4017
- console.log('');
4018
- console.log('━'.repeat(50));
4019
- console.log(chalk.dim(result.message));
4020
- console.log('');
4122
+ }
4123
+ function buildMinimalAdvisoryContractFromDiff(diffFiles, fallbackPlanId) {
4124
+ const expectedFiles = [...new Set(diffFiles.map((file) => toUnixPath(file.path)).filter(Boolean))];
4125
+ const planFiles = expectedFiles.map((path) => {
4126
+ const entry = diffFiles.find((file) => toUnixPath(file.path) === path);
4127
+ const changeType = entry?.changeType;
4128
+ const action = changeType === 'add' ? 'CREATE' : 'MODIFY';
4129
+ return {
4130
+ path,
4131
+ action: action,
4132
+ reason: 'Auto-generated advisory baseline from current diff',
4133
+ };
4134
+ });
4135
+ return (0, change_contract_1.createChangeContract)({
4136
+ planId: fallbackPlanId,
4137
+ intent: 'Advisory baseline generated from current repository diff',
4138
+ expectedFiles,
4139
+ planFiles,
4140
+ options: {
4141
+ enforceExpectedFiles: false,
4142
+ enforceActionMatching: false,
4143
+ enforceExpectedSymbols: false,
4144
+ enforceSymbolActionMatching: false,
4145
+ },
4146
+ });
4021
4147
  }
4022
4148
  //# sourceMappingURL=verify.js.map