@neurcode-ai/cli 0.9.43 → 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 (180) hide show
  1. package/dist/commands/check.js +20 -2
  2. package/dist/commands/contract.js +47 -0
  3. package/dist/commands/plan.js +40 -0
  4. package/dist/commands/verify.d.ts +2 -0
  5. package/dist/commands/verify.js +294 -136
  6. package/dist/config.d.ts +13 -0
  7. package/dist/config.js +48 -0
  8. package/dist/index.js +41 -5
  9. package/dist/utils/advisory-signals.d.ts +20 -0
  10. package/dist/utils/advisory-signals.js +177 -0
  11. package/dist/utils/artifact-signature.js +21 -0
  12. package/dist/utils/change-contract.d.ts +105 -1
  13. package/dist/utils/change-contract.js +685 -12
  14. package/dist/utils/diff-symbols.d.ts +10 -0
  15. package/dist/utils/diff-symbols.js +218 -0
  16. package/dist/utils/plan-symbols.d.ts +17 -0
  17. package/dist/utils/plan-symbols.js +209 -0
  18. package/package.json +2 -11
  19. package/dist/api-client.d.ts.map +0 -1
  20. package/dist/api-client.js.map +0 -1
  21. package/dist/commands/allow.d.ts.map +0 -1
  22. package/dist/commands/allow.js.map +0 -1
  23. package/dist/commands/apply.d.ts.map +0 -1
  24. package/dist/commands/apply.js.map +0 -1
  25. package/dist/commands/approve.d.ts.map +0 -1
  26. package/dist/commands/approve.js.map +0 -1
  27. package/dist/commands/ask.d.ts.map +0 -1
  28. package/dist/commands/ask.js.map +0 -1
  29. package/dist/commands/audit.d.ts.map +0 -1
  30. package/dist/commands/audit.js.map +0 -1
  31. package/dist/commands/bootstrap.d.ts.map +0 -1
  32. package/dist/commands/bootstrap.js.map +0 -1
  33. package/dist/commands/brain.d.ts.map +0 -1
  34. package/dist/commands/brain.js.map +0 -1
  35. package/dist/commands/check.d.ts.map +0 -1
  36. package/dist/commands/check.js.map +0 -1
  37. package/dist/commands/config.d.ts.map +0 -1
  38. package/dist/commands/config.js.map +0 -1
  39. package/dist/commands/contract.d.ts.map +0 -1
  40. package/dist/commands/contract.js.map +0 -1
  41. package/dist/commands/doctor.d.ts.map +0 -1
  42. package/dist/commands/doctor.js.map +0 -1
  43. package/dist/commands/feedback.d.ts.map +0 -1
  44. package/dist/commands/feedback.js.map +0 -1
  45. package/dist/commands/guard.d.ts.map +0 -1
  46. package/dist/commands/guard.js.map +0 -1
  47. package/dist/commands/init.d.ts.map +0 -1
  48. package/dist/commands/init.js.map +0 -1
  49. package/dist/commands/login.d.ts.map +0 -1
  50. package/dist/commands/login.js.map +0 -1
  51. package/dist/commands/logout.d.ts.map +0 -1
  52. package/dist/commands/logout.js.map +0 -1
  53. package/dist/commands/map.d.ts.map +0 -1
  54. package/dist/commands/map.js.map +0 -1
  55. package/dist/commands/plan-slo.d.ts.map +0 -1
  56. package/dist/commands/plan-slo.js.map +0 -1
  57. package/dist/commands/plan.d.ts.map +0 -1
  58. package/dist/commands/plan.js.map +0 -1
  59. package/dist/commands/policy.d.ts.map +0 -1
  60. package/dist/commands/policy.js.map +0 -1
  61. package/dist/commands/prompt.d.ts.map +0 -1
  62. package/dist/commands/prompt.js.map +0 -1
  63. package/dist/commands/refactor.d.ts.map +0 -1
  64. package/dist/commands/refactor.js.map +0 -1
  65. package/dist/commands/remediate.d.ts.map +0 -1
  66. package/dist/commands/remediate.js.map +0 -1
  67. package/dist/commands/repo.d.ts.map +0 -1
  68. package/dist/commands/repo.js.map +0 -1
  69. package/dist/commands/revert.d.ts.map +0 -1
  70. package/dist/commands/revert.js.map +0 -1
  71. package/dist/commands/security.d.ts.map +0 -1
  72. package/dist/commands/security.js.map +0 -1
  73. package/dist/commands/session.d.ts.map +0 -1
  74. package/dist/commands/session.js.map +0 -1
  75. package/dist/commands/ship.d.ts.map +0 -1
  76. package/dist/commands/ship.js.map +0 -1
  77. package/dist/commands/simulate.d.ts.map +0 -1
  78. package/dist/commands/simulate.js.map +0 -1
  79. package/dist/commands/verify.d.ts.map +0 -1
  80. package/dist/commands/verify.js.map +0 -1
  81. package/dist/commands/watch.d.ts.map +0 -1
  82. package/dist/commands/watch.js.map +0 -1
  83. package/dist/commands/whoami.d.ts.map +0 -1
  84. package/dist/commands/whoami.js.map +0 -1
  85. package/dist/config.d.ts.map +0 -1
  86. package/dist/config.js.map +0 -1
  87. package/dist/index.d.ts.map +0 -1
  88. package/dist/index.js.map +0 -1
  89. package/dist/rules.d.ts.map +0 -1
  90. package/dist/rules.js.map +0 -1
  91. package/dist/services/integrations/TicketService.d.ts.map +0 -1
  92. package/dist/services/integrations/TicketService.js.map +0 -1
  93. package/dist/services/mapper/ProjectScanner.d.ts.map +0 -1
  94. package/dist/services/mapper/ProjectScanner.js.map +0 -1
  95. package/dist/services/project-knowledge-service.d.ts.map +0 -1
  96. package/dist/services/project-knowledge-service.js.map +0 -1
  97. package/dist/services/security/SecurityGuard.d.ts.map +0 -1
  98. package/dist/services/security/SecurityGuard.js.map +0 -1
  99. package/dist/services/toolbox-service.d.ts.map +0 -1
  100. package/dist/services/toolbox-service.js.map +0 -1
  101. package/dist/services/watch/BlobStore.d.ts.map +0 -1
  102. package/dist/services/watch/BlobStore.js.map +0 -1
  103. package/dist/services/watch/CommandPoller.d.ts.map +0 -1
  104. package/dist/services/watch/CommandPoller.js.map +0 -1
  105. package/dist/services/watch/Journal.d.ts.map +0 -1
  106. package/dist/services/watch/Journal.js.map +0 -1
  107. package/dist/services/watch/Sentinel.d.ts.map +0 -1
  108. package/dist/services/watch/Sentinel.js.map +0 -1
  109. package/dist/services/watch/Syncer.d.ts.map +0 -1
  110. package/dist/services/watch/Syncer.js.map +0 -1
  111. package/dist/utils/ROILogger.d.ts.map +0 -1
  112. package/dist/utils/ROILogger.js.map +0 -1
  113. package/dist/utils/RelevanceScorer.d.ts.map +0 -1
  114. package/dist/utils/RelevanceScorer.js.map +0 -1
  115. package/dist/utils/ai-debt-budget.d.ts.map +0 -1
  116. package/dist/utils/ai-debt-budget.js.map +0 -1
  117. package/dist/utils/artifact-signature.d.ts.map +0 -1
  118. package/dist/utils/artifact-signature.js.map +0 -1
  119. package/dist/utils/ask-cache.d.ts.map +0 -1
  120. package/dist/utils/ask-cache.js.map +0 -1
  121. package/dist/utils/box.d.ts.map +0 -1
  122. package/dist/utils/box.js.map +0 -1
  123. package/dist/utils/brain-context.d.ts.map +0 -1
  124. package/dist/utils/brain-context.js.map +0 -1
  125. package/dist/utils/breakage-simulator.d.ts.map +0 -1
  126. package/dist/utils/breakage-simulator.js.map +0 -1
  127. package/dist/utils/change-contract.d.ts.map +0 -1
  128. package/dist/utils/change-contract.js.map +0 -1
  129. package/dist/utils/cli-json.d.ts.map +0 -1
  130. package/dist/utils/cli-json.js.map +0 -1
  131. package/dist/utils/custom-policy-rules.d.ts.map +0 -1
  132. package/dist/utils/custom-policy-rules.js.map +0 -1
  133. package/dist/utils/git.d.ts.map +0 -1
  134. package/dist/utils/git.js.map +0 -1
  135. package/dist/utils/gitignore.d.ts.map +0 -1
  136. package/dist/utils/gitignore.js.map +0 -1
  137. package/dist/utils/governance.d.ts.map +0 -1
  138. package/dist/utils/governance.js.map +0 -1
  139. package/dist/utils/ignore.d.ts.map +0 -1
  140. package/dist/utils/ignore.js.map +0 -1
  141. package/dist/utils/manual-approvals.d.ts.map +0 -1
  142. package/dist/utils/manual-approvals.js.map +0 -1
  143. package/dist/utils/messages.d.ts.map +0 -1
  144. package/dist/utils/messages.js.map +0 -1
  145. package/dist/utils/neurcode-context.d.ts.map +0 -1
  146. package/dist/utils/neurcode-context.js.map +0 -1
  147. package/dist/utils/plan-cache.d.ts.map +0 -1
  148. package/dist/utils/plan-cache.js.map +0 -1
  149. package/dist/utils/plan-slo.d.ts.map +0 -1
  150. package/dist/utils/plan-slo.js.map +0 -1
  151. package/dist/utils/policy-audit.d.ts.map +0 -1
  152. package/dist/utils/policy-audit.js.map +0 -1
  153. package/dist/utils/policy-compiler.d.ts.map +0 -1
  154. package/dist/utils/policy-compiler.js.map +0 -1
  155. package/dist/utils/policy-exceptions.d.ts.map +0 -1
  156. package/dist/utils/policy-exceptions.js.map +0 -1
  157. package/dist/utils/policy-governance.d.ts.map +0 -1
  158. package/dist/utils/policy-governance.js.map +0 -1
  159. package/dist/utils/policy-packs.d.ts.map +0 -1
  160. package/dist/utils/policy-packs.js.map +0 -1
  161. package/dist/utils/project-detector.d.ts.map +0 -1
  162. package/dist/utils/project-detector.js.map +0 -1
  163. package/dist/utils/project-root.d.ts.map +0 -1
  164. package/dist/utils/project-root.js.map +0 -1
  165. package/dist/utils/repo-links.d.ts.map +0 -1
  166. package/dist/utils/repo-links.js.map +0 -1
  167. package/dist/utils/restore.d.ts.map +0 -1
  168. package/dist/utils/restore.js.map +0 -1
  169. package/dist/utils/runtime-guard.d.ts.map +0 -1
  170. package/dist/utils/runtime-guard.js.map +0 -1
  171. package/dist/utils/scope-telemetry.d.ts.map +0 -1
  172. package/dist/utils/scope-telemetry.js.map +0 -1
  173. package/dist/utils/secret-masking.d.ts.map +0 -1
  174. package/dist/utils/secret-masking.js.map +0 -1
  175. package/dist/utils/state.d.ts.map +0 -1
  176. package/dist/utils/state.js.map +0 -1
  177. package/dist/utils/tier.d.ts.map +0 -1
  178. package/dist/utils/tier.js.map +0 -1
  179. package/dist/utils/user-context.d.ts.map +0 -1
  180. package/dist/utils/user-context.js.map +0 -1
@@ -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");
@@ -796,31 +797,29 @@ function parseSigningKeyRing(raw) {
796
797
  return out;
797
798
  }
798
799
  function resolveGovernanceSigningConfig() {
799
- const signingKeys = parseSigningKeyRing(process.env.NEURCODE_GOVERNANCE_SIGNING_KEYS);
800
- const envSigningKey = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY?.trim() ||
801
- process.env.NEURCODE_AI_LOG_SIGNING_KEY?.trim() ||
802
- '';
803
- const requestedKeyId = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY_ID?.trim() || '';
800
+ const artifactSigningConfig = (0, artifact_signature_1.resolveGovernanceArtifactSigningConfigFromEnv)();
804
801
  const signer = process.env.NEURCODE_GOVERNANCE_SIGNER || process.env.USER || 'neurcode-cli';
805
- let signingKey = envSigningKey || null;
806
- let signingKeyId = requestedKeyId || null;
807
- if (!signingKey && Object.keys(signingKeys).length > 0) {
808
- if (signingKeyId && signingKeys[signingKeyId]) {
809
- signingKey = signingKeys[signingKeyId];
810
- }
811
- else {
812
- const fallbackKeyId = Object.keys(signingKeys).sort((a, b) => a.localeCompare(b))[0];
813
- signingKey = signingKeys[fallbackKeyId];
814
- signingKeyId = signingKeyId || fallbackKeyId;
815
- }
816
- }
817
802
  return {
818
- signingKey,
819
- signingKeyId,
820
- signingKeys,
803
+ signingKey: artifactSigningConfig.signingKey,
804
+ signingKeyId: artifactSigningConfig.signingKeyId,
805
+ signingKeys: artifactSigningConfig.signingKeys,
821
806
  signer,
822
807
  };
823
808
  }
809
+ function isGitRepository(cwd) {
810
+ try {
811
+ const output = (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', {
812
+ cwd,
813
+ encoding: 'utf-8',
814
+ stdio: ['ignore', 'pipe', 'pipe'],
815
+ maxBuffer: 1024 * 1024,
816
+ }).trim().toLowerCase();
817
+ return output === 'true';
818
+ }
819
+ catch {
820
+ return false;
821
+ }
822
+ }
824
823
  function isSignedAiLogsRequired(orgGovernanceSettings) {
825
824
  return (orgGovernanceSettings?.requireSignedAiLogs === true ||
826
825
  isEnabledFlag(process.env.NEURCODE_GOVERNANCE_REQUIRE_SIGNED_LOGS) ||
@@ -1525,6 +1524,40 @@ async function verifyCommand(options) {
1525
1524
  };
1526
1525
  console.log(JSON.stringify(jsonPayload, null, 2));
1527
1526
  };
1527
+ if (!isGitRepository(projectRoot)) {
1528
+ const message = 'Verify requires a git repository. Initialize git (`git init`) or run this command inside an existing git project.';
1529
+ if (options.json) {
1530
+ emitVerifyJson({
1531
+ grade: 'F',
1532
+ score: 0,
1533
+ verdict: 'FAIL',
1534
+ violations: [
1535
+ {
1536
+ file: '.',
1537
+ rule: 'git_repository_required',
1538
+ severity: 'block',
1539
+ message,
1540
+ },
1541
+ ],
1542
+ adherenceScore: 0,
1543
+ bloatCount: 0,
1544
+ bloatFiles: [],
1545
+ plannedFilesModified: 0,
1546
+ totalPlannedFiles: 0,
1547
+ message,
1548
+ scopeGuardPassed: false,
1549
+ mode: 'git_repository_required',
1550
+ policyOnly: options.policyOnly === true,
1551
+ });
1552
+ }
1553
+ else {
1554
+ console.log(chalk.red('\n❌ Git Repository Required'));
1555
+ console.log(chalk.red(` ${message}`));
1556
+ console.log(chalk.dim(` Current path: ${projectRoot}`));
1557
+ console.log(chalk.dim(' Next step: git init && git add . && git commit -m "chore: baseline"\n'));
1558
+ }
1559
+ process.exit(1);
1560
+ }
1528
1561
  const enforceChangeContract = options.enforceChangeContract === true ||
1529
1562
  isEnabledFlag(process.env.NEURCODE_VERIFY_ENFORCE_CHANGE_CONTRACT);
1530
1563
  const explicitStrictArtifactMode = options.strictArtifacts === true ||
@@ -1972,7 +2005,7 @@ async function verifyCommand(options) {
1972
2005
  // Determine which diff to capture (staged + unstaged for full current work)
1973
2006
  let diffText;
1974
2007
  if (options.staged) {
1975
- diffText = (0, child_process_1.execSync)('git diff --staged', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2008
+ diffText = (0, child_process_1.execSync)('git diff --cached', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
1976
2009
  }
1977
2010
  else if (options.base) {
1978
2011
  diffText = (0, git_1.getDiffFromBase)(options.base);
@@ -1983,7 +2016,7 @@ async function verifyCommand(options) {
1983
2016
  else {
1984
2017
  // Default: combine staged + unstaged to capture all current work
1985
2018
  try {
1986
- const stagedDiff = (0, child_process_1.execSync)('git diff --staged', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
2019
+ const stagedDiff = (0, child_process_1.execSync)('git diff --cached', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
1987
2020
  const unstagedDiff = (0, child_process_1.execSync)('git diff', { maxBuffer: 1024 * 1024 * 1024, encoding: 'utf-8' });
1988
2021
  diffText = stagedDiff + (stagedDiff && unstagedDiff ? '\n' : '') + unstagedDiff;
1989
2022
  }
@@ -2276,6 +2309,9 @@ async function verifyCommand(options) {
2276
2309
  console.log(chalk.cyan('\n📊 Analyzing changes against plan...'));
2277
2310
  console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
2278
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
+ }
2279
2315
  }
2280
2316
  const runPolicyOnlyModeAndExit = async (source) => {
2281
2317
  const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary);
@@ -2368,10 +2404,104 @@ async function verifyCommand(options) {
2368
2404
  });
2369
2405
  process.exit(1);
2370
2406
  }
2371
- if (!options.json) {
2372
- 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
+ }
2443
+ }
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
+ });
2373
2490
  }
2374
- await runPolicyOnlyModeAndExit('fallback_missing_plan');
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);
2375
2505
  }
2376
2506
  if (!planId) {
2377
2507
  throw new Error('Plan ID resolution failed unexpectedly');
@@ -2965,6 +3095,7 @@ async function verifyCommand(options) {
2965
3095
  })),
2966
3096
  })),
2967
3097
  }));
3098
+ const changedSymbols = (0, diff_symbols_1.extractDeclaredSymbolsFromDiff)(diffFiles);
2968
3099
  const compiledIntentProof = (0, governance_runtime_1.compileDeterministicConstraints)({
2969
3100
  intentConstraints: intentConstraintsForVerification,
2970
3101
  policyRules: deterministicPolicyRules,
@@ -3036,6 +3167,16 @@ async function verifyCommand(options) {
3036
3167
  ? (0, change_contract_1.evaluateChangeContract)(changeContractRead.contract, {
3037
3168
  planId: finalPlanId,
3038
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
+ })),
3039
3180
  policyLockFingerprint: (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint || null,
3040
3181
  compiledPolicyFingerprint: effectiveCompiledPolicy?.fingerprint || null,
3041
3182
  })
@@ -3054,6 +3195,8 @@ async function verifyCommand(options) {
3054
3195
  code: item.code,
3055
3196
  message: item.message,
3056
3197
  file: item.file,
3198
+ symbol: item.symbol,
3199
+ symbolType: item.symbolType,
3057
3200
  expected: item.expected,
3058
3201
  actual: item.actual,
3059
3202
  })),
@@ -3065,9 +3208,8 @@ async function verifyCommand(options) {
3065
3208
  severity: 'block',
3066
3209
  message: item.message,
3067
3210
  }));
3068
- const message = `Change contract enforcement failed: ${changeContractEvaluation.violations
3069
- .map((item) => item.message)
3070
- .join('; ')}`;
3211
+ const message = `Implementation deviates from intended contract (` +
3212
+ `${changeContractEvaluation.violations.length} violation(s)).`;
3071
3213
  if (options.json) {
3072
3214
  emitVerifyJson({
3073
3215
  grade: 'F',
@@ -3088,11 +3230,7 @@ async function verifyCommand(options) {
3088
3230
  });
3089
3231
  }
3090
3232
  else {
3091
- console.log(chalk.red('\n⛔ Change contract enforcement failed'));
3092
- changeContractEvaluation.violations.forEach((item) => {
3093
- console.log(chalk.red(` • ${item.message}`));
3094
- });
3095
- console.log(chalk.dim(` Contract path: ${changeContractRead.path}`));
3233
+ displayChangeContractDrift(changeContractSummary, { advisory: false });
3096
3234
  }
3097
3235
  await recordVerificationIfRequested(options, config, {
3098
3236
  grade: 'F',
@@ -3116,13 +3254,7 @@ async function verifyCommand(options) {
3116
3254
  process.exit(2);
3117
3255
  }
3118
3256
  else if (!changeContractEvaluation.valid && !options.json) {
3119
- console.log(chalk.yellow('\n⚠️ Change contract drift detected (advisory mode)'));
3120
- changeContractEvaluation.violations.slice(0, 5).forEach((item) => {
3121
- console.log(chalk.yellow(` • ${item.message}`));
3122
- });
3123
- if (changeContractEvaluation.violations.length > 5) {
3124
- console.log(chalk.dim(` ... ${changeContractEvaluation.violations.length - 5} more violation(s)`));
3125
- }
3257
+ displayChangeContractDrift(changeContractSummary, { advisory: true });
3126
3258
  }
3127
3259
  }
3128
3260
  // Call verify API
@@ -3880,111 +4012,137 @@ function displayGovernanceInsights(governance, options = {}) {
3880
4012
  });
3881
4013
  }
3882
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
+ }
3883
4046
  /**
3884
4047
  * Display verification results in a formatted report card
3885
4048
  */
3886
4049
  function displayVerifyResults(result, policyViolations) {
3887
- // Calculate grade/score
3888
- // CRITICAL: 0/0 planned files = 'F' (Incomplete)
3889
- // Bloat automatically drops grade by at least one letter
3890
- let grade;
3891
- let gradeColor;
3892
- if (result.totalPlannedFiles === 0 && result.plannedFilesModified === 0) {
3893
- // Special case: No planned files = Incomplete (F)
3894
- grade = 'F';
3895
- gradeColor = chalk.red;
3896
- }
3897
- else if (result.verdict === 'PASS') {
3898
- grade = 'A';
3899
- gradeColor = chalk.green;
3900
- }
3901
- else if (result.verdict === 'WARN') {
3902
- // Base grade calculation
3903
- let baseGrade = result.adherenceScore >= 70 ? 'B' : result.adherenceScore >= 50 ? 'C' : 'D';
3904
- // Bloat drops grade by one letter (B -> C, C -> D, D -> F)
3905
- if (result.bloatCount > 0) {
3906
- if (baseGrade === 'B')
3907
- baseGrade = 'C';
3908
- else if (baseGrade === 'C')
3909
- baseGrade = 'D';
3910
- else if (baseGrade === 'D')
3911
- baseGrade = 'F';
3912
- }
3913
- grade = baseGrade;
3914
- gradeColor = chalk.yellow;
3915
- }
3916
- else {
3917
- grade = 'F';
3918
- gradeColor = chalk.red;
3919
- }
3920
- // Calculate estimated time saved (5 minutes per VERIFY_PASS)
3921
- const estimatedMinutesSaved = result.verdict === 'PASS' ? 5 : 0;
3922
- // Calculate policy compliance percentage
3923
- const policyCompliance = result.bloatCount === 0 ? 100 : Math.max(0, 100 - (result.bloatCount * 10));
3924
- // Display Governance Badge for PASS and FAIL verdicts (high visibility)
3925
- if (result.verdict === 'PASS' || result.verdict === 'FAIL') {
3926
- console.log('\n');
3927
- const borderColor = result.verdict === 'PASS' ? 'green' : 'red';
3928
- const gradeColorFunc = result.verdict === 'PASS' ? chalk.green.bold : chalk.red.bold;
3929
- const badgeContent = [
3930
- `${chalk.bold.white('Governance Badge')}`,
3931
- '',
3932
- `${chalk.cyan('Grade:')} ${gradeColorFunc(grade)} ${chalk.dim(`(${result.adherenceScore}%)`)}`,
3933
- result.verdict === 'PASS' ? `${chalk.cyan('Estimated Time Saved:')} ${chalk.green.bold(`${estimatedMinutesSaved}m`)}` : '',
3934
- `${chalk.cyan('Policy Compliance:')} ${result.verdict === 'PASS' ? chalk.green.bold(`${policyCompliance}%`) : chalk.red.bold(`${policyCompliance}%`)}`,
3935
- ].filter(line => line !== '').join('\n');
3936
- console.log((0, box_1.createBox)(badgeContent, {
3937
- borderColor,
3938
- titleColor: 'white',
3939
- padding: 2,
3940
- }));
3941
- console.log('');
3942
- }
3943
- console.log(chalk.bold.cyan('📋 Plan Adherence Report\n'));
3944
- console.log('━'.repeat(50));
3945
- const scoreDisplay = gradeColor(`Grade: ${grade} (${result.adherenceScore}%)`);
3946
- if (result.verdict === 'PASS') {
3947
- 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
+ }
3948
4067
  }
3949
- else if (result.verdict === 'WARN') {
3950
- 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
+ }
3951
4089
  }
3952
- else {
3953
- console.log(chalk.red(''), scoreDisplay);
4090
+ if (result.bloatCount === 0 && (!policyViolations || policyViolations.length === 0)) {
4091
+ console.log(chalk.green('\nNo drift detected.'));
3954
4092
  }
3955
- console.log('');
3956
- console.log(chalk.bold.white('Adherence:'));
3957
- console.log(` ${result.plannedFilesModified}/${result.totalPlannedFiles} planned files modified`);
3958
- console.log(` ${result.adherenceScore}% adherence to plan`);
3959
- // Display bloat
3960
- if (result.bloatCount > 0) {
3961
- console.log('');
3962
- console.log(chalk.bold.red(`🚫 Bloat Detected: ${result.bloatCount} unexpected file(s)`));
3963
- console.log(chalk.red(' Blocked Bloat:'));
3964
- result.bloatFiles.forEach(file => {
3965
- console.log(chalk.red(` • ${file}`));
3966
- });
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.'));
3967
4104
  }
3968
- else {
3969
- console.log('');
3970
- 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;
3971
4112
  }
3972
- // Display custom policy violations from dashboard
3973
- if (policyViolations && policyViolations.length > 0) {
3974
- console.log('');
3975
- const blockCount = policyViolations.filter(v => v.severity === 'block').length;
3976
- const label = blockCount > 0
3977
- ? chalk.bold.red(`🚫 Custom Policy Violations: ${policyViolations.length} (${blockCount} blocking)`)
3978
- : chalk.bold.yellow(`⚠️ Custom Policy Warnings: ${policyViolations.length}`);
3979
- console.log(label);
3980
- policyViolations.forEach(v => {
3981
- const lineColor = v.severity === 'block' ? chalk.red : chalk.yellow;
3982
- 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}`));
3983
4120
  });
3984
4121
  }
3985
- console.log('');
3986
- console.log('━'.repeat(50));
3987
- console.log(chalk.dim(result.message));
3988
- 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
+ });
3989
4147
  }
3990
4148
  //# sourceMappingURL=verify.js.map
package/dist/config.d.ts CHANGED
@@ -10,6 +10,19 @@ export interface NeurcodeConfig {
10
10
  projectId?: string;
11
11
  orgId?: string;
12
12
  }
13
+ export interface LocalGovernanceSigningMaterial {
14
+ signingKey: string;
15
+ signingKeyId: string;
16
+ source: 'persisted' | 'generated';
17
+ }
18
+ /**
19
+ * Resolve a local governance signing key from ~/.neurcoderc.
20
+ * If authenticated and no key exists, auto-provision one for smoother
21
+ * login-first onboarding in orgs that require signed AI logs.
22
+ */
23
+ export declare function getOrCreateLocalGovernanceSigningMaterial(options?: {
24
+ autoProvision?: boolean;
25
+ }): LocalGovernanceSigningMaterial | null;
13
26
  export declare function loadConfig(): NeurcodeConfig;
14
27
  /**
15
28
  * Get API key with helpful error message if not found
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_API_URL = void 0;
4
+ exports.getOrCreateLocalGovernanceSigningMaterial = getOrCreateLocalGovernanceSigningMaterial;
4
5
  exports.loadConfig = loadConfig;
5
6
  exports.getApiKey = getApiKey;
6
7
  exports.requireApiKey = requireApiKey;
@@ -10,6 +11,7 @@ exports.deleteGlobalAuth = deleteGlobalAuth;
10
11
  exports.deleteApiKeyFromAllSources = deleteApiKeyFromAllSources;
11
12
  const fs_1 = require("fs");
12
13
  const path_1 = require("path");
14
+ const crypto_1 = require("crypto");
13
15
  const state_1 = require("./utils/state");
14
16
  const project_root_1 = require("./utils/project-root");
15
17
  /**
@@ -93,6 +95,52 @@ function writeGlobalAuthFile(data) {
93
95
  // Ignore
94
96
  }
95
97
  }
98
+ /**
99
+ * Resolve a local governance signing key from ~/.neurcoderc.
100
+ * If authenticated and no key exists, auto-provision one for smoother
101
+ * login-first onboarding in orgs that require signed AI logs.
102
+ */
103
+ function getOrCreateLocalGovernanceSigningMaterial(options) {
104
+ const autoProvision = options?.autoProvision !== false;
105
+ const global = readGlobalAuthFile();
106
+ if (!global)
107
+ return null;
108
+ const cfg = global.data || {};
109
+ const persistedKey = typeof cfg.governanceSigningKey === 'string'
110
+ ? cfg.governanceSigningKey.trim()
111
+ : '';
112
+ let persistedKeyId = typeof cfg.governanceSigningKeyId === 'string'
113
+ ? cfg.governanceSigningKeyId.trim()
114
+ : '';
115
+ if (persistedKey) {
116
+ if (!persistedKeyId) {
117
+ persistedKeyId = `local-${Date.now().toString(36)}`;
118
+ cfg.governanceSigningKeyId = persistedKeyId;
119
+ writeGlobalAuthFile(cfg);
120
+ }
121
+ return {
122
+ signingKey: persistedKey,
123
+ signingKeyId: persistedKeyId,
124
+ source: 'persisted',
125
+ };
126
+ }
127
+ if (!autoProvision)
128
+ return null;
129
+ const hasApiAuthMaterial = Boolean((cfg.apiKey || '').trim())
130
+ || Object.keys(cfg.apiKeysByOrg || {}).length > 0;
131
+ if (!hasApiAuthMaterial)
132
+ return null;
133
+ const signingKey = (0, crypto_1.randomBytes)(32).toString('hex');
134
+ const signingKeyId = `local-${Date.now().toString(36)}`;
135
+ cfg.governanceSigningKey = signingKey;
136
+ cfg.governanceSigningKeyId = signingKeyId;
137
+ writeGlobalAuthFile(cfg);
138
+ return {
139
+ signingKey,
140
+ signingKeyId,
141
+ source: 'generated',
142
+ };
143
+ }
96
144
  function pickApiKeyFromKeyring(globalCfg, desiredOrgId) {
97
145
  const keyring = globalCfg.apiKeysByOrg || {};
98
146
  if (desiredOrgId) {