dsh-code-index 0.11.2 → 0.12.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/CHANGELOG.md +12 -0
- package/README.md +18 -5
- package/README.zh.md +18 -5
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +760 -20
- package/dist/index.js +433 -100
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/release-smoke.mjs +45 -7
package/dist/index.js
CHANGED
|
@@ -2304,6 +2304,102 @@ async function readGitFileAtRef(root, ref, repoRelativePath) {
|
|
|
2304
2304
|
|
|
2305
2305
|
// src/change-context.ts
|
|
2306
2306
|
import path7 from "path";
|
|
2307
|
+
|
|
2308
|
+
// src/evidence-summary.ts
|
|
2309
|
+
function summarizeEvidence(index, options) {
|
|
2310
|
+
const status = { parsed: 0, partial: 0, failed: 0, unknown: 0 };
|
|
2311
|
+
const diagnostics = [];
|
|
2312
|
+
for (const file of index.files) {
|
|
2313
|
+
const diagnostic = file.diagnostic;
|
|
2314
|
+
status[diagnostic?.status ?? "unknown"]++;
|
|
2315
|
+
if (diagnostic && diagnostic.status !== "parsed") diagnostics.push({
|
|
2316
|
+
file: file.path,
|
|
2317
|
+
status: diagnostic.status,
|
|
2318
|
+
...diagnostic.issues[0] ? { line: diagnostic.issues[0].line } : {},
|
|
2319
|
+
...diagnostic.reason ? { reason: diagnostic.reason } : {}
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
diagnostics.sort((a, b) => a.file.localeCompare(b.file));
|
|
2323
|
+
const relationships = { exact: 0, "import-scoped": 0, "name-only": 0 };
|
|
2324
|
+
for (const resolution of options.resolutions) relationships[resolution]++;
|
|
2325
|
+
const gaps = [...options.gaps ?? []];
|
|
2326
|
+
if (status.partial || status.failed) gaps.push({ kind: "index-incomplete", code: "parse-diagnostics", source: "indexed-file.diagnostic" });
|
|
2327
|
+
if (status.unknown) gaps.push({ kind: "unavailable", code: "parse-status-unknown", source: "indexed-file.diagnostic" });
|
|
2328
|
+
const result = {
|
|
2329
|
+
scope: "indexed-static-only",
|
|
2330
|
+
basis: options.basis ?? "context-pack",
|
|
2331
|
+
index: {
|
|
2332
|
+
...status,
|
|
2333
|
+
...diagnostics.length ? { diagnostics: diagnostics.slice(0, 3) } : {},
|
|
2334
|
+
...diagnostics.length > 3 ? { diagnosticsOmitted: diagnostics.length - 3 } : {}
|
|
2335
|
+
},
|
|
2336
|
+
relationships,
|
|
2337
|
+
tests: { candidates: new Set(options.testFiles).size, verification: "not-run" }
|
|
2338
|
+
};
|
|
2339
|
+
if (options.graph && options.seeds?.length) {
|
|
2340
|
+
const graph = options.graph;
|
|
2341
|
+
const seeds = [...new Map(options.seeds.map((seed) => [seed.id, seed])).values()];
|
|
2342
|
+
const zero = seeds.filter((seed) => !graph.incoming.get(seed.id)?.length).map((seed) => ({ file: seed.file, line: seed.line, name: seed.name }));
|
|
2343
|
+
result.callers = {
|
|
2344
|
+
checkedSymbols: seeds.length,
|
|
2345
|
+
zero: zero.slice(0, 3),
|
|
2346
|
+
...zero.length > 3 ? { zeroOmitted: zero.length - 3 } : {}
|
|
2347
|
+
};
|
|
2348
|
+
const unresolved = [];
|
|
2349
|
+
for (const seed of seeds) {
|
|
2350
|
+
const edges = graph.outgoing.get(seed.id) ?? [];
|
|
2351
|
+
for (const call of index.files.find((file) => file.path === seed.file)?.calls ?? []) {
|
|
2352
|
+
if (call.fromId !== seed.id) continue;
|
|
2353
|
+
if (!edges.some((edge) => edge.callSite.file === seed.file && edge.callSite.line === call.line && edge.name === call.name)) {
|
|
2354
|
+
unresolved.push({ file: seed.file, line: call.line, name: call.name });
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
if (unresolved.length) {
|
|
2359
|
+
result.unresolved = {
|
|
2360
|
+
count: unresolved.length,
|
|
2361
|
+
sites: unresolved.slice(0, 3),
|
|
2362
|
+
...unresolved.length > 3 ? { sitesOmitted: unresolved.length - 3 } : {}
|
|
2363
|
+
};
|
|
2364
|
+
gaps.push({ kind: "not-found", code: "unresolved-call-targets", source: "indexed-call-sites" });
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
if (gaps.length) result.gaps = [...new Map(gaps.map((gap) => [JSON.stringify(gap), gap])).values()];
|
|
2368
|
+
return result;
|
|
2369
|
+
}
|
|
2370
|
+
function renderEvidenceSummary(summary) {
|
|
2371
|
+
const i = summary.index;
|
|
2372
|
+
const r = summary.relationships;
|
|
2373
|
+
const lines = [
|
|
2374
|
+
`Evidence (indexed static scope; unscanned files not assessed):`,
|
|
2375
|
+
`Index: ${i.parsed} parsed, ${i.partial} partial, ${i.failed} failed, ${i.unknown} unknown.`,
|
|
2376
|
+
`Selected relationship items: exact ${r.exact}, import-scoped ${r["import-scoped"]}, name-only ${r["name-only"]}.`,
|
|
2377
|
+
`Tests: ${summary.tests.candidates} candidate(s), not run; not verified coverage.`
|
|
2378
|
+
];
|
|
2379
|
+
for (const d of i.diagnostics ?? []) lines.push(`Parse: ${d.file}${d.line ? `:${d.line}` : ""} ${d.status}${d.reason ? ` (${d.reason})` : ""}.`);
|
|
2380
|
+
if (i.diagnosticsOmitted) lines.push(`More parse diagnostics: ${i.diagnosticsOmitted}; inspect code_index.`);
|
|
2381
|
+
if (summary.callers?.zero.length) lines.push(`0 callers for ${summary.callers.zero.map((s) => `${s.name} (${s.file}:${s.line})`).join(", ")}: none found in current index/static resolution; not proof of no callers.`);
|
|
2382
|
+
if (summary.callers?.zeroOmitted) lines.push(`0 callers for ${summary.callers.zeroOmitted} other checked declaration(s) in indexed static scope only; not proof of no callers. Locations omitted for budget.`);
|
|
2383
|
+
if (summary.unresolved) lines.push(`Unresolved call sites: ${summary.unresolved.count}; target unknown, external not established.`);
|
|
2384
|
+
if (summary.gaps?.length) lines.push(`Evidence gaps: ${[...new Set(summary.gaps.map((g) => `${g.kind}/${g.code}`))].join(", ")}.`);
|
|
2385
|
+
return lines.join("\n");
|
|
2386
|
+
}
|
|
2387
|
+
function compactEvidence(summary) {
|
|
2388
|
+
const { diagnostics, diagnosticsOmitted, ...counts } = summary.index;
|
|
2389
|
+
const omitted = (diagnostics?.length ?? 0) + (diagnosticsOmitted ?? 0);
|
|
2390
|
+
return {
|
|
2391
|
+
...summary,
|
|
2392
|
+
index: { ...counts, ...omitted ? { diagnosticsOmitted: omitted } : {} },
|
|
2393
|
+
...summary.callers ? { callers: {
|
|
2394
|
+
checkedSymbols: summary.callers.checkedSymbols,
|
|
2395
|
+
zero: [],
|
|
2396
|
+
...summary.callers.zero.length || summary.callers.zeroOmitted ? { zeroOmitted: summary.callers.zero.length + (summary.callers.zeroOmitted ?? 0) } : {}
|
|
2397
|
+
} } : {},
|
|
2398
|
+
...summary.unresolved ? { unresolved: { count: summary.unresolved.count, sites: [], sitesOmitted: summary.unresolved.count } } : {}
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/change-context.ts
|
|
2307
2403
|
var LABEL_STRENGTH = {
|
|
2308
2404
|
exact: 3,
|
|
2309
2405
|
"import-scoped": 2,
|
|
@@ -2518,7 +2614,19 @@ async function buildChangeContext(index, input, options = {}) {
|
|
|
2518
2614
|
frontier = next;
|
|
2519
2615
|
}
|
|
2520
2616
|
}
|
|
2521
|
-
const
|
|
2617
|
+
const focusFiles = new Set(options.focusFiles ?? []);
|
|
2618
|
+
const focusIds = /* @__PURE__ */ new Set();
|
|
2619
|
+
if (focusFiles.size) {
|
|
2620
|
+
const impactAncestors = new Map([...allCallers, ...impactById.values()].map((row) => [row.symbol.id, row]));
|
|
2621
|
+
for (const row of impactById.values()) if (focusFiles.has(row.symbol.file)) {
|
|
2622
|
+
let cursor = row;
|
|
2623
|
+
for (let depth = 0; cursor && depth < maxDepth; depth++) {
|
|
2624
|
+
focusIds.add(cursor.symbol.id);
|
|
2625
|
+
cursor = impactAncestors.get(cursor.via.toId);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
const allImpact = [...impactById.values()].sort((a, b) => Number(focusIds.has(b.symbol.id)) - Number(focusIds.has(a.symbol.id)) || compareByDepthThenSymbol(a, b));
|
|
2522
2630
|
const allPaths = [];
|
|
2523
2631
|
const pathKey = /* @__PURE__ */ new Set();
|
|
2524
2632
|
for (const changed of changedSymbols) {
|
|
@@ -2545,7 +2653,7 @@ async function buildChangeContext(index, input, options = {}) {
|
|
|
2545
2653
|
impact: allImpact.length > maxImpact,
|
|
2546
2654
|
tests: allTests.length > maxTests
|
|
2547
2655
|
};
|
|
2548
|
-
|
|
2656
|
+
const result = {
|
|
2549
2657
|
root: index.root,
|
|
2550
2658
|
source: input.kind,
|
|
2551
2659
|
...baseRef !== void 0 ? { baseRef } : {},
|
|
@@ -2559,6 +2667,26 @@ async function buildChangeContext(index, input, options = {}) {
|
|
|
2559
2667
|
warnings,
|
|
2560
2668
|
truncated
|
|
2561
2669
|
};
|
|
2670
|
+
const gaps = [];
|
|
2671
|
+
if (!changedSymbols.length) gaps.push({ kind: "not-found", code: "no-changed-symbols", source: "diff-or-selection" });
|
|
2672
|
+
if (!allTests.length) gaps.push({ kind: "not-found", code: "no-test-candidates", source: "indexed-test-discovery" });
|
|
2673
|
+
if (Object.values(truncated).some(Boolean)) gaps.push({ kind: "selection-limit", code: "analysis-row-cap", source: "change-analysis" });
|
|
2674
|
+
for (const entry of result.unmapped.slice(0, 3)) gaps.push({
|
|
2675
|
+
kind: entry.reason === "base-content-unavailable" ? "unavailable" : "not-found",
|
|
2676
|
+
code: entry.reason,
|
|
2677
|
+
source: entry.reason === "base-content-unavailable" ? "baseline-content" : "diff-symbol-mapping",
|
|
2678
|
+
file: entry.file,
|
|
2679
|
+
side: entry.side
|
|
2680
|
+
});
|
|
2681
|
+
result.evidence = summarizeEvidence(index, {
|
|
2682
|
+
graph,
|
|
2683
|
+
basis: "change-analysis",
|
|
2684
|
+
seeds: changedSymbols.filter((row) => row.side === "current").map((row) => row.symbol),
|
|
2685
|
+
resolutions: [...result.directCallers, ...result.impact, ...result.importDependents, ...result.paths].map((row) => row.resolution),
|
|
2686
|
+
testFiles: result.tests.map((row) => row.file),
|
|
2687
|
+
gaps
|
|
2688
|
+
});
|
|
2689
|
+
return result;
|
|
2562
2690
|
}
|
|
2563
2691
|
function hopOf(edge) {
|
|
2564
2692
|
return {
|
|
@@ -2917,7 +3045,7 @@ function renderChangeContext(result, maxChars = 12e3) {
|
|
|
2917
3045
|
{
|
|
2918
3046
|
title: "LIKELY AFFECTED TESTS",
|
|
2919
3047
|
rows: result.tests.map(
|
|
2920
|
-
(test) => `${test.file} (${test.reason}${test.distance !== void 0 ? `, distance ${test.distance}` : ""}) [${test.resolution}]`
|
|
3048
|
+
(test) => `${test.file} (candidate; not run; coverage unverified; ${test.reason}${test.distance !== void 0 ? `, distance ${test.distance}` : ""}) [${test.resolution}]`
|
|
2921
3049
|
)
|
|
2922
3050
|
},
|
|
2923
3051
|
{
|
|
@@ -2929,11 +3057,12 @@ function renderChangeContext(result, maxChars = 12e3) {
|
|
|
2929
3057
|
];
|
|
2930
3058
|
const summary = `change context \u2014 source: ${result.source}${result.baseRef !== void 0 ? ` (base ${result.baseRef})` : ""}: ${result.changed.length} changed symbol(s)`;
|
|
2931
3059
|
const warningLines = result.warnings.map((warning) => `- ${warning}`);
|
|
3060
|
+
let evidenceText = result.evidence ? renderEvidenceSummary(result.evidence) : "";
|
|
2932
3061
|
const render = (kept) => {
|
|
2933
3062
|
const lines = [summary];
|
|
2934
3063
|
sections.forEach((section, index) => {
|
|
2935
3064
|
if (section.rows.length === 0) {
|
|
2936
|
-
lines.push(`${section.title}: (none)`);
|
|
3065
|
+
lines.push(`${section.title}: ${section.title === "DIRECT CALLERS" ? "(none found in current indexed static scope)" : "(none)"}`);
|
|
2937
3066
|
return;
|
|
2938
3067
|
}
|
|
2939
3068
|
lines.push(`${section.title}:`);
|
|
@@ -2941,6 +3070,7 @@ function renderChangeContext(result, maxChars = 12e3) {
|
|
|
2941
3070
|
for (let row = 0; row < take; row++) lines.push(` ${section.rows[row]}`);
|
|
2942
3071
|
if (take < section.rows.length) lines.push(` \u2026 (${section.rows.length - take} more)`);
|
|
2943
3072
|
});
|
|
3073
|
+
if (evidenceText) lines.push(evidenceText);
|
|
2944
3074
|
lines.push("WARNINGS:");
|
|
2945
3075
|
if (warningLines.length === 0) lines.push(" (none)");
|
|
2946
3076
|
else for (const warning of warningLines) lines.push(` ${warning}`);
|
|
@@ -2956,6 +3086,7 @@ function renderChangeContext(result, maxChars = 12e3) {
|
|
|
2956
3086
|
return kept;
|
|
2957
3087
|
};
|
|
2958
3088
|
const total = flattened.length;
|
|
3089
|
+
if (render(countsFor(0)).length > maxChars) evidenceText = "";
|
|
2959
3090
|
const full = render(sections.map((section) => section.rows.length));
|
|
2960
3091
|
if (full.length <= maxChars) return full;
|
|
2961
3092
|
const truncationNote = "\n\u2026 truncated rows to fit the character cap (raise maxChars or narrow the change)";
|
|
@@ -2979,6 +3110,75 @@ function renderChangeContext(result, maxChars = 12e3) {
|
|
|
2979
3110
|
return out;
|
|
2980
3111
|
}
|
|
2981
3112
|
|
|
3113
|
+
// src/context-terms.ts
|
|
3114
|
+
var STOP_WORDS = /* @__PURE__ */ new Set(
|
|
3115
|
+
[
|
|
3116
|
+
"a",
|
|
3117
|
+
"an",
|
|
3118
|
+
"and",
|
|
3119
|
+
"are",
|
|
3120
|
+
"be",
|
|
3121
|
+
"by",
|
|
3122
|
+
"does",
|
|
3123
|
+
"for",
|
|
3124
|
+
"from",
|
|
3125
|
+
"how",
|
|
3126
|
+
"in",
|
|
3127
|
+
"is",
|
|
3128
|
+
"it",
|
|
3129
|
+
"my",
|
|
3130
|
+
"of",
|
|
3131
|
+
"on",
|
|
3132
|
+
"or",
|
|
3133
|
+
"the",
|
|
3134
|
+
"this",
|
|
3135
|
+
"to",
|
|
3136
|
+
"what",
|
|
3137
|
+
"where",
|
|
3138
|
+
"why",
|
|
3139
|
+
"with",
|
|
3140
|
+
"src",
|
|
3141
|
+
"lib",
|
|
3142
|
+
"test",
|
|
3143
|
+
"tests",
|
|
3144
|
+
"spec",
|
|
3145
|
+
"fix",
|
|
3146
|
+
"explain",
|
|
3147
|
+
"please",
|
|
3148
|
+
"help",
|
|
3149
|
+
"current",
|
|
3150
|
+
"change",
|
|
3151
|
+
"changes",
|
|
3152
|
+
"code",
|
|
3153
|
+
"repo",
|
|
3154
|
+
"repository",
|
|
3155
|
+
"function",
|
|
3156
|
+
"symbol",
|
|
3157
|
+
"git"
|
|
3158
|
+
]
|
|
3159
|
+
);
|
|
3160
|
+
var CONCEPT_TERMS = [
|
|
3161
|
+
[/忽略|白名单|嵌套规则|gitignore/iu, "ignore git"],
|
|
3162
|
+
[/扫描/u, "scan"],
|
|
3163
|
+
[/索引/u, "index scan"],
|
|
3164
|
+
[/监听|watcher/iu, "watch"],
|
|
3165
|
+
[/刷新/u, "refresh"],
|
|
3166
|
+
[/工具/u, "tool"],
|
|
3167
|
+
[/配置/u, "config"],
|
|
3168
|
+
[/加载/u, "load"],
|
|
3169
|
+
[/空白|空格/u, "whitespace"],
|
|
3170
|
+
[/启动/u, "startup"]
|
|
3171
|
+
];
|
|
3172
|
+
function contextTerms(text) {
|
|
3173
|
+
const concepts = CONCEPT_TERMS.filter(([pattern]) => pattern.test(text)).map(([, terms]) => terms).join(" ");
|
|
3174
|
+
return [...new Set(`${concepts} ${text}`.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length >= 3 && !STOP_WORDS.has(term)).flatMap((term) => [
|
|
3175
|
+
term,
|
|
3176
|
+
...term.length > 5 && term.endsWith("ing") ? [term.slice(0, -3)] : [],
|
|
3177
|
+
...term.length > 5 && term.endsWith("ed") ? [term.slice(0, -1), term.slice(0, -2)] : [],
|
|
3178
|
+
...term.length > 4 && term.endsWith("s") ? [term.slice(0, -1)] : []
|
|
3179
|
+
]))];
|
|
3180
|
+
}
|
|
3181
|
+
|
|
2982
3182
|
// src/source-excerpts.ts
|
|
2983
3183
|
import { readFile as readFile6, realpath, stat as stat3 } from "fs/promises";
|
|
2984
3184
|
import path8 from "path";
|
|
@@ -3085,9 +3285,11 @@ ${item.kind}: ${item.name ?? item.text ?? ""}${location}${item.side ? ` [${item.
|
|
|
3085
3285
|
${item.code}`);
|
|
3086
3286
|
} else if (item.mode === "signature") rows.push(`Signature only: ${item.signature || "(source unavailable within budget)"}`);
|
|
3087
3287
|
}
|
|
3288
|
+
if (pack.evidence) rows.push(`
|
|
3289
|
+
${renderEvidenceSummary(pack.evidence)}`);
|
|
3088
3290
|
if (pack.gaps.length) rows.push(`
|
|
3089
3291
|
Gaps: ${pack.gaps.join("; ")}`);
|
|
3090
|
-
rows.push(`Budget: ${pack.budget.usedChars} / ${pack.budget.budgetChars} chars${pack.budget.truncated ? " (
|
|
3292
|
+
rows.push(`Budget: ${pack.budget.usedChars} / ${pack.budget.budgetChars} chars${pack.budget.truncated ? " (bounded output; see evidence/gaps)" : ""}`);
|
|
3091
3293
|
return rows.join("\n");
|
|
3092
3294
|
}
|
|
3093
3295
|
function account(pack) {
|
|
@@ -3099,32 +3301,48 @@ function account(pack) {
|
|
|
3099
3301
|
pack.budget.packChars = packChars;
|
|
3100
3302
|
}
|
|
3101
3303
|
}
|
|
3102
|
-
async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
3304
|
+
async function selectContextPack(index, context, budgetChars, maxFiles = 12, graph = buildReferenceGraph(index)) {
|
|
3103
3305
|
const read = sourceReader(index);
|
|
3104
|
-
const graph = buildReferenceGraph(index);
|
|
3105
3306
|
const candidates = [];
|
|
3307
|
+
const selection = /* @__PURE__ */ new Map();
|
|
3106
3308
|
const seen = /* @__PURE__ */ new Set();
|
|
3107
|
-
const gaps = [...context.warnings];
|
|
3309
|
+
const gaps = ["test candidates are static leads, not verified coverage", ...context.warnings];
|
|
3108
3310
|
const unavailable = /* @__PURE__ */ new Set();
|
|
3109
|
-
const
|
|
3311
|
+
const queryTerms = new Set(context.route.queries.flatMap(contextTerms));
|
|
3312
|
+
const primaryFiles = new Set(context.primarySymbols.map((seed) => seed.symbol.file));
|
|
3313
|
+
const fileSet = new Set(index.files.map((file) => file.path));
|
|
3314
|
+
const source = async (symbol, kind, reason, extra = {}, focus, priority = 50) => {
|
|
3110
3315
|
const side = extra.side ?? "current";
|
|
3111
3316
|
const key = `${side}:${extra.ref ?? ""}:${kind}:${symbol.file}:${symbol.line}:${symbol.name}`;
|
|
3112
3317
|
if (seen.has(key)) return;
|
|
3113
3318
|
seen.add(key);
|
|
3114
3319
|
const lines = await read(symbol.file, side, extra.ref);
|
|
3115
3320
|
if (!lines) unavailable.add(`${side}:${symbol.file}`);
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
}
|
|
3321
|
+
if (side === "current" && (focus === void 0 || focus <= symbol.line + 5) && lines && (symbol.kind === "function" || symbol.kind === "method")) {
|
|
3322
|
+
const calls = index.files.find((file) => file.path === symbol.file)?.calls ?? [];
|
|
3323
|
+
const relevant = calls.filter((call) => call.line > symbol.line && call.line <= symbol.endLine).map((call) => {
|
|
3324
|
+
const terms = contextTerms(call.name).filter((term) => queryTerms.has(term)).length;
|
|
3325
|
+
return { line: call.line, terms, score: terms * 4 + (context.primarySymbols.some((seed) => seed.symbol.name === call.name) ? 3 : 0) + (/\b(?:if|return|while)\b/.test(lines[call.line - 1]) ? 2 : 0) };
|
|
3326
|
+
}).filter((call) => call.terms > 0).sort((a, b) => b.score - a.score || a.line - b.line);
|
|
3327
|
+
focus = relevant[0]?.line ?? focus;
|
|
3328
|
+
}
|
|
3329
|
+
candidates.push({
|
|
3330
|
+
priority,
|
|
3331
|
+
range: { start: symbol.line, end: symbol.endLine },
|
|
3332
|
+
protected: kind === "primary" && (symbol.kind === "function" || symbol.kind === "method" || context.route.exactSymbolNames.includes(symbol.name)) || kind === "change" || (kind === "caller" || kind === "callee") && (priority >= 75 || context.route.mentionedFiles.some((file) => symbol.file.toLowerCase() === file.toLowerCase() || symbol.file.toLowerCase().endsWith(`/${file.toLowerCase()}`))) || kind === "test" && priority >= 60,
|
|
3333
|
+
variants: excerptVariants(lines, symbol, side, extra.ref, focus).map((excerpt) => ({
|
|
3334
|
+
...excerpt,
|
|
3335
|
+
...symbol.id === "" && excerpt.mode === "complete" ? { mode: "window" } : {},
|
|
3336
|
+
kind,
|
|
3337
|
+
name: symbol.name,
|
|
3338
|
+
reason,
|
|
3339
|
+
...extra
|
|
3340
|
+
}))
|
|
3341
|
+
});
|
|
3124
3342
|
};
|
|
3125
|
-
for (const primary of context.primarySymbols) {
|
|
3343
|
+
for (const [rank, primary] of context.primarySymbols.entries()) {
|
|
3126
3344
|
const changed = context.changeContext?.changed.find((row) => row.side === "current" && row.symbol.id === primary.symbol.id);
|
|
3127
|
-
await source(primary.symbol, "primary", primary.reason, changed ? { change: changed.change } : {}, changed?.focusLine);
|
|
3345
|
+
await source(primary.symbol, "primary", primary.reason, changed ? { change: changed.change } : {}, changed?.focusLine, rank < 4 ? 100 : 45);
|
|
3128
3346
|
}
|
|
3129
3347
|
for (const changed of context.changeContext?.changed ?? []) {
|
|
3130
3348
|
if (changed.side === "current" && context.primarySymbols.some((seed) => seed.symbol.id === changed.symbol.id)) continue;
|
|
@@ -3132,14 +3350,22 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3132
3350
|
side: changed.side,
|
|
3133
3351
|
...changed.side === "base" ? { ref: context.changeContext.baseRef } : {},
|
|
3134
3352
|
change: changed.change
|
|
3135
|
-
}, changed.focusLine);
|
|
3353
|
+
}, changed.focusLine, changed.side === "current" ? 80 : 40);
|
|
3136
3354
|
}
|
|
3137
|
-
|
|
3355
|
+
const stem = (file) => file.split("/").at(-1).replace(/\.(?:spec|test)(?=\.)/i, "").replace(/\.[^.]+$/, "");
|
|
3356
|
+
const seedStems = new Set([...primaryFiles].map(stem));
|
|
3357
|
+
const sortedTests = [...context.testEvidence].sort((a, b) => Number(seedStems.has(stem(b.file))) - Number(seedStems.has(stem(a.file))));
|
|
3358
|
+
for (const [rank, test] of sortedTests.entries()) {
|
|
3138
3359
|
const file = index.files.find((row) => row.path === test.file);
|
|
3139
3360
|
const seedNames = new Set(context.primarySymbols.map((seed) => seed.symbol.name));
|
|
3361
|
+
for (const imported of file.importDetails ?? []) if (primaryFiles.has(resolveImport(imported.specifier, file.path, fileSet) ?? "")) {
|
|
3362
|
+
for (const binding of imported.bindings) seedNames.add(binding.local);
|
|
3363
|
+
}
|
|
3140
3364
|
const call = file.calls?.find((call2) => seedNames.has(call2.name));
|
|
3141
3365
|
const lines = await read(file.path, "current");
|
|
3142
3366
|
const symbol = file.symbols.find((symbol2) => call && symbol2.line <= call.line && symbol2.endLine >= call.line);
|
|
3367
|
+
const changedTest = context.changeContext?.changed.some((row) => row.side === "current" && row.symbol.file === test.file);
|
|
3368
|
+
const reason = test.reason === "imports-changed-file" ? "test imports primary seed file" : test.reason === "changed-test" ? changedTest ? "test declaration intersects Git change" : "primary seed is a test file" : test.reason;
|
|
3143
3369
|
await source(
|
|
3144
3370
|
symbol ?? {
|
|
3145
3371
|
id: "",
|
|
@@ -3154,9 +3380,10 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3154
3380
|
exported: false
|
|
3155
3381
|
},
|
|
3156
3382
|
"test",
|
|
3157
|
-
|
|
3158
|
-
{ resolution: test.resolution, provenance: test.reason },
|
|
3159
|
-
call?.line
|
|
3383
|
+
`candidate test (not verified coverage): ${reason}`,
|
|
3384
|
+
{ resolution: test.resolution, provenance: test.reason === "changed-test" && !changedTest ? "primary seed file; not Git change evidence" : test.reason },
|
|
3385
|
+
call?.line,
|
|
3386
|
+
rank < 2 ? 60 : 30
|
|
3160
3387
|
);
|
|
3161
3388
|
}
|
|
3162
3389
|
for (const primary of context.primarySymbols.slice(0, 4)) {
|
|
@@ -3171,7 +3398,8 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3171
3398
|
direction,
|
|
3172
3399
|
`direct ${direction} of ${primary.symbol.name}`,
|
|
3173
3400
|
{ resolution: edge.resolution, provenance: edge.provenance },
|
|
3174
|
-
direction === "caller" ? edge.callSite.line : symbol.line
|
|
3401
|
+
direction === "caller" ? edge.callSite.line : symbol.line,
|
|
3402
|
+
75
|
|
3175
3403
|
);
|
|
3176
3404
|
}
|
|
3177
3405
|
}
|
|
@@ -3197,30 +3425,47 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3197
3425
|
},
|
|
3198
3426
|
"import",
|
|
3199
3427
|
`import binding referenced in primary source ${primary.symbol.file}`,
|
|
3200
|
-
{ resolution: "import-scoped", provenance: "import statement" }
|
|
3428
|
+
{ resolution: "import-scoped", provenance: "import statement" },
|
|
3429
|
+
void 0,
|
|
3430
|
+
35
|
|
3201
3431
|
);
|
|
3202
3432
|
}
|
|
3203
3433
|
}
|
|
3204
|
-
|
|
3434
|
+
const mentioned = (file) => context.route.mentionedFiles.some((path11) => file.toLowerCase() === path11.toLowerCase() || file.toLowerCase().endsWith(`/${path11.toLowerCase()}`));
|
|
3435
|
+
const isTest = (file) => /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\./i.test(file);
|
|
3436
|
+
const impactSources = (context.changeContext?.impact ?? []).filter((row) => row.via.resolution !== "name-only").sort((a, b) => Number(mentioned(b.symbol.file)) - Number(mentioned(a.symbol.file)) || Number(isTest(a.symbol.file)) - Number(isTest(b.symbol.file)) || a.depth - b.depth);
|
|
3437
|
+
for (const row of impactSources.slice(0, 4)) {
|
|
3438
|
+
if (!context.primarySymbols.some((seed) => seed.symbol.id === row.symbol.id)) {
|
|
3439
|
+
await source(row.symbol, "caller", `candidate transitive caller at depth ${row.depth}; weakest path: ${row.resolution}`, {
|
|
3440
|
+
resolution: row.resolution,
|
|
3441
|
+
provenance: row.via.reason
|
|
3442
|
+
}, row.via.callSite.line, 70);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
for (const relation of context.relationships) candidates.push({ priority: 20, variants: [{
|
|
3205
3446
|
kind: "relationship",
|
|
3206
3447
|
text: relation.text,
|
|
3207
3448
|
reason: "bounded primary graph neighborhood",
|
|
3208
3449
|
resolution: relation.resolution,
|
|
3209
3450
|
provenance: relation.provenance
|
|
3210
|
-
}]);
|
|
3451
|
+
}] });
|
|
3211
3452
|
if (!context.primarySymbols.length) for (const file of context.relevantFiles.slice(0, 4)) {
|
|
3212
|
-
candidates.push([{ kind: "file", file: file.path, reason: file.reason }]);
|
|
3453
|
+
candidates.push({ priority: 10, variants: [{ kind: "file", file: file.path, reason: file.reason }] });
|
|
3213
3454
|
}
|
|
3214
3455
|
if (!context.primarySymbols.length) gaps.push("no strong primary symbol; specify a symbol, path, or failing behavior");
|
|
3215
3456
|
if (!context.testEvidence.length) gaps.push("no related indexed test found");
|
|
3216
3457
|
gaps.push(...context.warnings);
|
|
3458
|
+
const initialGaps = [];
|
|
3459
|
+
if (context.warnings.some((warning) => warning.startsWith("index incomplete:"))) initialGaps.push("index incomplete; inspect code_index");
|
|
3460
|
+
if (context.route.includeChangeContext && !context.changeContext) initialGaps.push("change context unavailable; source: change-analysis");
|
|
3461
|
+
if (!initialGaps.length) initialGaps.push("indexed static only");
|
|
3217
3462
|
const pack = {
|
|
3218
3463
|
version: 1,
|
|
3219
3464
|
root: index.root,
|
|
3220
3465
|
task: context.task.slice(0, 120),
|
|
3221
3466
|
route: context.route.kind,
|
|
3222
3467
|
items: [],
|
|
3223
|
-
gaps:
|
|
3468
|
+
gaps: initialGaps,
|
|
3224
3469
|
budget: { budgetChars, usedChars: 0, packChars: 0, truncated: context.task.length > 120 }
|
|
3225
3470
|
};
|
|
3226
3471
|
const fits = () => {
|
|
@@ -3236,25 +3481,35 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3236
3481
|
fits();
|
|
3237
3482
|
}
|
|
3238
3483
|
if (!fits()) throw new Error("budget too small for repository identity");
|
|
3239
|
-
|
|
3484
|
+
let budgetLimited = false;
|
|
3485
|
+
let fileLimited = false;
|
|
3486
|
+
for (const entry of candidates.sort((a, b) => b.priority - a.priority)) {
|
|
3487
|
+
const { variants } = entry;
|
|
3240
3488
|
let added = false;
|
|
3241
3489
|
for (const candidate of variants) {
|
|
3490
|
+
if (context.primarySymbols.length > 4 && candidate.code !== void 0 && JSON.stringify(candidate).length > Math.max(600, Math.floor(budgetChars / 8))) {
|
|
3491
|
+
budgetLimited = true;
|
|
3492
|
+
continue;
|
|
3493
|
+
}
|
|
3242
3494
|
const fragments = candidate.code !== void 0 ? subtractExcerpt(candidate, pack.items.filter((item) => item.code !== void 0)).map((fragment) => ({ ...candidate, ...fragment })) : [candidate];
|
|
3243
3495
|
if (!fragments.length) continue;
|
|
3244
3496
|
pack.items.push(...fragments);
|
|
3245
3497
|
const fileCount = new Set(pack.items.flatMap((item) => item.file ? [item.file] : [])).size;
|
|
3246
|
-
|
|
3498
|
+
const withinBudget = fits();
|
|
3499
|
+
if (withinBudget && fileCount <= maxFiles) {
|
|
3500
|
+
for (const item of fragments) selection.set(item, entry);
|
|
3247
3501
|
added = true;
|
|
3248
3502
|
if (candidate.mode === "signature") pack.budget.truncated = true;
|
|
3249
3503
|
break;
|
|
3250
3504
|
}
|
|
3505
|
+
if (!withinBudget) budgetLimited = true;
|
|
3506
|
+
if (fileCount > maxFiles) fileLimited = true;
|
|
3251
3507
|
pack.items.splice(pack.items.length - fragments.length);
|
|
3252
3508
|
}
|
|
3253
3509
|
if (!added) pack.budget.truncated = true;
|
|
3254
3510
|
}
|
|
3255
|
-
if (!pack.budget.truncated) pack.gaps = pack.gaps.filter((gap) => gap.startsWith("index incomplete"));
|
|
3256
3511
|
for (const item of pack.items) if (item.file && unavailable.has(`${item.side}:${item.file}`)) {
|
|
3257
|
-
gaps.push(`source unavailable
|
|
3512
|
+
gaps.push(`source unavailable (reader rejected): ${item.file} (${item.side})`);
|
|
3258
3513
|
}
|
|
3259
3514
|
for (const gap of [...new Set(gaps)].slice(0, 4)) {
|
|
3260
3515
|
pack.gaps.push(gap.slice(0, 140));
|
|
@@ -3263,9 +3518,76 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3263
3518
|
account(pack);
|
|
3264
3519
|
while (Math.max(pack.budget.usedChars, pack.budget.packChars) > budgetChars && pack.items.length) {
|
|
3265
3520
|
pack.items.pop();
|
|
3521
|
+
budgetLimited = true;
|
|
3266
3522
|
pack.budget.truncated = true;
|
|
3267
3523
|
account(pack);
|
|
3268
3524
|
}
|
|
3525
|
+
const summary = () => {
|
|
3526
|
+
const detected = [];
|
|
3527
|
+
if (!context.primarySymbols.length) detected.push({ kind: "not-found", code: "no-primary-symbol", source: "task-selection" });
|
|
3528
|
+
if (!context.testEvidence.length) detected.push({ kind: "not-found", code: "no-test-candidates", source: "indexed-test-discovery" });
|
|
3529
|
+
if (context.route.includeChangeContext && !context.changeContext) detected.push({ kind: "unavailable", code: "change-context-unavailable", source: "change-analysis" });
|
|
3530
|
+
if (context.task.length > 120) detected.push({ kind: "selection-limit", code: "task-summary-shortened", source: "task-summary" });
|
|
3531
|
+
if (fileLimited) detected.push({ kind: "selection-limit", code: "max-files", source: "pack-selection" });
|
|
3532
|
+
if (budgetLimited) detected.push({ kind: "budget", code: "pack-selection-trimmed", source: "text-and-json-budget" });
|
|
3533
|
+
const missingWindowLines = pack.items.some((item) => {
|
|
3534
|
+
const range = selection.get(item)?.range;
|
|
3535
|
+
if (item.mode !== "window" || !range) return false;
|
|
3536
|
+
let next = range.start;
|
|
3537
|
+
const covered = pack.items.filter((other) => other.code !== void 0 && other.file === item.file && other.side === item.side && other.ref === item.ref).sort((a, b) => a.startLine - b.startLine);
|
|
3538
|
+
for (const other of covered) {
|
|
3539
|
+
if (other.startLine > next) break;
|
|
3540
|
+
next = Math.max(next, other.endLine + 1);
|
|
3541
|
+
}
|
|
3542
|
+
return next <= range.end;
|
|
3543
|
+
});
|
|
3544
|
+
if (missingWindowLines) detected.push({ kind: "selection-limit", code: "bounded-source-window", source: "source-excerpts" });
|
|
3545
|
+
for (const item of pack.items) if (item.file && unavailable.has(`${item.side}:${item.file}`)) detected.push({
|
|
3546
|
+
kind: "unavailable",
|
|
3547
|
+
code: "source-reader-rejected",
|
|
3548
|
+
source: "source-reader",
|
|
3549
|
+
file: item.file,
|
|
3550
|
+
side: item.side
|
|
3551
|
+
});
|
|
3552
|
+
const seeds = context.primarySymbols.filter((seed) => pack.items.some((item) => item.kind === "primary" && item.file === seed.symbol.file && item.name === seed.symbol.name && item.side === "current")).map((seed) => seed.symbol);
|
|
3553
|
+
return summarizeEvidence(index, {
|
|
3554
|
+
graph: context.route.includeRelationships ? graph : void 0,
|
|
3555
|
+
seeds,
|
|
3556
|
+
resolutions: pack.items.flatMap((item) => item.resolution && item.kind !== "test" ? [item.resolution] : []),
|
|
3557
|
+
testFiles: pack.items.flatMap((item) => item.kind === "test" && item.file ? [item.file] : []),
|
|
3558
|
+
gaps: detected
|
|
3559
|
+
});
|
|
3560
|
+
};
|
|
3561
|
+
const removable = [...pack.items].filter((item) => !selection.get(item)?.protected).sort((a, b) => (selection.get(a)?.priority ?? 0) - (selection.get(b)?.priority ?? 0));
|
|
3562
|
+
const originalItems = [...pack.items];
|
|
3563
|
+
const originalTruncated = pack.budget.truncated;
|
|
3564
|
+
const originalBudgetLimited = budgetLimited;
|
|
3565
|
+
pack.evidence = summary();
|
|
3566
|
+
if (!fits()) pack.evidence = compactEvidence(summary());
|
|
3567
|
+
while (!fits() && removable.length) {
|
|
3568
|
+
const item = removable.shift();
|
|
3569
|
+
pack.items.splice(pack.items.indexOf(item), 1);
|
|
3570
|
+
budgetLimited = true;
|
|
3571
|
+
pack.budget.truncated = true;
|
|
3572
|
+
pack.evidence = compactEvidence(summary());
|
|
3573
|
+
}
|
|
3574
|
+
if (!fits()) {
|
|
3575
|
+
delete pack.evidence;
|
|
3576
|
+
pack.items = originalItems;
|
|
3577
|
+
pack.budget.truncated = originalTruncated;
|
|
3578
|
+
budgetLimited = originalBudgetLimited;
|
|
3579
|
+
if (pack.items.some((item) => item.kind === "test" && item.reason.includes("not verified coverage"))) {
|
|
3580
|
+
pack.gaps = pack.gaps.filter((gap) => gap !== "test candidates are static leads, not verified coverage");
|
|
3581
|
+
}
|
|
3582
|
+
const note = "budget: summary omitted";
|
|
3583
|
+
pack.gaps.push(note);
|
|
3584
|
+
if (!fits()) pack.gaps.pop();
|
|
3585
|
+
} else {
|
|
3586
|
+
const compact = pack.evidence;
|
|
3587
|
+
pack.evidence = summary();
|
|
3588
|
+
if (!fits()) pack.evidence = compact;
|
|
3589
|
+
}
|
|
3590
|
+
account(pack);
|
|
3269
3591
|
return pack;
|
|
3270
3592
|
}
|
|
3271
3593
|
|
|
@@ -3273,47 +3595,30 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
|
|
|
3273
3595
|
var TEST_PATH_RE2 = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.(?:[cm]?[jt]sx?|ets)$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/i;
|
|
3274
3596
|
var FILE_TOKEN_RE = /[A-Za-z0-9_./\\-]+\.[A-Za-z0-9]+/g;
|
|
3275
3597
|
var CODE_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?/g;
|
|
3276
|
-
var
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
"why",
|
|
3301
|
-
"with",
|
|
3302
|
-
"src",
|
|
3303
|
-
"lib",
|
|
3304
|
-
"test",
|
|
3305
|
-
"tests",
|
|
3306
|
-
"spec",
|
|
3307
|
-
"fix",
|
|
3308
|
-
"explain",
|
|
3309
|
-
"please",
|
|
3310
|
-
"help",
|
|
3311
|
-
"current",
|
|
3312
|
-
"change",
|
|
3313
|
-
"changes",
|
|
3314
|
-
"code"
|
|
3315
|
-
]
|
|
3316
|
-
);
|
|
3598
|
+
var PROSE_SYMBOL_WORDS = /* @__PURE__ */ new Set([
|
|
3599
|
+
"config",
|
|
3600
|
+
"configuration",
|
|
3601
|
+
"loading",
|
|
3602
|
+
"startup",
|
|
3603
|
+
"file",
|
|
3604
|
+
"files",
|
|
3605
|
+
"path",
|
|
3606
|
+
"directory",
|
|
3607
|
+
"index",
|
|
3608
|
+
"scan",
|
|
3609
|
+
"tool",
|
|
3610
|
+
"flow",
|
|
3611
|
+
"coverage",
|
|
3612
|
+
"error",
|
|
3613
|
+
"errors",
|
|
3614
|
+
"impact",
|
|
3615
|
+
"source",
|
|
3616
|
+
"keep",
|
|
3617
|
+
"other",
|
|
3618
|
+
"behavior",
|
|
3619
|
+
"scope",
|
|
3620
|
+
"root"
|
|
3621
|
+
]);
|
|
3317
3622
|
var CHANGE_RE = /\b(fix|bug|broken|breaks?|change|changed|current|working\s+tree|regression|impact|modified|duplicate|fails?|failure)\b/i;
|
|
3318
3623
|
var TEST_RE3 = /\b(test|tests|spec|specs|failing|assert|coverage)\b/i;
|
|
3319
3624
|
var ARCH_RE = /\b(architecture|architectural|flow|loading|authentication|authorization|plugin|startup|overview|pipeline|lifecycle|design|works?)\b/i;
|
|
@@ -3343,32 +3648,40 @@ function fileMentionMatches(file, mentions) {
|
|
|
3343
3648
|
const lower = file.toLowerCase();
|
|
3344
3649
|
return mentions.some((mention) => {
|
|
3345
3650
|
const normalized = mention.replace(/\\/g, "/").toLowerCase();
|
|
3346
|
-
return lower === normalized || lower.endsWith(`/${normalized}`)
|
|
3651
|
+
return lower === normalized || lower.endsWith(`/${normalized}`);
|
|
3347
3652
|
});
|
|
3348
3653
|
}
|
|
3349
|
-
function contextTerms(text) {
|
|
3350
|
-
return unique(text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length >= 3 && !STOP_WORDS.has(term)).flatMap((term) => [
|
|
3351
|
-
term,
|
|
3352
|
-
...term.length > 5 && term.endsWith("ing") ? [term.slice(0, -3)] : [],
|
|
3353
|
-
...term.length > 4 && term.endsWith("s") ? [term.slice(0, -1)] : []
|
|
3354
|
-
]), (term) => term);
|
|
3355
|
-
}
|
|
3356
3654
|
function routeTask(task, index) {
|
|
3357
3655
|
const text = task.trim();
|
|
3358
3656
|
const mentionedFiles = unique(text.match(FILE_TOKEN_RE) ?? [], (value) => value.toLowerCase()).map((value) => value.replace(/\\/g, "/")).filter((value) => !index || index.files.some((file) => fileMentionMatches(file.path, [value])));
|
|
3657
|
+
const identifiers = text.replace(FILE_TOKEN_RE, (value) => /[\\/]/.test(value) || /\.(?:[cm]?[jt]sx?|ets|pyi?|go|rs|java|c(?:pp|c|xx)?|h(?:pp|xx)?|json|md|ya?ml|toml)$/i.test(value) || index?.files.some((file) => fileMentionMatches(file.path, [value])) ? " " : value).replace(/(?:[A-Za-z]:)?(?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.*?\\/-]*/g, " ");
|
|
3658
|
+
const explicitlyNamed = (name2) => {
|
|
3659
|
+
if (!wordBoundary(name2).test(identifiers)) return false;
|
|
3660
|
+
const escaped = name2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3661
|
+
const syntax = new RegExp("`" + escaped + "`|[\"\u201C\u2018']" + escaped + `["\u201D\u2019']|\\b${escaped}\\s*\\(|(?:\\b(?:function|symbol|method)\\s+|(?:\u51FD\u6570|\u7B26\u53F7|\u65B9\u6CD5)\\s*)${escaped}(?=$|[^A-Za-z0-9_$])|(?:\\b(?:find|locate|lookup)\\s+|(?:\u67E5\u627E|\u5BFB\u627E|\u5B9A\u4F4D)\\s*)${escaped}\\s*(?:[?!.\u3002\uFF1F]|$)|(^|[^A-Za-z0-9_$])${escaped}\\s*(?:\u51FD\u6570|\u7B26\u53F7|\u65B9\u6CD5|function\\b|symbol\\b|method\\b)`, "i").test(identifiers);
|
|
3662
|
+
if (syntax) return true;
|
|
3663
|
+
if (STOP_WORDS.has(name2.toLowerCase())) return false;
|
|
3664
|
+
const words = (identifiers.match(CODE_TOKEN_RE) ?? []).filter((word) => !STOP_WORDS.has(word.toLowerCase()));
|
|
3665
|
+
return !PROSE_SYMBOL_WORDS.has(name2.toLowerCase()) || words.length <= 1;
|
|
3666
|
+
};
|
|
3359
3667
|
const exactSymbolNames = index ? unique(
|
|
3360
3668
|
index.files.flatMap((file) => file.symbols.map((symbol) => symbol.name)),
|
|
3361
3669
|
(name2) => name2
|
|
3362
|
-
).filter(
|
|
3670
|
+
).filter(explicitlyNamed).sort((a, b) => b.length - a.length || a.localeCompare(b)) : [];
|
|
3363
3671
|
const codeTokens = unique(
|
|
3364
|
-
(
|
|
3672
|
+
(identifiers.match(CODE_TOKEN_RE) ?? []).map((token) => token.replace(/^[^A-Za-z_$]+|[^A-Za-z0-9_$]+$/g, "")).filter((token) => token.length >= 3 && !STOP_WORDS.has(token.toLowerCase())),
|
|
3365
3673
|
(token) => token.toLowerCase()
|
|
3366
3674
|
);
|
|
3367
3675
|
const queries = unique(
|
|
3368
|
-
[...exactSymbolNames, ...contextTerms(
|
|
3676
|
+
[...exactSymbolNames, ...contextTerms(identifiers), ...codeTokens.filter((token) => !exactSymbolNames.includes(token))],
|
|
3369
3677
|
(value) => value.toLowerCase()
|
|
3370
3678
|
).slice(0, 12);
|
|
3371
|
-
const
|
|
3679
|
+
const intent = identifiers.replace(/(?:不要|无需|不必|禁止)(?:再|实际)?(?:修改代码|修改|编辑|修复|运行测试|测试|提交|发布)/gu, "");
|
|
3680
|
+
const chineseChange = /修复|排查(?:错误|异常|问题)|修改(?:代码|实现|逻辑)|变更|影响(?:范围|分析)?|回归|报错|异常/u.test(intent);
|
|
3681
|
+
const chineseTest = /(?:查找|寻找|定位|补充|分析|运行|验证)(?:相关|候选|单元|集成)?测试|测试覆盖/u.test(intent);
|
|
3682
|
+
const chineseArchitecture = /架构|调用流程|调用链|工作原理|生命周期/u.test(intent);
|
|
3683
|
+
const chineseExplore = /查找|定位|实现|理解|追踪|分析原因|解释原因/u.test(intent);
|
|
3684
|
+
const kind = chineseChange ? "change" : chineseTest ? "test" : TEST_RE3.test(intent) || mentionedFiles.some(isTestFile) ? "test" : CHANGE_RE.test(intent) ? "change" : exactSymbolNames.length > 0 ? "symbol" : chineseArchitecture ? "architecture" : EXPLORE_RE.test(intent) || chineseExplore ? "exploration" : ARCH_RE.test(intent) ? "architecture" : "ambiguous";
|
|
3372
3685
|
const includeChangeContext = kind === "change" || kind === "test";
|
|
3373
3686
|
const includeRelationships = kind !== "ambiguous";
|
|
3374
3687
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -3391,10 +3704,15 @@ function routeTask(task, index) {
|
|
|
3391
3704
|
};
|
|
3392
3705
|
}
|
|
3393
3706
|
function routeScore(route, hit) {
|
|
3394
|
-
|
|
3395
|
-
|
|
3707
|
+
const queryTerms = new Set(route.queries.flatMap(contextTerms));
|
|
3708
|
+
const names = contextTerms(hit.name).filter((term) => queryTerms.has(term));
|
|
3709
|
+
const signature = contextTerms(hit.signature).filter((term) => queryTerms.has(term) && !names.includes(term));
|
|
3710
|
+
const path11 = contextTerms(hit.file).filter((term) => queryTerms.has(term) && !names.includes(term) && !signature.includes(term));
|
|
3711
|
+
let score = names.length * 24 + signature.length * 4 + path11.length * 2 + hit.score * 5;
|
|
3712
|
+
if (route.exactSymbolNames.includes(hit.name)) score += 200;
|
|
3396
3713
|
if (fileMentionMatches(hit.file, route.mentionedFiles)) score += 30;
|
|
3397
|
-
if (
|
|
3714
|
+
if (hit.kind === "function" || hit.kind === "method") score += 30;
|
|
3715
|
+
if (isTestFile(hit.file) && !fileMentionMatches(hit.file, route.mentionedFiles) && !route.exactSymbolNames.includes(hit.name)) score -= 45;
|
|
3398
3716
|
return score;
|
|
3399
3717
|
}
|
|
3400
3718
|
function gatherPrimarySymbols(index, route, maxSymbols, changedIds, changedFiles, refs) {
|
|
@@ -3416,9 +3734,10 @@ function gatherPrimarySymbols(index, route, maxSymbols, changedIds, changedFiles
|
|
|
3416
3734
|
const overlap = contextTerms(`${hit.name} ${hit.signature} ${hit.file}`).filter((term) => queryTerms.has(term)).length;
|
|
3417
3735
|
if (!exact && route.kind === "ambiguous" && queryTerms.size > 1 && overlap < 2) continue;
|
|
3418
3736
|
let score = routeScore(route, hit);
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
if (
|
|
3737
|
+
const changeRelevant = !isTestFile(hit.file) || route.kind === "test" || exact || fileMentionMatches(hit.file, route.mentionedFiles);
|
|
3738
|
+
if (changeRelevant && changedIds.has(hit.id)) score += 60;
|
|
3739
|
+
else if (changeRelevant && changedFiles.has(hit.file)) score += 8;
|
|
3740
|
+
if (route.kind === "test" && isTestFile(hit.file)) score += 15;
|
|
3422
3741
|
const candidate = {
|
|
3423
3742
|
symbol: hit,
|
|
3424
3743
|
score,
|
|
@@ -3444,7 +3763,7 @@ function gatherPrimarySymbols(index, route, maxSymbols, changedIds, changedFiles
|
|
|
3444
3763
|
if (byId.has(symbol.id)) continue;
|
|
3445
3764
|
byId.set(symbol.id, {
|
|
3446
3765
|
symbol,
|
|
3447
|
-
score:
|
|
3766
|
+
score: 72,
|
|
3448
3767
|
provenance: "lexical",
|
|
3449
3768
|
reason: "declaration intersects current change"
|
|
3450
3769
|
});
|
|
@@ -3453,7 +3772,7 @@ function gatherPrimarySymbols(index, route, maxSymbols, changedIds, changedFiles
|
|
|
3453
3772
|
}
|
|
3454
3773
|
const definitionMatch = (candidate) => contextTerms(`${candidate.symbol.name} ${candidate.symbol.signature}`).some((term) => queryTerms.has(term));
|
|
3455
3774
|
const hasDefinitionMatch = [...byId.values()].some(definitionMatch);
|
|
3456
|
-
return [...byId.values()].filter((candidate) => !hasDefinitionMatch || definitionMatch(candidate) || candidate.reason === "declaration intersects current change" || fileMentionMatches(candidate.symbol.file, route.mentionedFiles)).sort(
|
|
3775
|
+
return [...byId.values()].filter((candidate) => candidate.provenance === "exact" || candidate.score >= 20).filter((candidate) => !hasDefinitionMatch || definitionMatch(candidate) || candidate.reason === "declaration intersects current change" || fileMentionMatches(candidate.symbol.file, route.mentionedFiles)).sort(
|
|
3457
3776
|
(a, b) => b.score - a.score || Number(b.provenance === "exact") - Number(a.provenance === "exact") || a.symbol.file.localeCompare(b.symbol.file) || a.symbol.line - b.symbol.line || a.symbol.id.localeCompare(b.symbol.id)
|
|
3458
3777
|
).slice(0, maxSymbols);
|
|
3459
3778
|
}
|
|
@@ -3543,7 +3862,7 @@ async function buildTaskContext(index, task, options = {}) {
|
|
|
3543
3862
|
kind: "git",
|
|
3544
3863
|
root: index.root,
|
|
3545
3864
|
baseRef: "HEAD"
|
|
3546
|
-
});
|
|
3865
|
+
}, { focusFiles: index.files.filter((file) => fileMentionMatches(file.path, route.mentionedFiles)).map((file) => file.path) });
|
|
3547
3866
|
warnings.push(...changeContext.warnings);
|
|
3548
3867
|
} catch (error) {
|
|
3549
3868
|
warnings.push(`change context unavailable: ${error.message ?? String(error)}`);
|
|
@@ -3563,10 +3882,18 @@ async function buildTaskContext(index, task, options = {}) {
|
|
|
3563
3882
|
if (changeContext && taskFiles.size) {
|
|
3564
3883
|
const changed = changeContext.changed.filter((row) => taskFiles.has(row.symbol.file));
|
|
3565
3884
|
const changedIds2 = new Set(changed.map((row) => row.symbol.id));
|
|
3885
|
+
const directCallers = changeContext.directCallers.filter((row) => changedIds2.has(row.via.toId));
|
|
3886
|
+
const reachable = /* @__PURE__ */ new Set([...changedIds2, ...directCallers.map((row) => row.symbol.id)]);
|
|
3887
|
+
const impact = [...changeContext.impact].sort((a, b) => a.depth - b.depth).filter((row) => {
|
|
3888
|
+
if (!reachable.has(row.via.toId)) return false;
|
|
3889
|
+
reachable.add(row.symbol.id);
|
|
3890
|
+
return true;
|
|
3891
|
+
});
|
|
3566
3892
|
changeContext = {
|
|
3567
3893
|
...changeContext,
|
|
3568
3894
|
changed,
|
|
3569
|
-
directCallers
|
|
3895
|
+
directCallers,
|
|
3896
|
+
impact,
|
|
3570
3897
|
tests: collectSeedTests(index, changed.map((row) => row.symbol)),
|
|
3571
3898
|
importDependents: changeContext.importDependents.filter((row) => taskFiles.has(row.changedFile)),
|
|
3572
3899
|
paths: changeContext.paths.filter((row) => changedIds2.has(row.changed.id))
|
|
@@ -3610,11 +3937,12 @@ async function buildTaskContext(index, task, options = {}) {
|
|
|
3610
3937
|
confidence,
|
|
3611
3938
|
warnings
|
|
3612
3939
|
};
|
|
3613
|
-
const pack = await selectContextPack(index, partial, budgetChars, maxFiles);
|
|
3940
|
+
const pack = await selectContextPack(index, partial, budgetChars, maxFiles, graph ?? void 0);
|
|
3614
3941
|
const selected = (file, name2, kind) => pack.items.some((item) => item.file === file && (!name2 || item.name === name2) && (!kind || item.kind === kind));
|
|
3615
3942
|
const selectedRelationships = relationships.filter((row) => pack.items.some((item) => item.text === row.text));
|
|
3616
3943
|
const selectedChange = changeContext ? {
|
|
3617
3944
|
...changeContext,
|
|
3945
|
+
evidence: pack.evidence,
|
|
3618
3946
|
changed: changeContext.changed.filter((row) => pack.items.some((item) => item.file === row.symbol.file && item.name === row.symbol.name && item.side === row.side && item.change === row.change)),
|
|
3619
3947
|
unmapped: [],
|
|
3620
3948
|
directCallers: [],
|
|
@@ -3910,6 +4238,10 @@ async function buildImpactXray(index, result) {
|
|
|
3910
4238
|
} }
|
|
3911
4239
|
};
|
|
3912
4240
|
const read = sourceReader(index);
|
|
4241
|
+
if (result.evidence) {
|
|
4242
|
+
view.evidence = result.evidence;
|
|
4243
|
+
view.gaps.push(...renderEvidenceSummary(result.evidence).split("\n"));
|
|
4244
|
+
}
|
|
3913
4245
|
const files = new Map(index.files.map((f) => [f.path, f]));
|
|
3914
4246
|
const symbols = new Map(index.files.flatMap((f) => f.symbols.map((s) => [s.id, s])));
|
|
3915
4247
|
const changedFiles = new Set(result.changed.map((r) => r.symbol.file).concat(result.unmapped.map((r) => r.file)));
|
|
@@ -4369,7 +4701,7 @@ var tools = [
|
|
|
4369
4701
|
}
|
|
4370
4702
|
if (direction === "callers" || direction === "both") {
|
|
4371
4703
|
lines.push("", `callers (${refs.callers.length}):`);
|
|
4372
|
-
if (refs.callers.length === 0) lines.push(" (none)");
|
|
4704
|
+
if (refs.callers.length === 0) lines.push(" (none found in current index/static resolution; not proof of no callers)");
|
|
4373
4705
|
for (const caller of refs.callers) {
|
|
4374
4706
|
lines.push(` ${caller.file}:${caller.line}${caller.from ? ` in ${caller.from}` : ""}`);
|
|
4375
4707
|
}
|
|
@@ -4380,7 +4712,7 @@ var tools = [
|
|
|
4380
4712
|
for (const callee of refs.callees) {
|
|
4381
4713
|
const target = callee.targets[0];
|
|
4382
4714
|
lines.push(
|
|
4383
|
-
` ${callee.name} :${callee.line}${target ? ` \u2192 ${target.file}:${target.line}` : " (external)"}`
|
|
4715
|
+
` ${callee.name} :${callee.line}${target ? ` \u2192 ${target.file}:${target.line}` : " (unresolved in current index; external not established)"}`
|
|
4384
4716
|
);
|
|
4385
4717
|
}
|
|
4386
4718
|
}
|
|
@@ -4754,6 +5086,7 @@ export {
|
|
|
4754
5086
|
readWorkingTreeDiff,
|
|
4755
5087
|
renderChangeContext,
|
|
4756
5088
|
renderContextPack,
|
|
5089
|
+
renderEvidenceSummary,
|
|
4757
5090
|
renderHit,
|
|
4758
5091
|
renderRepoMap,
|
|
4759
5092
|
renderTaskContext,
|