autosnippet 1.6.1 → 1.7.0

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 (118) hide show
  1. package/README.md +5 -8
  2. package/bin/api-server.js +84 -0
  3. package/bin/asd-cli.js +3 -2
  4. package/bin/cli-commands.js +169 -16
  5. package/bin/dashboard/routes/recipes.js +45 -0
  6. package/bin/dashboard/routes/search.js +194 -27
  7. package/bin/dashboard/routes/snippets.js +28 -0
  8. package/bin/dashboard/routes/spm.js +44 -12
  9. package/bin/dashboard-server.js +17 -4
  10. package/checksums.json +4 -4
  11. package/dashboard/dist/assets/index-C4kWcLRI.js +62 -0
  12. package/dashboard/dist/assets/index-DqAMMzYn.css +1 -0
  13. package/dashboard/dist/assets/{markdown-DqCR-tTD.js → markdown-YurNruXx.js} +1 -1
  14. package/dashboard/dist/assets/{vendor-DcWukCsG.js → vendor-Bz-TYq6B.js} +64 -69
  15. package/dashboard/dist/index.html +4 -4
  16. package/lib/agent/Agent.js +402 -0
  17. package/lib/agent/AgentCoordinator.js +10 -0
  18. package/lib/agent/ResultFusion.js +33 -0
  19. package/lib/agent/Task.js +337 -0
  20. package/lib/agent/UserPreferenceManager.js +5 -0
  21. package/lib/ai/providers/ClaudeProvider.js +131 -6
  22. package/lib/ai/providers/GoogleGeminiProvider.js +153 -28
  23. package/lib/ai/providers/MockProvider.js +49 -2
  24. package/lib/ai/providers/OpenAiProvider.js +161 -18
  25. package/lib/api/APIGateway.js +689 -0
  26. package/lib/application/services/ContextServiceCompat.js +73 -30
  27. package/lib/application/services/IntelligentServiceLayer.js +172 -7
  28. package/lib/application/services/SearchServiceV2.js +198 -41
  29. package/lib/automation/ActionPipeline.js +13 -0
  30. package/lib/automation/AutomationOrchestrator.js +23 -0
  31. package/lib/automation/ContextCollector.js +10 -0
  32. package/lib/automation/OutputApplier.js +10 -0
  33. package/lib/automation/TriggerResolver.js +13 -0
  34. package/lib/business/metrics/MetricsHub.js +442 -0
  35. package/lib/business/recipe/RecipeHub.js +460 -0
  36. package/lib/business/search/SearchHub.js +484 -0
  37. package/lib/candidate/aggregateCandidates.js +79 -0
  38. package/lib/cli/candidateCommand.js +143 -0
  39. package/lib/cli/embedCommand.js +47 -0
  40. package/lib/cli/searchCommand.js +138 -0
  41. package/lib/cli/statusCommand.js +256 -0
  42. package/lib/guard/guardViolations.js +1 -1
  43. package/lib/infrastructure/cache/CacheHub.js +243 -0
  44. package/lib/infrastructure/error/ErrorManager.js +453 -0
  45. package/lib/infrastructure/external/OpenBrowser.js +14 -1
  46. package/lib/infrastructure/external/spm/DepFixer.js +164 -0
  47. package/lib/infrastructure/external/spm/DepGraphAnalyzer.js +98 -0
  48. package/lib/infrastructure/external/spm/DepGraphService.js +208 -0
  49. package/lib/infrastructure/external/spm/DepPolicyEngine.js +49 -0
  50. package/lib/infrastructure/external/spm/DepReport.js +10 -0
  51. package/lib/infrastructure/external/spm/SpmDepsServiceV2.js +219 -194
  52. package/lib/infrastructure/external/spm/spmDepGraphPatch.js +4 -0
  53. package/lib/infrastructure/external/spm/spmDepMapUpdater.js +10 -0
  54. package/lib/infrastructure/logging/LogFactory.js +160 -0
  55. package/lib/infrastructure/notification/ClipboardManager.js +137 -0
  56. package/lib/infrastructure/notification/NativeUi.js +71 -6
  57. package/lib/infrastructure/notification/Notifier.js +52 -0
  58. package/lib/infrastructure/paths/PathFinder.js +19 -110
  59. package/lib/infrastructure/paths/ProjectStructure.js +222 -0
  60. package/lib/infrastructure/process/ProcessHub.js +452 -0
  61. package/lib/injection/DirectiveParserV2.js +33 -17
  62. package/lib/injection/ImportDecisionEngine.js +39 -0
  63. package/lib/injection/ImportWriterV2.js +210 -5
  64. package/lib/injection/ModuleResolverV2.js +93 -17
  65. package/lib/injection/injectionService.js +172 -21
  66. package/lib/mcp/envelope.js +21 -0
  67. package/lib/mcp/schemas.js +46 -0
  68. package/lib/rateLimit.js +10 -2
  69. package/lib/recipe/validateRecipeCandidate.js +54 -0
  70. package/lib/search/indexer.js +6 -1
  71. package/lib/search/rankingEngine.js +11 -2
  72. package/lib/search/recallEngine.js +5 -1
  73. package/lib/search/unifiedSearch.js +245 -0
  74. package/lib/watch/DirectiveDetector.js +1 -1
  75. package/lib/watch/FileWatchService.js +31 -0
  76. package/lib/watch/handlers/AlinkHandler.js +14 -0
  77. package/lib/watch/handlers/CreateHandler.js +72 -4
  78. package/lib/watch/handlers/DraftHandler.js +14 -0
  79. package/lib/watch/handlers/GuardHandler.js +14 -0
  80. package/lib/watch/handlers/HeaderHandler.js +14 -0
  81. package/lib/watch/handlers/SearchHandler.js +899 -43
  82. package/lib/writeGuard.js +10 -15
  83. package/package.json +9 -5
  84. package/recipes/README.md +2 -2
  85. package/resources/native-ui/main.swift +169 -30
  86. package/scripts/cursor-rules/autosnippet-conventions.mdc +10 -8
  87. package/scripts/demo-candidates-submit.js +76 -0
  88. package/scripts/diagnose-mcp.js +74 -0
  89. package/scripts/generate-checksums.js +16 -0
  90. package/scripts/generate-recipe-drafts.js +151 -0
  91. package/scripts/init-xcode-snippets.js +310 -0
  92. package/scripts/install-cursor-skill.js +13 -20
  93. package/scripts/install-full.js +2 -11
  94. package/scripts/mcp-server.js +460 -97
  95. package/scripts/random-search-test.js +142 -0
  96. package/scripts/recipe-audit.js +230 -0
  97. package/scripts/recipe-migration-diagnose.js +0 -11
  98. package/scripts/test-ai-failure-handling.js +79 -0
  99. package/scripts/test-google-models.js +85 -0
  100. package/scripts/test-hybrid-comprehensive.js +120 -0
  101. package/scripts/test-hybrid-search.js +54 -0
  102. package/scripts/test-search-modes.js +364 -0
  103. package/scripts/test-sentence-search.js +80 -0
  104. package/skills/autosnippet-batch-scan/SKILL.md +4 -2
  105. package/skills/autosnippet-candidates/SKILL.md +79 -0
  106. package/skills/autosnippet-candidates/SKILL_REDESIGNED.md +813 -0
  107. package/skills/autosnippet-concepts/SKILL.md +140 -125
  108. package/skills/autosnippet-create/SKILL.md +4 -2
  109. package/skills/autosnippet-dep-graph/SKILL.md +5 -3
  110. package/skills/autosnippet-guard/SKILL.md +7 -1
  111. package/skills/autosnippet-intent/SKILL.md +40 -0
  112. package/skills/autosnippet-recipe-candidates/SKILL.md +4 -2
  113. package/skills/autosnippet-recipes/SKILL.md +5 -2
  114. package/skills/autosnippet-search/SKILL.md +5 -3
  115. package/skills/autosnippet-structure/SKILL.md +34 -0
  116. package/skills/autosnippet-when/SKILL.md +11 -9
  117. package/dashboard/dist/assets/index-DIIxAaTf.js +0 -54
  118. package/dashboard/dist/assets/index-DnrK_geI.css +0 -1
@@ -3,6 +3,23 @@ function registerSearchRoutes(app, ctx) {
3
3
  const Paths = require('../../../lib/infrastructure/config/Paths.js');
4
4
  const { analyzeContext } = require('../../../lib/search/contextAnalyzer.js');
5
5
 
6
+ // ============================================================
7
+ // 调试工具
8
+ // ============================================================
9
+ const DEBUG = process.env.ASD_DEBUG_SEARCH === '1' || process.env.DEBUG?.includes('search');
10
+
11
+ function debug(taskName, step, data) {
12
+ if (!DEBUG) return;
13
+ const timestamp = new Date().toISOString().split('T')[1];
14
+ console.log(`\n[${timestamp}] [Search:${taskName}] ${step}`, data ? JSON.stringify(data, null, 2) : '');
15
+ }
16
+
17
+ function debugError(taskName, step, error) {
18
+ if (!DEBUG) return;
19
+ const timestamp = new Date().toISOString().split('T')[1];
20
+ console.error(`\n[${timestamp}] [Search:${taskName}] ❌ ${step}`, error?.message || error);
21
+ }
22
+
6
23
  // ============================================================
7
24
  // 统一的搜索核心函数(共享给所有搜索 API)
8
25
  // ============================================================
@@ -21,33 +38,57 @@ function registerSearchRoutes(app, ctx) {
21
38
  throw new Error('keyword is required');
22
39
  }
23
40
 
41
+ const taskId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
42
+ debug(taskId, 'INIT', {
43
+ keyword,
44
+ targetName,
45
+ currentFile,
46
+ language,
47
+ limit,
48
+ hasFileContent: !!fileContent
49
+ });
50
+
24
51
  const { getInstance } = require('../../../lib/context');
25
52
  const recipeStats = require('../../../lib/recipe/recipeStats');
26
53
  const service = getInstance(projectRoot);
27
54
  const rootSpec = require(Paths.getProjectSpecPath(projectRoot));
28
55
 
29
56
  // 1. 分析当前上下文(使用 analyzeContext 深度分析)
57
+ debug(taskId, '1️⃣ Context Analysis Start', { targetName, currentFile, language });
30
58
  const contextInfo = await analyzeContext(projectRoot, { targetName, currentFile, language });
59
+ debug(taskId, '1️⃣ Context Analysis Complete', contextInfo);
31
60
 
32
61
  let vectorResults = [];
33
62
  let keywordResults = [];
34
63
 
35
64
  // 2. 向量搜索
65
+ debug(taskId, '2️⃣ Vector Search Start', { keyword, limit: limit + 5 });
36
66
  try {
37
67
  const ai = await AiFactory.getProvider(projectRoot);
38
68
  if (ai) {
39
69
  vectorResults = await service.search(keyword, { limit: limit + 5, filter: { type: 'recipe' } });
40
70
  vectorResults = vectorResults.map(r => ({ ...r, _vectorScore: r.similarity || 0 }));
71
+ debug(taskId, '2️⃣ Vector Search Complete', {
72
+ count: vectorResults.length,
73
+ topResults: vectorResults.slice(0, 3).map(r => ({
74
+ name: r.name,
75
+ score: r._vectorScore
76
+ }))
77
+ });
78
+ } else {
79
+ debug(taskId, '2️⃣ Vector Search Skipped', { reason: 'No AI provider' });
41
80
  }
42
81
  } catch (e) {
43
- console.warn('[Unified Search] Vector search failed:', e.message);
82
+ debugError(taskId, '2️⃣ Vector Search Failed', e);
44
83
  }
45
84
 
46
85
  // 3. 关键词搜索
86
+ debug(taskId, '3️⃣ Keyword Search Start', { keyword });
47
87
  try {
48
88
  const recipesDir = Paths.getProjectRecipesPath(projectRoot, rootSpec);
49
89
  if (fs.existsSync(recipesDir)) {
50
90
  const keywordTerms = keyword.toLowerCase().split(/\s+/).filter(w => w.length > 1);
91
+ debug(taskId, '3️⃣ Keyword Terms', { keywordTerms });
51
92
 
52
93
  // 从 contextInfo 获取丰富的上下文术语
53
94
  const contextTerms = [
@@ -106,15 +147,27 @@ function registerSearchRoutes(app, ctx) {
106
147
  });
107
148
  }
108
149
  } catch (e) {
109
- console.warn(`[Unified Search] Failed to read recipe ${recipePath}:`, e.message);
150
+ debugError(taskId, '3️⃣ Recipe Parse Error', { file: recipePath, error: e.message });
110
151
  }
111
152
  }
153
+ debug(taskId, '3️⃣ Keyword Search Complete', {
154
+ count: keywordResults.length,
155
+ topResults: keywordResults.slice(0, 3).map(r => ({
156
+ name: r.name,
157
+ score: r._keywordScore,
158
+ contextMatches: r._contextMatches
159
+ }))
160
+ });
112
161
  }
113
162
  } catch (e) {
114
- console.warn('[Unified Search] Keyword search failed:', e.message);
163
+ debugError(taskId, '3️⃣ Keyword Search Failed', e);
115
164
  }
116
165
 
117
166
  // 4. 合并和评分
167
+ debug(taskId, '4️⃣ Merge & Score Start', {
168
+ vectorCount: vectorResults.length,
169
+ keywordCount: keywordResults.length
170
+ });
118
171
  const mergedMap = new Map();
119
172
 
120
173
  for (const vr of vectorResults) {
@@ -161,10 +214,25 @@ function registerSearchRoutes(app, ctx) {
161
214
  });
162
215
 
163
216
  // 6. 权威分加权排序
217
+ debug(taskId, '5️⃣ Hybrid Score Calculation', {
218
+ count: results.length,
219
+ topResults: results.slice(0, 3).map(r => ({
220
+ name: r.name,
221
+ hybridScore: r._hybridScore,
222
+ vectorScore: r._vectorScore,
223
+ keywordScore: r._keywordScore,
224
+ isContextRelevant: r.isContextRelevant
225
+ }))
226
+ });
164
227
  try {
165
228
  const stats = recipeStats.getRecipeStats(projectRoot);
166
229
  const byFileEntries = Object.values(stats.byFile || {});
167
230
 
231
+ debug(taskId, '6️⃣ Authority Scoring Start', {
232
+ statsCount: Object.keys(stats.byFile || {}).length,
233
+ totalByFileEntries: byFileEntries.length
234
+ });
235
+
168
236
  results = results.map(item => {
169
237
  const key = path.basename(item.name);
170
238
  const entry = (stats.byFile || {})[key];
@@ -225,22 +293,37 @@ Recipe: ${results[0]?.name || 'N/A'}
225
293
  similarity: r.similarity * 0.7 + aiBoost * 0.3
226
294
  }));
227
295
 
228
- console.log(`[Unified Search] AI re-evaluated ${results.length} results, boost: ${aiBoost.toFixed(2)}`);
296
+ debug(taskId, '7️⃣ AI Evaluation Complete', {
297
+ aiScore,
298
+ aiBoost: aiBoost.toFixed(2),
299
+ topResultFinalScore: results[0]?.similarity
300
+ });
229
301
  }
230
302
  } catch (e) {
231
- console.warn('[Unified Search] AI context evaluation failed:', e.message);
303
+ debugError(taskId, '7️⃣ AI Evaluation Failed', e);
232
304
  }
233
305
  }
234
306
 
235
307
  // 8. 排序和限制
308
+ debug(taskId, '8️⃣ Sort & Filter Start', { beforeCount: results.length, minSimilarity: vectorResults.length > 0 ? 0.3 : 0.2 });
236
309
  results.sort((a, b) => b.similarity - a.similarity);
237
310
 
238
311
  // 设置最小相似度阈值
239
312
  const minSimilarity = vectorResults.length > 0 ? 0.3 : 0.2;
240
313
  results = results.filter(r => r.similarity >= minSimilarity);
241
314
 
315
+ debug(taskId, '8️⃣ After Filter', { afterCount: results.length, limit });
242
316
  results = results.slice(0, limit);
243
317
 
318
+ debug(taskId, '✅ COMPLETE', {
319
+ finalCount: results.length,
320
+ topResults: results.slice(0, 3).map(r => ({
321
+ name: r.name,
322
+ similarity: r.similarity,
323
+ authority: r.authority
324
+ }))
325
+ });
326
+
244
327
  return {
245
328
  results,
246
329
  contextInfo,
@@ -410,16 +493,58 @@ Recipe: ${results[0]?.name || 'N/A'}
410
493
  console.log('[Trigger from Code] Search Results:', JSON.stringify(resultInfo, null, 2));
411
494
 
412
495
  // 5. 清理结果为 Xcode 格式
413
- const cleanResults = results.map(r => ({
414
- name: r.name,
415
- snippet: r.content ? r.content.substring(0, 300) : '',
416
- similarity: Math.round(r.similarity * 100),
417
- isContextRelevant: r.isContextRelevant,
418
- authority: Math.round(r.authority * 100) / 100,
419
- usageCount: r.usageCount,
420
- stats: r.stats,
421
- aiRelevanceScore: r._aiRelevanceScore ? Math.round(r._aiRelevanceScore) : undefined
422
- }));
496
+ // 5.1. 使用 SearchServiceV2 为结果添加 Agent 评分
497
+ let agentScores = new Map();
498
+ try {
499
+ const SearchServiceV2 = require('../../../lib/application/services/SearchServiceV2');
500
+ const searchServiceV2 = new SearchServiceV2(projectRoot, {
501
+ enableIntelligentLayer: true,
502
+ enableLearning: false
503
+ });
504
+
505
+ // 生成 sessionId 和 userId
506
+ const sessionId = `xcode-${filePath || 'general'}-${Date.now()}`;
507
+ const userId = process.env.ASD_USER_ID || process.env.USER || process.env.USERNAME || 'unknown';
508
+
509
+ // 对前 5 个结果调用 SearchServiceV2 以获得 Agent 评分
510
+ const agentResults = await searchServiceV2.search(searchKeyword, {
511
+ limit: 5,
512
+ sessionId,
513
+ userId,
514
+ context: { source: 'dashboard-xcode-watch' }
515
+ });
516
+
517
+ // 构建 agentScores map
518
+ if (agentResults && Array.isArray(agentResults)) {
519
+ for (const agentResult of agentResults) {
520
+ if (agentResult.name && agentResult.qualityScore !== undefined) {
521
+ agentScores.set(agentResult.name, {
522
+ qualityScore: agentResult.qualityScore,
523
+ recommendReason: agentResult.recommendReason || undefined
524
+ });
525
+ }
526
+ }
527
+ }
528
+ } catch (e) {
529
+ console.warn('[Trigger from Code] Agent scoring failed, using defaults:', e.message);
530
+ }
531
+
532
+ // 5.2. 清理结果为 Xcode 格式
533
+ const cleanResults = results.map(r => {
534
+ const agentScore = agentScores.get(r.name);
535
+ return {
536
+ name: r.name,
537
+ snippet: r.content ? r.content.substring(0, 300) : '',
538
+ similarity: Math.round(r.similarity * 100),
539
+ isContextRelevant: r.isContextRelevant,
540
+ authority: Math.round(r.authority * 100) / 100,
541
+ usageCount: r.usageCount,
542
+ stats: r.stats,
543
+ aiRelevanceScore: r._aiRelevanceScore ? Math.round(r._aiRelevanceScore) : undefined,
544
+ qualityScore: agentScore?.qualityScore,
545
+ recommendReason: agentScore?.recommendReason
546
+ };
547
+ });
423
548
 
424
549
  const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
425
550
  res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
@@ -498,18 +623,60 @@ Recipe: ${results[0]?.name || 'N/A'}
498
623
  console.log('[Context-Aware Search] Search Results:', JSON.stringify(resultInfo, null, 2));
499
624
 
500
625
  // 格式化结果供 Dashboard 使用
501
- const cleanResults = results.map(r => ({
502
- name: r.name,
503
- content: r.content,
504
- similarity: Math.round(r.similarity * 100) / 100,
505
- authority: r.authority,
506
- usageCount: r.usageCount,
507
- stats: r.stats,
508
- isContextRelevant: r.isContextRelevant,
509
- matchType: r._vectorScore > r._keywordScore ? 'semantic' : 'keyword',
510
- metadata: r.metadata,
511
- aiRelevanceScore: r.aiRelevanceScore // 多标记 AI 评分
512
- }));
626
+ // 使用 SearchServiceV2 为结果添加 Agent 评分
627
+ let agentScores = new Map();
628
+ try {
629
+ const SearchServiceV2 = require('../../../lib/application/services/SearchServiceV2');
630
+ const searchServiceV2 = new SearchServiceV2(projectRoot, {
631
+ enableIntelligentLayer: true,
632
+ enableLearning: false
633
+ });
634
+
635
+ // 生成 sessionId 和 userId
636
+ const sessionId = `dashboard-${currentFile || 'general'}-${Date.now()}`;
637
+ const userId = process.env.ASD_USER_ID || process.env.USER || process.env.USERNAME || 'unknown';
638
+
639
+ // 对前 5 个结果调用 SearchServiceV2 以获得 Agent 评分
640
+ const agentResults = await searchServiceV2.search(keyword, {
641
+ limit: 5,
642
+ sessionId,
643
+ userId,
644
+ context: { source: 'dashboard-context-aware' }
645
+ });
646
+
647
+ // 构建 agentScores map
648
+ if (agentResults && Array.isArray(agentResults)) {
649
+ for (const agentResult of agentResults) {
650
+ if (agentResult.name && agentResult.qualityScore !== undefined) {
651
+ agentScores.set(agentResult.name, {
652
+ qualityScore: agentResult.qualityScore,
653
+ recommendReason: agentResult.recommendReason || undefined
654
+ });
655
+ }
656
+ }
657
+ }
658
+ } catch (e) {
659
+ console.warn('[Context-Aware Search] Agent scoring failed, using defaults:', e.message);
660
+ }
661
+
662
+ // 格式化结果供 Dashboard 使用
663
+ const cleanResults = results.map(r => {
664
+ const agentScore = agentScores.get(r.name);
665
+ return {
666
+ name: r.name,
667
+ content: r.content,
668
+ similarity: Math.round(r.similarity * 100) / 100,
669
+ authority: r.authority,
670
+ usageCount: r.usageCount,
671
+ stats: r.stats,
672
+ isContextRelevant: r.isContextRelevant,
673
+ matchType: r._vectorScore > r._keywordScore ? 'semantic' : 'keyword',
674
+ metadata: r.metadata,
675
+ aiRelevanceScore: r.aiRelevanceScore, // 多标记 AI 评分
676
+ qualityScore: agentScore?.qualityScore,
677
+ recommendReason: agentScore?.recommendReason
678
+ };
679
+ });
513
680
 
514
681
  res.json({
515
682
  results: cleanResults,
@@ -101,6 +101,34 @@ function registerSnippetsRoutes(app, ctx) {
101
101
  res.status(500).json({ error: err.message });
102
102
  }
103
103
  });
104
+
105
+ // API: 删除所有 Snippets
106
+ app.post('/api/snippets/delete-all', async (req, res) => {
107
+ try {
108
+ const probe = writeGuard.checkWritePermission(projectRoot);
109
+ if (!probe.ok) {
110
+ return res.status(403).json({ error: probe.error || '没权限', code: 'RECIPE_WRITE_FORBIDDEN' });
111
+ }
112
+ const rootSpecPath = Paths.getProjectSpecPath(projectRoot);
113
+ const spec = specRepository.readSpecFile(rootSpecPath);
114
+ const count = spec?.list?.length || 0;
115
+
116
+ if (count > 0) {
117
+ // 获取所有 snippet identifiers
118
+ const identifiers = spec.list.map(s => s.identifier);
119
+
120
+ // 逐个删除(使用 deleteSnippet 确保清理分体文件和同步)
121
+ for (const identifier of identifiers) {
122
+ await specRepository.deleteSnippet(rootSpecPath, identifier, { syncRoot: true });
123
+ }
124
+ }
125
+
126
+ res.json({ success: true, count });
127
+ } catch (err) {
128
+ console.error('[API Error]', err);
129
+ res.status(500).json({ error: err.message });
130
+ }
131
+ });
104
132
  }
105
133
 
106
134
  module.exports = {
@@ -15,6 +15,7 @@ function registerSpmRoutes(app, ctx) {
15
15
  // API: 获取项目 SPM 依赖关系图(优先读 spmmap 全解析结果,用于前端「依赖关系图」页展示)
16
16
  app.get('/api/dep-graph', async (req, res) => {
17
17
  try {
18
+ const level = String(req.query?.level || 'package');
18
19
  const knowledgeDir = Paths.getProjectKnowledgePath(projectRoot);
19
20
  const mapPath = path.join(knowledgeDir, 'AutoSnippet.spmmap.json');
20
21
  let graph = null;
@@ -31,18 +32,48 @@ function registerSpmRoutes(app, ctx) {
31
32
  if (!graph || !graph.packages) {
32
33
  return res.json({ nodes: [], edges: [], projectRoot: null });
33
34
  }
34
- const nodes = Object.keys(graph.packages).map((id) => ({
35
- id,
36
- label: id,
37
- type: 'package',
38
- packageDir: graph.packages[id]?.packageDir,
39
- packageSwift: graph.packages[id]?.packageSwift,
40
- targets: graph.packages[id]?.targets,
41
- }));
42
- const edges = [];
43
- for (const [from, tos] of Object.entries(graph.edges || {})) {
44
- for (const to of tos || []) {
45
- edges.push({ from, to });
35
+ let nodes = [];
36
+ let edges = [];
37
+
38
+ if (level === 'target') {
39
+ const nodeList = [];
40
+ const baseEdges = [];
41
+ for (const [pkgName, pkgInfo] of Object.entries(graph.packages)) {
42
+ const targetsInfo = pkgInfo?.targetsInfo || {};
43
+ for (const [targetName, info] of Object.entries(targetsInfo)) {
44
+ const id = `${pkgName}::${targetName}`;
45
+ nodeList.push({
46
+ id,
47
+ label: targetName,
48
+ type: 'target',
49
+ packageName: pkgName,
50
+ });
51
+ const deps = info?.dependencies || [];
52
+ for (const d of deps) {
53
+ const depName = d?.name;
54
+ if (!depName) continue;
55
+ const depPkg = d?.package || pkgName;
56
+ const toId = `${depPkg}::${depName}`;
57
+ baseEdges.push({ from: id, to: toId, source: 'base' });
58
+ }
59
+ }
60
+ }
61
+
62
+ nodes = nodeList;
63
+ edges = baseEdges;
64
+ } else {
65
+ nodes = Object.keys(graph.packages).map((id) => ({
66
+ id,
67
+ label: id,
68
+ type: 'package',
69
+ packageDir: graph.packages[id]?.packageDir,
70
+ packageSwift: graph.packages[id]?.packageSwift,
71
+ targets: graph.packages[id]?.targets,
72
+ }));
73
+ for (const [from, tos] of Object.entries(graph.edges || {})) {
74
+ for (const to of tos || []) {
75
+ edges.push({ from, to, source: 'base' });
76
+ }
46
77
  }
47
78
  }
48
79
  res.json({
@@ -57,6 +88,7 @@ function registerSpmRoutes(app, ctx) {
57
88
  }
58
89
  });
59
90
 
91
+
60
92
  // API: 获取 Target 将要扫描的文件列表(不调用 AI)。支持 body.target 或 body.targetName(按名称查 target)
61
93
  app.post('/api/spm/target-files', async (req, res) => {
62
94
  try {
@@ -130,12 +130,25 @@ async function launch(projectRoot, port = 3000, options = {}) {
130
130
 
131
131
  const specRepository = new SpecRepositoryV2(projectRoot);
132
132
  const forceBuild = options.forceBuild === true || process.env.ASD_UI_BUILD === '1' || process.env.ASD_UI_REBUILD === '1';
133
- // 1. 在后台启动 Watcher
134
- console.log(`[Dashboard] 正在后台启动项目监听器...`);
133
+
134
+ // 1. 在后台启动 Watcher(支持调试模式)
135
+ const isDebugMode = process.env.ASD_DEBUG_WATCH === '1' || process.env.ASD_DEBUG_SEARCH === '1';
136
+ if (isDebugMode) {
137
+ console.log(`[Dashboard] 正在启动项目监听器(调试模式)...`);
138
+ } else {
139
+ console.log(`[Dashboard] 正在后台启动项目监听器...`);
140
+ }
141
+
135
142
  const rootSpecPath = Paths.getProjectSpecPath(projectRoot);
136
143
  try {
137
- watch.watchFileChange(rootSpecPath, projectRoot, { quiet: true });
138
- console.log(`[Dashboard] 监听器已就绪`);
144
+ // 调试模式下不启用 quiet,以便看到详细日志
145
+ watch.watchFileChange(rootSpecPath, projectRoot, { quiet: !isDebugMode });
146
+ if (isDebugMode) {
147
+ console.log(`[Dashboard] ✅ 监听器已就绪(调试模式已启用)`);
148
+ console.log(`[Dashboard] 💡 在 Xcode 中使用 // as:s 将触发搜索`);
149
+ } else {
150
+ console.log(`[Dashboard] ✅ 监听器已就绪`);
151
+ }
139
152
  } catch (err) {
140
153
  console.error(`[Dashboard] ❌ 监听器启动失败: ${err.message}`);
141
154
  }
package/checksums.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "bin/asd-cli.js": "a208a8204fe637bfa23f15e1b4893d286647e8cd575590b70ebadb441c1e1404",
3
- "bin/dashboard-server.js": "e62032f5d92de0f672a19b678b8c446e75fa4cb258fcb1ac2545084e1c435f72",
4
- "lib/writeGuard.js": "0a2b0f894d37d0efeea756ed49ffe3a5af51c1eeb18e19ca9e45b9b916f39b64",
5
- "lib/rateLimit.js": "418dc1bf0ada5c511f7444e18ac22864b22f8ff8685f3757045716268a9219e0",
2
+ "bin/asd-cli.js": "1e62f7b8792dbb0919deaa1db2b2d5e1282f535b220508685d8caff9a79e5d21",
3
+ "bin/dashboard-server.js": "6bcbfffae8fbdba8472653f934177077e8c4e25d95d8c5ae3080698239c3a513",
4
+ "lib/writeGuard.js": "2ec5b4fa958b013702ba4023b1d1d51d93db3e0be1921e814ef2abb032b45775",
5
+ "lib/rateLimit.js": "ad55cc15b3dc01efa6847c405db9cc7cfab1719a4ca9bc2fb183d4d9c8f51baf",
6
6
  "lib/services/context/ContextService.js": "e1e87506b1c66da274a068ca078491fdf09e1eea4ffbc22c3409f6f0f2e40a8e",
7
7
  "lib/application/services/ContextServiceV2.js": "d42a572e8bea9e002714f23935c6370ed1975904b8ecbc1e18ffad98f99bb79a",
8
8
  "lib/context/adapters/MilvusAdapter.js": "b7c11e95c64f8b57f9edf2380b1038e89438fbe2d87d3d831aee9dbeae2afcbd",