autosnippet 3.2.6 → 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.
Files changed (65) hide show
  1. package/README.md +16 -1
  2. package/bin/cli.js +7 -0
  3. package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
  4. package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
  5. package/dashboard/dist/index.html +2 -2
  6. package/lib/cli/AiScanService.js +3 -3
  7. package/lib/core/AstAnalyzer.js +26 -4
  8. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  9. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  10. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  11. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  12. package/lib/core/analysis/ImportPathResolver.js +189 -0
  13. package/lib/core/analysis/ImportRecord.js +105 -0
  14. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  15. package/lib/core/ast/ProjectGraph.js +8 -0
  16. package/lib/core/ast/lang-dart.js +352 -5
  17. package/lib/core/ast/lang-go.js +212 -10
  18. package/lib/core/ast/lang-java.js +205 -1
  19. package/lib/core/ast/lang-kotlin.js +330 -1
  20. package/lib/core/ast/lang-python.js +31 -2
  21. package/lib/core/ast/lang-rust.js +284 -3
  22. package/lib/core/ast/lang-swift.js +180 -1
  23. package/lib/core/ast/lang-typescript.js +290 -1
  24. package/lib/external/mcp/McpServer.js +1 -0
  25. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
  26. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
  27. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  28. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
  29. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
  30. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  31. package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
  32. package/lib/external/mcp/handlers/consolidated.js +9 -0
  33. package/lib/external/mcp/handlers/guard.js +3 -3
  34. package/lib/external/mcp/handlers/structure.js +62 -0
  35. package/lib/external/mcp/handlers/task.js +182 -10
  36. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  37. package/lib/external/mcp/tools.js +36 -1
  38. package/lib/http/HttpServer.js +4 -0
  39. package/lib/http/routes/remote.js +1138 -0
  40. package/lib/http/routes/task.js +1 -0
  41. package/lib/infrastructure/database/migrations/003_add_remote_commands.js +27 -0
  42. package/lib/injection/ServiceContainer.js +6 -11
  43. package/lib/platform/ios/index.js +2 -2
  44. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  45. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  46. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  47. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  48. package/lib/service/chat/ChatAgent.js +1 -1
  49. package/lib/service/chat/ChatAgentPrompts.js +13 -1
  50. package/lib/service/chat/ExplorationTracker.js +52 -8
  51. package/lib/service/chat/HandoffProtocol.js +19 -1
  52. package/lib/service/chat/WorkingMemory.js +3 -1
  53. package/lib/service/chat/memory/ActiveContext.js +3 -1
  54. package/lib/service/chat/memory/SessionStore.js +4 -3
  55. package/lib/service/chat/tools/ast-graph.js +229 -32
  56. package/lib/service/chat/tools/index.js +6 -1
  57. package/lib/service/chat/tools/infrastructure.js +5 -0
  58. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  59. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  60. package/lib/service/knowledge/KnowledgeService.js +5 -1
  61. package/lib/service/module/ModuleService.js +9 -0
  62. package/lib/service/wiki/WikiGenerator.js +1 -1
  63. package/lib/shared/PathGuard.js +1 -1
  64. package/package.json +12 -1
  65. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
@@ -75,6 +75,9 @@ export async function bootstrapExternal(ctx) {
75
75
  contentMaxLines: 120,
76
76
  sourceTag: 'bootstrap-external',
77
77
  summaryPrefix: 'Bootstrap-external scan',
78
+ clearOldData: true,
79
+ generateReport: true,
80
+ incremental: true,
78
81
  });
79
82
 
80
83
  // 空项目 fast-path
@@ -88,9 +91,9 @@ export async function bootstrapExternal(ctx) {
88
91
 
89
92
  const {
90
93
  allFiles, primaryLang, depGraphData, langStats,
91
- astProjectSummary, codeEntityResult, guardAudit,
94
+ astProjectSummary, codeEntityResult, callGraphResult, guardAudit,
92
95
  activeDimensions: dimensions, targetsSummary,
93
- langProfile,
96
+ langProfile, incrementalPlan,
94
97
  } = phaseResults;
95
98
 
96
99
  // ═══════════════════════════════════════════════════════════
@@ -115,6 +118,7 @@ export async function bootstrapExternal(ctx) {
115
118
  allFiles,
116
119
  astProjectSummary,
117
120
  codeEntityResult,
121
+ callGraphResult,
118
122
  depGraphData,
119
123
  guardAudit,
120
124
  langStats,
@@ -138,12 +142,15 @@ export async function bootstrapExternal(ctx) {
138
142
  projectMeta,
139
143
  astData: astProjectSummary,
140
144
  codeEntityResult,
145
+ callGraphResult,
141
146
  depGraphData,
142
147
  guardAudit,
143
148
  targets: targetsSummary,
144
149
  activeDimensions: dimensions,
145
150
  session,
146
151
  languageExtension: buildLanguageExtension(primaryLang), // §7.1
152
+ incrementalPlan,
153
+ languageStats: langStats,
147
154
  });
148
155
 
149
156
  // 附加 warnings
@@ -87,6 +87,7 @@ export async function bootstrapKnowledge(ctx, args) {
87
87
  const maxFiles = args.maxFiles || 500;
88
88
  const skipGuard = args.skipGuard || false;
89
89
  const contentMaxLines = args.contentMaxLines || 120;
90
+ const skipAsyncFill = args.skipAsyncFill || false;
90
91
 
91
92
  // ═══════════════════════════════════════════════════════════
92
93
  // Phase 1-4: 共享管线(文件收集→AST→依赖→Guard→维度解析)
@@ -149,7 +150,12 @@ export async function bootstrapKnowledge(ctx, args) {
149
150
  categories: astProjectSummary?.categories?.length || 0,
150
151
  patterns: Object.keys(astProjectSummary?.patternStats || {}),
151
152
  },
152
- codeEntityGraph: phaseReport?.phases?.entityGraph || { entities: 0, edges: 0, durationMs: 0 },
153
+ codeEntityGraph: phaseReport?.phases?.entityGraph || { entityCount: 0, edgeCount: 0, ms: 0 },
154
+ callGraph: phaseReport?.phases?.callGraph ? {
155
+ entities: phaseReport.phases.callGraph.result?.entitiesUpserted || 0,
156
+ edges: phaseReport.phases.callGraph.result?.edgesCreated || 0,
157
+ ms: phaseReport.phases.callGraph.ms || 0,
158
+ } : { entities: 0, edges: 0, ms: 0 },
153
159
  dependencyGraph: { edgesWritten: depEdgesWritten || 0 },
154
160
  enhancementPacks: {
155
161
  matched: enhancementPackInfo,
@@ -371,12 +377,17 @@ export async function bootstrapKnowledge(ctx, args) {
371
377
  };
372
378
 
373
379
  // 使用 setImmediate 避免阻塞 HTTP 响应
374
- setImmediate(() => {
375
- ctx.logger.info(`[Bootstrap] Dispatching v3 AI-First pipeline`);
376
- fillDimensionsV3(fillContext).catch((e) => {
377
- ctx.logger.error(`[Bootstrap] Async fill (v3) failed: ${e.message}`);
380
+ // skipAsyncFill: CLI 非 --wait 模式跳过异步填充,避免进程退出后 DB 断连
381
+ if (!skipAsyncFill) {
382
+ setImmediate(() => {
383
+ ctx.logger.info(`[Bootstrap] Dispatching v3 AI-First pipeline`);
384
+ fillDimensionsV3(fillContext).catch((e) => {
385
+ ctx.logger.error(`[Bootstrap] Async fill (v3) failed: ${e.message}`);
386
+ });
378
387
  });
379
- });
388
+ } else {
389
+ ctx.logger.info(`[Bootstrap] Async fill skipped (skipAsyncFill=true)`);
390
+ }
380
391
 
381
392
  // ── SkillHooks: onBootstrapStarted (fire-and-forget) ──
382
393
  try {
@@ -99,6 +99,15 @@ export async function consolidatedStructure(ctx, args) {
99
99
  }
100
100
  }
101
101
 
102
+ // ─── autosnippet_call_context (Phase 5) ─────────────────────
103
+
104
+ /**
105
+ * 调用链上下文查询:直接转发到 structure.callContext
106
+ */
107
+ export async function consolidatedCallContext(ctx, args) {
108
+ return structureHandlers.callContext(ctx, args);
109
+ }
110
+
102
111
  // ─── autosnippet_graph (整合 4 → 1) ─────────────────────────
103
112
 
104
113
  /**
@@ -416,14 +416,14 @@ export async function scanProject(ctx, args) {
416
416
 
417
417
  const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
418
418
 
419
- // 优先使用 ModuleService(多语言统一入口),回退到 SpmService
419
+ // 优先使用 ModuleService(多语言统一入口),回退到 SpmHelper
420
420
  let service;
421
421
  try {
422
422
  const { ModuleService } = await import('../../../service/module/ModuleService.js');
423
423
  service = new ModuleService(projectRoot);
424
424
  } catch {
425
- const { SpmService } = await import('../../../platform/ios/spm/SpmService.js');
426
- service = new SpmService(projectRoot);
425
+ const { SpmHelper } = await import('../../../platform/ios/spm/SpmHelper.js');
426
+ service = new SpmHelper(projectRoot);
427
427
  }
428
428
  await service.load();
429
429
  const allTargets = await service.listTargets();
@@ -527,6 +527,68 @@ async function _fallbackPathFromRecipe(ctx, fromId, toId) {
527
527
  }
528
528
  }
529
529
 
530
+ // ─── call_context — 调用链上下文 (Phase 5) ──────────────────
531
+
532
+ /**
533
+ * autosnippet_call_context handler
534
+ * 查询方法的调用者、被调用者、影响半径
535
+ */
536
+ export async function callContext(ctx, args) {
537
+ if (!args.methodName) {
538
+ throw new Error('Missing required parameter: methodName');
539
+ }
540
+
541
+ const ceg = ctx.container.get('codeEntityGraph');
542
+ if (!ceg) {
543
+ return envelope({
544
+ success: false,
545
+ message: 'CodeEntityGraph not available — 请先运行 bootstrap',
546
+ meta: { tool: 'autosnippet_call_context' },
547
+ });
548
+ }
549
+
550
+ const direction = args.direction || 'both';
551
+ const maxDepth = Math.min(Math.max(args.maxDepth ?? 2, 1), 5);
552
+ const result = {};
553
+
554
+ try {
555
+ if (direction === 'callers' || direction === 'both') {
556
+ result.callers = ceg.getCallers(args.methodName, maxDepth);
557
+ }
558
+ if (direction === 'callees' || direction === 'both') {
559
+ result.callees = ceg.getCallees(args.methodName, maxDepth);
560
+ }
561
+ if (direction === 'impact') {
562
+ result.impact = ceg.getCallImpactRadius(args.methodName);
563
+ }
564
+ } catch (err) {
565
+ if (err.message?.includes('no such table')) {
566
+ return envelope({
567
+ success: true,
568
+ data: {
569
+ methodName: args.methodName,
570
+ callers: [],
571
+ callees: [],
572
+ note: 'knowledge_edges 表不存在,请运行 bootstrap 后再查询',
573
+ },
574
+ meta: { tool: 'autosnippet_call_context' },
575
+ });
576
+ }
577
+ throw err;
578
+ }
579
+
580
+ return envelope({
581
+ success: true,
582
+ data: {
583
+ methodName: args.methodName,
584
+ direction,
585
+ maxDepth,
586
+ ...result,
587
+ },
588
+ meta: { tool: 'autosnippet_call_context' },
589
+ });
590
+ }
591
+
530
592
  // ─── graph_stats — 图谱统计 ────────────────────────────────
531
593
 
532
594
  export async function graphStats(ctx) {
@@ -19,27 +19,35 @@ import { envelope } from '../envelope.js';
19
19
  export async function taskHandler(ctx, args) {
20
20
  const taskService = ctx.container.get('taskGraphService');
21
21
 
22
+ let result;
22
23
  switch (args.operation) {
23
24
  // ── Session ──
24
25
  case 'prime':
25
26
  return _prime(taskService, args);
26
27
  // ── Task CRUD ──
27
28
  case 'create':
28
- return _create(taskService, args);
29
+ result = await _create(taskService, args);
30
+ break;
29
31
  case 'ready':
30
32
  return _ready(taskService, args);
31
33
  case 'claim':
32
- return _claim(taskService, args);
34
+ result = await _claim(taskService, args);
35
+ break;
33
36
  case 'close':
34
- return _close(ctx, taskService, args);
37
+ result = await _close(ctx, taskService, args);
38
+ break;
35
39
  case 'fail':
36
- return _fail(taskService, args);
40
+ result = await _fail(taskService, args);
41
+ break;
37
42
  case 'defer':
38
- return _defer(taskService, args);
43
+ result = await _defer(taskService, args);
44
+ break;
39
45
  case 'progress':
40
- return _progress(taskService, args);
46
+ result = await _progress(taskService, args);
47
+ break;
41
48
  case 'decompose':
42
- return _decompose(taskService, args);
49
+ result = await _decompose(taskService, args);
50
+ break;
43
51
  case 'show':
44
52
  return _show(taskService, args);
45
53
  case 'list':
@@ -54,11 +62,14 @@ export async function taskHandler(ctx, args) {
54
62
  return _stats(taskService);
55
63
  // ── Decisions ──
56
64
  case 'record_decision':
57
- return _recordDecision(taskService, args);
65
+ result = await _recordDecision(taskService, args);
66
+ break;
58
67
  case 'revise_decision':
59
- return _reviseDecision(taskService, args);
68
+ result = await _reviseDecision(taskService, args);
69
+ break;
60
70
  case 'unpin_decision':
61
- return _unpinDecision(taskService, args);
71
+ result = await _unpinDecision(taskService, args);
72
+ break;
62
73
  case 'list_decisions':
63
74
  return _listDecisions(taskService);
64
75
  default:
@@ -68,6 +79,13 @@ export async function taskHandler(ctx, args) {
68
79
  meta: { tool: 'autosnippet_task' },
69
80
  });
70
81
  }
82
+
83
+ // ── 飞书任务进度通知(异步非阻塞)──
84
+ _notifyTaskProgress(args.operation, args, result).catch((err) => {
85
+ process.stderr.write(`[MCP/Task] Notify error: ${err?.message}\n`);
86
+ });
87
+
88
+ return result;
71
89
  }
72
90
 
73
91
  // ── create ──
@@ -455,3 +473,157 @@ async function _listDecisions(svc) {
455
473
  meta: { tool: 'autosnippet_task' },
456
474
  });
457
475
  }
476
+
477
+ // ═══ 飞书任务进度通知(通过 API Server 中转)═══════════════
478
+ //
479
+ // MCP Server 与 API Server 是独立进程。
480
+ // 飞书 WSClient 连接在 API Server 中,因此通知需 HTTP 中转。
481
+ // ═══════════════════════════════════════════════════════════
482
+
483
+ const PRIORITY_LABELS = ['P0 紧急', 'P1 高', 'P2 中', 'P3 低', 'P4 微'];
484
+
485
+ /**
486
+ * 通过 API Server 的 /api/v1/remote/notify 发送飞书通知
487
+ * @param {string} text
488
+ * @returns {Promise<boolean>}
489
+ */
490
+ async function _sendLarkViaApi(text) {
491
+ try {
492
+ const port = process.env.PORT || 3000;
493
+ const resp = await fetch(`http://localhost:${port}/api/v1/remote/notify`, {
494
+ method: 'POST',
495
+ headers: { 'Content-Type': 'application/json' },
496
+ body: JSON.stringify({ text }),
497
+ signal: AbortSignal.timeout(5000),
498
+ });
499
+ if (!resp.ok) {
500
+ process.stderr.write(`[MCP/Task] Lark notify HTTP ${resp.status}\n`);
501
+ return false;
502
+ }
503
+ const body = await resp.json();
504
+ return body.success === true;
505
+ } catch (err) {
506
+ process.stderr.write(`[MCP/Task] Lark notify failed: ${err?.message}\n`);
507
+ return false;
508
+ }
509
+ }
510
+
511
+ /**
512
+ * 通过 API Server 截取 IDE 窗口截图并发送到飞书
513
+ * @param {string} [caption] — 可选文字说明
514
+ * @returns {Promise<boolean>}
515
+ */
516
+ async function _sendScreenshotViaApi(caption = '') {
517
+ try {
518
+ const port = process.env.PORT || 3000;
519
+ const resp = await fetch(`http://localhost:${port}/api/v1/remote/screenshot`, {
520
+ method: 'POST',
521
+ headers: { 'Content-Type': 'application/json' },
522
+ body: JSON.stringify({ caption }),
523
+ signal: AbortSignal.timeout(15000),
524
+ });
525
+ if (!resp.ok) {
526
+ process.stderr.write(`[MCP/Task] Screenshot HTTP ${resp.status}\n`);
527
+ return false;
528
+ }
529
+ const body = await resp.json();
530
+ return body.success === true;
531
+ } catch (err) {
532
+ process.stderr.write(`[MCP/Task] Screenshot failed: ${err?.message}\n`);
533
+ return false;
534
+ }
535
+ }
536
+
537
+ /**
538
+ * 根据任务操作向飞书发送进度通知(异步非阻塞)
539
+ * result 是 envelope() 返回的 { success, data, message, meta }
540
+ */
541
+ async function _notifyTaskProgress(operation, args, result) {
542
+ if (!result || result.success === false) return;
543
+
544
+ const data = result.data;
545
+ let text = '';
546
+
547
+ switch (operation) {
548
+ case 'create': {
549
+ const title = data?.title || args.title || '';
550
+ const id = data?.id || '';
551
+ const type = data?.taskType || args.taskType || 'task';
552
+ const pri = PRIORITY_LABELS[data?.priority ?? args.priority ?? 2] || 'P2';
553
+ const dup = result.message?.includes('Duplicate') || result.message?.startsWith('⚠') ? ' (重复)' : '';
554
+ text = `📋 新任务${dup}: ${id}\n${title}\n类型: ${type} | ${pri}`;
555
+ break;
556
+ }
557
+ case 'claim': {
558
+ const id = data?.id || args.id;
559
+ const title = data?.title || '';
560
+ text = `🔨 开始执行: ${id}\n${title}`;
561
+ break;
562
+ }
563
+ case 'close': {
564
+ const closed = data?.closed || data;
565
+ const title = closed?.title || '';
566
+ const id = closed?.id || args.id;
567
+ const reason = closed?.closeReason || args.reason || '';
568
+ const readyCount = data?.newlyReady?.length || 0;
569
+ const readyInfo = readyCount > 0 ? `\n→ ${readyCount} 个任务新就绪` : '';
570
+ text = `✅ 完成: ${id}\n${title}\n原因: ${reason}${readyInfo}`;
571
+ break;
572
+ }
573
+ case 'fail': {
574
+ const title = data?.title || '';
575
+ const id = data?.id || args.id;
576
+ const reason = data?.lastFailReason || args.reason || '未知';
577
+ const count = data?.failCount || 0;
578
+ text = `❌ 失败: ${id}\n${title}\n原因: ${reason}${count > 1 ? ` (第${count}次)` : ''}`;
579
+ break;
580
+ }
581
+ case 'defer': {
582
+ const id = data?.id || args.id;
583
+ const title = data?.title || '';
584
+ text = `⏸️ 暂缓: ${id} — ${title}`;
585
+ break;
586
+ }
587
+ case 'progress': {
588
+ const id = data?.id || args.id;
589
+ const note = args.reason || args.description || '';
590
+ text = note
591
+ ? `📝 进度: ${id}\n${note.slice(0, 200)}`
592
+ : `📝 进度: ${id}`;
593
+ break;
594
+ }
595
+ case 'decompose': {
596
+ const epicId = args.id;
597
+ const count = Array.isArray(data) ? data.length : 0;
598
+ const subTitles = Array.isArray(data)
599
+ ? data.slice(0, 5).map((t, i) => ` ${i + 1}. ${t.title || t.id}`).join('\n')
600
+ : '';
601
+ text = `📂 拆解: ${epicId} → ${count} 个子任务${subTitles ? '\n' + subTitles : ''}`;
602
+ break;
603
+ }
604
+ case 'record_decision': {
605
+ const title = data?.title || args.title || '';
606
+ text = `📌 决策: ${title}`;
607
+ break;
608
+ }
609
+ case 'revise_decision': {
610
+ const oldId = data?.superseded || args.id;
611
+ const newTitle = data?.newDecision?.title || args.title;
612
+ text = `🔄 决策更新: ${oldId} → ${newTitle}`;
613
+ break;
614
+ }
615
+ case 'unpin_decision': {
616
+ const id = data?.id || args.id;
617
+ text = `🔓 决策取消: ${id}`;
618
+ break;
619
+ }
620
+ default:
621
+ return;
622
+ }
623
+
624
+ if (text) {
625
+ await _sendLarkViaApi(text);
626
+ // 发送文字通知后,附带 IDE 窗口截图
627
+ await _sendScreenshotViaApi();
628
+ }
629
+ }
@@ -60,10 +60,73 @@ export async function wikiPlan(ctx, args) {
60
60
  const session = getActiveSession(container, args.sessionId);
61
61
  const cachedData = session?.phaseCache;
62
62
 
63
- if (cachedData?.projectInfo && cachedData?.astInfo) {
64
- ({ projectInfo, astInfo, moduleInfo, knowledgeInfo } = cachedData);
63
+ if (cachedData?.astProjectSummary) {
64
+ // Bootstrap phase cache WikiGenerator-compatible format 转换
65
+ const allFiles = cachedData.allFiles || [];
66
+ const ast = cachedData.astProjectSummary;
67
+
68
+ // projectInfo: 从 bootstrap 文件列表和语言统计构建
69
+ const filesByModule = {};
70
+ for (const f of allFiles) {
71
+ const mod = f.targetName || '_default';
72
+ if (!filesByModule[mod]) filesByModule[mod] = [];
73
+ filesByModule[mod].push(f.relativePath);
74
+ }
75
+ projectInfo = {
76
+ name: path.basename(projectRoot),
77
+ root: projectRoot,
78
+ sourceFiles: allFiles.map(f => f.relativePath),
79
+ languages: cachedData.langStats || {},
80
+ primaryLanguage: cachedData.primaryLang || 'unknown',
81
+ sourceFilesByModule: filesByModule,
82
+ buildSystems: [],
83
+ };
84
+
85
+ // astInfo: 从 AstAnalyzer 结果构建
86
+ const classesByModule = {};
87
+ const protocolsByModule = {};
88
+ for (const cls of ast.classes || []) {
89
+ const mod = cls.targetName || '_default';
90
+ if (!classesByModule[mod]) classesByModule[mod] = [];
91
+ classesByModule[mod].push(cls.name);
92
+ }
93
+ for (const p of ast.protocols || []) {
94
+ const mod = p.targetName || '_default';
95
+ if (!protocolsByModule[mod]) protocolsByModule[mod] = [];
96
+ protocolsByModule[mod].push(p.name);
97
+ }
98
+ astInfo = {
99
+ classes: (ast.classes || []).map(c => c.name),
100
+ protocols: (ast.protocols || []).map(p => p.name),
101
+ overview: ast.projectMetrics || null,
102
+ classNamesByModule: classesByModule,
103
+ protocolNamesByModule: protocolsByModule,
104
+ };
105
+
106
+ // moduleInfo: 从依赖图和 targets 构建
107
+ moduleInfo = {
108
+ targets: (cachedData.targetsSummary || []).map(t => ({
109
+ name: t.name, type: t.type, fileCount: t.fileCount,
110
+ })),
111
+ depGraph: cachedData.depGraphData || null,
112
+ };
113
+
114
+ // knowledgeInfo: 始终从 DB 获取最新(bootstrap 期间可能已写入知识)
115
+ try {
116
+ const ks = tryGet(container, 'knowledgeService');
117
+ if (ks) {
118
+ const items = await ks.list({ limit: 200 });
119
+ const stats = typeof ks.getStats === 'function' ? await ks.getStats() : null;
120
+ knowledgeInfo = { recipes: items?.items || items || [], stats };
121
+ } else {
122
+ knowledgeInfo = { recipes: [], stats: null };
123
+ }
124
+ } catch {
125
+ knowledgeInfo = { recipes: [], stats: null };
126
+ }
127
+
65
128
  cacheHit = true;
66
- logger.info('[wiki-plan] Reusing bootstrap phase cache');
129
+ logger.info('[wiki-plan] Reusing bootstrap phase cache (converted to WikiGenerator format)');
67
130
  } else {
68
131
  // 无缓存(独立调用 wiki_plan 或进程已重启)→ 重新扫描
69
132
  logger.info('[wiki-plan] No bootstrap cache, running fresh scan...');
@@ -210,7 +210,42 @@ export const TOOLS = [
210
210
  },
211
211
  },
212
212
 
213
- // 6. Guard 检查(统一入口)
213
+ // 6. 调用链上下文 (Phase 5)
214
+ {
215
+ name: 'autosnippet_call_context',
216
+ tier: 'agent',
217
+ description:
218
+ '查询方法的调用链上下文。\n' +
219
+ '• callers: 谁调用了这个方法?(调用者链)\n' +
220
+ '• callees: 这个方法调用了谁?(依赖链)\n' +
221
+ '• impact: 修改此方法的影响半径分析\n' +
222
+ '• both: 同时获取调用者和被调用者',
223
+ inputSchema: {
224
+ type: 'object',
225
+ properties: {
226
+ methodName: {
227
+ type: 'string',
228
+ description: '方法名 (e.g. "UserService.getUser" 或 "getUser")',
229
+ },
230
+ direction: {
231
+ type: 'string',
232
+ enum: ['callers', 'callees', 'both', 'impact'],
233
+ default: 'both',
234
+ description: 'callers=调用者 | callees=被调用者 | both=双向 | impact=影响分析',
235
+ },
236
+ maxDepth: {
237
+ type: 'number',
238
+ default: 2,
239
+ minimum: 1,
240
+ maximum: 5,
241
+ description: '最大遍历深度',
242
+ },
243
+ },
244
+ required: ['methodName'],
245
+ },
246
+ },
247
+
248
+ // 7. Guard 检查(统一入口)
214
249
  {
215
250
  name: 'autosnippet_guard',
216
251
  tier: 'agent',
@@ -38,6 +38,7 @@ import skillsRouter from './routes/skills.js';
38
38
  import snippetRouter from './routes/snippets.js';
39
39
  import spmRouter from './routes/spm.js';
40
40
  import taskRouter from './routes/task.js';
41
+ import remoteRouter from './routes/remote.js';
41
42
  import violationsRouter from './routes/violations.js';
42
43
  import wikiRouter from './routes/wiki.js';
43
44
 
@@ -302,6 +303,9 @@ export class HttpServer {
302
303
  // Wiki 路由
303
304
  this.app.use(`${apiPrefix}/wiki`, wikiRouter);
304
305
 
306
+ // Remote 路由(飞书 Bot → IDE 远程指令桥接)
307
+ this.app.use(`${apiPrefix}/remote`, remoteRouter);
308
+
305
309
  // 根路径 — 返回 API 元信息(避免外部探测产生无意义 404)
306
310
  this.app.all('/', (req, res) => {
307
311
  res.json({