@davesheffer/hunch 1.18.1 → 1.19.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.
@@ -0,0 +1,321 @@
1
+ import { createHash } from "node:crypto";
2
+ export const FILE_FIRST_DECLARATION_CLUSTER_RULE = "flat-file-anchored-semantic-clusters-v3";
3
+ export const FILE_FIRST_FILE_LIMIT = 5;
4
+ export const FILE_FIRST_CLUSTER_LIMIT = 2;
5
+ export const FILE_FIRST_MEMBER_LIMIT = 3;
6
+ export const PROGRESSIVE_DECLARATION_PLAN_RULE = "promoted-only-progressive-inspection-v4";
7
+ export const PROGRESSIVE_DECLARATION_BASELINE_LIMIT = 5;
8
+ export const PROGRESSIVE_DECLARATION_PRIMARY_LIMIT = 10;
9
+ export const PROGRESSIVE_DECLARATION_TOTAL_LIMIT = 11;
10
+ export const FILE_FIRST_DECLARATION_CLUSTER_TRANSFER = {
11
+ benchmark: "flat-file-anchored-clusters-transfer-v3",
12
+ scorable_tasks: 12,
13
+ baseline_top_five_hits: 3,
14
+ combined_hits: 6,
15
+ combined_improvement_points: 0.25,
16
+ baseline_file_hits: 8,
17
+ cluster_file_hits: 10,
18
+ rescues: 3,
19
+ average_inspected_declarations: 18.83,
20
+ max_inspected_declarations: 24,
21
+ decision: "promoted-supplemental-diagnostic",
22
+ exact_owner_policy: "disabled",
23
+ };
24
+ export const PROGRESSIVE_DECLARATION_DEVELOPMENT = {
25
+ benchmark: "revealed-cluster-transfer-v1-v2-v3-replay",
26
+ scorable_tasks: 36,
27
+ baseline_top_five_hits: 15,
28
+ progressive_plan_hits: 21,
29
+ full_cluster_union_hits: 21,
30
+ previous_average_inspected_declarations: 19.81,
31
+ progressive_max_inspected_declarations: 11,
32
+ inspection_reduction: 0.4446,
33
+ decision: "candidate-awaiting-fresh-transfer",
34
+ exact_owner_policy: "disabled",
35
+ };
36
+ export const PROGRESSIVE_DECLARATION_TRANSFER = {
37
+ benchmark: "progressive-inspection-cross-repository-transfer-v4",
38
+ scorable_tasks: 12,
39
+ baseline_top_five_hits: 5,
40
+ progressive_plan_hits: 5,
41
+ full_cluster_union_hits: 5,
42
+ rescues: 0,
43
+ losses: 0,
44
+ average_inspected_declarations: 11,
45
+ previous_average_inspected_declarations: 18.92,
46
+ inspection_reduction: 0.4185,
47
+ decision: "retain-efficiency-advisory-v4",
48
+ accuracy_promotion: "rejected-no-fresh-rescue",
49
+ exact_owner_policy: "disabled",
50
+ };
51
+ const SEMANTIC_STOP_WORDS = new Set([
52
+ "src", "source", "lib", "library", "package", "packages", "core", "index",
53
+ "schema", "schemas", "type", "types", "zod", "mini", "internals", "internal",
54
+ "params", "param", "options", "option", "context", "config", "props", "def",
55
+ "input", "output", "result", "results", "handle", "handler", "get", "set",
56
+ "make", "create", "build", "process", "processor", "run", "apply", "with",
57
+ "from", "into", "this", "that", "value", "values",
58
+ ]);
59
+ function ownerPath(owner) {
60
+ return owner.slice(0, owner.indexOf("::"));
61
+ }
62
+ function ownerSymbol(owner) {
63
+ return owner.slice(owner.indexOf("::") + 2);
64
+ }
65
+ function semanticTerms(symbol) {
66
+ return [...new Set(symbol
67
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
68
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1 $2")
69
+ .toLowerCase()
70
+ .split(/[^a-z0-9]+/)
71
+ .filter((term) => term.length >= 3 && !SEMANTIC_STOP_WORDS.has(term)))];
72
+ }
73
+ function fallbackTerm(symbol) {
74
+ return symbol.replace(/[^A-Za-z0-9]+/g, "").toLowerCase() || "anonymous";
75
+ }
76
+ function overlap(left, right) {
77
+ const rightSet = new Set(right);
78
+ return left.filter((term) => rightSet.has(term)).length;
79
+ }
80
+ function shortHash(value) {
81
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 24);
82
+ }
83
+ function clusterFileDeclarations(path, candidates, clusterLimit, memberLimit) {
84
+ const clusters = [];
85
+ for (const item of candidates) {
86
+ const symbol = ownerSymbol(item.candidate.owner);
87
+ const terms = semanticTerms(symbol);
88
+ const effectiveTerms = terms.length ? terms : [fallbackTerm(symbol)];
89
+ const matching = clusters.map((cluster, index) => ({ index, overlap: overlap(effectiveTerms, cluster.terms) }))
90
+ .filter((entry) => entry.overlap > 0)
91
+ .sort((left, right) => right.overlap - left.overlap || left.index - right.index)[0];
92
+ const member = {
93
+ owner: item.candidate.owner,
94
+ global_rank: item.globalRank,
95
+ static_score: item.candidate.score,
96
+ runtime_declaration: item.candidate.runtime_declaration,
97
+ type_scaffolding: item.candidate.type_scaffolding,
98
+ };
99
+ if (matching) {
100
+ const cluster = clusters[matching.index];
101
+ cluster.terms = [...new Set([...cluster.terms, ...effectiveTerms])].sort();
102
+ cluster.members.push(member);
103
+ }
104
+ else {
105
+ clusters.push({ terms: effectiveTerms, label: effectiveTerms.join(" / "), members: [member] });
106
+ }
107
+ }
108
+ return clusters.slice(0, clusterLimit).map((cluster) => {
109
+ const members = cluster.members.slice(0, memberLimit);
110
+ const representative = members[0];
111
+ const value = {
112
+ path,
113
+ terms: cluster.terms,
114
+ representative: representative.owner,
115
+ members: members.map((member) => member.owner),
116
+ };
117
+ return {
118
+ cluster_id: shortHash(value),
119
+ label: cluster.label,
120
+ semantic_terms: cluster.terms,
121
+ representative: representative.owner,
122
+ representative_global_rank: representative.global_rank,
123
+ members,
124
+ members_truncated: Math.max(0, cluster.members.length - members.length),
125
+ };
126
+ });
127
+ }
128
+ /** Build a hierarchical diagnostic from the existing static ranking. The flat
129
+ * shortlist remains untouched: this view collapses related declaration
130
+ * scaffolding into semantic families so one noisy family cannot consume every
131
+ * inspection slot inside a promising file. It is still a candidate view, not
132
+ * an exact-owner claim. */
133
+ export function buildFileFirstDeclarationClusters(rankedCandidatesValue, requestedFileLimit = FILE_FIRST_FILE_LIMIT, requestedClusterLimit = FILE_FIRST_CLUSTER_LIMIT, requestedMemberLimit = FILE_FIRST_MEMBER_LIMIT) {
134
+ const fileLimit = Number.isSafeInteger(requestedFileLimit)
135
+ ? Math.max(1, Math.min(FILE_FIRST_FILE_LIMIT, requestedFileLimit))
136
+ : FILE_FIRST_FILE_LIMIT;
137
+ const clusterLimit = Number.isSafeInteger(requestedClusterLimit)
138
+ ? Math.max(1, Math.min(FILE_FIRST_CLUSTER_LIMIT, requestedClusterLimit))
139
+ : FILE_FIRST_CLUSTER_LIMIT;
140
+ const memberLimit = Number.isSafeInteger(requestedMemberLimit)
141
+ ? Math.max(1, Math.min(FILE_FIRST_MEMBER_LIMIT, requestedMemberLimit))
142
+ : FILE_FIRST_MEMBER_LIMIT;
143
+ const distinctCandidates = rankedCandidatesValue.filter((candidate, index, all) => all.findIndex((entry) => entry.owner === candidate.owner) === index);
144
+ const byFile = new Map();
145
+ distinctCandidates.forEach((candidate, index) => {
146
+ const path = ownerPath(candidate.owner);
147
+ const entries = byFile.get(path) ?? [];
148
+ entries.push({ candidate, globalRank: index + 1 });
149
+ byFile.set(path, entries);
150
+ });
151
+ const prepared = [...byFile].map(([path, entries]) => {
152
+ const allClusters = clusterFileDeclarations(path, entries, Number.MAX_SAFE_INTEGER, memberLimit);
153
+ const scoringClusters = allClusters.slice(0, clusterLimit);
154
+ return {
155
+ path,
156
+ firstCandidateRank: entries[0].globalRank,
157
+ fileScore: Math.round(scoringClusters.reduce((sum, cluster) => sum + (cluster.members[0]?.static_score ?? 0), 0) * 100) / 100,
158
+ clusters: scoringClusters,
159
+ };
160
+ }).sort((left, right) => right.fileScore - left.fileScore
161
+ || left.firstCandidateRank - right.firstCandidateRank
162
+ || left.path.localeCompare(right.path));
163
+ // The first design re-ranked files by aggregate family score and lost a file
164
+ // already represented by the working flat shortlist on fresh transfer. V2
165
+ // makes the hierarchy supplemental in fact as well as name: preserve every
166
+ // distinct file from the flat top five first, then use aggregate scoring only
167
+ // to fill any remaining file slots.
168
+ const flatFileAnchors = [...new Set(distinctCandidates.slice(0, 5).map((candidate) => ownerPath(candidate.owner)))];
169
+ const selectedPaths = [...new Set([...flatFileAnchors, ...prepared.map((entry) => entry.path)])].slice(0, fileLimit);
170
+ const selected = selectedPaths.flatMap((path) => {
171
+ const entry = prepared.find((candidate) => candidate.path === path);
172
+ return entry ? [entry] : [];
173
+ });
174
+ const files = selected.map((entry, index) => ({
175
+ path: entry.path,
176
+ file_rank: index + 1,
177
+ file_score: entry.fileScore,
178
+ first_candidate_rank: entry.firstCandidateRank,
179
+ declaration_clusters: entry.clusters,
180
+ }));
181
+ const receiptWithoutId = {
182
+ version: 3,
183
+ rule: FILE_FIRST_DECLARATION_CLUSTER_RULE,
184
+ source_candidates: distinctCandidates.length,
185
+ file_limit: fileLimit,
186
+ cluster_limit_per_file: clusterLimit,
187
+ member_limit_per_cluster: memberLimit,
188
+ selected_files: files.map((file) => file.path),
189
+ selected_cluster_ids: files.flatMap((file) => file.declaration_clusters.map((cluster) => cluster.cluster_id)),
190
+ file_selection_strategy: "flat-shortlist-file-anchor",
191
+ flat_shortlist_anchor_size: 5,
192
+ flat_shortlist_preserved: true,
193
+ exact_owner_enabled: false,
194
+ };
195
+ return {
196
+ files,
197
+ receipt: { ...receiptWithoutId, receipt_id: shortHash(receiptWithoutId) },
198
+ transfer_calibration: FILE_FIRST_DECLARATION_CLUSTER_TRANSFER,
199
+ };
200
+ }
201
+ /** Turn the promoted flat shortlist and semantic clusters into a progressive
202
+ * inspection queue. The first five candidates remain byte-for-byte intact.
203
+ * Supplemental candidates must already belong to a selected semantic family,
204
+ * and are ordered by the frozen repository-adaptive rank instead of widening
205
+ * to every declaration in the file. The last slot is explicitly a fallback so
206
+ * callers can stop after ten inspections when the behavior is already owned. */
207
+ export function buildProgressiveDeclarationPlan(rankedCandidatesValue, clusters, requestedBaselineLimit = PROGRESSIVE_DECLARATION_BASELINE_LIMIT, requestedTotalLimit = PROGRESSIVE_DECLARATION_TOTAL_LIMIT) {
208
+ const baselineLimit = Number.isSafeInteger(requestedBaselineLimit)
209
+ ? Math.max(1, Math.min(PROGRESSIVE_DECLARATION_BASELINE_LIMIT, requestedBaselineLimit))
210
+ : PROGRESSIVE_DECLARATION_BASELINE_LIMIT;
211
+ const totalLimit = Number.isSafeInteger(requestedTotalLimit)
212
+ ? Math.max(baselineLimit, Math.min(PROGRESSIVE_DECLARATION_TOTAL_LIMIT, requestedTotalLimit))
213
+ : PROGRESSIVE_DECLARATION_TOTAL_LIMIT;
214
+ const primaryLimit = Math.min(PROGRESSIVE_DECLARATION_PRIMARY_LIMIT, totalLimit);
215
+ const distinctCandidates = rankedCandidatesValue.filter((candidate, index, all) => all.findIndex((entry) => entry.owner === candidate.owner) === index);
216
+ const byOwner = new Map(distinctCandidates.map((candidate, index) => [candidate.owner, {
217
+ candidate,
218
+ globalRank: index + 1,
219
+ }]));
220
+ const clusterMembership = new Map();
221
+ for (const file of clusters.files) {
222
+ for (const cluster of file.declaration_clusters) {
223
+ for (const member of cluster.members) {
224
+ if (!clusterMembership.has(member.owner)) {
225
+ clusterMembership.set(member.owner, {
226
+ clusterId: cluster.cluster_id,
227
+ clusterLabel: cluster.label,
228
+ filePath: file.path,
229
+ fileRank: file.file_rank,
230
+ });
231
+ }
232
+ }
233
+ }
234
+ }
235
+ const baselineOwners = distinctCandidates.slice(0, baselineLimit).map((candidate) => candidate.owner);
236
+ const baselineSet = new Set(baselineOwners);
237
+ const expansion = clusters.files.flatMap((file) => file.declaration_clusters.flatMap((cluster) => cluster.members.map((member) => ({ member, fileRank: file.file_rank, clusterId: cluster.cluster_id }))))
238
+ .filter((entry, index, all) => !baselineSet.has(entry.member.owner)
239
+ && all.findIndex((candidate) => candidate.member.owner === entry.member.owner) === index)
240
+ .sort((left, right) => left.member.global_rank - right.member.global_rank
241
+ || Number(right.member.runtime_declaration) - Number(left.member.runtime_declaration)
242
+ || Number(left.member.type_scaffolding) - Number(right.member.type_scaffolding)
243
+ || left.fileRank - right.fileRank
244
+ || left.clusterId.localeCompare(right.clusterId)
245
+ || left.member.owner.localeCompare(right.member.owner))
246
+ .slice(0, Math.max(0, totalLimit - baselineOwners.length));
247
+ const baseline = baselineOwners.flatMap((owner, index) => {
248
+ const ranked = byOwner.get(owner);
249
+ if (!ranked)
250
+ return [];
251
+ const membership = clusterMembership.get(owner);
252
+ return [{
253
+ owner,
254
+ inspection_rank: index + 1,
255
+ phase: "flat-shortlist",
256
+ file_path: ownerPath(owner),
257
+ global_rank: ranked.globalRank,
258
+ cluster_id: membership?.clusterId ?? null,
259
+ cluster_label: membership?.clusterLabel ?? null,
260
+ runtime_declaration: ranked.candidate.runtime_declaration,
261
+ type_scaffolding: ranked.candidate.type_scaffolding,
262
+ }];
263
+ });
264
+ const supplemental = expansion.map((entry, index) => {
265
+ const membership = clusterMembership.get(entry.member.owner);
266
+ const inspectionRank = baseline.length + index + 1;
267
+ return {
268
+ owner: entry.member.owner,
269
+ inspection_rank: inspectionRank,
270
+ phase: inspectionRank <= primaryLimit ? "cluster-expansion" : "cluster-fallback",
271
+ file_path: membership?.filePath ?? ownerPath(entry.member.owner),
272
+ global_rank: entry.member.global_rank,
273
+ cluster_id: membership?.clusterId ?? null,
274
+ cluster_label: membership?.clusterLabel ?? null,
275
+ runtime_declaration: entry.member.runtime_declaration,
276
+ type_scaffolding: entry.member.type_scaffolding,
277
+ };
278
+ });
279
+ const candidates = [...baseline, ...supplemental];
280
+ const phases = [
281
+ {
282
+ phase: "flat-shortlist",
283
+ instruction: "Inspect the preserved repository-adaptive shortlist first.",
284
+ stop_condition: "Stop when one candidate explains and reproduces the behavior; otherwise expand within the anchored files.",
285
+ candidates: baseline,
286
+ },
287
+ {
288
+ phase: "cluster-expansion",
289
+ instruction: "Inspect the strongest remaining declarations from the selected semantic families.",
290
+ stop_condition: "Stop by inspection ten when a family owns the behavior; use the final fallback only while ownership remains unresolved.",
291
+ candidates: supplemental.filter((candidate) => candidate.phase === "cluster-expansion"),
292
+ },
293
+ {
294
+ phase: "cluster-fallback",
295
+ instruction: "Inspect one final ranked family member without widening to another file.",
296
+ stop_condition: "After this bounded fallback, report uncertainty instead of generating more owner guesses.",
297
+ candidates: supplemental.filter((candidate) => candidate.phase === "cluster-fallback"),
298
+ },
299
+ ];
300
+ const receiptWithoutId = {
301
+ version: 1,
302
+ rule: PROGRESSIVE_DECLARATION_PLAN_RULE,
303
+ baseline_limit: baselineLimit,
304
+ primary_limit: primaryLimit,
305
+ total_limit: totalLimit,
306
+ selected_owners: candidates.map((candidate) => candidate.owner),
307
+ selected_cluster_ids: [...new Set(candidates.flatMap((candidate) => candidate.cluster_id ? [candidate.cluster_id] : []))],
308
+ promoted_mechanisms: ["repository-adaptive-ranking", "flat-file-anchored-semantic-clusters"],
309
+ rejected_rerankers_disabled: true,
310
+ flat_shortlist_preserved: true,
311
+ exact_owner_enabled: false,
312
+ };
313
+ return {
314
+ phases,
315
+ candidates,
316
+ receipt: { ...receiptWithoutId, receipt_id: shortHash(receiptWithoutId) },
317
+ development_calibration: PROGRESSIVE_DECLARATION_DEVELOPMENT,
318
+ transfer_calibration: PROGRESSIVE_DECLARATION_TRANSFER,
319
+ };
320
+ }
321
+ //# sourceMappingURL=declarationClusters.js.map