@zqagent/core 0.3.16 → 0.3.22

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.d.cts CHANGED
@@ -519,6 +519,13 @@ type SymbolKind = "class" | "interface" | "struct" | "enum" | "record" | "trait"
519
519
  interface SymbolNode {
520
520
  name: string;
521
521
  kind: SymbolKind;
522
+ /**
523
+ * 声明起始行(1 起始)。渲染成 `Name:12`,模型可直接 `read_file start_line=12`,
524
+ * 省掉先 grep 一行号的那一轮 —— 一轮工具调用要重发整个上下文,比这几个字符贵得多。
525
+ */
526
+ line?: number;
527
+ /** 声明结束行;与 {@link line} 相同时渲染层只写一个行号 */
528
+ endLine?: number;
522
529
  }
523
530
  /** 单文件的索引结果 */
524
531
  interface FileSymbols {
@@ -532,6 +539,14 @@ interface FileSymbols {
532
539
  * 比目录结构可靠,用于渲染分组;取不到则缺省。
533
540
  */
534
541
  pkg?: string;
542
+ /**
543
+ * 再导出语句数(`export * from "./x"` / `export { a } from "./x"`)。
544
+ *
545
+ * barrel 文件(`index.ts` 只有再导出)提取不到任何符号,但它往往是**全仓
546
+ * 被 import 最多的文件**,是「该从哪个包入口开始」的最佳答案。没有这个计数,
547
+ * 索引器会把它连同其它 0 符号文件一起丢掉,包入口在结构图里彻底不可见。
548
+ */
549
+ reexports?: number;
535
550
  /** 该文件由哪个提取器处理(诊断用) */
536
551
  via: "tree-sitter" | "regex" | "path-only";
537
552
  }
@@ -546,7 +561,7 @@ interface RepoMapCache {
546
561
  scan: {
547
562
  /** 枚举到的文件总数(可能远大于 indexed) */
548
563
  files: number;
549
- /** 实际提取了符号的文件数(受 MAX_FILES 封顶) */
564
+ /** 进地图的文件数(有声明 / 纯 barrel / 入口;受 MAX_FILES 封顶) */
550
565
  indexed: number;
551
566
  /** 是否因上限被截断 */
552
567
  truncated: boolean;
@@ -591,9 +606,14 @@ interface RepoMap {
591
606
  root: string;
592
607
  scale: Scale;
593
608
  scan: RepoMapCache["scan"];
594
- /** 已排序的文件(score 降序) */
609
+ /** 已排序的文件(score 降序),与 {@link files} 的键集完全一致 */
595
610
  ranked: RankedFile[];
596
- /** path → 索引结果 */
611
+ /**
612
+ * path → 索引结果,**只含进地图的文件**:有顶层声明、纯 barrel、或模块入口。
613
+ *
614
+ * 解析成功但三者都不满足的文件(例如只有顶层调用的脚本)不在这里 —— 它们在
615
+ * 结构图里只能占一行而不提供任何定位信息。索引磁盘缓存仍保留它们以支持增量。
616
+ */
597
617
  files: Record<string, FileSymbols>;
598
618
  shape?: ProjectShape;
599
619
  }
@@ -608,7 +628,8 @@ interface RepoMap {
608
628
  *
609
629
  * token 上的取舍(都按"能否帮模型少走一轮"来判断):
610
630
  * - 目录路径只写一次(分组标题),不在每行重复
611
- * - 符号只写名字,不写 kind、不写行号
631
+ * - 符号写「名字 + 行号」,不写 kind:行号能省掉先 grep 定位的那一轮,
632
+ * kind 省不了任何一次工具调用
612
633
  * - 不画树形符号(`├──` 每行要花 4-8 个 token)
613
634
  */
614
635
 
package/dist/index.d.ts CHANGED
@@ -519,6 +519,13 @@ type SymbolKind = "class" | "interface" | "struct" | "enum" | "record" | "trait"
519
519
  interface SymbolNode {
520
520
  name: string;
521
521
  kind: SymbolKind;
522
+ /**
523
+ * 声明起始行(1 起始)。渲染成 `Name:12`,模型可直接 `read_file start_line=12`,
524
+ * 省掉先 grep 一行号的那一轮 —— 一轮工具调用要重发整个上下文,比这几个字符贵得多。
525
+ */
526
+ line?: number;
527
+ /** 声明结束行;与 {@link line} 相同时渲染层只写一个行号 */
528
+ endLine?: number;
522
529
  }
523
530
  /** 单文件的索引结果 */
524
531
  interface FileSymbols {
@@ -532,6 +539,14 @@ interface FileSymbols {
532
539
  * 比目录结构可靠,用于渲染分组;取不到则缺省。
533
540
  */
534
541
  pkg?: string;
542
+ /**
543
+ * 再导出语句数(`export * from "./x"` / `export { a } from "./x"`)。
544
+ *
545
+ * barrel 文件(`index.ts` 只有再导出)提取不到任何符号,但它往往是**全仓
546
+ * 被 import 最多的文件**,是「该从哪个包入口开始」的最佳答案。没有这个计数,
547
+ * 索引器会把它连同其它 0 符号文件一起丢掉,包入口在结构图里彻底不可见。
548
+ */
549
+ reexports?: number;
535
550
  /** 该文件由哪个提取器处理(诊断用) */
536
551
  via: "tree-sitter" | "regex" | "path-only";
537
552
  }
@@ -546,7 +561,7 @@ interface RepoMapCache {
546
561
  scan: {
547
562
  /** 枚举到的文件总数(可能远大于 indexed) */
548
563
  files: number;
549
- /** 实际提取了符号的文件数(受 MAX_FILES 封顶) */
564
+ /** 进地图的文件数(有声明 / 纯 barrel / 入口;受 MAX_FILES 封顶) */
550
565
  indexed: number;
551
566
  /** 是否因上限被截断 */
552
567
  truncated: boolean;
@@ -591,9 +606,14 @@ interface RepoMap {
591
606
  root: string;
592
607
  scale: Scale;
593
608
  scan: RepoMapCache["scan"];
594
- /** 已排序的文件(score 降序) */
609
+ /** 已排序的文件(score 降序),与 {@link files} 的键集完全一致 */
595
610
  ranked: RankedFile[];
596
- /** path → 索引结果 */
611
+ /**
612
+ * path → 索引结果,**只含进地图的文件**:有顶层声明、纯 barrel、或模块入口。
613
+ *
614
+ * 解析成功但三者都不满足的文件(例如只有顶层调用的脚本)不在这里 —— 它们在
615
+ * 结构图里只能占一行而不提供任何定位信息。索引磁盘缓存仍保留它们以支持增量。
616
+ */
597
617
  files: Record<string, FileSymbols>;
598
618
  shape?: ProjectShape;
599
619
  }
@@ -608,7 +628,8 @@ interface RepoMap {
608
628
  *
609
629
  * token 上的取舍(都按"能否帮模型少走一轮"来判断):
610
630
  * - 目录路径只写一次(分组标题),不在每行重复
611
- * - 符号只写名字,不写 kind、不写行号
631
+ * - 符号写「名字 + 行号」,不写 kind:行号能省掉先 grep 定位的那一轮,
632
+ * kind 省不了任何一次工具调用
612
633
  * - 不画树形符号(`├──` 每行要花 4-8 个 token)
613
634
  */
614
635
 
package/dist/index.js CHANGED
@@ -8733,6 +8733,9 @@ function nameOf(node) {
8733
8733
  if (typeNode?.text) return typeNode.text.split("<")[0].trim();
8734
8734
  return null;
8735
8735
  }
8736
+ function spanOf(node) {
8737
+ return { line: node.startPosition.row + 1, endLine: node.endPosition.row + 1 };
8738
+ }
8736
8739
  var MAX_COLLECT = 200;
8737
8740
  var VARIABLE_DECLARATIONS = /* @__PURE__ */ new Set(["lexical_declaration", "variable_declaration"]);
8738
8741
  var MODULE_CONTAINERS = /* @__PURE__ */ new Set([
@@ -8770,7 +8773,12 @@ function collectVariables(node, out, exported) {
8770
8773
  if (!nameNode || nameNode.type !== "identifier") continue;
8771
8774
  const isFn = isFunctionValue(decl);
8772
8775
  if (!isFn && !exported) continue;
8773
- out.push({ name: nameNode.text, kind: isFn ? "function" : "const", exported });
8776
+ out.push({
8777
+ name: nameNode.text,
8778
+ kind: isFn ? "function" : "const",
8779
+ exported,
8780
+ ...spanOf(decl)
8781
+ });
8774
8782
  }
8775
8783
  }
8776
8784
  function collect(node, ruleTypes, out, max, depth, exported = false, lang = "", inModule = false) {
@@ -8786,7 +8794,12 @@ function collect(node, ruleTypes, out, max, depth, exported = false, lang = "",
8786
8794
  const name = nameOf(child);
8787
8795
  const usable = name && !name.includes("\n") && name.length <= 80;
8788
8796
  if (usable) {
8789
- out.push({ name, kind, exported: isExport || isPublicDeclaration(child, lang, name) });
8797
+ out.push({
8798
+ name,
8799
+ kind,
8800
+ exported: isExport || isPublicDeclaration(child, lang, name),
8801
+ ...spanOf(child)
8802
+ });
8790
8803
  if (RECORD_AND_DESCEND.has(child.type) && depth < MAX_DEPTH) {
8791
8804
  collect(child, ruleTypes, out, max, depth + 1, isExport, lang, MODULE_CONTAINERS.has(child.type));
8792
8805
  }
@@ -8801,7 +8814,8 @@ function collect(node, ruleTypes, out, max, depth, exported = false, lang = "",
8801
8814
  out.push({
8802
8815
  name: nameNode.text,
8803
8816
  kind: "type",
8804
- exported: isPublicDeclaration(spec, lang, nameNode.text)
8817
+ exported: isPublicDeclaration(spec, lang, nameNode.text),
8818
+ ...spanOf(spec)
8805
8819
  });
8806
8820
  }
8807
8821
  continue;
@@ -8830,10 +8844,13 @@ function prioritizeSymbols(symbols, maxSymbols) {
8830
8844
  seen.add(s.name);
8831
8845
  return true;
8832
8846
  });
8833
- if (unique.length <= maxSymbols) return unique.map(({ name, kind }) => ({ name, kind }));
8834
- const exported = unique.filter((s) => s.exported);
8835
- const inner = unique.filter((s) => !s.exported);
8836
- return [...exported, ...inner].slice(0, maxSymbols).map(({ name, kind }) => ({ name, kind }));
8847
+ const trimmed = unique.length <= maxSymbols ? unique : [...unique.filter((s) => s.exported), ...unique.filter((s) => !s.exported)].slice(
8848
+ 0,
8849
+ maxSymbols
8850
+ );
8851
+ return trimmed.map(
8852
+ (s) => s.line === void 0 ? { name: s.name, kind: s.kind } : { name: s.name, kind: s.kind, line: s.line, endLine: s.endLine }
8853
+ );
8837
8854
  }
8838
8855
  function packageOf(tree, packageNode) {
8839
8856
  if (!packageNode) return void 0;
@@ -8844,6 +8861,14 @@ function packageOf(tree, packageNode) {
8844
8861
  }
8845
8862
  return void 0;
8846
8863
  }
8864
+ function countReexports(tree) {
8865
+ let n = 0;
8866
+ for (const child of tree.rootNode.children) {
8867
+ if (child.type !== "export_statement") continue;
8868
+ if (child.childForFieldName("source")) n += 1;
8869
+ }
8870
+ return n;
8871
+ }
8847
8872
  async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
8848
8873
  const rule = TS_RULES[langId];
8849
8874
  if (!rule) return null;
@@ -8867,7 +8892,8 @@ async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
8867
8892
  }
8868
8893
  return {
8869
8894
  symbols: prioritizeSymbols(symbols, maxSymbols),
8870
- pkg: packageOf(tree, rule.packageNode)
8895
+ pkg: packageOf(tree, rule.packageNode),
8896
+ reexports: countReexports(tree)
8871
8897
  };
8872
8898
  } catch {
8873
8899
  return null;
@@ -8880,6 +8906,13 @@ async function extractWithTreeSitter(langId, bytes, source, maxSymbols) {
8880
8906
  // src/repomap/extract/regex.ts
8881
8907
  init_esm_shims();
8882
8908
  var MAX_LINE_CHARS = 500;
8909
+ var REEXPORT_RE = /^export\s+(?:\*\s*(?:as\s+[\w$]+\s*)?|type\s*(?:\*\s*|\{[^}]*\}\s*)|\{[^}]*\}\s*)from\s+['"]/gm;
8910
+ var BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
8911
+ function countReexports2(source, langId) {
8912
+ if (langId !== "typescript" && langId !== "tsx" && langId !== "javascript") return 0;
8913
+ const text = source.includes("/*") ? source.replace(BLOCK_COMMENT_RE, "") : source;
8914
+ return text.match(REEXPORT_RE)?.length ?? 0;
8915
+ }
8883
8916
  function stripBlockComments(line, state) {
8884
8917
  let out = "";
8885
8918
  let i = 0;
@@ -8913,7 +8946,8 @@ function extractWithRegex(langId, source, maxSymbols) {
8913
8946
  let pkg;
8914
8947
  const state = { inBlock: false };
8915
8948
  const lines = source.split("\n");
8916
- for (const raw of lines) {
8949
+ for (let i = 0; i < lines.length; i++) {
8950
+ const raw = lines[i];
8917
8951
  if (symbols.length >= maxSymbols) break;
8918
8952
  const stripped = stripBlockComments(raw, state);
8919
8953
  const line = /^\s/.test(raw) ? stripped : stripped.replace(/^\s+/, "");
@@ -8926,9 +8960,9 @@ function extractWithRegex(langId, source, maxSymbols) {
8926
8960
  if (!name) continue;
8927
8961
  if (seen.has(name)) continue;
8928
8962
  seen.add(name);
8929
- symbols.push({ name, kind: rule.kind });
8963
+ symbols.push({ name, kind: rule.kind, line: i + 1 });
8930
8964
  }
8931
- return { symbols, pkg };
8965
+ return { symbols, pkg, reexports: countReexports2(source, langId) };
8932
8966
  }
8933
8967
 
8934
8968
  // src/repomap/grammar.ts
@@ -9098,10 +9132,10 @@ async function extractFile(relPath, source) {
9098
9132
  const bytes = await loadGrammar(lang);
9099
9133
  if (bytes) {
9100
9134
  const ts = await extractWithTreeSitter(lang, bytes, source, MAX_SYMBOLS_PER_FILE);
9101
- if (ts) return { symbols: ts.symbols, pkg: ts.pkg, via: "tree-sitter" };
9135
+ if (ts) return { symbols: ts.symbols, pkg: ts.pkg, reexports: ts.reexports, via: "tree-sitter" };
9102
9136
  }
9103
9137
  const re = extractWithRegex(lang, source, MAX_SYMBOLS_PER_FILE);
9104
- return { symbols: re.symbols, pkg: re.pkg, via: "regex" };
9138
+ return { symbols: re.symbols, pkg: re.pkg, reexports: re.reexports, via: "regex" };
9105
9139
  }
9106
9140
  function wantsContent(relPath) {
9107
9141
  if (!langOf(relPath)) return false;
@@ -9288,6 +9322,10 @@ function hasLayerWord(name) {
9288
9322
  }
9289
9323
  var ENTRY_RE = /(^|\/)(main|index|program|startup|app|application|bootstrap|mod|__init__)(\.|\/)/i;
9290
9324
  var ENTRY_SUFFIX_RE = /(application|program|startup)\.(java|kt|cs|scala)$/i;
9325
+ function isEntryFile(relPath, sourceRoots = []) {
9326
+ if (ENTRY_SUFFIX_RE.test(relPath)) return true;
9327
+ return ENTRY_RE.test(relPath) && depthFromSourceRoot(relPath, sourceRoots) <= 1;
9328
+ }
9291
9329
  var TEST_RE = /(^|\/)(test|tests|__tests__|__mocks__|spec|specs|e2e)(\/|$)|\.(test|spec)\.[a-z]+$|test\.(java|kt|cs)$|_test\.(go|py|rb)$/i;
9292
9330
  var DOC_RE = /(^|\/)(docs?|examples?|samples?|demo|benchmarks?|bench|fixtures?)(\/|$)|\.(md|mdx|rst|txt|adoc)$/i;
9293
9331
  var GENERATED_RE = /\.(min|generated|designer)\.|_pb2?\.|\.g\.(cs|java)$|(^|\/)gen\//i;
@@ -9323,7 +9361,7 @@ function scorePath(relPath, sourceRoots) {
9323
9361
  score += W.sourceRoot;
9324
9362
  }
9325
9363
  const depth = depthFromSourceRoot(relPath, sourceRoots);
9326
- if (ENTRY_SUFFIX_RE.test(relPath) || ENTRY_RE.test(relPath) && depth <= 1) {
9364
+ if (isEntryFile(relPath, sourceRoots)) {
9327
9365
  score += W.entry;
9328
9366
  }
9329
9367
  const base = relPath.slice(relPath.lastIndexOf("/") + 1).replace(/\.[^.]+$/, "");
@@ -9351,7 +9389,7 @@ function rankFiles(paths, sourceRoots, hasSymbols) {
9351
9389
 
9352
9390
  // src/repomap/types.ts
9353
9391
  init_esm_shims();
9354
- var REPOMAP_CACHE_VERSION = 3;
9392
+ var REPOMAP_CACHE_VERSION = 5;
9355
9393
 
9356
9394
  // src/repomap/indexer.ts
9357
9395
  import { createHash as createHash2 } from "crypto";
@@ -9559,6 +9597,7 @@ async function buildRepoMap(root, opts = {}) {
9559
9597
  size: stat.size,
9560
9598
  symbols: ex.symbols,
9561
9599
  pkg: ex.pkg,
9600
+ reexports: ex.reexports,
9562
9601
  via: ex.via
9563
9602
  };
9564
9603
  } catch {
@@ -9566,15 +9605,18 @@ async function buildRepoMap(root, opts = {}) {
9566
9605
  }
9567
9606
  await yieldToLoop();
9568
9607
  }
9569
- const withSymbols = new Set(
9570
- Object.entries(result).filter(([, v]) => v.symbols.length > 0).map(([k]) => k)
9571
- );
9572
- const ranked = rankFiles([...withSymbols], shape.sourceRoots);
9608
+ const retained = {};
9609
+ for (const [rel, v] of Object.entries(result)) {
9610
+ if (v.symbols.length > 0 || (v.reexports ?? 0) > 0 || isEntryFile(rel, shape.sourceRoots)) {
9611
+ retained[rel] = v;
9612
+ }
9613
+ }
9614
+ const ranked = rankFiles(Object.keys(retained), shape.sourceRoots);
9573
9615
  const scan = {
9574
9616
  files: files.length,
9575
9617
  // 报「地图里有多少文件」而不是「本次新提取了多少」:命中缓存时
9576
- // 后者是 0,模型会以为结构图是空的(实测曾把 1324 个文件报成 0)
9577
- indexed: Object.keys(result).length,
9618
+ // 后者为 0,模型会误判结构图为空(实测曾把 1324 个文件报成 0)
9619
+ indexed: Object.keys(retained).length,
9578
9620
  truncated: enumTruncated || limit < rankedAll.length,
9579
9621
  elapsedMs: Date.now() - started
9580
9622
  };
@@ -9591,7 +9633,7 @@ async function buildRepoMap(root, opts = {}) {
9591
9633
  shape
9592
9634
  });
9593
9635
  }
9594
- return { root: absRoot, scale, scan, ranked, files: result, shape };
9636
+ return { root: absRoot, scale, scan, ranked, files: retained, shape };
9595
9637
  } catch {
9596
9638
  return empty;
9597
9639
  }
@@ -9632,6 +9674,11 @@ var GENERIC_SYMBOLS = /* @__PURE__ */ new Set([
9632
9674
  function meaningful(symbols) {
9633
9675
  return symbols.filter((s) => s.name.length > 3 && !GENERIC_SYMBOLS.has(s.name));
9634
9676
  }
9677
+ function symbolRef(s, withRange = false) {
9678
+ if (s.line === void 0 || s.name.includes(":")) return s.name;
9679
+ const end = withRange && s.endLine !== void 0 && s.endLine > s.line ? `-${s.endLine}` : "";
9680
+ return `${s.name}:${s.line}${end}`;
9681
+ }
9635
9682
  var GROUP_FILE_LIMIT = { small: 5, medium: 4, large: 1 };
9636
9683
  var BREADTH_SHARE = 0.5;
9637
9684
  var TAIL_RESERVE = 64;
@@ -9655,7 +9702,7 @@ function baseName(relPath) {
9655
9702
  function symbolsInline(symbols) {
9656
9703
  const kept = meaningful(symbols);
9657
9704
  if (kept.length === 0) return "";
9658
- const names = kept.slice(0, MAX_SYMBOLS_PER_LINE).map((s) => s.name);
9705
+ const names = kept.slice(0, MAX_SYMBOLS_PER_LINE).map((s) => symbolRef(s));
9659
9706
  const more = kept.length > MAX_SYMBOLS_PER_LINE ? ", \u2026" : "";
9660
9707
  return names.join(", ") + more;
9661
9708
  }
@@ -9663,14 +9710,14 @@ function buildGroups(map) {
9663
9710
  const groups = /* @__PURE__ */ new Map();
9664
9711
  for (const { path: rel } of map.ranked) {
9665
9712
  const entry = map.files[rel];
9666
- if (!entry || entry.symbols.length === 0) continue;
9713
+ if (!entry) continue;
9667
9714
  const label = groupLabelOf(rel, entry.pkg);
9668
9715
  let g = groups.get(label);
9669
9716
  if (!g) {
9670
9717
  g = { label, files: [] };
9671
9718
  groups.set(label, g);
9672
9719
  }
9673
- g.files.push({ path: rel, symbols: entry.symbols });
9720
+ g.files.push({ path: rel, symbols: entry.symbols, reexports: entry.reexports });
9674
9721
  }
9675
9722
  return [...groups.values()];
9676
9723
  }
@@ -9681,12 +9728,20 @@ function moduleLine(mod, info) {
9681
9728
  return ` ${label} (${info.files} \u6587\u4EF6)${layers ? ` ${layers}` : ""}${entry}`;
9682
9729
  }
9683
9730
  function renderLarge(map, budget) {
9731
+ const bestOfModule = /* @__PURE__ */ new Map();
9732
+ for (const r of map.ranked) {
9733
+ const slash = r.path.indexOf("/");
9734
+ const mod = slash < 0 ? "." : r.path.slice(0, slash);
9735
+ const prev = bestOfModule.get(mod);
9736
+ if (prev === void 0 || r.score > prev) bestOfModule.set(mod, r.score);
9737
+ }
9684
9738
  const perModule = /* @__PURE__ */ new Map();
9685
9739
  for (const rel of Object.keys(map.files)) {
9686
9740
  const slash = rel.indexOf("/");
9687
9741
  const first = slash < 0 ? "." : rel.slice(0, slash);
9688
9742
  const bucket = perModule.get(first) ?? {
9689
9743
  files: 0,
9744
+ best: bestOfModule.get(first) ?? Number.NEGATIVE_INFINITY,
9690
9745
  layers: /* @__PURE__ */ new Set(),
9691
9746
  entryDepth: Number.POSITIVE_INFINITY
9692
9747
  };
@@ -9706,7 +9761,9 @@ function renderLarge(map, budget) {
9706
9761
  }
9707
9762
  perModule.set(first, bucket);
9708
9763
  }
9709
- const sorted = [...perModule.entries()].sort((a, b) => b[1].files - a[1].files);
9764
+ const sorted = [...perModule.entries()].sort(
9765
+ (a, b) => b[1].best - a[1].best || b[1].files - a[1].files || a[0].localeCompare(b[0])
9766
+ );
9710
9767
  const lines = [];
9711
9768
  const lineBudget = Math.max(200, budget - TAIL_RESERVE);
9712
9769
  let used = 0;
@@ -9728,8 +9785,13 @@ function renderLarge(map, budget) {
9728
9785
  return lines.join("\n");
9729
9786
  }
9730
9787
  function fileLine(f) {
9731
- const sym = symbolsInline(f.symbols);
9732
- return ` ${baseName(f.path)}${sym ? `: ${sym}` : ""}`;
9788
+ const name = baseName(f.path);
9789
+ if (f.symbols.length > 0) {
9790
+ const sym = symbolsInline(f.symbols);
9791
+ return ` ${name}${sym ? `: ${sym}` : ""}`;
9792
+ }
9793
+ if (f.reexports) return ` ${name}: (\u518D\u5BFC\u51FA ${f.reexports})`;
9794
+ return ` ${name}: (\u5165\u53E3)`;
9733
9795
  }
9734
9796
  function renderDetailed(map, budget) {
9735
9797
  const limit = GROUP_FILE_LIMIT[map.scale === "medium" ? "medium" : "small"];
@@ -9764,23 +9826,36 @@ function renderDetailed(map, budget) {
9764
9826
  const out = [];
9765
9827
  for (const b of blocks) out.push(b.header, ...b.lines);
9766
9828
  const omitted = map.ranked.length - shownFiles;
9767
- if (omitted > 0) out.push(`\u2026 \u53E6\u6709 ${omitted} \u4E2A\u6587\u4EF6\u672A\u5217\u51FA\uFF0C\u7528 repo_map \u5DE5\u5177\u67E5\u770B`);
9829
+ 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`);
9768
9830
  return out.join("\n");
9769
9831
  }
9832
+ function shapeLine(shape) {
9833
+ if (!shape) return "";
9834
+ const parts = [];
9835
+ const kinds = shape.kinds.filter((k) => k !== "npm" || !shape.kinds.includes("npm-workspace"));
9836
+ if (kinds.length) parts.push(kinds.slice(0, 3).join(" \xB7 "));
9837
+ if (shape.modules.length > 1) parts.push(`${shape.modules.length} \u6A21\u5757`);
9838
+ const roots = [...new Set(shape.sourceRoots)].sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, 3);
9839
+ if (roots.length) parts.push(`\u6E90\u7801\u6839 ${roots.join(", ")}`);
9840
+ return parts.length ? `\u9879\u76EE\u5F62\u6001: ${parts.join(" \xB7 ")}` : "";
9841
+ }
9770
9842
  function renderRepoMap(map) {
9771
9843
  if (map.ranked.length === 0 && !map.shape) return "";
9772
9844
  const budget = renderBudget(map.scale);
9773
- 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";
9845
+ 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";
9846
+ const shape = shapeLine(map.shape);
9847
+ const head = shape ? `${title}
9848
+ ${shape}` : title;
9774
9849
  const bodyBudget = Math.max(200, budget - head.length - 1);
9775
9850
  const body = map.scale === "large" ? renderLarge(map, bodyBudget) : renderDetailed(map, bodyBudget);
9776
- if (!body.trim()) return "";
9851
+ if (!body.trim()) return shape;
9777
9852
  const text = `${head}
9778
9853
  ${body}`;
9779
9854
  if (text.length <= budget) return text;
9780
9855
  return cutAtLine(text, budget);
9781
9856
  }
9782
9857
  function cutAtLine(text, budget) {
9783
- const marker = "\n\u2026\uFF08\u7ED3\u6784\u56FE\u5DF2\u622A\u65AD\uFF0C\u7528 repo_map \u5DE5\u5177\u67E5\u770B\uFF09";
9858
+ const marker = "\n\u2026\uFF08\u7ED3\u6784\u56FE\u5DF2\u622A\u65AD\uFF0C\u7528 repo_map \u5DE5\u5177\u6309\u76EE\u5F55\u67E5\u770B\uFF09";
9784
9859
  const room = budget - marker.length;
9785
9860
  if (room <= 0) return marker.slice(1);
9786
9861
  const cut = text.slice(0, room);
@@ -13088,17 +13163,26 @@ function registerSearchTool(registry, workspace) {
13088
13163
  // src/tools/repoMap.ts
13089
13164
  init_esm_shims();
13090
13165
  var MAX_CHARS = 16e3;
13166
+ function filesUnder(map, prefix) {
13167
+ const keys = Object.keys(map.files);
13168
+ if (!prefix) return keys;
13169
+ if (prefix === ".") return keys.filter((rel) => !rel.includes("/"));
13170
+ return keys.filter((rel) => rel === prefix || rel.startsWith(`${prefix}/`));
13171
+ }
13091
13172
  function renderSubset(map, prefix, maxFiles) {
13092
13173
  const order = new Map(map.ranked.map((r, i) => [r.path, i]));
13093
- 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));
13174
+ const under = filesUnder(map, prefix).sort(
13175
+ (a, b) => (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity)
13176
+ );
13094
13177
  const matched = under.slice(0, maxFiles);
13095
13178
  if (matched.length === 0) return null;
13096
13179
  const entries = [];
13097
13180
  let used = 0;
13098
13181
  for (const rel of matched) {
13099
13182
  const f = map.files[rel];
13100
- const sym = f.symbols.slice(0, 8).map((s) => s.name).join(", ");
13101
- const line = `${rel}${sym ? `: ${sym}` : ""}`;
13183
+ const sym = f.symbols.slice(0, 8).map((s) => symbolRef(s, true)).join(", ");
13184
+ const body = f.symbols.length > 0 ? sym : f.reexports ? `(\u518D\u5BFC\u51FA ${f.reexports})` : "(\u5165\u53E3)";
13185
+ const line = `${rel}: ${body}`;
13102
13186
  if (used + line.length + 1 > MAX_CHARS) break;
13103
13187
  entries.push(line);
13104
13188
  used += line.length + 1;
@@ -13130,18 +13214,20 @@ function registerRepoMapTool(registry, workspace) {
13130
13214
  if (sub) return sub;
13131
13215
  return { error: `\u6CA1\u6709\u5339\u914D\u7684\u76EE\u5F55\uFF1A${path31}` };
13132
13216
  }
13133
- const groups = buildGroups(map).slice(0, 40).map((g) => ({
13134
- label: g.label,
13135
- files: g.files.length,
13136
- path: g.files[0]?.path.split("/").slice(0, -1).join("/") || "."
13137
- }));
13217
+ const groups = buildGroups(map).slice(0, 40).map((g) => {
13218
+ const dir = g.files[0]?.path.split("/").slice(0, -1).join("/") || ".";
13219
+ return {
13220
+ label: g.label,
13221
+ files: filesUnder(map, dir).length,
13222
+ ...dir === g.label ? {} : { path: dir }
13223
+ };
13224
+ });
13138
13225
  return {
13139
13226
  scale: map.scale,
13140
13227
  files: map.scan.files,
13141
13228
  indexed: map.scan.indexed,
13142
13229
  groups,
13143
- map: renderRepoMap(map),
13144
- 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"'
13230
+ 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"'
13145
13231
  };
13146
13232
  },
13147
13233
  { category: "search", riskLevel: "low", capabilities: ["repo_map"], requiresApproval: false },