autosnippet 3.0.1 → 3.0.2

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 (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  import express from 'express';
7
- import { asyncHandler } from '../middleware/errorHandler.js';
7
+ import Logger from '../../infrastructure/logging/Logger.js';
8
8
  import { getServiceContainer } from '../../injection/ServiceContainer.js';
9
9
  import { ValidationError } from '../../shared/errors/index.js';
10
- import Logger from '../../infrastructure/logging/Logger.js';
10
+ import { asyncHandler } from '../middleware/errorHandler.js';
11
11
  import { safeInt } from '../utils/routeHelpers.js';
12
12
 
13
13
  const router = express.Router();
@@ -18,267 +18,312 @@ const logger = Logger.getInstance();
18
18
  * 统一搜索
19
19
  * ?q=keyword&type=all|recipe|solution|rule&limit=20&mode=keyword|bm25|semantic&groupByKind=true
20
20
  */
21
- router.get('/', asyncHandler(async (req, res) => {
22
- const { q, type = 'all', mode = 'keyword' } = req.query;
23
- const limit = safeInt(req.query.limit, 20, 1, 100);
24
- const page = safeInt(req.query.page, 1);
25
- const groupByKind = req.query.groupByKind === 'true';
26
-
27
- if (!q || !q.trim()) {
28
- throw new ValidationError('Search query (q) is required');
29
- }
30
-
31
- const container = getServiceContainer();
32
-
33
- // 所有模式优先通过 SearchEngine(含 auto/bm25/semantic/keyword/ranking)
34
- try {
35
- const searchEngine = container.get('searchEngine');
36
- const result = await searchEngine.search(q, { type, limit, mode, groupByKind });
37
- return res.json({ success: true, data: result });
38
- } catch (err) {
39
- logger.warn('SearchEngine 搜索失败,降级到传统搜索', { mode, error: err.message });
40
- }
41
-
42
- const results = {};
43
- const pagination = { page, pageSize: limit };
44
-
45
- // 搜索知识条目(V3 统一模型)
46
- if (type === 'all' || type === 'recipe' || type === 'solution') {
47
- try {
48
- const knowledgeService = container.get('knowledgeService');
49
- results.recipes = await knowledgeService.search(q, pagination);
50
- } catch (err) {
51
- logger.warn('Knowledge 搜索失败', { query: q, error: err.message });
52
- results.recipes = { items: [], total: 0 };
21
+ router.get(
22
+ '/',
23
+ asyncHandler(async (req, res) => {
24
+ const { q, type = 'all', mode = 'keyword' } = req.query;
25
+ const limit = safeInt(req.query.limit, 20, 1, 100);
26
+ const page = safeInt(req.query.page, 1);
27
+ const groupByKind = req.query.groupByKind === 'true';
28
+
29
+ if (!q || !q.trim()) {
30
+ throw new ValidationError('Search query (q) is required');
53
31
  }
54
- }
55
32
 
56
- // 搜索 Guard Rule(boundary-constraint 类型的 Recipe)
57
- if (type === 'all' || type === 'rule') {
33
+ const container = getServiceContainer();
34
+
35
+ // 所有模式优先通过 SearchEngine(含 auto/bm25/semantic/keyword/ranking)
58
36
  try {
59
- const guardService = container.get('guardService');
60
- results.rules = await guardService.searchRules(q, pagination);
37
+ const searchEngine = container.get('searchEngine');
38
+ const result = await searchEngine.search(q, { type, limit, mode, groupByKind });
39
+ return res.json({ success: true, data: result });
61
40
  } catch (err) {
62
- logger.warn('Guard Rule 搜索失败', { query: q, error: err.message });
63
- results.rules = { items: [], total: 0 };
41
+ logger.warn('SearchEngine 搜索失败,降级到传统搜索', { mode, error: err.message });
64
42
  }
65
- }
66
43
 
67
- // 搜索候选知识条目 (V3: lifecycle=draft/pending)
68
- if (type === 'all' || type === 'candidate') {
69
- try {
70
- const knowledgeService = container.get('knowledgeService');
71
- results.candidates = await knowledgeService.search(q, pagination);
72
- } catch (err) {
73
- logger.warn('Candidate 搜索失败', { query: q, error: err.message });
74
- results.candidates = { items: [], total: 0 };
44
+ const results = {};
45
+ const pagination = { page, pageSize: limit };
46
+
47
+ // 搜索知识条目(V3 统一模型)
48
+ if (type === 'all' || type === 'recipe' || type === 'solution') {
49
+ try {
50
+ const knowledgeService = container.get('knowledgeService');
51
+ results.recipes = await knowledgeService.search(q, pagination);
52
+ } catch (err) {
53
+ logger.warn('Knowledge 搜索失败', { query: q, error: err.message });
54
+ results.recipes = { items: [], total: 0 };
55
+ }
75
56
  }
76
- }
77
-
78
- const totalResults = Object.values(results).reduce((sum, r) => sum + (r.total || r.items?.length || 0), 0);
79
-
80
- res.json({
81
- success: true,
82
- data: {
83
- query: q,
84
- type,
85
- mode,
86
- totalResults,
87
- ...results,
88
- },
89
- });
90
- }));
57
+
58
+ // 搜索 Guard Rule(boundary-constraint 类型的 Recipe)
59
+ if (type === 'all' || type === 'rule') {
60
+ try {
61
+ const guardService = container.get('guardService');
62
+ results.rules = await guardService.searchRules(q, pagination);
63
+ } catch (err) {
64
+ logger.warn('Guard Rule 搜索失败', { query: q, error: err.message });
65
+ results.rules = { items: [], total: 0 };
66
+ }
67
+ }
68
+
69
+ // 搜索候选知识条目 (V3: lifecycle=draft/pending)
70
+ if (type === 'all' || type === 'candidate') {
71
+ try {
72
+ const knowledgeService = container.get('knowledgeService');
73
+ results.candidates = await knowledgeService.search(q, pagination);
74
+ } catch (err) {
75
+ logger.warn('Candidate 搜索失败', { query: q, error: err.message });
76
+ results.candidates = { items: [], total: 0 };
77
+ }
78
+ }
79
+
80
+ const totalResults = Object.values(results).reduce(
81
+ (sum, r) => sum + (r.total || r.items?.length || 0),
82
+ 0
83
+ );
84
+
85
+ res.json({
86
+ success: true,
87
+ data: {
88
+ query: q,
89
+ type,
90
+ mode,
91
+ totalResults,
92
+ ...results,
93
+ },
94
+ });
95
+ })
96
+ );
91
97
 
92
98
  /**
93
99
  * GET /api/v1/search/graph
94
100
  * 知识图谱查询
95
101
  * ?nodeId=xxx&nodeType=recipe
96
102
  */
97
- router.get('/graph', asyncHandler(async (req, res) => {
98
- const { nodeId, nodeType, relation, direction = 'both' } = req.query;
103
+ router.get(
104
+ '/graph',
105
+ asyncHandler(async (req, res) => {
106
+ const { nodeId, nodeType, relation, direction = 'both' } = req.query;
99
107
 
100
- if (!nodeId || !nodeType) {
101
- throw new ValidationError('nodeId and nodeType are required');
102
- }
108
+ if (!nodeId || !nodeType) {
109
+ throw new ValidationError('nodeId and nodeType are required');
110
+ }
103
111
 
104
- const container = getServiceContainer();
105
- const graphService = container.get('knowledgeGraphService');
112
+ const container = getServiceContainer();
113
+ const graphService = container.get('knowledgeGraphService');
106
114
 
107
- if (!graphService) {
108
- return res.json({ success: true, data: { outgoing: [], incoming: [] } });
109
- }
115
+ if (!graphService) {
116
+ return res.json({ success: true, data: { outgoing: [], incoming: [] } });
117
+ }
110
118
 
111
- const edges = relation
112
- ? graphService.getRelated(nodeId, nodeType, relation)
113
- : graphService.getEdges(nodeId, nodeType, direction);
119
+ const edges = relation
120
+ ? graphService.getRelated(nodeId, nodeType, relation)
121
+ : graphService.getEdges(nodeId, nodeType, direction);
114
122
 
115
- res.json({ success: true, data: edges });
116
- }));
123
+ res.json({ success: true, data: edges });
124
+ })
125
+ );
117
126
 
118
127
  /**
119
128
  * GET /api/v1/search/graph/impact
120
129
  * 影响分析
121
130
  */
122
- router.get('/graph/impact', asyncHandler(async (req, res) => {
123
- const { nodeId, nodeType } = req.query;
124
- const maxDepth = safeInt(req.query.maxDepth, 3, 1, 5);
125
-
126
- if (!nodeId || !nodeType) {
127
- throw new ValidationError('nodeId and nodeType are required');
128
- }
131
+ router.get(
132
+ '/graph/impact',
133
+ asyncHandler(async (req, res) => {
134
+ const { nodeId, nodeType } = req.query;
135
+ const maxDepth = safeInt(req.query.maxDepth, 3, 1, 5);
136
+
137
+ if (!nodeId || !nodeType) {
138
+ throw new ValidationError('nodeId and nodeType are required');
139
+ }
129
140
 
130
- const container = getServiceContainer();
131
- const graphService = container.get('knowledgeGraphService');
141
+ const container = getServiceContainer();
142
+ const graphService = container.get('knowledgeGraphService');
132
143
 
133
- if (!graphService) {
134
- return res.json({ success: true, data: [] });
135
- }
144
+ if (!graphService) {
145
+ return res.json({ success: true, data: [] });
146
+ }
136
147
 
137
- const impact = graphService.getImpactAnalysis(nodeId, nodeType, maxDepth);
138
- res.json({ success: true, data: impact });
139
- }));
148
+ const impact = graphService.getImpactAnalysis(nodeId, nodeType, maxDepth);
149
+ res.json({ success: true, data: impact });
150
+ })
151
+ );
140
152
 
141
153
  /**
142
154
  * GET /api/v1/search/graph/all
143
155
  * 全量知识图谱边(Dashboard 可视化用)
144
156
  * ?limit=500
145
157
  */
146
- router.get('/graph/all', asyncHandler(async (req, res) => {
147
- const limit = safeInt(req.query.limit, 500, 1, 2000);
148
-
149
- const container = getServiceContainer();
150
- const graphService = container.get('knowledgeGraphService');
151
-
152
- if (!graphService) {
153
- return res.json({ success: true, data: { edges: [], nodeLabels: {} } });
154
- }
155
-
156
- // 只返回 recipe 类型的关系边;module 依赖已由 /spm/dep-graph 提供
157
- const nodeType = req.query.nodeType || 'recipe';
158
- const edges = graphService.getAllEdges(limit, nodeType === 'all' ? undefined : nodeType);
159
-
160
- // 收集节点 ID + 类型 按类型查标签
161
- const nodeMap = new Map(); // id → Set<type>
162
- for (const e of edges) {
163
- if (!nodeMap.has(e.fromId)) nodeMap.set(e.fromId, new Set());
164
- nodeMap.get(e.fromId).add(e.fromType);
165
- if (!nodeMap.has(e.toId)) nodeMap.set(e.toId, new Set());
166
- nodeMap.get(e.toId).add(e.toType);
167
- }
168
-
169
- const nodeLabels = {};
170
- const nodeTypes = {}; // id → 主要类型(供前端区分渲染)
171
- const nodeCategories = {}; // id → category/target 名(供前端分组布局)
172
- if (nodeMap.size > 0) {
173
- const knowledgeRepo = container.get('knowledgeRepository');
174
- for (const [id, types] of nodeMap) {
175
- const primaryType = types.has('recipe') ? 'recipe' : [...types][0];
176
- nodeTypes[id] = primaryType;
177
-
178
- if ((primaryType === 'recipe' || primaryType === 'knowledge') && knowledgeRepo) {
179
- try {
180
- const r = await knowledgeRepo.findById(id);
181
- if (r) {
182
- nodeLabels[id] = r.title || r.name || id;
183
- nodeCategories[id] = r.category || '';
184
- continue;
158
+ router.get(
159
+ '/graph/all',
160
+ asyncHandler(async (req, res) => {
161
+ const limit = safeInt(req.query.limit, 500, 1, 2000);
162
+
163
+ const container = getServiceContainer();
164
+ const graphService = container.get('knowledgeGraphService');
165
+
166
+ if (!graphService) {
167
+ return res.json({ success: true, data: { edges: [], nodeLabels: {} } });
168
+ }
169
+
170
+ // 只返回 recipe 类型的关系边;module 依赖已由 /spm/dep-graph 提供
171
+ const nodeType = req.query.nodeType || 'recipe';
172
+ const edges = graphService.getAllEdges(limit, nodeType === 'all' ? undefined : nodeType);
173
+
174
+ // 收集节点 ID + 类型 → 按类型查标签
175
+ const nodeMap = new Map(); // id → Set<type>
176
+ for (const e of edges) {
177
+ if (!nodeMap.has(e.fromId)) {
178
+ nodeMap.set(e.fromId, new Set());
179
+ }
180
+ nodeMap.get(e.fromId).add(e.fromType);
181
+ if (!nodeMap.has(e.toId)) {
182
+ nodeMap.set(e.toId, new Set());
183
+ }
184
+ nodeMap.get(e.toId).add(e.toType);
185
+ }
186
+
187
+ const nodeLabels = {};
188
+ const nodeTypes = {}; // id → 主要类型(供前端区分渲染)
189
+ const nodeCategories = {}; // id → category/target 名(供前端分组布局)
190
+ if (nodeMap.size > 0) {
191
+ const knowledgeRepo = container.get('knowledgeRepository');
192
+ for (const [id, types] of nodeMap) {
193
+ const primaryType = types.has('recipe') ? 'recipe' : [...types][0];
194
+ nodeTypes[id] = primaryType;
195
+
196
+ if ((primaryType === 'recipe' || primaryType === 'knowledge') && knowledgeRepo) {
197
+ try {
198
+ const r = await knowledgeRepo.findById(id);
199
+ if (r) {
200
+ nodeLabels[id] = r.title || r.name || id;
201
+ nodeCategories[id] = r.category || '';
202
+ continue;
203
+ }
204
+ } catch {
205
+ /* not found – fall through */
185
206
  }
186
- } catch { /* not found – fall through */ }
207
+ }
208
+ nodeLabels[id] = id;
187
209
  }
188
- nodeLabels[id] = id;
189
210
  }
190
- }
191
211
 
192
- res.json({ success: true, data: { edges, nodeLabels, nodeTypes, nodeCategories } });
193
- }));
212
+ res.json({ success: true, data: { edges, nodeLabels, nodeTypes, nodeCategories } });
213
+ })
214
+ );
194
215
 
195
216
  /**
196
217
  * GET /api/v1/search/graph/stats
197
218
  * 图谱统计
198
219
  */
199
- router.get('/graph/stats', asyncHandler(async (req, res) => {
200
- const container = getServiceContainer();
201
- const graphService = container.get('knowledgeGraphService');
202
-
203
- if (!graphService) {
204
- return res.json({ success: true, data: { totalEdges: 0, byRelation: {}, nodeTypes: [] } });
205
- }
220
+ router.get(
221
+ '/graph/stats',
222
+ asyncHandler(async (req, res) => {
223
+ const container = getServiceContainer();
224
+ const graphService = container.get('knowledgeGraphService');
225
+
226
+ if (!graphService) {
227
+ return res.json({ success: true, data: { totalEdges: 0, byRelation: {}, nodeTypes: [] } });
228
+ }
206
229
 
207
- const nodeType = req.query.nodeType || 'recipe';
208
- const stats = graphService.getStats(nodeType === 'all' ? undefined : nodeType);
209
- res.json({ success: true, data: stats });
210
- }));
230
+ const nodeType = req.query.nodeType || 'recipe';
231
+ const stats = graphService.getStats(nodeType === 'all' ? undefined : nodeType);
232
+ res.json({ success: true, data: stats });
233
+ })
234
+ );
211
235
 
212
236
  /**
213
237
  * POST /api/v1/search/context-aware
214
238
  * 上下文感知搜索 — SearchEngine 内置 Ranking Pipeline(CoarseRanker + MultiSignalRanker + ContextBoost)
215
239
  */
216
- router.post('/context-aware', asyncHandler(async (req, res) => {
217
- const { keyword, limit, language, sessionHistory } = req.body;
218
- if (!keyword || !keyword.trim()) {
219
- throw new ValidationError('keyword is required');
220
- }
221
- const t0 = Date.now();
222
- const container = getServiceContainer();
223
- const pageSize = Math.min(limit || 10, 100);
224
- let results = [];
225
- let source = 'knowledgeService';
226
-
227
- // SearchEngine BM25 + 内置 Ranking Pipeline
228
- try {
229
- const searchEngine = container.get('searchEngine');
230
- const result = await searchEngine.search(keyword, {
231
- mode: 'bm25', limit: pageSize, rank: true,
232
- context: { intent: 'search', language, sessionHistory: sessionHistory || [] },
233
- });
234
- const items = result?.items || [];
235
- if (items.length > 0) {
236
- source = result.ranked ? 'search-engine+ranking' : 'search-engine';
237
- results = items.map(r => {
238
- let contentStr = '';
239
- try {
240
- const c = typeof r.content === 'string' && r.content.startsWith('{') ? JSON.parse(r.content) : (r.content || {});
241
- contentStr = c.pattern || c.markdown || c.code || '';
242
- } catch { contentStr = r.content || r.code || ''; }
243
- return {
244
- name: (r.title || r.id) + '.md',
245
- content: contentStr,
246
- similarity: r.score || 0,
247
- authority: r.authorityScore || 0,
248
- matchType: result.ranked ? 'ranked' : 'bm25',
249
- qualityScore: r.qualityScore || 0,
250
- usageCount: r.usageCount || 0,
251
- };
252
- });
240
+ router.post(
241
+ '/context-aware',
242
+ asyncHandler(async (req, res) => {
243
+ const { keyword, limit, language, sessionHistory } = req.body;
244
+ if (!keyword || !keyword.trim()) {
245
+ throw new ValidationError('keyword is required');
253
246
  }
254
- } catch (err) {
255
- logger.warn('SearchEngine context-aware 失败,降级到 KnowledgeService', { error: err.message });
256
- }
247
+ const t0 = Date.now();
248
+ const container = getServiceContainer();
249
+ const pageSize = Math.min(limit || 10, 100);
250
+ let results = [];
251
+ let source = 'knowledgeService';
257
252
 
258
- // 降级: KnowledgeService SQL LIKE
259
- if (results.length === 0) {
253
+ // SearchEngine BM25 + 内置 Ranking Pipeline
260
254
  try {
261
- const knowledgeService = container.get('knowledgeService');
262
- const list = await knowledgeService.search(keyword, { page: 1, pageSize });
263
- const items = list.data || list.items || [];
264
- results = items.map(r => ({
265
- name: (r.title || r.id) + '.md',
266
- content: (r.content || {}).pattern || (r.content || {}).markdown || '',
267
- similarity: 1,
268
- authority: (r.quality || {}).overall || 0,
269
- matchType: 'keyword',
270
- qualityScore: (r.quality || {}).overall || 0,
271
- }));
272
- source = 'knowledgeService';
273
- } catch { /* 全部失败 */ }
274
- }
255
+ const searchEngine = container.get('searchEngine');
256
+ const result = await searchEngine.search(keyword, {
257
+ mode: 'bm25',
258
+ limit: pageSize,
259
+ rank: true,
260
+ context: { intent: 'search', language, sessionHistory: sessionHistory || [] },
261
+ });
262
+ const items = result?.items || [];
263
+ if (items.length > 0) {
264
+ source = result.ranked ? 'search-engine+ranking' : 'search-engine';
265
+ results = items.map((r) => {
266
+ let contentStr = '';
267
+ try {
268
+ const c =
269
+ typeof r.content === 'string' && r.content.startsWith('{')
270
+ ? JSON.parse(r.content)
271
+ : r.content || {};
272
+ contentStr = c.pattern || c.markdown || c.code || '';
273
+ } catch {
274
+ contentStr = r.content || r.code || '';
275
+ }
276
+ return {
277
+ name: `${r.title || r.id}.md`,
278
+ content: contentStr,
279
+ similarity: r.score || 0,
280
+ authority: r.authorityScore || 0,
281
+ matchType: result.ranked ? 'ranked' : 'bm25',
282
+ qualityScore: r.qualityScore || 0,
283
+ usageCount: r.usageCount || 0,
284
+ };
285
+ });
286
+ }
287
+ } catch (err) {
288
+ logger.warn('SearchEngine context-aware 失败,降级到 KnowledgeService', {
289
+ error: err.message,
290
+ });
291
+ }
292
+
293
+ // 降级: KnowledgeService SQL LIKE
294
+ if (results.length === 0) {
295
+ try {
296
+ const knowledgeService = container.get('knowledgeService');
297
+ const list = await knowledgeService.search(keyword, { page: 1, pageSize });
298
+ const items = list.data || list.items || [];
299
+ results = items.map((r) => ({
300
+ name: `${r.title || r.id}.md`,
301
+ content: r.content?.pattern || r.content?.markdown || '',
302
+ similarity: 1,
303
+ authority: r.quality?.overall || 0,
304
+ matchType: 'keyword',
305
+ qualityScore: r.quality?.overall || 0,
306
+ }));
307
+ source = 'knowledgeService';
308
+ } catch {
309
+ /* 全部失败 */
310
+ }
311
+ }
275
312
 
276
- const elapsed = Date.now() - t0;
277
- res.json({
278
- success: true,
279
- data: { results, context: {}, total: results.length, hasAiEvaluation: false, searchTime: elapsed, source },
280
- });
281
- }));
313
+ const elapsed = Date.now() - t0;
314
+ res.json({
315
+ success: true,
316
+ data: {
317
+ results,
318
+ context: {},
319
+ total: results.length,
320
+ hasAiEvaluation: false,
321
+ searchTime: elapsed,
322
+ source,
323
+ },
324
+ });
325
+ })
326
+ );
282
327
 
283
328
  /* ═══ 相似度检测 ════════════════════════════════════════ */
284
329
 
@@ -287,110 +332,121 @@ router.post('/context-aware', asyncHandler(async (req, res) => {
287
332
  * 候选与已有 Recipe 的相似度检测
288
333
  * Body: { code, language } 或 { targetName, candidateId } 或 { candidate: {title, summary, code} }
289
334
  */
290
- router.post('/similarity', asyncHandler(async (req, res) => {
291
- const { code, language, targetName, candidateId, candidate } = req.body;
292
- const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
335
+ router.post(
336
+ '/similarity',
337
+ asyncHandler(async (req, res) => {
338
+ const { code, targetName, candidateId, candidate } = req.body;
339
+ const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
293
340
 
294
- let candidateObj;
341
+ let candidateObj;
295
342
 
296
- if (candidateId && targetName) {
297
- // 从知识库加载候选
298
- try {
299
- const container = getServiceContainer();
300
- const knowledgeService = container.get('knowledgeService');
301
- const entry = await knowledgeService.get(candidateId);
302
- if (entry) {
303
- const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
304
- candidateObj = {
305
- title: json.title || '',
306
- summary: json.description || '',
307
- code: json.content?.pattern || '',
308
- usageGuide: json.content?.markdown || '',
309
- };
343
+ if (candidateId && targetName) {
344
+ // 从知识库加载候选
345
+ try {
346
+ const container = getServiceContainer();
347
+ const knowledgeService = container.get('knowledgeService');
348
+ const entry = await knowledgeService.get(candidateId);
349
+ if (entry) {
350
+ const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
351
+ candidateObj = {
352
+ title: json.title || '',
353
+ summary: json.description || '',
354
+ code: json.content?.pattern || '',
355
+ usageGuide: json.content?.markdown || '',
356
+ };
357
+ }
358
+ } catch (err) {
359
+ logger.warn('similarity: failed to load candidate', { candidateId, error: err.message });
310
360
  }
361
+ } else if (candidate) {
362
+ candidateObj = {
363
+ title: candidate.title || '',
364
+ summary: candidate.summary || candidate.description || '',
365
+ code: candidate.code || candidate.pattern || '',
366
+ usageGuide: candidate.usageGuide || candidate.markdown || '',
367
+ };
368
+ } else if (code) {
369
+ candidateObj = { title: '', summary: '', code: code || '', usageGuide: '' };
370
+ }
371
+
372
+ if (!candidateObj) {
373
+ return res.json({ success: true, data: { similar: [] } });
374
+ }
375
+
376
+ try {
377
+ const { findSimilarRecipes } = await import('../../service/candidate/SimilarityService.js');
378
+ const similar = findSimilarRecipes(projectRoot, candidateObj, { threshold: 0.3, topK: 10 });
379
+
380
+ // 映射为前端期望格式
381
+ const mapped = similar.map((s) => ({
382
+ recipeName: s.title || s.file?.replace(/\.md$/, '') || '',
383
+ similarity: s.similarity,
384
+ file: s.file,
385
+ }));
386
+
387
+ res.json({ success: true, data: { similar: mapped } });
311
388
  } catch (err) {
312
- logger.warn('similarity: failed to load candidate', { candidateId, error: err.message });
389
+ logger.warn('similarity search failed', { error: err.message });
390
+ res.json({ success: true, data: { similar: [] } });
313
391
  }
314
- } else if (candidate) {
315
- candidateObj = {
316
- title: candidate.title || '',
317
- summary: candidate.summary || candidate.description || '',
318
- code: candidate.code || candidate.pattern || '',
319
- usageGuide: candidate.usageGuide || candidate.markdown || '',
320
- };
321
- } else if (code) {
322
- candidateObj = { title: '', summary: '', code: code || '', usageGuide: '' };
323
- }
324
-
325
- if (!candidateObj) {
326
- return res.json({ success: true, data: { similar: [] } });
327
- }
328
-
329
- try {
330
- const { findSimilarRecipes } = await import('../../service/candidate/SimilarityService.js');
331
- const similar = findSimilarRecipes(projectRoot, candidateObj, { threshold: 0.3, topK: 10 });
332
-
333
- // 映射为前端期望格式
334
- const mapped = similar.map(s => ({
335
- recipeName: s.title || s.file?.replace(/\.md$/, '') || '',
336
- similarity: s.similarity,
337
- file: s.file,
338
- }));
339
-
340
- res.json({ success: true, data: { similar: mapped } });
341
- } catch (err) {
342
- logger.warn('similarity search failed', { error: err.message });
343
- res.json({ success: true, data: { similar: [] } });
344
- }
345
- }));
392
+ })
393
+ );
346
394
 
347
395
  /**
348
396
  * POST /api/v1/search/xcode-simulate
349
397
  * Xcode 编辑器上下文模拟搜索
350
398
  * Body: { keyword, currentFile?, language?, limit? }
351
399
  */
352
- router.post('/xcode-simulate', asyncHandler(async (req, res) => {
353
- const { keyword, currentFile, language, limit = 10 } = req.body;
354
- if (!keyword) {
355
- throw new ValidationError('keyword is required');
356
- }
357
-
358
- const container = getServiceContainer();
359
- const pageSize = Math.min(limit || 10, 50);
360
- let results = [];
361
-
362
- // 复用 context-aware 搜索,注入 Xcode 上下文
363
- try {
364
- const searchEngine = container.get('searchEngine');
365
- const result = await searchEngine.search(keyword, {
366
- mode: 'bm25',
367
- limit: pageSize,
368
- rank: true,
369
- context: {
370
- intent: 'xcode-suggest',
371
- language: language || 'swift',
372
- currentFile,
373
- },
374
- });
375
- results = (result?.items || []).map(r => {
376
- let contentStr = '';
377
- try {
378
- const c = typeof r.content === 'string' && r.content.startsWith('{') ? JSON.parse(r.content) : (r.content || {});
379
- contentStr = c.pattern || c.markdown || c.code || '';
380
- } catch { contentStr = r.content || ''; }
381
- return {
382
- name: (r.title || r.id) + '.md',
383
- content: contentStr,
384
- similarity: r.score || 0,
385
- trigger: r.trigger || '',
386
- matchType: result.ranked ? 'ranked' : 'bm25',
387
- };
388
- });
389
- } catch (err) {
390
- logger.warn('xcode-simulate search failed', { error: err.message });
391
- }
400
+ router.post(
401
+ '/xcode-simulate',
402
+ asyncHandler(async (req, res) => {
403
+ const { keyword, currentFile, language, limit = 10 } = req.body;
404
+ if (!keyword) {
405
+ throw new ValidationError('keyword is required');
406
+ }
407
+
408
+ const container = getServiceContainer();
409
+ const pageSize = Math.min(limit || 10, 50);
410
+ let results = [];
411
+
412
+ // 复用 context-aware 搜索,注入 Xcode 上下文
413
+ try {
414
+ const searchEngine = container.get('searchEngine');
415
+ const result = await searchEngine.search(keyword, {
416
+ mode: 'bm25',
417
+ limit: pageSize,
418
+ rank: true,
419
+ context: {
420
+ intent: 'xcode-suggest',
421
+ language: language || 'swift',
422
+ currentFile,
423
+ },
424
+ });
425
+ results = (result?.items || []).map((r) => {
426
+ let contentStr = '';
427
+ try {
428
+ const c =
429
+ typeof r.content === 'string' && r.content.startsWith('{')
430
+ ? JSON.parse(r.content)
431
+ : r.content || {};
432
+ contentStr = c.pattern || c.markdown || c.code || '';
433
+ } catch {
434
+ contentStr = r.content || '';
435
+ }
436
+ return {
437
+ name: `${r.title || r.id}.md`,
438
+ content: contentStr,
439
+ similarity: r.score || 0,
440
+ trigger: r.trigger || '',
441
+ matchType: result.ranked ? 'ranked' : 'bm25',
442
+ };
443
+ });
444
+ } catch (err) {
445
+ logger.warn('xcode-simulate search failed', { error: err.message });
446
+ }
392
447
 
393
- res.json({ success: true, data: { results, total: results.length } });
394
- }));
448
+ res.json({ success: true, data: { results, total: results.length } });
449
+ })
450
+ );
395
451
 
396
452
  export default router;