@neurcode-ai/cli 0.9.65 → 0.10.0

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 (260) hide show
  1. package/dist/commands/bootstrap-policy.d.ts +29 -0
  2. package/dist/commands/bootstrap-policy.d.ts.map +1 -0
  3. package/dist/commands/bootstrap-policy.js +334 -0
  4. package/dist/commands/bootstrap-policy.js.map +1 -0
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +82 -0
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/governance.d.ts +3 -0
  9. package/dist/commands/governance.d.ts.map +1 -0
  10. package/dist/commands/governance.js +390 -0
  11. package/dist/commands/governance.js.map +1 -0
  12. package/dist/commands/quickstart.d.ts +21 -0
  13. package/dist/commands/quickstart.d.ts.map +1 -0
  14. package/dist/commands/quickstart.js +178 -0
  15. package/dist/commands/quickstart.js.map +1 -0
  16. package/dist/commands/remediate-export.d.ts +36 -0
  17. package/dist/commands/remediate-export.d.ts.map +1 -0
  18. package/dist/commands/remediate-export.js +1072 -0
  19. package/dist/commands/remediate-export.js.map +1 -0
  20. package/dist/commands/replay.d.ts.map +1 -1
  21. package/dist/commands/replay.js +14 -0
  22. package/dist/commands/replay.js.map +1 -1
  23. package/dist/commands/session.d.ts +7 -0
  24. package/dist/commands/session.d.ts.map +1 -1
  25. package/dist/commands/session.js +156 -0
  26. package/dist/commands/session.js.map +1 -1
  27. package/dist/commands/start-intent.d.ts.map +1 -1
  28. package/dist/commands/start-intent.js +61 -11
  29. package/dist/commands/start-intent.js.map +1 -1
  30. package/dist/commands/verify-guidance.d.ts +5 -0
  31. package/dist/commands/verify-guidance.d.ts.map +1 -0
  32. package/dist/commands/verify-guidance.js +49 -0
  33. package/dist/commands/verify-guidance.js.map +1 -0
  34. package/dist/commands/verify-output.d.ts +37 -0
  35. package/dist/commands/verify-output.d.ts.map +1 -0
  36. package/dist/commands/verify-output.js +572 -0
  37. package/dist/commands/verify-output.js.map +1 -0
  38. package/dist/commands/verify-render.d.ts +41 -0
  39. package/dist/commands/verify-render.d.ts.map +1 -0
  40. package/dist/commands/verify-render.js +457 -0
  41. package/dist/commands/verify-render.js.map +1 -0
  42. package/dist/commands/verify.d.ts.map +1 -1
  43. package/dist/commands/verify.js +384 -1091
  44. package/dist/commands/verify.js.map +1 -1
  45. package/dist/commands/workspace.d.ts.map +1 -1
  46. package/dist/commands/workspace.js +3 -14
  47. package/dist/commands/workspace.js.map +1 -1
  48. package/dist/context-engine/graph.d.ts.map +1 -1
  49. package/dist/context-engine/graph.js +69 -7
  50. package/dist/context-engine/graph.js.map +1 -1
  51. package/dist/context-engine/scanner.d.ts.map +1 -1
  52. package/dist/context-engine/scanner.js +9 -2
  53. package/dist/context-engine/scanner.js.map +1 -1
  54. package/dist/daemon/compatibility/execution.d.ts +42 -0
  55. package/dist/daemon/compatibility/execution.d.ts.map +1 -0
  56. package/dist/daemon/compatibility/execution.js +183 -0
  57. package/dist/daemon/compatibility/execution.js.map +1 -0
  58. package/dist/daemon/compatibility/mutation.d.ts +24 -0
  59. package/dist/daemon/compatibility/mutation.d.ts.map +1 -0
  60. package/dist/daemon/compatibility/mutation.js +724 -0
  61. package/dist/daemon/compatibility/mutation.js.map +1 -0
  62. package/dist/daemon/routes.d.ts +19 -0
  63. package/dist/daemon/routes.d.ts.map +1 -0
  64. package/dist/daemon/routes.js +123 -0
  65. package/dist/daemon/routes.js.map +1 -0
  66. package/dist/daemon/runtime/execution-bus.d.ts +217 -0
  67. package/dist/daemon/runtime/execution-bus.d.ts.map +1 -0
  68. package/dist/daemon/runtime/execution-bus.js +1420 -0
  69. package/dist/daemon/runtime/execution-bus.js.map +1 -0
  70. package/dist/daemon/runtime/workspace-runtime.d.ts +280 -0
  71. package/dist/daemon/runtime/workspace-runtime.d.ts.map +1 -0
  72. package/dist/daemon/runtime/workspace-runtime.js +1473 -0
  73. package/dist/daemon/runtime/workspace-runtime.js.map +1 -0
  74. package/dist/daemon/server.d.ts.map +1 -1
  75. package/dist/daemon/server.js +171 -874
  76. package/dist/daemon/server.js.map +1 -1
  77. package/dist/daemon/shaping.d.ts +11 -0
  78. package/dist/daemon/shaping.d.ts.map +1 -0
  79. package/dist/daemon/shaping.js +240 -0
  80. package/dist/daemon/shaping.js.map +1 -0
  81. package/dist/governance/canonical-invariants.d.ts +88 -0
  82. package/dist/governance/canonical-invariants.d.ts.map +1 -0
  83. package/dist/governance/canonical-invariants.js +197 -0
  84. package/dist/governance/canonical-invariants.js.map +1 -0
  85. package/dist/governance/canonical-ordering.d.ts +76 -0
  86. package/dist/governance/canonical-ordering.d.ts.map +1 -0
  87. package/dist/governance/canonical-ordering.js +189 -0
  88. package/dist/governance/canonical-ordering.js.map +1 -0
  89. package/dist/governance/canonical-pipeline.d.ts +9 -1
  90. package/dist/governance/canonical-pipeline.d.ts.map +1 -1
  91. package/dist/governance/canonical-pipeline.js +367 -24
  92. package/dist/governance/canonical-pipeline.js.map +1 -1
  93. package/dist/governance/diff-line-provenance.d.ts +59 -0
  94. package/dist/governance/diff-line-provenance.d.ts.map +1 -0
  95. package/dist/governance/diff-line-provenance.js +118 -0
  96. package/dist/governance/diff-line-provenance.js.map +1 -0
  97. package/dist/governance/pilot-readiness.d.ts +34 -0
  98. package/dist/governance/pilot-readiness.d.ts.map +1 -0
  99. package/dist/governance/pilot-readiness.js +226 -0
  100. package/dist/governance/pilot-readiness.js.map +1 -0
  101. package/dist/governance/policy-parity-validator.d.ts +62 -0
  102. package/dist/governance/policy-parity-validator.d.ts.map +1 -0
  103. package/dist/governance/policy-parity-validator.js +137 -0
  104. package/dist/governance/policy-parity-validator.js.map +1 -0
  105. package/dist/governance/remediation-boundary.d.ts +55 -0
  106. package/dist/governance/remediation-boundary.d.ts.map +1 -0
  107. package/dist/governance/remediation-boundary.js +120 -0
  108. package/dist/governance/remediation-boundary.js.map +1 -0
  109. package/dist/governance/structural-cache.d.ts +103 -0
  110. package/dist/governance/structural-cache.d.ts.map +1 -0
  111. package/dist/governance/structural-cache.js +235 -0
  112. package/dist/governance/structural-cache.js.map +1 -0
  113. package/dist/governance/structural-on-diff.d.ts +22 -2
  114. package/dist/governance/structural-on-diff.d.ts.map +1 -1
  115. package/dist/governance/structural-on-diff.js +36 -4
  116. package/dist/governance/structural-on-diff.js.map +1 -1
  117. package/dist/governance/structural-policy-merge.d.ts +8 -0
  118. package/dist/governance/structural-policy-merge.d.ts.map +1 -1
  119. package/dist/governance/structural-policy-merge.js +7 -0
  120. package/dist/governance/structural-policy-merge.js.map +1 -1
  121. package/dist/governance/verify-runtime-guard.d.ts +99 -0
  122. package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
  123. package/dist/governance/verify-runtime-guard.js +129 -0
  124. package/dist/governance/verify-runtime-guard.js.map +1 -0
  125. package/dist/index.js +277 -77
  126. package/dist/index.js.map +1 -1
  127. package/dist/intent-engine/repo-classifier.d.ts +64 -0
  128. package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
  129. package/dist/intent-engine/repo-classifier.js +178 -0
  130. package/dist/intent-engine/repo-classifier.js.map +1 -0
  131. package/dist/structural-rules/index.d.ts +4 -0
  132. package/dist/structural-rules/index.d.ts.map +1 -1
  133. package/dist/structural-rules/index.js +18 -1
  134. package/dist/structural-rules/index.js.map +1 -1
  135. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +21 -0
  136. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -1
  137. package/dist/structural-rules/python/PY003-broad-except-clause.js +212 -21
  138. package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -1
  139. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts +11 -0
  140. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
  141. package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
  142. package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
  143. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
  144. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
  145. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
  146. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
  147. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
  148. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
  149. package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
  150. package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
  151. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
  152. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
  153. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
  154. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js.map +1 -0
  155. package/dist/structural-rules/types.d.ts +12 -0
  156. package/dist/structural-rules/types.d.ts.map +1 -1
  157. package/dist/utils/active-engineering-context.d.ts +12 -0
  158. package/dist/utils/active-engineering-context.d.ts.map +1 -0
  159. package/dist/utils/active-engineering-context.js +67 -0
  160. package/dist/utils/active-engineering-context.js.map +1 -0
  161. package/dist/utils/artifact-io.d.ts +33 -0
  162. package/dist/utils/artifact-io.d.ts.map +1 -0
  163. package/dist/utils/artifact-io.js +183 -0
  164. package/dist/utils/artifact-io.js.map +1 -0
  165. package/dist/utils/change-contract.d.ts +6 -2
  166. package/dist/utils/change-contract.d.ts.map +1 -1
  167. package/dist/utils/change-contract.js +175 -0
  168. package/dist/utils/change-contract.js.map +1 -1
  169. package/dist/utils/context-pack.d.ts +12 -0
  170. package/dist/utils/context-pack.d.ts.map +1 -0
  171. package/dist/utils/context-pack.js +147 -0
  172. package/dist/utils/context-pack.js.map +1 -0
  173. package/dist/utils/control-plane.d.ts +18 -0
  174. package/dist/utils/control-plane.d.ts.map +1 -1
  175. package/dist/utils/control-plane.js +31 -4
  176. package/dist/utils/control-plane.js.map +1 -1
  177. package/dist/utils/drift-intelligence.d.ts +47 -0
  178. package/dist/utils/drift-intelligence.d.ts.map +1 -0
  179. package/dist/utils/drift-intelligence.js +2099 -0
  180. package/dist/utils/drift-intelligence.js.map +1 -0
  181. package/dist/utils/execution-actions.d.ts +22 -0
  182. package/dist/utils/execution-actions.d.ts.map +1 -0
  183. package/dist/utils/execution-actions.js +103 -0
  184. package/dist/utils/execution-actions.js.map +1 -0
  185. package/dist/utils/execution-bus.d.ts +1 -214
  186. package/dist/utils/execution-bus.d.ts.map +1 -1
  187. package/dist/utils/execution-bus.js +15 -1359
  188. package/dist/utils/execution-bus.js.map +1 -1
  189. package/dist/utils/git.d.ts +1 -0
  190. package/dist/utils/git.d.ts.map +1 -1
  191. package/dist/utils/git.js +13 -3
  192. package/dist/utils/git.js.map +1 -1
  193. package/dist/utils/governance-decisions.d.ts +75 -0
  194. package/dist/utils/governance-decisions.d.ts.map +1 -0
  195. package/dist/utils/governance-decisions.js +412 -0
  196. package/dist/utils/governance-decisions.js.map +1 -0
  197. package/dist/utils/governance-provenance.d.ts +1 -1
  198. package/dist/utils/governance-provenance.d.ts.map +1 -1
  199. package/dist/utils/governance-provenance.js +5 -7
  200. package/dist/utils/governance-provenance.js.map +1 -1
  201. package/dist/utils/governance.d.ts +108 -0
  202. package/dist/utils/governance.d.ts.map +1 -1
  203. package/dist/utils/governance.js +209 -7
  204. package/dist/utils/governance.js.map +1 -1
  205. package/dist/utils/intelligence-runtime-common.d.ts +30 -0
  206. package/dist/utils/intelligence-runtime-common.d.ts.map +1 -0
  207. package/dist/utils/intelligence-runtime-common.js +156 -0
  208. package/dist/utils/intelligence-runtime-common.js.map +1 -0
  209. package/dist/utils/intent-contract-diagnostics.d.ts +9 -0
  210. package/dist/utils/intent-contract-diagnostics.d.ts.map +1 -0
  211. package/dist/utils/intent-contract-diagnostics.js +322 -0
  212. package/dist/utils/intent-contract-diagnostics.js.map +1 -0
  213. package/dist/utils/intent-pack.d.ts +15 -0
  214. package/dist/utils/intent-pack.d.ts.map +1 -0
  215. package/dist/utils/intent-pack.js +196 -0
  216. package/dist/utils/intent-pack.js.map +1 -0
  217. package/dist/utils/plan-sync.d.ts +1 -0
  218. package/dist/utils/plan-sync.d.ts.map +1 -1
  219. package/dist/utils/plan-sync.js +23 -0
  220. package/dist/utils/plan-sync.js.map +1 -1
  221. package/dist/utils/policy-decision.d.ts +5 -0
  222. package/dist/utils/policy-decision.d.ts.map +1 -0
  223. package/dist/utils/policy-decision.js +17 -0
  224. package/dist/utils/policy-decision.js.map +1 -0
  225. package/dist/utils/replay-custody.d.ts +43 -0
  226. package/dist/utils/replay-custody.d.ts.map +1 -0
  227. package/dist/utils/replay-custody.js +168 -0
  228. package/dist/utils/replay-custody.js.map +1 -0
  229. package/dist/utils/replay-runtime.d.ts +13 -0
  230. package/dist/utils/replay-runtime.d.ts.map +1 -1
  231. package/dist/utils/replay-runtime.js +96 -9
  232. package/dist/utils/replay-runtime.js.map +1 -1
  233. package/dist/utils/repository-intelligence.d.ts +9 -0
  234. package/dist/utils/repository-intelligence.d.ts.map +1 -0
  235. package/dist/utils/repository-intelligence.js +372 -0
  236. package/dist/utils/repository-intelligence.js.map +1 -0
  237. package/dist/utils/runtime-events.d.ts.map +1 -1
  238. package/dist/utils/runtime-events.js +25 -6
  239. package/dist/utils/runtime-events.js.map +1 -1
  240. package/dist/utils/semantic-contract-intelligence.d.ts +20 -0
  241. package/dist/utils/semantic-contract-intelligence.d.ts.map +1 -0
  242. package/dist/utils/semantic-contract-intelligence.js +825 -0
  243. package/dist/utils/semantic-contract-intelligence.js.map +1 -0
  244. package/dist/utils/session-continuity.d.ts +56 -0
  245. package/dist/utils/session-continuity.d.ts.map +1 -0
  246. package/dist/utils/session-continuity.js +318 -0
  247. package/dist/utils/session-continuity.js.map +1 -0
  248. package/dist/utils/verification-evidence.d.ts.map +1 -1
  249. package/dist/utils/verification-evidence.js +4 -1
  250. package/dist/utils/verification-evidence.js.map +1 -1
  251. package/dist/utils/verify-runtime-stability.d.ts +142 -0
  252. package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
  253. package/dist/utils/verify-runtime-stability.js +230 -0
  254. package/dist/utils/verify-runtime-stability.js.map +1 -0
  255. package/dist/utils/workspace-runtime.d.ts +1 -266
  256. package/dist/utils/workspace-runtime.d.ts.map +1 -1
  257. package/dist/utils/workspace-runtime.js +15 -1412
  258. package/dist/utils/workspace-runtime.js.map +1 -1
  259. package/package.json +11 -10
  260. package/LICENSE +0 -201
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Enforcement Parity Validator
4
+ *
5
+ * Closes the governance theatre gap: a policy can appear in policy.yml
6
+ * claiming "deterministic" enforcement, but have zero structural rule
7
+ * implementation. This validator surfaces those mismatches explicitly.
8
+ *
9
+ * Benchmark finding (Apache Airflow, 2026-05-12):
10
+ * 7 of 15 policies were unenforced. Enterprise teams believed those
11
+ * policies were being checked. They were not.
12
+ *
13
+ * This module is called from `neurcode verify --policy-only` to emit
14
+ * GOV_PARITY_MISMATCH advisory findings for every enforcement gap.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.validatePolicyEnforcementParity = validatePolicyEnforcementParity;
18
+ exports.formatParityReport = formatParityReport;
19
+ /**
20
+ * Validate enforcement parity between policy declarations and the structural rule engine.
21
+ *
22
+ * @param policyRules - Rules from the compiled/loaded policy (array of { id, name, enforcementType? })
23
+ * @param registeredStructuralRuleIds - Set of rule IDs currently registered in StructuralRuleEngine
24
+ * @returns PolicyParityReport + mismatch findings (one per unenforced deterministic policy)
25
+ */
26
+ function validatePolicyEnforcementParity(policyRules, registeredStructuralRuleIds) {
27
+ const entries = [];
28
+ const findings = [];
29
+ let deterministicCount = 0;
30
+ let advisoryCount = 0;
31
+ let semanticCount = 0;
32
+ let noneCount = 0;
33
+ const unenforced = [];
34
+ for (const rule of policyRules) {
35
+ const rawType = rule.enforcementType ?? 'none';
36
+ const enforcementType = normalizeEnforcementType(rawType);
37
+ const hasStructuralImpl = registeredStructuralRuleIds.has(rule.id);
38
+ // Policy-engine rules (evaluated separately) — count as implemented if known
39
+ const hasPolicyEngineImpl = KNOWN_POLICY_ENGINE_RULES.has(rule.id);
40
+ entries.push({
41
+ ruleId: rule.id,
42
+ name: rule.name,
43
+ enforcementType,
44
+ hasStructuralImpl,
45
+ hasPolicyEngineImpl,
46
+ });
47
+ switch (enforcementType) {
48
+ case 'deterministic':
49
+ deterministicCount++;
50
+ break;
51
+ case 'advisory':
52
+ advisoryCount++;
53
+ break;
54
+ case 'semantic':
55
+ semanticCount++;
56
+ break;
57
+ case 'none':
58
+ noneCount++;
59
+ break;
60
+ }
61
+ // Emit a mismatch finding if a policy claims deterministic enforcement
62
+ // but has no matching rule implementation
63
+ if (enforcementType === 'deterministic' && !hasStructuralImpl && !hasPolicyEngineImpl) {
64
+ unenforced.push(rule.id);
65
+ findings.push({
66
+ ruleId: rule.id,
67
+ policyName: rule.name,
68
+ severity: 'advisory',
69
+ governanceCode: 'GOV_PARITY_MISMATCH',
70
+ message: `Policy "${rule.name ?? rule.id}" declares enforcementType: deterministic ` +
71
+ `but has no registered structural or policy-engine implementation. ` +
72
+ `This policy will NEVER produce a finding. ` +
73
+ `Either add a structural rule for ${rule.id}, change enforcementType to "none", ` +
74
+ `or remove the policy. ` +
75
+ `(Unenforced deterministic policies create false governance confidence.)`,
76
+ });
77
+ }
78
+ }
79
+ const enforced = deterministicCount - unenforced.length;
80
+ const coveragePct = deterministicCount === 0
81
+ ? 100 // no deterministic claims = 100% parity (vacuously true)
82
+ : Math.round((enforced / deterministicCount) * 100);
83
+ const report = {
84
+ totalPolicies: policyRules.length,
85
+ deterministicCount,
86
+ advisoryCount,
87
+ semanticCount,
88
+ noneCount,
89
+ coveragePct,
90
+ unenforced,
91
+ entries,
92
+ };
93
+ return { report, findings };
94
+ }
95
+ function normalizeEnforcementType(raw) {
96
+ const v = raw.toLowerCase().trim();
97
+ if (v === 'deterministic')
98
+ return 'deterministic';
99
+ if (v === 'advisory')
100
+ return 'advisory';
101
+ if (v === 'semantic')
102
+ return 'semantic';
103
+ return 'none';
104
+ }
105
+ /**
106
+ * Known policy-engine rule IDs that are enforced via the policy YAML evaluator
107
+ * (not via the structural rule engine). These are counted as implemented.
108
+ */
109
+ const KNOWN_POLICY_ENGINE_RULES = new Set([
110
+ 'NO_HARDCODED_SECRETS',
111
+ 'REQUIRE_RESOURCE_TAGGING',
112
+ 'PY001_NO_BARE_EXCEPT',
113
+ 'PY003_ASYNC_SAFETY',
114
+ 'TS001_TYPED_BOUNDARIES',
115
+ 'GOV001_REPLAY_INTEGRITY_REQUIRED',
116
+ 'GOV002_SUPPRESSION_REQUIRES_JUSTIFICATION',
117
+ 'potential-secret-default',
118
+ 'potential-secret-high',
119
+ 'require-signed-ai-logs',
120
+ 'ai-change-log-integrity',
121
+ ]);
122
+ /**
123
+ * Generate a compact governance coverage summary string for CLI output.
124
+ */
125
+ function formatParityReport(report) {
126
+ const { totalPolicies, deterministicCount, advisoryCount, semanticCount, noneCount, coveragePct, unenforced } = report;
127
+ const lines = [
128
+ `Policy Enforcement Coverage: ${coveragePct}% (${deterministicCount - unenforced.length}/${deterministicCount} deterministic policies enforced)`,
129
+ ` Total policies: ${totalPolicies} | Deterministic: ${deterministicCount} | Advisory: ${advisoryCount} | Semantic: ${semanticCount} | Unenforced: ${noneCount + unenforced.length}`,
130
+ ];
131
+ if (unenforced.length > 0) {
132
+ lines.push(` ⚠ Unenforced deterministic policies: ${unenforced.join(', ')}`);
133
+ lines.push(` These policies appear in policy.yml as deterministic but will NEVER produce findings.`);
134
+ }
135
+ return lines.join('\n');
136
+ }
137
+ //# sourceMappingURL=policy-parity-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-parity-validator.js","sourceRoot":"","sources":["../../src/governance/policy-parity-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Remediation Boundary Enforcement (Phase 4)
3
+ *
4
+ * Validates that a remediation suggestion respects strict boundaries:
5
+ * - Same language as the finding
6
+ * - Same package/subsystem boundary (inferred from file path)
7
+ * - No cross-frontend/backend boundary crossings
8
+ *
9
+ * If no safe remediation can be proposed within these constraints, returns
10
+ * the sentinel string "No safe remediation suggestion available." which is
11
+ * preferable to misleading cross-boundary guidance.
12
+ *
13
+ * This is a deterministic, pure-function module — no I/O, no LLM.
14
+ */
15
+ import type { RuleLanguage } from '../structural-rules/types';
16
+ export interface RemediationBoundaryInput {
17
+ /** The file path where the violation was found */
18
+ findingFilePath: string;
19
+ /** The language of the finding */
20
+ findingLanguage: RuleLanguage | string;
21
+ /** The proposed target file for the remediation */
22
+ targetFilePath: string;
23
+ /** The language the remediation targets (if known) */
24
+ targetLanguage?: RuleLanguage | string;
25
+ }
26
+ export interface BoundaryCheckResult {
27
+ allowed: boolean;
28
+ reason: string;
29
+ /** Sentinel value when no safe remediation is possible */
30
+ safeRemediationUnavailable?: boolean;
31
+ }
32
+ /**
33
+ * Validate whether a remediation suggestion crosses acceptable boundaries.
34
+ *
35
+ * Enforcement rules (in priority order):
36
+ * 1. Language boundary: finding language must match target language
37
+ * Exception: typescript ↔ javascript is allowed (same ecosystem)
38
+ * 2. Frontend/backend boundary: must not cross unless explicitly linked
39
+ * 3. Infrastructure boundary: infra files cannot be remediation targets for
40
+ * application-layer findings
41
+ */
42
+ export declare function validateRemediationBoundary(input: RemediationBoundaryInput): BoundaryCheckResult;
43
+ /**
44
+ * The sentinel string returned when no safe remediation is available.
45
+ * Consumers MUST check for this exact string and suppress remediation output.
46
+ */
47
+ export declare const NO_SAFE_REMEDIATION: "No safe remediation suggestion available.";
48
+ /**
49
+ * Wrap a remediation string with boundary validation.
50
+ *
51
+ * Returns the original remediation if the boundary is safe, or the
52
+ * NO_SAFE_REMEDIATION sentinel if the boundary is violated.
53
+ */
54
+ export declare function boundedRemediation(input: RemediationBoundaryInput, remediation: string): string;
55
+ //# sourceMappingURL=remediation-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remediation-boundary.d.ts","sourceRoot":"","sources":["../../src/governance/remediation-boundary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAI9D,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,eAAe,EAAE,YAAY,GAAG,MAAM,CAAC;IACvC,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,cAAc,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,wBAAwB,GAC9B,mBAAmB,CAgErB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAG,2CAAoD,CAAC;AAExF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,wBAAwB,EAC/B,WAAW,EAAE,MAAM,GAClB,MAAM,CAIR"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Remediation Boundary Enforcement (Phase 4)
4
+ *
5
+ * Validates that a remediation suggestion respects strict boundaries:
6
+ * - Same language as the finding
7
+ * - Same package/subsystem boundary (inferred from file path)
8
+ * - No cross-frontend/backend boundary crossings
9
+ *
10
+ * If no safe remediation can be proposed within these constraints, returns
11
+ * the sentinel string "No safe remediation suggestion available." which is
12
+ * preferable to misleading cross-boundary guidance.
13
+ *
14
+ * This is a deterministic, pure-function module — no I/O, no LLM.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.NO_SAFE_REMEDIATION = void 0;
18
+ exports.validateRemediationBoundary = validateRemediationBoundary;
19
+ exports.boundedRemediation = boundedRemediation;
20
+ const FRONTEND_PATH_RE = /(?:^|\/)(?:web|frontend|client|ui|dashboard|pages?|components?|views?|screens?|app\/(?:page|layout))[/\\]/i;
21
+ const BACKEND_PATH_RE = /(?:^|\/)(?:api|services?|server|backend|controllers?|handlers?|routes?|domain)[/\\]/i;
22
+ const INFRA_PATH_RE = /(?:^|\/)(?:infra|k8s|terraform|docker|deploy|charts?)[/\\]/i;
23
+ const TEST_PATH_RE = /(?:^|\/)(?:__tests?__|tests?|spec|e2e)[/\\]|\.(?:test|spec)\.\w+$/i;
24
+ function classifySubsystem(filePath) {
25
+ const p = filePath.replace(/\\/g, '/');
26
+ if (TEST_PATH_RE.test(p))
27
+ return 'test';
28
+ if (INFRA_PATH_RE.test(p))
29
+ return 'infra';
30
+ if (FRONTEND_PATH_RE.test(p))
31
+ return 'frontend';
32
+ if (BACKEND_PATH_RE.test(p))
33
+ return 'backend';
34
+ return 'unknown';
35
+ }
36
+ // ── Language inference from file extension ────────────────────────────────────
37
+ function inferLanguageFromPath(filePath) {
38
+ if (/\.(ts|tsx)$/.test(filePath))
39
+ return 'typescript';
40
+ if (/\.(js|jsx|mjs|cjs)$/.test(filePath))
41
+ return 'javascript';
42
+ if (/\.py$/.test(filePath))
43
+ return 'python';
44
+ return 'unknown';
45
+ }
46
+ // ── Boundary validation ───────────────────────────────────────────────────────
47
+ /**
48
+ * Validate whether a remediation suggestion crosses acceptable boundaries.
49
+ *
50
+ * Enforcement rules (in priority order):
51
+ * 1. Language boundary: finding language must match target language
52
+ * Exception: typescript ↔ javascript is allowed (same ecosystem)
53
+ * 2. Frontend/backend boundary: must not cross unless explicitly linked
54
+ * 3. Infrastructure boundary: infra files cannot be remediation targets for
55
+ * application-layer findings
56
+ */
57
+ function validateRemediationBoundary(input) {
58
+ const { findingFilePath, findingLanguage, targetFilePath, targetLanguage: explicitTargetLang, } = input;
59
+ const effectiveTargetLang = explicitTargetLang ?? inferLanguageFromPath(targetFilePath);
60
+ const findingSubsystem = classifySubsystem(findingFilePath);
61
+ const targetSubsystem = classifySubsystem(targetFilePath);
62
+ // ── Rule 1: Language boundary ──────────────────────────────────────────────
63
+ if (effectiveTargetLang !== 'unknown' &&
64
+ findingLanguage !== effectiveTargetLang) {
65
+ // Allow typescript ↔ javascript (same ecosystem, transpiled)
66
+ const tsJsCompat = (findingLanguage === 'typescript' && effectiveTargetLang === 'javascript') ||
67
+ (findingLanguage === 'javascript' && effectiveTargetLang === 'typescript');
68
+ if (!tsJsCompat) {
69
+ return {
70
+ allowed: false,
71
+ safeRemediationUnavailable: true,
72
+ reason: `Language boundary violation: finding is in '${findingLanguage}' but remediation ` +
73
+ `targets '${effectiveTargetLang}' file '${targetFilePath}'. ` +
74
+ 'Cross-language remediation is not safe.',
75
+ };
76
+ }
77
+ }
78
+ // ── Rule 2: Frontend/backend boundary ─────────────────────────────────────
79
+ if ((findingSubsystem === 'frontend' && targetSubsystem === 'backend') ||
80
+ (findingSubsystem === 'backend' && targetSubsystem === 'frontend')) {
81
+ return {
82
+ allowed: false,
83
+ safeRemediationUnavailable: true,
84
+ reason: `Subsystem boundary violation: finding is in '${findingSubsystem}' ` +
85
+ `but remediation targets '${targetSubsystem}' file '${targetFilePath}'. ` +
86
+ 'Cross-boundary remediation requires explicit architectural approval.',
87
+ };
88
+ }
89
+ // ── Rule 3: Infra boundary ─────────────────────────────────────────────────
90
+ if (targetSubsystem === 'infra' && findingSubsystem !== 'infra') {
91
+ return {
92
+ allowed: false,
93
+ safeRemediationUnavailable: true,
94
+ reason: `Infrastructure boundary violation: application-layer finding in '${findingFilePath}' ` +
95
+ `cannot be remediated by modifying infrastructure file '${targetFilePath}'.`,
96
+ };
97
+ }
98
+ return {
99
+ allowed: true,
100
+ reason: 'Remediation is within safe boundaries.',
101
+ };
102
+ }
103
+ /**
104
+ * The sentinel string returned when no safe remediation is available.
105
+ * Consumers MUST check for this exact string and suppress remediation output.
106
+ */
107
+ exports.NO_SAFE_REMEDIATION = 'No safe remediation suggestion available.';
108
+ /**
109
+ * Wrap a remediation string with boundary validation.
110
+ *
111
+ * Returns the original remediation if the boundary is safe, or the
112
+ * NO_SAFE_REMEDIATION sentinel if the boundary is violated.
113
+ */
114
+ function boundedRemediation(input, remediation) {
115
+ const result = validateRemediationBoundary(input);
116
+ if (!result.allowed)
117
+ return exports.NO_SAFE_REMEDIATION;
118
+ return remediation;
119
+ }
120
+ //# sourceMappingURL=remediation-boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remediation-boundary.js","sourceRoot":"","sources":["../../src/governance/remediation-boundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Structural Analysis Cache (Phase 5 — Performance Stability)
3
+ *
4
+ * Persists rule-engine results to disk so that unchanged files are not
5
+ * re-analyzed on every `verify` run. Provides consistent CI performance
6
+ * independent of repo size.
7
+ *
8
+ * Cache design:
9
+ * Key: file path (relative to project root)
10
+ * Value: { contentHash, rulesVersion, violations, analysisMs, cachedAt }
11
+ *
12
+ * Invalidation triggers:
13
+ * 1. File content changes (SHA-256 of file content)
14
+ * 2. Rules version changes (SHA-256 of all rule IDs + policyRefs)
15
+ *
16
+ * Storage: .neurcode/structural-cache.json
17
+ * Write strategy: atomic (write to .tmp, then rename) to prevent corruption
18
+ * on process kill mid-write.
19
+ *
20
+ * This module is fully synchronous to stay compatible with the existing
21
+ * structural-on-diff.ts read path.
22
+ */
23
+ import type { StructuralViolation } from '../structural-rules/types';
24
+ export declare class StructuralCache {
25
+ private readonly cacheFilePath;
26
+ private store;
27
+ private dirty;
28
+ constructor(projectRoot: string);
29
+ private load;
30
+ /**
31
+ * Check if a valid cache entry exists for the given file.
32
+ *
33
+ * @param filePath Relative file path (cache key)
34
+ * @param content Current file content
35
+ * @param rulesVersion Current rules fingerprint
36
+ */
37
+ get(filePath: string, content: string, rulesVersion: string): StructuralViolation[] | null;
38
+ /**
39
+ * Store analysis results for a file.
40
+ */
41
+ set(filePath: string, content: string, rulesVersion: string, violations: StructuralViolation[], analysisMs: number): void;
42
+ /**
43
+ * Invalidate a specific file entry (e.g. on explicit cache bust).
44
+ */
45
+ invalidate(filePath: string): void;
46
+ /**
47
+ * Flush dirty cache to disk (atomic write).
48
+ * Safe to call even if nothing changed (no-op).
49
+ */
50
+ flush(): void;
51
+ /**
52
+ * Return cache diagnostics for the `neurcode cache diagnostics` command.
53
+ */
54
+ diagnostics(): {
55
+ cacheFilePath: string;
56
+ entryCount: number;
57
+ totalCachedViolations: number;
58
+ averageAnalysisMs: number;
59
+ oldestEntry: string | null;
60
+ newestEntry: string | null;
61
+ staleRiskEntryCount: number;
62
+ implementationHashCoveragePct: number;
63
+ };
64
+ }
65
+ /**
66
+ * Compute a stable two-tier fingerprint of the currently active rule set.
67
+ *
68
+ * Tier 1 (always): ruleId:policyRef — invalidates on rule additions/removals/policyRef changes
69
+ * Tier 2 (when available): implementation hash — invalidates when rule logic changes
70
+ * without a policyRef change (e.g. PY003 logic rewrite keeping policyRef='P017')
71
+ *
72
+ * The combined fingerprint is:
73
+ * sha256(tier1 + '\n\x1e\n' + tier2).slice(0,16)
74
+ *
75
+ * If Tier 2 is unavailable (distDir not found), the fingerprint uses Tier 1 only
76
+ * and the caller should set implementationHashAvailable=false in the cache entry.
77
+ *
78
+ * @param rules Array of { id, policyRef } from the registered rule engine
79
+ * @param distDir Optional path to the CLI dist/governance/ directory for Tier 2
80
+ */
81
+ export declare function computeRulesVersion(rules: Array<{
82
+ id: string;
83
+ policyRef: string;
84
+ }>, distDir?: string): {
85
+ rulesVersion: string;
86
+ implementationHash: string | null;
87
+ implementationHashAvailable: boolean;
88
+ };
89
+ /**
90
+ * Compute a hash of the compiled rule implementation files.
91
+ *
92
+ * Reads compiled .js files for each rule under distDir/structural-rules and
93
+ * hashes the concatenated content. Changes when rule logic changes, even
94
+ * if ruleId and policyRef are unchanged (two-tier fingerprinting).
95
+ *
96
+ * Falls back gracefully when files are unreadable. Returns null if distDir
97
+ * does not exist or no rule implementation files are found.
98
+ */
99
+ export declare function computeImplementationHash(rules: Array<{
100
+ id: string;
101
+ policyRef: string;
102
+ }>, distDir: string): string | null;
103
+ //# sourceMappingURL=structural-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-cache.d.ts","sourceRoot":"","sources":["../../src/governance/structural-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AA0DrE,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAEV,WAAW,EAAE,MAAM;IAO/B,OAAO,CAAC,IAAI;IAuBZ;;;;;;OAMG;IACH,GAAG,CACD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,mBAAmB,EAAE,GAAG,IAAI;IAW/B;;OAEG;IACH,GAAG,CACD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,mBAAmB,EAAE,EACjC,UAAU,EAAE,MAAM,GACjB,IAAI;IAYP;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOlC;;;OAGG;IACH,KAAK,IAAI,IAAI;IAUb;;OAEG;IACH,WAAW,IAAI;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,6BAA6B,EAAE,MAAM,CAAC;KACvC;CAqBF;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,2BAA2B,EAAE,OAAO,CAAA;CAAE,CAoBnG;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAuCf"}
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * Structural Analysis Cache (Phase 5 — Performance Stability)
4
+ *
5
+ * Persists rule-engine results to disk so that unchanged files are not
6
+ * re-analyzed on every `verify` run. Provides consistent CI performance
7
+ * independent of repo size.
8
+ *
9
+ * Cache design:
10
+ * Key: file path (relative to project root)
11
+ * Value: { contentHash, rulesVersion, violations, analysisMs, cachedAt }
12
+ *
13
+ * Invalidation triggers:
14
+ * 1. File content changes (SHA-256 of file content)
15
+ * 2. Rules version changes (SHA-256 of all rule IDs + policyRefs)
16
+ *
17
+ * Storage: .neurcode/structural-cache.json
18
+ * Write strategy: atomic (write to .tmp, then rename) to prevent corruption
19
+ * on process kill mid-write.
20
+ *
21
+ * This module is fully synchronous to stay compatible with the existing
22
+ * structural-on-diff.ts read path.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.StructuralCache = void 0;
26
+ exports.computeRulesVersion = computeRulesVersion;
27
+ exports.computeImplementationHash = computeImplementationHash;
28
+ const crypto_1 = require("crypto");
29
+ const fs_1 = require("fs");
30
+ const path_1 = require("path");
31
+ const artifact_io_1 = require("../utils/artifact-io");
32
+ // ── Constants ─────────────────────────────────────────────────────────────────
33
+ const CACHE_FILE = 'structural-cache.json';
34
+ const NEURCODE_DIR = '.neurcode';
35
+ const CACHE_VERSION = 1;
36
+ // ── Helpers ───────────────────────────────────────────────────────────────────
37
+ function sha256(input) {
38
+ return (0, crypto_1.createHash)('sha256').update(input, 'utf-8').digest('hex');
39
+ }
40
+ function atomicWrite(filePath, content) {
41
+ (0, artifact_io_1.atomicWriteUtf8FileSync)(filePath, content, { fsync: false });
42
+ }
43
+ // ── StructuralCache class ─────────────────────────────────────────────────────
44
+ class StructuralCache {
45
+ cacheFilePath;
46
+ store;
47
+ dirty = false;
48
+ constructor(projectRoot) {
49
+ this.cacheFilePath = (0, path_1.join)(projectRoot, NEURCODE_DIR, CACHE_FILE);
50
+ this.store = this.load();
51
+ }
52
+ // ── Load ────────────────────────────────────────────────────────────────────
53
+ load() {
54
+ if (!(0, fs_1.existsSync)(this.cacheFilePath)) {
55
+ return { version: CACHE_VERSION, entries: {} };
56
+ }
57
+ try {
58
+ const raw = (0, fs_1.readFileSync)(this.cacheFilePath, 'utf-8');
59
+ const parsed = JSON.parse(raw);
60
+ if (typeof parsed === 'object' &&
61
+ parsed !== null &&
62
+ parsed.version === CACHE_VERSION &&
63
+ typeof parsed.entries === 'object') {
64
+ return parsed;
65
+ }
66
+ }
67
+ catch {
68
+ // Corrupt cache — start fresh
69
+ }
70
+ return { version: CACHE_VERSION, entries: {} };
71
+ }
72
+ // ── Public API ──────────────────────────────────────────────────────────────
73
+ /**
74
+ * Check if a valid cache entry exists for the given file.
75
+ *
76
+ * @param filePath Relative file path (cache key)
77
+ * @param content Current file content
78
+ * @param rulesVersion Current rules fingerprint
79
+ */
80
+ get(filePath, content, rulesVersion) {
81
+ const entry = this.store.entries[filePath];
82
+ if (!entry)
83
+ return null;
84
+ const contentHash = sha256(content);
85
+ if (entry.contentHash !== contentHash)
86
+ return null;
87
+ if (entry.rulesVersion !== rulesVersion)
88
+ return null;
89
+ return entry.violations;
90
+ }
91
+ /**
92
+ * Store analysis results for a file.
93
+ */
94
+ set(filePath, content, rulesVersion, violations, analysisMs) {
95
+ const contentHash = sha256(content);
96
+ this.store.entries[filePath] = {
97
+ contentHash,
98
+ rulesVersion,
99
+ violations,
100
+ analysisMs,
101
+ cachedAt: new Date().toISOString(),
102
+ };
103
+ this.dirty = true;
104
+ }
105
+ /**
106
+ * Invalidate a specific file entry (e.g. on explicit cache bust).
107
+ */
108
+ invalidate(filePath) {
109
+ if (this.store.entries[filePath]) {
110
+ delete this.store.entries[filePath];
111
+ this.dirty = true;
112
+ }
113
+ }
114
+ /**
115
+ * Flush dirty cache to disk (atomic write).
116
+ * Safe to call even if nothing changed (no-op).
117
+ */
118
+ flush() {
119
+ if (!this.dirty)
120
+ return;
121
+ try {
122
+ atomicWrite(this.cacheFilePath, JSON.stringify(this.store, null, 2));
123
+ this.dirty = false;
124
+ }
125
+ catch {
126
+ // Non-fatal — cache write failure degrades to uncached analysis
127
+ }
128
+ }
129
+ /**
130
+ * Return cache diagnostics for the `neurcode cache diagnostics` command.
131
+ */
132
+ diagnostics() {
133
+ const entries = Object.values(this.store.entries);
134
+ const totalViolations = entries.reduce((s, e) => s + e.violations.length, 0);
135
+ const totalMs = entries.reduce((s, e) => s + e.analysisMs, 0);
136
+ const dates = entries.map(e => e.cachedAt).sort();
137
+ const staleRiskCount = entries.filter(e => e.staleRisk === true || e.implementationHashAvailable === false).length;
138
+ const withImplHash = entries.filter(e => e.implementationHashAvailable === true).length;
139
+ return {
140
+ cacheFilePath: this.cacheFilePath,
141
+ entryCount: entries.length,
142
+ totalCachedViolations: totalViolations,
143
+ averageAnalysisMs: entries.length > 0 ? Math.round(totalMs / entries.length) : 0,
144
+ oldestEntry: dates[0] ?? null,
145
+ newestEntry: dates[dates.length - 1] ?? null,
146
+ staleRiskEntryCount: staleRiskCount,
147
+ implementationHashCoveragePct: entries.length > 0
148
+ ? Math.round((withImplHash / entries.length) * 100)
149
+ : 100,
150
+ };
151
+ }
152
+ }
153
+ exports.StructuralCache = StructuralCache;
154
+ // ── Rules version fingerprint ─────────────────────────────────────────────────
155
+ /**
156
+ * Compute a stable two-tier fingerprint of the currently active rule set.
157
+ *
158
+ * Tier 1 (always): ruleId:policyRef — invalidates on rule additions/removals/policyRef changes
159
+ * Tier 2 (when available): implementation hash — invalidates when rule logic changes
160
+ * without a policyRef change (e.g. PY003 logic rewrite keeping policyRef='P017')
161
+ *
162
+ * The combined fingerprint is:
163
+ * sha256(tier1 + '\n\x1e\n' + tier2).slice(0,16)
164
+ *
165
+ * If Tier 2 is unavailable (distDir not found), the fingerprint uses Tier 1 only
166
+ * and the caller should set implementationHashAvailable=false in the cache entry.
167
+ *
168
+ * @param rules Array of { id, policyRef } from the registered rule engine
169
+ * @param distDir Optional path to the CLI dist/governance/ directory for Tier 2
170
+ */
171
+ function computeRulesVersion(rules, distDir) {
172
+ // Tier 1: ruleId:policyRef fingerprint
173
+ const tier1 = rules
174
+ .map(r => `${r.id}:${r.policyRef}`)
175
+ .sort()
176
+ .join('\n');
177
+ const tier1Hash = sha256(tier1);
178
+ // Tier 2: compiled implementation hash
179
+ const implHash = distDir ? computeImplementationHash(rules, distDir) : null;
180
+ const combined = implHash
181
+ ? sha256(`${tier1Hash}\n\x1e\n${implHash}`).slice(0, 16)
182
+ : tier1Hash.slice(0, 16);
183
+ return {
184
+ rulesVersion: combined,
185
+ implementationHash: implHash,
186
+ implementationHashAvailable: implHash !== null,
187
+ };
188
+ }
189
+ /**
190
+ * Compute a hash of the compiled rule implementation files.
191
+ *
192
+ * Reads compiled .js files for each rule under distDir/structural-rules and
193
+ * hashes the concatenated content. Changes when rule logic changes, even
194
+ * if ruleId and policyRef are unchanged (two-tier fingerprinting).
195
+ *
196
+ * Falls back gracefully when files are unreadable. Returns null if distDir
197
+ * does not exist or no rule implementation files are found.
198
+ */
199
+ function computeImplementationHash(rules, distDir) {
200
+ if (!(0, fs_1.existsSync)(distDir))
201
+ return null;
202
+ const hasher = (0, crypto_1.createHash)('sha256');
203
+ let filesHashed = 0;
204
+ // Sort rules for deterministic hash order
205
+ const sortedRules = [...rules].sort((a, b) => a.id.localeCompare(b.id));
206
+ for (const rule of sortedRules) {
207
+ const ruleIdLower = rule.id.toLowerCase();
208
+ const rulesBase = (0, path_1.join)(distDir, 'structural-rules');
209
+ // Check direct rule file
210
+ const targetFile = `${ruleIdLower}.js`;
211
+ let found = false;
212
+ if ((0, fs_1.existsSync)(rulesBase)) {
213
+ try {
214
+ const files = (0, fs_1.readdirSync)(rulesBase);
215
+ const match = files.find(f => f.toLowerCase() === targetFile);
216
+ if (match) {
217
+ hasher.update(`${rule.id}:`);
218
+ hasher.update((0, fs_1.readFileSync)((0, path_1.join)(rulesBase, match)));
219
+ filesHashed++;
220
+ found = true;
221
+ }
222
+ }
223
+ catch {
224
+ // Skip unreadable directory
225
+ }
226
+ }
227
+ if (!found) {
228
+ hasher.update(`${rule.id}:MISSING`);
229
+ }
230
+ }
231
+ if (filesHashed === 0)
232
+ return null;
233
+ return hasher.digest('hex').slice(0, 32);
234
+ }
235
+ //# sourceMappingURL=structural-cache.js.map