@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.
- package/README.md +59 -2
- package/dist/cli/index.js +110 -7
- package/dist/core/agenthook.js +41 -0
- package/dist/core/correctionStage.js +513 -0
- package/dist/core/declarationClusters.js +321 -0
- package/dist/core/delivery.js +307 -6
- package/dist/core/evidenceMap.js +202 -0
- package/dist/core/jsonc.js +72 -0
- package/dist/core/pipeline.js +953 -10
- package/dist/extractors/correctionSources.js +40 -0
- package/dist/extractors/landscapeDiscovery.js +1205 -0
- package/dist/integrations/providers.js +2 -78
- package/dist/integrations/scaffold.js +6 -0
- package/dist/mcp/server.js +84 -2
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { rankIssueImplementationOwners, } from "./pipeline.js";
|
|
3
|
+
import { compileVerifiedEvidenceMap, } from "./evidenceMap.js";
|
|
4
|
+
import { buildFileFirstDeclarationClusters, buildProgressiveDeclarationPlan, } from "./declarationClusters.js";
|
|
5
|
+
export const EVIDENCE_GUIDED_SHORTLIST_RULE = "guarded-evidence-bridge-v3";
|
|
6
|
+
export const CORRECTION_STAGE_CANDIDATE_LIMIT = 5;
|
|
7
|
+
export const EVIDENCE_GUIDED_SLOT_LIMIT = 3;
|
|
8
|
+
export const EVIDENCE_GUIDED_BASELINE_FLOOR = 2;
|
|
9
|
+
export const EXECUTION_GUIDED_SLOT_LIMIT = 1;
|
|
10
|
+
export const EXECUTION_GUIDED_BASELINE_FLOOR = 4;
|
|
11
|
+
export const EXECUTION_DIRECT_RATIO_MIN = 4;
|
|
12
|
+
export const EXECUTION_DIRECT_STATIC_RANK_MAX = 20;
|
|
13
|
+
export const EXECUTION_FILE_RATIO_MIN = 2;
|
|
14
|
+
export const EXECUTION_FILE_STATIC_RANK_MAX = 10;
|
|
15
|
+
const EXECUTION_BRIDGE_INFRASTRUCTURE_PATH = /(?:^|\/)doc\.ts$/;
|
|
16
|
+
export const CORRECTION_STAGE_CALIBRATION = {
|
|
17
|
+
holdout_tasks: 11,
|
|
18
|
+
likely_file_hits: 9,
|
|
19
|
+
top_five_hits: 8,
|
|
20
|
+
};
|
|
21
|
+
export const CORRECTION_STAGE_TRANSFER = {
|
|
22
|
+
repositories: ["jquense/yup", "sinclairzx81/typebox"],
|
|
23
|
+
holdout_tasks: 16,
|
|
24
|
+
likely_file_hits: 0,
|
|
25
|
+
top_five_hits: 0,
|
|
26
|
+
decision: "rejected",
|
|
27
|
+
};
|
|
28
|
+
export const ADAPTIVE_CORRECTION_STAGE_TRANSFER = {
|
|
29
|
+
repositories: ["arktypeio/arktype", "typestack/class-validator"],
|
|
30
|
+
scorable_tasks: 11,
|
|
31
|
+
likely_file_hits: 8,
|
|
32
|
+
top_five_hits: 9,
|
|
33
|
+
exact_symbol_hits: 7,
|
|
34
|
+
decision: "promoted-diagnostic",
|
|
35
|
+
};
|
|
36
|
+
export const ADAPTIVE_CORRECTION_STAGE_REPLICATION = {
|
|
37
|
+
repositories: ["trpc/trpc", "elysiajs/elysia"],
|
|
38
|
+
scorable_tasks: 11,
|
|
39
|
+
likely_file_hits: 4,
|
|
40
|
+
top_five_hits: 5,
|
|
41
|
+
exact_symbol_hits: 4,
|
|
42
|
+
decision: "failed-replication",
|
|
43
|
+
};
|
|
44
|
+
export const CORRECTION_OPTIMIZATION_POLICY = {
|
|
45
|
+
active: [
|
|
46
|
+
{ mechanism: "repository-adaptive-ranking", verdict: "promote-adaptive-diagnostic" },
|
|
47
|
+
{ mechanism: "flat-file-anchored-semantic-clusters", verdict: "promote-flat-file-anchored-clusters-v3" },
|
|
48
|
+
],
|
|
49
|
+
advisory_only: [
|
|
50
|
+
{ mechanism: "static-stage-shortlist", verdict: "retain-diagnostic-stage-shortlist" },
|
|
51
|
+
{ mechanism: "progressive-inspection-budget", verdict: "retain-efficiency-advisory-v4" },
|
|
52
|
+
],
|
|
53
|
+
disabled: [
|
|
54
|
+
{ mechanism: "fixed-repository-stage-router", verdict: "reject-cross-repository-transfer" },
|
|
55
|
+
{ mechanism: "score-gap-confidence", verdict: "reject-shortlist-evidence" },
|
|
56
|
+
{ mechanism: "cross-view-confidence", verdict: "reject-cross-view-evidence" },
|
|
57
|
+
{ mechanism: "causal-slot-owner", verdict: "reject-causal-slot" },
|
|
58
|
+
{ mechanism: "causal-intervention-owner", verdict: "reject-causal-intervention-owner" },
|
|
59
|
+
{ mechanism: "evidence-guided-reordering", verdict: "reject-guarded-evidence-bridge-v3" },
|
|
60
|
+
{ mechanism: "product-source-filter", verdict: "reject-development-v5-one-loss" },
|
|
61
|
+
{ mechanism: "one-hop-relationship-expansion", verdict: "reject-development-v5-no-lift" },
|
|
62
|
+
{ mechanism: "same-file-frontier-replacement", verdict: "reject-development-v5-three-losses" },
|
|
63
|
+
{ mechanism: "additive-same-file-frontier", verdict: "reject-additive-frontier-v5-no-fresh-rescue" },
|
|
64
|
+
],
|
|
65
|
+
exact_owner_policy: "disabled",
|
|
66
|
+
per_case_confidence: "disabled",
|
|
67
|
+
};
|
|
68
|
+
function boundedIssue(value) {
|
|
69
|
+
return typeof value === "string" ? value.trim().slice(0, 100_000) : "";
|
|
70
|
+
}
|
|
71
|
+
/** Classify the layer that owns the broken contract, not the public API through
|
|
72
|
+
* which the symptom happened to surface. This is deterministic and read-only. */
|
|
73
|
+
export function inferIssueCorrectionStage(issueValue) {
|
|
74
|
+
const text = boundedIssue(issueValue).toLowerCase();
|
|
75
|
+
if (/fromjsonschema|from json schema|json schema (?:input|import|conversion)/.test(text))
|
|
76
|
+
return "schema-ingestion";
|
|
77
|
+
if (/tojsonschema|to json schema|\$ref|\$defs|json pointer|json schema (?:output|emit|serial)/.test(text))
|
|
78
|
+
return "schema-emission";
|
|
79
|
+
if (/error message|message (?:uses|says|wording|ignore)|wording|locale|render(?:ing|er)?/.test(text))
|
|
80
|
+
return "presentation";
|
|
81
|
+
if (/\borigin\b|\bminimum\b|\bmaximum\b|\binclusive\b|\bexact flag\b|constraint/.test(text))
|
|
82
|
+
return "constraint-definition";
|
|
83
|
+
return "runtime-policy";
|
|
84
|
+
}
|
|
85
|
+
/** Repository paths that commonly implement each correction stage. Exported so
|
|
86
|
+
* safe source collection can prioritize the relevant layer in very large repos. */
|
|
87
|
+
export function correctionStagePathPattern(stage, issueValue) {
|
|
88
|
+
const issue = boundedIssue(issueValue).toLowerCase();
|
|
89
|
+
const referenceAssembly = stage === "schema-emission" && /\$ref|\$defs|json pointer/.test(issue);
|
|
90
|
+
const patterns = {
|
|
91
|
+
"schema-emission": referenceAssembly ? /to-json-schema/ : /(?:json-schema-processors|to-json-schema)/,
|
|
92
|
+
"schema-ingestion": /from-json-schema/,
|
|
93
|
+
presentation: /(?:^|\/)(?:locales\/|errors?\.ts$)/,
|
|
94
|
+
"constraint-definition": /(?:^|\/)(?:checks|api)\.ts$/,
|
|
95
|
+
"runtime-policy": /(?:^|\/)(?:schemas|checks|parse)\.ts$/,
|
|
96
|
+
};
|
|
97
|
+
return patterns[stage];
|
|
98
|
+
}
|
|
99
|
+
function terms(value) {
|
|
100
|
+
return new Set(value
|
|
101
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.split(/[^a-z0-9_$]+/)
|
|
104
|
+
.map((term) => term.replace(/^[$_]+/, ""))
|
|
105
|
+
.filter((term) => term.length >= 3));
|
|
106
|
+
}
|
|
107
|
+
/** Approximate top-level runtime declarations without loading TypeScript into
|
|
108
|
+
* every Hunch process. Type-only declarations intentionally remain candidates,
|
|
109
|
+
* but runtime declarations win ties in the selected layer. */
|
|
110
|
+
function runtimeDeclarationOwners(sources) {
|
|
111
|
+
const owners = new Set();
|
|
112
|
+
const declaration = /^(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:async\s+)?(?:function|class|enum|const|let|var)\s+([$A-Za-z_][$\w]*)/gm;
|
|
113
|
+
for (const source of sources) {
|
|
114
|
+
for (const match of source.content.matchAll(declaration)) {
|
|
115
|
+
owners.add(`${source.path}::${match[1]}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return owners;
|
|
119
|
+
}
|
|
120
|
+
/** Return a stage-constrained declaration ranking. Public APIs invoked by the
|
|
121
|
+
* reproduction are treated as symptom entrances and excluded whenever deeper
|
|
122
|
+
* candidates exist in the selected stage. */
|
|
123
|
+
export function rankIssueCorrectionStageCandidates(issueValue, sources) {
|
|
124
|
+
const issue = boundedIssue(issueValue);
|
|
125
|
+
if (!issue)
|
|
126
|
+
return [];
|
|
127
|
+
const stage = inferIssueCorrectionStage(issue);
|
|
128
|
+
const stagePath = correctionStagePathPattern(stage, issue);
|
|
129
|
+
const invoked = new Set([...issue.matchAll(/\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g)]
|
|
130
|
+
.map((match) => match[1].replace(/^[$_]+/, "").toLowerCase()));
|
|
131
|
+
const issueTerms = terms(issue);
|
|
132
|
+
const runtimeOwners = runtimeDeclarationOwners(sources);
|
|
133
|
+
const lexical = rankIssueImplementationOwners(issue, sources, 4_000)?.candidates ?? [];
|
|
134
|
+
const inStage = lexical.filter((candidate) => stagePath.test(candidate.owner.split("::")[0]));
|
|
135
|
+
const deeper = inStage.filter((candidate) => !invoked.has((candidate.owner.split("::")[1] ?? "").replace(/^[$_]+/, "").toLowerCase()));
|
|
136
|
+
const ranked = (deeper.length ? deeper : inStage).map((candidate) => {
|
|
137
|
+
const [path, symbol = ""] = candidate.owner.split("::");
|
|
138
|
+
return {
|
|
139
|
+
owner: candidate.owner,
|
|
140
|
+
stage,
|
|
141
|
+
lexical_score: candidate.score,
|
|
142
|
+
symbol_overlap: [...terms(symbol)].filter((term) => issueTerms.has(term)).length,
|
|
143
|
+
runtime_declaration: runtimeOwners.has(candidate.owner),
|
|
144
|
+
type_scaffolding: /(?:Def|Internals?|Context|Options?|Params?|Input|Output)$/.test(symbol),
|
|
145
|
+
default_locale: stage === "presentation" && path.endsWith("/locales/en.ts"),
|
|
146
|
+
};
|
|
147
|
+
}).sort((a, b) => Number(b.default_locale) - Number(a.default_locale)
|
|
148
|
+
|| Number(b.runtime_declaration) - Number(a.runtime_declaration)
|
|
149
|
+
|| Number(a.type_scaffolding) - Number(b.type_scaffolding)
|
|
150
|
+
|| b.symbol_overlap - a.symbol_overlap
|
|
151
|
+
|| b.lexical_score - a.lexical_score
|
|
152
|
+
|| a.owner.localeCompare(b.owner));
|
|
153
|
+
// Overloads and repeated declarations can produce the same owner more than
|
|
154
|
+
// once. A shortlist must spend each slot on a distinct correction candidate.
|
|
155
|
+
const seen = new Set();
|
|
156
|
+
return ranked.filter((candidate) => {
|
|
157
|
+
if (seen.has(candidate.owner))
|
|
158
|
+
return false;
|
|
159
|
+
seen.add(candidate.owner);
|
|
160
|
+
return true;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const ADAPTIVE_STOP_WORDS = new Set([
|
|
164
|
+
"src", "source", "lib", "library", "package", "packages", "core", "index", "type", "types", "schema", "schemas",
|
|
165
|
+
"test", "tests", "with", "from", "into", "this", "that", "when", "then", "value", "values", "error", "issue",
|
|
166
|
+
]);
|
|
167
|
+
const ADAPTIVE_TYPE_SCAFFOLD = /(?:Def|Internals?|Context|Options?|Params?|Input|Output|Config|Props|Type)$/;
|
|
168
|
+
const ADAPTIVE_GENERIC_ENTRANCE = /^(?:parse|parser|validate|validator|check|schema|error|assert|create|build|process|compile)$/i;
|
|
169
|
+
function adaptiveTerms(value) {
|
|
170
|
+
return new Set(value
|
|
171
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
172
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1 $2")
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.split(/[^a-z0-9_$]+/)
|
|
175
|
+
.map((term) => term.replace(/^[$_]+/, ""))
|
|
176
|
+
.filter((term) => term.length >= 3 && !ADAPTIVE_STOP_WORDS.has(term)));
|
|
177
|
+
}
|
|
178
|
+
function adaptiveOverlap(left, right) {
|
|
179
|
+
return [...left].filter((term) => right.has(term)).length;
|
|
180
|
+
}
|
|
181
|
+
function adaptiveComponentKeys(path) {
|
|
182
|
+
const parts = path.split("/");
|
|
183
|
+
const directories = parts.slice(0, -1);
|
|
184
|
+
const keys = [];
|
|
185
|
+
for (let depth = Math.max(0, directories.length - 3); depth < directories.length; depth++) {
|
|
186
|
+
const suffix = directories.slice(depth).join("/");
|
|
187
|
+
if (suffix && !/^(?:src|lib|source|packages?)$/.test(suffix))
|
|
188
|
+
keys.push(suffix);
|
|
189
|
+
}
|
|
190
|
+
return keys;
|
|
191
|
+
}
|
|
192
|
+
function ownerPath(owner) {
|
|
193
|
+
return owner.slice(0, owner.indexOf("::"));
|
|
194
|
+
}
|
|
195
|
+
function distinct(values) {
|
|
196
|
+
return [...new Set(values)];
|
|
197
|
+
}
|
|
198
|
+
function normalizedClaim(value) {
|
|
199
|
+
return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
|
|
200
|
+
}
|
|
201
|
+
function receiptId(receipt) {
|
|
202
|
+
return createHash("sha256").update(JSON.stringify(receipt)).digest("hex").slice(0, 24);
|
|
203
|
+
}
|
|
204
|
+
function optimizationReceipt(value) {
|
|
205
|
+
return { ...value, receipt_id: receiptId(value) };
|
|
206
|
+
}
|
|
207
|
+
/** Reserve a bounded portion of a shortlist for declarations proven to affect
|
|
208
|
+
* the same authenticated behavior. File peers are included because the held
|
|
209
|
+
* intervention experiments localized files more reliably than exact owners. */
|
|
210
|
+
export function reserveEvidenceGuidedOwners(baselineOwnersValue, rankedOwnersValue, evidenceMap, requestedLimit = CORRECTION_STAGE_CANDIDATE_LIMIT) {
|
|
211
|
+
const limit = Number.isSafeInteger(requestedLimit)
|
|
212
|
+
? Math.max(1, Math.min(CORRECTION_STAGE_CANDIDATE_LIMIT, requestedLimit))
|
|
213
|
+
: CORRECTION_STAGE_CANDIDATE_LIMIT;
|
|
214
|
+
const baselineOwners = distinct(baselineOwnersValue).slice(0, limit);
|
|
215
|
+
if (!evidenceMap.verification.authenticated || evidenceMap.level !== "behavior-sensitive")
|
|
216
|
+
return baselineOwners;
|
|
217
|
+
const rankedOwners = distinct([...rankedOwnersValue, ...baselineOwners]);
|
|
218
|
+
const rankedSet = new Set(rankedOwners);
|
|
219
|
+
const sensitiveOwners = new Set(evidenceMap.intervention_slice.behavior_sensitive_owners);
|
|
220
|
+
const sensitiveFiles = new Set(evidenceMap.intervention_slice.behavior_sensitive_files);
|
|
221
|
+
const direct = rankedOwners.filter((owner) => sensitiveOwners.has(owner) && rankedSet.has(owner));
|
|
222
|
+
const filePeers = rankedOwners.filter((owner) => !sensitiveOwners.has(owner) && sensitiveFiles.has(ownerPath(owner)));
|
|
223
|
+
const baselineFloor = Math.min(EVIDENCE_GUIDED_BASELINE_FLOOR, limit);
|
|
224
|
+
const slotLimit = Math.min(EVIDENCE_GUIDED_SLOT_LIMIT, Math.max(0, limit - baselineFloor));
|
|
225
|
+
const evidenceOwners = distinct([...direct, ...filePeers]).slice(0, slotLimit);
|
|
226
|
+
if (!evidenceOwners.length)
|
|
227
|
+
return baselineOwners;
|
|
228
|
+
return distinct([...evidenceOwners, ...baselineOwners, ...rankedOwners]).slice(0, limit);
|
|
229
|
+
}
|
|
230
|
+
/** Combine execution contrast with static plausibility. Files already covered
|
|
231
|
+
* by the baseline are ignored, generic instrumentation files are excluded,
|
|
232
|
+
* and only the final shortlist slot is eligible. Runtime evidence proposes a
|
|
233
|
+
* bounded hypothesis; it never becomes an exact-owner claim. */
|
|
234
|
+
export function selectGuardedExecutionBridge(baselineOwnersValue, rankedOwnersValue, evidenceMap, requestedLimit = CORRECTION_STAGE_CANDIDATE_LIMIT) {
|
|
235
|
+
const limit = Number.isSafeInteger(requestedLimit)
|
|
236
|
+
? Math.max(1, Math.min(CORRECTION_STAGE_CANDIDATE_LIMIT, requestedLimit))
|
|
237
|
+
: CORRECTION_STAGE_CANDIDATE_LIMIT;
|
|
238
|
+
const baseline = distinct(baselineOwnersValue).slice(0, limit);
|
|
239
|
+
const baselineFloor = Math.min(EXECUTION_GUIDED_BASELINE_FLOOR, limit);
|
|
240
|
+
if (!evidenceMap.verification.authenticated || limit - baselineFloor < EXECUTION_GUIDED_SLOT_LIMIT)
|
|
241
|
+
return null;
|
|
242
|
+
const ranked = distinct(rankedOwnersValue);
|
|
243
|
+
const rankByOwner = new Map(ranked.map((owner, index) => [owner, index + 1]));
|
|
244
|
+
const baselineFiles = new Set(baseline.map(ownerPath));
|
|
245
|
+
const eligibleEvidence = evidenceMap.execution_slice.strong_differential.filter((entry) => {
|
|
246
|
+
const path = ownerPath(entry.owner);
|
|
247
|
+
return !baselineFiles.has(path) && !EXECUTION_BRIDGE_INFRASTRUCTURE_PATH.test(path);
|
|
248
|
+
});
|
|
249
|
+
const paths = distinct(eligibleEvidence.map((entry) => ownerPath(entry.owner)));
|
|
250
|
+
const choices = paths.flatMap((path) => {
|
|
251
|
+
const evidence = eligibleEvidence.filter((entry) => ownerPath(entry.owner) === path);
|
|
252
|
+
const direct = evidence.flatMap((entry) => {
|
|
253
|
+
const staticRank = rankByOwner.get(entry.owner);
|
|
254
|
+
return staticRank !== undefined
|
|
255
|
+
&& staticRank <= EXECUTION_DIRECT_STATIC_RANK_MAX
|
|
256
|
+
&& entry.ratio >= EXECUTION_DIRECT_RATIO_MIN
|
|
257
|
+
? [{
|
|
258
|
+
owner: entry.owner,
|
|
259
|
+
path,
|
|
260
|
+
strategy: "direct-high-contrast-execution",
|
|
261
|
+
execution_ratio: entry.ratio,
|
|
262
|
+
static_rank: staticRank,
|
|
263
|
+
}]
|
|
264
|
+
: [];
|
|
265
|
+
}).sort((left, right) => right.execution_ratio - left.execution_ratio
|
|
266
|
+
|| left.static_rank - right.static_rank
|
|
267
|
+
|| left.owner.localeCompare(right.owner))[0];
|
|
268
|
+
if (direct)
|
|
269
|
+
return [direct];
|
|
270
|
+
const maxRatio = evidence.reduce((best, entry) => Math.max(best, entry.ratio), 0);
|
|
271
|
+
const peer = ranked.map((owner, index) => ({ owner, rank: index + 1 }))
|
|
272
|
+
.find((item) => ownerPath(item.owner) === path && !baseline.includes(item.owner));
|
|
273
|
+
return peer && peer.rank <= EXECUTION_FILE_STATIC_RANK_MAX && maxRatio >= EXECUTION_FILE_RATIO_MIN
|
|
274
|
+
? [{
|
|
275
|
+
owner: peer.owner,
|
|
276
|
+
path,
|
|
277
|
+
strategy: "guarded-execution-file-peer",
|
|
278
|
+
execution_ratio: maxRatio,
|
|
279
|
+
static_rank: peer.rank,
|
|
280
|
+
}]
|
|
281
|
+
: [];
|
|
282
|
+
}).sort((left, right) => Number(right.strategy === "direct-high-contrast-execution")
|
|
283
|
+
- Number(left.strategy === "direct-high-contrast-execution")
|
|
284
|
+
|| right.execution_ratio - left.execution_ratio
|
|
285
|
+
|| left.static_rank - right.static_rank
|
|
286
|
+
|| left.owner.localeCompare(right.owner));
|
|
287
|
+
return choices[0] ?? null;
|
|
288
|
+
}
|
|
289
|
+
export function reserveExecutionGuidedFileOwner(baselineOwnersValue, rankedOwnersValue, evidenceMap, requestedLimit = CORRECTION_STAGE_CANDIDATE_LIMIT) {
|
|
290
|
+
const limit = Number.isSafeInteger(requestedLimit)
|
|
291
|
+
? Math.max(1, Math.min(CORRECTION_STAGE_CANDIDATE_LIMIT, requestedLimit))
|
|
292
|
+
: CORRECTION_STAGE_CANDIDATE_LIMIT;
|
|
293
|
+
const baseline = distinct(baselineOwnersValue).slice(0, limit);
|
|
294
|
+
const selection = selectGuardedExecutionBridge(baseline, rankedOwnersValue, evidenceMap, limit);
|
|
295
|
+
return selection ? distinct([...baseline.slice(0, Math.min(EXECUTION_GUIDED_BASELINE_FLOOR, limit)), selection.owner]).slice(0, limit) : baseline;
|
|
296
|
+
}
|
|
297
|
+
/** Repository-adaptive replacement for fixed Zod path routing. It discovers
|
|
298
|
+
* issue vocabulary in this repository's own paths and symbols, adds local
|
|
299
|
+
* component consensus, and removes an invoked facade only when a deeper
|
|
300
|
+
* repository-native candidate is available. */
|
|
301
|
+
export function rankIssueAdaptiveCorrectionCandidates(issueValue, sources) {
|
|
302
|
+
const issue = boundedIssue(issueValue);
|
|
303
|
+
if (!issue)
|
|
304
|
+
return [];
|
|
305
|
+
const issueTerms = adaptiveTerms(issue);
|
|
306
|
+
const invoked = new Set([...issue.matchAll(/\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g)]
|
|
307
|
+
.map((match) => match[1].replace(/^[$_]+/, "").toLowerCase()));
|
|
308
|
+
const runtime = runtimeDeclarationOwners(sources);
|
|
309
|
+
const lexical = rankIssueImplementationOwners(issue, sources, 4_000)?.candidates ?? [];
|
|
310
|
+
const distinct = lexical.filter((candidate, index, all) => all.findIndex((item) => item.owner === candidate.owner) === index);
|
|
311
|
+
const prepared = distinct.map((candidate) => {
|
|
312
|
+
const [path, symbol = ""] = candidate.owner.split("::");
|
|
313
|
+
const pathOverlap = adaptiveOverlap(issueTerms, adaptiveTerms(path));
|
|
314
|
+
const symbolOverlap = adaptiveOverlap(issueTerms, adaptiveTerms(symbol));
|
|
315
|
+
const direct = candidate.score + pathOverlap * 14 + symbolOverlap * 10;
|
|
316
|
+
return { candidate, path: path, symbol, pathOverlap, symbolOverlap, direct, keys: adaptiveComponentKeys(path) };
|
|
317
|
+
});
|
|
318
|
+
const componentScores = new Map();
|
|
319
|
+
for (const item of prepared) {
|
|
320
|
+
for (const key of item.keys)
|
|
321
|
+
componentScores.set(key, Math.max(componentScores.get(key) ?? 0, item.direct));
|
|
322
|
+
}
|
|
323
|
+
const ranked = prepared.map((item) => {
|
|
324
|
+
const normalized = item.symbol.replace(/^[$_]+/, "").toLowerCase();
|
|
325
|
+
const invokedEntrance = invoked.has(normalized);
|
|
326
|
+
const genericEntrance = ADAPTIVE_GENERIC_ENTRANCE.test(item.symbol);
|
|
327
|
+
const typeScaffolding = ADAPTIVE_TYPE_SCAFFOLD.test(item.symbol);
|
|
328
|
+
const componentSupport = item.keys.reduce((best, key) => Math.max(best, componentScores.get(key) ?? 0), 0);
|
|
329
|
+
let score = item.direct + componentSupport * 0.12;
|
|
330
|
+
if (runtime.has(item.candidate.owner))
|
|
331
|
+
score += 4;
|
|
332
|
+
if (invokedEntrance)
|
|
333
|
+
score -= 14;
|
|
334
|
+
if (genericEntrance && item.pathOverlap === 0 && item.symbolOverlap === 0)
|
|
335
|
+
score -= 8;
|
|
336
|
+
if (typeScaffolding)
|
|
337
|
+
score -= 3;
|
|
338
|
+
return {
|
|
339
|
+
owner: item.candidate.owner,
|
|
340
|
+
stage: inferIssueCorrectionStage(issue),
|
|
341
|
+
score: Math.round(score * 100) / 100,
|
|
342
|
+
lexical_score: item.candidate.score,
|
|
343
|
+
path_overlap: item.pathOverlap,
|
|
344
|
+
symbol_overlap: item.symbolOverlap,
|
|
345
|
+
component_support: Math.round(componentSupport * 100) / 100,
|
|
346
|
+
runtime_declaration: runtime.has(item.candidate.owner),
|
|
347
|
+
invoked_entrance: invokedEntrance,
|
|
348
|
+
generic_entrance: genericEntrance,
|
|
349
|
+
type_scaffolding: typeScaffolding,
|
|
350
|
+
};
|
|
351
|
+
}).sort((a, b) => b.score - a.score
|
|
352
|
+
|| b.path_overlap - a.path_overlap
|
|
353
|
+
|| b.symbol_overlap - a.symbol_overlap
|
|
354
|
+
|| Number(b.runtime_declaration) - Number(a.runtime_declaration)
|
|
355
|
+
|| a.owner.localeCompare(b.owner));
|
|
356
|
+
const deeper = ranked.filter((candidate) => !candidate.invoked_entrance
|
|
357
|
+
&& (candidate.path_overlap > 0 || candidate.symbol_overlap > 0));
|
|
358
|
+
return deeper.length ? ranked.filter((candidate) => !candidate.invoked_entrance) : ranked;
|
|
359
|
+
}
|
|
360
|
+
export function optimizeIssueCorrectionCandidates(issueValue, sources, evidenceValue, requestedLimit = CORRECTION_STAGE_CANDIDATE_LIMIT) {
|
|
361
|
+
const issue = boundedIssue(issueValue);
|
|
362
|
+
const limit = Number.isSafeInteger(requestedLimit)
|
|
363
|
+
? Math.max(1, Math.min(CORRECTION_STAGE_CANDIDATE_LIMIT, requestedLimit))
|
|
364
|
+
: CORRECTION_STAGE_CANDIDATE_LIMIT;
|
|
365
|
+
const evidenceMap = compileVerifiedEvidenceMap(evidenceValue);
|
|
366
|
+
const ranked = rankIssueAdaptiveCorrectionCandidates(issue, sources);
|
|
367
|
+
const byOwner = new Map(ranked.map((candidate, index) => [candidate.owner, { candidate, baselineRank: index + 1 }]));
|
|
368
|
+
const baseline = ranked.slice(0, limit).map((candidate) => candidate.owner);
|
|
369
|
+
const claimBound = normalizedClaim(issue) === normalizedClaim(evidenceMap.claim);
|
|
370
|
+
let reason;
|
|
371
|
+
let optimized = baseline;
|
|
372
|
+
let evidenceStrategy = null;
|
|
373
|
+
let selectedExecutionRatio = null;
|
|
374
|
+
let selectedStaticRank = null;
|
|
375
|
+
if (!claimBound) {
|
|
376
|
+
reason = "claim-mismatch";
|
|
377
|
+
}
|
|
378
|
+
else if (!evidenceMap.verification.authenticated) {
|
|
379
|
+
reason = "probe-unverified";
|
|
380
|
+
}
|
|
381
|
+
else if (evidenceMap.level === "behavior-sensitive"
|
|
382
|
+
|| evidenceMap.execution_slice.strong_differential_files.length) {
|
|
383
|
+
// Three fresh transfer cohorts rejected evidence-based shortlist mutation.
|
|
384
|
+
// Preserve the observations on each candidate, but never let behavioral
|
|
385
|
+
// influence masquerade as correction ownership in production.
|
|
386
|
+
reason = "transfer-rejected-read-only";
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
reason = "no-actionable-evidence";
|
|
390
|
+
}
|
|
391
|
+
const baselineSet = new Set(baseline);
|
|
392
|
+
const optimizedSet = new Set(optimized);
|
|
393
|
+
const sensitiveOwners = new Set(evidenceMap.intervention_slice.behavior_sensitive_owners);
|
|
394
|
+
const sensitiveFiles = new Set(evidenceMap.intervention_slice.behavior_sensitive_files);
|
|
395
|
+
const targetOnly = new Set(evidenceMap.execution_slice.target_only_owners);
|
|
396
|
+
const shared = new Set(evidenceMap.execution_slice.shared_owners);
|
|
397
|
+
const strongDifferentialFiles = new Set(evidenceMap.execution_slice.strong_differential_files);
|
|
398
|
+
const receiptWithoutId = {
|
|
399
|
+
version: 3,
|
|
400
|
+
rule: EVIDENCE_GUIDED_SHORTLIST_RULE,
|
|
401
|
+
applied: false,
|
|
402
|
+
reason,
|
|
403
|
+
evidence_level: evidenceMap.level,
|
|
404
|
+
probe_authenticated: evidenceMap.verification.authenticated,
|
|
405
|
+
claim_bound: claimBound,
|
|
406
|
+
requested_limit: limit,
|
|
407
|
+
evidence_slots: optimized.filter((owner) => !baselineSet.has(owner)).length,
|
|
408
|
+
evidence_strategy: evidenceStrategy,
|
|
409
|
+
selected_execution_ratio: selectedExecutionRatio,
|
|
410
|
+
selected_static_rank: selectedStaticRank,
|
|
411
|
+
baseline_candidates: baseline,
|
|
412
|
+
optimized_candidates: optimized,
|
|
413
|
+
promoted_candidates: optimized.filter((owner) => !baselineSet.has(owner)),
|
|
414
|
+
displaced_candidates: baseline.filter((owner) => !optimizedSet.has(owner)),
|
|
415
|
+
behavior_sensitive_files: evidenceMap.intervention_slice.behavior_sensitive_files,
|
|
416
|
+
strong_differential_files: evidenceMap.execution_slice.strong_differential_files,
|
|
417
|
+
exact_owner_enabled: false,
|
|
418
|
+
};
|
|
419
|
+
return {
|
|
420
|
+
candidates: optimized.flatMap((owner, index) => {
|
|
421
|
+
const item = byOwner.get(owner);
|
|
422
|
+
if (!item)
|
|
423
|
+
return [];
|
|
424
|
+
return [{
|
|
425
|
+
...item.candidate,
|
|
426
|
+
baseline_rank: item.baselineRank,
|
|
427
|
+
optimized_rank: index + 1,
|
|
428
|
+
evidence: {
|
|
429
|
+
behavior_sensitive_owner: sensitiveOwners.has(owner),
|
|
430
|
+
behavior_sensitive_file: sensitiveFiles.has(ownerPath(owner)),
|
|
431
|
+
target_only_execution: targetOnly.has(owner),
|
|
432
|
+
shared_execution: shared.has(owner),
|
|
433
|
+
strong_differential_file: strongDifferentialFiles.has(ownerPath(owner)),
|
|
434
|
+
},
|
|
435
|
+
}];
|
|
436
|
+
}),
|
|
437
|
+
receipt: optimizationReceipt(receiptWithoutId),
|
|
438
|
+
evidence_map: evidenceMap,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
export function diagnoseIssueCorrectionStage(issueValue, sources, requestedLimit = CORRECTION_STAGE_CANDIDATE_LIMIT, evidenceValue) {
|
|
442
|
+
const limit = Number.isSafeInteger(requestedLimit)
|
|
443
|
+
? Math.max(1, Math.min(CORRECTION_STAGE_CANDIDATE_LIMIT, requestedLimit))
|
|
444
|
+
: CORRECTION_STAGE_CANDIDATE_LIMIT;
|
|
445
|
+
const optimized = evidenceValue === undefined
|
|
446
|
+
? null
|
|
447
|
+
: optimizeIssueCorrectionCandidates(issueValue, sources, evidenceValue, limit);
|
|
448
|
+
const ranked = rankIssueAdaptiveCorrectionCandidates(issueValue, sources);
|
|
449
|
+
const candidates = optimized?.candidates ?? ranked.slice(0, limit);
|
|
450
|
+
const fileFirstDeclarationClusters = buildFileFirstDeclarationClusters(ranked);
|
|
451
|
+
const progressiveInspection = buildProgressiveDeclarationPlan(ranked, fileFirstDeclarationClusters, limit);
|
|
452
|
+
return {
|
|
453
|
+
stage: inferIssueCorrectionStage(issueValue),
|
|
454
|
+
likely_file: candidates[0]?.owner.split("::")[0] ?? null,
|
|
455
|
+
candidates,
|
|
456
|
+
file_first_declaration_clusters: fileFirstDeclarationClusters,
|
|
457
|
+
progressive_inspection: progressiveInspection,
|
|
458
|
+
exact_owner_enabled: false,
|
|
459
|
+
optimization: optimized?.receipt ?? null,
|
|
460
|
+
calibration: CORRECTION_STAGE_CALIBRATION,
|
|
461
|
+
cross_repository_transfer: CORRECTION_STAGE_TRANSFER,
|
|
462
|
+
adaptive_transfer: ADAPTIVE_CORRECTION_STAGE_TRANSFER,
|
|
463
|
+
adaptive_replication: ADAPTIVE_CORRECTION_STAGE_REPLICATION,
|
|
464
|
+
optimization_policy: CORRECTION_OPTIMIZATION_POLICY,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
export function formatCorrectionStageDiagnostic(diagnostic) {
|
|
468
|
+
const candidates = diagnostic.candidates.length
|
|
469
|
+
? diagnostic.candidates.map((candidate, index) => ` ${index + 1}. ${candidate.owner}`).join("\n")
|
|
470
|
+
: " (none — the issue did not resolve to a declaration in the selected stage)";
|
|
471
|
+
const optimization = diagnostic.optimization
|
|
472
|
+
? [
|
|
473
|
+
`Optimization: ${diagnostic.optimization.applied ? "applied" : "not applied"} — ${diagnostic.optimization.rule} (${diagnostic.optimization.reason})`,
|
|
474
|
+
`Optimization receipt: ${diagnostic.optimization.receipt_id}`,
|
|
475
|
+
`Evidence: ${diagnostic.optimization.evidence_level}; probe ${diagnostic.optimization.probe_authenticated ? "authenticated" : "not authenticated"}; claim ${diagnostic.optimization.claim_bound ? "bound" : "mismatch"}`,
|
|
476
|
+
`Evidence strategy: ${diagnostic.optimization.evidence_strategy ?? "none"}`,
|
|
477
|
+
`Promoted by evidence: ${diagnostic.optimization.promoted_candidates.join(", ") || "none"}`,
|
|
478
|
+
]
|
|
479
|
+
: ["Optimization: no verified evidence receipt supplied"];
|
|
480
|
+
const fileClusters = diagnostic.file_first_declaration_clusters.files.length
|
|
481
|
+
? diagnostic.file_first_declaration_clusters.files.flatMap((file) => [
|
|
482
|
+
` ${file.file_rank}. ${file.path} (file score ${file.file_score})`,
|
|
483
|
+
...file.declaration_clusters.map((cluster, index) => ` ${index + 1}. ${cluster.label}: ${cluster.members.map((member) => member.owner).join(", ")}`),
|
|
484
|
+
])
|
|
485
|
+
: [" (none)"];
|
|
486
|
+
const progressive = diagnostic.progressive_inspection.phases.flatMap((phase) => phase.candidates.length
|
|
487
|
+
? [
|
|
488
|
+
` ${phase.phase}: ${phase.instruction}`,
|
|
489
|
+
...phase.candidates.map((candidate) => ` ${candidate.inspection_rank}. ${candidate.owner}`),
|
|
490
|
+
` Stop: ${phase.stop_condition}`,
|
|
491
|
+
]
|
|
492
|
+
: []);
|
|
493
|
+
return [
|
|
494
|
+
"Correction-stage diagnostic (experimental, read-only)",
|
|
495
|
+
`Stage: ${diagnostic.stage}`,
|
|
496
|
+
`Likely file: ${diagnostic.likely_file ?? "unknown"}`,
|
|
497
|
+
...optimization,
|
|
498
|
+
"Candidate declarations (shortlist only):",
|
|
499
|
+
candidates,
|
|
500
|
+
`File-first declaration clusters (${diagnostic.file_first_declaration_clusters.receipt.rule}):`,
|
|
501
|
+
...fileClusters,
|
|
502
|
+
`File-cluster receipt: ${diagnostic.file_first_declaration_clusters.receipt.receipt_id}`,
|
|
503
|
+
`Progressive inspection plan (${diagnostic.progressive_inspection.receipt.rule}; max ${diagnostic.progressive_inspection.receipt.total_limit}):`,
|
|
504
|
+
...progressive,
|
|
505
|
+
`Progressive-plan receipt: ${diagnostic.progressive_inspection.receipt.receipt_id}`,
|
|
506
|
+
"Only experimentally promoted ranking mechanisms can change this queue; rejected evidence and causal-owner rerankers are annotation-only or disabled.",
|
|
507
|
+
"Progressive-plan transfer: retained all 5/12 full-cluster hits with zero losses while reducing mean inspections from 18.9 to 11 (41.9%); it is an efficiency advisory, not an accuracy promotion (zero fresh rescues).",
|
|
508
|
+
"File-cluster transfer: preserved union 6/12 vs flat top five 3/12 (+25 points); correct file 10/12 vs 8/12; three rescues; exact owner remains disabled.",
|
|
509
|
+
"The flat shortlist is preserved; clusters are bounded inspection families, not exact-owner claims.",
|
|
510
|
+
"Evidence is mixed: the repository-adaptive ranker passed its ArkType/class-validator holdout (top five 9/11; likely file 8/11), then failed a narrower tRPC/Elysia replication (top five 5/11; likely file 4/11). Per-case confidence and exact-owner claims are disabled.",
|
|
511
|
+
].join("\n");
|
|
512
|
+
}
|
|
513
|
+
//# sourceMappingURL=correctionStage.js.map
|