MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / findSimilarHelpers

Function findSimilarHelpers

src/codegraph/helper-extract.ts:94–179  ·  view source on GitHub ↗
(
  units: FunctionUnit[],
  opts: { minSim?: number; maxSim?: number; minTokens?: number } = {},
)

Source from the content-addressed store, hash-verified

92 * collapses a whole codebase of vaguely-alike boilerplate into one meaningless mega-cluster.
93 */
94export function findSimilarHelpers(
95 units: FunctionUnit[],
96 opts: { minSim?: number; maxSim?: number; minTokens?: number } = {},
97): HelperCluster[] {
98 const minSim = opts.minSim ?? 0.88;
99 const maxSim = opts.maxSim ?? 1.0;
100 const minTokens = opts.minTokens ?? 40;
101
102 const base = units
103 .map(u => {
104 const norm = normalizeBody(u.body, u.name);
105 const tokens = codeTokens(norm);
106 return { u, norm, tf: termFreq(tokens), tok: tokens.length };
107 })
108 .filter(x => x.tok >= minTokens);
109
110 // TF-IDF: weight each token by how DISTINCTIVE it is across the corpus. Ubiquitous structural
111 // tokens (`.`, `(`, `const`, `=`) appear in nearly every function → idf≈0 → they stop dominating,
112 // so only genuinely-shared logic (specific identifiers + call sequences) drives similarity. Without
113 // this, every large function looks ~85% like every other and the whole thing over-clusters.
114 const df = new Map<string, number>();
115 for (const b of base) for (const t of b.tf.keys()) df.set(t, (df.get(t) ?? 0) + 1);
116 const N = base.length;
117 const idf = (t: string): number => Math.log((N + 1) / ((df.get(t) ?? 0) + 1)); // smoothed; token in all docs → ~0
118 const weight = (tf: Map<string, number>): Map<string, number> => {
119 const w = new Map<string, number>();
120 for (const [t, f] of tf) { const iw = idf(t); if (iw > 0) w.set(t, f * iw); }
121 return w;
122 };
123
124 const prepared = base
125 // Weighted vector; fall back to raw tf if weighting empties it (degenerate all-tokens-universal case).
126 .map(x => { const w = weight(x.tf); return { ...x, tf: w.size > 0 ? w : x.tf }; })
127 // Larger functions first, so the biggest is the seed a cluster forms around (stable + meaningful).
128 .sort((a, b) => b.tok - a.tok);
129
130 // Seed-based clustering: each function joins the seed it's MOST similar to (≥ minSim), else seeds a new cluster.
131 const seeded: { seed: number; members: number[] }[] = [];
132 for (let i = 0; i < prepared.length; i++) {
133 let best = -1, bestSim = -1;
134 for (let c = 0; c < seeded.length; c++) {
135 const s = cosineSim(prepared[i]!.tf, prepared[seeded[c]!.seed]!.tf);
136 if (s >= minSim && s <= maxSim && s > bestSim) { bestSim = s; best = c; }
137 }
138 if (best >= 0) seeded[best]!.members.push(i);
139 else seeded.push({ seed: i, members: [i] });
140 }
141
142 const clusters: HelperCluster[] = [];
143 for (const { members: rawIdxs } of seeded) {
144 // Drop members NESTED inside another member (same file, contained line range): a inner helper
145 // shares most of its parent's tokens, so parent+child cluster together — but "extract the child
146 // from its parent" is not a duplicate-consolidation opportunity. Keep the outermost.
147 const idxs = rawIdxs.filter(i => {
148 const a = prepared[i]!.u;
149 return !rawIdxs.some(j => {
150 if (j === i) return false;
151 const b = prepared[j]!.u;

Callers 2

executeMethod · 0.85

Calls 10

normalizeBodyFunction · 0.85
codeTokensFunction · 0.85
termFreqFunction · 0.85
weightFunction · 0.85
suggestNameFunction · 0.85
roundFunction · 0.85
cosineSimFunction · 0.50
setMethod · 0.45
getMethod · 0.45
pushMethod · 0.45

Tested by

no test coverage detected