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
@@ -82,9 +82,10 @@
82
82
  import fs from 'node:fs';
83
83
  import path from 'node:path';
84
84
  import { fileURLToPath } from 'node:url';
85
+ import Logger from '../../infrastructure/logging/Logger.js';
86
+ import { LanguageService } from '../../shared/LanguageService.js';
85
87
  import { findSimilarRecipes } from '../candidate/SimilarityService.js';
86
88
  import { CandidateGuardrail } from './CandidateGuardrail.js';
87
- import Logger from '../../infrastructure/logging/Logger.js';
88
89
 
89
90
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
90
91
 
@@ -103,23 +104,37 @@ const PROJECT_SKILLS_DIR = path.resolve(PROJECT_ROOT, '.autosnippet', 'skills');
103
104
  // ────────────────────────────────────────────────────────────
104
105
 
105
106
  /** 三方库路径识别(与 bootstrap/shared/third-party-filter.js 对齐) */
106
- const THIRD_PARTY_RE = /(?:^|\/)(?:Pods|Carthage|\.build\/checkouts|vendor|ThirdParty|External|Submodules|DerivedData|include|node_modules|build)\/|(?:^|\/)(?:Masonry|AFNetworking|SDWebImage|MJRefresh|MJExtension|YYKit|YYModel|Lottie|FLEX|IQKeyboardManager|MBProgressHUD|SVProgressHUD|SnapKit|Kingfisher|Alamofire|Moya|ReactiveObjC|ReactiveCocoa|RxSwift|RxCocoa|FMDB|Realm|Mantle|JSONModel|CocoaLumberjack|CocoaAsyncSocket|SocketRocket|GPUImage|FBSDKCore|FBSDKLogin|FlatBuffers|Protobuf|PromiseKit|Charts|Hero)\//i;
107
+ const THIRD_PARTY_RE =
108
+ /(?:^|\/)(?:Pods|Carthage|\.build\/checkouts|vendor|ThirdParty|External|Submodules|DerivedData|include|node_modules|build)\/|(?:^|\/)(?:Masonry|AFNetworking|SDWebImage|MJRefresh|MJExtension|YYKit|YYModel|Lottie|FLEX|IQKeyboardManager|MBProgressHUD|SVProgressHUD|SnapKit|Kingfisher|Alamofire|Moya|ReactiveObjC|ReactiveCocoa|RxSwift|RxCocoa|FMDB|Realm|Mantle|JSONModel|CocoaLumberjack|CocoaAsyncSocket|SocketRocket|GPUImage|FBSDKCore|FBSDKLogin|FlatBuffers|Protobuf|PromiseKit|Charts|Hero)\//i;
107
109
 
108
110
  /** 源码文件扩展名 */
109
111
  const SOURCE_EXT_RE = /\.(m|mm|swift|h|c|cpp|js|ts|jsx|tsx|py|rb|java|kt|go|rs)$/i;
110
112
 
111
113
  /** 声明行识别 — 用于对匹配行打分(与 bootstrap/shared/scanner.js 对齐) */
112
- const DECL_RE = /^\s*(@property\b|@interface\b|@protocol\b|@class\b|@synthesize\b|@dynamic\b|@end\b|NS_ASSUME_NONNULL|#import\b|#include\b|#define\b)/;
114
+ const DECL_RE =
115
+ /^\s*(@property\b|@interface\b|@protocol\b|@class\b|@synthesize\b|@dynamic\b|@end\b|NS_ASSUME_NONNULL|#import\b|#include\b|#define\b)/;
113
116
  const TYPE_DECL_RE = /^\s*\w[\w<>*\s]+[\s*]+_?\w+\s*;$/;
114
117
 
115
118
  function _scoreSearchLine(line) {
116
119
  const t = line.trim();
117
- if (DECL_RE.test(t)) return -2;
118
- if (TYPE_DECL_RE.test(t)) return -1;
119
- if (/^[-+]\s*\([^)]+\)\s*\w+[^{]*;\s*$/.test(t)) return -1;
120
- if (/\[.*\w+.*\]/.test(t)) return 2; // ObjC message send
121
- if (/\w+\s*\(/.test(t)) return 2; // function call
122
- if (/\^\s*[{(]/.test(t)) return 1; // block literal
120
+ if (DECL_RE.test(t)) {
121
+ return -2;
122
+ }
123
+ if (TYPE_DECL_RE.test(t)) {
124
+ return -1;
125
+ }
126
+ if (/^[-+]\s*\([^)]+\)\s*\w+[^{]*;\s*$/.test(t)) {
127
+ return -1;
128
+ }
129
+ if (/\[.*\w+.*\]/.test(t)) {
130
+ return 2; // ObjC message send
131
+ }
132
+ if (/\w+\s*\(/.test(t)) {
133
+ return 2; // function call
134
+ }
135
+ if (/\^\s*[{(]/.test(t)) {
136
+ return 1; // block literal
137
+ }
123
138
  return 0;
124
139
  }
125
140
 
@@ -133,7 +148,7 @@ async function _getProjectFiles(params, ctx) {
133
148
 
134
149
  let extFilter = null;
135
150
  if (fileFilter) {
136
- const exts = fileFilter.split(',').map(e => e.trim().replace(/^\./, ''));
151
+ const exts = fileFilter.split(',').map((e) => e.trim().replace(/^\./, ''));
137
152
  extFilter = new RegExp(`\\.(${exts.join('|')})$`, 'i');
138
153
  }
139
154
 
@@ -142,11 +157,18 @@ async function _getProjectFiles(params, ctx) {
142
157
  let skippedThirdParty = 0;
143
158
 
144
159
  if (fileCache && Array.isArray(fileCache)) {
145
- files = fileCache.filter(f => {
160
+ files = fileCache.filter((f) => {
146
161
  const p = f.relativePath || f.path || '';
147
- if (THIRD_PARTY_RE.test(p)) { skippedThirdParty++; return false; }
148
- if (extFilter && !extFilter.test(p)) return false;
149
- if (!SOURCE_EXT_RE.test(p)) return false;
162
+ if (THIRD_PARTY_RE.test(p)) {
163
+ skippedThirdParty++;
164
+ return false;
165
+ }
166
+ if (extFilter && !extFilter.test(p)) {
167
+ return false;
168
+ }
169
+ if (!SOURCE_EXT_RE.test(p)) {
170
+ return false;
171
+ }
150
172
  return true;
151
173
  });
152
174
  } else {
@@ -158,25 +180,65 @@ async function _getProjectFiles(params, ctx) {
158
180
  for (const entry of entries) {
159
181
  const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
160
182
  const fullPath = path.join(dir, entry.name);
161
- const isDir = entry.isDirectory() || (entry.isSymbolicLink() && (() => { try { return fs.statSync(fullPath).isDirectory(); } catch { return false; } })());
162
- const isFile = entry.isFile() || (entry.isSymbolicLink() && (() => { try { return fs.statSync(fullPath).isFile(); } catch { return false; } })());
183
+ const isDir =
184
+ entry.isDirectory() ||
185
+ (entry.isSymbolicLink() &&
186
+ (() => {
187
+ try {
188
+ return fs.statSync(fullPath).isDirectory();
189
+ } catch {
190
+ return false;
191
+ }
192
+ })());
193
+ const isFile =
194
+ entry.isFile() ||
195
+ (entry.isSymbolicLink() &&
196
+ (() => {
197
+ try {
198
+ return fs.statSync(fullPath).isFile();
199
+ } catch {
200
+ return false;
201
+ }
202
+ })());
163
203
  if (isDir) {
164
- if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'build') continue;
165
- if (THIRD_PARTY_RE.test(relPath + '/')) { skippedThirdParty++; continue; }
204
+ if (
205
+ entry.name.startsWith('.') ||
206
+ entry.name === 'node_modules' ||
207
+ entry.name === 'build'
208
+ ) {
209
+ continue;
210
+ }
211
+ if (THIRD_PARTY_RE.test(`${relPath}/`)) {
212
+ skippedThirdParty++;
213
+ continue;
214
+ }
166
215
  walk(fullPath, relPath);
167
216
  } else if (isFile) {
168
- if (THIRD_PARTY_RE.test(relPath)) { skippedThirdParty++; continue; }
169
- if (!SOURCE_EXT_RE.test(entry.name)) continue;
170
- if (extFilter && !extFilter.test(entry.name)) continue;
217
+ if (THIRD_PARTY_RE.test(relPath)) {
218
+ skippedThirdParty++;
219
+ continue;
220
+ }
221
+ if (!SOURCE_EXT_RE.test(entry.name)) {
222
+ continue;
223
+ }
224
+ if (extFilter && !extFilter.test(entry.name)) {
225
+ continue;
226
+ }
171
227
  try {
172
228
  const stat = fs.statSync(fullPath);
173
- if (stat.size > MAX_FILE_SIZE) continue;
229
+ if (stat.size > MAX_FILE_SIZE) {
230
+ continue;
231
+ }
174
232
  const content = fs.readFileSync(fullPath, 'utf-8');
175
233
  files.push({ relativePath: relPath, content, name: entry.name });
176
- } catch { /* skip unreadable files */ }
234
+ } catch {
235
+ /* skip unreadable files */
236
+ }
177
237
  }
178
238
  }
179
- } catch { /* skip inaccessible dirs */ }
239
+ } catch {
240
+ /* skip inaccessible dirs */
241
+ }
180
242
  };
181
243
  walk(projectRoot);
182
244
  }
@@ -186,26 +248,34 @@ async function _getProjectFiles(params, ctx) {
186
248
 
187
249
  const searchProjectCode = {
188
250
  name: 'search_project_code',
189
- description: '在用户项目源码中搜索指定模式。返回匹配的代码片段及上下文。' +
251
+ description:
252
+ '在用户项目源码中搜索指定模式。返回匹配的代码片段及上下文。' +
190
253
  '自动过滤三方库代码(Pods/Carthage/node_modules),优先返回实际使用行而非声明行。' +
191
254
  '适用场景:验证代码模式存在性、查找更多项目示例、理解项目中某个 API 的用法。' +
192
255
  '批量搜索:传入 patterns 数组可一次搜索多个关键词(每个关键词独立返回结果),减少工具调用次数。',
193
256
  parameters: {
194
257
  type: 'object',
195
258
  properties: {
196
- pattern: { type: 'string', description: '搜索词或正则表达式(单个搜索时使用)' },
197
- patterns: { type: 'array', items: { type: 'string' }, description: '批量搜索:多个搜索词数组,如 ["methodA", "methodB", "classC"]。与 pattern 互斥,优先使用 patterns。' },
198
- isRegex: { type: 'boolean', description: '是否为正则表达式,默认 false' },
199
- fileFilter: { type: 'string', description: '文件扩展名过滤,如 ".m,.swift"' },
259
+ pattern: { type: 'string', description: '搜索词或正则表达式(单个搜索时使用)' },
260
+ patterns: {
261
+ type: 'array',
262
+ items: { type: 'string' },
263
+ description:
264
+ '批量搜索:多个搜索词数组,如 ["methodA", "methodB", "classC"]。与 pattern 互斥,优先使用 patterns。',
265
+ },
266
+ isRegex: { type: 'boolean', description: '是否为正则表达式,默认 false' },
267
+ fileFilter: { type: 'string', description: '文件扩展名过滤,如 ".m,.swift"' },
200
268
  contextLines: { type: 'number', description: '匹配行前后的上下文行数,默认 3' },
201
- maxResults: { type: 'number', description: '每个 pattern 的最大返回结果数,默认 5' },
269
+ maxResults: { type: 'number', description: '每个 pattern 的最大返回结果数,默认 5' },
202
270
  },
203
271
  required: [],
204
272
  },
205
273
  handler: async (params, ctx) => {
206
274
  // ── 去重缓存初始化 ──
207
275
  const state = ctx._sharedState || ctx;
208
- if (!state._searchCache) state._searchCache = new Map();
276
+ if (!state._searchCache) {
277
+ state._searchCache = new Map();
278
+ }
209
279
 
210
280
  // ── 批量模式:patterns 数组 ──
211
281
  if (Array.isArray(params.patterns) && params.patterns.length > 0) {
@@ -222,7 +292,7 @@ const searchProjectCode = {
222
292
  }
223
293
  const sub = await searchProjectCode.handler(
224
294
  { ...params, pattern: p, patterns: undefined },
225
- ctx,
295
+ ctx
226
296
  );
227
297
  const entry = { matches: sub.matches || [], total: sub.total || 0 };
228
298
  state._searchCache.set(cacheKey, entry);
@@ -232,30 +302,46 @@ const searchProjectCode = {
232
302
  batchResults,
233
303
  patternsSearched: batchPatterns.length,
234
304
  searchedFiles: (await _getProjectFiles(params, ctx)).files.length,
235
- ...(dedupCount > 0 ? { _deduped: dedupCount, hint: `${dedupCount} 个 pattern 命中缓存,请避免重复搜索相同关键词。` } : {}),
305
+ ...(dedupCount > 0
306
+ ? {
307
+ _deduped: dedupCount,
308
+ hint: `${dedupCount} 个 pattern 命中缓存,请避免重复搜索相同关键词。`,
309
+ }
310
+ : {}),
236
311
  };
237
312
  }
238
313
 
239
314
  // 兼容 AI 传 "query" / "search" / "keyword" 替代 "pattern"
240
- const pattern = params.pattern || params.query || params.search || params.keyword || params.search_query;
241
- const { isRegex = false, fileFilter, contextLines = 3, maxResults = 5 } = params;
242
- const projectRoot = ctx.projectRoot || process.cwd();
315
+ const pattern =
316
+ params.pattern || params.query || params.search || params.keyword || params.search_query;
317
+ const { isRegex = false, contextLines = 3, maxResults = 5 } = params;
318
+ const _projectRoot = ctx.projectRoot || process.cwd();
243
319
 
244
320
  if (!pattern || typeof pattern !== 'string') {
245
- return { error: '参数错误: 请提供 pattern(搜索关键词或正则表达式)或 patterns 数组', matches: [], total: 0 };
321
+ return {
322
+ error: '参数错误: 请提供 pattern(搜索关键词或正则表达式)或 patterns 数组',
323
+ matches: [],
324
+ total: 0,
325
+ };
246
326
  }
247
327
 
248
328
  // ── 单 pattern 去重检查 ──
249
329
  const cacheKey = `${pattern}|${params.isRegex || false}|${params.fileFilter || ''}`;
250
330
  if (state._searchCache.has(cacheKey)) {
251
331
  const cached = state._searchCache.get(cacheKey);
252
- return { ...cached, _cached: true, hint: `⚠ 已搜索过 "${pattern}",返回缓存结果。请搜索不同的关键词以获取新信息。` };
332
+ return {
333
+ ...cached,
334
+ _cached: true,
335
+ hint: `⚠ 已搜索过 "${pattern}",返回缓存结果。请搜索不同的关键词以获取新信息。`,
336
+ };
253
337
  }
254
338
 
255
339
  // 构建搜索正则
256
340
  let searchRe;
257
341
  try {
258
- searchRe = isRegex ? new RegExp(pattern, 'gi') : new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
342
+ searchRe = isRegex
343
+ ? new RegExp(pattern, 'gi')
344
+ : new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
259
345
  } catch (err) {
260
346
  return { error: `Invalid pattern: ${err.message}`, matches: [], total: 0 };
261
347
  }
@@ -267,17 +353,23 @@ const searchProjectCode = {
267
353
  let total = 0;
268
354
 
269
355
  for (const f of files) {
270
- if (!f.content) continue;
356
+ if (!f.content) {
357
+ continue;
358
+ }
271
359
  // 快速预过滤
272
360
  searchRe.lastIndex = 0;
273
- if (!searchRe.test(f.content)) continue;
361
+ if (!searchRe.test(f.content)) {
362
+ continue;
363
+ }
274
364
 
275
365
  const lines = f.content.split('\n');
276
366
  searchRe.lastIndex = 0;
277
367
 
278
368
  for (let i = 0; i < lines.length; i++) {
279
369
  searchRe.lastIndex = 0;
280
- if (!searchRe.test(lines[i])) continue;
370
+ if (!searchRe.test(lines[i])) {
371
+ continue;
372
+ }
281
373
  total++;
282
374
 
283
375
  if (matches.length < maxResults) {
@@ -307,14 +399,16 @@ const searchProjectCode = {
307
399
  total,
308
400
  searchedFiles: files.length,
309
401
  skippedThirdParty,
310
- ...((() => {
402
+ ...(() => {
311
403
  // P2.2: 搜索超限提示 — 引导使用 AST 工具
312
404
  state._searchCallCount = (state._searchCallCount || 0) + 1;
313
405
  if (state._searchCallCount > 12 && ctx.source === 'system') {
314
- return { hint: `💡 你已搜索 ${state._searchCallCount} 次。考虑使用 get_class_info / get_class_hierarchy / get_project_overview 获取结构化信息,效率更高。` };
406
+ return {
407
+ hint: `💡 你已搜索 ${state._searchCallCount} 次。考虑使用 get_class_info / get_class_hierarchy / get_project_overview 获取结构化信息,效率更高。`,
408
+ };
315
409
  }
316
410
  return {};
317
- })()),
411
+ })(),
318
412
  };
319
413
 
320
414
  // 缓存搜索结果
@@ -329,24 +423,34 @@ const searchProjectCode = {
329
423
  // ────────────────────────────────────────────────────────────
330
424
  const readProjectFile = {
331
425
  name: 'read_project_file',
332
- description: '读取项目中指定文件的内容(部分或全部)。' +
426
+ description:
427
+ '读取项目中指定文件的内容(部分或全部)。' +
333
428
  '通常在 search_project_code 找到匹配后使用,获取更完整的上下文。' +
334
429
  '批量读取:传入 filePaths 数组可一次读取多个文件,减少工具调用次数。',
335
430
  parameters: {
336
431
  type: 'object',
337
432
  properties: {
338
- filePath: { type: 'string', description: '相对于项目根目录的文件路径(单个文件时使用)' },
339
- filePaths: { type: 'array', items: { type: 'string' }, description: '批量读取:多个文件路径数组。与 filePath 互斥,优先使用 filePaths。' },
340
- startLine: { type: 'number', description: '起始行号(1-based),默认 1' },
341
- endLine: { type: 'number', description: '结束行号(1-based),默认文件末尾' },
342
- maxLines: { type: 'number', description: '最大返回行数,默认 200(批量模式下每个文件最多 100 行)' },
433
+ filePath: { type: 'string', description: '相对于项目根目录的文件路径(单个文件时使用)' },
434
+ filePaths: {
435
+ type: 'array',
436
+ items: { type: 'string' },
437
+ description: '批量读取:多个文件路径数组。与 filePath 互斥,优先使用 filePaths。',
438
+ },
439
+ startLine: { type: 'number', description: '起始行号(1-based),默认 1' },
440
+ endLine: { type: 'number', description: '结束行号(1-based),默认文件末尾' },
441
+ maxLines: {
442
+ type: 'number',
443
+ description: '最大返回行数,默认 200(批量模式下每个文件最多 100 行)',
444
+ },
343
445
  },
344
446
  required: [],
345
447
  },
346
448
  handler: async (params, ctx) => {
347
449
  // ── 去重缓存初始化 ──
348
450
  const state = ctx._sharedState || ctx;
349
- if (!state._readCache) state._readCache = new Map();
451
+ if (!state._readCache) {
452
+ state._readCache = new Map();
453
+ }
350
454
 
351
455
  // ── 批量模式:filePaths 数组 ──
352
456
  if (Array.isArray(params.filePaths) && params.filePaths.length > 0) {
@@ -361,23 +465,38 @@ const readProjectFile = {
361
465
  continue;
362
466
  }
363
467
  const sub = await readProjectFile.handler(
364
- { ...params, filePath: fp, filePaths: undefined, maxLines: Math.min(params.maxLines || 100, 100) },
365
- ctx,
468
+ {
469
+ ...params,
470
+ filePath: fp,
471
+ filePaths: undefined,
472
+ maxLines: Math.min(params.maxLines || 100, 100),
473
+ },
474
+ ctx
366
475
  );
367
- const entry = sub.error ? { error: sub.error } : { content: sub.content, totalLines: sub.totalLines, language: sub.language };
476
+ const entry = sub.error
477
+ ? { error: sub.error }
478
+ : { content: sub.content, totalLines: sub.totalLines, language: sub.language };
368
479
  state._readCache.set(cacheKey, entry);
369
480
  batchResults[fp] = entry;
370
481
  }
371
482
  return {
372
483
  batchResults,
373
484
  filesRead: batchPaths.length,
374
- ...(dedupCount > 0 ? { _deduped: dedupCount, hint: `${dedupCount} 个文件命中缓存,请避免重复读取相同文件。` } : {}),
485
+ ...(dedupCount > 0
486
+ ? { _deduped: dedupCount, hint: `${dedupCount} 个文件命中缓存,请避免重复读取相同文件。` }
487
+ : {}),
375
488
  };
376
489
  }
377
490
 
378
491
  // 兼容各种参数名变体 (ToolRegistry 层已做 snake→camel 归一化,
379
492
  // 这里兜底处理漏网之鱼)
380
- const filePath = params.filePath || params.path || params.file_path || params.filepath || params.file || params.filename;
493
+ const filePath =
494
+ params.filePath ||
495
+ params.path ||
496
+ params.file_path ||
497
+ params.filepath ||
498
+ params.file ||
499
+ params.filename;
381
500
  const { startLine = 1, maxLines = 200 } = params;
382
501
  const projectRoot = ctx.projectRoot || process.cwd();
383
502
 
@@ -388,7 +507,11 @@ const readProjectFile = {
388
507
  // ── 单文件去重检查 ──
389
508
  const readCacheKey = `${filePath}|${startLine}|${params.endLine || ''}|${maxLines}`;
390
509
  if (state._readCache.has(readCacheKey)) {
391
- return { ...state._readCache.get(readCacheKey), _cached: true, hint: `⚠ 已读取过该文件相同行范围,返回缓存结果。如需其他行范围请指定不同的 startLine/endLine。` };
510
+ return {
511
+ ...state._readCache.get(readCacheKey),
512
+ _cached: true,
513
+ hint: `⚠ 已读取过该文件相同行范围,返回缓存结果。如需其他行范围请指定不同的 startLine/endLine。`,
514
+ };
392
515
  }
393
516
 
394
517
  // 安全检查: 禁止路径遍历
@@ -402,11 +525,14 @@ const readProjectFile = {
402
525
  let content = null;
403
526
 
404
527
  if (fileCache && Array.isArray(fileCache)) {
405
- const cached = fileCache.find(f =>
406
- (f.relativePath || f.path || '') === filePath ||
407
- (f.relativePath || f.path || '') === normalized
528
+ const cached = fileCache.find(
529
+ (f) =>
530
+ (f.relativePath || f.path || '') === filePath ||
531
+ (f.relativePath || f.path || '') === normalized
408
532
  );
409
- if (cached) content = cached.content;
533
+ if (cached) {
534
+ content = cached.content;
535
+ }
410
536
  }
411
537
 
412
538
  // 降级: 从磁盘读取
@@ -438,8 +564,7 @@ const readProjectFile = {
438
564
 
439
565
  // 推断语言
440
566
  const ext = path.extname(filePath).toLowerCase();
441
- const langMap = { '.m': 'objectivec', '.mm': 'objectivec', '.h': 'objectivec', '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.py': 'python', '.java': 'java', '.kt': 'kotlin', '.go': 'go', '.rs': 'rust', '.rb': 'ruby' };
442
- const language = langMap[ext] || 'unknown';
567
+ const language = LanguageService.langFromExt(ext);
443
568
 
444
569
  const readResult = {
445
570
  filePath,
@@ -462,14 +587,18 @@ const readProjectFile = {
462
587
  // ────────────────────────────────────────────────────────────
463
588
  const listProjectStructure = {
464
589
  name: 'list_project_structure',
465
- description: '列出项目目录结构和文件统计信息。不读取文件内容,只返回目录树和元数据。' +
590
+ description:
591
+ '列出项目目录结构和文件统计信息。不读取文件内容,只返回目录树和元数据。' +
466
592
  '适用场景:了解项目整体布局、识别关键目录、规划探索路径。',
467
593
  parameters: {
468
594
  type: 'object',
469
595
  properties: {
470
- directory: { type: 'string', description: '相对于项目根目录的子目录路径,默认根目录' },
471
- depth: { type: 'number', description: '目录展开深度,默认 3' },
472
- includeStats: { type: 'boolean', description: '是否包含文件统计(语言分布、行数),默认 true' },
596
+ directory: { type: 'string', description: '相对于项目根目录的子目录路径,默认根目录' },
597
+ depth: { type: 'number', description: '目录展开深度,默认 3' },
598
+ includeStats: {
599
+ type: 'boolean',
600
+ description: '是否包含文件统计(语言分布、行数),默认 true',
601
+ },
473
602
  },
474
603
  },
475
604
  handler: async (params, ctx) => {
@@ -491,33 +620,36 @@ const listProjectStructure = {
491
620
  const treeLines = [];
492
621
  const stats = { totalFiles: 0, totalDirs: 0, byLanguage: {}, totalLines: 0 };
493
622
 
494
- const LANG_MAP = {
495
- '.m': 'Objective-C', '.mm': 'Objective-C++', '.h': 'Header',
496
- '.swift': 'Swift', '.js': 'JavaScript', '.ts': 'TypeScript',
497
- '.jsx': 'JSX', '.tsx': 'TSX', '.py': 'Python', '.java': 'Java',
498
- '.kt': 'Kotlin', '.go': 'Go', '.rs': 'Rust', '.rb': 'Ruby',
499
- '.c': 'C', '.cpp': 'C++',
500
- };
501
-
502
623
  const walk = (dir, relBase, currentDepth, prefix) => {
503
- if (currentDepth > depth) return;
624
+ if (currentDepth > depth) {
625
+ return;
626
+ }
504
627
  let entries;
505
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
506
- catch { return; }
628
+ try {
629
+ entries = fs.readdirSync(dir, { withFileTypes: true });
630
+ } catch {
631
+ return;
632
+ }
507
633
 
508
634
  // 排序: 目录在前,文件在后
509
635
  entries.sort((a, b) => {
510
636
  const aIsDir = a.isDirectory();
511
637
  const bIsDir = b.isDirectory();
512
- if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
638
+ if (aIsDir !== bIsDir) {
639
+ return aIsDir ? -1 : 1;
640
+ }
513
641
  return a.name.localeCompare(b.name);
514
642
  });
515
643
 
516
644
  // 过滤隐藏和三方
517
- entries = entries.filter(e => {
518
- if (e.name.startsWith('.')) return false;
645
+ entries = entries.filter((e) => {
646
+ if (e.name.startsWith('.')) {
647
+ return false;
648
+ }
519
649
  const rel = relBase ? `${relBase}/${e.name}` : e.name;
520
- if (THIRD_PARTY_RE.test(rel + '/')) return false;
650
+ if (THIRD_PARTY_RE.test(`${rel}/`)) {
651
+ return false;
652
+ }
521
653
  return true;
522
654
  });
523
655
 
@@ -532,7 +664,11 @@ const listProjectStructure = {
532
664
  if (entry.isDirectory()) {
533
665
  // 计算子文件数
534
666
  let childCount = 0;
535
- try { childCount = fs.readdirSync(fullPath).length; } catch { /* skip */ }
667
+ try {
668
+ childCount = fs.readdirSync(fullPath).length;
669
+ } catch {
670
+ /* skip */
671
+ }
536
672
  treeLines.push(`${prefix}${connector}${entry.name}/ (${childCount})`);
537
673
  stats.totalDirs++;
538
674
  walk(fullPath, rel, currentDepth + 1, childPrefix);
@@ -549,10 +685,12 @@ const listProjectStructure = {
549
685
  lineCount = content.split('\n').length;
550
686
  stats.totalLines += lineCount;
551
687
  }
552
- } catch { /* skip */ }
688
+ } catch {
689
+ /* skip */
690
+ }
553
691
  }
554
- const lang = LANG_MAP[ext];
555
- if (lang) {
692
+ const lang = LanguageService.displayNameFromExt(ext);
693
+ if (lang !== ext) {
556
694
  stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
557
695
  }
558
696
  const sizeLabel = size > 1024 ? `${(size / 1024).toFixed(0)}KB` : `${size}B`;
@@ -580,31 +718,67 @@ const listProjectStructure = {
580
718
  /** 语言相关的声明提取正则 */
581
719
  const SUMMARY_EXTRACTORS = {
582
720
  objectivec: {
583
- imports: /^\s*(#import\s+.+|#include\s+.+|@import\s+\w+;)/gm,
584
- declarations: /^\s*(@interface\s+\w+[\s:(].*|@protocol\s+\w+[\s<(].*|@implementation\s+\w+|typedef\s+(?:NS_ENUM|NS_OPTIONS)\s*\([^)]+\)\s*\{?)/gm,
585
- methods: /^\s*[-+]\s*\([^)]+\)\s*[^;{]+/gm,
586
- properties: /^\s*@property\s*\([^)]*\)\s*[^;]+;/gm,
721
+ imports: /^\s*(#import\s+.+|#include\s+.+|@import\s+\w+;)/gm,
722
+ declarations:
723
+ /^\s*(@interface\s+\w+[\s:(].*|@protocol\s+\w+[\s<(].*|@implementation\s+\w+|typedef\s+(?:NS_ENUM|NS_OPTIONS)\s*\([^)]+\)\s*\{?)/gm,
724
+ methods: /^\s*[-+]\s*\([^)]+\)\s*[^;{]+/gm,
725
+ properties: /^\s*@property\s*\([^)]*\)\s*[^;]+;/gm,
587
726
  },
588
727
  swift: {
589
- imports: /^\s*import\s+\w+/gm,
590
- declarations: /^\s*(?:open|public|internal|fileprivate|private|final)?\s*(?:class|struct|enum|protocol|actor|extension)\s+\w+[^{]*/gm,
591
- methods: /^\s*(?:open|public|internal|fileprivate|private|override|static|class)?\s*func\s+\w+[^{]*/gm,
592
- properties: /^\s*(?:open|public|internal|fileprivate|private|static|class|lazy)?\s*(?:var|let)\s+\w+\s*:\s*[^={\n]+/gm,
728
+ imports: /^\s*import\s+\w+/gm,
729
+ declarations:
730
+ /^\s*(?:open|public|internal|fileprivate|private|final)?\s*(?:class|struct|enum|protocol|actor|extension)\s+\w+[^{]*/gm,
731
+ methods:
732
+ /^\s*(?:open|public|internal|fileprivate|private|override|static|class)?\s*func\s+\w+[^{]*/gm,
733
+ properties:
734
+ /^\s*(?:open|public|internal|fileprivate|private|static|class|lazy)?\s*(?:var|let)\s+\w+\s*:\s*[^={\n]+/gm,
593
735
  },
594
736
  javascript: {
595
- imports: /^\s*(?:import\s+.+from\s+['"].+['"]|const\s+\{?\s*\w+.*\}?\s*=\s*require\s*\(.+\))/gm,
737
+ imports: /^\s*(?:import\s+.+from\s+['"].+['"]|const\s+\{?\s*\w+.*\}?\s*=\s*require\s*\(.+\))/gm,
596
738
  declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|function|const|let|var)\s+\w+/gm,
597
- methods: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*\{/gm,
739
+ methods: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*\{/gm,
598
740
  },
599
741
  typescript: {
600
- imports: /^\s*import\s+.+from\s+['"].+['"]/gm,
601
- declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|interface|type|enum|function|const|let|var|abstract\s+class)\s+\w+/gm,
602
- methods: /^\s*(?:async\s+)?(?:static\s+)?(?:public|private|protected)?\s*(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*[:{]/gm,
742
+ imports: /^\s*import\s+.+from\s+['"].+['"]/gm,
743
+ declarations:
744
+ /^\s*(?:export\s+)?(?:default\s+)?(?:class|interface|type|enum|function|const|let|var|abstract\s+class)\s+\w+/gm,
745
+ methods:
746
+ /^\s*(?:async\s+)?(?:static\s+)?(?:public|private|protected)?\s*(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*[:{]/gm,
603
747
  },
604
748
  python: {
605
- imports: /^\s*(?:import\s+\w+|from\s+\w+\s+import\s+.+)/gm,
749
+ imports: /^\s*(?:import\s+\w+|from\s+\w+\s+import\s+.+)/gm,
606
750
  declarations: /^\s*class\s+\w+[^:]*:/gm,
607
- methods: /^\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)/gm,
751
+ methods: /^\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)/gm,
752
+ },
753
+ go: {
754
+ imports: /^\s*(?:import\s+"[^"]+"|import\s+\w+\s+"[^"]+")/gm,
755
+ declarations:
756
+ /^\s*(?:type\s+\w+\s+(?:struct|interface|func)\b.*)/gm,
757
+ methods:
758
+ /^\s*func\s+(?:\(\s*\w+\s+\*?\w+\s*\)\s+)?\w+\s*\([^)]*\)[^{]*/gm,
759
+ },
760
+ java: {
761
+ imports: /^\s*import\s+(?:static\s+)?[\w.]+\*?;/gm,
762
+ declarations:
763
+ /^\s*(?:public|private|protected)?\s*(?:abstract|final|static)?\s*(?:class|interface|enum|record|@interface)\s+\w+/gm,
764
+ methods:
765
+ /^\s*(?:public|private|protected)?\s*(?:abstract|static|final|synchronized|default)?\s*(?:<[^>]+>\s+)?\w[\w<>\[\],\s]*\s+\w+\s*\([^)]*\)/gm,
766
+ },
767
+ kotlin: {
768
+ imports: /^\s*import\s+[\w.]+/gm,
769
+ declarations:
770
+ /^\s*(?:open|abstract|data|sealed|inner|value|inline)?\s*(?:class|interface|object|enum\s+class|fun\s+interface)\s+\w+/gm,
771
+ methods:
772
+ /^\s*(?:override\s+)?(?:suspend\s+)?(?:fun|val|var)\s+(?:<[^>]+>\s+)?\w+/gm,
773
+ },
774
+ dart: {
775
+ imports: /^\s*import\s+['"][^'"]+['"];?/gm,
776
+ declarations:
777
+ /^\s*(?:abstract\s+|sealed\s+)?(?:class|mixin|extension|enum|typedef)\s+\w+[^{]*/gm,
778
+ methods:
779
+ /^\s*(?:@override\s+)?(?:static\s+)?(?:Future|Stream|void|\w[\w<>?]*)?\s+\w+\s*\([^)]*\)/gm,
780
+ properties:
781
+ /^\s*(?:static\s+)?(?:final\s+|late\s+|const\s+)?(?:\w[\w<>?]*)\s+\w+\s*[;=]/gm,
608
782
  },
609
783
  };
610
784
  // Alias variants
@@ -614,7 +788,8 @@ SUMMARY_EXTRACTORS.tsx = SUMMARY_EXTRACTORS.typescript;
614
788
 
615
789
  const getFileSummary = {
616
790
  name: 'get_file_summary',
617
- description: '获取文件的结构摘要(导入、声明、方法签名),不包含实现代码。' +
791
+ description:
792
+ '获取文件的结构摘要(导入、声明、方法签名),不包含实现代码。' +
618
793
  '比 read_project_file 更轻量,适合快速了解文件角色和 API。',
619
794
  parameters: {
620
795
  type: 'object',
@@ -642,11 +817,14 @@ const getFileSummary = {
642
817
  let content = null;
643
818
 
644
819
  if (fileCache && Array.isArray(fileCache)) {
645
- const cached = fileCache.find(f =>
646
- (f.relativePath || f.path || '') === filePath ||
647
- (f.relativePath || f.path || '') === normalized
820
+ const cached = fileCache.find(
821
+ (f) =>
822
+ (f.relativePath || f.path || '') === filePath ||
823
+ (f.relativePath || f.path || '') === normalized
648
824
  );
649
- if (cached) content = cached.content;
825
+ if (cached) {
826
+ content = cached.content;
827
+ }
650
828
  }
651
829
 
652
830
  if (content === null) {
@@ -663,8 +841,7 @@ const getFileSummary = {
663
841
 
664
842
  // 推断语言
665
843
  const ext = path.extname(filePath).toLowerCase();
666
- const langMap = { '.m': 'objectivec', '.mm': 'objectivec++', '.h': 'objectivec', '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.jsx': 'jsx', '.tsx': 'tsx', '.py': 'python', '.java': 'unknown', '.kt': 'unknown', '.go': 'unknown', '.rs': 'unknown', '.rb': 'unknown' };
667
- const language = langMap[ext] || 'unknown';
844
+ const language = LanguageService.langFromExt(ext);
668
845
  const extractor = SUMMARY_EXTRACTORS[language];
669
846
 
670
847
  const result = {
@@ -694,10 +871,18 @@ const getFileSummary = {
694
871
  return matches;
695
872
  };
696
873
 
697
- if (extractor.imports) result.imports = extract(extractor.imports);
698
- if (extractor.declarations) result.declarations = extract(extractor.declarations);
699
- if (extractor.methods) result.methods = extract(extractor.methods).slice(0, 50); // 限制数量
700
- if (extractor.properties) result.properties = extract(extractor.properties).slice(0, 30);
874
+ if (extractor.imports) {
875
+ result.imports = extract(extractor.imports);
876
+ }
877
+ if (extractor.declarations) {
878
+ result.declarations = extract(extractor.declarations);
879
+ }
880
+ if (extractor.methods) {
881
+ result.methods = extract(extractor.methods).slice(0, 50); // 限制数量
882
+ }
883
+ if (extractor.properties) {
884
+ result.properties = extract(extractor.properties).slice(0, 30);
885
+ }
701
886
 
702
887
  return result;
703
888
  },
@@ -708,14 +893,15 @@ const getFileSummary = {
708
893
  // ────────────────────────────────────────────────────────────
709
894
  const semanticSearchCode = {
710
895
  name: 'semantic_search_code',
711
- description: '在知识库中进行语义搜索。使用自然语言描述你要查找的代码模式或概念,' +
896
+ description:
897
+ '在知识库中进行语义搜索。使用自然语言描述你要查找的代码模式或概念,' +
712
898
  '返回语义最相关的知识条目。比关键词搜索更适合模糊/概念性查询。' +
713
899
  '示例: "网络请求的错误处理策略"、"线程安全的单例实现"',
714
900
  parameters: {
715
901
  type: 'object',
716
902
  properties: {
717
- query: { type: 'string', description: '自然语言搜索查询' },
718
- topK: { type: 'number', description: '返回结果数量,默认 5' },
903
+ query: { type: 'string', description: '自然语言搜索查询' },
904
+ topK: { type: 'number', description: '返回结果数量,默认 5' },
719
905
  category: { type: 'string', description: '按分类过滤 (View/Service/Network/Model 等)' },
720
906
  language: { type: 'string', description: '按语言过滤 (swift/objectivec 等)' },
721
907
  },
@@ -734,18 +920,23 @@ const semanticSearchCode = {
734
920
  let searchEngine = null;
735
921
  try {
736
922
  searchEngine = ctx.container?.get('searchEngine');
737
- } catch { /* not available */ }
923
+ } catch {
924
+ /* not available */
925
+ }
738
926
 
739
927
  if (!searchEngine) {
740
928
  // 尝试获取 VectorStore 直接搜索
741
929
  let vectorStore = null;
742
930
  try {
743
931
  vectorStore = ctx.container?.get('vectorStore');
744
- } catch { /* not available */ }
932
+ } catch {
933
+ /* not available */
934
+ }
745
935
 
746
936
  if (!vectorStore) {
747
937
  return {
748
- error: '语义搜索不可用: SearchEngine 和 VectorStore 均未初始化。可使用 search_project_code 进行关键词搜索替代。',
938
+ error:
939
+ '语义搜索不可用: SearchEngine 和 VectorStore 均未初始化。可使用 search_project_code 进行关键词搜索替代。',
749
940
  fallbackTool: 'search_project_code',
750
941
  };
751
942
  }
@@ -754,20 +945,26 @@ const semanticSearchCode = {
754
945
  let aiProvider = null;
755
946
  try {
756
947
  aiProvider = ctx.container?.get('aiProvider');
757
- } catch { /* not available */ }
948
+ } catch {
949
+ /* not available */
950
+ }
758
951
 
759
952
  if (!aiProvider || typeof aiProvider.generateEmbedding !== 'function') {
760
953
  // 向量搜索需要 embedding,降级到关键词匹配
761
954
  const filter = {};
762
- if (category) filter.category = category;
763
- if (language) filter.language = language;
955
+ if (category) {
956
+ filter.category = category;
957
+ }
958
+ if (language) {
959
+ filter.language = language;
960
+ }
764
961
 
765
962
  const results = await vectorStore.hybridSearch([], query, { topK, filter });
766
963
  return {
767
964
  mode: 'keyword-fallback',
768
965
  query,
769
966
  message: 'AI Provider 不支持 embedding,已降级到关键词匹配',
770
- results: results.map(r => ({
967
+ results: results.map((r) => ({
771
968
  id: r.item.id,
772
969
  content: (r.item.content || '').slice(0, 500),
773
970
  score: Math.round(r.score * 100) / 100,
@@ -780,14 +977,18 @@ const semanticSearchCode = {
780
977
  try {
781
978
  const embedding = await aiProvider.generateEmbedding(query);
782
979
  const filter = {};
783
- if (category) filter.category = category;
784
- if (language) filter.language = language;
980
+ if (category) {
981
+ filter.category = category;
982
+ }
983
+ if (language) {
984
+ filter.language = language;
985
+ }
785
986
 
786
987
  const results = await vectorStore.hybridSearch(embedding, query, { topK, filter });
787
988
  return {
788
989
  mode: 'vector',
789
990
  query,
790
- results: results.map(r => ({
991
+ results: results.map((r) => ({
791
992
  id: r.item.id,
792
993
  content: (r.item.content || '').slice(0, 500),
793
994
  score: Math.round(r.score * 100) / 100,
@@ -811,8 +1012,12 @@ const semanticSearchCode = {
811
1012
  const actualMode = result?.mode || 'bm25';
812
1013
 
813
1014
  // 应用过滤
814
- if (category) items = items.filter(i => (i.category || '').toLowerCase() === category.toLowerCase());
815
- if (language) items = items.filter(i => (i.language || '').toLowerCase() === language.toLowerCase());
1015
+ if (category) {
1016
+ items = items.filter((i) => (i.category || '').toLowerCase() === category.toLowerCase());
1017
+ }
1018
+ if (language) {
1019
+ items = items.filter((i) => (i.language || '').toLowerCase() === language.toLowerCase());
1020
+ }
816
1021
  items = items.slice(0, topK);
817
1022
 
818
1023
  return {
@@ -820,7 +1025,7 @@ const semanticSearchCode = {
820
1025
  query,
821
1026
  degraded: actualMode !== 'semantic',
822
1027
  totalResults: items.length,
823
- results: items.map(item => ({
1028
+ results: items.map((item) => ({
824
1029
  id: item.id,
825
1030
  title: item.title || '',
826
1031
  content: (item.content || item.description || '').slice(0, 500),
@@ -841,15 +1046,22 @@ const semanticSearchCode = {
841
1046
  // ────────────────────────────────────────────────────────────
842
1047
  const searchRecipes = {
843
1048
  name: 'search_recipes',
844
- description: '搜索知识库中的 Recipe(代码片段/最佳实践/架构模式)。支持关键词搜索和按分类/语言/类型筛选。',
1049
+ description:
1050
+ '搜索知识库中的 Recipe(代码片段/最佳实践/架构模式)。支持关键词搜索和按分类/语言/类型筛选。',
845
1051
  parameters: {
846
1052
  type: 'object',
847
1053
  properties: {
848
- keyword: { type: 'string', description: '搜索关键词' },
849
- category: { type: 'string', description: '分类过滤 (View/Service/Tool/Model/Network/Storage/UI/Utility)' },
850
- language: { type: 'string', description: '编程语言过滤 (swift/objectivec/typescript 等)' },
851
- knowledgeType: { type: 'string', description: '知识类型过滤 (code-standard/code-pattern/architecture/best-practice 等)' },
852
- limit: { type: 'number', description: '返回数量上限,默认 10' },
1054
+ keyword: { type: 'string', description: '搜索关键词' },
1055
+ category: {
1056
+ type: 'string',
1057
+ description: '分类过滤 (View/Service/Tool/Model/Network/Storage/UI/Utility)',
1058
+ },
1059
+ language: { type: 'string', description: '编程语言过滤 (swift/objectivec/typescript 等)' },
1060
+ knowledgeType: {
1061
+ type: 'string',
1062
+ description: '知识类型过滤 (code-standard/code-pattern/architecture/best-practice 等)',
1063
+ },
1064
+ limit: { type: 'number', description: '返回数量上限,默认 10' },
853
1065
  },
854
1066
  },
855
1067
  handler: async (params, ctx) => {
@@ -861,9 +1073,15 @@ const searchRecipes = {
861
1073
  }
862
1074
 
863
1075
  const filters = { lifecycle: 'active' };
864
- if (category) filters.category = category;
865
- if (language) filters.language = language;
866
- if (knowledgeType) filters.knowledgeType = knowledgeType;
1076
+ if (category) {
1077
+ filters.category = category;
1078
+ }
1079
+ if (language) {
1080
+ filters.language = language;
1081
+ }
1082
+ if (knowledgeType) {
1083
+ filters.knowledgeType = knowledgeType;
1084
+ }
867
1085
 
868
1086
  return knowledgeService.list(filters, { page: 1, pageSize: limit });
869
1087
  },
@@ -878,11 +1096,11 @@ const searchCandidates = {
878
1096
  parameters: {
879
1097
  type: 'object',
880
1098
  properties: {
881
- keyword: { type: 'string', description: '搜索关键词' },
882
- status: { type: 'string', description: '状态过滤 (pending/approved/rejected/applied)' },
1099
+ keyword: { type: 'string', description: '搜索关键词' },
1100
+ status: { type: 'string', description: '状态过滤 (pending/approved/rejected/applied)' },
883
1101
  language: { type: 'string', description: '编程语言过滤' },
884
1102
  category: { type: 'string', description: '分类过滤' },
885
- limit: { type: 'number', description: '返回数量上限,默认 10' },
1103
+ limit: { type: 'number', description: '返回数量上限,默认 10' },
886
1104
  },
887
1105
  },
888
1106
  handler: async (params, ctx) => {
@@ -895,9 +1113,15 @@ const searchCandidates = {
895
1113
 
896
1114
  // V3: status 映射为 lifecycle
897
1115
  const filters = {};
898
- if (status) filters.lifecycle = status;
899
- if (language) filters.language = language;
900
- if (category) filters.category = category;
1116
+ if (status) {
1117
+ filters.lifecycle = status;
1118
+ }
1119
+ if (language) {
1120
+ filters.language = language;
1121
+ }
1122
+ if (category) {
1123
+ filters.category = category;
1124
+ }
901
1125
 
902
1126
  return knowledgeService.list(filters, { page: 1, pageSize: limit });
903
1127
  },
@@ -932,7 +1156,8 @@ const getRecipeDetail = {
932
1156
  // ────────────────────────────────────────────────────────────
933
1157
  const getProjectStats = {
934
1158
  name: 'get_project_stats',
935
- description: '获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
1159
+ description:
1160
+ '获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
936
1161
  parameters: { type: 'object', properties: {} },
937
1162
  handler: async (_params, ctx) => {
938
1163
  const knowledgeService = ctx.container.get('knowledgeService');
@@ -943,7 +1168,9 @@ const getProjectStats = {
943
1168
  try {
944
1169
  const kgService = ctx.container.get('knowledgeGraphService');
945
1170
  graphStats = kgService.getStats();
946
- } catch { /* KG not available */ }
1171
+ } catch {
1172
+ /* KG not available */
1173
+ }
947
1174
 
948
1175
  return {
949
1176
  knowledge: stats,
@@ -962,7 +1189,7 @@ const searchKnowledge = {
962
1189
  type: 'object',
963
1190
  properties: {
964
1191
  query: { type: 'string', description: '搜索查询' },
965
- topK: { type: 'number', description: '返回结果数,默认 5' },
1192
+ topK: { type: 'number', description: '返回结果数,默认 5' },
966
1193
  },
967
1194
  required: ['query'],
968
1195
  },
@@ -977,9 +1204,10 @@ const searchKnowledge = {
977
1204
  const enriched = results.slice(0, topK).map((r, i) => ({
978
1205
  ...r,
979
1206
  reasoning: {
980
- whyRelevant: r.score != null
981
- ? `匹配分 ${(r.score * 100).toFixed(0)}%` + (r.matchType ? ` (${r.matchType})` : '')
982
- : '语义相关',
1207
+ whyRelevant:
1208
+ r.score != null
1209
+ ? `匹配分 ${(r.score * 100).toFixed(0)}%${r.matchType ? ` (${r.matchType})` : ''}`
1210
+ : '语义相关',
983
1211
  rank: i + 1,
984
1212
  },
985
1213
  }));
@@ -993,7 +1221,9 @@ const searchKnowledge = {
993
1221
  },
994
1222
  };
995
1223
  }
996
- } catch { /* SearchEngine not available */ }
1224
+ } catch {
1225
+ /* SearchEngine not available */
1226
+ }
997
1227
 
998
1228
  // 降级: RetrievalFunnel + 全量候选
999
1229
  try {
@@ -1003,7 +1233,7 @@ const searchKnowledge = {
1003
1233
  const allRecipes = allResult?.items || [];
1004
1234
 
1005
1235
  // 规范化为 funnel 输入格式
1006
- const candidates = allRecipes.map(r => ({
1236
+ const candidates = allRecipes.map((r) => ({
1007
1237
  id: r.id,
1008
1238
  title: r.title,
1009
1239
  content: r.content || r.code || '',
@@ -1017,9 +1247,19 @@ const searchKnowledge = {
1017
1247
  const results = await funnel.execute(query, candidates, {});
1018
1248
  return { source: 'retrievalFunnel', results: results.slice(0, topK) };
1019
1249
  }
1020
- } catch { /* RetrievalFunnel not available */ }
1250
+ } catch {
1251
+ /* RetrievalFunnel not available */
1252
+ }
1021
1253
 
1022
- return { source: 'none', results: [], message: 'No search engine available', _meta: { confidence: 'none', hint: '搜索引擎不可用。请确认向量索引已构建(rebuild_index)。' } };
1254
+ return {
1255
+ source: 'none',
1256
+ results: [],
1257
+ message: 'No search engine available',
1258
+ _meta: {
1259
+ confidence: 'none',
1260
+ hint: '搜索引擎不可用。请确认向量索引已构建(rebuild_index)。',
1261
+ },
1262
+ };
1023
1263
  },
1024
1264
  };
1025
1265
 
@@ -1033,7 +1273,11 @@ const getRelatedRecipes = {
1033
1273
  type: 'object',
1034
1274
  properties: {
1035
1275
  recipeId: { type: 'string', description: 'Recipe ID' },
1036
- relation: { type: 'string', description: '关系类型过滤 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite),不传则返回全部关系' },
1276
+ relation: {
1277
+ type: 'string',
1278
+ description:
1279
+ '关系类型过滤 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite),不传则返回全部关系',
1280
+ },
1037
1281
  },
1038
1282
  required: ['recipeId'],
1039
1283
  },
@@ -1060,13 +1304,15 @@ const summarizeCode = {
1060
1304
  parameters: {
1061
1305
  type: 'object',
1062
1306
  properties: {
1063
- code: { type: 'string', description: '代码内容' },
1307
+ code: { type: 'string', description: '代码内容' },
1064
1308
  language: { type: 'string', description: '编程语言' },
1065
1309
  },
1066
1310
  required: ['code'],
1067
1311
  },
1068
1312
  handler: async (params, ctx) => {
1069
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1313
+ if (!ctx.aiProvider) {
1314
+ return { error: 'AI provider not available' };
1315
+ }
1070
1316
  return ctx.aiProvider.summarize(params.code, params.language);
1071
1317
  },
1072
1318
  };
@@ -1076,17 +1322,27 @@ const summarizeCode = {
1076
1322
  // ────────────────────────────────────────────────────────────
1077
1323
  const extractRecipes = {
1078
1324
  name: 'extract_recipes',
1079
- description: '从源码文件中批量提取可复用的 Recipe 结构(代码标准、设计模式、最佳实践)。支持自动 provider fallback。',
1325
+ description:
1326
+ '从源码文件中批量提取可复用的 Recipe 结构(代码标准、设计模式、最佳实践)。支持自动 provider fallback。',
1080
1327
  parameters: {
1081
1328
  type: 'object',
1082
1329
  properties: {
1083
1330
  targetName: { type: 'string', description: 'SPM Target / 模块名称' },
1084
- files: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, content: { type: 'string' } } }, description: '文件数组 [{name, content}]' },
1331
+ files: {
1332
+ type: 'array',
1333
+ items: {
1334
+ type: 'object',
1335
+ properties: { name: { type: 'string' }, content: { type: 'string' } },
1336
+ },
1337
+ description: '文件数组 [{name, content}]',
1338
+ },
1085
1339
  },
1086
1340
  required: ['targetName', 'files'],
1087
1341
  },
1088
1342
  handler: async (params, ctx) => {
1089
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1343
+ if (!ctx.aiProvider) {
1344
+ return { error: 'AI provider not available' };
1345
+ }
1090
1346
  const { targetName, files, comprehensive } = params;
1091
1347
 
1092
1348
  // 加载语言参考 Skill(如有),注入到 AI 提取 prompt
@@ -1097,20 +1353,22 @@ const extractRecipes = {
1097
1353
  const primaryLang = langProfile?.primaryLanguage;
1098
1354
  if (primaryLang) {
1099
1355
  const skillCtx = loadBootstrapSkills(primaryLang);
1100
- skillReference = skillCtx.languageSkill
1101
- ? skillCtx.languageSkill.substring(0, 2000)
1102
- : null;
1356
+ skillReference = skillCtx.languageSkill ? skillCtx.languageSkill.substring(0, 2000) : null;
1103
1357
  }
1104
- } catch { /* Skills not available, proceed without */ }
1358
+ } catch {
1359
+ /* Skills not available, proceed without */
1360
+ }
1105
1361
 
1106
1362
  // AST 代码结构分析(如可用),注入到 AI 提取 prompt
1107
1363
  let astContext = null;
1108
1364
  try {
1109
- const { analyzeProject, generateContextForAgent, isAvailable } = await import('../../../core/AstAnalyzer.js');
1365
+ const { analyzeProject, generateContextForAgent, isAvailable } = await import(
1366
+ '../../../core/AstAnalyzer.js'
1367
+ );
1110
1368
  if (isAvailable()) {
1111
1369
  const sourceFiles = files
1112
- .filter(f => /\.(m|mm|h|swift|js|ts|jsx|tsx)$/.test(f.name || ''))
1113
- .map(f => ({ path: f.name, source: f.content }));
1370
+ .filter((f) => /\.(m|mm|h|swift|js|ts|jsx|tsx)$/.test(f.name || ''))
1371
+ .map((f) => ({ path: f.name, source: f.content }));
1114
1372
  if (sourceFiles.length > 0) {
1115
1373
  const langProfile2 = ctx.aiProvider._detectLanguageProfile?.(files);
1116
1374
  const lang = langProfile2?.primaryLanguage === 'swift' ? 'swift' : 'objc';
@@ -1118,12 +1376,20 @@ const extractRecipes = {
1118
1376
  astContext = generateContextForAgent(summary);
1119
1377
  }
1120
1378
  }
1121
- } catch { /* AST not available, proceed without */ }
1379
+ } catch {
1380
+ /* AST not available, proceed without */
1381
+ }
1122
1382
 
1123
1383
  const extractOpts = {};
1124
- if (skillReference) extractOpts.skillReference = skillReference;
1125
- if (astContext) extractOpts.astContext = astContext;
1126
- if (comprehensive) extractOpts.comprehensive = true;
1384
+ if (skillReference) {
1385
+ extractOpts.skillReference = skillReference;
1386
+ }
1387
+ if (astContext) {
1388
+ extractOpts.astContext = astContext;
1389
+ }
1390
+ if (comprehensive) {
1391
+ extractOpts.comprehensive = true;
1392
+ }
1127
1393
 
1128
1394
  // 首选:使用当前 aiProvider
1129
1395
  let recipes;
@@ -1145,23 +1411,41 @@ const extractRecipes = {
1145
1411
  fallbackUsed = fbName;
1146
1412
  recovered = true;
1147
1413
  break;
1148
- } catch { /* next fallback */ }
1414
+ } catch {
1415
+ /* next fallback */
1416
+ }
1149
1417
  }
1150
1418
  }
1151
- } catch { /* AiFactory not available */ }
1152
- if (!recovered) throw primaryErr;
1419
+ } catch {
1420
+ /* AiFactory not available */
1421
+ }
1422
+ if (!recovered) {
1423
+ throw primaryErr;
1424
+ }
1153
1425
  }
1154
1426
 
1155
- if (!Array.isArray(recipes)) recipes = [];
1427
+ if (!Array.isArray(recipes)) {
1428
+ recipes = [];
1429
+ }
1156
1430
  if (recipes.length === 0) {
1157
- ctx.logger?.warn?.(`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`);
1431
+ ctx.logger?.warn?.(
1432
+ `[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`
1433
+ );
1158
1434
  }
1159
1435
 
1160
1436
  // ── V3 直透:AI 已输出完整 V3 结构,仅做来源标记 + 程序化评分/标签 ──
1161
1437
  let qualityScorer = null;
1162
1438
  let recipeExtractor = null;
1163
- try { qualityScorer = ctx.container?.get?.('qualityScorer'); } catch { /* not available */ }
1164
- try { recipeExtractor = ctx.container?.get?.('recipeExtractor'); } catch { /* not available */ }
1439
+ try {
1440
+ qualityScorer = ctx.container?.get?.('qualityScorer');
1441
+ } catch {
1442
+ /* not available */
1443
+ }
1444
+ try {
1445
+ recipeExtractor = ctx.container?.get?.('recipeExtractor');
1446
+ } catch {
1447
+ /* not available */
1448
+ }
1165
1449
 
1166
1450
  for (const recipe of recipes) {
1167
1451
  // 来源 & 生命周期(非 AI 职责)
@@ -1173,15 +1457,23 @@ const extractRecipes = {
1173
1457
  if (recipeExtractor && codeText) {
1174
1458
  try {
1175
1459
  const extracted = recipeExtractor.extractFromContent(
1176
- codeText, `${recipe.title || 'unknown'}.${recipe.language || 'swift'}`, ''
1460
+ codeText,
1461
+ `${recipe.title || 'unknown'}.${recipe.language || 'unknown'}`,
1462
+ ''
1177
1463
  );
1178
1464
  if (extracted.semanticTags?.length > 0) {
1179
1465
  recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
1180
1466
  }
1181
- if ((!recipe.category || recipe.category === 'Utility') && extracted.category && extracted.category !== 'general') {
1467
+ if (
1468
+ (!recipe.category || recipe.category === 'Utility') &&
1469
+ extracted.category &&
1470
+ extracted.category !== 'general'
1471
+ ) {
1182
1472
  recipe.category = extracted.category;
1183
1473
  }
1184
- } catch { /* best effort */ }
1474
+ } catch {
1475
+ /* best effort */
1476
+ }
1185
1477
  }
1186
1478
 
1187
1479
  // QualityScorer 评分 → quality 结构化
@@ -1195,12 +1487,16 @@ const extractRecipes = {
1195
1487
  overall: scoreResult.score ?? 0,
1196
1488
  grade: scoreResult.grade || '',
1197
1489
  };
1198
- } catch { /* best effort */ }
1490
+ } catch {
1491
+ /* best effort */
1492
+ }
1199
1493
  }
1200
1494
  }
1201
1495
 
1202
1496
  const result = { targetName, extracted: recipes.length, recipes };
1203
- if (fallbackUsed) result.fallbackUsed = fallbackUsed;
1497
+ if (fallbackUsed) {
1498
+ result.fallbackUsed = fallbackUsed;
1499
+ }
1204
1500
  return result;
1205
1501
  },
1206
1502
  };
@@ -1210,16 +1506,23 @@ const extractRecipes = {
1210
1506
  // ────────────────────────────────────────────────────────────
1211
1507
  const enrichCandidate = {
1212
1508
  name: 'enrich_candidate',
1213
- description: '① 结构补齐 — 自动填充缺失的结构性语义字段(rationale/knowledgeType/complexity/scope/steps/constraints)。批量处理,只填空不覆盖。建议在 refine_bootstrap_candidates 之前执行。',
1509
+ description:
1510
+ '① 结构补齐 — 自动填充缺失的结构性语义字段(rationale/knowledgeType/complexity/scope/steps/constraints)。批量处理,只填空不覆盖。建议在 refine_bootstrap_candidates 之前执行。',
1214
1511
  parameters: {
1215
1512
  type: 'object',
1216
1513
  properties: {
1217
- candidateIds: { type: 'array', items: { type: 'string' }, description: '候选 ID 列表 (最多 20 个)' },
1514
+ candidateIds: {
1515
+ type: 'array',
1516
+ items: { type: 'string' },
1517
+ description: '候选 ID 列表 (最多 20 个)',
1518
+ },
1218
1519
  },
1219
1520
  required: ['candidateIds'],
1220
1521
  },
1221
1522
  handler: async (params, ctx) => {
1222
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1523
+ if (!ctx.aiProvider) {
1524
+ return { error: 'AI provider not available' };
1525
+ }
1223
1526
  // V3: 使用 MCP handler enrichCandidates 的逻辑
1224
1527
  const { enrichCandidates: enrichFn } = await import('../../external/mcp/handlers/candidate.js');
1225
1528
  const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
@@ -1232,17 +1535,27 @@ const enrichCandidate = {
1232
1535
  // ────────────────────────────────────────────────────────────
1233
1536
  const refineBootstrapCandidates = {
1234
1537
  name: 'refine_bootstrap_candidates',
1235
- description: '② 内容润色 — 逐条精炼 Bootstrap 候选的内容质量:改善 summary、补充架构 insight、推断 relations 关联、调整 confidence、丰富 tags。建议在 enrich_candidate 之后执行。',
1538
+ description:
1539
+ '② 内容润色 — 逐条精炼 Bootstrap 候选的内容质量:改善 summary、补充架构 insight、推断 relations 关联、调整 confidence、丰富 tags。建议在 enrich_candidate 之后执行。',
1236
1540
  parameters: {
1237
1541
  type: 'object',
1238
1542
  properties: {
1239
- candidateIds: { type: 'array', items: { type: 'string' }, description: '指定候选 ID 列表(可选,默认全部 bootstrap 候选)' },
1240
- userPrompt: { type: 'string', description: '用户自定义润色提示词,指导 AI 润色方向(如“侧重描述线程安全注意事项”)' },
1543
+ candidateIds: {
1544
+ type: 'array',
1545
+ items: { type: 'string' },
1546
+ description: '指定候选 ID 列表(可选,默认全部 bootstrap 候选)',
1547
+ },
1548
+ userPrompt: {
1549
+ type: 'string',
1550
+ description: '用户自定义润色提示词,指导 AI 润色方向(如“侧重描述线程安全注意事项”)',
1551
+ },
1241
1552
  dryRun: { type: 'boolean', description: '仅预览 AI 润色结果,不写入数据库' },
1242
1553
  },
1243
1554
  },
1244
1555
  handler: async (params, ctx) => {
1245
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1556
+ if (!ctx.aiProvider) {
1557
+ return { error: 'AI provider not available' };
1558
+ }
1246
1559
  // V3: 委托给 bootstrap handler 的 refine 逻辑
1247
1560
  const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
1248
1561
  const result = await bootstrapRefine(ctx, {
@@ -1259,14 +1572,15 @@ const refineBootstrapCandidates = {
1259
1572
  // ────────────────────────────────────────────────────────────
1260
1573
  const checkDuplicate = {
1261
1574
  name: 'check_duplicate',
1262
- description: '候选查重 — 检测候选代码是否与已有 Recipe 重复(基于标题/摘要/代码的 Jaccard 相似度)。',
1575
+ description:
1576
+ '候选查重 — 检测候选代码是否与已有 Recipe 重复(基于标题/摘要/代码的 Jaccard 相似度)。',
1263
1577
  parameters: {
1264
1578
  type: 'object',
1265
1579
  properties: {
1266
- candidate: { type: 'object', description: '候选对象 { title, summary, code, usageGuide }' },
1580
+ candidate: { type: 'object', description: '候选对象 { title, summary, code, usageGuide }' },
1267
1581
  candidateId: { type: 'string', description: '或提供候选 ID,从数据库读取' },
1268
1582
  projectRoot: { type: 'string', description: '项目根目录(可选,默认当前项目)' },
1269
- threshold: { type: 'number', description: '相似度阈值,默认 0.5' },
1583
+ threshold: { type: 'number', description: '相似度阈值,默认 0.5' },
1270
1584
  },
1271
1585
  },
1272
1586
  handler: async (params, ctx) => {
@@ -1288,10 +1602,14 @@ const checkDuplicate = {
1288
1602
  usageGuide: '',
1289
1603
  };
1290
1604
  }
1291
- } catch { /* ignore */ }
1605
+ } catch {
1606
+ /* ignore */
1607
+ }
1292
1608
  }
1293
1609
 
1294
- if (!cand) return { similar: [], message: 'No candidate provided' };
1610
+ if (!cand) {
1611
+ return { similar: [], message: 'No candidate provided' };
1612
+ }
1295
1613
 
1296
1614
  const similar = findSimilarRecipes(projectRoot, cand, {
1297
1615
  threshold,
@@ -1300,14 +1618,16 @@ const checkDuplicate = {
1300
1618
 
1301
1619
  return {
1302
1620
  similar,
1303
- hasDuplicate: similar.some(s => s.similarity >= 0.7),
1621
+ hasDuplicate: similar.some((s) => s.similarity >= 0.7),
1304
1622
  highestSimilarity: similar.length > 0 ? similar[0].similarity : 0,
1305
1623
  _meta: {
1306
- confidence: similar.length === 0 ? 'none'
1307
- : similar[0].similarity >= 0.7 ? 'high' : 'low',
1308
- hint: similar.length === 0 ? '未发现相似 Recipe,可放心提交。'
1309
- : similar[0].similarity >= 0.7 ? '发现高度相似 Recipe,建议人工审核是否重复。'
1310
- : '有低相似度匹配,大概率不是重复。',
1624
+ confidence: similar.length === 0 ? 'none' : similar[0].similarity >= 0.7 ? 'high' : 'low',
1625
+ hint:
1626
+ similar.length === 0
1627
+ ? '未发现相似 Recipe,可放心提交。'
1628
+ : similar[0].similarity >= 0.7
1629
+ ? '发现高度相似 Recipe,建议人工审核是否重复。'
1630
+ : '有低相似度匹配,大概率不是重复。',
1311
1631
  },
1312
1632
  };
1313
1633
  },
@@ -1318,7 +1638,8 @@ const checkDuplicate = {
1318
1638
  // ────────────────────────────────────────────────────────────
1319
1639
  const discoverRelations = {
1320
1640
  name: 'discover_relations',
1321
- description: 'AI 知识图谱关系发现 — 分析 Recipe 对之间的潜在关系(requires/extends/enforces/calls 等),并自动写入知识图谱。',
1641
+ description:
1642
+ 'AI 知识图谱关系发现 — 分析 Recipe 对之间的潜在关系(requires/extends/enforces/calls 等),并自动写入知识图谱。',
1322
1643
  parameters: {
1323
1644
  type: 'object',
1324
1645
  properties: {
@@ -1327,30 +1648,55 @@ const discoverRelations = {
1327
1648
  items: {
1328
1649
  type: 'object',
1329
1650
  properties: {
1330
- a: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' }, category: { type: 'string' }, code: { type: 'string' } } },
1331
- b: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' }, category: { type: 'string' }, code: { type: 'string' } } },
1651
+ a: {
1652
+ type: 'object',
1653
+ properties: {
1654
+ id: { type: 'string' },
1655
+ title: { type: 'string' },
1656
+ category: { type: 'string' },
1657
+ code: { type: 'string' },
1658
+ },
1659
+ },
1660
+ b: {
1661
+ type: 'object',
1662
+ properties: {
1663
+ id: { type: 'string' },
1664
+ title: { type: 'string' },
1665
+ category: { type: 'string' },
1666
+ code: { type: 'string' },
1667
+ },
1668
+ },
1332
1669
  },
1333
1670
  },
1334
- description: 'Recipe 对数组 [{ a: {id, title, category, code}, b: {id, title, category, code} }]',
1671
+ description:
1672
+ 'Recipe 对数组 [{ a: {id, title, category, code}, b: {id, title, category, code} }]',
1335
1673
  },
1336
1674
  dryRun: { type: 'boolean', description: '仅分析不写入,默认 false' },
1337
1675
  },
1338
1676
  required: ['recipePairs'],
1339
1677
  },
1340
1678
  handler: async (params, ctx) => {
1341
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1679
+ if (!ctx.aiProvider) {
1680
+ return { error: 'AI provider not available' };
1681
+ }
1342
1682
 
1343
1683
  const { recipePairs, dryRun = false } = params;
1344
- if (!recipePairs || recipePairs.length === 0) return { relations: [] };
1684
+ if (!recipePairs || recipePairs.length === 0) {
1685
+ return { relations: [] };
1686
+ }
1345
1687
 
1346
1688
  // 构建 LLM prompt
1347
- const pairsText = recipePairs.map((p, i) => `
1689
+ const pairsText = recipePairs
1690
+ .map(
1691
+ (p, i) => `
1348
1692
  --- Pair #${i + 1} ---
1349
1693
  Recipe A [${p.a.id}]: ${p.a.title} (${p.a.category}/${p.a.language || ''})
1350
1694
  ${p.a.code ? `Code: ${p.a.code.substring(0, 300)}` : ''}
1351
1695
 
1352
1696
  Recipe B [${p.b.id}]: ${p.b.title} (${p.b.category}/${p.b.language || ''})
1353
- ${p.b.code ? `Code: ${p.b.code.substring(0, 300)}` : ''}`).join('\n');
1697
+ ${p.b.code ? `Code: ${p.b.code.substring(0, 300)}` : ''}`
1698
+ )
1699
+ .join('\n');
1354
1700
 
1355
1701
  const prompt = `# Role
1356
1702
  You are a Software Architect analyzing relationships between code recipes (knowledge units).
@@ -1379,7 +1725,9 @@ Return ONLY a JSON array. No markdown, no extra text. Return [] if no relationsh
1379
1725
  ${pairsText}`;
1380
1726
 
1381
1727
  const parsed = await ctx.aiProvider.chatWithStructuredOutput(prompt, {
1382
- openChar: '[', closeChar: ']', temperature: 0.2,
1728
+ openChar: '[',
1729
+ closeChar: ']',
1730
+ temperature: 0.2,
1383
1731
  });
1384
1732
  const relations = Array.isArray(parsed) ? parsed : [];
1385
1733
 
@@ -1389,21 +1737,22 @@ ${pairsText}`;
1389
1737
  const kgService = ctx.container.get('knowledgeGraphService');
1390
1738
  for (const rel of relations) {
1391
1739
  if (rel.from_id && rel.to_id && rel.relation && rel.relation !== 'none') {
1392
- kgService.addEdge(
1393
- rel.from_id, 'recipe',
1394
- rel.to_id, 'recipe',
1395
- rel.relation,
1396
- { confidence: rel.confidence || 0.5, reason: rel.reason || '', source: 'ai-discovery' },
1397
- );
1740
+ kgService.addEdge(rel.from_id, 'recipe', rel.to_id, 'recipe', rel.relation, {
1741
+ confidence: rel.confidence || 0.5,
1742
+ reason: rel.reason || '',
1743
+ source: 'ai-discovery',
1744
+ });
1398
1745
  }
1399
1746
  }
1400
- } catch { /* KG not available */ }
1747
+ } catch {
1748
+ /* KG not available */
1749
+ }
1401
1750
  }
1402
1751
 
1403
1752
  return {
1404
1753
  analyzed: recipePairs.length,
1405
- relations: relations.filter(r => r.relation !== 'none'),
1406
- written: dryRun ? 0 : relations.filter(r => r.relation !== 'none').length,
1754
+ relations: relations.filter((r) => r.relation !== 'none'),
1755
+ written: dryRun ? 0 : relations.filter((r) => r.relation !== 'none').length,
1407
1756
  };
1408
1757
  },
1409
1758
  };
@@ -1417,22 +1766,28 @@ const addGraphEdge = {
1417
1766
  parameters: {
1418
1767
  type: 'object',
1419
1768
  properties: {
1420
- fromId: { type: 'string', description: '源节点 ID' },
1769
+ fromId: { type: 'string', description: '源节点 ID' },
1421
1770
  fromType: { type: 'string', description: '源节点类型 (recipe/candidate)' },
1422
- toId: { type: 'string', description: '目标节点 ID' },
1423
- toType: { type: 'string', description: '目标节点类型 (recipe/candidate)' },
1424
- relation: { type: 'string', description: '关系类型 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite)' },
1425
- weight: { type: 'number', description: '权重 0-1,默认 1.0' },
1771
+ toId: { type: 'string', description: '目标节点 ID' },
1772
+ toType: { type: 'string', description: '目标节点类型 (recipe/candidate)' },
1773
+ relation: {
1774
+ type: 'string',
1775
+ description:
1776
+ '关系类型 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite)',
1777
+ },
1778
+ weight: { type: 'number', description: '权重 0-1,默认 1.0' },
1426
1779
  },
1427
1780
  required: ['fromId', 'fromType', 'toId', 'toType', 'relation'],
1428
1781
  },
1429
1782
  handler: async (params, ctx) => {
1430
1783
  const kgService = ctx.container.get('knowledgeGraphService');
1431
1784
  return kgService.addEdge(
1432
- params.fromId, params.fromType,
1433
- params.toId, params.toType,
1785
+ params.fromId,
1786
+ params.fromType,
1787
+ params.toId,
1788
+ params.toType,
1434
1789
  params.relation,
1435
- { weight: params.weight || 1.0, source: 'manual' },
1790
+ { weight: params.weight || 1.0, source: 'manual' }
1436
1791
  );
1437
1792
  },
1438
1793
  };
@@ -1464,16 +1819,21 @@ const listGuardRules = {
1464
1819
  const guardService = ctx.container.get('guardService');
1465
1820
  const dbRules = await guardService.listRules({}, { page: 1, pageSize: limit });
1466
1821
  results.push(...(dbRules.data || dbRules.items || []));
1467
- } catch { /* not available */ }
1822
+ } catch {
1823
+ /* not available */
1824
+ }
1468
1825
 
1469
1826
  // 内置规则
1470
1827
  if (includeBuiltIn) {
1471
1828
  try {
1472
1829
  const guardCheckEngine = ctx.container.get('guardCheckEngine');
1473
- const builtIn = guardCheckEngine.getRules(language || null)
1474
- .filter(r => r.source === 'built-in');
1830
+ const builtIn = guardCheckEngine
1831
+ .getRules(language || null)
1832
+ .filter((r) => r.source === 'built-in');
1475
1833
  results.push(...builtIn);
1476
- } catch { /* not available */ }
1834
+ } catch {
1835
+ /* not available */
1836
+ }
1477
1837
  }
1478
1838
 
1479
1839
  return { total: results.length, rules: results.slice(0, limit) };
@@ -1495,7 +1855,10 @@ const getRecommendations = {
1495
1855
  handler: async (params, ctx) => {
1496
1856
  const knowledgeService = ctx.container.get('knowledgeService');
1497
1857
  // V3: 推荐 = 活跃条目按使用量排序
1498
- return knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: params.limit || 10 });
1858
+ return knowledgeService.list(
1859
+ { lifecycle: 'active' },
1860
+ { page: 1, pageSize: params.limit || 10 }
1861
+ );
1499
1862
  },
1500
1863
  };
1501
1864
 
@@ -1513,17 +1876,27 @@ const aiTranslate = {
1513
1876
  },
1514
1877
  },
1515
1878
  handler: async (params, ctx) => {
1516
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1879
+ if (!ctx.aiProvider) {
1880
+ return { error: 'AI provider not available' };
1881
+ }
1517
1882
  const { summary, usageGuide } = params;
1518
- if (!summary && !usageGuide) return { summaryEn: '', usageGuideEn: '' };
1883
+ if (!summary && !usageGuide) {
1884
+ return { summaryEn: '', usageGuideEn: '' };
1885
+ }
1519
1886
 
1520
- const systemPrompt = 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
1887
+ const systemPrompt =
1888
+ 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
1521
1889
  const parts = [];
1522
- if (summary) parts.push(`summary: ${summary}`);
1523
- if (usageGuide) parts.push(`usageGuide: ${usageGuide}`);
1890
+ if (summary) {
1891
+ parts.push(`summary: ${summary}`);
1892
+ }
1893
+ if (usageGuide) {
1894
+ parts.push(`usageGuide: ${usageGuide}`);
1895
+ }
1524
1896
 
1525
1897
  const parsed = await ctx.aiProvider.chatWithStructuredOutput(parts.join('\n'), {
1526
- systemPrompt, temperature: 0.2,
1898
+ systemPrompt,
1899
+ temperature: 0.2,
1527
1900
  });
1528
1901
  return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
1529
1902
  },
@@ -1538,9 +1911,9 @@ const guardCheckCode = {
1538
1911
  parameters: {
1539
1912
  type: 'object',
1540
1913
  properties: {
1541
- code: { type: 'string', description: '待检查的源代码' },
1914
+ code: { type: 'string', description: '待检查的源代码' },
1542
1915
  language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
1543
- scope: { type: 'string', description: '检查范围 (file/target/project),默认 file' },
1916
+ scope: { type: 'string', description: '检查范围 (file/target/project),默认 file' },
1544
1917
  },
1545
1918
  required: ['code'],
1546
1919
  },
@@ -1553,7 +1926,9 @@ const guardCheckCode = {
1553
1926
  const violations = engine.checkCode(code, language || 'unknown', { scope });
1554
1927
  // reasoning 已由 GuardCheckEngine.checkCode() 内置附加
1555
1928
  return { violationCount: violations.length, violations };
1556
- } catch { /* not available */ }
1929
+ } catch {
1930
+ /* not available */
1931
+ }
1557
1932
 
1558
1933
  // 降级到 GuardService.checkCode(仅 DB 规则)
1559
1934
  try {
@@ -1575,7 +1950,7 @@ const queryViolations = {
1575
1950
  parameters: {
1576
1951
  type: 'object',
1577
1952
  properties: {
1578
- file: { type: 'string', description: '按文件路径过滤' },
1953
+ file: { type: 'string', description: '按文件路径过滤' },
1579
1954
  limit: { type: 'number', description: '返回数量,默认 20' },
1580
1955
  statsOnly: { type: 'boolean', description: '仅返回统计数据,默认 false' },
1581
1956
  },
@@ -1605,15 +1980,20 @@ const generateGuardRule = {
1605
1980
  parameters: {
1606
1981
  type: 'object',
1607
1982
  properties: {
1608
- description: { type: 'string', description: '规则描述(例如 "禁止在主线程使用同步网络请求")' },
1609
- language: { type: 'string', description: '目标语言 (swift/objc 等)' },
1610
- severity: { type: 'string', description: '严重程度 (error/warning/info),默认 warning' },
1611
- autoCreate: { type: 'boolean', description: '是否自动创建到数据库,默认 false' },
1983
+ description: {
1984
+ type: 'string',
1985
+ description: '规则描述(例如 "禁止在主线程使用同步网络请求")',
1986
+ },
1987
+ language: { type: 'string', description: '目标语言 (swift/objc 等)' },
1988
+ severity: { type: 'string', description: '严重程度 (error/warning/info),默认 warning' },
1989
+ autoCreate: { type: 'boolean', description: '是否自动创建到数据库,默认 false' },
1612
1990
  },
1613
1991
  required: ['description'],
1614
1992
  },
1615
1993
  handler: async (params, ctx) => {
1616
- if (!ctx.aiProvider) return { error: 'AI provider not available' };
1994
+ if (!ctx.aiProvider) {
1995
+ return { error: 'AI provider not available' };
1996
+ }
1617
1997
  const { description, language = 'swift', severity = 'warning', autoCreate = false } = params;
1618
1998
 
1619
1999
  const prompt = `Generate a Guard rule for this requirement:
@@ -1636,7 +2016,9 @@ Return ONLY valid JSON:
1636
2016
  }`;
1637
2017
 
1638
2018
  const rule = await ctx.aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.2 });
1639
- if (!rule) return { error: 'Failed to parse AI response' };
2019
+ if (!rule) {
2020
+ return { error: 'Failed to parse AI response' };
2021
+ }
1640
2022
 
1641
2023
  // 验证正则表达式
1642
2024
  try {
@@ -1649,13 +2031,16 @@ Return ONLY valid JSON:
1649
2031
  if (autoCreate && rule.name && rule.pattern) {
1650
2032
  try {
1651
2033
  const guardService = ctx.container.get('guardService');
1652
- const created = await guardService.createRule({
1653
- name: rule.name,
1654
- description: rule.description || description,
1655
- pattern: rule.pattern,
1656
- languages: rule.languages || [language],
1657
- severity: rule.severity || severity,
1658
- }, { userId: 'agent' });
2034
+ const created = await guardService.createRule(
2035
+ {
2036
+ name: rule.name,
2037
+ description: rule.description || description,
2038
+ pattern: rule.pattern,
2039
+ languages: rule.languages || [language],
2040
+ severity: rule.severity || severity,
2041
+ },
2042
+ { userId: 'agent' }
2043
+ );
1659
2044
  return { rule, created: true, recipeId: created.id };
1660
2045
  } catch (err) {
1661
2046
  return { rule, created: false, error: err.message };
@@ -1672,15 +2057,15 @@ Return ONLY valid JSON:
1672
2057
  // ────────────────────────────────────────────────────────────
1673
2058
 
1674
2059
  const DIMENSION_DISPLAY_GROUP = {
1675
- 'architecture': 'architecture', // → 架构与设计
1676
- 'code-pattern': 'architecture', // → 架构与设计
1677
- 'project-profile': 'architecture', // → 架构与设计
1678
- 'best-practice': 'best-practice', // → 规范与实践
1679
- 'code-standard': 'best-practice', // → 规范与实践
2060
+ architecture: 'architecture', // → 架构与设计
2061
+ 'code-pattern': 'architecture', // → 架构与设计
2062
+ 'project-profile': 'architecture', // → 架构与设计
2063
+ 'best-practice': 'best-practice', // → 规范与实践
2064
+ 'code-standard': 'best-practice', // → 规范与实践
1680
2065
  'event-and-data-flow': 'event-and-data-flow', // → 事件与数据流
1681
- 'objc-deep-scan': 'objc-deep-scan', // → 深度扫描
1682
- 'category-scan': 'objc-deep-scan', // → 深度扫描
1683
- 'agent-guidelines': 'agent-guidelines', // skill-only
2066
+ 'objc-deep-scan': 'objc-deep-scan', // → 深度扫描
2067
+ 'category-scan': 'objc-deep-scan', // → 深度扫描
2068
+ 'agent-guidelines': 'agent-guidelines', // skill-only
1684
2069
  };
1685
2070
 
1686
2071
  // ────────────────────────────────────────────────────────────
@@ -1698,7 +2083,9 @@ const DIMENSION_DISPLAY_GROUP = {
1698
2083
  function _checkDimensionType(dimensionMeta, params, logger) {
1699
2084
  // 1. Skill-only 维度不允许提交 Candidate
1700
2085
  if (dimensionMeta.outputType === 'skill') {
1701
- logger?.info(`[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`);
2086
+ logger?.info(
2087
+ `[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`
2088
+ );
1702
2089
  return {
1703
2090
  status: 'rejected',
1704
2091
  reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_knowledge。请只在最终回复中提供 dimensionDigest JSON。`,
@@ -1710,7 +2097,9 @@ function _checkDimensionType(dimensionMeta, params, logger) {
1710
2097
  if (allowed.length > 0 && params.knowledgeType) {
1711
2098
  if (!allowed.includes(params.knowledgeType)) {
1712
2099
  const corrected = allowed[0];
1713
- logger?.warn(`[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`);
2100
+ logger?.warn(
2101
+ `[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`
2102
+ );
1714
2103
  params.knowledgeType = corrected;
1715
2104
  }
1716
2105
  }
@@ -1727,31 +2116,55 @@ const submitCandidate = {
1727
2116
  type: 'object',
1728
2117
  properties: {
1729
2118
  // ── 内容(V3 content 子对象) ──
1730
- content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
2119
+ content: {
2120
+ type: 'object',
2121
+ description: '{ markdown: "项目特写 Markdown(≥200字)", pattern: "核心代码 3-8 行", rationale: "设计原理" }',
2122
+ },
1731
2123
 
1732
2124
  // ── 基本信息 ──
1733
- title: { type: 'string', description: '候选标题' },
1734
- description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
1735
- tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
2125
+ title: { type: 'string', description: '候选标题(中文 ≤20 字)' },
2126
+ description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
2127
+ tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
1736
2128
 
1737
2129
  // ── Cursor 交付(AI 必填)──
1738
- trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
1739
- kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
1740
- topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'], description: '主题分类' },
1741
- whenClause: { type: 'string', description: '触发场景英文' },
1742
- doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
1743
- dontClause: { type: 'string', description: '反向约束英文(不以 Don\'t 开头)' },
2130
+ trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2131
+ kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
2132
+ topicHint: {
2133
+ type: 'string',
2134
+ enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
2135
+ description: '主题分类',
2136
+ },
2137
+ whenClause: { type: 'string', description: '触发场景英文' },
2138
+ doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
2139
+ dontClause: { type: 'string', description: "反向约束英文(不以 Don't 开头)" },
1744
2140
 
1745
- // ── 推理 ──
1746
- reasoning: { type: 'object', description: '{ whyStandard: string, sources: string[], confidence: number }' },
2141
+ // ── 推理(必填) ──
2142
+ reasoning: {
2143
+ type: 'object',
2144
+ description: '{ whyStandard: string, sources: string[], confidence: number } — 全部必填',
2145
+ },
1747
2146
 
1748
2147
  // ── V3 扩展字段 ──
1749
- scope: { type: 'string', enum: ['universal', 'project-specific', 'team-convention'], description: '适用范围' },
1750
- complexity: { type: 'string', enum: ['basic', 'intermediate', 'advanced'], description: '复杂度' },
1751
- headers: { type: 'array', items: { type: 'string' }, description: '依赖的 import/require 行' },
1752
- sourceFile: { type: 'string', description: '来源文件相对路径' },
2148
+ scope: {
2149
+ type: 'string',
2150
+ enum: ['universal', 'project-specific', 'team-convention'],
2151
+ description: '适用范围',
2152
+ },
2153
+ complexity: {
2154
+ type: 'string',
2155
+ enum: ['basic', 'intermediate', 'advanced'],
2156
+ description: '复杂度',
2157
+ },
2158
+ headers: {
2159
+ type: 'array',
2160
+ items: { type: 'string' },
2161
+ description: '依赖的 import/require 行(无 import 时传 [])',
2162
+ },
2163
+ knowledgeType: { type: 'string', description: '知识维度:code-pattern / architecture / best-practice 等' },
2164
+ usageGuide: { type: 'string', description: '使用指南 Markdown(### 章节格式)' },
2165
+ sourceFile: { type: 'string', description: '来源文件相对路径' },
1753
2166
  },
1754
- required: ['content', 'title', 'trigger', 'kind', 'doClause'],
2167
+ required: ['content', 'title', 'trigger', 'kind', 'doClause', 'description', 'headers', 'reasoning'],
1755
2168
  },
1756
2169
  handler: async (params, ctx) => {
1757
2170
  const knowledgeService = ctx.container.get('knowledgeService');
@@ -1760,21 +2173,26 @@ const submitCandidate = {
1760
2173
  const dimMeta = ctx._dimensionMeta;
1761
2174
  if (dimMeta && ctx.source === 'system') {
1762
2175
  const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
1763
- if (rejected) return rejected;
2176
+ if (rejected) {
2177
+ return rejected;
2178
+ }
1764
2179
 
1765
2180
  // 自动注入维度标签
1766
- if (!params.tags) params.tags = [];
1767
- if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
1768
- if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
2181
+ if (!params.tags) {
2182
+ params.tags = [];
2183
+ }
2184
+ if (!params.tags.includes(dimMeta.id)) {
2185
+ params.tags.push(dimMeta.id);
2186
+ }
2187
+ if (!params.tags.includes('bootstrap')) {
2188
+ params.tags.push('bootstrap');
2189
+ }
1769
2190
 
1770
2191
  // Bootstrap 模式: 将 category 覆盖为展示分组 ID
1771
2192
  params._category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
1772
2193
 
1773
2194
  // ── CandidateGuardrail 质量验证 ──
1774
- const guardrail = new CandidateGuardrail(
1775
- ctx._submittedTitles || new Set(),
1776
- dimMeta,
1777
- );
2195
+ const guardrail = new CandidateGuardrail(ctx._submittedTitles || new Set(), dimMeta, ctx._submittedPatterns || new Set());
1778
2196
  const guardResult = guardrail.validate(params);
1779
2197
  if (!guardResult.valid) {
1780
2198
  ctx.logger?.info(`[submit_knowledge] ✗ guardrail rejected: ${guardResult.error}`);
@@ -1788,10 +2206,10 @@ const submitCandidate = {
1788
2206
 
1789
2207
  // ── 系统自动设置 ──
1790
2208
  const systemFields = {
1791
- language: ctx._projectLanguage || '',
1792
- category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
2209
+ language: ctx._projectLanguage || '',
2210
+ category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
1793
2211
  knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
1794
- source: ctx.source === 'system' ? 'bootstrap' : 'agent',
2212
+ source: ctx.source === 'system' ? 'bootstrap' : 'agent',
1795
2213
  };
1796
2214
 
1797
2215
  // ── 直传 → KnowledgeEntry ──
@@ -1801,39 +2219,42 @@ const submitCandidate = {
1801
2219
  }
1802
2220
 
1803
2221
  // V3 content 直透
1804
- const contentObj = params.content && typeof params.content === 'object'
1805
- ? params.content
1806
- : { markdown: '', pattern: '' };
2222
+ const contentObj =
2223
+ params.content && typeof params.content === 'object'
2224
+ ? params.content
2225
+ : { markdown: '', pattern: '' };
1807
2226
 
1808
2227
  const data = {
1809
2228
  ...systemFields,
1810
- title: params.title || '',
2229
+ title: params.title || '',
1811
2230
  description: params.description || '',
1812
- tags: params.tags || [],
1813
- trigger: params.trigger || '',
1814
- kind: params.kind || 'pattern',
1815
- topicHint: params.topicHint || '',
1816
- whenClause: params.whenClause || '',
1817
- doClause: params.doClause || '',
1818
- dontClause: params.dontClause || '',
1819
- coreCode: contentObj.pattern || '',
1820
- content: contentObj,
2231
+ tags: params.tags || [],
2232
+ trigger: params.trigger || '',
2233
+ kind: params.kind || 'pattern',
2234
+ topicHint: params.topicHint || '',
2235
+ whenClause: params.whenClause || '',
2236
+ doClause: params.doClause || '',
2237
+ dontClause: params.dontClause || '',
2238
+ coreCode: contentObj.pattern || '',
2239
+ content: contentObj,
1821
2240
  reasoning,
1822
2241
  // V3 扩展字段直透
1823
- scope: params.scope || '',
1824
- complexity: params.complexity || '',
1825
- headers: params.headers || [],
2242
+ scope: params.scope || '',
2243
+ complexity: params.complexity || '',
2244
+ headers: params.headers || [],
1826
2245
  // sourceFile: 优先取 params,Bootstrap 回退从 reasoning.sources 推断
1827
- sourceFile: params.sourceFile
1828
- || (Array.isArray(reasoning.sources) && reasoning.sources.length > 0
1829
- && reasoning.sources[0] !== 'agent'
1830
- ? reasoning.sources[0]
1831
- : ''),
2246
+ sourceFile:
2247
+ params.sourceFile ||
2248
+ (Array.isArray(reasoning.sources) &&
2249
+ reasoning.sources.length > 0 &&
2250
+ reasoning.sources[0] !== 'agent'
2251
+ ? reasoning.sources[0]
2252
+ : ''),
1832
2253
  // 7.3.9 agentNotes/aiInsight 注入
1833
- agentNotes: dimMeta
1834
- ? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
1835
- : null,
1836
- aiInsight: reasoning.whyStandard || params.description || null,
2254
+ agentNotes: dimMeta
2255
+ ? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
2256
+ : null,
2257
+ aiInsight: reasoning.whyStandard || params.description || null,
1837
2258
  };
1838
2259
 
1839
2260
  if (dimMeta && ctx.source === 'system') {
@@ -1846,7 +2267,9 @@ const submitCandidate = {
1846
2267
  // ── QualityScorer 自动评分 ──
1847
2268
  try {
1848
2269
  await knowledgeService.updateQuality(saved.id, { userId: 'agent' });
1849
- } catch { /* best effort — 不阻塞创建流程 */ }
2270
+ } catch {
2271
+ /* best effort — 不阻塞创建流程 */
2272
+ }
1850
2273
 
1851
2274
  return saved;
1852
2275
  },
@@ -1857,15 +2280,24 @@ const submitCandidate = {
1857
2280
  // ────────────────────────────────────────────────────────────
1858
2281
  const saveDocument = {
1859
2282
  name: 'save_document',
1860
- description: '保存开发文档到知识库(架构设计、排查报告、决策记录、调研笔记等)。仅需 title + markdown,无需 Cursor Delivery 字段。文档自动发布,可通过 autosnippet_search 检索。',
2283
+ description:
2284
+ '保存开发文档到知识库(架构设计、排查报告、决策记录、调研笔记等)。仅需 title + markdown,无需 Cursor Delivery 字段。文档自动发布,可通过 autosnippet_search 检索。',
1861
2285
  parameters: {
1862
2286
  type: 'object',
1863
2287
  properties: {
1864
- title: { type: 'string', description: '文档标题' },
1865
- markdown: { type: 'string', description: '文档 Markdown 全文' },
2288
+ title: { type: 'string', description: '文档标题' },
2289
+ markdown: { type: 'string', description: '文档 Markdown 全文' },
1866
2290
  description: { type: 'string', description: '一句话摘要(可选)' },
1867
- tags: { type: 'array', items: { type: 'string' }, description: '标签: adr, debug-report, design-doc, research, performance 等' },
1868
- scope: { type: 'string', enum: ['universal', 'project-specific'], description: '适用范围(默认 project-specific)' },
2291
+ tags: {
2292
+ type: 'array',
2293
+ items: { type: 'string' },
2294
+ description: '标签: adr, debug-report, design-doc, research, performance 等',
2295
+ },
2296
+ scope: {
2297
+ type: 'string',
2298
+ enum: ['universal', 'project-specific'],
2299
+ description: '适用范围(默认 project-specific)',
2300
+ },
1869
2301
  },
1870
2302
  required: ['title', 'markdown'],
1871
2303
  },
@@ -1873,27 +2305,27 @@ const saveDocument = {
1873
2305
  const knowledgeService = ctx.container.get('knowledgeService');
1874
2306
 
1875
2307
  const data = {
1876
- title: params.title.trim(),
1877
- description: params.description || '',
2308
+ title: params.title.trim(),
2309
+ description: params.description || '',
1878
2310
  knowledgeType: 'dev-document',
1879
- kind: 'fact',
1880
- source: 'agent',
1881
- scope: params.scope || 'project-specific',
1882
- tags: params.tags || [],
2311
+ kind: 'fact',
2312
+ source: 'agent',
2313
+ scope: params.scope || 'project-specific',
2314
+ tags: params.tags || [],
1883
2315
  content: {
1884
2316
  markdown: params.markdown,
1885
- pattern: '',
2317
+ pattern: '',
1886
2318
  },
1887
- trigger: '',
1888
- doClause: '',
2319
+ trigger: '',
2320
+ doClause: '',
1889
2321
  dontClause: '',
1890
2322
  whenClause: '',
1891
- topicHint: '',
1892
- coreCode: '',
2323
+ topicHint: '',
2324
+ coreCode: '',
1893
2325
  reasoning: {
1894
2326
  whyStandard: 'Agent development document',
1895
- sources: ['agent'],
1896
- confidence: 0.8,
2327
+ sources: ['agent'],
2328
+ confidence: 0.8,
1897
2329
  },
1898
2330
  };
1899
2331
 
@@ -1902,14 +2334,16 @@ const saveDocument = {
1902
2334
  // 自动发布(文档不需要人工审核)
1903
2335
  try {
1904
2336
  await knowledgeService.publish(saved.id, { userId: 'agent' });
1905
- } catch { /* best effort */ }
2337
+ } catch {
2338
+ /* best effort */
2339
+ }
1906
2340
 
1907
2341
  return {
1908
- id: saved.id,
1909
- title: saved.title,
1910
- lifecycle: 'active',
2342
+ id: saved.id,
2343
+ title: saved.title,
2344
+ lifecycle: 'active',
1911
2345
  knowledgeType: 'dev-document',
1912
- message: `文档「${saved.title}」已保存到知识库`,
2346
+ message: `文档「${saved.title}」已保存到知识库`,
1913
2347
  };
1914
2348
  },
1915
2349
  };
@@ -1943,7 +2377,7 @@ const rejectCandidate = {
1943
2377
  type: 'object',
1944
2378
  properties: {
1945
2379
  candidateId: { type: 'string', description: '候选 ID' },
1946
- reason: { type: 'string', description: '驳回理由' },
2380
+ reason: { type: 'string', description: '驳回理由' },
1947
2381
  },
1948
2382
  required: ['candidateId', 'reason'],
1949
2383
  },
@@ -1982,7 +2416,7 @@ const deprecateRecipe = {
1982
2416
  type: 'object',
1983
2417
  properties: {
1984
2418
  recipeId: { type: 'string', description: 'Recipe ID' },
1985
- reason: { type: 'string', description: '弃用原因' },
2419
+ reason: { type: 'string', description: '弃用原因' },
1986
2420
  },
1987
2421
  required: ['recipeId', 'reason'],
1988
2422
  },
@@ -2002,7 +2436,7 @@ const updateRecipe = {
2002
2436
  type: 'object',
2003
2437
  properties: {
2004
2438
  recipeId: { type: 'string', description: 'Recipe ID' },
2005
- updates: { type: 'object', description: '要更新的字段和值' },
2439
+ updates: { type: 'object', description: '要更新的字段和值' },
2006
2440
  },
2007
2441
  required: ['recipeId', 'updates'],
2008
2442
  },
@@ -2022,7 +2456,7 @@ const recordUsage = {
2022
2456
  type: 'object',
2023
2457
  properties: {
2024
2458
  recipeId: { type: 'string', description: 'Recipe ID' },
2025
- type: { type: 'string', description: 'adoption 或 application,默认 adoption' },
2459
+ type: { type: 'string', description: 'adoption 或 application,默认 adoption' },
2026
2460
  },
2027
2461
  required: ['recipeId'],
2028
2462
  },
@@ -2039,12 +2473,16 @@ const recordUsage = {
2039
2473
  // ────────────────────────────────────────────────────────────
2040
2474
  const qualityScore = {
2041
2475
  name: 'quality_score',
2042
- description: 'Recipe 质量评分 — 5 维度综合评估(完整性/格式/代码质量/元数据/互动),返回分数和等级(A-F)。',
2476
+ description:
2477
+ 'Recipe 质量评分 — 5 维度综合评估(完整性/格式/代码质量/元数据/互动),返回分数和等级(A-F)。',
2043
2478
  parameters: {
2044
2479
  type: 'object',
2045
2480
  properties: {
2046
2481
  recipeId: { type: 'string', description: 'Recipe ID(从数据库读取后评分)' },
2047
- recipe: { type: 'object', description: '或直接提供 Recipe 对象 { title, trigger, code, language, ... }' },
2482
+ recipe: {
2483
+ type: 'object',
2484
+ description: '或直接提供 Recipe 对象 { title, trigger, code, language, ... }',
2485
+ },
2048
2486
  },
2049
2487
  },
2050
2488
  handler: async (params, ctx) => {
@@ -2060,7 +2498,9 @@ const qualityScore = {
2060
2498
  return { error: `Knowledge entry '${params.recipeId}' not found` };
2061
2499
  }
2062
2500
  }
2063
- if (!recipe) return { error: 'Provide recipeId or recipe object' };
2501
+ if (!recipe) {
2502
+ return { error: 'Provide recipeId or recipe object' };
2503
+ }
2064
2504
 
2065
2505
  return qualityScorer.score(recipe);
2066
2506
  },
@@ -2071,11 +2511,15 @@ const qualityScore = {
2071
2511
  // ────────────────────────────────────────────────────────────
2072
2512
  const validateCandidate = {
2073
2513
  name: 'validate_candidate',
2074
- description: '候选校验 — 检查候选是否满足提交要求(必填字段/格式/质量),返回 errors 和 warnings。',
2514
+ description:
2515
+ '候选校验 — 检查候选是否满足提交要求(必填字段/格式/质量),返回 errors 和 warnings。',
2075
2516
  parameters: {
2076
2517
  type: 'object',
2077
2518
  properties: {
2078
- candidate: { type: 'object', description: '候选对象 { title, trigger, category, language, code, reasoning, ... }' },
2519
+ candidate: {
2520
+ type: 'object',
2521
+ description: '候选对象 { title, trigger, category, language, code, reasoning, ... }',
2522
+ },
2079
2523
  },
2080
2524
  required: ['candidate'],
2081
2525
  },
@@ -2095,7 +2539,7 @@ const getFeedbackStats = {
2095
2539
  type: 'object',
2096
2540
  properties: {
2097
2541
  recipeId: { type: 'string', description: '查询指定 Recipe 的反馈(可选)' },
2098
- topN: { type: 'number', description: '热门 Recipe 数量,默认 10' },
2542
+ topN: { type: 'number', description: '热门 Recipe 数量,默认 10' },
2099
2543
  },
2100
2544
  },
2101
2545
  handler: async (params, ctx) => {
@@ -2139,7 +2583,8 @@ const graphImpactAnalysis = {
2139
2583
  // ────────────────────────────────────────────────────────────
2140
2584
  const rebuildIndex = {
2141
2585
  name: 'rebuild_index',
2142
- description: '向量索引重建 — 重新扫描 Recipe 文件并更新向量索引(用于索引过期或新增大量 Recipe 后)。',
2586
+ description:
2587
+ '向量索引重建 — 重新扫描 Recipe 文件并更新向量索引(用于索引过期或新增大量 Recipe 后)。',
2143
2588
  parameters: {
2144
2589
  type: 'object',
2145
2590
  properties: {
@@ -2162,17 +2607,24 @@ const queryAuditLog = {
2162
2607
  parameters: {
2163
2608
  type: 'object',
2164
2609
  properties: {
2165
- action: { type: 'string', description: '按操作类型过滤 (create_candidate/approve_candidate/create_guard_rule 等)' },
2166
- actor: { type: 'string', description: '按操作者过滤' },
2167
- limit: { type: 'number', description: '返回数量,默认 20' },
2610
+ action: {
2611
+ type: 'string',
2612
+ description: '按操作类型过滤 (create_candidate/approve_candidate/create_guard_rule 等)',
2613
+ },
2614
+ actor: { type: 'string', description: '按操作者过滤' },
2615
+ limit: { type: 'number', description: '返回数量,默认 20' },
2168
2616
  },
2169
2617
  },
2170
2618
  handler: async (params, ctx) => {
2171
2619
  const auditLogger = ctx.container.get('auditLogger');
2172
2620
  const { action, actor, limit = 20 } = params;
2173
2621
 
2174
- if (actor) return auditLogger.getByActor(actor, limit);
2175
- if (action) return auditLogger.getByAction(action, limit);
2622
+ if (actor) {
2623
+ return auditLogger.getByActor(actor, limit);
2624
+ }
2625
+ if (action) {
2626
+ return auditLogger.getByAction(action, limit);
2627
+ }
2176
2628
  return auditLogger.getStats();
2177
2629
  },
2178
2630
  };
@@ -2182,11 +2634,15 @@ const queryAuditLog = {
2182
2634
  // ────────────────────────────────────────────────────────────
2183
2635
  const loadSkill = {
2184
2636
  name: 'load_skill',
2185
- description: '加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts) 等。',
2637
+ description:
2638
+ '加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts/python/java/kotlin/go) 等。',
2186
2639
  parameters: {
2187
2640
  type: 'object',
2188
2641
  properties: {
2189
- skillName: { type: 'string', description: 'Skill 目录名(如 autosnippet-coldstart, autosnippet-reference-swift 等)' },
2642
+ skillName: {
2643
+ type: 'string',
2644
+ description: 'Skill 目录名(如 autosnippet-coldstart, autosnippet-reference-swift, autosnippet-reference-go 等)',
2645
+ },
2190
2646
  },
2191
2647
  required: ['skillName'],
2192
2648
  },
@@ -2201,8 +2657,16 @@ const loadSkill = {
2201
2657
  return { skillName: params.skillName, source, content };
2202
2658
  } catch {
2203
2659
  const available = new Set();
2204
- try { fs.readdirSync(SKILLS_DIR, { withFileTypes: true }).filter(d => d.isDirectory()).forEach(d => available.add(d.name)); } catch {}
2205
- try { fs.readdirSync(PROJECT_SKILLS_DIR, { withFileTypes: true }).filter(d => d.isDirectory()).forEach(d => available.add(d.name)); } catch {}
2660
+ try {
2661
+ fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
2662
+ .filter((d) => d.isDirectory())
2663
+ .forEach((d) => available.add(d.name));
2664
+ } catch {}
2665
+ try {
2666
+ fs.readdirSync(PROJECT_SKILLS_DIR, { withFileTypes: true })
2667
+ .filter((d) => d.isDirectory())
2668
+ .forEach((d) => available.add(d.name));
2669
+ } catch {}
2206
2670
  return { error: `Skill "${params.skillName}" not found`, availableSkills: [...available] };
2207
2671
  }
2208
2672
  },
@@ -2213,14 +2677,18 @@ const loadSkill = {
2213
2677
  // ────────────────────────────────────────────────────────────
2214
2678
  const createSkillTool = {
2215
2679
  name: 'create_skill',
2216
- description: '创建项目级 Skill 文档,写入 AutoSnippet/skills/<name>/SKILL.md。Skill 是 Agent 的领域知识增强文档。创建后自动更新编辑器索引。',
2680
+ description:
2681
+ '创建项目级 Skill 文档,写入 AutoSnippet/skills/<name>/SKILL.md。Skill 是 Agent 的领域知识增强文档。创建后自动更新编辑器索引。',
2217
2682
  parameters: {
2218
2683
  type: 'object',
2219
2684
  properties: {
2220
- name: { type: 'string', description: 'Skill 名称(kebab-case,如 my-auth-guide),3-64 字符' },
2685
+ name: {
2686
+ type: 'string',
2687
+ description: 'Skill 名称(kebab-case,如 my-auth-guide),3-64 字符',
2688
+ },
2221
2689
  description: { type: 'string', description: 'Skill 一句话描述(写入 frontmatter)' },
2222
- content: { type: 'string', description: 'Skill 正文内容(Markdown 格式,不含 frontmatter)' },
2223
- overwrite: { type: 'boolean', description: '如果同名 Skill 已存在,是否覆盖(默认 false)' },
2690
+ content: { type: 'string', description: 'Skill 正文内容(Markdown 格式,不含 frontmatter)' },
2691
+ overwrite: { type: 'boolean', description: '如果同名 Skill 已存在,是否覆盖(默认 false)' },
2224
2692
  },
2225
2693
  required: ['name', 'description', 'content'],
2226
2694
  },
@@ -2229,7 +2697,11 @@ const createSkillTool = {
2229
2697
  // 根据 ChatAgent 的 source 推断 createdBy
2230
2698
  const createdBy = ctx?.source === 'system' ? 'system-ai' : 'user-ai';
2231
2699
  const raw = createSkill(null, { ...params, createdBy });
2232
- try { return JSON.parse(raw); } catch { return { success: false, error: raw }; }
2700
+ try {
2701
+ return JSON.parse(raw);
2702
+ } catch {
2703
+ return { success: false, error: raw };
2704
+ }
2233
2705
  },
2234
2706
  };
2235
2707
 
@@ -2238,7 +2710,8 @@ const createSkillTool = {
2238
2710
  // ────────────────────────────────────────────────────────────
2239
2711
  const suggestSkills = {
2240
2712
  name: 'suggest_skills',
2241
- description: '基于项目使用模式分析,推荐创建 Skill。分析 Guard 违规频率、Memory 偏好积累、Recipe 分布缺口、候选积压率。返回推荐列表(含 name/description/rationale/priority),可据此直接调用 create_skill 创建。',
2713
+ description:
2714
+ '基于项目使用模式分析,推荐创建 Skill。分析 Guard 违规频率、Memory 偏好积累、Recipe 分布缺口、候选积压率。返回推荐列表(含 name/description/rationale/priority),可据此直接调用 create_skill 创建。',
2242
2715
  parameters: {
2243
2716
  type: 'object',
2244
2717
  properties: {},
@@ -2258,14 +2731,18 @@ const suggestSkills = {
2258
2731
  // ────────────────────────────────────────────────────────────
2259
2732
  const bootstrapKnowledgeTool = {
2260
2733
  name: 'bootstrap_knowledge',
2261
- description: '冷启动知识库初始化(纯启发式,不使用 AI): SPM Target 扫描 → 依赖图谱 → Guard 审计 → 9 维度 Candidate 自动创建。支持 Skill 增强维度定义。产出为初稿候选,后续由 DAG pipeline 自动编排 AI 增强(enrich → refine)。',
2734
+ description:
2735
+ '冷启动知识库初始化(纯启发式,不使用 AI): SPM Target 扫描 → 依赖图谱 → Guard 审计 → 9 维度 Candidate 自动创建。支持 Skill 增强维度定义。产出为初稿候选,后续由 DAG pipeline 自动编排 AI 增强(enrich → refine)。',
2262
2736
  parameters: {
2263
2737
  type: 'object',
2264
2738
  properties: {
2265
2739
  maxFiles: { type: 'number', description: '最大扫描文件数,默认 500' },
2266
2740
  skipGuard: { type: 'boolean', description: '是否跳过 Guard 审计,默认 false' },
2267
2741
  contentMaxLines: { type: 'number', description: '每文件读取最大行数,默认 120' },
2268
- loadSkills: { type: 'boolean', description: '是否加载 Skills 增强维度定义(推荐开启),默认 true' },
2742
+ loadSkills: {
2743
+ type: 'boolean',
2744
+ description: '是否加载 Skills 增强维度定义(推荐开启),默认 true',
2745
+ },
2269
2746
  },
2270
2747
  },
2271
2748
  handler: async (params, ctx) => {
@@ -2278,7 +2755,7 @@ const bootstrapKnowledgeTool = {
2278
2755
  skipGuard: params.skipGuard || false,
2279
2756
  contentMaxLines: params.contentMaxLines || 120,
2280
2757
  loadSkills: params.loadSkills ?? true,
2281
- },
2758
+ }
2282
2759
  );
2283
2760
  // bootstrapKnowledge 返回 envelope JSON string,解析提取 data
2284
2761
  const parsed = typeof result === 'string' ? JSON.parse(result) : result;
@@ -2295,11 +2772,12 @@ const bootstrapKnowledgeTool = {
2295
2772
  // ────────────────────────────────────────────────────────────
2296
2773
  const analyzeCode = {
2297
2774
  name: 'analyze_code',
2298
- description: '综合分析一段代码:Guard 规范检查 + 相关 Recipe 搜索。一次调用完成完整分析,减少多轮工具调用。',
2775
+ description:
2776
+ '综合分析一段代码:Guard 规范检查 + 相关 Recipe 搜索。一次调用完成完整分析,减少多轮工具调用。',
2299
2777
  parameters: {
2300
2778
  type: 'object',
2301
2779
  properties: {
2302
- code: { type: 'string', description: '待分析的源码' },
2780
+ code: { type: 'string', description: '待分析的源码' },
2303
2781
  language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
2304
2782
  filePath: { type: 'string', description: '文件路径(可选,用于上下文)' },
2305
2783
  },
@@ -2321,7 +2799,9 @@ const analyzeCode = {
2321
2799
  const guardService = ctx.container.get('guardService');
2322
2800
  const matches = await guardService.checkCode(code, { language });
2323
2801
  return { violationCount: matches.length, violations: matches };
2324
- } catch { return { violationCount: 0, violations: [] }; }
2802
+ } catch {
2803
+ return { violationCount: 0, violations: [] };
2804
+ }
2325
2805
  }
2326
2806
  })(),
2327
2807
  (async () => {
@@ -2331,7 +2811,9 @@ const analyzeCode = {
2331
2811
  const query = code.substring(0, 200).replace(/\n/g, ' ');
2332
2812
  const rawResults = await searchEngine.search(query, { limit: 5 });
2333
2813
  return { results: rawResults || [], total: rawResults?.length || 0 };
2334
- } catch { return { results: [], total: 0 }; }
2814
+ } catch {
2815
+ return { results: [], total: 0 };
2816
+ }
2335
2817
  })(),
2336
2818
  ]);
2337
2819
 
@@ -2356,7 +2838,8 @@ const analyzeCode = {
2356
2838
  // ────────────────────────────────────────────────────────────
2357
2839
  const knowledgeOverview = {
2358
2840
  name: 'knowledge_overview',
2359
- description: '一次性获取知识库全貌:各类型 Recipe 分布 + 候选状态 + 知识图谱概况 + 质量概览。比分别调用 get_project_stats + search_recipes 更高效。',
2841
+ description:
2842
+ '一次性获取知识库全貌:各类型 Recipe 分布 + 候选状态 + 知识图谱概况 + 质量概览。比分别调用 get_project_stats + search_recipes 更高效。',
2360
2843
  parameters: {
2361
2844
  type: 'object',
2362
2845
  properties: {
@@ -2374,14 +2857,20 @@ const knowledgeOverview = {
2374
2857
  try {
2375
2858
  const knowledgeService = ctx.container.get('knowledgeService');
2376
2859
  return knowledgeService.getStats();
2377
- } catch { return null; }
2860
+ } catch {
2861
+ return null;
2862
+ }
2378
2863
  })(),
2379
2864
  (async () => {
2380
- if (!includeTopRecipes) return null;
2865
+ if (!includeTopRecipes) {
2866
+ return null;
2867
+ }
2381
2868
  try {
2382
2869
  const feedbackCollector = ctx.container.get('feedbackCollector');
2383
2870
  return feedbackCollector.getTopRecipes(limit);
2384
- } catch { return null; }
2871
+ } catch {
2872
+ return null;
2873
+ }
2385
2874
  })(),
2386
2875
  ]);
2387
2876
 
@@ -2393,9 +2882,13 @@ const knowledgeOverview = {
2393
2882
  try {
2394
2883
  const kgService = ctx.container.get('knowledgeGraphService');
2395
2884
  result.knowledgeGraph = kgService.getStats();
2396
- } catch { /* KG not available */ }
2885
+ } catch {
2886
+ /* KG not available */
2887
+ }
2397
2888
 
2398
- if (feedbackResult) result.topRecipes = feedbackResult;
2889
+ if (feedbackResult) {
2890
+ result.topRecipes = feedbackResult;
2891
+ }
2399
2892
 
2400
2893
  const recipeCount = result.recipes?.total || result.recipes?.count || 0;
2401
2894
  result._meta = {
@@ -2412,22 +2905,29 @@ const knowledgeOverview = {
2412
2905
  // ────────────────────────────────────────────────────────────
2413
2906
  const submitWithCheck = {
2414
2907
  name: 'submit_with_check',
2415
- description: '安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
2908
+ description:
2909
+ '安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
2416
2910
  parameters: {
2417
2911
  type: 'object',
2418
2912
  properties: {
2419
- content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
2420
- title: { type: 'string', description: '候选标题' },
2421
- description:{ type: 'string', description: '中文简述 ≤80 字' },
2422
- trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2423
- kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
2424
- topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'] },
2913
+ content: {
2914
+ type: 'object',
2915
+ description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行,必须语法完整(括号配对、不能以 } 开头或 { 结尾)" }',
2916
+ },
2917
+ title: { type: 'string', description: '候选标题' },
2918
+ description: { type: 'string', description: '中文简述 ≤80 ' },
2919
+ trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
2920
+ kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
2921
+ topicHint: {
2922
+ type: 'string',
2923
+ enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
2924
+ },
2425
2925
  whenClause: { type: 'string', description: '触发场景英文' },
2426
- doClause: { type: 'string', description: '正向指令英文' },
2926
+ doClause: { type: 'string', description: '正向指令英文' },
2427
2927
  dontClause: { type: 'string', description: '反向约束英文' },
2428
- tags: { type: 'array', items: { type: 'string' } },
2429
- reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
2430
- threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
2928
+ tags: { type: 'array', items: { type: 'string' } },
2929
+ reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
2930
+ threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
2431
2931
  },
2432
2932
  required: ['content', 'title', 'trigger', 'kind', 'doClause'],
2433
2933
  },
@@ -2438,21 +2938,34 @@ const submitWithCheck = {
2438
2938
  const dimMeta = ctx._dimensionMeta;
2439
2939
  if (dimMeta && ctx.source === 'system') {
2440
2940
  const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
2441
- if (rejected) return rejected;
2941
+ if (rejected) {
2942
+ return rejected;
2943
+ }
2442
2944
 
2443
- if (!params.tags) params.tags = [];
2444
- if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
2445
- if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
2945
+ if (!params.tags) {
2946
+ params.tags = [];
2947
+ }
2948
+ if (!params.tags.includes(dimMeta.id)) {
2949
+ params.tags.push(dimMeta.id);
2950
+ }
2951
+ if (!params.tags.includes('bootstrap')) {
2952
+ params.tags.push('bootstrap');
2953
+ }
2446
2954
  }
2447
2955
 
2448
2956
  // Step 1: 查重
2449
2957
  const threshold = params.threshold || 0.7;
2450
- const contentObj2 = params.content && typeof params.content === 'object'
2451
- ? params.content
2452
- : { markdown: '', pattern: '' };
2453
- const cand = { title: params.title || '', summary: params.description || '', code: contentObj2.markdown || contentObj2.pattern || '' };
2958
+ const contentObj2 =
2959
+ params.content && typeof params.content === 'object'
2960
+ ? params.content
2961
+ : { markdown: '', pattern: '' };
2962
+ const cand = {
2963
+ title: params.title || '',
2964
+ summary: params.description || '',
2965
+ code: contentObj2.markdown || contentObj2.pattern || '',
2966
+ };
2454
2967
  const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
2455
- const hasDuplicate = similar.some(s => s.similarity >= threshold);
2968
+ const hasDuplicate = similar.some((s) => s.similarity >= threshold);
2456
2969
 
2457
2970
  if (hasDuplicate) {
2458
2971
  return {
@@ -2470,28 +2983,32 @@ const submitWithCheck = {
2470
2983
  // Step 2: 提交 — 委托给 submit_knowledge handler
2471
2984
  try {
2472
2985
  const knowledgeService = ctx.container.get('knowledgeService');
2473
- const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
2986
+ const reasoning = params.reasoning || {
2987
+ whyStandard: '',
2988
+ sources: ['agent'],
2989
+ confidence: 0.7,
2990
+ };
2474
2991
 
2475
2992
  const systemFields = {
2476
- language: ctx._projectLanguage || '',
2477
- category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
2993
+ language: ctx._projectLanguage || '',
2994
+ category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
2478
2995
  knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
2479
- source: ctx.source === 'system' ? 'bootstrap' : 'agent',
2996
+ source: ctx.source === 'system' ? 'bootstrap' : 'agent',
2480
2997
  };
2481
2998
 
2482
2999
  const data = {
2483
3000
  ...systemFields,
2484
- title: params.title || '',
3001
+ title: params.title || '',
2485
3002
  description: params.description || '',
2486
- tags: params.tags || [],
2487
- trigger: params.trigger || '',
2488
- kind: params.kind || 'pattern',
2489
- topicHint: params.topicHint || '',
2490
- whenClause: params.whenClause || '',
2491
- doClause: params.doClause || '',
2492
- dontClause: params.dontClause || '',
2493
- coreCode: contentObj2.pattern || '',
2494
- content: contentObj2,
3003
+ tags: params.tags || [],
3004
+ trigger: params.trigger || '',
3005
+ kind: params.kind || 'pattern',
3006
+ topicHint: params.topicHint || '',
3007
+ whenClause: params.whenClause || '',
3008
+ doClause: params.doClause || '',
3009
+ dontClause: params.dontClause || '',
3010
+ coreCode: contentObj2.pattern || '',
3011
+ content: contentObj2,
2495
3012
  reasoning,
2496
3013
  };
2497
3014
 
@@ -2503,9 +3020,10 @@ const submitWithCheck = {
2503
3020
  similar: similar.length > 0 ? similar : [],
2504
3021
  _meta: {
2505
3022
  confidence: 'high',
2506
- hint: similar.length > 0
2507
- ? `已提交,但有 ${similar.length} 个低相似度匹配。`
2508
- : '已提交,无重复风险。',
3023
+ hint:
3024
+ similar.length > 0
3025
+ ? `已提交,但有 ${similar.length} 个低相似度匹配。`
3026
+ : '已提交,无重复风险。',
2509
3027
  },
2510
3028
  };
2511
3029
  } catch (err) {
@@ -2540,12 +3058,14 @@ const getToolDetails = {
2540
3058
  },
2541
3059
  handler: async ({ toolName }, context) => {
2542
3060
  const registry = context.container?.get('toolRegistry');
2543
- if (!registry) return { error: 'ToolRegistry not available' };
3061
+ if (!registry) {
3062
+ return { error: 'ToolRegistry not available' };
3063
+ }
2544
3064
 
2545
3065
  const schemas = registry.getToolSchemas();
2546
- const found = schemas.find(t => t.name === toolName);
3066
+ const found = schemas.find((t) => t.name === toolName);
2547
3067
  if (!found) {
2548
- const allNames = schemas.map(t => t.name);
3068
+ const allNames = schemas.map((t) => t.name);
2549
3069
  return {
2550
3070
  error: `Tool "${toolName}" not found`,
2551
3071
  availableTools: allNames,
@@ -2563,7 +3083,8 @@ const getToolDetails = {
2563
3083
  // ─── 元工具: 任务规划 ───────────────────────────────────
2564
3084
  const planTask = {
2565
3085
  name: 'plan_task',
2566
- description: '分析当前任务并制定结构化执行计划。在开始复杂任务前调用此工具可提高执行效率和决策质量。输出将记录到日志供审计,但不会改变实际执行流程。',
3086
+ description:
3087
+ '分析当前任务并制定结构化执行计划。在开始复杂任务前调用此工具可提高执行效率和决策质量。输出将记录到日志供审计,但不会改变实际执行流程。',
2567
3088
  parameters: {
2568
3089
  type: 'object',
2569
3090
  properties: {
@@ -2611,7 +3132,8 @@ const planTask = {
2611
3132
  // ─── 元工具: 自我质量审查 ───────────────────────────────
2612
3133
  const reviewMyOutput = {
2613
3134
  name: 'review_my_output',
2614
- description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
3135
+ description:
3136
+ '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
2615
3137
  parameters: {
2616
3138
  type: 'object',
2617
3139
  properties: {
@@ -2624,7 +3146,7 @@ const reviewMyOutput = {
2624
3146
  },
2625
3147
  handler: async (params, context) => {
2626
3148
  const submitted = (context._sessionToolCalls || []).filter(
2627
- tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check',
3149
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
2628
3150
  );
2629
3151
 
2630
3152
  if (submitted.length === 0) {
@@ -2653,7 +3175,10 @@ const reviewMyOutput = {
2653
3175
  candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
2654
3176
  }
2655
3177
  // 去掉代码块后,剩余描述性文字应足够
2656
- const proseLength = markdown.replace(/```[\s\S]*?```/g, '').replace(/[#>\-*`\n]/g, '').trim().length;
3178
+ const proseLength = markdown
3179
+ .replace(/```[\s\S]*?```/g, '')
3180
+ .replace(/[#>\-*`\n]/g, '')
3181
+ .trim().length;
2657
3182
  if (proseLength < 50) {
2658
3183
  candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
2659
3184
  }
@@ -2665,7 +3190,9 @@ const reviewMyOutput = {
2665
3190
 
2666
3191
  // 检查 4: description 过短
2667
3192
  if (description.length < 15) {
2668
- candidateIssues.push(`description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`);
3193
+ candidateIssues.push(
3194
+ `description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`
3195
+ );
2669
3196
  }
2670
3197
 
2671
3198
  // 检查 5: content.markdown 过短(可能是空壳)
@@ -2680,14 +3207,24 @@ const reviewMyOutput = {
2680
3207
  }
2681
3208
 
2682
3209
  // 检查 7: Cursor 交付字段
2683
- if (!p.trigger) candidateIssues.push('缺少 trigger 字段');
2684
- if (!p.doClause) candidateIssues.push('缺少 doClause 字段');
2685
- if (!p.kind) candidateIssues.push('缺少 kind 字段');
3210
+ if (!p.trigger) {
3211
+ candidateIssues.push('缺少 trigger 字段');
3212
+ }
3213
+ if (!p.doClause) {
3214
+ candidateIssues.push('缺少 doClause 字段');
3215
+ }
3216
+ if (!p.kind) {
3217
+ candidateIssues.push('缺少 kind 字段');
3218
+ }
2686
3219
 
2687
3220
  if (candidateIssues.length > 0) {
2688
3221
  issues.push({ title, issues: candidateIssues });
2689
3222
  }
2690
- checked.push({ title, passed: candidateIssues.length === 0, issueCount: candidateIssues.length });
3223
+ checked.push({
3224
+ title,
3225
+ passed: candidateIssues.length === 0,
3226
+ issueCount: candidateIssues.length,
3227
+ });
2691
3228
  }
2692
3229
 
2693
3230
  if (issues.length === 0) {
@@ -2699,7 +3236,7 @@ const reviewMyOutput = {
2699
3236
  }
2700
3237
 
2701
3238
  const issueLines = issues.flatMap(({ title, issues: iss }) =>
2702
- iss.map(i => `• "${title}": ${i}`),
3239
+ iss.map((i) => `• "${title}": ${i}`)
2703
3240
  );
2704
3241
 
2705
3242
  return {
@@ -2735,7 +3272,8 @@ function _getProjectGraph(ctx) {
2735
3272
  // ────────────────────────────────────────────────────────────
2736
3273
  const getProjectOverview = {
2737
3274
  name: 'get_project_overview',
2738
- description: '获取项目的整体结构概览:文件统计、模块列表、入口点、类/协议/Category 数量。' +
3275
+ description:
3276
+ '获取项目的整体结构概览:文件统计、模块列表、入口点、类/协议/Category 数量。' +
2739
3277
  '适用场景:了解项目规模和架构布局,规划探索路径。',
2740
3278
  parameters: {
2741
3279
  type: 'object',
@@ -2743,7 +3281,9 @@ const getProjectOverview = {
2743
3281
  },
2744
3282
  handler: async (_params, ctx) => {
2745
3283
  const graph = _getProjectGraph(ctx);
2746
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。请检查 tree-sitter 是否已安装。';
3284
+ if (!graph) {
3285
+ return 'AST 分析不可用 — ProjectGraph 未构建。请检查 tree-sitter 是否已安装。';
3286
+ }
2747
3287
 
2748
3288
  const o = graph.getOverview();
2749
3289
  const lines = [
@@ -2772,7 +3312,8 @@ const getProjectOverview = {
2772
3312
  // ────────────────────────────────────────────────────────────
2773
3313
  const getClassHierarchy = {
2774
3314
  name: 'get_class_hierarchy',
2775
- description: '查看指定类的继承链(向上到根类)和直接子类列表。' +
3315
+ description:
3316
+ '查看指定类的继承链(向上到根类)和直接子类列表。' +
2776
3317
  '传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
2777
3318
  parameters: {
2778
3319
  type: 'object',
@@ -2782,28 +3323,31 @@ const getClassHierarchy = {
2782
3323
  },
2783
3324
  handler: async (params, ctx) => {
2784
3325
  const graph = _getProjectGraph(ctx);
2785
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
3326
+ if (!graph) {
3327
+ return 'AST 分析不可用 — ProjectGraph 未构建。';
3328
+ }
2786
3329
 
2787
3330
  const className = params.className || params.class_name;
2788
3331
  if (className) {
2789
3332
  const chain = graph.getInheritanceChain(className);
2790
3333
  const subs = graph.getSubclasses(className);
2791
- if (chain.length === 0) return `未找到类 ${className}`;
3334
+ if (chain.length === 0) {
3335
+ return `未找到类 ${className}`;
3336
+ }
2792
3337
 
2793
- const lines = [
2794
- `🔗 ${className} 继承链:`,
2795
- ` ${chain.join(' → ')}`,
2796
- ];
3338
+ const lines = [`🔗 ${className} 继承链:`, ` ${chain.join(' → ')}`];
2797
3339
  if (subs.length > 0) {
2798
3340
  lines.push(``, `直接子类 (${subs.length}):`);
2799
- for (const s of subs) lines.push(` ├── ${s}`);
3341
+ for (const s of subs) {
3342
+ lines.push(` ├── ${s}`);
3343
+ }
2800
3344
  }
2801
3345
  return lines.join('\n');
2802
3346
  }
2803
3347
 
2804
3348
  // 全量: 找出所有根类 (没有父类或父类不在项目中的类)
2805
3349
  const allClasses = graph.getAllClassNames();
2806
- const roots = allClasses.filter(c => {
3350
+ const roots = allClasses.filter((c) => {
2807
3351
  const chain = graph.getInheritanceChain(c);
2808
3352
  return chain.length <= 1 || !allClasses.includes(chain[1]);
2809
3353
  });
@@ -2815,9 +3359,13 @@ const getClassHierarchy = {
2815
3359
  for (const d of descendants.slice(0, 5)) {
2816
3360
  lines.push(` └── ${d}`);
2817
3361
  }
2818
- if (descendants.length > 5) lines.push(` ... 还有 ${descendants.length - 5} 个`);
3362
+ if (descendants.length > 5) {
3363
+ lines.push(` ... 还有 ${descendants.length - 5} 个`);
3364
+ }
3365
+ }
3366
+ if (roots.length > 30) {
3367
+ lines.push(`... 还有 ${roots.length - 30} 棵树`);
2819
3368
  }
2820
- if (roots.length > 30) lines.push(`... 还有 ${roots.length - 30} 棵树`);
2821
3369
  return lines.join('\n');
2822
3370
  },
2823
3371
  };
@@ -2837,11 +3385,15 @@ const getClassInfo = {
2837
3385
  },
2838
3386
  handler: async (params, ctx) => {
2839
3387
  const graph = _getProjectGraph(ctx);
2840
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
3388
+ if (!graph) {
3389
+ return 'AST 分析不可用 — ProjectGraph 未构建。';
3390
+ }
2841
3391
 
2842
3392
  const className = params.className || params.class_name;
2843
3393
  const info = graph.getClassInfo(className);
2844
- if (!info) return `未找到类 "${className}"。可以使用 get_project_overview 查看项目中的所有类。`;
3394
+ if (!info) {
3395
+ return `未找到类 "${className}"。可以使用 get_project_overview 查看项目中的所有类。`;
3396
+ }
2845
3397
 
2846
3398
  const chain = graph.getInheritanceChain(className);
2847
3399
  const cats = graph.getCategoryExtensions(className);
@@ -2867,8 +3419,8 @@ const getClassInfo = {
2867
3419
 
2868
3420
  if (info.methods.length > 0) {
2869
3421
  lines.push(``, `── 方法 (${info.methods.length}) ──`);
2870
- const classMethods = info.methods.filter(m => m.isClassMethod);
2871
- const instanceMethods = info.methods.filter(m => !m.isClassMethod);
3422
+ const classMethods = info.methods.filter((m) => m.isClassMethod);
3423
+ const instanceMethods = info.methods.filter((m) => !m.isClassMethod);
2872
3424
  for (const m of classMethods) {
2873
3425
  const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
2874
3426
  lines.push(` + ${m.selector} → ${m.returnType}${cx}`);
@@ -2882,14 +3434,16 @@ const getClassInfo = {
2882
3434
  if (cats.length > 0) {
2883
3435
  lines.push(``, `── Category 扩展 (${cats.length}) ──`);
2884
3436
  for (const cat of cats) {
2885
- const methodNames = cat.methods.map(m => m.selector).join(', ');
3437
+ const methodNames = cat.methods.map((m) => m.selector).join(', ');
2886
3438
  lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
2887
3439
  }
2888
3440
  }
2889
3441
 
2890
3442
  if (subs.length > 0) {
2891
3443
  lines.push(``, `── 直接子类 (${subs.length}) ──`);
2892
- for (const s of subs) lines.push(` ${s}`);
3444
+ for (const s of subs) {
3445
+ lines.push(` ${s}`);
3446
+ }
2893
3447
  }
2894
3448
 
2895
3449
  return lines.join('\n');
@@ -2911,16 +3465,17 @@ const getProtocolInfo = {
2911
3465
  },
2912
3466
  handler: async (params, ctx) => {
2913
3467
  const graph = _getProjectGraph(ctx);
2914
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
3468
+ if (!graph) {
3469
+ return 'AST 分析不可用 — ProjectGraph 未构建。';
3470
+ }
2915
3471
 
2916
3472
  const protocolName = params.protocolName || params.protocol_name;
2917
3473
  const info = graph.getProtocolInfo(protocolName);
2918
- if (!info) return `未找到协议 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议。`;
3474
+ if (!info) {
3475
+ return `未找到协议 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议。`;
3476
+ }
2919
3477
 
2920
- const lines = [
2921
- `📋 @protocol ${info.name}`,
2922
- `文件: ${info.filePath}:${info.line}`,
2923
- ];
3478
+ const lines = [`📋 @protocol ${info.name}`, `文件: ${info.filePath}:${info.line}`];
2924
3479
 
2925
3480
  if (info.inherits.length > 0) {
2926
3481
  lines.push(`继承: <${info.inherits.join(', ')}>`);
@@ -2942,7 +3497,9 @@ const getProtocolInfo = {
2942
3497
 
2943
3498
  if (info.conformers.length > 0) {
2944
3499
  lines.push(``, `── 遵循者 (${info.conformers.length}) ──`);
2945
- for (const c of info.conformers) lines.push(` ${c}`);
3500
+ for (const c of info.conformers) {
3501
+ lines.push(` ${c}`);
3502
+ }
2946
3503
  } else {
2947
3504
  lines.push(``, `⚠️ 暂未发现遵循此协议的类`);
2948
3505
  }
@@ -2960,14 +3517,16 @@ const getMethodOverrides = {
2960
3517
  parameters: {
2961
3518
  type: 'object',
2962
3519
  properties: {
2963
- className: { type: 'string', description: '定义该方法的基类名 (必填)' },
3520
+ className: { type: 'string', description: '定义该方法的基类名 (必填)' },
2964
3521
  methodName: { type: 'string', description: '方法名或 selector (必填)' },
2965
3522
  },
2966
3523
  required: ['className', 'methodName'],
2967
3524
  },
2968
3525
  handler: async (params, ctx) => {
2969
3526
  const graph = _getProjectGraph(ctx);
2970
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
3527
+ if (!graph) {
3528
+ return 'AST 分析不可用 — ProjectGraph 未构建。';
3529
+ }
2971
3530
 
2972
3531
  const className = params.className || params.class_name;
2973
3532
  const methodName = params.methodName || params.method_name;
@@ -2977,9 +3536,7 @@ const getMethodOverrides = {
2977
3536
  return `"${className}.${methodName}" 没有在任何子类中被覆写。`;
2978
3537
  }
2979
3538
 
2980
- const lines = [
2981
- `🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`,
2982
- ];
3539
+ const lines = [`🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`];
2983
3540
  for (const o of overrides) {
2984
3541
  const cx = o.method.complexity > 3 ? ` [复杂度:${o.method.complexity}]` : '';
2985
3542
  lines.push(` ${o.className} — ${o.filePath}:${o.method.line}${cx}`);
@@ -2993,21 +3550,29 @@ const getMethodOverrides = {
2993
3550
  // ────────────────────────────────────────────────────────────
2994
3551
  const getCategoryMap = {
2995
3552
  name: 'get_category_map',
2996
- description: '获取指定类或整个项目的 ObjC Category 扩展映射。Category 是 ObjC 的核心模式,了解它有助于发现功能划分。',
3553
+ description:
3554
+ '获取指定类或整个项目的 ObjC Category 扩展映射。Category 是 ObjC 的核心模式,了解它有助于发现功能划分。',
2997
3555
  parameters: {
2998
3556
  type: 'object',
2999
3557
  properties: {
3000
- className: { type: 'string', description: '类名 — 可选, 不填则返回整个项目中有 Category 的类列表' },
3558
+ className: {
3559
+ type: 'string',
3560
+ description: '类名 — 可选, 不填则返回整个项目中有 Category 的类列表',
3561
+ },
3001
3562
  },
3002
3563
  },
3003
3564
  handler: async (params, ctx) => {
3004
3565
  const graph = _getProjectGraph(ctx);
3005
- if (!graph) return 'AST 分析不可用 — ProjectGraph 未构建。';
3566
+ if (!graph) {
3567
+ return 'AST 分析不可用 — ProjectGraph 未构建。';
3568
+ }
3006
3569
 
3007
3570
  const className = params.className || params.class_name;
3008
3571
  if (className) {
3009
3572
  const cats = graph.getCategoryExtensions(className);
3010
- if (cats.length === 0) return `"${className}" 没有 Category 扩展。`;
3573
+ if (cats.length === 0) {
3574
+ return `"${className}" 没有 Category 扩展。`;
3575
+ }
3011
3576
 
3012
3577
  const lines = [`📂 ${className} 的 Category 扩展 (${cats.length}):`];
3013
3578
  for (const cat of cats) {
@@ -3025,18 +3590,22 @@ const getCategoryMap = {
3025
3590
  // 全量概览
3026
3591
  const allClasses = graph.getAllClassNames();
3027
3592
  const withCats = allClasses
3028
- .map(c => ({ name: c, cats: graph.getCategoryExtensions(c) }))
3029
- .filter(x => x.cats.length > 0)
3593
+ .map((c) => ({ name: c, cats: graph.getCategoryExtensions(c) }))
3594
+ .filter((x) => x.cats.length > 0)
3030
3595
  .sort((a, b) => b.cats.length - a.cats.length);
3031
3596
 
3032
- if (withCats.length === 0) return '项目中没有发现 Category 扩展。';
3597
+ if (withCats.length === 0) {
3598
+ return '项目中没有发现 Category 扩展。';
3599
+ }
3033
3600
 
3034
3601
  const lines = [`📂 项目 Category 概览 (${withCats.length} 个类有 Category):`];
3035
3602
  for (const { name, cats } of withCats.slice(0, 30)) {
3036
- const catNames = cats.map(c => c.categoryName).join(', ');
3603
+ const catNames = cats.map((c) => c.categoryName).join(', ');
3037
3604
  lines.push(` ${name} — ${cats.length} 个: (${catNames})`);
3038
3605
  }
3039
- if (withCats.length > 30) lines.push(`... 还有 ${withCats.length - 30} 个类`);
3606
+ if (withCats.length > 30) {
3607
+ lines.push(`... 还有 ${withCats.length - 30} 个类`);
3608
+ }
3040
3609
  return lines.join('\n');
3041
3610
  },
3042
3611
  };
@@ -3047,7 +3616,8 @@ const getCategoryMap = {
3047
3616
 
3048
3617
  const getPreviousAnalysis = {
3049
3618
  name: 'get_previous_analysis',
3050
- description: '获取前序维度的分析摘要。在 bootstrap 中,每个维度可能有前面维度的分析结果可用。' +
3619
+ description:
3620
+ '获取前序维度的分析摘要。在 bootstrap 中,每个维度可能有前面维度的分析结果可用。' +
3051
3621
  '调用此工具可以获取之前维度产出的候选标题、设计决策等上下文,避免重复分析。' +
3052
3622
  '注意: 只有在你认为前序上下文对当前任务有帮助时才调用。',
3053
3623
  parameters: {
@@ -3062,7 +3632,9 @@ const getPreviousAnalysis = {
3062
3632
  }
3063
3633
 
3064
3634
  const prev = meta.previousAnalysis;
3065
- if (typeof prev === 'string') return prev;
3635
+ if (typeof prev === 'string') {
3636
+ return prev;
3637
+ }
3066
3638
 
3067
3639
  // 格式化前序分析
3068
3640
  const lines = ['📋 前序维度分析摘要:'];
@@ -3092,7 +3664,8 @@ const getPreviousAnalysis = {
3092
3664
  // ────────────────────────────────────────────────────────────
3093
3665
  const noteFinding = {
3094
3666
  name: 'note_finding',
3095
- description: '记录一个关键发现到工作记忆的 Scratchpad。在分析过程中发现重要模式、设计决策或事实时调用。' +
3667
+ description:
3668
+ '记录一个关键发现到工作记忆的 Scratchpad。在分析过程中发现重要模式、设计决策或事实时调用。' +
3096
3669
  '这些发现会在上下文窗口压缩后依然保留,确保分析后期不会遗忘早期重要发现。' +
3097
3670
  '建议在发现关键架构模式、核心类职责、重要设计约束时调用。',
3098
3671
  parameters: {
@@ -3100,7 +3673,8 @@ const noteFinding = {
3100
3673
  properties: {
3101
3674
  finding: {
3102
3675
  type: 'string',
3103
- description: '关键发现描述 (≤150 字)。应是具体、可验证的陈述,例如 "BDNetworkManager 使用单例模式,所有请求通过其发起"',
3676
+ description:
3677
+ '关键发现描述 (≤150 字)。应是具体、可验证的陈述,例如 "BDNetworkManager 使用单例模式,所有请求通过其发起"',
3104
3678
  },
3105
3679
  evidence: {
3106
3680
  type: 'string',
@@ -3135,7 +3709,8 @@ const noteFinding = {
3135
3709
  // ────────────────────────────────────────────────────────────
3136
3710
  const getPreviousEvidence = {
3137
3711
  name: 'get_previous_evidence',
3138
- description: '获取前序维度对特定文件/类/模式的分析证据。避免重复搜索和读取已经被其他维度分析过的内容。' +
3712
+ description:
3713
+ '获取前序维度对特定文件/类/模式的分析证据。避免重复搜索和读取已经被其他维度分析过的内容。' +
3139
3714
  '当你要搜索某个类名或文件时,先调用此工具看前序维度是否已有发现。',
3140
3715
  parameters: {
3141
3716
  type: 'object',
@@ -3157,10 +3732,7 @@ const getPreviousEvidence = {
3157
3732
  return '没有前序维度的证据可用。';
3158
3733
  }
3159
3734
 
3160
- const results = episodicMemory.searchEvidence(
3161
- params.query,
3162
- params.dimId || undefined
3163
- );
3735
+ const results = episodicMemory.searchEvidence(params.query, params.dimId || undefined);
3164
3736
 
3165
3737
  if (results.length === 0) {
3166
3738
  return `没有找到与 "${params.query}" 相关的前序证据。建议自行搜索。`;
@@ -3169,7 +3741,9 @@ const getPreviousEvidence = {
3169
3741
  const lines = [`📋 前序维度证据 (匹配 "${params.query}", ${results.length} 条):`];
3170
3742
  for (const r of results.slice(0, 8)) {
3171
3743
  lines.push(` 📄 ${r.filePath}`);
3172
- lines.push(` [${r.evidence.dimId}] [${r.evidence.importance || 5}/10] ${r.evidence.finding}`);
3744
+ lines.push(
3745
+ ` [${r.evidence.dimId}] [${r.evidence.importance || 5}/10] ${r.evidence.finding}`
3746
+ );
3173
3747
  }
3174
3748
  if (results.length > 8) {
3175
3749
  lines.push(` …还有 ${results.length - 8} 条证据`);
@@ -3183,15 +3757,25 @@ const getPreviousEvidence = {
3183
3757
  // ────────────────────────────────────────────────────────────
3184
3758
  const queryCodeGraph = {
3185
3759
  name: 'query_code_graph',
3186
- description: '查询代码实体图谱 (Code Entity Graph)。可查询类继承链、协议遵循者、实体搜索、影响分析等。' +
3760
+ description:
3761
+ '查询代码实体图谱 (Code Entity Graph)。可查询类继承链、协议遵循者、实体搜索、影响分析等。' +
3187
3762
  '图谱包含从 AST 提取的类、协议、Category、模块、设计模式及其关系。',
3188
3763
  parameters: {
3189
3764
  type: 'object',
3190
3765
  properties: {
3191
3766
  action: {
3192
3767
  type: 'string',
3193
- enum: ['search', 'inheritance_chain', 'descendants', 'conformances', 'impact', 'topology', 'entity_edges'],
3194
- description: '查询动作: search=搜索实体, inheritance_chain=继承链, descendants=子类/遵循者, conformances=协议遵循, impact=影响分析, topology=拓扑概览, entity_edges=实体的所有边',
3768
+ enum: [
3769
+ 'search',
3770
+ 'inheritance_chain',
3771
+ 'descendants',
3772
+ 'conformances',
3773
+ 'impact',
3774
+ 'topology',
3775
+ 'entity_edges',
3776
+ ],
3777
+ description:
3778
+ '查询动作: search=搜索实体, inheritance_chain=继承链, descendants=子类/遵循者, conformances=协议遵循, impact=影响分析, topology=拓扑概览, entity_edges=实体的所有边',
3195
3779
  },
3196
3780
  entity_id: {
3197
3781
  type: 'string',
@@ -3213,7 +3797,9 @@ const queryCodeGraph = {
3213
3797
  try {
3214
3798
  const { CodeEntityGraph } = await import('./../../service/knowledge/CodeEntityGraph.js');
3215
3799
  const db = ctx?.container?.get('database');
3216
- if (!db) return '代码实体图谱不可用: 数据库未初始化';
3800
+ if (!db) {
3801
+ return '代码实体图谱不可用: 数据库未初始化';
3802
+ }
3217
3803
 
3218
3804
  const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
3219
3805
  const ceg = new CodeEntityGraph(db, { projectRoot });
@@ -3225,24 +3811,32 @@ const queryCodeGraph = {
3225
3811
  type: params.entity_type,
3226
3812
  limit: 15,
3227
3813
  });
3228
- if (results.length === 0) return `未找到匹配 "${params.entity_id}" 的代码实体。`;
3814
+ if (results.length === 0) {
3815
+ return `未找到匹配 "${params.entity_id}" 的代码实体。`;
3816
+ }
3229
3817
  const lines = [`🔍 代码实体搜索 "${params.entity_id}" (${results.length} 条):`];
3230
3818
  for (const e of results) {
3231
- lines.push(` • ${e.entityType}: \`${e.name}\`${e.filePath ? ` (${e.filePath}:${e.line || '?'})` : ''}${e.superclass ? ` → ${e.superclass}` : ''}`);
3819
+ lines.push(
3820
+ ` • ${e.entityType}: \`${e.name}\`${e.filePath ? ` (${e.filePath}:${e.line || '?'})` : ''}${e.superclass ? ` → ${e.superclass}` : ''}`
3821
+ );
3232
3822
  }
3233
3823
  return lines.join('\n');
3234
3824
  }
3235
3825
 
3236
3826
  case 'inheritance_chain': {
3237
3827
  const chain = ceg.getInheritanceChain(params.entity_id, maxDepth);
3238
- if (chain.length <= 1) return `\`${params.entity_id}\` 没有已知的继承关系。`;
3828
+ if (chain.length <= 1) {
3829
+ return `\`${params.entity_id}\` 没有已知的继承关系。`;
3830
+ }
3239
3831
  return `📐 继承链: \`${chain.join(' → ')}\``;
3240
3832
  }
3241
3833
 
3242
3834
  case 'descendants': {
3243
3835
  const type = params.entity_type || 'class';
3244
3836
  const desc = ceg.getDescendants(params.entity_id, type, maxDepth);
3245
- if (desc.length === 0) return `\`${params.entity_id}\` 没有已知的子类/遵循者。`;
3837
+ if (desc.length === 0) {
3838
+ return `\`${params.entity_id}\` 没有已知的子类/遵循者。`;
3839
+ }
3246
3840
  const lines = [`📊 ${params.entity_id} 的后代 (${desc.length}):`];
3247
3841
  for (const d of desc.slice(0, 20)) {
3248
3842
  lines.push(` ${' '.repeat(d.depth - 1)}└─ \`${d.id}\` (${d.type}, ${d.relation})`);
@@ -3252,14 +3846,18 @@ const queryCodeGraph = {
3252
3846
 
3253
3847
  case 'conformances': {
3254
3848
  const protos = ceg.getConformances(params.entity_id);
3255
- if (protos.length === 0) return `\`${params.entity_id}\` 没有已知的协议遵循。`;
3256
- return `📋 \`${params.entity_id}\` 遵循: ${protos.map(p => '`' + p + '`').join(', ')}`;
3849
+ if (protos.length === 0) {
3850
+ return `\`${params.entity_id}\` 没有已知的协议遵循。`;
3851
+ }
3852
+ return `📋 \`${params.entity_id}\` 遵循: ${protos.map((p) => `\`${p}\``).join(', ')}`;
3257
3853
  }
3258
3854
 
3259
3855
  case 'impact': {
3260
3856
  const type = params.entity_type || 'class';
3261
3857
  const impact = ceg.getImpactRadius(params.entity_id, type, maxDepth);
3262
- if (impact.length === 0) return `修改 \`${params.entity_id}\` 没有检测到直接影响。`;
3858
+ if (impact.length === 0) {
3859
+ return `修改 \`${params.entity_id}\` 没有检测到直接影响。`;
3860
+ }
3263
3861
  const lines = [`⚡ 修改 \`${params.entity_id}\` 的影响范围 (${impact.length}):`];
3264
3862
  for (const i of impact.slice(0, 20)) {
3265
3863
  lines.push(` ${' '.repeat(i.depth - 1)}⬆ \`${i.id}\` (${i.type}, via ${i.relation})`);
@@ -3269,7 +3867,9 @@ const queryCodeGraph = {
3269
3867
 
3270
3868
  case 'topology': {
3271
3869
  const topo = ceg.getTopology();
3272
- if (topo.totalEntities === 0) return '代码实体图谱为空。需先执行 Bootstrap。';
3870
+ if (topo.totalEntities === 0) {
3871
+ return '代码实体图谱为空。需先执行 Bootstrap。';
3872
+ }
3273
3873
  const lines = ['📈 代码实体图谱概览:'];
3274
3874
  lines.push(' 实体:');
3275
3875
  for (const [type, count] of Object.entries(topo.entities)) {
@@ -3289,7 +3889,9 @@ const queryCodeGraph = {
3289
3889
  const type = params.entity_type || 'class';
3290
3890
  const edges = ceg.getEntityEdges(params.entity_id, type);
3291
3891
  const total = edges.outgoing.length + edges.incoming.length;
3292
- if (total === 0) return `\`${params.entity_id}\` 没有已知的图谱边。`;
3892
+ if (total === 0) {
3893
+ return `\`${params.entity_id}\` 没有已知的图谱边。`;
3894
+ }
3293
3895
  const lines = [`🔗 \`${params.entity_id}\` 的关系 (${total} 条):`];
3294
3896
  if (edges.outgoing.length > 0) {
3295
3897
  lines.push(' 出边:');