autosnippet 3.2.7 → 3.2.8
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/bin/cli.js +7 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
- package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +3 -3
- package/lib/core/AstAnalyzer.js +26 -4
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/routes/remote.js +15 -15
- package/lib/injection/ServiceContainer.js +6 -11
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/chat/ChatAgent.js +1 -1
- package/lib/service/chat/ChatAgentPrompts.js +13 -1
- package/lib/service/chat/ExplorationTracker.js +52 -8
- package/lib/service/chat/HandoffProtocol.js +19 -1
- package/lib/service/chat/WorkingMemory.js +3 -1
- package/lib/service/chat/memory/ActiveContext.js +3 -1
- package/lib/service/chat/memory/SessionStore.js +4 -3
- package/lib/service/chat/tools/ast-graph.js +229 -32
- package/lib/service/chat/tools/index.js +6 -1
- package/lib/service/chat/tools/infrastructure.js +5 -0
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +9 -0
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ast-graph.js — AST 结构化分析 + Agent Memory 工具 (
|
|
2
|
+
* ast-graph.js — AST 结构化分析 + Agent Memory 工具 (11)
|
|
3
3
|
*
|
|
4
4
|
* 44. get_project_overview 项目 AST 概览
|
|
5
5
|
* 45. get_class_hierarchy 类继承层级
|
|
6
|
-
* 46. get_class_info
|
|
7
|
-
* 47. get_protocol_info
|
|
6
|
+
* 46. get_class_info 类/结构体详细信息 (跨语言)
|
|
7
|
+
* 47. get_protocol_info 协议/接口/trait 详细信息 (跨语言)
|
|
8
8
|
* 48. get_method_overrides 方法覆写查询
|
|
9
|
-
* 49. get_category_map Category 扩展映射
|
|
9
|
+
* 49. get_category_map Category/Extension 扩展映射
|
|
10
10
|
* 50. get_previous_analysis 前序维度分析结果
|
|
11
11
|
* 51. note_finding 记录关键发现
|
|
12
12
|
* 52. get_previous_evidence 检索前序维度证据
|
|
13
13
|
* 53. query_code_graph 查询代码实体图谱
|
|
14
|
+
* 54. query_call_graph 查询方法调用链 (Phase 5)
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
// ════════════════════════════════════════════════════════════
|
|
@@ -36,7 +37,8 @@ function _getProjectGraph(ctx) {
|
|
|
36
37
|
export const getProjectOverview = {
|
|
37
38
|
name: 'get_project_overview',
|
|
38
39
|
description:
|
|
39
|
-
'
|
|
40
|
+
'获取项目的整体结构概览:文件统计、语言分布、模块列表、入口点、类/协议/Category 数量。' +
|
|
41
|
+
'支持所有语言 (Swift/ObjC/Java/Kotlin/Python/TS/JS/Dart/Rust/Go 等)。' +
|
|
40
42
|
'适用场景:了解项目规模和架构布局,规划探索路径。',
|
|
41
43
|
parameters: {
|
|
42
44
|
type: 'object',
|
|
@@ -49,13 +51,30 @@ export const getProjectOverview = {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const o = graph.getOverview();
|
|
54
|
+
|
|
55
|
+
// §P2: 从文件扩展名统计语言分布
|
|
56
|
+
const langStats = {};
|
|
57
|
+
for (const filePath of graph.getAllFilePaths?.() || []) {
|
|
58
|
+
const ext = filePath.split('.').pop();
|
|
59
|
+
if (ext) langStats[ext] = (langStats[ext] || 0) + 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
const lines = [
|
|
53
63
|
`📊 项目 AST 概览 (构建耗时 ${o.buildTimeMs}ms)`,
|
|
54
64
|
``,
|
|
55
|
-
`文件: ${o.totalFiles} | 类: ${o.totalClasses} |
|
|
56
|
-
``,
|
|
57
|
-
`── 模块 ──`,
|
|
65
|
+
`文件: ${o.totalFiles} | 类: ${o.totalClasses} | 协议/接口: ${o.totalProtocols} | Category/Extension: ${o.totalCategories} | 方法: ${o.totalMethods}`,
|
|
58
66
|
];
|
|
67
|
+
|
|
68
|
+
// 语言分布
|
|
69
|
+
const langEntries = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
|
|
70
|
+
if (langEntries.length > 0) {
|
|
71
|
+
lines.push(``, `── 语言分布 ──`);
|
|
72
|
+
for (const [ext, count] of langEntries) {
|
|
73
|
+
lines.push(` .${ext}: ${count} 个文件`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
lines.push(``, `── 模块 ──`);
|
|
59
78
|
for (const mod of o.topLevelModules) {
|
|
60
79
|
const count = o.classesPerModule[mod] || 0;
|
|
61
80
|
lines.push(` ${mod}/ — ${count} 个类`);
|
|
@@ -76,7 +95,8 @@ export const getProjectOverview = {
|
|
|
76
95
|
export const getClassHierarchy = {
|
|
77
96
|
name: 'get_class_hierarchy',
|
|
78
97
|
description:
|
|
79
|
-
'
|
|
98
|
+
'查看指定类/结构体的继承链(向上到根类)和直接子类列表。' +
|
|
99
|
+
'支持所有语言的类继承体系 (Swift class/struct, Java/Kotlin class, Python class, TS class, Dart class, Rust struct, Go struct 等)。' +
|
|
80
100
|
'传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
|
|
81
101
|
parameters: {
|
|
82
102
|
type: 'object',
|
|
@@ -138,7 +158,9 @@ export const getClassHierarchy = {
|
|
|
138
158
|
// ────────────────────────────────────────────────────────────
|
|
139
159
|
export const getClassInfo = {
|
|
140
160
|
name: 'get_class_info',
|
|
141
|
-
description:
|
|
161
|
+
description:
|
|
162
|
+
'获取指定类/结构体的详细信息: 属性、方法签名、导入、继承关系、扩展。' +
|
|
163
|
+
'跨语言通用:ObjC @interface, Swift class/struct, Java/Kotlin class, Python class, TS/JS class, Dart class, Rust struct impl, Go struct 等。',
|
|
142
164
|
parameters: {
|
|
143
165
|
type: 'object',
|
|
144
166
|
properties: {
|
|
@@ -169,7 +191,7 @@ export const getClassInfo = {
|
|
|
169
191
|
];
|
|
170
192
|
|
|
171
193
|
if (info.protocols.length > 0) {
|
|
172
|
-
lines.push(
|
|
194
|
+
lines.push(`实现: ${info.protocols.join(', ')}`);
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
if (info.properties.length > 0) {
|
|
@@ -195,7 +217,7 @@ export const getClassInfo = {
|
|
|
195
217
|
}
|
|
196
218
|
|
|
197
219
|
if (cats.length > 0) {
|
|
198
|
-
lines.push(``, `──
|
|
220
|
+
lines.push(``, `── 扩展 (${cats.length}) ──`);
|
|
199
221
|
for (const cat of cats) {
|
|
200
222
|
const methodNames = cat.methods.map((m) => m.selector).join(', ');
|
|
201
223
|
lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
|
|
@@ -218,7 +240,9 @@ export const getClassInfo = {
|
|
|
218
240
|
// ────────────────────────────────────────────────────────────
|
|
219
241
|
export const getProtocolInfo = {
|
|
220
242
|
name: 'get_protocol_info',
|
|
221
|
-
description:
|
|
243
|
+
description:
|
|
244
|
+
'获取指定协议/接口/trait 的定义(必选/可选方法)及所有实现者列表。' +
|
|
245
|
+
'跨语言通用:ObjC @protocol, Swift protocol, Java/Kotlin interface, Python ABC, TS interface, Dart abstract class, Rust trait, Go interface 等。',
|
|
222
246
|
parameters: {
|
|
223
247
|
type: 'object',
|
|
224
248
|
properties: {
|
|
@@ -235,36 +259,36 @@ export const getProtocolInfo = {
|
|
|
235
259
|
const protocolName = params.protocolName || params.protocol_name;
|
|
236
260
|
const info = graph.getProtocolInfo(protocolName);
|
|
237
261
|
if (!info) {
|
|
238
|
-
return
|
|
262
|
+
return `未找到协议/接口 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议/接口。`;
|
|
239
263
|
}
|
|
240
264
|
|
|
241
|
-
const lines = [`📋
|
|
265
|
+
const lines = [`📋 ${info.name}`, `文件: ${info.filePath}:${info.line}`];
|
|
242
266
|
|
|
243
267
|
if (info.inherits.length > 0) {
|
|
244
|
-
lines.push(`继承:
|
|
268
|
+
lines.push(`继承: ${info.inherits.join(', ')}`);
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
if (info.requiredMethods.length > 0) {
|
|
248
|
-
lines.push(``, `──
|
|
272
|
+
lines.push(``, `── 必须实现 (${info.requiredMethods.length}) ──`);
|
|
249
273
|
for (const m of info.requiredMethods) {
|
|
250
274
|
lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
|
|
251
275
|
}
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
if (info.optionalMethods.length > 0) {
|
|
255
|
-
lines.push(``, `──
|
|
279
|
+
lines.push(``, `── 可选实现 (${info.optionalMethods.length}) ──`);
|
|
256
280
|
for (const m of info.optionalMethods) {
|
|
257
281
|
lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
|
|
258
282
|
}
|
|
259
283
|
}
|
|
260
284
|
|
|
261
285
|
if (info.conformers.length > 0) {
|
|
262
|
-
lines.push(``, `──
|
|
286
|
+
lines.push(``, `── 实现者 (${info.conformers.length}) ──`);
|
|
263
287
|
for (const c of info.conformers) {
|
|
264
288
|
lines.push(` ${c}`);
|
|
265
289
|
}
|
|
266
290
|
} else {
|
|
267
|
-
lines.push(``, `⚠️
|
|
291
|
+
lines.push(``, `⚠️ 暂未发现实现此协议/接口的类`);
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
return lines.join('\n');
|
|
@@ -276,7 +300,9 @@ export const getProtocolInfo = {
|
|
|
276
300
|
// ────────────────────────────────────────────────────────────
|
|
277
301
|
export const getMethodOverrides = {
|
|
278
302
|
name: 'get_method_overrides',
|
|
279
|
-
description:
|
|
303
|
+
description:
|
|
304
|
+
'查找覆写了指定方法的所有子类。适用于理解方法在继承树中的多态行为。' +
|
|
305
|
+
'跨语言通用:支持 ObjC/Swift/Java/Kotlin/Python/TS/Dart 等语言的方法覆写。',
|
|
280
306
|
parameters: {
|
|
281
307
|
type: 'object',
|
|
282
308
|
properties: {
|
|
@@ -314,7 +340,9 @@ export const getMethodOverrides = {
|
|
|
314
340
|
export const getCategoryMap = {
|
|
315
341
|
name: 'get_category_map',
|
|
316
342
|
description:
|
|
317
|
-
'
|
|
343
|
+
'获取指定类的 Category/Extension 扩展映射。' +
|
|
344
|
+
'ObjC Category、Swift Extension 等语言的类扩展机制。了解它有助于发现功能划分。' +
|
|
345
|
+
'不传 className 则返回整个项目中有扩展的类列表。',
|
|
318
346
|
parameters: {
|
|
319
347
|
type: 'object',
|
|
320
348
|
properties: {
|
|
@@ -455,7 +483,9 @@ export const noteFinding = {
|
|
|
455
483
|
const coordinator = ctx._memoryCoordinator;
|
|
456
484
|
if (coordinator) {
|
|
457
485
|
const finding = params.finding || '';
|
|
458
|
-
|
|
486
|
+
// P0 Fix: AI 可能传入 array/object,强制转为 string
|
|
487
|
+
const rawEvidence = params.evidence;
|
|
488
|
+
const evidence = typeof rawEvidence === 'string' ? rawEvidence : Array.isArray(rawEvidence) ? rawEvidence.join(', ') : rawEvidence ? String(rawEvidence) : '';
|
|
459
489
|
const importance = params.importance || 5;
|
|
460
490
|
const round = ctx._currentRound || 0;
|
|
461
491
|
const scopeId = ctx._dimensionScopeId || undefined;
|
|
@@ -547,8 +577,8 @@ export const queryCodeGraph = {
|
|
|
547
577
|
},
|
|
548
578
|
entity_type: {
|
|
549
579
|
type: 'string',
|
|
550
|
-
enum: ['class', 'protocol', 'category', 'module', 'pattern'],
|
|
551
|
-
description: '实体类型过滤 (
|
|
580
|
+
enum: ['class', 'protocol', 'category', 'module', 'pattern', 'method'],
|
|
581
|
+
description: '实体类型过滤 (可选,含 Phase 5 method 类型)',
|
|
552
582
|
},
|
|
553
583
|
max_depth: {
|
|
554
584
|
type: 'number',
|
|
@@ -559,14 +589,17 @@ export const queryCodeGraph = {
|
|
|
559
589
|
},
|
|
560
590
|
handler: async (params, ctx) => {
|
|
561
591
|
try {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (!
|
|
565
|
-
|
|
592
|
+
// 优先从 container 获取单例 CEG,避免每次创建新实例
|
|
593
|
+
let ceg = ctx?.container?.get?.('codeEntityGraph');
|
|
594
|
+
if (!ceg) {
|
|
595
|
+
const { CodeEntityGraph } = await import('../../knowledge/CodeEntityGraph.js');
|
|
596
|
+
const db = ctx?.container?.get?.('database');
|
|
597
|
+
if (!db) {
|
|
598
|
+
return '代码实体图谱不可用: 数据库未初始化';
|
|
599
|
+
}
|
|
600
|
+
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
601
|
+
ceg = new CodeEntityGraph(db, { projectRoot });
|
|
566
602
|
}
|
|
567
|
-
|
|
568
|
-
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
569
|
-
const ceg = new CodeEntityGraph(db, { projectRoot });
|
|
570
603
|
const maxDepth = params.max_depth || 3;
|
|
571
604
|
|
|
572
605
|
switch (params.action) {
|
|
@@ -680,3 +713,167 @@ export const queryCodeGraph = {
|
|
|
680
713
|
}
|
|
681
714
|
},
|
|
682
715
|
};
|
|
716
|
+
|
|
717
|
+
// ────────────────────────────────────────────────────────────
|
|
718
|
+
// 54. query_call_graph — 查询方法调用链 (Phase 5)
|
|
719
|
+
// ────────────────────────────────────────────────────────────
|
|
720
|
+
export const queryCallGraph = {
|
|
721
|
+
name: 'query_call_graph',
|
|
722
|
+
description:
|
|
723
|
+
'查询方法的调用链上下文 (Call Graph)。支持 10+ 种编程语言 (Swift/ObjC/Java/Kotlin/Python/TS/JS/Dart/Rust/Go)。\n' +
|
|
724
|
+
'• callers: 谁调用了这个方法?(向上追踪调用者链)\n' +
|
|
725
|
+
'• callees: 这个方法调用了谁?(向下追踪依赖链)\n' +
|
|
726
|
+
'• both: 同时获取调用者和被调用者\n' +
|
|
727
|
+
'• impact: 修改此方法的影响半径分析 (受影响文件数)\n' +
|
|
728
|
+
'• search: 按名称搜索方法实体\n' +
|
|
729
|
+
'⚠️ 需要先完成 bootstrap 才有数据。方法名格式: "ClassName.methodName" 或简写 "methodName"。',
|
|
730
|
+
parameters: {
|
|
731
|
+
type: 'object',
|
|
732
|
+
properties: {
|
|
733
|
+
methodName: {
|
|
734
|
+
type: 'string',
|
|
735
|
+
description:
|
|
736
|
+
'方法名 (e.g. "UserService.getUser", "viewDidLoad", "handleClick")。' +
|
|
737
|
+
'search 模式下为搜索关键词。',
|
|
738
|
+
},
|
|
739
|
+
direction: {
|
|
740
|
+
type: 'string',
|
|
741
|
+
enum: ['callers', 'callees', 'both', 'impact', 'search'],
|
|
742
|
+
description:
|
|
743
|
+
'callers=调用者 | callees=被调用者 | both=双向 | impact=影响分析 | search=搜索方法实体',
|
|
744
|
+
},
|
|
745
|
+
maxDepth: {
|
|
746
|
+
type: 'number',
|
|
747
|
+
description: '最大遍历深度 (1-5, 默认 2)',
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
required: ['methodName'],
|
|
751
|
+
},
|
|
752
|
+
handler: async (params, ctx) => {
|
|
753
|
+
try {
|
|
754
|
+
// 前置: 必填参数校验 (fail-fast)
|
|
755
|
+
const methodName = params.methodName || params.method_name;
|
|
756
|
+
if (!methodName) {
|
|
757
|
+
return '请提供 methodName 参数。';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 优先从 container 获取单例 CEG,避免每次创建新实例
|
|
761
|
+
let ceg = ctx?.container?.get?.('codeEntityGraph');
|
|
762
|
+
if (!ceg) {
|
|
763
|
+
// fallback: 手动构建
|
|
764
|
+
const { CodeEntityGraph } = await import('../../knowledge/CodeEntityGraph.js');
|
|
765
|
+
const db = ctx?.container?.get?.('database');
|
|
766
|
+
if (!db) {
|
|
767
|
+
return '调用图查询不可用: 数据库未初始化。';
|
|
768
|
+
}
|
|
769
|
+
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
770
|
+
ceg = new CodeEntityGraph(db, { projectRoot });
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const direction = params.direction || 'both';
|
|
774
|
+
const maxDepth = Math.min(Math.max(Number(params.maxDepth || params.max_depth) || 2, 1), 5);
|
|
775
|
+
|
|
776
|
+
// search 模式: 按名称搜索方法实体
|
|
777
|
+
if (direction === 'search') {
|
|
778
|
+
const results = ceg.searchEntities(methodName, { type: 'method', limit: 15 });
|
|
779
|
+
if (results.length === 0) {
|
|
780
|
+
// 降级: 不限类型搜索
|
|
781
|
+
const allResults = ceg.searchEntities(methodName, { limit: 15 });
|
|
782
|
+
if (allResults.length === 0) {
|
|
783
|
+
return `未找到匹配 "${methodName}" 的实体。请确认 bootstrap 已完成。`;
|
|
784
|
+
}
|
|
785
|
+
const lines = [`🔍 搜索 "${methodName}" (${allResults.length} 条, 无 method 类型匹配):`];
|
|
786
|
+
for (const e of allResults) {
|
|
787
|
+
lines.push(` • [${e.entityType}] ${e.name}${e.filePath ? ` (${e.filePath})` : ''}`);
|
|
788
|
+
}
|
|
789
|
+
lines.push(``, `💡 提示: 调用图查询需使用 method 实体的 entityId。`);
|
|
790
|
+
return lines.join('\n');
|
|
791
|
+
}
|
|
792
|
+
const lines = [`🔍 方法搜索 "${methodName}" (${results.length} 条):`];
|
|
793
|
+
for (const e of results) {
|
|
794
|
+
lines.push(` • ${e.name}${e.filePath ? ` (${e.filePath})` : ''}`);
|
|
795
|
+
}
|
|
796
|
+
lines.push(``, `💡 复制上面的完整方法名,用 callers/callees/both/impact 查询其调用链。`);
|
|
797
|
+
return lines.join('\n');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// impact 模式
|
|
801
|
+
if (direction === 'impact') {
|
|
802
|
+
const impact = ceg.getCallImpactRadius(methodName);
|
|
803
|
+
const lines = [
|
|
804
|
+
`⚡ 修改 "${methodName}" 的影响半径:`,
|
|
805
|
+
` 直接调用者: ${impact.directCallers}`,
|
|
806
|
+
` 传递性调用者 (depth≤3): ${impact.transitiveCallers}`,
|
|
807
|
+
];
|
|
808
|
+
if (impact.affectedFiles.length > 0) {
|
|
809
|
+
lines.push(` 受影响文件 (${impact.affectedFiles.length}):`);
|
|
810
|
+
for (const f of impact.affectedFiles.slice(0, 15)) {
|
|
811
|
+
lines.push(` 📄 ${f}`);
|
|
812
|
+
}
|
|
813
|
+
if (impact.affectedFiles.length > 15) {
|
|
814
|
+
lines.push(` ... 还有 ${impact.affectedFiles.length - 15} 个文件`);
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
lines.push(` 未发现受影响文件 (可能是叶子方法或 bootstrap 无调用图数据)。`);
|
|
818
|
+
}
|
|
819
|
+
return lines.join('\n');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// callers / callees / both 模式
|
|
823
|
+
const result = {};
|
|
824
|
+
if (direction === 'callers' || direction === 'both') {
|
|
825
|
+
result.callers = ceg.getCallers(methodName, maxDepth);
|
|
826
|
+
}
|
|
827
|
+
if (direction === 'callees' || direction === 'both') {
|
|
828
|
+
result.callees = ceg.getCallees(methodName, maxDepth);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const totalCallers = result.callers?.length || 0;
|
|
832
|
+
const totalCallees = result.callees?.length || 0;
|
|
833
|
+
|
|
834
|
+
if (totalCallers === 0 && totalCallees === 0) {
|
|
835
|
+
return (
|
|
836
|
+
`"${methodName}" 在调用图中没有找到调用关系。\n\n` +
|
|
837
|
+
`可能原因:\n` +
|
|
838
|
+
` 1. 方法名拼写不匹配 — 尝试 search 模式: query_call_graph({methodName:"关键词", direction:"search"})\n` +
|
|
839
|
+
` 2. 该方法是入口点/叶子节点,无上下游\n` +
|
|
840
|
+
` 3. bootstrap 尚未执行或不包含此文件`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const lines = [`📞 "${methodName}" 的调用链 (depth≤${maxDepth}):`];
|
|
845
|
+
|
|
846
|
+
if (result.callers?.length > 0) {
|
|
847
|
+
lines.push(``, `── 调用者 (谁调用了它, ${result.callers.length} 条) ──`);
|
|
848
|
+
for (const c of result.callers.slice(0, 20)) {
|
|
849
|
+
const indent = ' '.repeat(c.depth);
|
|
850
|
+
const typeTag = c.callType !== 'unknown' ? ` [${c.callType}]` : '';
|
|
851
|
+
lines.push(`${indent}⬆ ${c.caller}${typeTag}`);
|
|
852
|
+
}
|
|
853
|
+
if (result.callers.length > 20) {
|
|
854
|
+
lines.push(` ... 还有 ${result.callers.length - 20} 个调用者`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (result.callees?.length > 0) {
|
|
859
|
+
lines.push(``, `── 被调用者 (它调用了谁, ${result.callees.length} 条) ──`);
|
|
860
|
+
for (const c of result.callees.slice(0, 20)) {
|
|
861
|
+
const indent = ' '.repeat(c.depth);
|
|
862
|
+
const typeTag = c.callType !== 'unknown' ? ` [${c.callType}]` : '';
|
|
863
|
+
lines.push(`${indent}⬇ ${c.callee}${typeTag}`);
|
|
864
|
+
}
|
|
865
|
+
if (result.callees.length > 20) {
|
|
866
|
+
lines.push(` ... 还有 ${result.callees.length - 20} 个被调用者`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return lines.join('\n');
|
|
871
|
+
} catch (err) {
|
|
872
|
+
// 表不存在 → 未 bootstrap
|
|
873
|
+
if (err.message?.includes('no such table')) {
|
|
874
|
+
return '调用图数据不可用 — knowledge_edges 表不存在,请先运行 bootstrap。';
|
|
875
|
+
}
|
|
876
|
+
return `调用图查询失败: ${err.message}`;
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
refineBootstrapCandidates,
|
|
12
12
|
summarizeCode,
|
|
13
13
|
} from './ai-analysis.js';
|
|
14
|
-
// ── AST 结构化分析 + Agent Memory (
|
|
14
|
+
// ── AST 结构化分析 + Agent Memory (11) ──
|
|
15
15
|
import {
|
|
16
16
|
getCategoryMap,
|
|
17
17
|
getClassHierarchy,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getProjectOverview,
|
|
23
23
|
getProtocolInfo,
|
|
24
24
|
noteFinding,
|
|
25
|
+
queryCallGraph,
|
|
25
26
|
queryCodeGraph,
|
|
26
27
|
} from './ast-graph.js';
|
|
27
28
|
// ── 组合工具 + 元工具 (6) ──
|
|
@@ -158,6 +159,8 @@ export {
|
|
|
158
159
|
getPreviousEvidence,
|
|
159
160
|
// 代码实体图谱
|
|
160
161
|
queryCodeGraph,
|
|
162
|
+
// 调用图查询 (Phase 5)
|
|
163
|
+
queryCallGraph,
|
|
161
164
|
};
|
|
162
165
|
|
|
163
166
|
// ── ALL_TOOLS 数组(与原始 tools.js 顺序一致)──
|
|
@@ -234,6 +237,8 @@ export const ALL_TOOLS = [
|
|
|
234
237
|
getPreviousEvidence,
|
|
235
238
|
// 代码实体图谱 (1) — Phase E
|
|
236
239
|
queryCodeGraph,
|
|
240
|
+
// 调用图查询 (1) — Phase 5
|
|
241
|
+
queryCallGraph,
|
|
237
242
|
];
|
|
238
243
|
|
|
239
244
|
export default ALL_TOOLS;
|
|
@@ -206,6 +206,10 @@ export const bootstrapKnowledgeTool = {
|
|
|
206
206
|
type: 'boolean',
|
|
207
207
|
description: '是否加载 Skills 增强维度定义(推荐开启),默认 true',
|
|
208
208
|
},
|
|
209
|
+
skipAsyncFill: {
|
|
210
|
+
type: 'boolean',
|
|
211
|
+
description: '跳过异步 AI 填充(CLI 非 --wait 模式下使用,避免 DB 断连)',
|
|
212
|
+
},
|
|
209
213
|
},
|
|
210
214
|
},
|
|
211
215
|
handler: async (params, ctx) => {
|
|
@@ -218,6 +222,7 @@ export const bootstrapKnowledgeTool = {
|
|
|
218
222
|
skipGuard: params.skipGuard || false,
|
|
219
223
|
contentMaxLines: params.contentMaxLines || 120,
|
|
220
224
|
loadSkills: params.loadSkills ?? true,
|
|
225
|
+
skipAsyncFill: params.skipAsyncFill || false,
|
|
221
226
|
}
|
|
222
227
|
);
|
|
223
228
|
// bootstrapKnowledge 返回 envelope JSON string,解析提取 data
|
|
@@ -35,11 +35,12 @@ export class CursorDeliveryPipeline {
|
|
|
35
35
|
* @param {string} [options.projectName] - 项目名称
|
|
36
36
|
* @param {Object} [options.logger] - 日志器
|
|
37
37
|
*/
|
|
38
|
-
constructor({ knowledgeService, projectRoot, projectName, logger }) {
|
|
38
|
+
constructor({ knowledgeService, projectRoot, projectName, logger, database }) {
|
|
39
39
|
this.knowledgeService = knowledgeService;
|
|
40
40
|
this.projectRoot = projectRoot;
|
|
41
41
|
this.projectName = projectName || this._inferProjectName(projectRoot);
|
|
42
42
|
this.logger = logger || console;
|
|
43
|
+
this.database = database || null;
|
|
43
44
|
|
|
44
45
|
// 子模块
|
|
45
46
|
this.compressor = new KnowledgeCompressor();
|
|
@@ -87,6 +88,18 @@ export class CursorDeliveryPipeline {
|
|
|
87
88
|
const channelB = this._generateChannelB(patterns, facts);
|
|
88
89
|
stats.channelB = channelB;
|
|
89
90
|
|
|
91
|
+
// ── Channel B+: Call Graph Architecture Rules (Phase 5.2) ──
|
|
92
|
+
const archResult = this._generateCallGraphArchitectureRules();
|
|
93
|
+
if (archResult) {
|
|
94
|
+
stats.channelB.topicCount++;
|
|
95
|
+
stats.channelB.totalTokens += archResult.tokensUsed;
|
|
96
|
+
stats.channelB.topics['call-architecture'] = {
|
|
97
|
+
patternsCount: archResult.insightsCount,
|
|
98
|
+
factsCount: 0,
|
|
99
|
+
tokensUsed: archResult.tokensUsed,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
// ── Channel C: Skills Sync ──
|
|
91
104
|
const channelC = await this._generateChannelC();
|
|
92
105
|
stats.channelC = channelC;
|
|
@@ -326,6 +339,159 @@ export class CursorDeliveryPipeline {
|
|
|
326
339
|
return result;
|
|
327
340
|
}
|
|
328
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Channel B+ — Call Graph Architecture Rules (Phase 5.2)
|
|
344
|
+
* 从调用图拓扑分析架构分层,生成 architecture smart rule
|
|
345
|
+
* @private
|
|
346
|
+
* @returns {{ insightsCount: number, tokensUsed: number, filePath: string }|null}
|
|
347
|
+
*/
|
|
348
|
+
_generateCallGraphArchitectureRules() {
|
|
349
|
+
if (!this.database) return null;
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const db = typeof this.database.getDb === 'function' ? this.database.getDb() : this.database;
|
|
353
|
+
|
|
354
|
+
// 查询调用边中的跨目录调用模式
|
|
355
|
+
const callEdges = db.prepare(
|
|
356
|
+
`SELECT from_id, to_id, metadata_json FROM knowledge_edges
|
|
357
|
+
WHERE relation = 'calls' AND metadata_json LIKE '%phase5%'`
|
|
358
|
+
).all();
|
|
359
|
+
|
|
360
|
+
if (!callEdges || callEdges.length < 5) return null;
|
|
361
|
+
|
|
362
|
+
// 提取 caller/callee 对应的文件路径
|
|
363
|
+
const entityFiles = new Map();
|
|
364
|
+
const entities = db.prepare(
|
|
365
|
+
`SELECT entity_id, file_path FROM code_entities WHERE entity_type = 'method'`
|
|
366
|
+
).all();
|
|
367
|
+
for (const e of entities) {
|
|
368
|
+
entityFiles.set(e.entity_id, e.file_path);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 构建目录级调用矩阵
|
|
372
|
+
const dirCalls = new Map(); // 'src/controllers' → Map('src/services' → count)
|
|
373
|
+
for (const edge of callEdges) {
|
|
374
|
+
const callerFile = entityFiles.get(edge.from_id);
|
|
375
|
+
const calleeFile = entityFiles.get(edge.to_id);
|
|
376
|
+
if (!callerFile || !calleeFile || callerFile === calleeFile) continue;
|
|
377
|
+
|
|
378
|
+
const callerDir = this._extractLayerDir(callerFile);
|
|
379
|
+
const calleeDir = this._extractLayerDir(calleeFile);
|
|
380
|
+
if (!callerDir || !calleeDir || callerDir === calleeDir) continue;
|
|
381
|
+
|
|
382
|
+
if (!dirCalls.has(callerDir)) dirCalls.set(callerDir, new Map());
|
|
383
|
+
const targets = dirCalls.get(callerDir);
|
|
384
|
+
targets.set(calleeDir, (targets.get(calleeDir) || 0) + 1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (dirCalls.size === 0) return null;
|
|
388
|
+
|
|
389
|
+
// 检测架构层: 入度高(被调用多)的目录是底层服务, 出度高(调用多)的是上层
|
|
390
|
+
const dirInDegree = new Map();
|
|
391
|
+
const dirOutDegree = new Map();
|
|
392
|
+
for (const [from, targets] of dirCalls) {
|
|
393
|
+
for (const [to, count] of targets) {
|
|
394
|
+
dirOutDegree.set(from, (dirOutDegree.get(from) || 0) + count);
|
|
395
|
+
dirInDegree.set(to, (dirInDegree.get(to) || 0) + count);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 生成架构洞察
|
|
400
|
+
const lines = [];
|
|
401
|
+
lines.push('## Call Graph Architecture');
|
|
402
|
+
lines.push('');
|
|
403
|
+
|
|
404
|
+
// 分层推断: 按 (inDegree - outDegree) 排序, 值越大 = 越底层
|
|
405
|
+
const allDirs = new Set([...dirInDegree.keys(), ...dirOutDegree.keys()]);
|
|
406
|
+
const layers = [...allDirs].map((dir) => ({
|
|
407
|
+
dir,
|
|
408
|
+
inDegree: dirInDegree.get(dir) || 0,
|
|
409
|
+
outDegree: dirOutDegree.get(dir) || 0,
|
|
410
|
+
layerScore: (dirInDegree.get(dir) || 0) - (dirOutDegree.get(dir) || 0),
|
|
411
|
+
})).sort((a, b) => b.layerScore - a.layerScore);
|
|
412
|
+
|
|
413
|
+
// 分层标签
|
|
414
|
+
const total = layers.length;
|
|
415
|
+
let insightsCount = 0;
|
|
416
|
+
|
|
417
|
+
if (total >= 2) {
|
|
418
|
+
lines.push('### Architecture Layers (inferred from call graph)');
|
|
419
|
+
lines.push('');
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < layers.length && i < 10; i++) {
|
|
422
|
+
const l = layers[i];
|
|
423
|
+
let layerLabel;
|
|
424
|
+
if (i < total * 0.33) layerLabel = '🔽 low-level (service/repository)';
|
|
425
|
+
else if (i < total * 0.66) layerLabel = '↔️ mid-level (business logic)';
|
|
426
|
+
else layerLabel = '🔼 high-level (controller/UI)';
|
|
427
|
+
|
|
428
|
+
lines.push(`- \`${l.dir}/\` — ${layerLabel} (in:${l.inDegree} out:${l.outDegree})`);
|
|
429
|
+
insightsCount++;
|
|
430
|
+
}
|
|
431
|
+
lines.push('');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 核心调用链
|
|
435
|
+
const hotPaths = [...dirCalls.entries()]
|
|
436
|
+
.flatMap(([from, targets]) =>
|
|
437
|
+
[...targets.entries()].map(([to, count]) => ({ from, to, count }))
|
|
438
|
+
)
|
|
439
|
+
.sort((a, b) => b.count - a.count)
|
|
440
|
+
.slice(0, 8);
|
|
441
|
+
|
|
442
|
+
if (hotPaths.length > 0) {
|
|
443
|
+
lines.push('### Key Call Paths');
|
|
444
|
+
lines.push('');
|
|
445
|
+
for (const p of hotPaths) {
|
|
446
|
+
lines.push(`- \`${p.from}/\` → \`${p.to}/\` (${p.count} calls)`);
|
|
447
|
+
insightsCount++;
|
|
448
|
+
}
|
|
449
|
+
lines.push('');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (insightsCount === 0) return null;
|
|
453
|
+
|
|
454
|
+
// 构建 description (用于 smart rule 关联性判断)
|
|
455
|
+
const dirList = layers.map((l) => l.dir).join(', ');
|
|
456
|
+
const description = `Architecture layer analysis for ${this.projectName}. ` +
|
|
457
|
+
`Relevant when editing files in: ${dirList}. ` +
|
|
458
|
+
`Call graph shows ${callEdges.length} cross-file call relationships.`;
|
|
459
|
+
|
|
460
|
+
const body = lines.join('\n');
|
|
461
|
+
const writeResult = this.rulesGenerator.writeSmartRules('call-architecture', body, description);
|
|
462
|
+
|
|
463
|
+
this.logger.info?.(
|
|
464
|
+
`[CursorDelivery] Channel B+: call-architecture — ${insightsCount} insights → ${writeResult.filePath}`
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
insightsCount,
|
|
469
|
+
tokensUsed: writeResult.tokensUsed,
|
|
470
|
+
filePath: writeResult.filePath,
|
|
471
|
+
};
|
|
472
|
+
} catch (err) {
|
|
473
|
+
this.logger.warn?.(`[CursorDelivery] Call graph architecture rules failed: ${err.message}`);
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 从文件路径中提取层级目录 (第一或第二级有意义的目录)
|
|
480
|
+
* @private
|
|
481
|
+
*/
|
|
482
|
+
_extractLayerDir(filePath) {
|
|
483
|
+
if (!filePath) return null;
|
|
484
|
+
const parts = filePath.split('/').filter(Boolean);
|
|
485
|
+
// 跳过 src/ lib/ app/ 等通用前缀
|
|
486
|
+
const skipPrefixes = new Set(['src', 'lib', 'app', 'pkg', 'internal', 'cmd']);
|
|
487
|
+
let startIdx = 0;
|
|
488
|
+
if (parts.length > 1 && skipPrefixes.has(parts[0])) {
|
|
489
|
+
startIdx = 1;
|
|
490
|
+
}
|
|
491
|
+
// 取第一个有意义的目录
|
|
492
|
+
return parts[startIdx] || parts[0] || null;
|
|
493
|
+
}
|
|
494
|
+
|
|
329
495
|
/**
|
|
330
496
|
* Channel C 生成
|
|
331
497
|
* @private
|