@lbroth/rothunter 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/adapters/llm.d.ts +68 -0
- package/dist/adapters/llm.d.ts.map +1 -0
- package/dist/adapters/llm.js +189 -0
- package/dist/adapters/llm.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/detector-registry.d.ts +32 -0
- package/dist/detector-registry.d.ts.map +1 -0
- package/dist/detector-registry.js +74 -0
- package/dist/detector-registry.js.map +1 -0
- package/dist/detectors/api-race.d.ts +6 -0
- package/dist/detectors/api-race.d.ts.map +1 -0
- package/dist/detectors/api-race.js +222 -0
- package/dist/detectors/api-race.js.map +1 -0
- package/dist/detectors/bad-config.d.ts +6 -0
- package/dist/detectors/bad-config.d.ts.map +1 -0
- package/dist/detectors/bad-config.js +529 -0
- package/dist/detectors/bad-config.js.map +1 -0
- package/dist/detectors/console-log-prod.d.ts +6 -0
- package/dist/detectors/console-log-prod.d.ts.map +1 -0
- package/dist/detectors/console-log-prod.js +72 -0
- package/dist/detectors/console-log-prod.js.map +1 -0
- package/dist/detectors/dead-api.d.ts +10 -0
- package/dist/detectors/dead-api.d.ts.map +1 -0
- package/dist/detectors/dead-api.js +115 -0
- package/dist/detectors/dead-api.js.map +1 -0
- package/dist/detectors/dead-export.d.ts +12 -0
- package/dist/detectors/dead-export.d.ts.map +1 -0
- package/dist/detectors/dead-export.js +140 -0
- package/dist/detectors/dead-export.js.map +1 -0
- package/dist/detectors/dead-handler.d.ts +12 -0
- package/dist/detectors/dead-handler.d.ts.map +1 -0
- package/dist/detectors/dead-handler.js +40 -0
- package/dist/detectors/dead-handler.js.map +1 -0
- package/dist/detectors/dead-module.d.ts +14 -0
- package/dist/detectors/dead-module.d.ts.map +1 -0
- package/dist/detectors/dead-module.js +50 -0
- package/dist/detectors/dead-module.js.map +1 -0
- package/dist/detectors/deep-nesting.d.ts +12 -0
- package/dist/detectors/deep-nesting.d.ts.map +1 -0
- package/dist/detectors/deep-nesting.js +133 -0
- package/dist/detectors/deep-nesting.js.map +1 -0
- package/dist/detectors/duplicate-function.d.ts +9 -0
- package/dist/detectors/duplicate-function.d.ts.map +1 -0
- package/dist/detectors/duplicate-function.js +199 -0
- package/dist/detectors/duplicate-function.js.map +1 -0
- package/dist/detectors/duplicate-type.d.ts +9 -0
- package/dist/detectors/duplicate-type.d.ts.map +1 -0
- package/dist/detectors/duplicate-type.js +166 -0
- package/dist/detectors/duplicate-type.js.map +1 -0
- package/dist/detectors/hot-hub-file.d.ts +11 -0
- package/dist/detectors/hot-hub-file.d.ts.map +1 -0
- package/dist/detectors/hot-hub-file.js +42 -0
- package/dist/detectors/hot-hub-file.js.map +1 -0
- package/dist/detectors/long-file.d.ts +12 -0
- package/dist/detectors/long-file.d.ts.map +1 -0
- package/dist/detectors/long-file.js +82 -0
- package/dist/detectors/long-file.js.map +1 -0
- package/dist/detectors/long-function.d.ts +12 -0
- package/dist/detectors/long-function.d.ts.map +1 -0
- package/dist/detectors/long-function.js +45 -0
- package/dist/detectors/long-function.js.map +1 -0
- package/dist/detectors/magic-numbers.d.ts +10 -0
- package/dist/detectors/magic-numbers.d.ts.map +1 -0
- package/dist/detectors/magic-numbers.js +332 -0
- package/dist/detectors/magic-numbers.js.map +1 -0
- package/dist/detectors/mutable-globals.d.ts +6 -0
- package/dist/detectors/mutable-globals.d.ts.map +1 -0
- package/dist/detectors/mutable-globals.js +95 -0
- package/dist/detectors/mutable-globals.js.map +1 -0
- package/dist/detectors/mutation.d.ts +11 -0
- package/dist/detectors/mutation.d.ts.map +1 -0
- package/dist/detectors/mutation.js +397 -0
- package/dist/detectors/mutation.js.map +1 -0
- package/dist/detectors/public-any.d.ts +6 -0
- package/dist/detectors/public-any.d.ts.map +1 -0
- package/dist/detectors/public-any.js +52 -0
- package/dist/detectors/public-any.js.map +1 -0
- package/dist/detectors/race-condition.d.ts +6 -0
- package/dist/detectors/race-condition.d.ts.map +1 -0
- package/dist/detectors/race-condition.js +608 -0
- package/dist/detectors/race-condition.js.map +1 -0
- package/dist/detectors/shared-db-write.d.ts +6 -0
- package/dist/detectors/shared-db-write.d.ts.map +1 -0
- package/dist/detectors/shared-db-write.js +656 -0
- package/dist/detectors/shared-db-write.js.map +1 -0
- package/dist/detectors/silent-catch.d.ts +6 -0
- package/dist/detectors/silent-catch.d.ts.map +1 -0
- package/dist/detectors/silent-catch.js +167 -0
- package/dist/detectors/silent-catch.js.map +1 -0
- package/dist/detectors/similar-functions.d.ts +15 -0
- package/dist/detectors/similar-functions.d.ts.map +1 -0
- package/dist/detectors/similar-functions.js +334 -0
- package/dist/detectors/similar-functions.js.map +1 -0
- package/dist/detectors/skip-tests.d.ts +6 -0
- package/dist/detectors/skip-tests.d.ts.map +1 -0
- package/dist/detectors/skip-tests.js +69 -0
- package/dist/detectors/skip-tests.js.map +1 -0
- package/dist/detectors/todo-comments.d.ts +29 -0
- package/dist/detectors/todo-comments.d.ts.map +1 -0
- package/dist/detectors/todo-comments.js +154 -0
- package/dist/detectors/todo-comments.js.map +1 -0
- package/dist/detectors/unused-deps.d.ts +8 -0
- package/dist/detectors/unused-deps.d.ts.map +1 -0
- package/dist/detectors/unused-deps.js +115 -0
- package/dist/detectors/unused-deps.js.map +1 -0
- package/dist/extraction/api-race-confirmer.d.ts +31 -0
- package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
- package/dist/extraction/api-race-confirmer.js +110 -0
- package/dist/extraction/api-race-confirmer.js.map +1 -0
- package/dist/extraction/llm-confirmer.d.ts +25 -0
- package/dist/extraction/llm-confirmer.d.ts.map +1 -0
- package/dist/extraction/llm-confirmer.js +118 -0
- package/dist/extraction/llm-confirmer.js.map +1 -0
- package/dist/extraction/mutation-confirmer.d.ts +30 -0
- package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
- package/dist/extraction/mutation-confirmer.js +73 -0
- package/dist/extraction/mutation-confirmer.js.map +1 -0
- package/dist/extraction/prompt-chunking.d.ts +37 -0
- package/dist/extraction/prompt-chunking.d.ts.map +1 -0
- package/dist/extraction/prompt-chunking.js +61 -0
- package/dist/extraction/prompt-chunking.js.map +1 -0
- package/dist/extraction/race-confirmer.d.ts +28 -0
- package/dist/extraction/race-confirmer.d.ts.map +1 -0
- package/dist/extraction/race-confirmer.js +68 -0
- package/dist/extraction/race-confirmer.js.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.js +141 -0
- package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
- package/dist/extraction/triage-confirmer.d.ts +59 -0
- package/dist/extraction/triage-confirmer.d.ts.map +1 -0
- package/dist/extraction/triage-confirmer.js +104 -0
- package/dist/extraction/triage-confirmer.js.map +1 -0
- package/dist/graph/cfg.d.ts +45 -0
- package/dist/graph/cfg.d.ts.map +1 -0
- package/dist/graph/cfg.js +198 -0
- package/dist/graph/cfg.js.map +1 -0
- package/dist/graph/decorator-entries.d.ts +2 -0
- package/dist/graph/decorator-entries.d.ts.map +1 -0
- package/dist/graph/decorator-entries.js +89 -0
- package/dist/graph/decorator-entries.js.map +1 -0
- package/dist/graph/entry-points.d.ts +12 -0
- package/dist/graph/entry-points.d.ts.map +1 -0
- package/dist/graph/entry-points.js +282 -0
- package/dist/graph/entry-points.js.map +1 -0
- package/dist/graph/handler-conventions.d.ts +2 -0
- package/dist/graph/handler-conventions.d.ts.map +1 -0
- package/dist/graph/handler-conventions.js +26 -0
- package/dist/graph/handler-conventions.js.map +1 -0
- package/dist/graph/iac-entries.d.ts +2 -0
- package/dist/graph/iac-entries.d.ts.map +1 -0
- package/dist/graph/iac-entries.js +123 -0
- package/dist/graph/iac-entries.js.map +1 -0
- package/dist/graph/import-graph.d.ts +48 -0
- package/dist/graph/import-graph.d.ts.map +1 -0
- package/dist/graph/import-graph.js +86 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/monorepo-detect.d.ts +3 -0
- package/dist/graph/monorepo-detect.d.ts.map +1 -0
- package/dist/graph/monorepo-detect.js +166 -0
- package/dist/graph/monorepo-detect.js.map +1 -0
- package/dist/graph/tsconfig-paths.d.ts +23 -0
- package/dist/graph/tsconfig-paths.d.ts.map +1 -0
- package/dist/graph/tsconfig-paths.js +217 -0
- package/dist/graph/tsconfig-paths.js.map +1 -0
- package/dist/multi-workspace-scanner.d.ts +13 -0
- package/dist/multi-workspace-scanner.d.ts.map +1 -0
- package/dist/multi-workspace-scanner.js +130 -0
- package/dist/multi-workspace-scanner.js.map +1 -0
- package/dist/normalizers/type-normalizer.d.ts +16 -0
- package/dist/normalizers/type-normalizer.d.ts.map +1 -0
- package/dist/normalizers/type-normalizer.js +189 -0
- package/dist/normalizers/type-normalizer.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +57 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +502 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/reporter/json-reporter.d.ts +12 -0
- package/dist/reporter/json-reporter.d.ts.map +1 -0
- package/dist/reporter/json-reporter.js +28 -0
- package/dist/reporter/json-reporter.js.map +1 -0
- package/dist/reporter/markdown-reporter.d.ts +11 -0
- package/dist/reporter/markdown-reporter.d.ts.map +1 -0
- package/dist/reporter/markdown-reporter.js +77 -0
- package/dist/reporter/markdown-reporter.js.map +1 -0
- package/dist/rothunter.d.ts +125 -0
- package/dist/rothunter.d.ts.map +1 -0
- package/dist/rothunter.js +1038 -0
- package/dist/rothunter.js.map +1 -0
- package/dist/server/false-positives.d.ts +34 -0
- package/dist/server/false-positives.d.ts.map +1 -0
- package/dist/server/false-positives.js +85 -0
- package/dist/server/false-positives.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1529 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/marked-to-fix.d.ts +16 -0
- package/dist/server/marked-to-fix.d.ts.map +1 -0
- package/dist/server/marked-to-fix.js +36 -0
- package/dist/server/marked-to-fix.js.map +1 -0
- package/dist/server/scan-store.d.ts +147 -0
- package/dist/server/scan-store.d.ts.map +1 -0
- package/dist/server/scan-store.js +291 -0
- package/dist/server/scan-store.js.map +1 -0
- package/dist/server/settings-store.d.ts +28 -0
- package/dist/server/settings-store.d.ts.map +1 -0
- package/dist/server/settings-store.js +46 -0
- package/dist/server/settings-store.js.map +1 -0
- package/dist/server/workspace-store.d.ts +39 -0
- package/dist/server/workspace-store.d.ts.map +1 -0
- package/dist/server/workspace-store.js +108 -0
- package/dist/server/workspace-store.js.map +1 -0
- package/dist/types/detector-input.d.ts +37 -0
- package/dist/types/detector-input.d.ts.map +1 -0
- package/dist/types/detector-input.js +2 -0
- package/dist/types/detector-input.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/clustering.d.ts +14 -0
- package/dist/utils/clustering.d.ts.map +1 -0
- package/dist/utils/clustering.js +56 -0
- package/dist/utils/clustering.js.map +1 -0
- package/dist/utils/gitignore.d.ts +32 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +122 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hash.d.ts +11 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/ignore-annotation.d.ts +28 -0
- package/dist/utils/ignore-annotation.d.ts.map +1 -0
- package/dist/utils/ignore-annotation.js +46 -0
- package/dist/utils/ignore-annotation.js.map +1 -0
- package/dist/utils/llm-json.d.ts +2 -0
- package/dist/utils/llm-json.d.ts.map +1 -0
- package/dist/utils/llm-json.js +53 -0
- package/dist/utils/llm-json.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +4 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-conventions.d.ts +2 -0
- package/dist/utils/project-conventions.d.ts.map +1 -0
- package/dist/utils/project-conventions.js +108 -0
- package/dist/utils/project-conventions.js.map +1 -0
- package/dist/utils/regex.d.ts +9 -0
- package/dist/utils/regex.d.ts.map +1 -0
- package/dist/utils/regex.js +11 -0
- package/dist/utils/regex.js.map +1 -0
- package/dist/utils/snippet.d.ts +20 -0
- package/dist/utils/snippet.d.ts.map +1 -0
- package/dist/utils/snippet.js +28 -0
- package/dist/utils/snippet.js.map +1 -0
- package/dist/utils/source-reader.d.ts +19 -0
- package/dist/utils/source-reader.d.ts.map +1 -0
- package/dist/utils/source-reader.js +32 -0
- package/dist/utils/source-reader.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +92 -0
- package/scripts/start-llm.mjs +161 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { UnionFind, bucketBy, representativeHash } from '../utils/clustering.js';
|
|
2
|
+
/** Functions shorter than this many body characters (after collapse) are too small to mean anything. */
|
|
3
|
+
const MIN_BODY_CHARS = 16;
|
|
4
|
+
// Below 0.65 Jaccard a 30%+ different body is a different function in practice.
|
|
5
|
+
const NEAR_DUP_THRESHOLD = 0.65;
|
|
6
|
+
/** Minimum shingle-set size on both sides for near-dup pairing (avoids 1-line bodies). */
|
|
7
|
+
const NEAR_DUP_MIN_SHINGLES = 6;
|
|
8
|
+
const LAYER_PRIORITY = ['strict', 'normalized-names', 'structural', 'near-duplicate'];
|
|
9
|
+
export class DuplicateFunctionDetector {
|
|
10
|
+
id = 'duplicate-function';
|
|
11
|
+
name = 'Duplicate function detector';
|
|
12
|
+
async run(symbols) {
|
|
13
|
+
const fnLike = symbols.filter((s) => s.kind === 'function' && s.structure?.kind === 'function');
|
|
14
|
+
// Skip trivial bodies — a single `return x;` collides across unrelated helpers.
|
|
15
|
+
const nonTrivial = fnLike.filter((s) => {
|
|
16
|
+
const fn = s.structure;
|
|
17
|
+
return fn.bodyNormalized.length >= MIN_BODY_CHARS;
|
|
18
|
+
});
|
|
19
|
+
const strictBuckets = bucketBy(nonTrivial, (s) => s.hashStrict);
|
|
20
|
+
const normalizedBuckets = bucketBy(nonTrivial, (s) => s.hashNormalizedNames);
|
|
21
|
+
const structuralBuckets = bucketBy(nonTrivial, (s) => s.hashStructural);
|
|
22
|
+
const indexOf = new Map(nonTrivial.map((s, i) => [s.id, i]));
|
|
23
|
+
const uf = new UnionFind(nonTrivial.length);
|
|
24
|
+
const edgeLayer = new Map();
|
|
25
|
+
const unionBucket = (bucket, matchedBy) => {
|
|
26
|
+
if (bucket.length < 2)
|
|
27
|
+
return;
|
|
28
|
+
const firstIdx = indexOf.get(bucket[0].id);
|
|
29
|
+
if (firstIdx == null)
|
|
30
|
+
return;
|
|
31
|
+
for (let i = 1; i < bucket.length; i++) {
|
|
32
|
+
const nextIdx = indexOf.get(bucket[i].id);
|
|
33
|
+
if (nextIdx == null)
|
|
34
|
+
continue;
|
|
35
|
+
uf.union(firstIdx, nextIdx);
|
|
36
|
+
}
|
|
37
|
+
for (const s of bucket) {
|
|
38
|
+
const idx = indexOf.get(s.id);
|
|
39
|
+
if (idx == null)
|
|
40
|
+
continue;
|
|
41
|
+
const root = uf.find(idx);
|
|
42
|
+
const set = edgeLayer.get(root) ?? new Set();
|
|
43
|
+
set.add(matchedBy);
|
|
44
|
+
edgeLayer.set(root, set);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
for (const b of strictBuckets.values())
|
|
48
|
+
unionBucket(b, 'strict');
|
|
49
|
+
for (const b of normalizedBuckets.values())
|
|
50
|
+
unionBucket(b, 'normalized-names');
|
|
51
|
+
for (const b of structuralBuckets.values())
|
|
52
|
+
unionBucket(b, 'structural');
|
|
53
|
+
// Layer 4 — pairwise Jaccard on body shingles, GATED on signature compatibility.
|
|
54
|
+
// Picks up "this is almost the same function with one extra line + a few
|
|
55
|
+
// renames" cases the exact-hash layers miss. Two functions with the same
|
|
56
|
+
// body shape but different parameter or return types (e.g. `addNumbers` vs
|
|
57
|
+
// `concatStrings`) must NOT cluster, hence the signature gate.
|
|
58
|
+
// O(N²) but bounded by typeLike size, and the signature filter is cheap.
|
|
59
|
+
const signatures = nonTrivial.map((s) => signatureKey(s.structure));
|
|
60
|
+
for (let i = 0; i < nonTrivial.length; i++) {
|
|
61
|
+
const a = nonTrivial[i].structure;
|
|
62
|
+
const aIdx = indexOf.get(nonTrivial[i].id);
|
|
63
|
+
if (aIdx == null)
|
|
64
|
+
continue;
|
|
65
|
+
if (a.bodyShingles.size < NEAR_DUP_MIN_SHINGLES)
|
|
66
|
+
continue;
|
|
67
|
+
for (let j = i + 1; j < nonTrivial.length; j++) {
|
|
68
|
+
const b = nonTrivial[j].structure;
|
|
69
|
+
const bIdx = indexOf.get(nonTrivial[j].id);
|
|
70
|
+
if (bIdx == null)
|
|
71
|
+
continue;
|
|
72
|
+
if (b.bodyShingles.size < NEAR_DUP_MIN_SHINGLES)
|
|
73
|
+
continue;
|
|
74
|
+
if (signatures[i] !== signatures[j])
|
|
75
|
+
continue; // signature gate
|
|
76
|
+
if (uf.find(aIdx) === uf.find(bIdx))
|
|
77
|
+
continue;
|
|
78
|
+
const sim = jaccard(a.bodyShingles, b.bodyShingles);
|
|
79
|
+
if (sim < NEAR_DUP_THRESHOLD)
|
|
80
|
+
continue;
|
|
81
|
+
uf.union(aIdx, bIdx);
|
|
82
|
+
const root = uf.find(aIdx);
|
|
83
|
+
const set = edgeLayer.get(root) ?? new Set();
|
|
84
|
+
set.add('near-duplicate');
|
|
85
|
+
edgeLayer.set(root, set);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const finalSignals = new Map();
|
|
89
|
+
for (let i = 0; i < nonTrivial.length; i++) {
|
|
90
|
+
const root = uf.find(i);
|
|
91
|
+
const merged = finalSignals.get(root) ?? new Set();
|
|
92
|
+
for (const [k, v] of edgeLayer) {
|
|
93
|
+
if (uf.find(k) === root)
|
|
94
|
+
for (const s of v)
|
|
95
|
+
merged.add(s);
|
|
96
|
+
}
|
|
97
|
+
finalSignals.set(root, merged);
|
|
98
|
+
}
|
|
99
|
+
const componentsByRoot = new Map();
|
|
100
|
+
for (let i = 0; i < nonTrivial.length; i++) {
|
|
101
|
+
const root = uf.find(i);
|
|
102
|
+
const group = componentsByRoot.get(root) ?? [];
|
|
103
|
+
group.push(nonTrivial[i]);
|
|
104
|
+
componentsByRoot.set(root, group);
|
|
105
|
+
}
|
|
106
|
+
const clusters = [];
|
|
107
|
+
for (const [root, group] of componentsByRoot) {
|
|
108
|
+
if (group.length < 2)
|
|
109
|
+
continue;
|
|
110
|
+
const files = new Set(group.map((g) => g.file));
|
|
111
|
+
if (files.size === 1 && group.length === 2)
|
|
112
|
+
continue;
|
|
113
|
+
const signals = finalSignals.get(root) ?? new Set();
|
|
114
|
+
if (signals.size === 0)
|
|
115
|
+
continue;
|
|
116
|
+
const primary = LAYER_PRIORITY.find((p) => signals.has(p)) ?? 'structural';
|
|
117
|
+
clusters.push({
|
|
118
|
+
hash: representativeHash(group, primary, {
|
|
119
|
+
strict: (s) => s.hashStrict,
|
|
120
|
+
'normalized-names': (s) => s.hashNormalizedNames,
|
|
121
|
+
structural: (s) => s.hashStructural,
|
|
122
|
+
'near-duplicate': (s) => s.hashStrict ?? s.hashStructural,
|
|
123
|
+
}),
|
|
124
|
+
symbols: group,
|
|
125
|
+
matchedBy: primary,
|
|
126
|
+
layer: primary === 'near-duplicate' ? 2 : primary === 'normalized-names' ? 2 : 1,
|
|
127
|
+
signals,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return clusters.map((c) => this.toFinding(c));
|
|
131
|
+
}
|
|
132
|
+
toFinding(c) {
|
|
133
|
+
const baseConfidence = c.matchedBy === 'strict'
|
|
134
|
+
? 0.97
|
|
135
|
+
: c.matchedBy === 'structural'
|
|
136
|
+
? 0.82
|
|
137
|
+
: c.matchedBy === 'normalized-names'
|
|
138
|
+
? 0.72
|
|
139
|
+
: 0.68; // near-duplicate
|
|
140
|
+
const distinctNames = new Set(c.symbols.map((s) => s.name)).size;
|
|
141
|
+
const sameName = distinctNames === 1;
|
|
142
|
+
const names = Array.from(new Set(c.symbols.map((s) => s.name))).join(', ');
|
|
143
|
+
const title = sameName
|
|
144
|
+
? `'${c.symbols[0]?.name}' is defined ${c.symbols.length} times`
|
|
145
|
+
: `${c.symbols.length} functions appear equivalent: ${names}`;
|
|
146
|
+
return {
|
|
147
|
+
detectorId: this.id,
|
|
148
|
+
severity: c.symbols.length >= 3 ? 'high' : 'medium',
|
|
149
|
+
confidence: baseConfidence,
|
|
150
|
+
layer: c.layer,
|
|
151
|
+
title,
|
|
152
|
+
description: this.buildDescription(c),
|
|
153
|
+
evidence: c.symbols.map((s) => ({
|
|
154
|
+
file: s.file,
|
|
155
|
+
range: s.range,
|
|
156
|
+
snippet: s.source,
|
|
157
|
+
})),
|
|
158
|
+
suggestion: 'Consolidate into a single shared definition (e.g., a shared utils module) and import from one place.',
|
|
159
|
+
fingerprint: `dup-fn:${c.matchedBy}:${c.hash}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
buildDescription(c) {
|
|
163
|
+
const where = c.symbols
|
|
164
|
+
.map((s) => `- ${s.file}:${s.range.startLine} (function ${s.name})`)
|
|
165
|
+
.join('\n');
|
|
166
|
+
const matchedByLabel = {
|
|
167
|
+
strict: 'Same signature + same body (verbatim after whitespace/comment collapse)',
|
|
168
|
+
structural: 'Same skeleton — identifiers anonymised, signature shape and body shape match',
|
|
169
|
+
'normalized-names': 'Same signature + body after parameter-name normalisation (snake/camel + synonyms)',
|
|
170
|
+
'near-duplicate': `Near-duplicate body (Jaccard ≥ ${NEAR_DUP_THRESHOLD} on 4-token shingles)`,
|
|
171
|
+
};
|
|
172
|
+
const extra = c.signals.size > 1
|
|
173
|
+
? `\nAlso matches at: ${Array.from(c.signals).filter((s) => s !== c.matchedBy).join(', ')}.`
|
|
174
|
+
: '';
|
|
175
|
+
return `Match level: ${matchedByLabel[c.matchedBy]} (layer ${c.layer}).${extra}\nLocations:\n${where}`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stable signature string used as the Layer-4 compatibility gate. Two functions
|
|
180
|
+
* cluster as near-duplicates only when their async/generator flags + parameter
|
|
181
|
+
* types (in order) + return type all match — modulo whitespace normalisation.
|
|
182
|
+
*/
|
|
183
|
+
function signatureKey(fn) {
|
|
184
|
+
const flags = `${fn.async ? 'a' : ''}${fn.generator ? 'g' : ''}`;
|
|
185
|
+
const params = fn.params.map((p) => p.type.replace(/\s+/g, '')).join(',');
|
|
186
|
+
return `${flags}(${params})=>${fn.returnType.replace(/\s+/g, '')}`;
|
|
187
|
+
}
|
|
188
|
+
function jaccard(a, b) {
|
|
189
|
+
if (a.size === 0 || b.size === 0)
|
|
190
|
+
return 0;
|
|
191
|
+
let intersect = 0;
|
|
192
|
+
const [small, large] = a.size <= b.size ? [a, b] : [b, a];
|
|
193
|
+
for (const x of small)
|
|
194
|
+
if (large.has(x))
|
|
195
|
+
intersect++;
|
|
196
|
+
const union = a.size + b.size - intersect;
|
|
197
|
+
return union === 0 ? 0 : intersect / union;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=duplicate-function.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-function.js","sourceRoot":"","sources":["../../src/detectors/duplicate-function.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Detector, Finding, SymbolRecord } from '../types.js';
|
|
2
|
+
export declare class DuplicateTypeDetector implements Detector {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
run(symbols: SymbolRecord[]): Promise<Finding[]>;
|
|
6
|
+
private toFinding;
|
|
7
|
+
private buildDescription;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=duplicate-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-type.d.ts","sourceRoot":"","sources":["../../src/detectors/duplicate-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBnF,qBAAa,qBAAsB,YAAW,QAAQ;IACpD,EAAE,SAAoB;IACtB,IAAI,SAA6B;IAE3B,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAoGtD,OAAO,CAAC,SAAS;IA6BjB,OAAO,CAAC,gBAAgB;CAczB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { UnionFind, bucketBy, representativeHash } from '../utils/clustering.js';
|
|
2
|
+
/** Minimum field count for a structural-only match to be considered non-trivial. */
|
|
3
|
+
const STRUCTURAL_MIN_FIELDS = 4;
|
|
4
|
+
/** Ranking: highest-precision layer wins when multiple fire on the same cluster. */
|
|
5
|
+
const LAYER_PRIORITY = ['strict', 'normalized-names', 'structural'];
|
|
6
|
+
export class DuplicateTypeDetector {
|
|
7
|
+
id = 'duplicate-type';
|
|
8
|
+
name = 'Duplicate type detector';
|
|
9
|
+
async run(symbols) {
|
|
10
|
+
const typeLike = symbols.filter((s) => (s.kind === 'interface' || s.kind === 'type-alias') &&
|
|
11
|
+
s.structure?.kind === 'object' &&
|
|
12
|
+
(s.structure.fields?.length ?? 0) > 0);
|
|
13
|
+
// Compute buckets per layer, sharing every typeLike symbol across all layers.
|
|
14
|
+
// Old behavior excluded a symbol from later layers once its first layer fired
|
|
15
|
+
// — that meant a 3-way cluster like Product/ProductDTO/Item lost Item, since
|
|
16
|
+
// Product and ProductDTO bound at strict (layer 1a) and the structural layer
|
|
17
|
+
// then ran on `remaining`, leaving Item alone.
|
|
18
|
+
const strictBuckets = bucketBy(typeLike, (s) => s.hashStrict);
|
|
19
|
+
const normalizedBuckets = bucketBy(typeLike, (s) => s.hashNormalizedNames);
|
|
20
|
+
const structuralBuckets = bucketBy(typeLike.filter((s) => !isStructurallyTrivial(s)), (s) => s.hashStructural);
|
|
21
|
+
// Union-find — two symbols belong to the same cluster if they collide on
|
|
22
|
+
// any of the three hashes.
|
|
23
|
+
const indexOf = new Map(typeLike.map((s, i) => [s.id, i]));
|
|
24
|
+
const uf = new UnionFind(typeLike.length);
|
|
25
|
+
const edgeLayer = new Map(); // root index → layers that touched
|
|
26
|
+
const unionBucket = (bucket, matchedBy) => {
|
|
27
|
+
if (bucket.length < 2)
|
|
28
|
+
return;
|
|
29
|
+
const first = indexOf.get(bucket[0].id);
|
|
30
|
+
if (first == null)
|
|
31
|
+
return;
|
|
32
|
+
for (let i = 1; i < bucket.length; i++) {
|
|
33
|
+
const next = indexOf.get(bucket[i].id);
|
|
34
|
+
if (next == null)
|
|
35
|
+
continue;
|
|
36
|
+
uf.union(first, next);
|
|
37
|
+
}
|
|
38
|
+
// Tag every member with the layer signal.
|
|
39
|
+
for (const s of bucket) {
|
|
40
|
+
const idx = indexOf.get(s.id);
|
|
41
|
+
if (idx == null)
|
|
42
|
+
continue;
|
|
43
|
+
const root = uf.find(idx);
|
|
44
|
+
const set = edgeLayer.get(root) ?? new Set();
|
|
45
|
+
set.add(matchedBy);
|
|
46
|
+
edgeLayer.set(root, set);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
for (const b of strictBuckets.values())
|
|
50
|
+
unionBucket(b, 'strict');
|
|
51
|
+
for (const b of normalizedBuckets.values())
|
|
52
|
+
unionBucket(b, 'normalized-names');
|
|
53
|
+
for (const b of structuralBuckets.values())
|
|
54
|
+
unionBucket(b, 'structural');
|
|
55
|
+
// Re-aggregate edgeLayer keyed by the final canonical root after all unions.
|
|
56
|
+
const finalSignals = new Map();
|
|
57
|
+
for (let i = 0; i < typeLike.length; i++) {
|
|
58
|
+
const root = uf.find(i);
|
|
59
|
+
const merged = finalSignals.get(root) ?? new Set();
|
|
60
|
+
const seenAtThis = edgeLayer.get(uf.find(i));
|
|
61
|
+
if (seenAtThis)
|
|
62
|
+
for (const s of seenAtThis)
|
|
63
|
+
merged.add(s);
|
|
64
|
+
// Also pick up signals tagged on intermediate roots that ended up under `root`.
|
|
65
|
+
for (const [k, v] of edgeLayer) {
|
|
66
|
+
if (uf.find(k) === root)
|
|
67
|
+
for (const s of v)
|
|
68
|
+
merged.add(s);
|
|
69
|
+
}
|
|
70
|
+
finalSignals.set(root, merged);
|
|
71
|
+
}
|
|
72
|
+
// Group symbols by root.
|
|
73
|
+
const componentsByRoot = new Map();
|
|
74
|
+
for (let i = 0; i < typeLike.length; i++) {
|
|
75
|
+
const root = uf.find(i);
|
|
76
|
+
const group = componentsByRoot.get(root) ?? [];
|
|
77
|
+
group.push(typeLike[i]);
|
|
78
|
+
componentsByRoot.set(root, group);
|
|
79
|
+
}
|
|
80
|
+
const clusters = [];
|
|
81
|
+
for (const [root, group] of componentsByRoot) {
|
|
82
|
+
if (group.length < 2)
|
|
83
|
+
continue;
|
|
84
|
+
// Same-file size-2 skip — both members in the same file is almost always intentional.
|
|
85
|
+
const files = new Set(group.map((g) => g.file));
|
|
86
|
+
if (files.size === 1 && group.length === 2)
|
|
87
|
+
continue;
|
|
88
|
+
const signals = finalSignals.get(root) ?? new Set();
|
|
89
|
+
if (signals.size === 0)
|
|
90
|
+
continue; // shouldn't happen but be safe
|
|
91
|
+
const primary = LAYER_PRIORITY.find((p) => signals.has(p)) ?? 'structural';
|
|
92
|
+
clusters.push({
|
|
93
|
+
hash: representativeHash(group, primary, {
|
|
94
|
+
strict: (s) => s.hashStrict,
|
|
95
|
+
'normalized-names': (s) => s.hashNormalizedNames,
|
|
96
|
+
structural: (s) => s.hashStructural,
|
|
97
|
+
}),
|
|
98
|
+
symbols: group,
|
|
99
|
+
matchedBy: primary,
|
|
100
|
+
layer: primary === 'normalized-names' ? 2 : 1,
|
|
101
|
+
signals,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return clusters.map((c) => this.toFinding(c));
|
|
105
|
+
}
|
|
106
|
+
toFinding(c) {
|
|
107
|
+
const baseConfidence = c.matchedBy === 'strict' ? 1.0 : c.matchedBy === 'structural' ? 0.85 : 0.75;
|
|
108
|
+
const distinctNames = new Set(c.symbols.map((s) => s.name)).size;
|
|
109
|
+
const sameName = distinctNames === 1;
|
|
110
|
+
const names = Array.from(new Set(c.symbols.map((s) => s.name))).join(', ');
|
|
111
|
+
const title = sameName
|
|
112
|
+
? `'${c.symbols[0]?.name}' is defined ${c.symbols.length} times`
|
|
113
|
+
: `${c.symbols.length} types appear equivalent: ${names}`;
|
|
114
|
+
return {
|
|
115
|
+
detectorId: this.id,
|
|
116
|
+
severity: c.symbols.length >= 3 ? 'high' : 'medium',
|
|
117
|
+
confidence: baseConfidence,
|
|
118
|
+
layer: c.layer,
|
|
119
|
+
title,
|
|
120
|
+
description: this.buildDescription(c),
|
|
121
|
+
evidence: c.symbols.map((s) => ({
|
|
122
|
+
file: s.file,
|
|
123
|
+
range: s.range,
|
|
124
|
+
snippet: s.source,
|
|
125
|
+
})),
|
|
126
|
+
suggestion: 'Consolidate into a single shared definition (e.g., a shared types package) and import from one place.',
|
|
127
|
+
fingerprint: `dup-type:${c.matchedBy}:${c.hash}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
buildDescription(c) {
|
|
131
|
+
const where = c.symbols
|
|
132
|
+
.map((s) => `- ${s.file}:${s.range.startLine} (${s.kind} ${s.name})`)
|
|
133
|
+
.join('\n');
|
|
134
|
+
const matchedByLabel = {
|
|
135
|
+
strict: 'Same fields, same names, same types',
|
|
136
|
+
structural: 'Same field types, different names (anonymous structural match)',
|
|
137
|
+
'normalized-names': 'Same fields after normalizing naming convention (snake/camel + synonyms)',
|
|
138
|
+
};
|
|
139
|
+
const extra = c.signals.size > 1
|
|
140
|
+
? `\nAlso matches at: ${Array.from(c.signals).filter((s) => s !== c.matchedBy).join(', ')}.`
|
|
141
|
+
: '';
|
|
142
|
+
return `Match level: ${matchedByLabel[c.matchedBy]} (layer ${c.layer}).${extra}\nLocations:\n${where}`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function isStructurallyTrivial(s) {
|
|
146
|
+
const struct = s.structure;
|
|
147
|
+
const fields = struct && 'fields' in struct ? struct.fields ?? [] : [];
|
|
148
|
+
const kinds = new Set(fields.map(primitiveKindOf));
|
|
149
|
+
// A complex field (method / object / array / union with non-primitive) is a
|
|
150
|
+
// strong signal that the shape is meaningful. Allow it past the trivial gate
|
|
151
|
+
// with ≥3 fields instead of the usual 4-field minimum.
|
|
152
|
+
if (kinds.has('complex') && fields.length >= 3)
|
|
153
|
+
return false;
|
|
154
|
+
if (fields.length < STRUCTURAL_MIN_FIELDS)
|
|
155
|
+
return true;
|
|
156
|
+
return kinds.size < 2;
|
|
157
|
+
}
|
|
158
|
+
function primitiveKindOf(f) {
|
|
159
|
+
const t = f.type.replace(/\s+/g, '').toLowerCase();
|
|
160
|
+
if (/^(string|number|boolean|bigint|symbol|null|undefined|date)(\|.*)?$/.test(t)) {
|
|
161
|
+
const head = t.split('|')[0];
|
|
162
|
+
return head;
|
|
163
|
+
}
|
|
164
|
+
return 'complex';
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=duplicate-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-type.js","sourceRoot":"","sources":["../../src/detectors/duplicate-type.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImportGraph } from '../graph/import-graph.js';
|
|
2
|
+
import type { Finding } from '../types.js';
|
|
3
|
+
export interface HotHubFileDetectorInput {
|
|
4
|
+
graph: ImportGraph;
|
|
5
|
+
/** Threshold for incoming-import count. Default 20. */
|
|
6
|
+
threshold?: number;
|
|
7
|
+
/** Max findings to emit. Default 10 (this is an INFO signal, not a buglist). */
|
|
8
|
+
maxFindings?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function detectHotHubFiles(input: HotHubFileDetectorInput): Finding[];
|
|
11
|
+
//# sourceMappingURL=hot-hub-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hot-hub-file.d.ts","sourceRoot":"","sources":["../../src/detectors/hot-hub-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,EAAE,CA2B3E"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { stableHash } from '../utils/hash.js';
|
|
2
|
+
// Files imported by >threshold others — refactor blast-radius signal. INFO.
|
|
3
|
+
//
|
|
4
|
+
// TODO(under-implemented): minimal detector compared to peers. Open items:
|
|
5
|
+
// 1. Threshold tuning — current 20 is a static guess. Should scale with
|
|
6
|
+
// workspace size (e.g. p95 of incoming-import distribution + bonus
|
|
7
|
+
// for files with no outgoing imports → pure leaf hubs are worst).
|
|
8
|
+
// 2. Severity scaling — currently always 'low'. Bump to 'medium' at
|
|
9
|
+
// 2x threshold, 'high' at 4x (a barrel re-export pulled by 100 files
|
|
10
|
+
// is a much bigger blast radius than one pulled by 21).
|
|
11
|
+
// 3. Suggestion enrichment — detect barrel-export shape (file body is
|
|
12
|
+
// mostly `export { … } from './x'`) and tailor the suggestion
|
|
13
|
+
// (delete the barrel) vs utility-module shape (suggest splitting).
|
|
14
|
+
// 4. Per-workspace blast-radius — in multi-workspace mode, weight by
|
|
15
|
+
// how many workspaces import the file, not just file count.
|
|
16
|
+
export function detectHotHubFiles(input) {
|
|
17
|
+
const threshold = input.threshold ?? 20;
|
|
18
|
+
const maxFindings = input.maxFindings ?? 10;
|
|
19
|
+
const ranked = [...input.graph.incoming.entries()]
|
|
20
|
+
.map(([file, callers]) => ({ file, count: callers.size }))
|
|
21
|
+
.filter((x) => x.count >= threshold)
|
|
22
|
+
.sort((a, b) => b.count - a.count)
|
|
23
|
+
.slice(0, maxFindings);
|
|
24
|
+
return ranked.map(({ file, count }) => ({
|
|
25
|
+
detectorId: 'hot-hub-file',
|
|
26
|
+
severity: 'low',
|
|
27
|
+
confidence: 1,
|
|
28
|
+
layer: 1,
|
|
29
|
+
title: `Import hub: ${file} (${count} importers)`,
|
|
30
|
+
description: `\`${file}\` is imported by ${count} other files. Hubs concentrate change-blast-radius — every refactor of this file ripples across the whole workspace.`,
|
|
31
|
+
evidence: [
|
|
32
|
+
{
|
|
33
|
+
file,
|
|
34
|
+
range: { startLine: 1, endLine: 1 },
|
|
35
|
+
snippet: `// ${file} — imported by ${count} files`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
suggestion: 'If this is a barrel re-export, consider deleting it and asking importers to use the original module — barrels confuse tree-shaking and slow incremental builds. If it is a utility module, split it along its natural responsibilities.',
|
|
39
|
+
fingerprint: `hot-hub-file:${stableHash(file)}`,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=hot-hub-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hot-hub-file.js","sourceRoot":"","sources":["../../src/detectors/hot-hub-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAU9C,4EAA4E;AAC5E,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,0EAA0E;AAC1E,6DAA6D;AAC7D,wEAAwE;AACxE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,KAA8B;IAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC;IACxC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;SAC/C,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;SACzD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC;SACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACtC,UAAU,EAAE,cAAc;QAC1B,QAAQ,EAAE,KAAK;QACf,UAAU,EAAE,CAAC;QACb,KAAK,EAAE,CAAC;QACR,KAAK,EAAE,eAAe,IAAI,KAAK,KAAK,aAAa;QACjD,WAAW,EACT,KAAK,IAAI,qBAAqB,KAAK,sHAAsH;QAC3J,QAAQ,EAAE;YACR;gBACE,IAAI;gBACJ,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;gBACnC,OAAO,EAAE,MAAM,IAAI,kBAAkB,KAAK,QAAQ;aACnD;SACF;QACD,UAAU,EACR,yOAAyO;QAC3O,WAAW,EAAE,gBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE;KAChD,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
import type { FileWalkingDetectorInput } from '../types/detector-input.js';
|
|
3
|
+
export interface LongFileDetectorInput extends FileWalkingDetectorInput {
|
|
4
|
+
/** LOW threshold in non-blank/non-comment lines. Default 400. */
|
|
5
|
+
lowThreshold?: number;
|
|
6
|
+
/** MED threshold. Default 700. */
|
|
7
|
+
medThreshold?: number;
|
|
8
|
+
/** HIGH threshold. Default 1200. */
|
|
9
|
+
highThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectLongFiles(input: LongFileDetectorInput): Finding[];
|
|
12
|
+
//# sourceMappingURL=long-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-file.d.ts","sourceRoot":"","sources":["../../src/detectors/long-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE3E,MAAM,WAAW,qBAAsB,SAAQ,wBAAwB;IACvE,iEAAiE;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,EAAE,CAmCvE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { makeSourceReader } from '../utils/source-reader.js';
|
|
2
|
+
import { stableHash } from '../utils/hash.js';
|
|
3
|
+
// Effective LOC (non-blank, non-pure-comment) over 400 (low) / 700 (med) /
|
|
4
|
+
// 1200 (high). Generated + .d.ts excluded.
|
|
5
|
+
export function detectLongFiles(input) {
|
|
6
|
+
const low = input.lowThreshold ?? 400;
|
|
7
|
+
const med = input.medThreshold ?? 700;
|
|
8
|
+
const high = input.highThreshold ?? 1200;
|
|
9
|
+
const read = makeSourceReader(input.workspaceRoot, input.project);
|
|
10
|
+
const findings = [];
|
|
11
|
+
for (const rel of input.files) {
|
|
12
|
+
if (!isAnalysable(rel))
|
|
13
|
+
continue;
|
|
14
|
+
const raw = read(rel);
|
|
15
|
+
if (raw == null)
|
|
16
|
+
continue;
|
|
17
|
+
const loc = countEffectiveLines(raw);
|
|
18
|
+
if (loc < low)
|
|
19
|
+
continue;
|
|
20
|
+
const severity = loc >= high ? 'high' : loc >= med ? 'medium' : 'low';
|
|
21
|
+
findings.push({
|
|
22
|
+
detectorId: 'long-file',
|
|
23
|
+
severity,
|
|
24
|
+
confidence: 0.99,
|
|
25
|
+
layer: 1,
|
|
26
|
+
title: `Long file: ${rel} (${loc} LOC)`,
|
|
27
|
+
description: `${loc} non-blank lines of code (excluding pure-comment lines). Files this size are hard to navigate, hard to test, and accumulate unrelated concerns. Common root cause: feature-by-feature additions without periodic refactoring.`,
|
|
28
|
+
evidence: [
|
|
29
|
+
{
|
|
30
|
+
file: rel,
|
|
31
|
+
range: { startLine: 1, endLine: Math.min(raw.split('\n').length, loc) },
|
|
32
|
+
snippet: `// ${rel} — ${loc} effective lines of code`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
suggestion: 'Split the file along its natural concerns (e.g. one class per file, separate I/O from logic, extract pure helpers to a sibling `*-utils.ts`). Use the symbol-graph view to spot internal clusters that are good split candidates.',
|
|
36
|
+
fingerprint: `long-file:${stableHash(rel)}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return findings;
|
|
40
|
+
}
|
|
41
|
+
function countEffectiveLines(raw) {
|
|
42
|
+
let count = 0;
|
|
43
|
+
let inBlockComment = false;
|
|
44
|
+
for (const line of raw.split('\n')) {
|
|
45
|
+
let t = line.trim();
|
|
46
|
+
if (inBlockComment) {
|
|
47
|
+
const endIdx = t.indexOf('*/');
|
|
48
|
+
if (endIdx === -1)
|
|
49
|
+
continue;
|
|
50
|
+
inBlockComment = false;
|
|
51
|
+
t = t.slice(endIdx + 2).trim();
|
|
52
|
+
}
|
|
53
|
+
if (t === '')
|
|
54
|
+
continue;
|
|
55
|
+
// Strip leading block-comment that ends on same line.
|
|
56
|
+
while (t.startsWith('/*')) {
|
|
57
|
+
const endIdx = t.indexOf('*/');
|
|
58
|
+
if (endIdx === -1) {
|
|
59
|
+
inBlockComment = true;
|
|
60
|
+
t = '';
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
t = t.slice(endIdx + 2).trim();
|
|
64
|
+
}
|
|
65
|
+
if (t === '')
|
|
66
|
+
continue;
|
|
67
|
+
if (t.startsWith('//'))
|
|
68
|
+
continue;
|
|
69
|
+
if (t.startsWith('*'))
|
|
70
|
+
continue; // continuation of JSDoc
|
|
71
|
+
count += 1;
|
|
72
|
+
}
|
|
73
|
+
return count;
|
|
74
|
+
}
|
|
75
|
+
function isAnalysable(file) {
|
|
76
|
+
return /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(file)
|
|
77
|
+
&& !/\.d\.ts$/.test(file)
|
|
78
|
+
&& !/(^|\/)node_modules\//.test(file)
|
|
79
|
+
&& !/(^|\/)dist\//.test(file)
|
|
80
|
+
&& !/\.generated\.(?:ts|js)$/.test(file);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=long-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-file.js","sourceRoot":"","sources":["../../src/detectors/long-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding, SymbolRecord } from '../types.js';
|
|
2
|
+
export interface LongFunctionDetectorInput {
|
|
3
|
+
symbols: ReadonlyArray<SymbolRecord>;
|
|
4
|
+
/** LOW threshold. Default 60 source lines. */
|
|
5
|
+
lowThreshold?: number;
|
|
6
|
+
/** MED threshold. Default 120 source lines. */
|
|
7
|
+
medThreshold?: number;
|
|
8
|
+
/** HIGH threshold. Default 200 source lines. */
|
|
9
|
+
highThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectLongFunctions(input: LongFunctionDetectorInput): Finding[];
|
|
12
|
+
//# sourceMappingURL=long-function.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-function.d.ts","sourceRoot":"","sources":["../../src/detectors/long-function.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,EAAE,CAiC/E"}
|