@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.
- package/dist/commands/bootstrap-policy.d.ts +29 -0
- package/dist/commands/bootstrap-policy.d.ts.map +1 -0
- package/dist/commands/bootstrap-policy.js +334 -0
- package/dist/commands/bootstrap-policy.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/governance.d.ts +3 -0
- package/dist/commands/governance.d.ts.map +1 -0
- package/dist/commands/governance.js +390 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/quickstart.d.ts +21 -0
- package/dist/commands/quickstart.d.ts.map +1 -0
- package/dist/commands/quickstart.js +178 -0
- package/dist/commands/quickstart.js.map +1 -0
- package/dist/commands/remediate-export.d.ts +36 -0
- package/dist/commands/remediate-export.d.ts.map +1 -0
- package/dist/commands/remediate-export.js +1072 -0
- package/dist/commands/remediate-export.js.map +1 -0
- package/dist/commands/replay.d.ts.map +1 -1
- package/dist/commands/replay.js +14 -0
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/session.d.ts +7 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +156 -0
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/start-intent.d.ts.map +1 -1
- package/dist/commands/start-intent.js +61 -11
- package/dist/commands/start-intent.js.map +1 -1
- package/dist/commands/verify-guidance.d.ts +5 -0
- package/dist/commands/verify-guidance.d.ts.map +1 -0
- package/dist/commands/verify-guidance.js +49 -0
- package/dist/commands/verify-guidance.js.map +1 -0
- package/dist/commands/verify-output.d.ts +37 -0
- package/dist/commands/verify-output.d.ts.map +1 -0
- package/dist/commands/verify-output.js +572 -0
- package/dist/commands/verify-output.js.map +1 -0
- package/dist/commands/verify-render.d.ts +41 -0
- package/dist/commands/verify-render.d.ts.map +1 -0
- package/dist/commands/verify-render.js +457 -0
- package/dist/commands/verify-render.js.map +1 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +384 -1091
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/workspace.d.ts.map +1 -1
- package/dist/commands/workspace.js +3 -14
- package/dist/commands/workspace.js.map +1 -1
- package/dist/context-engine/graph.d.ts.map +1 -1
- package/dist/context-engine/graph.js +69 -7
- package/dist/context-engine/graph.js.map +1 -1
- package/dist/context-engine/scanner.d.ts.map +1 -1
- package/dist/context-engine/scanner.js +9 -2
- package/dist/context-engine/scanner.js.map +1 -1
- package/dist/daemon/compatibility/execution.d.ts +42 -0
- package/dist/daemon/compatibility/execution.d.ts.map +1 -0
- package/dist/daemon/compatibility/execution.js +183 -0
- package/dist/daemon/compatibility/execution.js.map +1 -0
- package/dist/daemon/compatibility/mutation.d.ts +24 -0
- package/dist/daemon/compatibility/mutation.d.ts.map +1 -0
- package/dist/daemon/compatibility/mutation.js +724 -0
- package/dist/daemon/compatibility/mutation.js.map +1 -0
- package/dist/daemon/routes.d.ts +19 -0
- package/dist/daemon/routes.d.ts.map +1 -0
- package/dist/daemon/routes.js +123 -0
- package/dist/daemon/routes.js.map +1 -0
- package/dist/daemon/runtime/execution-bus.d.ts +217 -0
- package/dist/daemon/runtime/execution-bus.d.ts.map +1 -0
- package/dist/daemon/runtime/execution-bus.js +1420 -0
- package/dist/daemon/runtime/execution-bus.js.map +1 -0
- package/dist/daemon/runtime/workspace-runtime.d.ts +280 -0
- package/dist/daemon/runtime/workspace-runtime.d.ts.map +1 -0
- package/dist/daemon/runtime/workspace-runtime.js +1473 -0
- package/dist/daemon/runtime/workspace-runtime.js.map +1 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +171 -874
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shaping.d.ts +11 -0
- package/dist/daemon/shaping.d.ts.map +1 -0
- package/dist/daemon/shaping.js +240 -0
- package/dist/daemon/shaping.js.map +1 -0
- package/dist/governance/canonical-invariants.d.ts +88 -0
- package/dist/governance/canonical-invariants.d.ts.map +1 -0
- package/dist/governance/canonical-invariants.js +197 -0
- package/dist/governance/canonical-invariants.js.map +1 -0
- package/dist/governance/canonical-ordering.d.ts +76 -0
- package/dist/governance/canonical-ordering.d.ts.map +1 -0
- package/dist/governance/canonical-ordering.js +189 -0
- package/dist/governance/canonical-ordering.js.map +1 -0
- package/dist/governance/canonical-pipeline.d.ts +9 -1
- package/dist/governance/canonical-pipeline.d.ts.map +1 -1
- package/dist/governance/canonical-pipeline.js +367 -24
- package/dist/governance/canonical-pipeline.js.map +1 -1
- package/dist/governance/diff-line-provenance.d.ts +59 -0
- package/dist/governance/diff-line-provenance.d.ts.map +1 -0
- package/dist/governance/diff-line-provenance.js +118 -0
- package/dist/governance/diff-line-provenance.js.map +1 -0
- package/dist/governance/pilot-readiness.d.ts +34 -0
- package/dist/governance/pilot-readiness.d.ts.map +1 -0
- package/dist/governance/pilot-readiness.js +226 -0
- package/dist/governance/pilot-readiness.js.map +1 -0
- package/dist/governance/policy-parity-validator.d.ts +62 -0
- package/dist/governance/policy-parity-validator.d.ts.map +1 -0
- package/dist/governance/policy-parity-validator.js +137 -0
- package/dist/governance/policy-parity-validator.js.map +1 -0
- package/dist/governance/remediation-boundary.d.ts +55 -0
- package/dist/governance/remediation-boundary.d.ts.map +1 -0
- package/dist/governance/remediation-boundary.js +120 -0
- package/dist/governance/remediation-boundary.js.map +1 -0
- package/dist/governance/structural-cache.d.ts +103 -0
- package/dist/governance/structural-cache.d.ts.map +1 -0
- package/dist/governance/structural-cache.js +235 -0
- package/dist/governance/structural-cache.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +22 -2
- package/dist/governance/structural-on-diff.d.ts.map +1 -1
- package/dist/governance/structural-on-diff.js +36 -4
- package/dist/governance/structural-on-diff.js.map +1 -1
- package/dist/governance/structural-policy-merge.d.ts +8 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -1
- package/dist/governance/structural-policy-merge.js +7 -0
- package/dist/governance/structural-policy-merge.js.map +1 -1
- package/dist/governance/verify-runtime-guard.d.ts +99 -0
- package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
- package/dist/governance/verify-runtime-guard.js +129 -0
- package/dist/governance/verify-runtime-guard.js.map +1 -0
- package/dist/index.js +277 -77
- package/dist/index.js.map +1 -1
- package/dist/intent-engine/repo-classifier.d.ts +64 -0
- package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
- package/dist/intent-engine/repo-classifier.js +178 -0
- package/dist/intent-engine/repo-classifier.js.map +1 -0
- package/dist/structural-rules/index.d.ts +4 -0
- package/dist/structural-rules/index.d.ts.map +1 -1
- package/dist/structural-rules/index.js +18 -1
- package/dist/structural-rules/index.js.map +1 -1
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +21 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -1
- package/dist/structural-rules/python/PY003-broad-except-clause.js +212 -21
- package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -1
- package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts +11 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.js.map +1 -0
- package/dist/structural-rules/types.d.ts +12 -0
- package/dist/structural-rules/types.d.ts.map +1 -1
- package/dist/utils/active-engineering-context.d.ts +12 -0
- package/dist/utils/active-engineering-context.d.ts.map +1 -0
- package/dist/utils/active-engineering-context.js +67 -0
- package/dist/utils/active-engineering-context.js.map +1 -0
- package/dist/utils/artifact-io.d.ts +33 -0
- package/dist/utils/artifact-io.d.ts.map +1 -0
- package/dist/utils/artifact-io.js +183 -0
- package/dist/utils/artifact-io.js.map +1 -0
- package/dist/utils/change-contract.d.ts +6 -2
- package/dist/utils/change-contract.d.ts.map +1 -1
- package/dist/utils/change-contract.js +175 -0
- package/dist/utils/change-contract.js.map +1 -1
- package/dist/utils/context-pack.d.ts +12 -0
- package/dist/utils/context-pack.d.ts.map +1 -0
- package/dist/utils/context-pack.js +147 -0
- package/dist/utils/context-pack.js.map +1 -0
- package/dist/utils/control-plane.d.ts +18 -0
- package/dist/utils/control-plane.d.ts.map +1 -1
- package/dist/utils/control-plane.js +31 -4
- package/dist/utils/control-plane.js.map +1 -1
- package/dist/utils/drift-intelligence.d.ts +47 -0
- package/dist/utils/drift-intelligence.d.ts.map +1 -0
- package/dist/utils/drift-intelligence.js +2099 -0
- package/dist/utils/drift-intelligence.js.map +1 -0
- package/dist/utils/execution-actions.d.ts +22 -0
- package/dist/utils/execution-actions.d.ts.map +1 -0
- package/dist/utils/execution-actions.js +103 -0
- package/dist/utils/execution-actions.js.map +1 -0
- package/dist/utils/execution-bus.d.ts +1 -214
- package/dist/utils/execution-bus.d.ts.map +1 -1
- package/dist/utils/execution-bus.js +15 -1359
- package/dist/utils/execution-bus.js.map +1 -1
- package/dist/utils/git.d.ts +1 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +13 -3
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/governance-decisions.d.ts +75 -0
- package/dist/utils/governance-decisions.d.ts.map +1 -0
- package/dist/utils/governance-decisions.js +412 -0
- package/dist/utils/governance-decisions.js.map +1 -0
- package/dist/utils/governance-provenance.d.ts +1 -1
- package/dist/utils/governance-provenance.d.ts.map +1 -1
- package/dist/utils/governance-provenance.js +5 -7
- package/dist/utils/governance-provenance.js.map +1 -1
- package/dist/utils/governance.d.ts +108 -0
- package/dist/utils/governance.d.ts.map +1 -1
- package/dist/utils/governance.js +209 -7
- package/dist/utils/governance.js.map +1 -1
- package/dist/utils/intelligence-runtime-common.d.ts +30 -0
- package/dist/utils/intelligence-runtime-common.d.ts.map +1 -0
- package/dist/utils/intelligence-runtime-common.js +156 -0
- package/dist/utils/intelligence-runtime-common.js.map +1 -0
- package/dist/utils/intent-contract-diagnostics.d.ts +9 -0
- package/dist/utils/intent-contract-diagnostics.d.ts.map +1 -0
- package/dist/utils/intent-contract-diagnostics.js +322 -0
- package/dist/utils/intent-contract-diagnostics.js.map +1 -0
- package/dist/utils/intent-pack.d.ts +15 -0
- package/dist/utils/intent-pack.d.ts.map +1 -0
- package/dist/utils/intent-pack.js +196 -0
- package/dist/utils/intent-pack.js.map +1 -0
- package/dist/utils/plan-sync.d.ts +1 -0
- package/dist/utils/plan-sync.d.ts.map +1 -1
- package/dist/utils/plan-sync.js +23 -0
- package/dist/utils/plan-sync.js.map +1 -1
- package/dist/utils/policy-decision.d.ts +5 -0
- package/dist/utils/policy-decision.d.ts.map +1 -0
- package/dist/utils/policy-decision.js +17 -0
- package/dist/utils/policy-decision.js.map +1 -0
- package/dist/utils/replay-custody.d.ts +43 -0
- package/dist/utils/replay-custody.d.ts.map +1 -0
- package/dist/utils/replay-custody.js +168 -0
- package/dist/utils/replay-custody.js.map +1 -0
- package/dist/utils/replay-runtime.d.ts +13 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -1
- package/dist/utils/replay-runtime.js +96 -9
- package/dist/utils/replay-runtime.js.map +1 -1
- package/dist/utils/repository-intelligence.d.ts +9 -0
- package/dist/utils/repository-intelligence.d.ts.map +1 -0
- package/dist/utils/repository-intelligence.js +372 -0
- package/dist/utils/repository-intelligence.js.map +1 -0
- package/dist/utils/runtime-events.d.ts.map +1 -1
- package/dist/utils/runtime-events.js +25 -6
- package/dist/utils/runtime-events.js.map +1 -1
- package/dist/utils/semantic-contract-intelligence.d.ts +20 -0
- package/dist/utils/semantic-contract-intelligence.d.ts.map +1 -0
- package/dist/utils/semantic-contract-intelligence.js +825 -0
- package/dist/utils/semantic-contract-intelligence.js.map +1 -0
- package/dist/utils/session-continuity.d.ts +56 -0
- package/dist/utils/session-continuity.d.ts.map +1 -0
- package/dist/utils/session-continuity.js +318 -0
- package/dist/utils/session-continuity.js.map +1 -0
- package/dist/utils/verification-evidence.d.ts.map +1 -1
- package/dist/utils/verification-evidence.js +4 -1
- package/dist/utils/verification-evidence.js.map +1 -1
- package/dist/utils/verify-runtime-stability.d.ts +142 -0
- package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
- package/dist/utils/verify-runtime-stability.js +230 -0
- package/dist/utils/verify-runtime-stability.js.map +1 -0
- package/dist/utils/workspace-runtime.d.ts +1 -266
- package/dist/utils/workspace-runtime.d.ts.map +1 -1
- package/dist/utils/workspace-runtime.js +15 -1412
- package/dist/utils/workspace-runtime.js.map +1 -1
- package/package.json +11 -10
- 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
|