@neurcode-ai/cli 0.9.65 → 0.9.66
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/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 +31 -0
- package/dist/commands/remediate-export.d.ts.map +1 -0
- package/dist/commands/remediate-export.js +283 -0
- package/dist/commands/remediate-export.js.map +1 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +106 -10
- package/dist/commands/verify.js.map +1 -1
- 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 +7 -0
- package/dist/governance/canonical-pipeline.d.ts.map +1 -1
- package/dist/governance/canonical-pipeline.js +184 -16
- 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 +240 -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 +50 -14
- 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/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/package.json +1 -1
|
@@ -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;AA+DrE,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,240 @@
|
|
|
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
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
32
|
+
const CACHE_FILE = 'structural-cache.json';
|
|
33
|
+
const NEURCODE_DIR = '.neurcode';
|
|
34
|
+
const CACHE_VERSION = 1;
|
|
35
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
36
|
+
function sha256(input) {
|
|
37
|
+
return (0, crypto_1.createHash)('sha256').update(input, 'utf-8').digest('hex');
|
|
38
|
+
}
|
|
39
|
+
function atomicWrite(filePath, content) {
|
|
40
|
+
const tmp = `${filePath}.tmp`;
|
|
41
|
+
const dir = (0, path_1.dirname)(filePath);
|
|
42
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
43
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
(0, fs_1.writeFileSync)(tmp, content, 'utf-8');
|
|
46
|
+
(0, fs_1.renameSync)(tmp, filePath);
|
|
47
|
+
}
|
|
48
|
+
// ── StructuralCache class ─────────────────────────────────────────────────────
|
|
49
|
+
class StructuralCache {
|
|
50
|
+
cacheFilePath;
|
|
51
|
+
store;
|
|
52
|
+
dirty = false;
|
|
53
|
+
constructor(projectRoot) {
|
|
54
|
+
this.cacheFilePath = (0, path_1.join)(projectRoot, NEURCODE_DIR, CACHE_FILE);
|
|
55
|
+
this.store = this.load();
|
|
56
|
+
}
|
|
57
|
+
// ── Load ────────────────────────────────────────────────────────────────────
|
|
58
|
+
load() {
|
|
59
|
+
if (!(0, fs_1.existsSync)(this.cacheFilePath)) {
|
|
60
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const raw = (0, fs_1.readFileSync)(this.cacheFilePath, 'utf-8');
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
if (typeof parsed === 'object' &&
|
|
66
|
+
parsed !== null &&
|
|
67
|
+
parsed.version === CACHE_VERSION &&
|
|
68
|
+
typeof parsed.entries === 'object') {
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Corrupt cache — start fresh
|
|
74
|
+
}
|
|
75
|
+
return { version: CACHE_VERSION, entries: {} };
|
|
76
|
+
}
|
|
77
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Check if a valid cache entry exists for the given file.
|
|
80
|
+
*
|
|
81
|
+
* @param filePath Relative file path (cache key)
|
|
82
|
+
* @param content Current file content
|
|
83
|
+
* @param rulesVersion Current rules fingerprint
|
|
84
|
+
*/
|
|
85
|
+
get(filePath, content, rulesVersion) {
|
|
86
|
+
const entry = this.store.entries[filePath];
|
|
87
|
+
if (!entry)
|
|
88
|
+
return null;
|
|
89
|
+
const contentHash = sha256(content);
|
|
90
|
+
if (entry.contentHash !== contentHash)
|
|
91
|
+
return null;
|
|
92
|
+
if (entry.rulesVersion !== rulesVersion)
|
|
93
|
+
return null;
|
|
94
|
+
return entry.violations;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Store analysis results for a file.
|
|
98
|
+
*/
|
|
99
|
+
set(filePath, content, rulesVersion, violations, analysisMs) {
|
|
100
|
+
const contentHash = sha256(content);
|
|
101
|
+
this.store.entries[filePath] = {
|
|
102
|
+
contentHash,
|
|
103
|
+
rulesVersion,
|
|
104
|
+
violations,
|
|
105
|
+
analysisMs,
|
|
106
|
+
cachedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
this.dirty = true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Invalidate a specific file entry (e.g. on explicit cache bust).
|
|
112
|
+
*/
|
|
113
|
+
invalidate(filePath) {
|
|
114
|
+
if (this.store.entries[filePath]) {
|
|
115
|
+
delete this.store.entries[filePath];
|
|
116
|
+
this.dirty = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Flush dirty cache to disk (atomic write).
|
|
121
|
+
* Safe to call even if nothing changed (no-op).
|
|
122
|
+
*/
|
|
123
|
+
flush() {
|
|
124
|
+
if (!this.dirty)
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
atomicWrite(this.cacheFilePath, JSON.stringify(this.store, null, 2));
|
|
128
|
+
this.dirty = false;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Non-fatal — cache write failure degrades to uncached analysis
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Return cache diagnostics for the `neurcode cache diagnostics` command.
|
|
136
|
+
*/
|
|
137
|
+
diagnostics() {
|
|
138
|
+
const entries = Object.values(this.store.entries);
|
|
139
|
+
const totalViolations = entries.reduce((s, e) => s + e.violations.length, 0);
|
|
140
|
+
const totalMs = entries.reduce((s, e) => s + e.analysisMs, 0);
|
|
141
|
+
const dates = entries.map(e => e.cachedAt).sort();
|
|
142
|
+
const staleRiskCount = entries.filter(e => e.staleRisk === true || e.implementationHashAvailable === false).length;
|
|
143
|
+
const withImplHash = entries.filter(e => e.implementationHashAvailable === true).length;
|
|
144
|
+
return {
|
|
145
|
+
cacheFilePath: this.cacheFilePath,
|
|
146
|
+
entryCount: entries.length,
|
|
147
|
+
totalCachedViolations: totalViolations,
|
|
148
|
+
averageAnalysisMs: entries.length > 0 ? Math.round(totalMs / entries.length) : 0,
|
|
149
|
+
oldestEntry: dates[0] ?? null,
|
|
150
|
+
newestEntry: dates[dates.length - 1] ?? null,
|
|
151
|
+
staleRiskEntryCount: staleRiskCount,
|
|
152
|
+
implementationHashCoveragePct: entries.length > 0
|
|
153
|
+
? Math.round((withImplHash / entries.length) * 100)
|
|
154
|
+
: 100,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.StructuralCache = StructuralCache;
|
|
159
|
+
// ── Rules version fingerprint ─────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Compute a stable two-tier fingerprint of the currently active rule set.
|
|
162
|
+
*
|
|
163
|
+
* Tier 1 (always): ruleId:policyRef — invalidates on rule additions/removals/policyRef changes
|
|
164
|
+
* Tier 2 (when available): implementation hash — invalidates when rule logic changes
|
|
165
|
+
* without a policyRef change (e.g. PY003 logic rewrite keeping policyRef='P017')
|
|
166
|
+
*
|
|
167
|
+
* The combined fingerprint is:
|
|
168
|
+
* sha256(tier1 + '\n\x1e\n' + tier2).slice(0,16)
|
|
169
|
+
*
|
|
170
|
+
* If Tier 2 is unavailable (distDir not found), the fingerprint uses Tier 1 only
|
|
171
|
+
* and the caller should set implementationHashAvailable=false in the cache entry.
|
|
172
|
+
*
|
|
173
|
+
* @param rules Array of { id, policyRef } from the registered rule engine
|
|
174
|
+
* @param distDir Optional path to the CLI dist/governance/ directory for Tier 2
|
|
175
|
+
*/
|
|
176
|
+
function computeRulesVersion(rules, distDir) {
|
|
177
|
+
// Tier 1: ruleId:policyRef fingerprint
|
|
178
|
+
const tier1 = rules
|
|
179
|
+
.map(r => `${r.id}:${r.policyRef}`)
|
|
180
|
+
.sort()
|
|
181
|
+
.join('\n');
|
|
182
|
+
const tier1Hash = sha256(tier1);
|
|
183
|
+
// Tier 2: compiled implementation hash
|
|
184
|
+
const implHash = distDir ? computeImplementationHash(rules, distDir) : null;
|
|
185
|
+
const combined = implHash
|
|
186
|
+
? sha256(`${tier1Hash}\n\x1e\n${implHash}`).slice(0, 16)
|
|
187
|
+
: tier1Hash.slice(0, 16);
|
|
188
|
+
return {
|
|
189
|
+
rulesVersion: combined,
|
|
190
|
+
implementationHash: implHash,
|
|
191
|
+
implementationHashAvailable: implHash !== null,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Compute a hash of the compiled rule implementation files.
|
|
196
|
+
*
|
|
197
|
+
* Reads compiled .js files for each rule under distDir/structural-rules and
|
|
198
|
+
* hashes the concatenated content. Changes when rule logic changes, even
|
|
199
|
+
* if ruleId and policyRef are unchanged (two-tier fingerprinting).
|
|
200
|
+
*
|
|
201
|
+
* Falls back gracefully when files are unreadable. Returns null if distDir
|
|
202
|
+
* does not exist or no rule implementation files are found.
|
|
203
|
+
*/
|
|
204
|
+
function computeImplementationHash(rules, distDir) {
|
|
205
|
+
if (!(0, fs_1.existsSync)(distDir))
|
|
206
|
+
return null;
|
|
207
|
+
const hasher = (0, crypto_1.createHash)('sha256');
|
|
208
|
+
let filesHashed = 0;
|
|
209
|
+
// Sort rules for deterministic hash order
|
|
210
|
+
const sortedRules = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
211
|
+
for (const rule of sortedRules) {
|
|
212
|
+
const ruleIdLower = rule.id.toLowerCase();
|
|
213
|
+
const rulesBase = (0, path_1.join)(distDir, 'structural-rules');
|
|
214
|
+
// Check direct rule file
|
|
215
|
+
const targetFile = `${ruleIdLower}.js`;
|
|
216
|
+
let found = false;
|
|
217
|
+
if ((0, fs_1.existsSync)(rulesBase)) {
|
|
218
|
+
try {
|
|
219
|
+
const files = (0, fs_1.readdirSync)(rulesBase);
|
|
220
|
+
const match = files.find(f => f.toLowerCase() === targetFile);
|
|
221
|
+
if (match) {
|
|
222
|
+
hasher.update(`${rule.id}:`);
|
|
223
|
+
hasher.update((0, fs_1.readFileSync)((0, path_1.join)(rulesBase, match)));
|
|
224
|
+
filesHashed++;
|
|
225
|
+
found = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Skip unreadable directory
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!found) {
|
|
233
|
+
hasher.update(`${rule.id}:MISSING`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (filesHashed === 0)
|
|
237
|
+
return null;
|
|
238
|
+
return hasher.digest('hex').slice(0, 32);
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=structural-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structural-cache.js","sourceRoot":"","sources":["../../src/governance/structural-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { type StructuralViolation } from '../structural-rules';
|
|
2
|
+
import type { DiffFile } from '@neurcode-ai/diff-parser';
|
|
2
3
|
export interface StructuralOnDiffResult {
|
|
3
4
|
violations: StructuralViolation[];
|
|
4
5
|
rulesApplied: string[];
|
|
5
6
|
suppressedCount: number;
|
|
7
|
+
/** Phase 2: violations on modified lines (new violations) */
|
|
8
|
+
newViolationCount: number;
|
|
9
|
+
/** Phase 2: violations on unmodified historical lines (demoted to ADVISORY) */
|
|
10
|
+
legacyDebtCount: number;
|
|
11
|
+
/** Whether diff-scoped enforcement was active */
|
|
12
|
+
diffScopedEnforcement: boolean;
|
|
6
13
|
}
|
|
7
14
|
/**
|
|
8
|
-
* Run the default structural rule set on files touched by the diff.
|
|
15
|
+
* Run the default structural rule set on files touched by the diff.
|
|
16
|
+
*
|
|
17
|
+
* Phase 2 — Diff-Scoped Enforcement:
|
|
18
|
+
* When diffFiles carries hunk line ranges, violations are classified as:
|
|
19
|
+
* - introducedOnModifiedLine: true → BLOCKING eligible (new code)
|
|
20
|
+
* - introducedOnModifiedLine: false → ADVISORY only (legacy debt)
|
|
21
|
+
*
|
|
22
|
+
* Pass strictFullFile=true to restore the original whole-file behaviour
|
|
23
|
+
* (equivalent to the --strict-full-file CLI flag).
|
|
24
|
+
*
|
|
25
|
+
* No I/O beyond reading the file contents. The modified-line index is built
|
|
26
|
+
* entirely from the already-parsed diffFiles structure.
|
|
9
27
|
*/
|
|
10
28
|
export declare function runStructuralOnDiffFiles(projectRoot: string, diffFiles: Array<{
|
|
11
29
|
path: string;
|
|
12
|
-
}
|
|
30
|
+
} | DiffFile>, options?: {
|
|
31
|
+
strictFullFile?: boolean;
|
|
32
|
+
}): StructuralOnDiffResult;
|
|
13
33
|
//# sourceMappingURL=structural-on-diff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-on-diff.d.ts","sourceRoot":"","sources":["../../src/governance/structural-on-diff.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqC,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"structural-on-diff.d.ts","sourceRoot":"","sources":["../../src/governance/structural-on-diff.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqC,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAClG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGzD,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,eAAe,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,EAC7C,OAAO,GAAE;IACP,cAAc,CAAC,EAAE,OAAO,CAAC;CACrB,GACL,sBAAsB,CAkDxB"}
|
|
@@ -4,10 +4,28 @@ exports.runStructuralOnDiffFiles = runStructuralOnDiffFiles;
|
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
const structural_rules_1 = require("../structural-rules");
|
|
7
|
+
const diff_line_provenance_1 = require("./diff-line-provenance");
|
|
7
8
|
/**
|
|
8
|
-
* Run the default structural rule set on files touched by the diff.
|
|
9
|
+
* Run the default structural rule set on files touched by the diff.
|
|
10
|
+
*
|
|
11
|
+
* Phase 2 — Diff-Scoped Enforcement:
|
|
12
|
+
* When diffFiles carries hunk line ranges, violations are classified as:
|
|
13
|
+
* - introducedOnModifiedLine: true → BLOCKING eligible (new code)
|
|
14
|
+
* - introducedOnModifiedLine: false → ADVISORY only (legacy debt)
|
|
15
|
+
*
|
|
16
|
+
* Pass strictFullFile=true to restore the original whole-file behaviour
|
|
17
|
+
* (equivalent to the --strict-full-file CLI flag).
|
|
18
|
+
*
|
|
19
|
+
* No I/O beyond reading the file contents. The modified-line index is built
|
|
20
|
+
* entirely from the already-parsed diffFiles structure.
|
|
9
21
|
*/
|
|
10
|
-
function runStructuralOnDiffFiles(projectRoot, diffFiles) {
|
|
22
|
+
function runStructuralOnDiffFiles(projectRoot, diffFiles, options = {}) {
|
|
23
|
+
const strictMode = options.strictFullFile ?? false;
|
|
24
|
+
// Build the modified-line index from hunk data (Phase 2).
|
|
25
|
+
// If the diff objects don't carry hunk info (legacy callers), the index
|
|
26
|
+
// will be empty and all violations will be treated as new (safe default).
|
|
27
|
+
const modifiedLineIndex = (0, diff_line_provenance_1.buildModifiedLineIndex)(diffFiles);
|
|
28
|
+
const hasDiffLineData = modifiedLineIndex.size > 0;
|
|
11
29
|
const engine = (0, structural_rules_1.createDefaultStructuralRuleEngine)();
|
|
12
30
|
const filesToAnalyze = [];
|
|
13
31
|
for (const df of diffFiles) {
|
|
@@ -23,13 +41,27 @@ function runStructuralOnDiffFiles(projectRoot, diffFiles) {
|
|
|
23
41
|
}
|
|
24
42
|
}
|
|
25
43
|
if (filesToAnalyze.length === 0) {
|
|
26
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
violations: [],
|
|
46
|
+
rulesApplied: [],
|
|
47
|
+
suppressedCount: 0,
|
|
48
|
+
newViolationCount: 0,
|
|
49
|
+
legacyDebtCount: 0,
|
|
50
|
+
diffScopedEnforcement: false,
|
|
51
|
+
};
|
|
27
52
|
}
|
|
28
53
|
const result = engine.analyze(filesToAnalyze);
|
|
54
|
+
// Apply diff-scoped provenance classification (Phase 2)
|
|
55
|
+
const { violations, legacyDebtCount, newViolationCount } = hasDiffLineData && !strictMode
|
|
56
|
+
? (0, diff_line_provenance_1.applyDiffScopedProvenance)(result.violations, modifiedLineIndex, false)
|
|
57
|
+
: { violations: result.violations, legacyDebtCount: 0, newViolationCount: result.violations.length };
|
|
29
58
|
return {
|
|
30
|
-
violations
|
|
59
|
+
violations,
|
|
31
60
|
rulesApplied: result.rulesApplied,
|
|
32
61
|
suppressedCount: result.suppressedCount,
|
|
62
|
+
newViolationCount,
|
|
63
|
+
legacyDebtCount,
|
|
64
|
+
diffScopedEnforcement: hasDiffLineData && !strictMode,
|
|
33
65
|
};
|
|
34
66
|
}
|
|
35
67
|
//# sourceMappingURL=structural-on-diff.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-on-diff.js","sourceRoot":"","sources":["../../src/governance/structural-on-diff.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"structural-on-diff.js","sourceRoot":"","sources":["../../src/governance/structural-on-diff.ts"],"names":[],"mappings":";;AAgCA,4DAwDC;AAxFD,2BAA8C;AAC9C,+BAA4B;AAC5B,0DAAkG;AAElG,iEAA2F;AAc3F;;;;;;;;;;;;;GAaG;AACH,SAAgB,wBAAwB,CACtC,WAAmB,EACnB,SAA6C,EAC7C,UAEI,EAAE;IAEN,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC;IAEnD,0DAA0D;IAC1D,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,iBAAiB,GAAG,IAAA,6CAAsB,EAAC,SAAuB,CAAC,CAAC;IAC1E,MAAM,eAAe,GAAG,iBAAiB,CAAC,IAAI,GAAG,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG,IAAA,oDAAiC,GAAE,CAAC;IACnD,MAAM,cAAc,GAAoD,EAAE,CAAC;IAE3E,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAA,eAAU,EAAC,OAAO,CAAC;YAAE,SAAS;QACnC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAA,iBAAY,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAClD,cAAc,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,UAAU,EAAE,EAAE;YACd,YAAY,EAAE,EAAE;YAChB,eAAe,EAAE,CAAC;YAClB,iBAAiB,EAAE,CAAC;YACpB,eAAe,EAAE,CAAC;YAClB,qBAAqB,EAAE,KAAK;SAC7B,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAE9C,wDAAwD;IACxD,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,GACtD,eAAe,IAAI,CAAC,UAAU;QAC5B,CAAC,CAAC,IAAA,gDAAyB,EAAC,MAAM,CAAC,UAAU,EAAE,iBAAiB,EAAE,KAAK,CAAC;QACxE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,EAAE,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;IAEzG,OAAO;QACL,UAAU;QACV,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,iBAAiB;QACjB,eAAe;QACf,qBAAqB,EAAE,eAAe,IAAI,CAAC,UAAU;KACtD,CAAC;AACJ,CAAC"}
|
|
@@ -5,10 +5,18 @@ export type PolicyViolationRow = {
|
|
|
5
5
|
severity: string;
|
|
6
6
|
message?: string;
|
|
7
7
|
line?: number;
|
|
8
|
+
/** Phase 4: language of the original finding — required for remediation boundary checks. */
|
|
9
|
+
language?: string;
|
|
8
10
|
};
|
|
9
11
|
/**
|
|
10
12
|
* Append structural violations to policy-engine rows so verdicts and JSON violations stay aligned.
|
|
11
13
|
* Dedupes identical structural rule + file + line.
|
|
14
|
+
*
|
|
15
|
+
* @deprecated The canonical pipeline (`buildGovernanceVerificationEnvelope`) is the single
|
|
16
|
+
* aggregation point. Prefer passing `structuralViolations` directly rather than merging here.
|
|
17
|
+
* Structural rows merged here are stripped by `stripStructuralPolicyRows()` in the pipeline
|
|
18
|
+
* to prevent duplicate GovernanceFinding objects. This function is kept for backward compat
|
|
19
|
+
* with existing callers that rely on the combined `violations` array for non-canonical output.
|
|
12
20
|
*/
|
|
13
21
|
export declare function mergeStructuralIntoPolicyViolations(policyViolations: PolicyViolationRow[], structuralViolations: StructuralViolation[]): void;
|
|
14
22
|
//# sourceMappingURL=structural-policy-merge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-policy-merge.d.ts","sourceRoot":"","sources":["../../src/governance/structural-policy-merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"structural-policy-merge.d.ts","sourceRoot":"","sources":["../../src/governance/structural-policy-merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,mCAAmC,CACjD,gBAAgB,EAAE,kBAAkB,EAAE,EACtC,oBAAoB,EAAE,mBAAmB,EAAE,GAC1C,IAAI,CAkBN"}
|
|
@@ -4,6 +4,12 @@ exports.mergeStructuralIntoPolicyViolations = mergeStructuralIntoPolicyViolation
|
|
|
4
4
|
/**
|
|
5
5
|
* Append structural violations to policy-engine rows so verdicts and JSON violations stay aligned.
|
|
6
6
|
* Dedupes identical structural rule + file + line.
|
|
7
|
+
*
|
|
8
|
+
* @deprecated The canonical pipeline (`buildGovernanceVerificationEnvelope`) is the single
|
|
9
|
+
* aggregation point. Prefer passing `structuralViolations` directly rather than merging here.
|
|
10
|
+
* Structural rows merged here are stripped by `stripStructuralPolicyRows()` in the pipeline
|
|
11
|
+
* to prevent duplicate GovernanceFinding objects. This function is kept for backward compat
|
|
12
|
+
* with existing callers that rely on the combined `violations` array for non-canonical output.
|
|
7
13
|
*/
|
|
8
14
|
function mergeStructuralIntoPolicyViolations(policyViolations, structuralViolations) {
|
|
9
15
|
const seen = new Set(policyViolations.map((v) => `${v.rule}|${v.file}|${v.line ?? 0}`));
|
|
@@ -19,6 +25,7 @@ function mergeStructuralIntoPolicyViolations(policyViolations, structuralViolati
|
|
|
19
25
|
severity: sv.severity === 'BLOCKING' ? 'block' : 'warn',
|
|
20
26
|
message: `[${sv.ruleId}] ${sv.ruleName}: ${sv.operationalRisk}`,
|
|
21
27
|
line: sv.line,
|
|
28
|
+
language: sv.language,
|
|
22
29
|
});
|
|
23
30
|
}
|
|
24
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-policy-merge.js","sourceRoot":"","sources":["../../src/governance/structural-policy-merge.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"structural-policy-merge.js","sourceRoot":"","sources":["../../src/governance/structural-policy-merge.ts"],"names":[],"mappings":";;AAsBA,kFAqBC;AA/BD;;;;;;;;;GASG;AACH,SAAgB,mCAAmC,CACjD,gBAAsC,EACtC,oBAA2C;IAE3C,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAClE,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,oBAAoB,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC,MAAM,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,gBAAgB,CAAC,IAAI,CAAC;YACpB,IAAI,EAAE,EAAE,CAAC,QAAQ;YACjB,IAAI;YACJ,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;YACvD,OAAO,EAAE,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,eAAe,EAAE;YAC/D,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,QAAQ,EAAE,EAAE,CAAC,QAAQ;SACtB,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Runtime Guard (Phase 5 — CI Stability Hardening)
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* 1. withVerifyTimeout() — wraps any verify sub-step with a wall-clock timeout.
|
|
6
|
+
* On timeout: returns a degraded result instead of hanging indefinitely.
|
|
7
|
+
* This ensures CI jobs always terminate, even when a rule engine or
|
|
8
|
+
* brain indexer stalls on an unexpectedly large file.
|
|
9
|
+
*
|
|
10
|
+
* 2. buildCIHealthSummary() — produces a structured CI health summary with:
|
|
11
|
+
* - cache warm/cold indicator
|
|
12
|
+
* - per-subsystem latency breakdown
|
|
13
|
+
* - degraded subsystem list
|
|
14
|
+
* Used for CI visibility when --ci flag is set.
|
|
15
|
+
*
|
|
16
|
+
* Key design invariant:
|
|
17
|
+
* withVerifyTimeout NEVER throws. It returns a typed DegradedResult.
|
|
18
|
+
* Callers must check result.degraded before using result.value.
|
|
19
|
+
*/
|
|
20
|
+
export interface DegradedResult<T> {
|
|
21
|
+
degraded: false;
|
|
22
|
+
value: T;
|
|
23
|
+
durationMs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface TimeoutResult {
|
|
26
|
+
degraded: true;
|
|
27
|
+
reason: 'timeout';
|
|
28
|
+
timeoutMs: number;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ErrorResult {
|
|
32
|
+
degraded: true;
|
|
33
|
+
reason: 'error';
|
|
34
|
+
error: string;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
}
|
|
37
|
+
export type SubstepResult<T> = DegradedResult<T> | TimeoutResult | ErrorResult;
|
|
38
|
+
export interface CISubsystemTiming {
|
|
39
|
+
name: string;
|
|
40
|
+
durationMs: number;
|
|
41
|
+
status: 'ok' | 'degraded' | 'timeout' | 'error' | 'skipped';
|
|
42
|
+
}
|
|
43
|
+
export interface CIHealthSummary {
|
|
44
|
+
/** Was the structural cache warm (hits > 0) or cold? */
|
|
45
|
+
cacheStatus: 'warm' | 'cold' | 'disabled';
|
|
46
|
+
/** Total wall-clock time across all subsystems */
|
|
47
|
+
totalDurationMs: number;
|
|
48
|
+
/** Per-subsystem timing breakdown */
|
|
49
|
+
subsystems: CISubsystemTiming[];
|
|
50
|
+
/** Names of subsystems that ran in degraded mode */
|
|
51
|
+
degradedSubsystems: string[];
|
|
52
|
+
/** Whether all subsystems completed successfully */
|
|
53
|
+
allSubsystemsHealthy: boolean;
|
|
54
|
+
/** ISO timestamp */
|
|
55
|
+
generatedAt: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run an async function with a wall-clock timeout.
|
|
59
|
+
*
|
|
60
|
+
* On success: returns { degraded: false, value: T, durationMs }
|
|
61
|
+
* On timeout: returns { degraded: true, reason: 'timeout', timeoutMs, durationMs }
|
|
62
|
+
* On error: returns { degraded: true, reason: 'error', error: string, durationMs }
|
|
63
|
+
*
|
|
64
|
+
* NEVER throws. All error/timeout paths are handled internally.
|
|
65
|
+
*
|
|
66
|
+
* @param fn The async function to execute
|
|
67
|
+
* @param timeoutMs Wall-clock timeout in milliseconds
|
|
68
|
+
*/
|
|
69
|
+
export declare function withVerifyTimeout<T>(fn: () => Promise<T>, timeoutMs: number): Promise<SubstepResult<T>>;
|
|
70
|
+
/**
|
|
71
|
+
* Synchronous version of withVerifyTimeout for use with non-async functions.
|
|
72
|
+
*
|
|
73
|
+
* Since true synchronous timeout is impossible in Node.js without worker threads,
|
|
74
|
+
* this wraps the synchronous function as a Promise and applies the async timeout.
|
|
75
|
+
* If the sync function blocks the event loop longer than timeoutMs, the timeout
|
|
76
|
+
* will fire only after it completes — this is a best-effort guard for functions
|
|
77
|
+
* that are expected to complete quickly but may unexpectedly stall.
|
|
78
|
+
*/
|
|
79
|
+
export declare function withVerifyTimeoutSync<T>(fn: () => T, timeoutMs: number): Promise<SubstepResult<T>>;
|
|
80
|
+
export interface CIHealthInput {
|
|
81
|
+
subsystems: CISubsystemTiming[];
|
|
82
|
+
cacheHits: number;
|
|
83
|
+
cacheEnabled: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build a structured CI health summary from subsystem timing data.
|
|
87
|
+
*
|
|
88
|
+
* @param input Subsystem timings collected during verify execution
|
|
89
|
+
*/
|
|
90
|
+
export declare function buildCIHealthSummary(input: CIHealthInput): CIHealthSummary;
|
|
91
|
+
/** Default timeout for the structural rule engine per verify run (30s) */
|
|
92
|
+
export declare const STRUCTURAL_ENGINE_TIMEOUT_MS = 30000;
|
|
93
|
+
/** Default timeout for the brain context indexer (20s) */
|
|
94
|
+
export declare const BRAIN_INDEXER_TIMEOUT_MS = 20000;
|
|
95
|
+
/** Default timeout for intent engine operations (25s) */
|
|
96
|
+
export declare const INTENT_ENGINE_TIMEOUT_MS = 25000;
|
|
97
|
+
/** Maximum total verify wall-clock time before CI is considered hung (5min) */
|
|
98
|
+
export declare const VERIFY_TOTAL_TIMEOUT_MS = 300000;
|
|
99
|
+
//# sourceMappingURL=verify-runtime-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-runtime-guard.d.ts","sourceRoot":"","sources":["../../src/governance/verify-runtime-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,EAAE,KAAK,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC;IACT,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG,WAAW,CAAC;AAE/E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;CAC7D;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;IAC1C,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,oDAAoD;IACpD,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,oDAAoD;IACpD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,oBAAoB;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CA4C3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,EAAE,EAAE,MAAM,CAAC,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAE3B;AAID,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,CAwB1E;AAID,0EAA0E;AAC1E,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,yDAAyD;AACzD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,SAAU,CAAC"}
|