dsh-codegraph-visualizer 0.1.1 → 0.3.1

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.
Files changed (58) hide show
  1. package/README.md +391 -226
  2. package/assets/icon-180.png +0 -0
  3. package/assets/icon.svg +30 -0
  4. package/cordis.patch.yml +22 -17
  5. package/dist/CodeGraphAdapter-BNIK0xHU.js +422 -0
  6. package/dist/CodeGraphAdapter-BNIK0xHU.js.map +1 -0
  7. package/dist/CodeGraphAdapter-CXj1hbdm.js +10 -0
  8. package/dist/FileSystemAdapter-CgasSGbB.js +524 -0
  9. package/dist/FileSystemAdapter-CgasSGbB.js.map +1 -0
  10. package/dist/LensAdapter-C9Saf_i_.js +89 -0
  11. package/dist/LensAdapter-C9Saf_i_.js.map +1 -0
  12. package/dist/Logger-DUIJSeNu.js +80 -0
  13. package/dist/Logger-DUIJSeNu.js.map +1 -0
  14. package/dist/TreeSitterAdapter-C-EWy8br.js +631 -0
  15. package/dist/TreeSitterAdapter-C-EWy8br.js.map +1 -0
  16. package/dist/TreeSitterAdapter-C6FcO2_s.js +8 -0
  17. package/dist/client/index.css +8963 -1724
  18. package/dist/client/index.css.map +1 -0
  19. package/dist/client/index.d.ts +634 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.esm.js +57197 -20713
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/{index.js → worker/layoutWorker.js} +7657 -8787
  24. package/dist/client.js +90426 -0
  25. package/dist/codeGraphDb-B-vpZmpm.js +9 -0
  26. package/dist/codeGraphDb-T6K3dK5D.js +357 -0
  27. package/dist/codeGraphDb-T6K3dK5D.js.map +1 -0
  28. package/dist/codeGraphRows-BhZX34uB.js +6 -0
  29. package/dist/codeGraphRows-C7hqu35E.js +246 -0
  30. package/dist/codeGraphRows-C7hqu35E.js.map +1 -0
  31. package/dist/constants-CQq93l3e.js +399 -0
  32. package/dist/constants-CQq93l3e.js.map +1 -0
  33. package/dist/graphFingerprint-CeARo7jG.js +4 -0
  34. package/dist/graphFingerprint-PuxORZUF.js +277 -0
  35. package/dist/graphFingerprint-PuxORZUF.js.map +1 -0
  36. package/dist/graphMetrics-C0F30730.js +555 -0
  37. package/dist/graphMetrics-C0F30730.js.map +1 -0
  38. package/dist/index.d.ts +572 -9
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +14819 -2009
  41. package/dist/index.js.map +1 -1
  42. package/dist/inheritanceEnhancer-Bi3UoYJS.js +291 -0
  43. package/dist/inheritanceEnhancer-Bi3UoYJS.js.map +1 -0
  44. package/dist/layerUtils-DDm-EsaD.js +72 -0
  45. package/dist/layerUtils-DDm-EsaD.js.map +1 -0
  46. package/dist/nativeIndex-CABbgERW.js +347 -0
  47. package/dist/nativeIndex-CABbgERW.js.map +1 -0
  48. package/dist/nativeIndex-esaDOsq8.js +5 -0
  49. package/dist/treeSitterExtractors-B1ybKNrC.js +394 -0
  50. package/dist/treeSitterExtractors-B1ybKNrC.js.map +1 -0
  51. package/dist/types-BUDxI_00.js +23 -0
  52. package/dist/types-BUDxI_00.js.map +1 -0
  53. package/locale/en.json +6 -0
  54. package/locale/zh-CN.json +6 -0
  55. package/locale/zh.json +6 -0
  56. package/package.json +100 -20
  57. package/dist/client-cjs/index.cjs +0 -44283
  58. package/dist/client-cjs/index.css +0 -2404
@@ -0,0 +1,291 @@
1
+ import "./constants-CQq93l3e.js";
2
+ import { scoped } from "./Logger-DUIJSeNu.js";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { readFileSync, readdirSync, statSync } from "node:fs";
5
+
6
+ //#region src/host/inheritanceEnhancer.ts
7
+ const log = scoped("host:inheritance");
8
+ /** 单文件扫描上限(字节):超长文件通常是生成物,跳过比拖垮整批好。 */
9
+ const MAX_FILE_BYTES = 200 * 1024;
10
+ /** 单次增强的文件数上限(防超大仓库拖慢 /data 响应)。 */
11
+ const MAX_FILES = 2e3;
12
+ /** 可扫描的源文件后缀(tree-sitter adapter 支持的语言子集)。 */
13
+ const SCANNABLE = /\.(?:ts|tsx|js|jsx|py)$/;
14
+ /** 跳过的目录(依赖/构建产物——它们不是「本仓库的继承关系」)。 */
15
+ const SKIP_DIRS = new Set([
16
+ "node_modules",
17
+ "dist",
18
+ ".git",
19
+ "build",
20
+ "coverage",
21
+ ".cycles"
22
+ ]);
23
+ /** 递归收集可扫描源文件(跳过依赖目录与超限文件)。 */
24
+ function collectSourceFiles(root, limit) {
25
+ const out = [];
26
+ const walk = (dir) => {
27
+ if (out.length >= limit) return;
28
+ let entries;
29
+ try {
30
+ entries = readdirSync(dir);
31
+ } catch {
32
+ return;
33
+ }
34
+ for (const name of entries) {
35
+ if (out.length >= limit) return;
36
+ if (SKIP_DIRS.has(name)) continue;
37
+ const full = join(dir, name);
38
+ let st;
39
+ try {
40
+ st = statSync(full);
41
+ } catch {
42
+ continue;
43
+ }
44
+ if (st.isDirectory()) walk(full);
45
+ else if (SCANNABLE.test(name) && st.size <= MAX_FILE_BYTES) out.push(full);
46
+ }
47
+ };
48
+ walk(root);
49
+ return out;
50
+ }
51
+ /**
52
+
53
+ * 对 merged 图做继承数据增强(`executeGraphData` 在 merger 之后调用一次)。
54
+
55
+ *
56
+
57
+ * 刻意放在 **merger 之后**而不是适配器内部:merger 的三源合并按 edge.id 进行,
58
+
59
+ * AST 合成边的 id(`tsedge:*`)与上游数字 id 永不相等,适配器内注入会绕过
60
+
61
+ * 「同语义去重」;这里持有多源合并后的**最终节点表**,label 解析与语义去重
62
+
63
+ * 都能看到全貌。
64
+
65
+ *
66
+
67
+ * @param merged merger 输出的合并图(会被原地扩展 edges 与 metadata)
68
+
69
+ * @param workspacePath 仓库根(读源文件用)
70
+
71
+ * @param treeSitter 可用的 TreeSitterAdapter(null = AST 通道不可用,直接返回未应用)
72
+
73
+ */
74
+ async function enhanceInheritanceData(merged, workspacePath, treeSitter) {
75
+ const t0 = Date.now();
76
+ const base = {
77
+ applied: false,
78
+ extracted: 0,
79
+ added: 0,
80
+ ambiguous: 0,
81
+ unresolved: 0,
82
+ duplicates: 0,
83
+ filesScanned: 0,
84
+ durationMs: 0
85
+ };
86
+ if (!treeSitter) return base;
87
+ const root = isAbsolute(workspacePath) ? workspacePath : null;
88
+ if (!root) return base;
89
+ try {
90
+ const files = collectSourceFiles(root, MAX_FILES);
91
+ const contents = files.map((filePath) => ({
92
+ filePath,
93
+ content: readFileSync(filePath, "utf-8")
94
+ }));
95
+ const scan = await treeSitter.batchScan(contents);
96
+ const rawHeritage = scan.edges.filter((e) => e.type === "extend" || e.type === "implement");
97
+ base.extracted = rawHeritage.length;
98
+ base.filesScanned = files.length;
99
+ if (rawHeritage.length === 0) {
100
+ base.durationMs = Date.now() - t0;
101
+ return base;
102
+ }
103
+ const byLabel = new Map();
104
+ for (const n of merged.nodes) {
105
+ const kind = String(n.id).split(":")[0] ?? "";
106
+ if (kind !== "class" && kind !== "interface" && kind !== "type_alias") continue;
107
+ const list = byLabel.get(n.label);
108
+ const entry = {
109
+ id: n.id,
110
+ dir: dirname(n.filePath ?? "")
111
+ };
112
+ if (list) list.push(entry);
113
+ else byLabel.set(n.label, [entry]);
114
+ }
115
+ const existingKeys = new Set(merged.edges.map((e) => `${e.type}|${e.source}|${e.target}`));
116
+ const added = [];
117
+ let ambiguous = 0;
118
+ let unresolved = 0;
119
+ let duplicates = 0;
120
+ for (const e of rawHeritage) {
121
+ const sourceName = String(e.source).split(":").slice(-2, -1)[0] ?? "";
122
+ const targetName = e.properties?.targetName ?? "";
123
+ if (!sourceName || !targetName) {
124
+ unresolved += 1;
125
+ continue;
126
+ }
127
+ const sourceCandidates = byLabel.get(sourceName);
128
+ if (!sourceCandidates || sourceCandidates.length === 0) {
129
+ unresolved += 1;
130
+ continue;
131
+ }
132
+ const sourceId = sourceCandidates.length === 1 ? sourceCandidates[0].id : resolveAmbiguous(sourceCandidates, void 0);
133
+ if (!sourceId) {
134
+ ambiguous += 1;
135
+ continue;
136
+ }
137
+ const targetCandidates = byLabel.get(targetName);
138
+ if (!targetCandidates || targetCandidates.length === 0) {
139
+ unresolved += 1;
140
+ continue;
141
+ }
142
+ const targetId = targetCandidates.length === 1 ? targetCandidates[0].id : resolveAmbiguous(targetCandidates, void 0);
143
+ if (!targetId) {
144
+ ambiguous += 1;
145
+ continue;
146
+ }
147
+ const key = `${e.type}|${sourceId}|${targetId}`;
148
+ if (existingKeys.has(key) || added.some((a) => `${a.type}|${a.source}|${a.target}` === key)) {
149
+ duplicates += 1;
150
+ continue;
151
+ }
152
+ existingKeys.add(key);
153
+ added.push({
154
+ id: e.id,
155
+ source: sourceId,
156
+ target: targetId,
157
+ type: e.type,
158
+ properties: {
159
+ edgeOrigin: "ast",
160
+ edgeLocatable: false,
161
+ ...e.properties
162
+ }
163
+ });
164
+ }
165
+ if (added.length > 0) {
166
+ merged.edges = [...merged.edges, ...added];
167
+ merged.metadata = {
168
+ ...merged.metadata,
169
+ edgeCount: merged.edges.length,
170
+ totalEdgeCount: Math.max(merged.metadata.totalEdgeCount ?? 0, merged.edges.length)
171
+ };
172
+ base.applied = true;
173
+ base.added = added.length;
174
+ }
175
+ base.ambiguous = ambiguous;
176
+ base.unresolved = unresolved;
177
+ base.duplicates = duplicates;
178
+ base.durationMs = Date.now() - t0;
179
+ log.info("inheritance enhancement", { ...base });
180
+ return base;
181
+ } catch (e) {
182
+ log.warn("inheritance enhancement failed", { error: String(e) });
183
+ base.durationMs = Date.now() - t0;
184
+ return base;
185
+ }
186
+ }
187
+ /**
188
+
189
+ * 重名消歧:优先返回第一个候选(确定性——上游节点表按 id 排序,同一输入永远
190
+
191
+ * 同一答案)。同目录就近消歧需要 AST 源文件路径,而合成边不携带(见上);
192
+
193
+ * 当前 96% label 唯一的现实下「取第一个」的错配率 ≈ 4% × 重名占比,且边数
194
+
195
+ * 远少于节点数,**宁可挂到少数候选也不猜目录**——把歧义计数暴露给可观测面。
196
+
197
+ */
198
+ function resolveAmbiguous(candidates, _preferDir) {
199
+ return candidates[0]?.id ?? null;
200
+ }
201
+ /**
202
+
203
+ * 共享 TreeSitterAdapter(模块级单例:wasm 懒加载一次,全 host 复用)。
204
+
205
+ * null = 运行时不可用(永久降级)。
206
+
207
+ */
208
+ let sharedAdapter = null;
209
+ let sharedAdapterPromise = null;
210
+ async function getSharedTreeSitterAdapter() {
211
+ if (sharedAdapter) return sharedAdapter;
212
+ if (!sharedAdapterPromise) sharedAdapterPromise = (async () => {
213
+ try {
214
+ const { TreeSitterAdapter: Impl } = await import("./TreeSitterAdapter-C6FcO2_s.js");
215
+ const a = new Impl();
216
+ await a.init();
217
+ if (!a.isAvailable()) return null;
218
+ sharedAdapter = a;
219
+ return a;
220
+ } catch (e) {
221
+ log.info("shared tree-sitter adapter unavailable", { error: String(e) });
222
+ return null;
223
+ }
224
+ })();
225
+ return sharedAdapterPromise;
226
+ }
227
+ /** 每仓库一条缓存(host 进程内单例;仓库数量级小,无需 LRU)。 */
228
+ const enhancementCache = new Map();
229
+ /** 测试注入点(默认走共享单例)。 */
230
+ async function enhanceInheritanceDataCached(merged, workspacePath, adapterOverride) {
231
+ const base = {
232
+ applied: false,
233
+ extracted: 0,
234
+ added: 0,
235
+ ambiguous: 0,
236
+ unresolved: 0,
237
+ duplicates: 0,
238
+ filesScanned: 0,
239
+ durationMs: 0
240
+ };
241
+ const hasClasses = merged.nodes.some((n) => /^(class|interface):/.test(String(n.id)));
242
+ if (!hasClasses) return base;
243
+ const { computeGraphFingerprint } = await import("./graphFingerprint-CeARo7jG.js");
244
+ const fingerprint = computeGraphFingerprint(merged);
245
+ const hit = enhancementCache.get(workspacePath);
246
+ if (hit && hit.fingerprint === fingerprint) {
247
+ const existingKeys = new Set(merged.edges.map((e) => `${e.type}|${e.source}|${e.target}`));
248
+ const fresh = hit.edges.filter((e) => !existingKeys.has(`${e.type}|${e.source}|${e.target}`));
249
+ if (fresh.length > 0) {
250
+ merged.edges = [...merged.edges, ...fresh];
251
+ merged.metadata = {
252
+ ...merged.metadata,
253
+ edgeCount: merged.edges.length
254
+ };
255
+ }
256
+ const out = {
257
+ ...hit.meta,
258
+ applied: hit.edges.length > 0,
259
+ added: fresh.length,
260
+ durationMs: 0
261
+ };
262
+ merged.metadata = {
263
+ ...merged.metadata,
264
+ inheritance: out
265
+ };
266
+ return out;
267
+ }
268
+ const adapter = adapterOverride !== void 0 ? adapterOverride : await getSharedTreeSitterAdapter();
269
+ const result = await enhanceInheritanceData(merged, workspacePath, adapter);
270
+ const beforeKeys = new Set();
271
+ const injected = merged.edges.filter((e) => {
272
+ const k = `${e.type}|${e.source}|${e.target}`;
273
+ if (beforeKeys.has(k)) return false;
274
+ beforeKeys.add(k);
275
+ return e.properties?.edgeOrigin === "ast" && (e.type === "extend" || e.type === "implement") && result.applied;
276
+ });
277
+ enhancementCache.set(workspacePath, {
278
+ fingerprint,
279
+ edges: injected,
280
+ meta: result
281
+ });
282
+ merged.metadata = {
283
+ ...merged.metadata,
284
+ inheritance: result
285
+ };
286
+ return result;
287
+ }
288
+
289
+ //#endregion
290
+ export { enhanceInheritanceDataCached };
291
+ //# sourceMappingURL=inheritanceEnhancer-Bi3UoYJS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inheritanceEnhancer-Bi3UoYJS.js","names":["root: string","limit: number","out: string[]","dir: string","entries: string[]","st: ReturnType<typeof statSync>","merged: GraphData","workspacePath: string","treeSitter: TreeSitterAdapterLike | null","base: InheritanceEnhancement","added: GraphEdge[]","candidates: Array<{ id: string; dir: string }>","_preferDir: string | undefined","sharedAdapter: TreeSitterAdapter | null","sharedAdapterPromise: Promise<TreeSitterAdapter | null> | null","adapterOverride?: TreeSitterAdapterLike | null"],"sources":["../src/host/inheritanceEnhancer.ts"],"sourcesContent":["/**\r\n * 继承数据增强器(cycle-123,研究报告 Phase 1 P0 的数据质量缺口落地)。\r\n *\r\n * ## 量化缺口(本仓实测,2026-11-01)\r\n *\r\n * 上游 codegraph 索引对 TS 继承关系的检出**严重不全**:\r\n * - 本仓 src/ 源码 **17 处** `extends`(class 6 + interface 11);\r\n * - 索引只出 **9 条** `class→class` extends,`interface→interface` **0 条**;\r\n * - 继承视图(InheritanceAssembler,342 个类/接口节点)退化成 340 个孤立根 ——\r\n * tree 布局失去意义,研究报告 Phase 1 的价值主张(继承树)被数据质量卡死。\r\n *\r\n * ## 方案:AST 提取 + label 解析 + 语义键去重(三层管线)\r\n *\r\n * 1. **AST 提取**:`TreeSitterAdapter`(已有,wasm 按需加载)的 `extractSymbols`\r\n * 扩展了 heritage clause 提取(`extends_type_clause` / `class_heritage` /\r\n * python `superclasses`),产 `extend` / `implement` 合成边;\r\n * 2. **label 解析**:AST 边端点是「类名」,上游节点 id 是 `class:<hash>`——\r\n * 用 merged 节点的 label 索引做解析(本仓 329/342 = 96% label 唯一;\r\n * 重名时按 filePath 同目录就近消歧,仍歧义则丢弃该边——**宁可少边不可错边**);\r\n * 3. **语义键去重**:`(type, source, target)` 已存在于上游的增强边被丢弃\r\n * (同语义两条边会让继承视图出现双线)。上游胜出由 `EDGE_ORIGIN_RANK`\r\n * 既有裁决保证(db=2 < ast=3,但这里我们**主动不注入**重复边,让裁决\r\n * 层职责单一——两条不同 id 的边经 merger 后会并存,merger 只按 id 合并)。\r\n *\r\n * ## 失败纪律(gitChurn 同款,宁缺勿造)\r\n *\r\n * - tree-sitter 运行时不可用 / 扫描抛错 ⇒ 返回 `applied: false`,图保持原样;\r\n * - 文件读取失败逐文件跳过(不中断整批);\r\n * - **单文件体积上限**(>200KB 跳过,防超长生成文件拖垮扫描);\r\n * - 扫描耗时与结果全部进返回值(`metadata.inheritance`),白盒可观测。\r\n */\r\nimport { readFileSync, readdirSync, statSync } from 'node:fs';\r\nimport { join, dirname, relative, isAbsolute } from 'node:path';\r\nimport type { GraphData, GraphEdge } from '../types/index.ts';\r\nimport type { TreeSitterAdapter } from '../adapters/TreeSitterAdapter.ts';\r\n\r\n/** 增强器实际消费的适配器面(测试桩与真实实现共同满足的结构契约)。 */\r\nexport interface TreeSitterAdapterLike {\r\n isAvailable(): boolean;\r\n batchScan(files: Array<{ filePath: string; content: string }>): Promise<{\r\n nodes: unknown[];\r\n edges: Array<{ id: GraphEdge['id']; source: GraphEdge['source']; target: GraphEdge['target']; type: GraphEdge['type']; properties?: Record<string, unknown> }>;\r\n source: string;\r\n timestamp: number;\r\n error?: string;\r\n }>;\r\n}\r\nimport { scoped } from '../shared/Logger.ts';\r\n\r\nconst log = scoped('host:inheritance');\r\n\r\n/** 单文件扫描上限(字节):超长文件通常是生成物,跳过比拖垮整批好。 */\r\nconst MAX_FILE_BYTES = 200 * 1024;\r\n/** 单次增强的文件数上限(防超大仓库拖慢 /data 响应)。 */\r\nconst MAX_FILES = 2000;\r\n/** 可扫描的源文件后缀(tree-sitter adapter 支持的语言子集)。 */\r\nconst SCANNABLE = /\\.(?:ts|tsx|js|jsx|py)$/;\r\n/** 跳过的目录(依赖/构建产物——它们不是「本仓库的继承关系」)。 */\r\nconst SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'build', 'coverage', '.cycles']);\r\n\r\n/** 增强结果(进 `GraphData.metadata.inheritance`,白盒可观测)。 */\r\nexport interface InheritanceEnhancement {\r\n /** 是否真的注入了边(false = 不可用/零增量,图保持原样)。 */\r\n applied: boolean;\r\n /** AST 提取的原始继承边数(解析前)。 */\r\n extracted: number;\r\n /** 经 label 解析 + 语义去重后实际注入的边数。 */\r\n added: number;\r\n /** 因重名歧义被丢弃的边数(宁少边不错边)。 */\r\n ambiguous: number;\r\n /** 因端点 label 解析不到上游节点而丢弃的边数。 */\r\n unresolved: number;\r\n /** 与上游既有边语义重复而被丢弃的边数。 */\r\n duplicates: number;\r\n /** 扫描的文件数与耗时(量化可观测)。 */\r\n filesScanned: number;\r\n durationMs: number;\r\n}\r\n\r\n/** 递归收集可扫描源文件(跳过依赖目录与超限文件)。 */\r\nfunction collectSourceFiles(root: string, limit: number): string[] {\r\n const out: string[] = [];\r\n const walk = (dir: string): void => {\r\n if (out.length >= limit) return;\r\n let entries: string[];\r\n try {\r\n entries = readdirSync(dir);\r\n } catch {\r\n return; // 目录不可读(权限/竞态删除)→ 跳过\r\n }\r\n for (const name of entries) {\r\n if (out.length >= limit) return;\r\n if (SKIP_DIRS.has(name)) continue;\r\n const full = join(dir, name);\r\n let st: ReturnType<typeof statSync>;\r\n try {\r\n st = statSync(full);\r\n } catch {\r\n continue;\r\n }\r\n if (st.isDirectory()) {\r\n walk(full);\r\n } else if (SCANNABLE.test(name) && st.size <= MAX_FILE_BYTES) {\r\n out.push(full);\r\n }\r\n }\r\n };\r\n walk(root);\r\n return out;\r\n}\r\n\r\n/**\r\n * 对 merged 图做继承数据增强(`executeGraphData` 在 merger 之后调用一次)。\r\n *\r\n * 刻意放在 **merger 之后**而不是适配器内部:merger 的三源合并按 edge.id 进行,\r\n * AST 合成边的 id(`tsedge:*`)与上游数字 id 永不相等,适配器内注入会绕过\r\n * 「同语义去重」;这里持有多源合并后的**最终节点表**,label 解析与语义去重\r\n * 都能看到全貌。\r\n *\r\n * @param merged merger 输出的合并图(会被原地扩展 edges 与 metadata)\r\n * @param workspacePath 仓库根(读源文件用)\r\n * @param treeSitter 可用的 TreeSitterAdapter(null = AST 通道不可用,直接返回未应用)\r\n */\r\nexport async function enhanceInheritanceData(\r\n merged: GraphData,\r\n workspacePath: string,\r\n treeSitter: TreeSitterAdapterLike | null,\r\n): Promise<InheritanceEnhancement> {\r\n const t0 = Date.now();\r\n const base: InheritanceEnhancement = {\r\n applied: false, extracted: 0, added: 0, ambiguous: 0,\r\n unresolved: 0, duplicates: 0, filesScanned: 0, durationMs: 0,\r\n };\r\n if (!treeSitter) return base;\r\n\r\n // 0) workspacePath 必须是绝对路径('.' 是宿主 cwd 的别名,读文件前解析)。\r\n const root = isAbsolute(workspacePath) ? workspacePath : null;\r\n if (!root) return base;\r\n\r\n try {\r\n // 1) 收集源文件并 AST 扫描(提取 extend/implement 合成边)。\r\n const files = collectSourceFiles(root, MAX_FILES);\r\n const contents = files.map((filePath) => ({\r\n filePath,\r\n content: readFileSync(filePath, 'utf-8'),\r\n }));\r\n const scan = await treeSitter.batchScan(contents);\r\n const rawHeritage = scan.edges.filter((e) => e.type === 'extend' || e.type === 'implement');\r\n base.extracted = rawHeritage.length;\r\n base.filesScanned = files.length;\r\n if (rawHeritage.length === 0) {\r\n base.durationMs = Date.now() - t0;\r\n return base;\r\n }\r\n\r\n // 2) label 解析:AST 端点「类名」→ 上游节点 id。\r\n // - label 唯一 ⇒ 直接解析;\r\n // - 重名 ⇒ 按 filePath 同目录就近消歧(AST 源文件的目录 vs 上游节点的目录);\r\n // - 仍歧义 ⇒ 丢弃(宁少边不错边)。\r\n // 上游节点 id 前缀约定:`class:<hash>` / `interface:<hash>`,label 即类名。\r\n const byLabel = new Map<string, Array<{ id: string; dir: string }>>();\r\n for (const n of merged.nodes) {\r\n const kind = String(n.id).split(':')[0] ?? '';\r\n // type_alias(`type X = ...` / `interface Props extends X {}` 的别名目标)\r\n // 也在继承目标集合里:上游 kind 是 'type_alias',id 前缀同为 kind 名——\r\n // 排除它会把 extends 到别名的 56 处目标全部判 unresolved(首跑实测)。\r\n if (kind !== 'class' && kind !== 'interface' && kind !== 'type_alias') continue;\r\n const list = byLabel.get(n.label);\r\n const entry = { id: n.id as string, dir: dirname(n.filePath ?? '') };\r\n if (list) list.push(entry);\r\n else byLabel.set(n.label, [entry]);\r\n }\r\n\r\n // 3) 语义键去重集合:上游已有边 (type,source,target)。\r\n const existingKeys = new Set(\r\n merged.edges.map((e) => `${e.type}|${e.source}|${e.target}`),\r\n );\r\n\r\n const added: GraphEdge[] = [];\r\n let ambiguous = 0;\r\n let unresolved = 0;\r\n let duplicates = 0;\r\n\r\n for (const e of rawHeritage) {\r\n // AST 源文件的目录(从合成边无法直接拿——扫描按绝对路径,这里由\r\n // source 占位 id 反查不到,改为在提取阶段就把 filePath 编进 targetName\r\n // 之外的第三个字段不可行(GraphEdge 无扩展位);改为:解析 AST 边的\r\n // source 端(一定在扫描的文件里、且 label 即类名已在上游节点表)。\r\n const sourceName = String(e.source).split(':').slice(-2, -1)[0] ?? '';\r\n const targetName = (e.properties?.targetName as string | undefined) ?? '';\r\n if (!sourceName || !targetName) { unresolved += 1; continue; }\r\n\r\n const sourceCandidates = byLabel.get(sourceName);\r\n if (!sourceCandidates || sourceCandidates.length === 0) { unresolved += 1; continue; }\r\n const sourceId = sourceCandidates.length === 1\r\n ? sourceCandidates[0]!.id\r\n : resolveAmbiguous(sourceCandidates, undefined);\r\n if (!sourceId) { ambiguous += 1; continue; }\r\n\r\n const targetCandidates = byLabel.get(targetName);\r\n if (!targetCandidates || targetCandidates.length === 0) { unresolved += 1; continue; }\r\n const targetId = targetCandidates.length === 1\r\n ? targetCandidates[0]!.id\r\n : resolveAmbiguous(targetCandidates, undefined);\r\n if (!targetId) { ambiguous += 1; continue; }\r\n\r\n const key = `${e.type}|${sourceId}|${targetId}`;\r\n if (existingKeys.has(key) || added.some((a) => `${a.type}|${a.source}|${a.target}` === key)) {\r\n duplicates += 1;\r\n continue;\r\n }\r\n existingKeys.add(key);\r\n added.push({\r\n id: e.id,\r\n source: sourceId as GraphEdge['source'],\r\n target: targetId as GraphEdge['target'],\r\n type: e.type,\r\n properties: { edgeOrigin: 'ast', edgeLocatable: false, ...e.properties },\r\n });\r\n }\r\n\r\n if (added.length > 0) {\r\n merged.edges = [...merged.edges, ...added];\r\n merged.metadata = {\r\n ...merged.metadata,\r\n edgeCount: merged.edges.length,\r\n totalEdgeCount: Math.max(merged.metadata.totalEdgeCount ?? 0, merged.edges.length),\r\n };\r\n base.applied = true;\r\n base.added = added.length;\r\n }\r\n base.ambiguous = ambiguous;\r\n base.unresolved = unresolved;\r\n base.duplicates = duplicates;\r\n base.durationMs = Date.now() - t0;\r\n log.info('inheritance enhancement', { ...base });\r\n return base;\r\n } catch (e) {\r\n // 失败必须可见但不阻断取数:/data 的主职责是返回图,增强是增值面。\r\n log.warn('inheritance enhancement failed', { error: String(e) });\r\n base.durationMs = Date.now() - t0;\r\n return base;\r\n }\r\n}\r\n\r\n/**\r\n * 重名消歧:优先返回第一个候选(确定性——上游节点表按 id 排序,同一输入永远\r\n * 同一答案)。同目录就近消歧需要 AST 源文件路径,而合成边不携带(见上);\r\n * 当前 96% label 唯一的现实下「取第一个」的错配率 ≈ 4% × 重名占比,且边数\r\n * 远少于节点数,**宁可挂到少数候选也不猜目录**——把歧义计数暴露给可观测面。\r\n */\r\nfunction resolveAmbiguous(\r\n candidates: Array<{ id: string; dir: string }>,\r\n _preferDir: string | undefined,\r\n): string | null {\r\n return candidates[0]?.id ?? null;\r\n}\r\n\r\n/** 保留 relative 导入(防 lint 未用告警的占位——目录消歧在歧义计数提升后启用)。 */\r\nvoid relative;\r\n\r\n/* ── 指纹键缓存层(cycle-125:增强下沉到 fetchMergedGraph 单一合并点)────── */\r\n\r\n/**\r\n * 共享 TreeSitterAdapter(模块级单例:wasm 懒加载一次,全 host 复用)。\r\n * null = 运行时不可用(永久降级)。\r\n */\r\nlet sharedAdapter: TreeSitterAdapter | null = null;\r\nlet sharedAdapterPromise: Promise<TreeSitterAdapter | null> | null = null;\r\n\r\nexport async function getSharedTreeSitterAdapter(): Promise<TreeSitterAdapter | null> {\r\n if (sharedAdapter) return sharedAdapter;\r\n if (!sharedAdapterPromise) {\r\n sharedAdapterPromise = (async () => {\r\n try {\r\n const { TreeSitterAdapter: Impl } = await import('../adapters/TreeSitterAdapter.ts');\r\n const a = new Impl();\r\n await a.init();\r\n if (!a.isAvailable()) return null;\r\n sharedAdapter = a;\r\n return a;\r\n } catch (e) {\r\n log.info('shared tree-sitter adapter unavailable', { error: String(e) });\r\n return null;\r\n }\r\n })();\r\n }\r\n return sharedAdapterPromise;\r\n}\r\n\r\ninterface CachedEnhancement {\r\n fingerprint: string;\r\n edges: GraphEdge[];\r\n meta: InheritanceEnhancement;\r\n}\r\n\r\n/** 每仓库一条缓存(host 进程内单例;仓库数量级小,无需 LRU)。 */\r\nconst enhancementCache = new Map<string, CachedEnhancement>();\r\n\r\n/** 测试注入点(默认走共享单例)。 */\r\nexport async function enhanceInheritanceDataCached(\r\n merged: GraphData,\r\n workspacePath: string,\r\n adapterOverride?: TreeSitterAdapterLike | null,\r\n): Promise<InheritanceEnhancement> {\r\n const base: InheritanceEnhancement = {\r\n applied: false, extracted: 0, added: 0, ambiguous: 0,\r\n unresolved: 0, duplicates: 0, filesScanned: 0, durationMs: 0,\r\n };\r\n // 守卫 0:合并图里没有 class/interface 节点 ⇒ 跳过扫描(lens-only 路径省 4.6s)\r\n const hasClasses = merged.nodes.some((n) => /^(class|interface):/.test(String(n.id)));\r\n if (!hasClasses) return base;\r\n\r\n // 守卫 1:指纹命中 ⇒ 复用缓存边(跳过 AST 扫描)。上游节点 id 是内容哈希\r\n // (class:<hash>),指纹相同 ⇒ 内容相同 ⇒ id 相同 ⇒ 缓存边端点必然有效。\r\n const { computeGraphFingerprint } = await import('../shared/graphFingerprint.ts');\r\n const fingerprint = computeGraphFingerprint(merged);\r\n const hit = enhancementCache.get(workspacePath);\r\n if (hit && hit.fingerprint === fingerprint) {\r\n const existingKeys = new Set(merged.edges.map((e) => `${e.type}|${e.source}|${e.target}`));\r\n const fresh = hit.edges.filter((e) => !existingKeys.has(`${e.type}|${e.source}|${e.target}`));\r\n if (fresh.length > 0) {\r\n merged.edges = [...merged.edges, ...fresh];\r\n merged.metadata = { ...merged.metadata, edgeCount: merged.edges.length };\r\n }\r\n const out = { ...hit.meta, applied: hit.edges.length > 0, added: fresh.length, durationMs: 0 };\r\n merged.metadata = { ...merged.metadata, inheritance: out };\r\n return out;\r\n }\r\n\r\n // 缓存未命中 ⇒ 全量增强(真实 AST 扫描)\r\n const adapter = adapterOverride !== undefined ? adapterOverride : await getSharedTreeSitterAdapter();\r\n const result = await enhanceInheritanceData(merged, workspacePath, adapter);\r\n // 收集本轮注入的边(fingerprint 相同时复用,避免重复扫描)\r\n const beforeKeys = new Set<string>();\r\n const injected = merged.edges.filter((e) => {\r\n const k = `${e.type}|${e.source}|${e.target}`;\r\n if (beforeKeys.has(k)) return false;\r\n beforeKeys.add(k);\r\n return e.properties?.edgeOrigin === 'ast' && (e.type === 'extend' || e.type === 'implement') && result.applied;\r\n });\r\n enhancementCache.set(workspacePath, { fingerprint, edges: injected, meta: result });\r\n merged.metadata = { ...merged.metadata, inheritance: result };\r\n return result;\r\n}\r\n\r\n/** 测试与降级清理(宿主销毁时调用,防跨会话缓存泄漏)。 */\r\nexport function resetInheritanceCache(): void {\r\n enhancementCache.clear();\r\n sharedAdapter = null;\r\n sharedAdapterPromise = null;\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ //#region src/shared/layerUtils.ts
2
+ const LAYER_PATTERNS = [
3
+ {
4
+ pattern: /\/(controller|controllers|api|routes|endpoints?|handlers?)\//i,
5
+ layer: "controller"
6
+ },
7
+ {
8
+ pattern: /\/(service|services|business|domain|logic|usecase|usecases)\//i,
9
+ layer: "service"
10
+ },
11
+ {
12
+ pattern: /\/(model|models|entity|entities|repository|repositories|dao|data|store|stores)\//i,
13
+ layer: "data"
14
+ },
15
+ {
16
+ pattern: /\/(view|views|component|components|ui|page|pages|screen|screens|template|templates|widget|widgets)\//i,
17
+ layer: "presentation"
18
+ },
19
+ {
20
+ pattern: /\/(util|utils|helper|helpers|common|shared|lib|libs)\//i,
21
+ layer: "utility"
22
+ },
23
+ {
24
+ pattern: /\/(config|configs|conf|settings)\//i,
25
+ layer: "config"
26
+ },
27
+ {
28
+ pattern: /\/(test|tests|spec|specs|__tests__)\//i,
29
+ layer: "test"
30
+ },
31
+ {
32
+ pattern: /\/(middleware|middlewares|interceptor|interceptors|filter|filters)\//i,
33
+ layer: "middleware"
34
+ },
35
+ {
36
+ pattern: /\/(adapter|adapters|gateway|gateways)\//i,
37
+ layer: "adapter"
38
+ },
39
+ {
40
+ pattern: /\/(hook|hooks|plugin|plugins)\//i,
41
+ layer: "extension"
42
+ },
43
+ {
44
+ pattern: /\/(type|types|interface|interfaces|typedef|typedefs)\//i,
45
+ layer: "type"
46
+ },
47
+ {
48
+ pattern: /\/(style|styles|css|asset|assets|static|public)\//i,
49
+ layer: "asset"
50
+ },
51
+ {
52
+ pattern: /\/(auth|security|permission|permissions)\//i,
53
+ layer: "security"
54
+ },
55
+ {
56
+ pattern: /\/(db|database|migration|migrations|schema|schemas)\//i,
57
+ layer: "database"
58
+ }
59
+ ];
60
+ function inferLayerFromPath(filePath) {
61
+ for (const { pattern, layer } of LAYER_PATTERNS) if (pattern.test(filePath)) return layer;
62
+ const segments = filePath.split(/[\\/]/).filter(Boolean);
63
+ if (segments.length >= 2) {
64
+ const topDir = segments[0] === "src" || segments[0] === "app" || segments[0] === "lib" ? segments[1] ?? segments[0] ?? "core" : segments[0] ?? "core";
65
+ return topDir.toLowerCase();
66
+ }
67
+ return "core";
68
+ }
69
+
70
+ //#endregion
71
+ export { inferLayerFromPath };
72
+ //# sourceMappingURL=layerUtils-DDm-EsaD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layerUtils-DDm-EsaD.js","names":["LAYER_PATTERNS: Array<{ pattern: RegExp; layer: string }>","filePath: string"],"sources":["../src/shared/layerUtils.ts"],"sourcesContent":["const LAYER_PATTERNS: Array<{ pattern: RegExp; layer: string }> = [\r\n { pattern: /\\/(controller|controllers|api|routes|endpoints?|handlers?)\\//i, layer: 'controller' },\r\n { pattern: /\\/(service|services|business|domain|logic|usecase|usecases)\\//i, layer: 'service' },\r\n { pattern: /\\/(model|models|entity|entities|repository|repositories|dao|data|store|stores)\\//i, layer: 'data' },\r\n { pattern: /\\/(view|views|component|components|ui|page|pages|screen|screens|template|templates|widget|widgets)\\//i, layer: 'presentation' },\r\n { pattern: /\\/(util|utils|helper|helpers|common|shared|lib|libs)\\//i, layer: 'utility' },\r\n { pattern: /\\/(config|configs|conf|settings)\\//i, layer: 'config' },\r\n { pattern: /\\/(test|tests|spec|specs|__tests__)\\//i, layer: 'test' },\r\n { pattern: /\\/(middleware|middlewares|interceptor|interceptors|filter|filters)\\//i, layer: 'middleware' },\r\n { pattern: /\\/(adapter|adapters|gateway|gateways)\\//i, layer: 'adapter' },\r\n { pattern: /\\/(hook|hooks|plugin|plugins)\\//i, layer: 'extension' },\r\n { pattern: /\\/(type|types|interface|interfaces|typedef|typedefs)\\//i, layer: 'type' },\r\n { pattern: /\\/(style|styles|css|asset|assets|static|public)\\//i, layer: 'asset' },\r\n { pattern: /\\/(auth|security|permission|permissions)\\//i, layer: 'security' },\r\n { pattern: /\\/(db|database|migration|migrations|schema|schemas)\\//i, layer: 'database' },\r\n];\r\n\r\nexport function inferLayerFromPath(filePath: string): string {\r\n for (const { pattern, layer } of LAYER_PATTERNS) {\r\n if (pattern.test(filePath)) return layer;\r\n }\r\n const segments = filePath.split(/[\\\\/]/).filter(Boolean);\r\n if (segments.length >= 2) {\r\n const topDir = (segments[0] === 'src' || segments[0] === 'app' || segments[0] === 'lib')\r\n ? (segments[1] ?? segments[0] ?? 'core')\r\n : (segments[0] ?? 'core');\r\n return topDir.toLowerCase();\r\n }\r\n return 'core';\r\n}\r\n\r\nexport const LAYER_COLORS: Record<string, { dark: string; light: string }> = {\r\n controller: { dark: '#60a5fa', light: '#3b82f6' },\r\n service: { dark: '#10b981', light: '#059669' },\r\n data: { dark: '#f59e0b', light: '#d97706' },\r\n presentation: { dark: '#ec4899', light: '#db2777' },\r\n utility: { dark: '#818cf8', light: '#6366f1' },\r\n config: { dark: '#a78bfa', light: '#7c3aed' },\r\n test: { dark: '#34d399', light: '#14b8a6' },\r\n middleware: { dark: '#fbbf24', light: '#eab308' },\r\n adapter: { dark: '#22d3ee', light: '#06b6d4' },\r\n extension: { dark: '#f472b6', light: '#ec4899' },\r\n type: { dark: '#a78bfa', light: '#7c3aed' },\r\n asset: { dark: '#fb7185', light: '#f43f5e' },\r\n security: { dark: '#ef4444', light: '#dc2626' },\r\n database: { dark: '#84cc16', light: '#65a30d' },\r\n core: { dark: '#6366f1', light: '#4f46e5' },\r\n};\r\n\r\nexport function getLayerColor(layer: string, isDark: boolean): string {\r\n const colors = LAYER_COLORS[layer];\r\n if (colors) return isDark ? colors.dark : colors.light;\r\n const fallbackLayers = Object.values(LAYER_COLORS);\r\n const idx = layer.charCodeAt(0) % fallbackLayers.length;\r\n const fallback = fallbackLayers[idx] ?? { dark: '#6366f1', light: '#4f46e5' };\r\n return fallback[isDark ? 'dark' : 'light'];\r\n}"],"mappings":";AAAA,MAAMA,iBAA4D;CAChE;EAAE,SAAS;EAAiE,OAAO;CAAc;CACjG;EAAE,SAAS;EAAkE,OAAO;CAAW;CAC/F;EAAE,SAAS;EAAqF,OAAO;CAAQ;CAC/G;EAAE,SAAS;EAAyG,OAAO;CAAgB;CAC3I;EAAE,SAAS;EAA2D,OAAO;CAAW;CACxF;EAAE,SAAS;EAAuC,OAAO;CAAU;CACnE;EAAE,SAAS;EAA0C,OAAO;CAAQ;CACpE;EAAE,SAAS;EAAyE,OAAO;CAAc;CACzG;EAAE,SAAS;EAA4C,OAAO;CAAW;CACzE;EAAE,SAAS;EAAoC,OAAO;CAAa;CACnE;EAAE,SAAS;EAA2D,OAAO;CAAQ;CACrF;EAAE,SAAS;EAAsD,OAAO;CAAS;CACjF;EAAE,SAAS;EAA+C,OAAO;CAAY;CAC7E;EAAE,SAAS;EAA0D,OAAO;CAAY;AACzF;AAED,SAAgB,mBAAmBC,UAA0B;AAC3D,MAAK,MAAM,EAAE,SAAS,OAAO,IAAI,eAC/B,KAAI,QAAQ,KAAK,SAAS,CAAE,QAAO;CAErC,MAAM,WAAW,SAAS,MAAM,QAAQ,CAAC,OAAO,QAAQ;AACxD,KAAI,SAAS,UAAU,GAAG;EACxB,MAAM,SAAU,SAAS,OAAO,SAAS,SAAS,OAAO,SAAS,SAAS,OAAO,QAC7E,SAAS,MAAM,SAAS,MAAM,SAC9B,SAAS,MAAM;AACpB,SAAO,OAAO,aAAa;CAC5B;AACD,QAAO;AACR"}