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
@@ -57,37 +57,37 @@ export class SkillAdvisor {
57
57
  try {
58
58
  const guardInsights = this.#analyzeGuardPatterns();
59
59
  analysisContext.guard = guardInsights.summary;
60
- suggestions.push(...guardInsights.suggestions.filter(
61
- s => !existingSkills.has(s.name),
62
- ));
63
- } catch { /* silent */ }
60
+ suggestions.push(...guardInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
61
+ } catch {
62
+ /* silent */
63
+ }
64
64
 
65
65
  // ── 维度 2: Memory 偏好积累 ──
66
66
  try {
67
67
  const memoryInsights = this.#analyzeMemoryPatterns();
68
68
  analysisContext.memory = memoryInsights.summary;
69
- suggestions.push(...memoryInsights.suggestions.filter(
70
- s => !existingSkills.has(s.name),
71
- ));
72
- } catch { /* silent */ }
69
+ suggestions.push(...memoryInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
70
+ } catch {
71
+ /* silent */
72
+ }
73
73
 
74
74
  // ── 维度 3: Recipe 分布与使用 ──
75
75
  try {
76
76
  const recipeInsights = this.#analyzeRecipePatterns();
77
77
  analysisContext.recipes = recipeInsights.summary;
78
- suggestions.push(...recipeInsights.suggestions.filter(
79
- s => !existingSkills.has(s.name),
80
- ));
81
- } catch { /* silent */ }
78
+ suggestions.push(...recipeInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
79
+ } catch {
80
+ /* silent */
81
+ }
82
82
 
83
83
  // ── 维度 4: 候选积压 ──
84
84
  try {
85
85
  const candidateInsights = this.#analyzeCandidatePatterns();
86
86
  analysisContext.candidates = candidateInsights.summary;
87
- suggestions.push(...candidateInsights.suggestions.filter(
88
- s => !existingSkills.has(s.name),
89
- ));
90
- } catch { /* silent */ }
87
+ suggestions.push(...candidateInsights.suggestions.filter((s) => !existingSkills.has(s.name)));
88
+ } catch {
89
+ /* silent */
90
+ }
91
91
 
92
92
  // 按优先级排序:high > medium > low
93
93
  const priorityOrder = { high: 0, medium: 1, low: 2 };
@@ -97,9 +97,10 @@ export class SkillAdvisor {
97
97
  suggestions,
98
98
  existingProjectSkills: [...existingSkills],
99
99
  analysisContext,
100
- hint: suggestions.length > 0
101
- ? `发现 ${suggestions.length} Skill 创建建议。你可以使用 autosnippet_skill({ operation: "create" }) 直接创建,也可以根据 rationale 自行判断是否需要。`
102
- : '当前项目使用模式暂无明确的 Skill 创建建议。继续使用后会积累更多信号。',
100
+ hint:
101
+ suggestions.length > 0
102
+ ? `发现 ${suggestions.length} 个 Skill 创建建议。你可以使用 autosnippet_skill({ operation: "create" }) 直接创建,也可以根据 rationale 自行判断是否需要。`
103
+ : '当前项目使用模式暂无明确的 Skill 创建建议。继续使用后会积累更多信号。',
103
104
  };
104
105
  }
105
106
 
@@ -109,11 +110,14 @@ export class SkillAdvisor {
109
110
 
110
111
  #analyzeGuardPatterns() {
111
112
  const suggestions = [];
112
- if (!this.#db) return { summary: 'DB 不可用', suggestions };
113
+ if (!this.#db) {
114
+ return { summary: 'DB 不可用', suggestions };
115
+ }
113
116
 
114
117
  try {
115
118
  // 查询 Guard 违规记录(audit_logs 中 action LIKE 'guard%' + result='violation')
116
- const rows = this.#db.prepare(`
119
+ const rows = this.#db
120
+ .prepare(`
117
121
  SELECT json_extract(operation_data, '$.ruleName') as ruleName,
118
122
  COUNT(*) as cnt
119
123
  FROM audit_logs
@@ -123,7 +127,8 @@ export class SkillAdvisor {
123
127
  HAVING cnt >= 3
124
128
  ORDER BY cnt DESC
125
129
  LIMIT 5
126
- `).all();
130
+ `)
131
+ .all();
127
132
 
128
133
  if (rows.length > 0) {
129
134
  const topRule = rows[0];
@@ -160,17 +165,29 @@ export class SkillAdvisor {
160
165
 
161
166
  try {
162
167
  const raw = fs.readFileSync(memoryPath, 'utf-8').trim();
163
- if (!raw) return { summary: '无 Memory 记录', suggestions };
168
+ if (!raw) {
169
+ return { summary: '无 Memory 记录', suggestions };
170
+ }
164
171
 
165
- const entries = raw.split('\n')
166
- .map(l => { try { return JSON.parse(l); } catch { return null; } })
172
+ const entries = raw
173
+ .split('\n')
174
+ .map((l) => {
175
+ try {
176
+ return JSON.parse(l);
177
+ } catch {
178
+ return null;
179
+ }
180
+ })
167
181
  .filter(Boolean);
168
182
 
169
- const preferences = entries.filter(e => e.type === 'preference');
183
+ const preferences = entries.filter((e) => e.type === 'preference');
170
184
 
171
185
  if (preferences.length >= 5) {
172
186
  // 有足够多的偏好积累 → 建议归纳为 Skill
173
- const sample = preferences.slice(-5).map(p => p.content).join('\n- ');
187
+ const sample = preferences
188
+ .slice(-5)
189
+ .map((p) => p.content)
190
+ .join('\n- ');
174
191
  suggestions.push({
175
192
  name: 'project-conventions',
176
193
  description: `项目约定总结 — 基于 ${preferences.length} 条团队偏好自动推荐`,
@@ -196,26 +213,32 @@ export class SkillAdvisor {
196
213
 
197
214
  #analyzeRecipePatterns() {
198
215
  const suggestions = [];
199
- if (!this.#db) return { summary: 'DB 不可用', suggestions };
216
+ if (!this.#db) {
217
+ return { summary: 'DB 不可用', suggestions };
218
+ }
200
219
 
201
220
  try {
202
221
  // 按 category 分布
203
- const categories = this.#db.prepare(`
222
+ const categories = this.#db
223
+ .prepare(`
204
224
  SELECT category, COUNT(*) as cnt
205
225
  FROM knowledge_entries
206
226
  WHERE lifecycle = 'active' AND category IS NOT NULL AND category != ''
207
227
  GROUP BY category
208
228
  ORDER BY cnt DESC
209
- `).all();
229
+ `)
230
+ .all();
210
231
 
211
232
  // 按 language 分布
212
- const languages = this.#db.prepare(`
233
+ const languages = this.#db
234
+ .prepare(`
213
235
  SELECT language, COUNT(*) as cnt
214
236
  FROM knowledge_entries
215
237
  WHERE lifecycle = 'active' AND language IS NOT NULL AND language != ''
216
238
  GROUP BY language
217
239
  ORDER BY cnt DESC
218
- `).all();
240
+ `)
241
+ .all();
219
242
 
220
243
  // 高频使用但无自定义 Skill 的 category
221
244
  const topCategory = categories[0];
@@ -227,14 +250,19 @@ export class SkillAdvisor {
227
250
  rationale: `项目中 ${topCategory.category} 类 Recipe 高达 ${topCategory.cnt} 条,占比最大。创建一个 Skill 汇总此类别的核心设计模式、常见用法和注意事项,让 AI 在处理相关代码时有更精准的参考。`,
228
251
  source: 'recipe_distribution',
229
252
  priority: 'low',
230
- signals: { category: topCategory.category, recipeCount: topCategory.cnt, allCategories: categories },
253
+ signals: {
254
+ category: topCategory.category,
255
+ recipeCount: topCategory.cnt,
256
+ allCategories: categories,
257
+ },
231
258
  });
232
259
  }
233
260
 
234
261
  // 高使用量 Recipe 统计(V3: stats JSON 中的 adoptions + applications)
235
262
  let hotRecipes = [];
236
263
  try {
237
- hotRecipes = this.#db.prepare(`
264
+ hotRecipes = this.#db
265
+ .prepare(`
238
266
  SELECT title, category,
239
267
  (COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) as total_usage
240
268
  FROM knowledge_entries
@@ -242,8 +270,11 @@ export class SkillAdvisor {
242
270
  AND (COALESCE(json_extract(stats, '$.adoptions'), 0) + COALESCE(json_extract(stats, '$.applications'), 0)) >= 5
243
271
  ORDER BY total_usage DESC
244
272
  LIMIT 10
245
- `).all();
246
- } catch { /* 查询失败时降级为空 */ }
273
+ `)
274
+ .all();
275
+ } catch {
276
+ /* 查询失败时降级为空 */
277
+ }
247
278
 
248
279
  return {
249
280
  summary: { categories: categories.length, languages, hotRecipeCount: hotRecipes.length },
@@ -260,17 +291,21 @@ export class SkillAdvisor {
260
291
 
261
292
  #analyzeCandidatePatterns() {
262
293
  const suggestions = [];
263
- if (!this.#db) return { summary: 'DB 不可用', suggestions };
294
+ if (!this.#db) {
295
+ return { summary: 'DB 不可用', suggestions };
296
+ }
264
297
 
265
298
  try {
266
299
  // V3: candidates 已合并到 knowledge_entries,ifecycle='pending' 即为候选
267
- const stats = this.#db.prepare(`
300
+ const stats = this.#db
301
+ .prepare(`
268
302
  SELECT
269
303
  COUNT(*) as total,
270
304
  SUM(CASE WHEN lifecycle='pending' THEN 1 ELSE 0 END) as pending,
271
305
  SUM(CASE WHEN lifecycle='deprecated' THEN 1 ELSE 0 END) as rejected
272
306
  FROM knowledge_entries
273
- `).get();
307
+ `)
308
+ .get();
274
309
 
275
310
  // 大量被拒绝 → 提示候选质量 Skill
276
311
  if (stats && stats.rejected >= 10) {
@@ -305,9 +340,11 @@ export class SkillAdvisor {
305
340
  const dir = getProjectSkillsPath(this.#projectRoot);
306
341
  try {
307
342
  fs.readdirSync(dir, { withFileTypes: true })
308
- .filter(d => d.isDirectory())
309
- .forEach(d => names.add(d.name));
310
- } catch { /* no project skills */ }
343
+ .filter((d) => d.isDirectory())
344
+ .forEach((d) => names.add(d.name));
345
+ } catch {
346
+ /* no project skills */
347
+ }
311
348
  return names;
312
349
  }
313
350
  }
@@ -16,8 +16,8 @@
16
16
  import fs from 'node:fs';
17
17
  import path from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
- import Logger from '../../infrastructure/logging/Logger.js';
20
19
  import { getProjectSkillsPath } from '../../infrastructure/config/Paths.js';
20
+ import Logger from '../../infrastructure/logging/Logger.js';
21
21
 
22
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
23
  const SKILLS_DIR = path.resolve(__dirname, '../../../skills');
@@ -31,18 +31,13 @@ function _getProjectSkillsDir() {
31
31
  return getProjectSkillsPath(projectRoot);
32
32
  }
33
33
 
34
- const HOOK_NAMES = [
35
- 'onCandidateSubmit',
36
- 'onRecipeCreated',
37
- 'onGuardCheck',
38
- 'onBootstrapComplete',
39
- ];
34
+ const HOOK_NAMES = ['onCandidateSubmit', 'onRecipeCreated', 'onGuardCheck', 'onBootstrapComplete'];
40
35
 
41
36
  export class SkillHooks {
42
37
  constructor() {
43
38
  this.logger = Logger.getInstance();
44
39
  /** @type {Map<string, Function[]>} hookName → [handler, ...] */
45
- this.hooks = new Map(HOOK_NAMES.map(n => [n, []]));
40
+ this.hooks = new Map(HOOK_NAMES.map((n) => [n, []]));
46
41
  }
47
42
 
48
43
  /**
@@ -83,14 +78,18 @@ export class SkillHooks {
83
78
  */
84
79
  async run(hookName, ...args) {
85
80
  const handlers = this.hooks.get(hookName);
86
- if (!handlers || handlers.length === 0) return undefined;
81
+ if (!handlers || handlers.length === 0) {
82
+ return undefined;
83
+ }
87
84
 
88
85
  let result;
89
86
  for (const handler of handlers) {
90
87
  try {
91
88
  result = await handler(...args);
92
89
  // 如果是 blocking hook 且返回 block=true,立即中断
93
- if (result?.block) return result;
90
+ if (result?.block) {
91
+ return result;
92
+ }
94
93
  } catch (err) {
95
94
  this.logger.warn(`SkillHook error in ${hookName}`, { error: err.message });
96
95
  }
@@ -111,16 +110,19 @@ export class SkillHooks {
111
110
  async #loadFromDir(dir, loaded) {
112
111
  let dirs;
113
112
  try {
114
- dirs = fs.readdirSync(dir, { withFileTypes: true })
115
- .filter(d => d.isDirectory())
116
- .map(d => d.name);
113
+ dirs = fs
114
+ .readdirSync(dir, { withFileTypes: true })
115
+ .filter((d) => d.isDirectory())
116
+ .map((d) => d.name);
117
117
  } catch {
118
118
  return; // 目录不存在
119
119
  }
120
120
 
121
121
  for (const name of dirs) {
122
122
  const hooksPath = path.join(dir, name, 'hooks.js');
123
- if (!fs.existsSync(hooksPath)) continue;
123
+ if (!fs.existsSync(hooksPath)) {
124
+ continue;
125
+ }
124
126
  try {
125
127
  const mod = await import(hooksPath);
126
128
  loaded.set(name, mod.default || mod);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @deprecated Moved to lib/platform/ios/snippet/PlaceholderConverter.js
3
+ * This re-export shim maintains backward compatibility.
4
+ */
5
+ export { PlaceholderConverter } from '../../platform/ios/snippet/PlaceholderConverter.js';
@@ -1,56 +1,58 @@
1
1
  /**
2
- * SnippetFactory — Xcode .codesnippet 生成工厂
3
- * 纯内存 Recipe → XML 转换器,不依赖 DB
2
+ * SnippetFactory — IDE 无关的 Snippet 生成工厂
4
3
  *
5
- * Snippet 是 Recipe 的附属产物:从 Recipe 取出 → 生成 XML → 同步 Xcode → 不再使用
4
+ * 职责:
5
+ * 1. Recipe → SnippetSpec (IDE 无关的中间表示)
6
+ * 2. 查询/列表操作 (listSnippets, getSnippet)
7
+ * 3. 委托 Codec 生成最终 IDE 格式 (generate, generateBatch)
8
+ *
9
+ * Codec 注册:
10
+ * factory.registerCodec(codec) — 注册 XcodeCodec / VSCodeCodec
11
+ * factory.generate(spec, 'xcode') — 按 target 生成
6
12
  */
7
13
 
8
- const TEMPLATE_TOKENS = ['{identifier}', '{title}', '{completion}', '{summary}', '{content}', '{language}'];
9
-
10
- const LANGUAGE_MAP = {
11
- swift: 'Xcode.SourceCodeLanguage.Swift',
12
- 'objective-c': 'Xcode.SourceCodeLanguage.Objective-C',
13
- objc: 'Xcode.SourceCodeLanguage.Objective-C',
14
- c: 'Xcode.SourceCodeLanguage.C',
15
- 'c++': 'Xcode.SourceCodeLanguage.C-Plus-Plus',
16
- javascript: 'Xcode.SourceCodeLanguage.JavaScript',
17
- };
18
-
19
- const SNIPPET_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
20
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
21
- <plist version="1.0">
22
- <dict>
23
- \t<key>IDECodeSnippetCompletionPrefix</key>
24
- \t<string>{completion}</string>
25
- \t<key>IDECodeSnippetCompletionScopes</key>
26
- \t<array>
27
- \t\t<string>All</string>
28
- \t</array>
29
- \t<key>IDECodeSnippetContents</key>
30
- \t<string>{content}</string>
31
- \t<key>IDECodeSnippetIdentifier</key>
32
- \t<string>{identifier}</string>
33
- \t<key>IDECodeSnippetLanguage</key>
34
- \t<string>{language}</string>
35
- \t<key>IDECodeSnippetSummary</key>
36
- \t<string>{summary}</string>
37
- \t<key>IDECodeSnippetTitle</key>
38
- \t<string>{title}</string>
39
- \t<key>IDECodeSnippetUserSnippet</key>
40
- \t<true/>
41
- \t<key>IDECodeSnippetVersion</key>
42
- \t<integer>2</integer>
43
- </dict>
44
- </plist>`;
14
+ import { XcodeCodec } from './codecs/XcodeCodec.js';
45
15
 
46
16
  export class SnippetFactory {
17
+ /** @type {Map<string, import('./codecs/SnippetCodec.js').SnippetCodec>} */
18
+ #codecs = new Map();
19
+
47
20
  /**
48
- * @param {object} [knowledgeRepository] — KnowledgeRepositoryImpl(可选,用于列表查询)
21
+ * @param {object} [knowledgeRepository] — KnowledgeRepositoryImpl(可选)
49
22
  */
50
23
  constructor(knowledgeRepository) {
51
24
  this._recipeRepo = knowledgeRepository || null;
52
25
  }
53
26
 
27
+ // ─────────────── Codec 注册 ───────────────
28
+
29
+ /**
30
+ * 注册一个 IDE codec
31
+ * @param {import('./codecs/SnippetCodec.js').SnippetCodec} codec
32
+ */
33
+ registerCodec(codec) {
34
+ this.#codecs.set(codec.id, codec);
35
+ }
36
+
37
+ /**
38
+ * 获取已注册的 codec
39
+ * @param {string} target — 'xcode' | 'vscode'
40
+ * @returns {import('./codecs/SnippetCodec.js').SnippetCodec|undefined}
41
+ */
42
+ getCodec(target) {
43
+ return this.#codecs.get(target);
44
+ }
45
+
46
+ /**
47
+ * 获取所有已注册 codec 的 ID 列表
48
+ * @returns {string[]}
49
+ */
50
+ getRegisteredTargets() {
51
+ return [...this.#codecs.keys()];
52
+ }
53
+
54
+ // ─────────────── 依赖注入 ───────────────
55
+
54
56
  /**
55
57
  * 运行时注入 knowledgeRepository(用于延迟绑定场景)
56
58
  */
@@ -61,17 +63,23 @@ export class SnippetFactory {
61
63
  // ─────────────── Recipe → Snippet 查询 ───────────────
62
64
 
63
65
  /**
64
- * 从 Recipe 列表实时生成 Snippet 列表(取代原 DB CRUD)
66
+ * 从 Recipe 列表实时生成 Snippet 列表
65
67
  * @param {object} [filters] — { language, category, keyword }
66
68
  * @param {object} [pagination]
67
69
  * @returns {Promise<Array>}
68
70
  */
69
71
  async listSnippets(filters = {}, pagination = { page: 1, pageSize: 50 }) {
70
- if (!this._recipeRepo) return [];
72
+ if (!this._recipeRepo) {
73
+ return [];
74
+ }
71
75
 
72
76
  const dbFilters = { status: 'active' };
73
- if (filters.language) dbFilters.language = filters.language;
74
- if (filters.category) dbFilters.category = filters.category;
77
+ if (filters.language) {
78
+ dbFilters.language = filters.language;
79
+ }
80
+ if (filters.category) {
81
+ dbFilters.category = filters.category;
82
+ }
75
83
 
76
84
  let result;
77
85
  if (filters.keyword) {
@@ -81,55 +89,70 @@ export class SnippetFactory {
81
89
  }
82
90
 
83
91
  const recipes = result?.data || result?.items || [];
84
- return recipes.map(r => this.fromRecipe(r));
92
+ return recipes.map((r) => this.fromRecipe(r));
85
93
  }
86
94
 
87
95
  /**
88
96
  * 从单个 Recipe ID 实时生成 Snippet
89
97
  */
90
98
  async getSnippet(recipeId) {
91
- if (!this._recipeRepo) return null;
99
+ if (!this._recipeRepo) {
100
+ return null;
101
+ }
92
102
  const recipe = await this._recipeRepo.findById(recipeId);
93
- if (!recipe) return null;
103
+ if (!recipe) {
104
+ return null;
105
+ }
94
106
  return this.fromRecipe(recipe);
95
107
  }
96
108
 
97
- // ─────────────── XML 生成 ───────────────
109
+ // ─────────────── Codec 委托生成 ───────────────
98
110
 
99
111
  /**
100
- * 从 spec 数据生成 .codesnippet XML 内容
101
- * @param {object} spec
102
- * @param {string} spec.identifier唯一标识 (如 "com.autosnippet.xxx")
103
- * @param {string} spec.title — 片段标题
104
- * @param {string} spec.completion — 触发补全前缀
105
- * @param {string} spec.summary — 描述
106
- * @param {string|string[]} spec.code — 代码内容(字符串或行数组)
107
- * @param {string} spec.language — 语言标识符
108
- * @returns {string} — XML plist 内容
112
+ * 使用指定 codec 从 spec 生成 IDE 格式内容
113
+ * @param {object} spec — SnippetSpec
114
+ * @param {string} [target='xcode']codec ID
115
+ * @returns {string}
109
116
  */
110
- generate(spec) {
111
- if (!spec?.identifier || !spec?.code) {
112
- throw new Error('Snippet spec must have identifier and code');
113
- }
117
+ generate(spec, target = 'xcode') {
118
+ const codec = this.#resolveCodec(target);
119
+ return codec.generate(spec);
120
+ }
114
121
 
115
- const content = Array.isArray(spec.code) ? spec.code.join('\n') : spec.code;
116
- const languageKey = LANGUAGE_MAP[(spec.language || 'swift').toLowerCase()] || LANGUAGE_MAP.swift;
122
+ /**
123
+ * 批量生成 (委托 codec)
124
+ * @param {Array} recipes
125
+ * @param {string} [target='xcode']
126
+ * @returns {Array<{ filename: string, content: string, spec: object }> | { filename: string, content: string, specs: object[] }}
127
+ */
128
+ generateBatch(recipes, target = 'xcode') {
129
+ const codec = this.#resolveCodec(target);
130
+ const specs = recipes.map((r) => this.fromRecipe(r));
117
131
 
118
- let xml = SNIPPET_TEMPLATE;
119
- xml = xml.replace('{identifier}', this.#escapeXml(spec.identifier));
120
- xml = xml.replace('{title}', this.#escapeXml(spec.title || spec.identifier));
121
- xml = xml.replace('{completion}', this.#escapeXml(spec.completion || spec.identifier));
122
- xml = xml.replace('{summary}', this.#escapeXml(spec.summary || ''));
123
- xml = xml.replace('{content}', this.#escapeXml(content));
124
- xml = xml.replace('{language}', languageKey);
132
+ const bundleFilename = codec.getBundleFilename();
133
+ if (bundleFilename) {
134
+ // VSCode 模式: bundle 文件
135
+ return {
136
+ filename: bundleFilename,
137
+ content: codec.generateBundle(specs),
138
+ specs,
139
+ };
140
+ }
125
141
 
126
- return xml;
142
+ // Xcode 模式: 每个 snippet 一个文件
143
+ return specs.map((spec) => ({
144
+ filename: `${spec.identifier}${codec.fileExtension}`,
145
+ content: codec.generate(spec),
146
+ spec,
147
+ }));
127
148
  }
128
149
 
150
+ // ─────────────── Recipe → SnippetSpec ───────────────
151
+
129
152
  /**
130
- * 从 Recipe/Candidate 生成 snippet spec
153
+ * 从 Recipe/Candidate 生成 IDE 无关的 snippet spec
131
154
  * @param {object} recipe — { id, title, trigger, code, description, language }
132
- * @returns {object} — snippet spec
155
+ * @returns {object} — SnippetSpec
133
156
  */
134
157
  fromRecipe(recipe) {
135
158
  return {
@@ -138,38 +161,32 @@ export class SnippetFactory {
138
161
  completion: recipe.trigger || this.#slugify(recipe.title),
139
162
  summary: recipe.description || recipe.summary || '',
140
163
  code: recipe.code,
141
- language: recipe.language || 'swift',
164
+ language: recipe.language || 'unknown',
142
165
  };
143
166
  }
144
167
 
168
+ // ─────────────── Private ───────────────
169
+
145
170
  /**
146
- * 批量生成
147
- * @param {Array} recipes
148
- * @returns {Array<{ filename: string, content: string, spec: object }>}
171
+ * @param {string} target
172
+ * @returns {import('./codecs/SnippetCodec.js').SnippetCodec}
149
173
  */
150
- generateBatch(recipes) {
151
- return recipes.map(recipe => {
152
- const spec = this.fromRecipe(recipe);
153
- return {
154
- filename: `${spec.identifier}.codesnippet`,
155
- content: this.generate(spec),
156
- spec,
157
- };
158
- });
159
- }
160
-
161
- #escapeXml(str) {
162
- if (!str) return '';
163
- return String(str)
164
- .replace(/&/g, '&amp;')
165
- .replace(/</g, '&lt;')
166
- .replace(/>/g, '&gt;')
167
- .replace(/"/g, '&quot;')
168
- .replace(/'/g, '&apos;');
174
+ #resolveCodec(target) {
175
+ const codec = this.#codecs.get(target);
176
+ if (!codec) {
177
+ throw new Error(`No codec registered for target "${target}". Available: [${this.getRegisteredTargets().join(', ')}]`);
178
+ }
179
+ return codec;
169
180
  }
170
181
 
171
182
  #slugify(str) {
172
- if (!str) return 'unnamed';
173
- return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 50);
183
+ if (!str) {
184
+ return 'unnamed';
185
+ }
186
+ return str
187
+ .toLowerCase()
188
+ .replace(/[^a-z0-9]+/g, '-')
189
+ .replace(/(^-|-$)/g, '')
190
+ .slice(0, 50);
174
191
  }
175
192
  }