code-auditor-mcp 3.6.0 → 3.8.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +73 -0
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +12 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +122 -43
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +6 -7
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +152 -20
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +38 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +437 -48
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +6 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +117 -35
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +86 -15
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +32 -7
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +104 -23
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts +55 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +318 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -8
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +54 -45
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +57 -0
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -0
- package/dist/analyzers/universal/functionConcerns.js +239 -0
- package/dist/analyzers/universal/functionConcerns.js.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +13 -7
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +124 -11
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +4 -6
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +74 -48
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +321 -98
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +23 -0
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +45 -10
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +2 -0
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/conventions/conventionMiner.d.ts +18 -3
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +130 -12
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/daemon/core.d.ts +128 -0
- package/dist/daemon/core.d.ts.map +1 -0
- package/dist/daemon/core.js +700 -0
- package/dist/daemon/core.js.map +1 -0
- package/dist/daemon/lspServer.d.ts +51 -0
- package/dist/daemon/lspServer.d.ts.map +1 -0
- package/dist/daemon/lspServer.js +168 -0
- package/dist/daemon/lspServer.js.map +1 -0
- package/dist/daemon/main.d.ts +34 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +147 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/resolve.d.ts +41 -0
- package/dist/daemon/resolve.d.ts.map +1 -0
- package/dist/daemon/resolve.js +90 -0
- package/dist/daemon/resolve.js.map +1 -0
- package/dist/daemon/socketClient.d.ts +21 -0
- package/dist/daemon/socketClient.d.ts.map +1 -0
- package/dist/daemon/socketClient.js +60 -0
- package/dist/daemon/socketClient.js.map +1 -0
- package/dist/daemon/socketServer.d.ts +33 -0
- package/dist/daemon/socketServer.d.ts.map +1 -0
- package/dist/daemon/socketServer.js +106 -0
- package/dist/daemon/socketServer.js.map +1 -0
- package/dist/daemon/types.d.ts +78 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +10 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/dataPaths.d.ts +21 -0
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +27 -0
- package/dist/dataPaths.js.map +1 -1
- package/dist/graph/importGraph.d.ts +11 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +11 -3
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +21 -4
- package/dist/hooks/core.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +2 -5
- package/dist/installer.js.map +1 -1
- package/dist/languages/RuntimeManager.js +10 -0
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/go/analyzer +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +166 -35
- package/dist/languages/go/analyzer-src/solid.go +80 -172
- package/dist/languages/go/analyzer-src/types.go +18 -18
- package/dist/ledger.d.ts +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +3 -3
- package/dist/ledger.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.js +1 -1
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +9 -3
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +32 -6
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipeline.d.ts +26 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +86 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +2 -1
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +228 -5
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/csvReportGenerator.js +1 -1
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +41 -2
- package/dist/ruleAliases.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +8 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +11 -1
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/types/crossLanguage.d.ts +1 -0
- package/dist/types/crossLanguage.d.ts.map +1 -1
- package/dist/types.d.ts +30 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/code-auditor/SKILL-RULE-KINDS.md +4 -2
- package/plugin/skills/code-auditor/SKILL.md +19 -11
package/dist/pipelineAdapters.js
CHANGED
|
@@ -21,6 +21,7 @@ import { RULE_REGISTRY } from './analyzers/ruleRegistry.js';
|
|
|
21
21
|
import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
|
|
22
22
|
import { walkAST, isExported, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
|
|
23
23
|
import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
|
|
24
|
+
import { resolveDependency, basenameNoExt } from './graph/importGraph.js';
|
|
24
25
|
import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
|
|
25
26
|
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
|
|
26
27
|
import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
|
|
@@ -125,6 +126,23 @@ export function createDocumentationVisitor() {
|
|
|
125
126
|
category: 'style',
|
|
126
127
|
};
|
|
127
128
|
}
|
|
129
|
+
// ── Secrets visitor ──────────────────────────────────────────────────────────
|
|
130
|
+
export function createSecretsVisitor() {
|
|
131
|
+
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecretsAnalyzer.js').then((m) => new m.UniversalSecretsAnalyzer()));
|
|
132
|
+
return {
|
|
133
|
+
name: 'secrets',
|
|
134
|
+
stage: 'visitor',
|
|
135
|
+
getRuleIds: () => getRuleIdsFor('secrets'),
|
|
136
|
+
async visit(ast, adapter, context, sourceCode) {
|
|
137
|
+
const a = await getAnalyzer();
|
|
138
|
+
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
139
|
+
return { violations, facts: {} };
|
|
140
|
+
},
|
|
141
|
+
defaultConfig: {},
|
|
142
|
+
description: 'Detects hardcoded credentials, API keys, and tokens',
|
|
143
|
+
category: 'security',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
128
146
|
// ── Function-Index visitor (infrastructure) ──────────────────────────────────
|
|
129
147
|
// Always-on Stage 2 visitor that populates the `functions` table with
|
|
130
148
|
// per-file definitions so the conventions and cross-domain reducers have data
|
|
@@ -861,7 +879,7 @@ export function createConventionsReducer() {
|
|
|
861
879
|
// ── Cross-Domain reducer (stage 4) ───────────────────────────────────────────
|
|
862
880
|
/**
|
|
863
881
|
* Cross-domain reducer — runs written-never-read, read-never-written,
|
|
864
|
-
* transaction-boundary risk,
|
|
882
|
+
* transaction-boundary risk, no-validator-reachable, and uncovered-risk detectors.
|
|
865
883
|
* Uses analyzer.analyze([]) with rawDb injected.
|
|
866
884
|
*/
|
|
867
885
|
export function createCrossDomainReducer() {
|
|
@@ -980,7 +998,7 @@ function clCollectCallees(raw) {
|
|
|
980
998
|
walk(raw);
|
|
981
999
|
return [...names];
|
|
982
1000
|
}
|
|
983
|
-
function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity) {
|
|
1001
|
+
function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
|
|
984
1002
|
return {
|
|
985
1003
|
id: clEntityId(filePath, 'function', name, line),
|
|
986
1004
|
name,
|
|
@@ -997,9 +1015,49 @@ function clMakeFunction(filePath, lang, name, line, signature, purpose, params,
|
|
|
997
1015
|
purpose,
|
|
998
1016
|
context: '',
|
|
999
1017
|
searchTokens: [name.toLowerCase()],
|
|
1000
|
-
metadata: { callees },
|
|
1018
|
+
metadata: { callees, isMethod },
|
|
1001
1019
|
};
|
|
1002
1020
|
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Collect a file's import specifiers and whether it declares any exports.
|
|
1023
|
+
*
|
|
1024
|
+
* Feeds file-level reachability: an `import_statement` (TS/JS) or
|
|
1025
|
+
* `import_declaration` (Go) records a dependency; an `export_statement`
|
|
1026
|
+
* (TS/JS) records that the file exposes symbols to importers. Go exports are
|
|
1027
|
+
* computed separately from the extracted entities (capitalized top-level
|
|
1028
|
+
* names), since Go has no `export` keyword.
|
|
1029
|
+
*/
|
|
1030
|
+
function clCollectFileInfo(root, lang) {
|
|
1031
|
+
const imports = new Set();
|
|
1032
|
+
let hasExports = false;
|
|
1033
|
+
walkAST(root, (node) => {
|
|
1034
|
+
const raw = node.raw;
|
|
1035
|
+
if (node.type === 'import_statement') {
|
|
1036
|
+
const source = clRawField(raw, 'source');
|
|
1037
|
+
if (source?.text)
|
|
1038
|
+
imports.add(source.text.replace(/^['"]|['"]$/g, ''));
|
|
1039
|
+
}
|
|
1040
|
+
else if (node.type === 'import_declaration') {
|
|
1041
|
+
for (const spec of raw?.namedChildren ?? []) {
|
|
1042
|
+
if (spec.type === 'import_spec') {
|
|
1043
|
+
const p = clRawField(spec, 'path');
|
|
1044
|
+
if (p?.text)
|
|
1045
|
+
imports.add(p.text.replace(/^['"]|['"]$/g, ''));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
else if (node.type === 'export_statement') {
|
|
1050
|
+
hasExports = true;
|
|
1051
|
+
// Re-exports (`export { x } from './y'`, `export * from './y'`) are import
|
|
1052
|
+
// edges for reachability: a barrel re-exporting a module marks it live,
|
|
1053
|
+
// not dead. The `source` field is present only on the `from` form.
|
|
1054
|
+
const source = clRawField(raw, 'source');
|
|
1055
|
+
if (source?.text)
|
|
1056
|
+
imports.add(source.text.replace(/^['"]|['"]$/g, ''));
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
return { imports: [...imports], hasExports };
|
|
1060
|
+
}
|
|
1003
1061
|
// ── TS/JS extraction ─────────────────────────────────────────────────────────
|
|
1004
1062
|
function clExtractTSParams(raw) {
|
|
1005
1063
|
const params = [];
|
|
@@ -1055,7 +1113,7 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1055
1113
|
}
|
|
1056
1114
|
}
|
|
1057
1115
|
const line = (raw?.startPosition?.row ?? 0) + 1;
|
|
1058
|
-
out.push(clMakeFunction(filePath, lang, displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractTSParams(raw), clCollectCallees(raw), isExported(node), calculateComplexity(node)));
|
|
1116
|
+
out.push(clMakeFunction(filePath, lang, displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractTSParams(raw), clCollectCallees(raw), isExported(node), calculateComplexity(node), node.type === 'method_definition'));
|
|
1059
1117
|
return;
|
|
1060
1118
|
}
|
|
1061
1119
|
if (node.type === 'variable_declarator') {
|
|
@@ -1222,9 +1280,15 @@ export function createCrossLanguageEntityVisitor() {
|
|
|
1222
1280
|
else {
|
|
1223
1281
|
clExtractTSEntities(root, filePath, sourceCode, lang, entities);
|
|
1224
1282
|
}
|
|
1283
|
+
const fileInfo = clCollectFileInfo(root, lang);
|
|
1284
|
+
// Go has no `export` keyword — an exported symbol is a capitalized
|
|
1285
|
+
// top-level name, which the entity extractor already records as public.
|
|
1286
|
+
const hasExports = lang === 'go'
|
|
1287
|
+
? entities.some((e) => e.visibility === 'public')
|
|
1288
|
+
: fileInfo.hasExports;
|
|
1225
1289
|
return {
|
|
1226
1290
|
violations: [],
|
|
1227
|
-
facts: { [filePath]: { entities } },
|
|
1291
|
+
facts: { [filePath]: { entities, imports: fileInfo.imports, hasExports } },
|
|
1228
1292
|
};
|
|
1229
1293
|
},
|
|
1230
1294
|
defaultConfig: {},
|
|
@@ -1244,6 +1308,86 @@ function clFlattenEntities(allFacts) {
|
|
|
1244
1308
|
}
|
|
1245
1309
|
return entities;
|
|
1246
1310
|
}
|
|
1311
|
+
/** Extract per-file `{ imports, hasExports }` from the cross-language facts. */
|
|
1312
|
+
function clFileFacts(allFacts) {
|
|
1313
|
+
const facts = allFacts['cross-language-entities'];
|
|
1314
|
+
const map = new Map();
|
|
1315
|
+
if (!facts)
|
|
1316
|
+
return map;
|
|
1317
|
+
for (const [filePath, data] of Object.entries(facts)) {
|
|
1318
|
+
if (!data)
|
|
1319
|
+
continue;
|
|
1320
|
+
map.set(filePath, { imports: data.imports ?? [], hasExports: data.hasExports ?? false });
|
|
1321
|
+
}
|
|
1322
|
+
return map;
|
|
1323
|
+
}
|
|
1324
|
+
function clIsTestFile(fp) {
|
|
1325
|
+
const lower = fp.toLowerCase();
|
|
1326
|
+
return lower.includes('.test.') || lower.includes('.spec.') ||
|
|
1327
|
+
lower.includes('__tests__') || lower.includes('/test/') || lower.includes('/tests/') ||
|
|
1328
|
+
lower.endsWith('_test.go');
|
|
1329
|
+
}
|
|
1330
|
+
// Files the framework loads directly rather than via an import from sibling
|
|
1331
|
+
// code. Skipped when flagging unreferenced modules — a route/page/entry file
|
|
1332
|
+
// that nothing imports is an entry point, not dead code.
|
|
1333
|
+
const CL_ENTRY_BASENAMES = new Set([
|
|
1334
|
+
'route', 'page', 'layout', 'loading', 'error', 'not-found', 'template', 'default',
|
|
1335
|
+
'middleware', 'instrumentation', 'server', 'client', 'cli', 'main', 'app', 'index', 'worker',
|
|
1336
|
+
'setup', 'seed',
|
|
1337
|
+
]);
|
|
1338
|
+
function clIsEntryPointFile(fp) {
|
|
1339
|
+
const segments = fp.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
1340
|
+
const base = segments[segments.length - 1] ?? '';
|
|
1341
|
+
const stem = base.replace(/\.[^.]+$/, '');
|
|
1342
|
+
if (CL_ENTRY_BASENAMES.has(stem))
|
|
1343
|
+
return true;
|
|
1344
|
+
if (stem.endsWith('.config'))
|
|
1345
|
+
return true; // next.config, vite.config, tailwind.config, …
|
|
1346
|
+
const joined = '/' + segments.join('/') + '/';
|
|
1347
|
+
if (joined.includes('/app/api/') || joined.includes('/pages/api/'))
|
|
1348
|
+
return true;
|
|
1349
|
+
if (joined.includes('/scripts/') || joined.includes('/bin/') || joined.includes('/cmd/'))
|
|
1350
|
+
return true;
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Compute file-level reachability from per-file imports.
|
|
1355
|
+
*
|
|
1356
|
+
* A file is a framework entry point (live → 1.0), imported by another file
|
|
1357
|
+
* (live → 0.5), or referenced by nothing (dead → 0.0). The resolver mirrors
|
|
1358
|
+
* importGraph.ts and handles relative paths, npm/alias basenames, and fuzzy
|
|
1359
|
+
* path-segment matches.
|
|
1360
|
+
*/
|
|
1361
|
+
function clComputeReachability(fileFacts) {
|
|
1362
|
+
const filePaths = new Set(fileFacts.keys());
|
|
1363
|
+
const moduleToFile = new Map();
|
|
1364
|
+
for (const fp of filePaths) {
|
|
1365
|
+
const bn = basenameNoExt(fp);
|
|
1366
|
+
if (!moduleToFile.has(bn))
|
|
1367
|
+
moduleToFile.set(bn, new Set());
|
|
1368
|
+
moduleToFile.get(bn).add(fp);
|
|
1369
|
+
}
|
|
1370
|
+
const importersOf = new Map();
|
|
1371
|
+
for (const [fp, info] of fileFacts) {
|
|
1372
|
+
for (const dep of info.imports) {
|
|
1373
|
+
const targets = resolveDependency(dep, filePaths, moduleToFile, fp);
|
|
1374
|
+
for (const t of targets) {
|
|
1375
|
+
if (t === fp)
|
|
1376
|
+
continue;
|
|
1377
|
+
if (!importersOf.has(t))
|
|
1378
|
+
importersOf.set(t, new Set());
|
|
1379
|
+
importersOf.get(t).add(fp);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const reachability = new Map();
|
|
1384
|
+
for (const fp of filePaths) {
|
|
1385
|
+
const entry = clIsEntryPointFile(fp);
|
|
1386
|
+
const imported = (importersOf.get(fp)?.size ?? 0) > 0;
|
|
1387
|
+
reachability.set(fp, entry ? 1 : imported ? 0.5 : 0);
|
|
1388
|
+
}
|
|
1389
|
+
return { reachability, importersOf };
|
|
1390
|
+
}
|
|
1247
1391
|
/**
|
|
1248
1392
|
* Build `calls` cross-references by resolving each entity's extracted callee
|
|
1249
1393
|
* names against the corpus entity name index. Library calls (fetch, map, …)
|
|
@@ -1407,6 +1551,30 @@ export function createDependencyGraphReducer() {
|
|
|
1407
1551
|
const idToEntity = new Map(entities.map((e) => [e.id, e]));
|
|
1408
1552
|
const violations = [];
|
|
1409
1553
|
for (const issue of health.issues) {
|
|
1554
|
+
if (issue.type === 'orphaned-nodes') {
|
|
1555
|
+
// Emit one violation per orphan so each is attributed to its own
|
|
1556
|
+
// file/line. The aggregated form anchored every orphan to the first
|
|
1557
|
+
// node's location while the message listed all of them — foreign
|
|
1558
|
+
// symbols leaked into the wrong file.
|
|
1559
|
+
for (const id of issue.affectedNodes) {
|
|
1560
|
+
const orphan = idToEntity.get(id);
|
|
1561
|
+
if (!orphan)
|
|
1562
|
+
continue;
|
|
1563
|
+
violations.push({
|
|
1564
|
+
file: orphan.file,
|
|
1565
|
+
line: orphan.startLine,
|
|
1566
|
+
severity: issue.severity,
|
|
1567
|
+
message: `Orphaned node "${orphan.name}" has no connections.`,
|
|
1568
|
+
rule: issue.type,
|
|
1569
|
+
type: issue.type, // dependency-graph rules match on field: 'type'
|
|
1570
|
+
analyzer: 'dependency-graph',
|
|
1571
|
+
category: 'cross-language-dependency',
|
|
1572
|
+
functionName: orphan.name,
|
|
1573
|
+
details: { orphanId: id },
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1410
1578
|
const anchor = issue.affectedNodes.map((id) => idToEntity.get(id)).find(Boolean);
|
|
1411
1579
|
violations.push({
|
|
1412
1580
|
file: anchor?.file ?? '(unknown)',
|
|
@@ -1420,6 +1588,61 @@ export function createDependencyGraphReducer() {
|
|
|
1420
1588
|
details: issue.details,
|
|
1421
1589
|
});
|
|
1422
1590
|
}
|
|
1591
|
+
// ── File-level reachability: unreferenced modules + ranking axis ──
|
|
1592
|
+
const fileFacts = clFileFacts(allFacts);
|
|
1593
|
+
if (fileFacts.size > 0) {
|
|
1594
|
+
const { reachability, importersOf } = clComputeReachability(fileFacts);
|
|
1595
|
+
// Persist reachability to graph_cache so the post-pipeline reorder
|
|
1596
|
+
// (auditRunner hotspot block) can rank every finding by live/dead.
|
|
1597
|
+
if (context.indexHandle?.rawDb) {
|
|
1598
|
+
try {
|
|
1599
|
+
const db = context.indexHandle.rawDb;
|
|
1600
|
+
const del = db.prepare("DELETE FROM graph_cache WHERE graph_type = 'reachability'");
|
|
1601
|
+
const ins = db.prepare("INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('reachability', ?, '', ?)");
|
|
1602
|
+
const tx = db.transaction(() => {
|
|
1603
|
+
del.run();
|
|
1604
|
+
for (const [fp, score] of reachability) {
|
|
1605
|
+
ins.run(fp, score);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
tx();
|
|
1609
|
+
}
|
|
1610
|
+
catch {
|
|
1611
|
+
// Reachability persistence is advisory — ranking falls back to a
|
|
1612
|
+
// neutral default without it.
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
// Flag files that export symbols yet are imported by nothing and are
|
|
1616
|
+
// not framework entry points — a dead module. This is the signal the
|
|
1617
|
+
// orphaned-nodes check missed: a whole unreferenced file whose every
|
|
1618
|
+
// internal symbol still forms a connected component.
|
|
1619
|
+
for (const [fp, info] of fileFacts) {
|
|
1620
|
+
if (!info.hasExports)
|
|
1621
|
+
continue;
|
|
1622
|
+
if (clIsTestFile(fp))
|
|
1623
|
+
continue;
|
|
1624
|
+
if (clIsEntryPointFile(fp))
|
|
1625
|
+
continue;
|
|
1626
|
+
if ((importersOf.get(fp)?.size ?? 0) > 0)
|
|
1627
|
+
continue;
|
|
1628
|
+
violations.push({
|
|
1629
|
+
file: fp,
|
|
1630
|
+
line: 1,
|
|
1631
|
+
// Promoted suggestion → warning (Spec 11 R5, one tier). The
|
|
1632
|
+
// method-dispatch fix removed the class-prefixed / isMethod false
|
|
1633
|
+
// positives, so a module that exports and is imported by nothing
|
|
1634
|
+
// is a genuine dead-module signal — a defect that should block,
|
|
1635
|
+
// not a suggestion to weigh. Validated across four corpora.
|
|
1636
|
+
severity: 'warning',
|
|
1637
|
+
message: 'Module is not imported by any other file and is not a framework entry point — dead code candidate.',
|
|
1638
|
+
rule: 'unreferenced-module',
|
|
1639
|
+
type: 'unreferenced-module',
|
|
1640
|
+
analyzer: 'dependency-graph',
|
|
1641
|
+
category: 'cross-language-dependency',
|
|
1642
|
+
details: { imports: info.imports },
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1423
1646
|
for (const s of health.suggestions) {
|
|
1424
1647
|
violations.push({
|
|
1425
1648
|
file: '(multiple)',
|