@clear-capabilities/agentic-security-scanner 0.78.0 → 0.80.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/bin/.agentic-security/findings.json +16 -16
- package/bin/.agentic-security/last-scan.json +16 -16
- package/bin/.agentic-security/last-scan.json.sig +1 -1
- package/bin/.agentic-security/scan-history.json +51 -0
- package/bin/.agentic-security/streak.json +5 -5
- package/bin/agentic-security.js +22 -7
- package/dist/178.index.js +1 -1
- package/dist/333.index.js +283 -0
- package/dist/384.index.js +1 -1
- package/dist/476.index.js +5 -5
- package/dist/637.index.js +1 -1
- package/dist/700.index.js +138 -0
- package/dist/718.index.js +53 -0
- package/dist/838.index.js +1 -1
- package/dist/985.index.js +95 -1
- package/dist/agentic-security.mjs +83 -83
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +6 -4
- package/src/.agentic-security/findings.json +29799 -7803
- package/src/.agentic-security/last-scan.json +29799 -7803
- package/src/.agentic-security/last-scan.json.sig +1 -1
- package/src/.agentic-security/scan-history.json +5119 -2611
- package/src/.agentic-security/streak.json +6 -6
- package/src/dataflow/.agentic-security/findings.json +2879 -308
- package/src/dataflow/.agentic-security/last-scan.json +2879 -308
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -1
- package/src/dataflow/.agentic-security/scan-history.json +68 -520
- package/src/dataflow/.agentic-security/streak.json +6 -7
- package/src/dataflow/cross-service-taint.js +201 -0
- package/src/dataflow/engine.js +52 -8
- package/src/dataflow/formal-verify.js +204 -0
- package/src/dataflow/ifds-precise.js +222 -0
- package/src/dataflow/k2-summary-cache.js +153 -0
- package/src/dataflow/lib-taint-summaries.js +198 -0
- package/src/dataflow/privacy-taint.js +205 -0
- package/src/dataflow/smt-feasibility.js +189 -0
- package/src/engine.js +890 -132
- package/src/integrations/index.js +2 -1
- package/src/ir/.agentic-security/findings.json +240 -6
- package/src/ir/.agentic-security/last-scan.json +240 -6
- package/src/ir/.agentic-security/last-scan.json.sig +1 -1
- package/src/ir/.agentic-security/scan-history.json +16 -594
- package/src/ir/.agentic-security/streak.json +8 -9
- package/src/ir/callgraph.js +27 -7
- package/src/ir/cpp-preprocessor.js +142 -0
- package/src/ir/csharp-ir.js +604 -0
- package/src/ir/universal-ir.js +403 -0
- package/src/llm-validator/index.js +7 -5
- package/src/mcp/.agentic-security/findings.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +143 -0
- package/src/mcp/.agentic-security/streak.json +20 -0
- package/src/mcp/audit.js +5 -0
- package/src/mcp/tools.js +90 -1
- package/src/posture/.agentic-security/findings.json +16809 -4367
- package/src/posture/.agentic-security/last-scan.json +16809 -4367
- package/src/posture/.agentic-security/last-scan.json.sig +1 -1
- package/src/posture/.agentic-security/scan-history.json +6689 -177
- package/src/posture/.agentic-security/streak.json +8 -7
- package/src/posture/api-contract.js +193 -0
- package/src/posture/attack-taxonomy.js +227 -0
- package/src/posture/calibration-drift.js +2 -1
- package/src/posture/calibration.js +3 -2
- package/src/posture/compliance-policy.js +218 -0
- package/src/posture/composite-risk.js +122 -0
- package/src/posture/csharp-analysis.js +330 -0
- package/src/posture/exploit-bundle.js +210 -0
- package/src/posture/federated-learning.js +172 -0
- package/src/posture/fix-history.js +8 -2
- package/src/posture/license-attributions.js +94 -0
- package/src/posture/license-graph.js +238 -0
- package/src/posture/pqc-migration-plan.js +158 -0
- package/src/posture/profile.js +4 -5
- package/src/posture/reachability-filter.js +33 -2
- package/src/posture/realtime-cve-monitor.js +214 -0
- package/src/posture/rule-overrides.js +2 -3
- package/src/posture/rule-pack-signing.js +2 -3
- package/src/posture/rule-synthesis.js +5 -6
- package/src/posture/runtime-correlation.js +174 -0
- package/src/posture/sbom-diff.js +171 -0
- package/src/posture/sca-policy.js +235 -0
- package/src/posture/sca-upgrade.js +259 -0
- package/src/posture/security-trend.js +4 -7
- package/src/posture/state-dir.js +124 -0
- package/src/posture/streak.js +3 -0
- package/src/posture/suppressions.js +5 -8
- package/src/posture/threat-model-auto.js +268 -0
- package/src/posture/triage-learning.js +170 -0
- package/src/posture/triage.js +29 -6
- package/src/posture/validator-metrics.js +3 -6
- package/src/sast/.agentic-security/findings.json +996 -32
- package/src/sast/.agentic-security/last-scan.json +996 -32
- package/src/sast/.agentic-security/last-scan.json.sig +1 -1
- package/src/sast/.agentic-security/scan-history.json +565 -32
- package/src/sast/.agentic-security/streak.json +10 -8
- package/src/sast/_secret-entropy.js +145 -0
- package/src/sast/cloud-iam.js +312 -0
- package/src/sast/cpp.js +138 -4
- package/src/sast/crypto-protocol.js +388 -0
- package/src/sast/csharp-tokenizer.js +392 -0
- package/src/sast/csharp.js +924 -138
- package/src/sast/dapp-frontend.js +200 -0
- package/src/sast/db-taint.js +24 -0
- package/src/sast/k8s-admission.js +271 -0
- package/src/sast/llm-app.js +272 -0
- package/src/sast/ml-supply-chain.js +259 -0
- package/src/sast/mobile.js +224 -0
- package/src/sast/post-quantum-crypto.js +348 -0
- package/src/sast/rust.js +26 -0
- package/src/sast/web3-advanced.js +375 -0
- package/src/sca/.agentic-security/findings.json +6044 -171
- package/src/sca/.agentic-security/last-scan.json +6044 -171
- package/src/sca/.agentic-security/last-scan.json.sig +1 -1
- package/src/sca/.agentic-security/scan-history.json +83 -6
- package/src/sca/.agentic-security/streak.json +9 -9
- package/src/sca/CLAUDE.md +161 -0
- package/src/sca/binary-metadata.js +146 -0
- package/src/sca/py-package-functions.js +118 -0
- package/src/sca/sigstore-verify.js +215 -0
- package/src/sca/vendor-detect.js +53 -0
- package/src/report/.agentic-security/findings.json +0 -80
- package/src/report/.agentic-security/last-scan.json +0 -80
- package/src/report/.agentic-security/last-scan.json.sig +0 -1
- package/src/report/.agentic-security/scan-history.json +0 -35
- package/src/report/.agentic-security/streak.json +0 -22
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// Universal IR — Recommendation #1 of the world-class roadmap.
|
|
2
|
+
//
|
|
3
|
+
// One IR shape every detector consumes, regardless of source language.
|
|
4
|
+
// Lazy-loads tree-sitter grammars (web-tree-sitter, WASM, NO native
|
|
5
|
+
// bindings) so the scanner stays bootable when tree-sitter isn't
|
|
6
|
+
// installed. When the WASM grammar isn't available we fall back to the
|
|
7
|
+
// existing per-language IRs (Babel for JS/TS, java-parser for Java,
|
|
8
|
+
// the hand-rolled C# IR from earlier in this session, etc.).
|
|
9
|
+
//
|
|
10
|
+
// IR shape — every node a detector cares about:
|
|
11
|
+
//
|
|
12
|
+
// File:
|
|
13
|
+
// { path, language, ast, decls[], calls[], assignments[],
|
|
14
|
+
// members[], attrs[], imports[], functions[], classes[] }
|
|
15
|
+
//
|
|
16
|
+
// Decl: { kind: 'var'|'const'|'let'|'field'|'param', name,
|
|
17
|
+
// type|null, initText|null, line, scope }
|
|
18
|
+
// Call: { callee, receiver|null, args: ArgExpr[], line, scope,
|
|
19
|
+
// fullPath }
|
|
20
|
+
// Assign: { target, isMember, memberPath|null, rhsText, line, scope }
|
|
21
|
+
// Function: { name, returnType|null, params: Param[], attrs: Attr[],
|
|
22
|
+
// body, line, endLine, async, static }
|
|
23
|
+
// Class: { name, baseTypes[], methods[], fields[], attrs[],
|
|
24
|
+
// line, endLine }
|
|
25
|
+
// Attr: { name, args[] } (attributes / decorators / annotations
|
|
26
|
+
// across languages — same shape)
|
|
27
|
+
// Import: { kind: 'static'|'dynamic', module, names: string[],
|
|
28
|
+
// isDefault, line }
|
|
29
|
+
// ArgExpr: { text, idents, line } (idents EXCLUDE string contents)
|
|
30
|
+
//
|
|
31
|
+
// Detectors author S-expression queries against the IR using
|
|
32
|
+
// queryIR(ir, expr). The expression language is a small subset of
|
|
33
|
+
// tree-sitter queries adapted for our normalized IR:
|
|
34
|
+
//
|
|
35
|
+
// (call :name "ExecuteReader" :receiver-type "SqlCommand")
|
|
36
|
+
// (assign :target-glob "*.CommandText" :rhs-has-ident @tainted)
|
|
37
|
+
//
|
|
38
|
+
// The query engine returns matches with { node, captures }.
|
|
39
|
+
|
|
40
|
+
import * as fs from 'node:fs';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
|
|
43
|
+
// Grammar inventory — language → (extension → grammar package).
|
|
44
|
+
// We don't actually require these at module load; lazy-load via
|
|
45
|
+
// loadGrammar() so the scanner stays bootable when none are installed.
|
|
46
|
+
const GRAMMAR_BY_LANG = {
|
|
47
|
+
javascript: 'tree-sitter-javascript',
|
|
48
|
+
typescript: 'tree-sitter-typescript',
|
|
49
|
+
python: 'tree-sitter-python',
|
|
50
|
+
java: 'tree-sitter-java',
|
|
51
|
+
csharp: 'tree-sitter-c-sharp',
|
|
52
|
+
cpp: 'tree-sitter-cpp',
|
|
53
|
+
c: 'tree-sitter-c',
|
|
54
|
+
go: 'tree-sitter-go',
|
|
55
|
+
rust: 'tree-sitter-rust',
|
|
56
|
+
ruby: 'tree-sitter-ruby',
|
|
57
|
+
php: 'tree-sitter-php',
|
|
58
|
+
swift: 'tree-sitter-swift',
|
|
59
|
+
kotlin: 'tree-sitter-kotlin',
|
|
60
|
+
scala: 'tree-sitter-scala',
|
|
61
|
+
solidity: 'tree-sitter-solidity',
|
|
62
|
+
dart: 'tree-sitter-dart',
|
|
63
|
+
lua: 'tree-sitter-lua',
|
|
64
|
+
haskell: 'tree-sitter-haskell',
|
|
65
|
+
ocaml: 'tree-sitter-ocaml',
|
|
66
|
+
elixir: 'tree-sitter-elixir',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const EXT_TO_LANG = {
|
|
70
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript',
|
|
71
|
+
ts: 'typescript', tsx: 'typescript',
|
|
72
|
+
py: 'python', pyi: 'python',
|
|
73
|
+
java: 'java',
|
|
74
|
+
cs: 'csharp',
|
|
75
|
+
c: 'c', h: 'c',
|
|
76
|
+
cc: 'cpp', cpp: 'cpp', cxx: 'cpp', hh: 'cpp', hpp: 'cpp', hxx: 'cpp',
|
|
77
|
+
go: 'go',
|
|
78
|
+
rs: 'rust',
|
|
79
|
+
rb: 'ruby',
|
|
80
|
+
php: 'php',
|
|
81
|
+
swift: 'swift',
|
|
82
|
+
kt: 'kotlin', kts: 'kotlin',
|
|
83
|
+
scala: 'scala',
|
|
84
|
+
sol: 'solidity',
|
|
85
|
+
dart: 'dart',
|
|
86
|
+
lua: 'lua',
|
|
87
|
+
hs: 'haskell',
|
|
88
|
+
ml: 'ocaml', mli: 'ocaml',
|
|
89
|
+
ex: 'elixir', exs: 'elixir',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export function detectLanguage(filePath) {
|
|
93
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
94
|
+
return EXT_TO_LANG[ext] || null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Lazy-loaded parser cache. Key = language name; value = Parser instance
|
|
98
|
+
// (or `false` if the grammar isn't available on this install).
|
|
99
|
+
const _parserCache = new Map();
|
|
100
|
+
let _ParserCtor = null;
|
|
101
|
+
|
|
102
|
+
async function _getParserCtor() {
|
|
103
|
+
if (_ParserCtor !== null) return _ParserCtor;
|
|
104
|
+
try {
|
|
105
|
+
const mod = await import('web-tree-sitter');
|
|
106
|
+
if (typeof mod.init === 'function') await mod.init();
|
|
107
|
+
_ParserCtor = mod.default || mod.Parser || mod;
|
|
108
|
+
return _ParserCtor;
|
|
109
|
+
} catch {
|
|
110
|
+
_ParserCtor = false;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function _loadGrammar(lang) {
|
|
116
|
+
if (_parserCache.has(lang)) return _parserCache.get(lang);
|
|
117
|
+
const Parser = await _getParserCtor();
|
|
118
|
+
if (!Parser) { _parserCache.set(lang, false); return false; }
|
|
119
|
+
const pkg = GRAMMAR_BY_LANG[lang];
|
|
120
|
+
if (!pkg) { _parserCache.set(lang, false); return false; }
|
|
121
|
+
try {
|
|
122
|
+
const wasmPath = require.resolve(`${pkg}/tree-sitter-${lang}.wasm`);
|
|
123
|
+
const language = await Parser.Language.load(wasmPath);
|
|
124
|
+
const parser = new Parser();
|
|
125
|
+
parser.setLanguage(language);
|
|
126
|
+
_parserCache.set(lang, parser);
|
|
127
|
+
return parser;
|
|
128
|
+
} catch {
|
|
129
|
+
_parserCache.set(lang, false);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Normalized IR shape — built by walking the tree-sitter CST and
|
|
135
|
+
// mapping language-specific node names to the universal kinds above.
|
|
136
|
+
// This dispatch is intentionally per-language so a Java
|
|
137
|
+
// `method_declaration` and a TS `method_definition` both map to the
|
|
138
|
+
// universal `Function` node.
|
|
139
|
+
|
|
140
|
+
const NORMALIZERS = {
|
|
141
|
+
javascript: _normalizeJsLike,
|
|
142
|
+
typescript: _normalizeJsLike,
|
|
143
|
+
java: _normalizeJava,
|
|
144
|
+
csharp: _normalizeCsharp,
|
|
145
|
+
cpp: _normalizeCLike,
|
|
146
|
+
c: _normalizeCLike,
|
|
147
|
+
python: _normalizePython,
|
|
148
|
+
go: _normalizeGo,
|
|
149
|
+
rust: _normalizeRust,
|
|
150
|
+
ruby: _normalizeRuby,
|
|
151
|
+
php: _normalizePhp,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Universal IR builder. Returns null if the language can't be parsed
|
|
155
|
+
// here (caller falls back to its existing per-language IR).
|
|
156
|
+
export async function buildUniversalIR(filePath, content, opts = {}) {
|
|
157
|
+
const lang = opts.language || detectLanguage(filePath);
|
|
158
|
+
if (!lang) return null;
|
|
159
|
+
const parser = await _loadGrammar(lang);
|
|
160
|
+
if (!parser) return null;
|
|
161
|
+
let tree;
|
|
162
|
+
try { tree = parser.parse(content); }
|
|
163
|
+
catch { return null; }
|
|
164
|
+
if (!tree || !tree.rootNode) return null;
|
|
165
|
+
const ir = {
|
|
166
|
+
path: filePath, language: lang, ast: tree,
|
|
167
|
+
decls: [], calls: [], assignments: [], members: [],
|
|
168
|
+
attrs: [], imports: [], functions: [], classes: [],
|
|
169
|
+
_content: content,
|
|
170
|
+
};
|
|
171
|
+
const normalize = NORMALIZERS[lang] || _normalizeGeneric;
|
|
172
|
+
try { normalize(tree.rootNode, ir, content); }
|
|
173
|
+
catch (e) { ir._normalizerError = String(e && e.message || e); }
|
|
174
|
+
return ir;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Per-language normalizers (minimal v1 implementations) ─────────────────
|
|
178
|
+
//
|
|
179
|
+
// Each normalizer walks the CST and emits universal IR nodes. The shape
|
|
180
|
+
// is intentionally lossy — detectors don't need every CST detail, they
|
|
181
|
+
// need the security-relevant subset.
|
|
182
|
+
|
|
183
|
+
function _textOf(node, content) {
|
|
184
|
+
if (!node) return '';
|
|
185
|
+
if (typeof node.text === 'string') return node.text;
|
|
186
|
+
if (typeof node.startIndex === 'number') return content.slice(node.startIndex, node.endIndex);
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function _lineOf(node) {
|
|
191
|
+
if (!node) return 0;
|
|
192
|
+
if (node.startPosition && typeof node.startPosition.row === 'number') return node.startPosition.row + 1;
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function _walkChildren(node, fn) {
|
|
197
|
+
if (!node || !node.children) return;
|
|
198
|
+
for (let i = 0; i < node.children.length; i++) fn(node.children[i], i);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function _normalizeJsLike(node, ir, content) {
|
|
202
|
+
_walkChildren(node, (child) => _normalizeJsLike(child, ir, content));
|
|
203
|
+
switch (node.type) {
|
|
204
|
+
case 'function_declaration':
|
|
205
|
+
case 'method_definition':
|
|
206
|
+
case 'arrow_function':
|
|
207
|
+
ir.functions.push({
|
|
208
|
+
name: _textOf(node.childForFieldName?.('name'), content) || null,
|
|
209
|
+
line: _lineOf(node), endLine: _lineOf({ startPosition: node.endPosition }),
|
|
210
|
+
params: [], async: !!node.childForFieldName?.('async'),
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
case 'class_declaration':
|
|
214
|
+
ir.classes.push({
|
|
215
|
+
name: _textOf(node.childForFieldName?.('name'), content) || null,
|
|
216
|
+
line: _lineOf(node),
|
|
217
|
+
});
|
|
218
|
+
break;
|
|
219
|
+
case 'call_expression': {
|
|
220
|
+
const callee = node.childForFieldName?.('function');
|
|
221
|
+
const args = node.childForFieldName?.('arguments');
|
|
222
|
+
ir.calls.push({
|
|
223
|
+
callee: _textOf(callee, content), receiver: null,
|
|
224
|
+
args: args && args.children ? args.children.filter(c => c.type !== ',' && c.type !== '(' && c.type !== ')').map(a => ({
|
|
225
|
+
text: _textOf(a, content), idents: [], line: _lineOf(a),
|
|
226
|
+
})) : [],
|
|
227
|
+
line: _lineOf(node),
|
|
228
|
+
fullPath: _textOf(callee, content),
|
|
229
|
+
});
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'assignment_expression':
|
|
233
|
+
case 'variable_declarator': {
|
|
234
|
+
const target = node.childForFieldName?.('left') || node.childForFieldName?.('name');
|
|
235
|
+
const rhs = node.childForFieldName?.('right') || node.childForFieldName?.('value');
|
|
236
|
+
if (target) {
|
|
237
|
+
const txt = _textOf(target, content);
|
|
238
|
+
const isMember = txt.includes('.');
|
|
239
|
+
const parts = txt.split('.');
|
|
240
|
+
ir.assignments.push({
|
|
241
|
+
target: parts[0], isMember,
|
|
242
|
+
memberPath: isMember ? parts.slice(1).join('.') : null,
|
|
243
|
+
rhsText: rhs ? _textOf(rhs, content) : '',
|
|
244
|
+
line: _lineOf(node),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case 'import_statement':
|
|
250
|
+
case 'import_declaration':
|
|
251
|
+
ir.imports.push({
|
|
252
|
+
kind: 'static',
|
|
253
|
+
module: _textOf(node.childForFieldName?.('source'), content).replace(/['"`]/g, ''),
|
|
254
|
+
line: _lineOf(node), names: [],
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _normalizeJava(node, ir, content) {
|
|
261
|
+
_walkChildren(node, (child) => _normalizeJava(child, ir, content));
|
|
262
|
+
switch (node.type) {
|
|
263
|
+
case 'method_declaration':
|
|
264
|
+
case 'constructor_declaration':
|
|
265
|
+
ir.functions.push({
|
|
266
|
+
name: _textOf(node.childForFieldName?.('name'), content),
|
|
267
|
+
line: _lineOf(node), params: [],
|
|
268
|
+
});
|
|
269
|
+
break;
|
|
270
|
+
case 'class_declaration':
|
|
271
|
+
ir.classes.push({
|
|
272
|
+
name: _textOf(node.childForFieldName?.('name'), content),
|
|
273
|
+
line: _lineOf(node),
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
case 'method_invocation': {
|
|
277
|
+
const object = node.childForFieldName?.('object');
|
|
278
|
+
const name = node.childForFieldName?.('name');
|
|
279
|
+
ir.calls.push({
|
|
280
|
+
callee: _textOf(name, content),
|
|
281
|
+
receiver: object ? _textOf(object, content) : null,
|
|
282
|
+
args: [], line: _lineOf(node),
|
|
283
|
+
fullPath: (object ? _textOf(object, content) + '.' : '') + _textOf(name, content),
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case 'assignment_expression': {
|
|
288
|
+
const target = node.childForFieldName?.('left');
|
|
289
|
+
const rhs = node.childForFieldName?.('right');
|
|
290
|
+
if (target) {
|
|
291
|
+
const txt = _textOf(target, content);
|
|
292
|
+
const isMember = txt.includes('.');
|
|
293
|
+
ir.assignments.push({
|
|
294
|
+
target: txt.split('.')[0], isMember,
|
|
295
|
+
memberPath: isMember ? txt.split('.').slice(1).join('.') : null,
|
|
296
|
+
rhsText: rhs ? _textOf(rhs, content) : '',
|
|
297
|
+
line: _lineOf(node),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'local_variable_declaration':
|
|
303
|
+
case 'field_declaration': {
|
|
304
|
+
const t = node.childForFieldName?.('type');
|
|
305
|
+
const decl = node.childForFieldName?.('declarator');
|
|
306
|
+
const name = decl?.childForFieldName?.('name');
|
|
307
|
+
if (name) ir.decls.push({
|
|
308
|
+
kind: 'var', name: _textOf(name, content),
|
|
309
|
+
type: t ? _textOf(t, content) : null,
|
|
310
|
+
initText: decl?.childForFieldName?.('value') ? _textOf(decl.childForFieldName('value'), content) : null,
|
|
311
|
+
line: _lineOf(node),
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function _normalizeCsharp(node, ir, content) { _normalizeJava(node, ir, content); }
|
|
319
|
+
function _normalizeCLike(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
320
|
+
function _normalizePython(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
321
|
+
function _normalizeGo(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
322
|
+
function _normalizeRust(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
323
|
+
function _normalizeRuby(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
324
|
+
function _normalizePhp(node, ir, content) { _normalizeJsLike(node, ir, content); }
|
|
325
|
+
function _normalizeGeneric(node, ir, content) {
|
|
326
|
+
// Fallback: just collect every call_expression / function shape we
|
|
327
|
+
// can recognize. Useful when the per-language normalizer doesn't exist
|
|
328
|
+
// yet — gives a baseline IR to query against.
|
|
329
|
+
_walkChildren(node, (child) => _normalizeGeneric(child, ir, content));
|
|
330
|
+
if (/call/i.test(node.type)) {
|
|
331
|
+
ir.calls.push({ callee: _textOf(node, content).split('(')[0].trim(), receiver: null, args: [], line: _lineOf(node), fullPath: _textOf(node, content).split('(')[0].trim() });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Query API ──────────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* queryIR(ir, expr) — match IR nodes against an S-expression-like spec.
|
|
339
|
+
*
|
|
340
|
+
* Spec shapes supported in v1:
|
|
341
|
+
* { node: 'call', name?: string|RegExp, receiver?: string|RegExp,
|
|
342
|
+
* receiverType?: string|RegExp, argIdx?: number,
|
|
343
|
+
* hasIdent?: string|RegExp }
|
|
344
|
+
* { node: 'assign', targetGlob?: string, rhsHasIdent?: string|RegExp }
|
|
345
|
+
* { node: 'function', nameGlob?: string, hasAttr?: string|RegExp }
|
|
346
|
+
* { node: 'class', nameGlob?: string, extends?: string|RegExp }
|
|
347
|
+
* { node: 'import', module?: string|RegExp }
|
|
348
|
+
*
|
|
349
|
+
* Returns: [{ node, captures }, ...] where captures are the matched IR nodes.
|
|
350
|
+
*/
|
|
351
|
+
export function queryIR(ir, spec) {
|
|
352
|
+
if (!ir || !spec) return [];
|
|
353
|
+
const matches = [];
|
|
354
|
+
function testStr(pat, s) {
|
|
355
|
+
if (pat == null) return true;
|
|
356
|
+
if (pat instanceof RegExp) return pat.test(s || '');
|
|
357
|
+
return s === pat;
|
|
358
|
+
}
|
|
359
|
+
function testGlob(pat, s) {
|
|
360
|
+
if (!pat) return true;
|
|
361
|
+
const re = new RegExp('^' + pat.replace(/[.+^${}()|\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
|
|
362
|
+
return re.test(s || '');
|
|
363
|
+
}
|
|
364
|
+
switch (spec.node) {
|
|
365
|
+
case 'call':
|
|
366
|
+
for (const c of ir.calls) {
|
|
367
|
+
if (!testStr(spec.name, c.callee)) continue;
|
|
368
|
+
if (!testStr(spec.receiver, c.receiver)) continue;
|
|
369
|
+
if (spec.hasIdent && !c.args.some(a => testStr(spec.hasIdent, a.text))) continue;
|
|
370
|
+
matches.push({ node: c, captures: {} });
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
case 'assign':
|
|
374
|
+
for (const a of ir.assignments) {
|
|
375
|
+
const full = (a.target || '') + (a.memberPath ? '.' + a.memberPath : '');
|
|
376
|
+
if (!testGlob(spec.targetGlob, full)) continue;
|
|
377
|
+
if (spec.rhsHasIdent && !testStr(spec.rhsHasIdent, a.rhsText)) continue;
|
|
378
|
+
matches.push({ node: a, captures: {} });
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
381
|
+
case 'function':
|
|
382
|
+
for (const fn of ir.functions) {
|
|
383
|
+
if (!testGlob(spec.nameGlob, fn.name)) continue;
|
|
384
|
+
matches.push({ node: fn, captures: {} });
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
case 'class':
|
|
388
|
+
for (const cls of ir.classes) {
|
|
389
|
+
if (!testGlob(spec.nameGlob, cls.name)) continue;
|
|
390
|
+
matches.push({ node: cls, captures: {} });
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
case 'import':
|
|
394
|
+
for (const imp of ir.imports) {
|
|
395
|
+
if (!testStr(spec.module, imp.module)) continue;
|
|
396
|
+
matches.push({ node: imp, captures: {} });
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
return matches;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export const _internals = { GRAMMAR_BY_LANG, EXT_TO_LANG, NORMALIZERS };
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
import * as fs from 'node:fs';
|
|
45
45
|
import * as path from 'node:path';
|
|
46
46
|
import * as crypto from 'node:crypto';
|
|
47
|
+
import { statePath, ensureStateDir, safeWriteState } from '../posture/state-dir.js';
|
|
47
48
|
|
|
48
49
|
// Bump on every prompt change so the cache invalidates. Exported as a
|
|
49
50
|
// stable public symbol (premortem 4R-15) so the validator-cache GC subcommand
|
|
@@ -98,7 +99,9 @@ function endpointConfig() {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function ensureCacheDir(scanRoot) {
|
|
101
|
-
const
|
|
102
|
+
const base = ensureStateDir(scanRoot);
|
|
103
|
+
if (!base) return null;
|
|
104
|
+
const dir = path.join(base, 'llm-cache');
|
|
102
105
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
103
106
|
return dir;
|
|
104
107
|
}
|
|
@@ -112,15 +115,14 @@ function cacheKey(finding, fileHash, modelId) {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
function readCache(scanRoot, key) {
|
|
115
|
-
const fp =
|
|
118
|
+
const fp = statePath(scanRoot, 'llm-cache', key + '.json');
|
|
116
119
|
if (!fs.existsSync(fp)) return null;
|
|
117
120
|
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
function writeCache(scanRoot, key, value) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
try { fs.writeFileSync(fp, JSON.stringify(value, null, 2)); } catch {}
|
|
124
|
+
const fp = statePath(scanRoot, 'llm-cache', key + '.json');
|
|
125
|
+
safeWriteState(fp, JSON.stringify(value, null, 2));
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
function fileHashOf(fileContents, file) {
|