@zqagent/core 0.3.16 → 0.3.25

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/dist/index.cjs CHANGED
@@ -8861,6 +8861,9 @@ function nameOf(node) {
8861
8861
  if (typeNode?.text) return typeNode.text.split("<")[0].trim();
8862
8862
  return null;
8863
8863
  }
8864
+ function spanOf(node) {
8865
+ return { line: node.startPosition.row + 1, endLine: node.endPosition.row + 1 };
8866
+ }
8864
8867
  var MAX_COLLECT = 200;
8865
8868
  var VARIABLE_DECLARATIONS = /* @__PURE__ */ new Set(["lexical_declaration", "variable_declaration"]);
8866
8869
  var MODULE_CONTAINERS = /* @__PURE__ */ new Set([
@@ -8898,7 +8901,12 @@ function collectVariables(node, out, exported) {
8898
8901
  if (!nameNode || nameNode.type !== "identifier") continue;
8899
8902
  const isFn = isFunctionValue(decl);
8900
8903
  if (!isFn && !exported) continue;
8901
- out.push({ name: nameNode.text, kind: isFn ? "function" : "const", exported });
8904
+ out.push({
8905
+ name: nameNode.text,
8906
+ kind: isFn ? "function" : "const",
8907
+ exported,
8908
+ ...spanOf(decl)
8909
+ });
8902
8910
  }
8903
8911
  }
8904
8912
  function collect(node, ruleTypes, out, max, depth, exported = false, lang = "", inModule = false) {
@@ -8914,7 +8922,12 @@ function collect(node, ruleTypes, out, max, depth, exported = false, lang = "",
8914
8922
  const name = nameOf(child);
8915
8923
  const usable = name && !name.includes("\n") && name.length <= 80;
8916
8924
  if (usable) {
8917
- out.push({ name, kind, exported: isExport || isPublicDeclaration(child, lang, name) });
8925
+ out.push({
8926
+ name,
8927
+ kind,
8928
+ exported: isExport || isPublicDeclaration(child, lang, name),
8929
+ ...spanOf(child)
8930
+ });
8918
8931
  if (RECORD_AND_DESCEND.has(child.type) && depth < MAX_DEPTH) {
8919
8932
  collect(child, ruleTypes, out, max, depth + 1, isExport, lang, MODULE_CONTAINERS.has(child.type));
8920
8933
  }
@@ -8929,7 +8942,8 @@ function collect(node, ruleTypes, out, max, depth, exported = false, lang = "",
8929
8942
  out.push({
8930
8943
  name: nameNode.text,
8931
8944
  kind: "type",
8932
- exported: isPublicDeclaration(spec, lang, nameNode.text)
8945
+ exported: isPublicDeclaration(spec, lang, nameNode.text),
8946
+ ...spanOf(spec)
8933
8947
  });
8934
8948
  }
8935
8949
  continue;
@@ -8958,10 +8972,13 @@ function prioritizeSymbols(symbols, maxSymbols) {
8958
8972
  seen.add(s.name);
8959
8973
  return true;
8960
8974
  });
8961
- if (unique.length <= maxSymbols) return unique.map(({ name, kind }) => ({ name, kind }));
8962
- const exported = unique.filter((s) => s.exported);
8963
- const inner = unique.filter((s) => !s.exported);
8964
- return [...exported, ...inner].slice(0, maxSymbols).map(({ name, kind }) => ({ name, kind }));
8975
+ const trimmed = unique.length <= maxSymbols ? unique : [...unique.filter((s) => s.exported), ...unique.filter((s) => !s.exported)].slice(
8976
+ 0,
8977
+ maxSymbols
8978
+ );
8979
+ return trimmed.map(
8980
+ (s) => s.line === void 0 ? { name: s.name, kind: s.kind } : { name: s.name, kind: s.kind, line: s.line, endLine: s.endLine }
8981
+ );
8965
8982
  }
8966
8983
  function packageOf(tree, packageNode) {
8967
8984
  if (!packageNode) return void 0;
@@ -8972,6 +8989,14 @@ function packageOf(tree, packageNode) {
8972
8989
  }
8973
8990
  return void 0;
8974
8991
  }
8992
+ function countReexports(tree) {
8993
+ let n = 0;
8994
+ for (const child of tree.rootNode.children) {
8995
+ if (child.type !== "export_statement") continue;
8996
+ if (child.childForFieldName("source")) n += 1;
8997
+ }
8998
+ return n;
8999
+ }
8975
9000
  async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
8976
9001
  const rule = TS_RULES[langId];
8977
9002
  if (!rule) return null;
@@ -8995,7 +9020,8 @@ async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
8995
9020
  }
8996
9021
  return {
8997
9022
  symbols: prioritizeSymbols(symbols, maxSymbols),
8998
- pkg: packageOf(tree, rule.packageNode)
9023
+ pkg: packageOf(tree, rule.packageNode),
9024
+ reexports: countReexports(tree)
8999
9025
  };
9000
9026
  } catch {
9001
9027
  return null;
@@ -9008,6 +9034,13 @@ async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
9008
9034
  // src/repomap/extract/regex.ts
9009
9035
  init_cjs_shims();
9010
9036
  var MAX_LINE_CHARS = 500;
9037
+ var REEXPORT_RE = /^export\s+(?:\*\s*(?:as\s+[\w$]+\s*)?|type\s*(?:\*\s*|\{[^}]*\}\s*)|\{[^}]*\}\s*)from\s+['"]/gm;
9038
+ var BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
9039
+ function countReexports2(source, langId) {
9040
+ if (langId !== "typescript" && langId !== "tsx" && langId !== "javascript") return 0;
9041
+ const text = source.includes("/*") ? source.replace(BLOCK_COMMENT_RE, "") : source;
9042
+ return text.match(REEXPORT_RE)?.length ?? 0;
9043
+ }
9011
9044
  function stripBlockComments(line, state) {
9012
9045
  let out = "";
9013
9046
  let i = 0;
@@ -9041,7 +9074,8 @@ function extractWithRegex(langId, source, maxSymbols) {
9041
9074
  let pkg;
9042
9075
  const state = { inBlock: false };
9043
9076
  const lines = source.split("\n");
9044
- for (const raw of lines) {
9077
+ for (let i = 0; i < lines.length; i++) {
9078
+ const raw = lines[i];
9045
9079
  if (symbols.length >= maxSymbols) break;
9046
9080
  const stripped = stripBlockComments(raw, state);
9047
9081
  const line = /^\s/.test(raw) ? stripped : stripped.replace(/^\s+/, "");
@@ -9054,9 +9088,9 @@ function extractWithRegex(langId, source, maxSymbols) {
9054
9088
  if (!name) continue;
9055
9089
  if (seen.has(name)) continue;
9056
9090
  seen.add(name);
9057
- symbols.push({ name, kind: rule.kind });
9091
+ symbols.push({ name, kind: rule.kind, line: i + 1 });
9058
9092
  }
9059
- return { symbols, pkg };
9093
+ return { symbols, pkg, reexports: countReexports2(source, langId) };
9060
9094
  }
9061
9095
 
9062
9096
  // src/repomap/grammar.ts
@@ -9114,6 +9148,7 @@ function markLoaded(langId) {
9114
9148
  failedAt.delete(langId);
9115
9149
  }
9116
9150
  var inflight = /* @__PURE__ */ new Map();
9151
+ var readyCache = /* @__PURE__ */ new Map();
9117
9152
  function grammarDir() {
9118
9153
  const explicit = process.env.ZQAGENT_REPOMAP_GRAMMAR_DIR?.trim();
9119
9154
  if (explicit) return explicit;
@@ -9137,10 +9172,7 @@ function verify(spec, buf) {
9137
9172
  return sha256Hex(buf) === spec.sha256;
9138
9173
  }
9139
9174
  function readLocal(spec) {
9140
- const candidates = [cachePath(spec)];
9141
- const explicit = process.env.ZQAGENT_REPOMAP_GRAMMAR_DIR?.trim();
9142
- if (explicit) candidates.push(path13.join(explicit, `${spec.id}.wasm`));
9143
- for (const file of candidates) {
9175
+ for (const file of localCandidates(spec)) {
9144
9176
  try {
9145
9177
  const buf = fs11.readFileSync(file);
9146
9178
  if (verify(spec, buf)) return new Uint8Array(buf);
@@ -9149,6 +9181,24 @@ function readLocal(spec) {
9149
9181
  }
9150
9182
  return null;
9151
9183
  }
9184
+ function localCandidates(spec) {
9185
+ const candidates = [cachePath(spec)];
9186
+ const explicit = process.env.ZQAGENT_REPOMAP_GRAMMAR_DIR?.trim();
9187
+ if (explicit) candidates.push(path13.join(explicit, `${spec.id}.wasm`));
9188
+ return candidates;
9189
+ }
9190
+ function localSignature(spec) {
9191
+ const parts = [];
9192
+ for (const file of localCandidates(spec)) {
9193
+ try {
9194
+ const st = fs11.statSync(file);
9195
+ parts.push(`${st.mtimeMs}:${st.size}`);
9196
+ } catch {
9197
+ parts.push("-");
9198
+ }
9199
+ }
9200
+ return parts.join("|");
9201
+ }
9152
9202
  async function download(spec) {
9153
9203
  if (offline()) return null;
9154
9204
  for (const build of CDN_TEMPLATES) {
@@ -9207,7 +9257,13 @@ async function loadGrammar(langId) {
9207
9257
  function grammarReady(langId) {
9208
9258
  if (resolved.get(langId)) return true;
9209
9259
  const spec = GRAMMARS[langId];
9210
- return spec ? readLocal(spec) !== null : false;
9260
+ if (!spec) return false;
9261
+ const sig = localSignature(spec);
9262
+ const hit = readyCache.get(langId);
9263
+ if (hit && hit.sig === sig) return hit.ready;
9264
+ const ready = readLocal(spec) !== null;
9265
+ readyCache.set(langId, { sig, ready });
9266
+ return ready;
9211
9267
  }
9212
9268
 
9213
9269
  // src/repomap/extract/index.ts
@@ -9226,10 +9282,10 @@ async function extractFile(relPath, source) {
9226
9282
  const bytes = await loadGrammar(lang);
9227
9283
  if (bytes) {
9228
9284
  const ts = await extractWithTreeSitter(lang, bytes, source, MAX_SYMBOLS_PER_FILE);
9229
- if (ts) return { symbols: ts.symbols, pkg: ts.pkg, via: "tree-sitter" };
9285
+ if (ts) return { symbols: ts.symbols, pkg: ts.pkg, reexports: ts.reexports, via: "tree-sitter" };
9230
9286
  }
9231
9287
  const re = extractWithRegex(lang, source, MAX_SYMBOLS_PER_FILE);
9232
- return { symbols: re.symbols, pkg: re.pkg, via: "regex" };
9288
+ return { symbols: re.symbols, pkg: re.pkg, reexports: re.reexports, via: "regex" };
9233
9289
  }
9234
9290
  function wantsContent(relPath) {
9235
9291
  if (!langOf(relPath)) return false;
@@ -9416,6 +9472,10 @@ function hasLayerWord(name) {
9416
9472
  }
9417
9473
  var ENTRY_RE = /(^|\/)(main|index|program|startup|app|application|bootstrap|mod|__init__)(\.|\/)/i;
9418
9474
  var ENTRY_SUFFIX_RE = /(application|program|startup)\.(java|kt|cs|scala)$/i;
9475
+ function isEntryFile(relPath, sourceRoots = []) {
9476
+ if (ENTRY_SUFFIX_RE.test(relPath)) return true;
9477
+ return ENTRY_RE.test(relPath) && depthFromSourceRoot(relPath, sourceRoots) <= 1;
9478
+ }
9419
9479
  var TEST_RE = /(^|\/)(test|tests|__tests__|__mocks__|spec|specs|e2e)(\/|$)|\.(test|spec)\.[a-z]+$|test\.(java|kt|cs)$|_test\.(go|py|rb)$/i;
9420
9480
  var DOC_RE = /(^|\/)(docs?|examples?|samples?|demo|benchmarks?|bench|fixtures?)(\/|$)|\.(md|mdx|rst|txt|adoc)$/i;
9421
9481
  var GENERATED_RE = /\.(min|generated|designer)\.|_pb2?\.|\.g\.(cs|java)$|(^|\/)gen\//i;
@@ -9451,7 +9511,7 @@ function scorePath(relPath, sourceRoots) {
9451
9511
  score += W.sourceRoot;
9452
9512
  }
9453
9513
  const depth = depthFromSourceRoot(relPath, sourceRoots);
9454
- if (ENTRY_SUFFIX_RE.test(relPath) || ENTRY_RE.test(relPath) && depth <= 1) {
9514
+ if (isEntryFile(relPath, sourceRoots)) {
9455
9515
  score += W.entry;
9456
9516
  }
9457
9517
  const base = relPath.slice(relPath.lastIndexOf("/") + 1).replace(/\.[^.]+$/, "");
@@ -9479,7 +9539,7 @@ function rankFiles(paths, sourceRoots, hasSymbols) {
9479
9539
 
9480
9540
  // src/repomap/types.ts
9481
9541
  init_cjs_shims();
9482
- var REPOMAP_CACHE_VERSION = 3;
9542
+ var REPOMAP_CACHE_VERSION = 5;
9483
9543
 
9484
9544
  // src/repomap/indexer.ts
9485
9545
  var import_node_crypto2 = require("crypto");
@@ -9687,6 +9747,7 @@ async function buildRepoMap(root, opts = {}) {
9687
9747
  size: stat.size,
9688
9748
  symbols: ex.symbols,
9689
9749
  pkg: ex.pkg,
9750
+ reexports: ex.reexports,
9690
9751
  via: ex.via
9691
9752
  };
9692
9753
  } catch {
@@ -9694,15 +9755,18 @@ async function buildRepoMap(root, opts = {}) {
9694
9755
  }
9695
9756
  await yieldToLoop();
9696
9757
  }
9697
- const withSymbols = new Set(
9698
- Object.entries(result).filter(([, v]) => v.symbols.length > 0).map(([k]) => k)
9699
- );
9700
- const ranked = rankFiles([...withSymbols], shape.sourceRoots);
9758
+ const retained = {};
9759
+ for (const [rel, v] of Object.entries(result)) {
9760
+ if (v.symbols.length > 0 || (v.reexports ?? 0) > 0 || isEntryFile(rel, shape.sourceRoots)) {
9761
+ retained[rel] = v;
9762
+ }
9763
+ }
9764
+ const ranked = rankFiles(Object.keys(retained), shape.sourceRoots);
9701
9765
  const scan = {
9702
9766
  files: files.length,
9703
9767
  // 报「地图里有多少文件」而不是「本次新提取了多少」:命中缓存时
9704
- // 后者是 0,模型会以为结构图是空的(实测曾把 1324 个文件报成 0)
9705
- indexed: Object.keys(result).length,
9768
+ // 后者为 0,模型会误判结构图为空(实测曾把 1324 个文件报成 0)
9769
+ indexed: Object.keys(retained).length,
9706
9770
  truncated: enumTruncated || limit < rankedAll.length,
9707
9771
  elapsedMs: Date.now() - started
9708
9772
  };
@@ -9719,7 +9783,7 @@ async function buildRepoMap(root, opts = {}) {
9719
9783
  shape
9720
9784
  });
9721
9785
  }
9722
- return { root: absRoot, scale, scan, ranked, files: result, shape };
9786
+ return { root: absRoot, scale, scan, ranked, files: retained, shape };
9723
9787
  } catch {
9724
9788
  return empty;
9725
9789
  }
@@ -9760,6 +9824,11 @@ var GENERIC_SYMBOLS = /* @__PURE__ */ new Set([
9760
9824
  function meaningful(symbols) {
9761
9825
  return symbols.filter((s) => s.name.length > 3 && !GENERIC_SYMBOLS.has(s.name));
9762
9826
  }
9827
+ function symbolRef(s, withRange = false) {
9828
+ if (s.line === void 0 || s.name.includes(":")) return s.name;
9829
+ const end = withRange && s.endLine !== void 0 && s.endLine > s.line ? `-${s.endLine}` : "";
9830
+ return `${s.name}:${s.line}${end}`;
9831
+ }
9763
9832
  var GROUP_FILE_LIMIT = { small: 5, medium: 4, large: 1 };
9764
9833
  var BREADTH_SHARE = 0.5;
9765
9834
  var TAIL_RESERVE = 64;
@@ -9783,7 +9852,7 @@ function baseName(relPath) {
9783
9852
  function symbolsInline(symbols) {
9784
9853
  const kept = meaningful(symbols);
9785
9854
  if (kept.length === 0) return "";
9786
- const names = kept.slice(0, MAX_SYMBOLS_PER_LINE).map((s) => s.name);
9855
+ const names = kept.slice(0, MAX_SYMBOLS_PER_LINE).map((s) => symbolRef(s));
9787
9856
  const more = kept.length > MAX_SYMBOLS_PER_LINE ? ", \u2026" : "";
9788
9857
  return names.join(", ") + more;
9789
9858
  }
@@ -9791,14 +9860,14 @@ function buildGroups(map) {
9791
9860
  const groups = /* @__PURE__ */ new Map();
9792
9861
  for (const { path: rel } of map.ranked) {
9793
9862
  const entry = map.files[rel];
9794
- if (!entry || entry.symbols.length === 0) continue;
9863
+ if (!entry) continue;
9795
9864
  const label = groupLabelOf(rel, entry.pkg);
9796
9865
  let g = groups.get(label);
9797
9866
  if (!g) {
9798
9867
  g = { label, files: [] };
9799
9868
  groups.set(label, g);
9800
9869
  }
9801
- g.files.push({ path: rel, symbols: entry.symbols });
9870
+ g.files.push({ path: rel, symbols: entry.symbols, reexports: entry.reexports });
9802
9871
  }
9803
9872
  return [...groups.values()];
9804
9873
  }
@@ -9809,12 +9878,20 @@ function moduleLine(mod, info) {
9809
9878
  return ` ${label} (${info.files} \u6587\u4EF6)${layers ? ` ${layers}` : ""}${entry}`;
9810
9879
  }
9811
9880
  function renderLarge(map, budget) {
9881
+ const bestOfModule = /* @__PURE__ */ new Map();
9882
+ for (const r of map.ranked) {
9883
+ const slash = r.path.indexOf("/");
9884
+ const mod = slash < 0 ? "." : r.path.slice(0, slash);
9885
+ const prev = bestOfModule.get(mod);
9886
+ if (prev === void 0 || r.score > prev) bestOfModule.set(mod, r.score);
9887
+ }
9812
9888
  const perModule = /* @__PURE__ */ new Map();
9813
9889
  for (const rel of Object.keys(map.files)) {
9814
9890
  const slash = rel.indexOf("/");
9815
9891
  const first = slash < 0 ? "." : rel.slice(0, slash);
9816
9892
  const bucket = perModule.get(first) ?? {
9817
9893
  files: 0,
9894
+ best: bestOfModule.get(first) ?? Number.NEGATIVE_INFINITY,
9818
9895
  layers: /* @__PURE__ */ new Set(),
9819
9896
  entryDepth: Number.POSITIVE_INFINITY
9820
9897
  };
@@ -9834,7 +9911,9 @@ function renderLarge(map, budget) {
9834
9911
  }
9835
9912
  perModule.set(first, bucket);
9836
9913
  }
9837
- const sorted = [...perModule.entries()].sort((a, b) => b[1].files - a[1].files);
9914
+ const sorted = [...perModule.entries()].sort(
9915
+ (a, b) => b[1].best - a[1].best || b[1].files - a[1].files || a[0].localeCompare(b[0])
9916
+ );
9838
9917
  const lines = [];
9839
9918
  const lineBudget = Math.max(200, budget - TAIL_RESERVE);
9840
9919
  let used = 0;
@@ -9856,8 +9935,13 @@ function renderLarge(map, budget) {
9856
9935
  return lines.join("\n");
9857
9936
  }
9858
9937
  function fileLine(f) {
9859
- const sym = symbolsInline(f.symbols);
9860
- return ` ${baseName(f.path)}${sym ? `: ${sym}` : ""}`;
9938
+ const name = baseName(f.path);
9939
+ if (f.symbols.length > 0) {
9940
+ const sym = symbolsInline(f.symbols);
9941
+ return ` ${name}${sym ? `: ${sym}` : ""}`;
9942
+ }
9943
+ if (f.reexports) return ` ${name}: (\u518D\u5BFC\u51FA ${f.reexports})`;
9944
+ return ` ${name}: (\u5165\u53E3)`;
9861
9945
  }
9862
9946
  function renderDetailed(map, budget) {
9863
9947
  const limit = GROUP_FILE_LIMIT[map.scale === "medium" ? "medium" : "small"];
@@ -9892,23 +9976,36 @@ function renderDetailed(map, budget) {
9892
9976
  const out = [];
9893
9977
  for (const b of blocks) out.push(b.header, ...b.lines);
9894
9978
  const omitted = map.ranked.length - shownFiles;
9895
- if (omitted > 0) out.push(`\u2026 \u53E6\u6709 ${omitted} \u4E2A\u6587\u4EF6\u672A\u5217\u51FA\uFF0C\u7528 repo_map \u5DE5\u5177\u67E5\u770B`);
9979
+ if (omitted > 0) out.push(`\u2026 \u53E6\u6709 ${omitted} \u4E2A\u6587\u4EF6\u672A\u5217\u51FA\uFF0C\u7528 repo_map \u5DE5\u5177\u6309\u76EE\u5F55\u67E5\u770B`);
9896
9980
  return out.join("\n");
9897
9981
  }
9982
+ function shapeLine(shape) {
9983
+ if (!shape) return "";
9984
+ const parts = [];
9985
+ const kinds = shape.kinds.filter((k) => k !== "npm" || !shape.kinds.includes("npm-workspace"));
9986
+ if (kinds.length) parts.push(kinds.slice(0, 3).join(" \xB7 "));
9987
+ if (shape.modules.length > 1) parts.push(`${shape.modules.length} \u6A21\u5757`);
9988
+ const roots = [...new Set(shape.sourceRoots)].sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, 3);
9989
+ if (roots.length) parts.push(`\u6E90\u7801\u6839 ${roots.join(", ")}`);
9990
+ return parts.length ? `\u9879\u76EE\u5F62\u6001: ${parts.join(" \xB7 ")}` : "";
9991
+ }
9898
9992
  function renderRepoMap(map) {
9899
9993
  if (map.ranked.length === 0 && !map.shape) return "";
9900
9994
  const budget = renderBudget(map.scale);
9901
- const head = map.scale === "large" ? "\u4EE3\u7801\u7ED3\u6784\uFF08\u81EA\u52A8\u6458\u8981 \xB7 \u5927\u578B\u9879\u76EE\uFF1B\u6309\u6A21\u5757\u4E0B\u94BB\u7528 repo_map \u5DE5\u5177\uFF09" : "\u4EE3\u7801\u7ED3\u6784\uFF08\u81EA\u52A8\u751F\u6210\uFF1B\u66F4\u7EC6\u7684\u89C6\u56FE\u7528 repo_map \u5DE5\u5177\uFF09";
9995
+ const title = map.scale === "large" ? "\u4EE3\u7801\u7ED3\u6784\uFF08\u81EA\u52A8\u6458\u8981 \xB7 \u5927\u578B\u9879\u76EE\uFF1B\u6309\u6A21\u5757\u4E0B\u94BB\u7528 repo_map \u5DE5\u5177\uFF09" : "\u4EE3\u7801\u7ED3\u6784\uFF08\u81EA\u52A8\u751F\u6210\uFF1B\u7B26\u53F7\u540E\u7684\u6570\u5B57\u662F\u884C\u53F7\uFF1B\u66F4\u7EC6\u7684\u89C6\u56FE\u7528 repo_map \u5DE5\u5177\uFF09";
9996
+ const shape = shapeLine(map.shape);
9997
+ const head = shape ? `${title}
9998
+ ${shape}` : title;
9902
9999
  const bodyBudget = Math.max(200, budget - head.length - 1);
9903
10000
  const body = map.scale === "large" ? renderLarge(map, bodyBudget) : renderDetailed(map, bodyBudget);
9904
- if (!body.trim()) return "";
10001
+ if (!body.trim()) return shape;
9905
10002
  const text = `${head}
9906
10003
  ${body}`;
9907
10004
  if (text.length <= budget) return text;
9908
10005
  return cutAtLine(text, budget);
9909
10006
  }
9910
10007
  function cutAtLine(text, budget) {
9911
- const marker = "\n\u2026\uFF08\u7ED3\u6784\u56FE\u5DF2\u622A\u65AD\uFF0C\u7528 repo_map \u5DE5\u5177\u67E5\u770B\uFF09";
10008
+ const marker = "\n\u2026\uFF08\u7ED3\u6784\u56FE\u5DF2\u622A\u65AD\uFF0C\u7528 repo_map \u5DE5\u5177\u6309\u76EE\u5F55\u67E5\u770B\uFF09";
9912
10009
  const room = budget - marker.length;
9913
10010
  if (room <= 0) return marker.slice(1);
9914
10011
  const cut = text.slice(0, room);
@@ -10018,10 +10115,12 @@ function attachRepoMap(engine, opts) {
10018
10115
  const original = spec.func;
10019
10116
  engine.registry.register(
10020
10117
  async (args, ctx) => {
10021
- const out = await original(args, ctx);
10022
- invalidateRepoMap(workspace);
10023
- schedule();
10024
- return out;
10118
+ try {
10119
+ return await original(args, ctx);
10120
+ } finally {
10121
+ invalidateRepoMap(workspace);
10122
+ schedule();
10123
+ }
10025
10124
  },
10026
10125
  spec.metadata,
10027
10126
  spec.schema,
@@ -13213,17 +13312,26 @@ function registerSearchTool(registry, workspace) {
13213
13312
  // src/tools/repoMap.ts
13214
13313
  init_cjs_shims();
13215
13314
  var MAX_CHARS = 16e3;
13315
+ function filesUnder(map, prefix) {
13316
+ const keys = Object.keys(map.files);
13317
+ if (!prefix) return keys;
13318
+ if (prefix === ".") return keys.filter((rel) => !rel.includes("/"));
13319
+ return keys.filter((rel) => rel === prefix || rel.startsWith(`${prefix}/`));
13320
+ }
13216
13321
  function renderSubset(map, prefix, maxFiles) {
13217
13322
  const order = new Map(map.ranked.map((r, i) => [r.path, i]));
13218
- const under = Object.keys(map.files).filter((rel) => !prefix || rel === prefix || rel.startsWith(`${prefix}/`)).sort((a, b) => (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity));
13323
+ const under = filesUnder(map, prefix).sort(
13324
+ (a, b) => (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity)
13325
+ );
13219
13326
  const matched = under.slice(0, maxFiles);
13220
13327
  if (matched.length === 0) return null;
13221
13328
  const entries = [];
13222
13329
  let used = 0;
13223
13330
  for (const rel of matched) {
13224
13331
  const f = map.files[rel];
13225
- const sym = f.symbols.slice(0, 8).map((s) => s.name).join(", ");
13226
- const line = `${rel}${sym ? `: ${sym}` : ""}`;
13332
+ const sym = f.symbols.slice(0, 8).map((s) => symbolRef(s, true)).join(", ");
13333
+ const body = f.symbols.length > 0 ? sym : f.reexports ? `(\u518D\u5BFC\u51FA ${f.reexports})` : "(\u5165\u53E3)";
13334
+ const line = `${rel}: ${body}`;
13227
13335
  if (used + line.length + 1 > MAX_CHARS) break;
13228
13336
  entries.push(line);
13229
13337
  used += line.length + 1;
@@ -13255,18 +13363,20 @@ function registerRepoMapTool(registry, workspace) {
13255
13363
  if (sub) return sub;
13256
13364
  return { error: `\u6CA1\u6709\u5339\u914D\u7684\u76EE\u5F55\uFF1A${path30}` };
13257
13365
  }
13258
- const groups = buildGroups(map).slice(0, 40).map((g) => ({
13259
- label: g.label,
13260
- files: g.files.length,
13261
- path: g.files[0]?.path.split("/").slice(0, -1).join("/") || "."
13262
- }));
13366
+ const groups = buildGroups(map).slice(0, 40).map((g) => {
13367
+ const dir = g.files[0]?.path.split("/").slice(0, -1).join("/") || ".";
13368
+ return {
13369
+ label: g.label,
13370
+ files: filesUnder(map, dir).length,
13371
+ ...dir === g.label ? {} : { path: dir }
13372
+ };
13373
+ });
13263
13374
  return {
13264
13375
  scale: map.scale,
13265
13376
  files: map.scan.files,
13266
13377
  indexed: map.scan.indexed,
13267
13378
  groups,
13268
- map: renderRepoMap(map),
13269
- hint: '\u7528 path \u53C2\u6570\u53EF\u53EA\u770B\u67D0\u4E2A\u6A21\u5757/\u76EE\u5F55\uFF08\u53D6 groups[].path\uFF09\uFF0C\u4F8B\u5982 path="packages/core/src"'
13379
+ hint: '\u7ED3\u6784\u56FE\u5DF2\u5728\u7CFB\u7EDF\u63D0\u793A\u8BCD\u91CC\uFF0C\u8FD9\u91CC\u53EA\u7ED9\u5168\u5C40\u5206\u7EC4\uFF08files \u542B\u5B50\u76EE\u5F55\uFF09\u3002\u7528 path \u53C2\u6570\u6309\u76EE\u5F55\u4E0B\u94BB\uFF1A\u5206\u7EC4\u6807\u9898\u662F\u5305\u540D\u65F6\u7528 groups[].path\uFF0C\u5426\u5219\u76F4\u63A5\u7528 label\uFF0C\u4F8B\u5982 path="packages/core/src"'
13270
13380
  };
13271
13381
  },
13272
13382
  { category: "search", riskLevel: "low", capabilities: ["repo_map"], requiresApproval: false },