@neurcode-ai/cli 0.9.38 → 0.9.40

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.
@@ -64,6 +64,7 @@ const policy_audit_1 = require("../utils/policy-audit");
64
64
  const governance_1 = require("../utils/governance");
65
65
  const policy_compiler_1 = require("../utils/policy-compiler");
66
66
  const change_contract_1 = require("../utils/change-contract");
67
+ const runtime_guard_1 = require("../utils/runtime-guard");
67
68
  const artifact_signature_1 = require("../utils/artifact-signature");
68
69
  const policy_1 = require("@neurcode-ai/policy");
69
70
  // Import chalk with fallback
@@ -434,6 +435,51 @@ function isEnabledFlag(value) {
434
435
  const normalized = value.trim().toLowerCase();
435
436
  return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
436
437
  }
438
+ function createRuntimeGuardSummary(required, projectRoot, runtimeGuardPath) {
439
+ return {
440
+ required,
441
+ path: (0, runtime_guard_1.resolveRuntimeGuardPath)(projectRoot, runtimeGuardPath),
442
+ exists: false,
443
+ valid: false,
444
+ active: false,
445
+ pass: !required,
446
+ changedFiles: 0,
447
+ outOfScopeFiles: [],
448
+ constraintViolations: [],
449
+ violations: [],
450
+ };
451
+ }
452
+ function runtimeGuardViolationsToReport(summary) {
453
+ if (!summary.required || summary.pass) {
454
+ return [];
455
+ }
456
+ if (!summary.exists) {
457
+ return [
458
+ {
459
+ file: summary.path,
460
+ rule: 'runtime_guard_missing',
461
+ severity: 'block',
462
+ message: 'Runtime guard artifact is missing. Run `neurcode guard start` before verify.',
463
+ },
464
+ ];
465
+ }
466
+ if (!summary.valid) {
467
+ return [
468
+ {
469
+ file: summary.path,
470
+ rule: 'runtime_guard_invalid',
471
+ severity: 'block',
472
+ message: 'Runtime guard artifact is invalid. Regenerate with `neurcode guard start`.',
473
+ },
474
+ ];
475
+ }
476
+ return summary.violations.map((item) => ({
477
+ file: item.file || summary.path,
478
+ rule: `runtime_guard:${item.code.toLowerCase()}`,
479
+ severity: 'block',
480
+ message: item.message,
481
+ }));
482
+ }
437
483
  function parseSigningKeyRing(raw) {
438
484
  if (!raw || !raw.trim()) {
439
485
  return {};
@@ -1188,6 +1234,8 @@ async function verifyCommand(options) {
1188
1234
  && !isEnabledFlag(process.env.NEURCODE_VERIFY_ALLOW_NON_STRICT_CI)
1189
1235
  && Boolean(options.apiKey || process.env.NEURCODE_API_KEY);
1190
1236
  const strictArtifactMode = explicitStrictArtifactMode || ciEnterpriseDefaultStrict;
1237
+ const requireRuntimeGuard = options.requireRuntimeGuard === true
1238
+ || isEnabledFlag(process.env.NEURCODE_VERIFY_REQUIRE_RUNTIME_GUARD);
1191
1239
  const signingConfig = resolveGovernanceSigningConfig();
1192
1240
  const aiLogSigningKey = signingConfig.signingKey;
1193
1241
  const aiLogSigningKeyId = signingConfig.signingKeyId;
@@ -1201,6 +1249,7 @@ async function verifyCommand(options) {
1201
1249
  || (!allowUnsignedArtifacts && strictArtifactMode && hasSigningMaterial);
1202
1250
  const changeContractRead = (0, change_contract_1.readChangeContract)(projectRoot, options.changeContract);
1203
1251
  const compiledPolicyRead = (0, policy_compiler_1.readCompiledPolicyArtifact)(projectRoot, options.compiledPolicy);
1252
+ let runtimeGuardSummary = createRuntimeGuardSummary(requireRuntimeGuard, projectRoot, options.runtimeGuard);
1204
1253
  let compiledPolicyMetadata = resolveCompiledPolicyMetadata(compiledPolicyRead.artifact, compiledPolicyRead.exists ? compiledPolicyRead.path : null);
1205
1254
  const compiledPolicySignatureStatus = compiledPolicyRead.artifact
1206
1255
  ? (0, artifact_signature_1.verifyGovernanceArtifactSignature)({
@@ -1705,6 +1754,140 @@ async function verifyCommand(options) {
1705
1754
  const normalized = toUnixPath(filePath || '');
1706
1755
  return ignoreFilter(normalized) || runtimeIgnoreSet.has(normalized);
1707
1756
  };
1757
+ if (requireRuntimeGuard) {
1758
+ const guardRead = (0, runtime_guard_1.readRuntimeGuardArtifact)(projectRoot, options.runtimeGuard);
1759
+ runtimeGuardSummary = {
1760
+ ...runtimeGuardSummary,
1761
+ path: guardRead.path,
1762
+ exists: guardRead.exists,
1763
+ valid: Boolean(guardRead.artifact),
1764
+ };
1765
+ if (!guardRead.artifact) {
1766
+ const message = guardRead.error
1767
+ ? `Runtime guard artifact is invalid: ${guardRead.error}`
1768
+ : 'Runtime guard artifact missing. Run `neurcode guard start` before verify.';
1769
+ runtimeGuardSummary = {
1770
+ ...runtimeGuardSummary,
1771
+ active: false,
1772
+ pass: false,
1773
+ violations: [
1774
+ {
1775
+ code: guardRead.error ? 'RUNTIME_GUARD_INACTIVE' : 'RUNTIME_GUARD_INACTIVE',
1776
+ message,
1777
+ },
1778
+ ],
1779
+ };
1780
+ const runtimeGuardViolationItems = runtimeGuardViolationsToReport(runtimeGuardSummary);
1781
+ recordVerifyEvent('FAIL', 'runtime_guard_missing_or_invalid', diffFiles.map((f) => f.path));
1782
+ if (options.json) {
1783
+ emitVerifyJson({
1784
+ grade: 'F',
1785
+ score: 0,
1786
+ verdict: 'FAIL',
1787
+ violations: runtimeGuardViolationItems,
1788
+ adherenceScore: 0,
1789
+ bloatCount: 0,
1790
+ bloatFiles: [],
1791
+ plannedFilesModified: 0,
1792
+ totalPlannedFiles: 0,
1793
+ message,
1794
+ scopeGuardPassed: false,
1795
+ mode: 'runtime_guard_required',
1796
+ policyOnly: Boolean(options.policyOnly),
1797
+ runtimeGuard: runtimeGuardSummary,
1798
+ });
1799
+ }
1800
+ else {
1801
+ console.log(chalk.red('\n⛔ Runtime Guard Required'));
1802
+ console.log(chalk.red(` ${message}`));
1803
+ console.log(chalk.dim(` Path: ${runtimeGuardSummary.path}`));
1804
+ console.log(chalk.dim(' Start guard: neurcode guard start --strict\n'));
1805
+ }
1806
+ await recordVerificationIfRequested(options, config, {
1807
+ grade: 'F',
1808
+ violations: runtimeGuardViolationItems,
1809
+ verifyResult: {
1810
+ adherenceScore: 0,
1811
+ verdict: 'FAIL',
1812
+ bloatCount: 0,
1813
+ bloatFiles: [],
1814
+ message,
1815
+ },
1816
+ projectId: projectId || undefined,
1817
+ jsonMode: Boolean(options.json),
1818
+ });
1819
+ process.exit(2);
1820
+ }
1821
+ const runtimeGuardEvaluation = (0, runtime_guard_1.evaluateRuntimeGuardArtifact)(guardRead.artifact, diffFiles.filter((file) => !shouldIgnore(file.path)));
1822
+ runtimeGuardSummary = {
1823
+ ...runtimeGuardSummary,
1824
+ active: guardRead.artifact.active,
1825
+ pass: runtimeGuardEvaluation.pass,
1826
+ changedFiles: runtimeGuardEvaluation.changedFiles.length,
1827
+ outOfScopeFiles: runtimeGuardEvaluation.outOfScopeFiles,
1828
+ constraintViolations: runtimeGuardEvaluation.constraintViolations,
1829
+ violations: runtimeGuardEvaluation.violations.map((item) => ({
1830
+ code: item.code,
1831
+ message: item.message,
1832
+ ...(item.file ? { file: item.file } : {}),
1833
+ })),
1834
+ };
1835
+ const runtimeGuardUpdated = (0, runtime_guard_1.withRuntimeGuardCheckStats)(guardRead.artifact, {
1836
+ blocked: !runtimeGuardEvaluation.pass,
1837
+ });
1838
+ (0, runtime_guard_1.writeRuntimeGuardArtifact)(projectRoot, runtimeGuardUpdated, options.runtimeGuard);
1839
+ if (!runtimeGuardEvaluation.pass) {
1840
+ const message = runtimeGuardEvaluation.violations.length > 0
1841
+ ? `Runtime guard blocked ${runtimeGuardEvaluation.violations.length} violation(s).`
1842
+ : 'Runtime guard blocked verification.';
1843
+ const runtimeGuardViolationItems = runtimeGuardViolationsToReport(runtimeGuardSummary);
1844
+ recordVerifyEvent('FAIL', `runtime_guard_violations=${runtimeGuardEvaluation.violations.length}`, diffFiles.map((f) => f.path));
1845
+ if (options.json) {
1846
+ emitVerifyJson({
1847
+ grade: 'F',
1848
+ score: 0,
1849
+ verdict: 'FAIL',
1850
+ violations: runtimeGuardViolationItems,
1851
+ adherenceScore: 0,
1852
+ bloatCount: runtimeGuardEvaluation.outOfScopeFiles.length,
1853
+ bloatFiles: runtimeGuardEvaluation.outOfScopeFiles,
1854
+ plannedFilesModified: runtimeGuardEvaluation.plannedFilesModified,
1855
+ totalPlannedFiles: runtimeGuardEvaluation.totalPlannedFiles,
1856
+ message,
1857
+ scopeGuardPassed: false,
1858
+ mode: 'runtime_guard_blocked',
1859
+ policyOnly: Boolean(options.policyOnly),
1860
+ runtimeGuard: runtimeGuardSummary,
1861
+ });
1862
+ }
1863
+ else {
1864
+ console.log(chalk.red('\n⛔ Runtime Guard Blocked Verification'));
1865
+ runtimeGuardEvaluation.violations.forEach((item) => {
1866
+ const file = item.file ? `${item.file}: ` : '';
1867
+ console.log(chalk.red(` • ${file}${item.message}`));
1868
+ });
1869
+ console.log(chalk.dim(`\n Guard artifact: ${runtimeGuardSummary.path}\n`));
1870
+ }
1871
+ await recordVerificationIfRequested(options, config, {
1872
+ grade: 'F',
1873
+ violations: runtimeGuardViolationItems,
1874
+ verifyResult: {
1875
+ adherenceScore: runtimeGuardEvaluation.adherenceScore,
1876
+ verdict: 'FAIL',
1877
+ bloatCount: runtimeGuardEvaluation.outOfScopeFiles.length,
1878
+ bloatFiles: runtimeGuardEvaluation.outOfScopeFiles,
1879
+ message,
1880
+ },
1881
+ projectId: projectId || undefined,
1882
+ jsonMode: Boolean(options.json),
1883
+ });
1884
+ process.exit(2);
1885
+ }
1886
+ if (!options.json) {
1887
+ console.log(chalk.dim(` Runtime guard passed (${runtimeGuardSummary.changedFiles} changed file(s), ` +
1888
+ `${runtimeGuardSummary.violations.length} violation(s))`));
1889
+ }
1890
+ }
1708
1891
  const baselineContextPolicyLocal = (0, policy_1.loadContextPolicy)(projectRoot);
1709
1892
  const baselineContextPolicy = orgGovernanceSettings?.contextPolicy
1710
1893
  ? (0, policy_1.mergeContextPolicies)(baselineContextPolicyLocal, orgGovernanceSettings.contextPolicy)
@@ -2550,6 +2733,19 @@ async function verifyCommand(options) {
2550
2733
  console.log(chalk.yellow('⚠️ Verify API unavailable, using local deterministic fallback.'));
2551
2734
  console.log(chalk.dim(` Reason: ${fallbackReason}`));
2552
2735
  }
2736
+ const localFileContents = {};
2737
+ for (const file of changedFiles) {
2738
+ const absolutePath = (0, path_1.join)(projectRoot, file.path);
2739
+ if (!(0, fs_1.existsSync)(absolutePath)) {
2740
+ continue;
2741
+ }
2742
+ try {
2743
+ localFileContents[file.path] = (0, fs_1.readFileSync)(absolutePath, 'utf-8');
2744
+ }
2745
+ catch {
2746
+ // Best effort only; fallback can still run on diff-only context.
2747
+ }
2748
+ }
2553
2749
  const localEvaluation = (0, governance_runtime_1.evaluatePlanVerification)({
2554
2750
  planFiles: planFilesForVerification.map((path) => ({
2555
2751
  path,
@@ -2560,6 +2756,7 @@ async function verifyCommand(options) {
2560
2756
  intentConstraints: intentConstraintsForVerification,
2561
2757
  policyRules: deterministicPolicyRules,
2562
2758
  extraConstraintRules: hydratedCompiledPolicyRules.length > 0 ? hydratedCompiledPolicyRules : undefined,
2759
+ fileContents: localFileContents,
2563
2760
  });
2564
2761
  verifyResult = {
2565
2762
  verificationId: `local-fallback-${Date.now()}`,
@@ -2693,6 +2890,7 @@ async function verifyCommand(options) {
2693
2890
  },
2694
2891
  policyExceptions: policyExceptionsSummary,
2695
2892
  policyGovernance: policyGovernanceSummary,
2893
+ ...(runtimeGuardSummary.required ? { runtimeGuard: runtimeGuardSummary } : {}),
2696
2894
  ...(policyViolations.length > 0 && { policyDecision }),
2697
2895
  ...(effectiveRules.policyPack
2698
2896
  ? {
@@ -2774,6 +2972,9 @@ async function verifyCommand(options) {
2774
2972
  console.log(chalk.red(` • ${issue}`));
2775
2973
  });
2776
2974
  }
2975
+ if (runtimeGuardSummary.required) {
2976
+ console.log(chalk.dim(`\n Runtime guard: ${runtimeGuardSummary.pass ? 'pass' : 'block'} (${runtimeGuardSummary.path})`));
2977
+ }
2777
2978
  }
2778
2979
  // Report to Neurcode Cloud if --record flag is set
2779
2980
  const filteredBloatForReport = (verifyResult.bloatFiles || []).filter((f) => !shouldIgnore(f));