autosnippet 3.0.0 → 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 +759 -243
  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
@@ -3,9 +3,10 @@
3
3
  * 从 Markdown 文件提取 Recipe 元数据、代码块、语义标签、质量评分
4
4
  */
5
5
 
6
- import { readFileSync, existsSync } from 'node:fs';
7
6
  import { createHash } from 'node:crypto';
8
- import { basename, extname } from 'node:path';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { basename } from 'node:path';
9
+ import { LanguageService } from '../../shared/LanguageService.js';
9
10
 
10
11
  export class RecipeExtractor {
11
12
  #options;
@@ -25,7 +26,9 @@ export class RecipeExtractor {
25
26
  * @returns {object|null}
26
27
  */
27
28
  extractFromFile(filePath) {
28
- if (!existsSync(filePath)) return null;
29
+ if (!existsSync(filePath)) {
30
+ return null;
31
+ }
29
32
  const content = readFileSync(filePath, 'utf-8');
30
33
  return this.extractFromContent(content, basename(filePath), filePath);
31
34
  }
@@ -55,22 +58,25 @@ export class RecipeExtractor {
55
58
 
56
59
  // 6. 语义标签
57
60
  const semanticTags = this.#options.extractSemanticTags
58
- ? this.#extractSemanticTags(body, codeBlocks) : [];
61
+ ? this.#extractSemanticTags(body, codeBlocks)
62
+ : [];
59
63
 
60
64
  // 7. 代码质量分析
61
65
  const quality = this.#options.analyzeCodeQuality
62
- ? this.#analyzeCodeQuality(codeBlocks, body) : {};
66
+ ? this.#analyzeCodeQuality(codeBlocks, body)
67
+ : {};
63
68
 
64
69
  // 8. 内容 hash
65
70
  const contentHash = this.#options.contentHashEnabled
66
- ? createHash('sha256').update(content).digest('hex').slice(0, 16) : null;
71
+ ? createHash('sha256').update(content).digest('hex').slice(0, 16)
72
+ : null;
67
73
 
68
74
  return {
69
75
  id: frontmatter.id || this.#generateId(filePath || filename),
70
76
  title,
71
77
  language,
72
78
  category,
73
- code: codeBlocks.map(b => b.code).join('\n\n'),
79
+ code: codeBlocks.map((b) => b.code).join('\n\n'),
74
80
  description: frontmatter.description || this.#extractDescription(body),
75
81
  content: body,
76
82
  filePath,
@@ -90,7 +96,9 @@ export class RecipeExtractor {
90
96
 
91
97
  #parseFrontmatter(content) {
92
98
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
93
- if (!match) return { frontmatter: {}, body: content };
99
+ if (!match) {
100
+ return { frontmatter: {}, body: content };
101
+ }
94
102
 
95
103
  const frontmatter = {};
96
104
  const lines = match[1].split('\n');
@@ -101,11 +109,19 @@ export class RecipeExtractor {
101
109
  let value = line.slice(colonIdx + 1).trim();
102
110
  // 简单 YAML 值解析
103
111
  if (value.startsWith('[') && value.endsWith(']')) {
104
- value = value.slice(1, -1).split(',').map(s => s.trim().replace(/^['"]|['"]$/g, ''));
105
- } else if (value === 'true') value = true;
106
- else if (value === 'false') value = false;
107
- else if (/^\d+$/.test(value)) value = parseInt(value, 10);
108
- else value = value.replace(/^['"]|['"]$/g, '');
112
+ value = value
113
+ .slice(1, -1)
114
+ .split(',')
115
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
116
+ } else if (value === 'true') {
117
+ value = true;
118
+ } else if (value === 'false') {
119
+ value = false;
120
+ } else if (/^\d+$/.test(value)) {
121
+ value = parseInt(value, 10);
122
+ } else {
123
+ value = value.replace(/^['"]|['"]$/g, '');
124
+ }
109
125
  frontmatter[key] = value;
110
126
  }
111
127
  }
@@ -142,15 +158,34 @@ export class RecipeExtractor {
142
158
  // 从代码块推断
143
159
  if (codeBlocks.length > 0) {
144
160
  const lang = codeBlocks[0].language;
145
- if (lang && lang !== 'text') return lang;
161
+ if (lang && lang !== 'text') {
162
+ return lang;
163
+ }
164
+ }
165
+ // 从文件名推断 —— 委托给 LanguageService
166
+ const detected = LanguageService.inferLang(filename);
167
+ if (detected !== 'unknown') {
168
+ return detected;
146
169
  }
147
- // 从文件名推断
148
- const ext = extname(filename).toLowerCase();
149
- const map = { '.swift': 'swift', '.js': 'javascript', '.ts': 'typescript', '.py': 'python' };
150
- if (map[ext]) return map[ext];
151
170
  // 从内容关键词推断
152
- if (/\bSwiftUI\b|\bUIKit\b|\bfunc\s/.test(body)) return 'swift';
153
- if (/\bimport\s+React\b|\bconst\s/.test(body)) return 'javascript';
171
+ if (/\bSwiftUI\b|\bUIKit\b|\bfunc\s/.test(body)) {
172
+ return 'swift';
173
+ }
174
+ if (/\bimport\s+React\b|\bconst\s/.test(body)) {
175
+ return 'javascript';
176
+ }
177
+ if (/\bdef\s+\w+.*:/.test(body)) {
178
+ return 'python';
179
+ }
180
+ if (/\bclass\s+\w+.*\{/.test(body) && /\bimport\s+java\./.test(body)) {
181
+ return 'java';
182
+ }
183
+ if (/\bpackage\s+\w+/.test(body) && /\bfunc\s/.test(body)) {
184
+ return 'go';
185
+ }
186
+ if (/\bfun\s+\w+/.test(body) && /\bval\s|\bvar\s/.test(body)) {
187
+ return 'kotlin';
188
+ }
154
189
  return 'markdown';
155
190
  }
156
191
 
@@ -159,21 +194,42 @@ export class RecipeExtractor {
159
194
  #inferCategory(title, body, language) {
160
195
  const text = `${title} ${body}`.toLowerCase();
161
196
  const categories = [
162
- { name: 'networking', keywords: ['network', 'api', 'http', 'url', 'fetch', 'request', 'response'] },
163
- { name: 'ui', keywords: ['ui', 'view', 'button', 'label', 'layout', 'component', 'render'] },
164
- { name: 'storage', keywords: ['storage', 'database', 'cache', 'persist', 'save', 'file', 'coredata'] },
165
- { name: 'testing', keywords: ['test', 'spec', 'assert', 'mock', 'expect', 'coverage'] },
166
- { name: 'security', keywords: ['security', 'auth', 'encrypt', 'token', 'permission', 'keychain'] },
167
- { name: 'performance', keywords: ['performance', 'optimize', 'speed', 'memory', 'async', 'concurrency'] },
168
- { name: 'error-handling', keywords: ['error', 'exception', 'catch', 'throw', 'fault', 'recovery'] },
169
- { name: 'architecture', keywords: ['mvvm', 'mvc', 'pattern', 'dependency', 'inject', 'protocol', 'design'] },
197
+ {
198
+ name: 'networking',
199
+ keywords: ['network', 'api', 'http', 'url', 'fetch', 'request', 'response'],
200
+ },
201
+ { name: 'ui', keywords: ['ui', 'view', 'button', 'label', 'layout', 'component', 'render'] },
202
+ {
203
+ name: 'storage',
204
+ keywords: ['storage', 'database', 'cache', 'persist', 'save', 'file', 'coredata'],
205
+ },
206
+ { name: 'testing', keywords: ['test', 'spec', 'assert', 'mock', 'expect', 'coverage'] },
207
+ {
208
+ name: 'security',
209
+ keywords: ['security', 'auth', 'encrypt', 'token', 'permission', 'keychain'],
210
+ },
211
+ {
212
+ name: 'performance',
213
+ keywords: ['performance', 'optimize', 'speed', 'memory', 'async', 'concurrency'],
214
+ },
215
+ {
216
+ name: 'error-handling',
217
+ keywords: ['error', 'exception', 'catch', 'throw', 'fault', 'recovery'],
218
+ },
219
+ {
220
+ name: 'architecture',
221
+ keywords: ['mvvm', 'mvc', 'pattern', 'dependency', 'inject', 'protocol', 'design'],
222
+ },
170
223
  ];
171
224
 
172
225
  let bestCat = 'general';
173
226
  let bestScore = 0;
174
227
  for (const { name, keywords } of categories) {
175
- const score = keywords.filter(kw => text.includes(kw)).length;
176
- if (score > bestScore) { bestScore = score; bestCat = name; }
228
+ const score = keywords.filter((kw) => text.includes(kw)).length;
229
+ if (score > bestScore) {
230
+ bestScore = score;
231
+ bestCat = name;
232
+ }
177
233
  }
178
234
  return bestCat;
179
235
  }
@@ -183,23 +239,25 @@ export class RecipeExtractor {
183
239
  #extractSemanticTags(body, codeBlocks) {
184
240
  const tags = new Set();
185
241
  const text = body.toLowerCase();
186
- const code = codeBlocks.map(b => b.code).join('\n');
242
+ const code = codeBlocks.map((b) => b.code).join('\n');
187
243
 
188
244
  // 关键词标签
189
245
  const tagPatterns = [
190
- { tag: 'async', pattern: /\basync\b|\bawait\b|\bPromise\b/i },
246
+ { tag: 'async', pattern: /\basync\b|\bawait\b|\bPromise\b/i },
191
247
  { tag: 'error-handling', pattern: /\btry\b.*\bcatch\b|\bthrow\b|\bError\b/i },
192
- { tag: 'generics', pattern: /\b<\w+>\b|<T>|<Element>/i },
193
- { tag: 'protocol', pattern: /\bprotocol\b|\binterface\b|\bimplements\b/i },
194
- { tag: 'closure', pattern: /\bclosure\b|\bcallback\b|=>\s*{/i },
195
- { tag: 'testing', pattern: /\bXCTest\b|\bdescribe\b|\bit\b.*\bshould\b/i },
196
- { tag: 'reactive', pattern: /\bCombine\b|\bRxSwift\b|\bObservable\b|\buseState\b/i },
197
- { tag: 'caching', pattern: /\bcache\b|\bNSCache\b|\bmemoize\b/i },
248
+ { tag: 'generics', pattern: /\b<\w+>\b|<T>|<Element>/i },
249
+ { tag: 'protocol', pattern: /\bprotocol\b|\binterface\b|\bimplements\b/i },
250
+ { tag: 'closure', pattern: /\bclosure\b|\bcallback\b|=>\s*{/i },
251
+ { tag: 'testing', pattern: /\bXCTest\b|\bdescribe\b|\bit\b.*\bshould\b/i },
252
+ { tag: 'reactive', pattern: /\bCombine\b|\bRxSwift\b|\bObservable\b|\buseState\b/i },
253
+ { tag: 'caching', pattern: /\bcache\b|\bNSCache\b|\bmemoize\b/i },
198
254
  { tag: 'concurrency', pattern: /\bDispatchQueue\b|\bTask\s*{|\bactor\b/i },
199
255
  ];
200
256
 
201
257
  for (const { tag, pattern } of tagPatterns) {
202
- if (pattern.test(text) || pattern.test(code)) tags.add(tag);
258
+ if (pattern.test(text) || pattern.test(code)) {
259
+ tags.add(tag);
260
+ }
203
261
  }
204
262
 
205
263
  return [...tags];
@@ -208,22 +266,34 @@ export class RecipeExtractor {
208
266
  // --- Quality Analysis ---
209
267
 
210
268
  #analyzeCodeQuality(codeBlocks, body) {
211
- if (codeBlocks.length === 0) return { score: 0.5, hasCode: false };
269
+ if (codeBlocks.length === 0) {
270
+ return { score: 0.5, hasCode: false };
271
+ }
212
272
 
213
- const allCode = codeBlocks.map(b => b.code).join('\n');
273
+ const allCode = codeBlocks.map((b) => b.code).join('\n');
214
274
  let score = 0.5;
215
275
 
216
276
  // 有测试 +0.1
217
- if (/test|spec|assert|expect/i.test(allCode)) score += 0.1;
277
+ if (/test|spec|assert|expect/i.test(allCode)) {
278
+ score += 0.1;
279
+ }
218
280
  // 有文档注释 +0.1
219
- if (/\/\/\/|\/\*\*|"""/.test(allCode)) score += 0.1;
281
+ if (/\/\/\/|\/\*\*|"""/.test(allCode)) {
282
+ score += 0.1;
283
+ }
220
284
  // 有错误处理 +0.1
221
- if (/try|catch|throw|guard|Result</.test(allCode)) score += 0.1;
285
+ if (/try|catch|throw|guard|Result</.test(allCode)) {
286
+ score += 0.1;
287
+ }
222
288
  // 合理长度 +0.1
223
289
  const lines = allCode.split('\n').length;
224
- if (lines >= 5 && lines <= 200) score += 0.1;
290
+ if (lines >= 5 && lines <= 200) {
291
+ score += 0.1;
292
+ }
225
293
  // 无安全红旗 +0.1
226
- if (!/eval\(|exec\(|force_unwrap/.test(allCode)) score += 0.1;
294
+ if (!/eval\(|exec\(|force_unwrap/.test(allCode)) {
295
+ score += 0.1;
296
+ }
227
297
 
228
298
  return {
229
299
  score: Math.min(score, 1.0),
@@ -245,11 +315,22 @@ export class RecipeExtractor {
245
315
  let inCode = false;
246
316
 
247
317
  for (const line of lines) {
248
- if (line.startsWith('```')) { inCode = !inCode; continue; }
249
- if (inCode) continue;
250
- if (line.startsWith('#')) continue;
251
- if (line.trim().length > 0) paragraphs.push(line.trim());
252
- if (paragraphs.length >= 3) break;
318
+ if (line.startsWith('```')) {
319
+ inCode = !inCode;
320
+ continue;
321
+ }
322
+ if (inCode) {
323
+ continue;
324
+ }
325
+ if (line.startsWith('#')) {
326
+ continue;
327
+ }
328
+ if (line.trim().length > 0) {
329
+ paragraphs.push(line.trim());
330
+ }
331
+ if (paragraphs.length >= 3) {
332
+ break;
333
+ }
253
334
  }
254
335
 
255
336
  return paragraphs.join(' ').slice(0, 300) || '';
@@ -10,14 +10,14 @@
10
10
  * 4. `asd upgrade` 时作为升级步骤执行
11
11
  */
12
12
 
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { DELIVERY_RANK, KNOWLEDGE_CONFIDENCE } from '../../shared/constants.js';
13
16
  import { KnowledgeCompressor } from './KnowledgeCompressor.js';
14
- import { TopicClassifier } from './TopicClassifier.js';
15
17
  import { RulesGenerator } from './RulesGenerator.js';
16
18
  import { SkillsSyncer } from './SkillsSyncer.js';
17
- import { estimateTokens, BUDGET } from './TokenBudget.js';
18
- import { KNOWLEDGE_CONFIDENCE, DELIVERY_RANK } from '../../shared/constants.js';
19
- import path from 'node:path';
20
- import fs from 'node:fs';
19
+ import { BUDGET } from './TokenBudget.js';
20
+ import { TopicClassifier } from './TopicClassifier.js';
21
21
 
22
22
  export class CursorDeliveryPipeline {
23
23
  /**
@@ -62,7 +62,9 @@ export class CursorDeliveryPipeline {
62
62
 
63
63
  // 2. 分类:rules vs patterns vs facts vs documents
64
64
  const { rules, patterns, documents } = this._classify(entries);
65
- this.logger.info?.(`[CursorDelivery] Classified: ${rules.length} rules, ${patterns.length} patterns, ${documents.length} documents`);
65
+ this.logger.info?.(
66
+ `[CursorDelivery] Classified: ${rules.length} rules, ${patterns.length} patterns, ${documents.length} documents`
67
+ );
66
68
 
67
69
  // 3. 清理旧的动态生成文件
68
70
  this.rulesGenerator.cleanDynamicFiles();
@@ -91,11 +93,13 @@ export class CursorDeliveryPipeline {
91
93
  stats.totalTokensUsed = channelA.tokensUsed + channelB.totalTokens;
92
94
  stats.duration = Date.now() - startTime;
93
95
 
94
- this.logger.info?.(`[CursorDelivery] Done in ${stats.duration}ms — ` +
95
- `A: ${channelA.rulesCount} rules (${channelA.tokensUsed} tokens), ` +
96
- `B: ${channelB.topicCount} topics (${channelB.totalTokens} tokens), ` +
97
- `C: ${channelC.synced} skills synced, ` +
98
- `D: ${channelD.documentsCount} documents`);
96
+ this.logger.info?.(
97
+ `[CursorDelivery] Done in ${stats.duration}ms ` +
98
+ `A: ${channelA.rulesCount} rules (${channelA.tokensUsed} tokens), ` +
99
+ `B: ${channelB.topicCount} topics (${channelB.totalTokens} tokens), ` +
100
+ `C: ${channelC.synced} skills synced, ` +
101
+ `D: ${channelD.documentsCount} documents`
102
+ );
99
103
 
100
104
  return { channelA, channelB, channelC, channelD, stats };
101
105
  } catch (error) {
@@ -133,7 +137,7 @@ export class CursorDeliveryPipeline {
133
137
  );
134
138
  const pendingItems = this._extractItems(pending);
135
139
  // 过滤高置信度 pending(quality.confidence >= PENDING_MIN 或无 quality 字段)
136
- const highConfPending = pendingItems.filter(e => {
140
+ const highConfPending = pendingItems.filter((e) => {
137
141
  const conf = e.quality?.confidence;
138
142
  return conf === undefined || conf === null || conf >= KNOWLEDGE_CONFIDENCE.PENDING_MIN;
139
143
  });
@@ -150,9 +154,15 @@ export class CursorDeliveryPipeline {
150
154
  * @private
151
155
  */
152
156
  _extractItems(result) {
153
- if (Array.isArray(result)) return result;
154
- if (result?.items) return result.items;
155
- if (result?.data) return result.data;
157
+ if (Array.isArray(result)) {
158
+ return result;
159
+ }
160
+ if (result?.items) {
161
+ return result.items;
162
+ }
163
+ if (result?.data) {
164
+ return result.data;
165
+ }
156
166
  return [];
157
167
  }
158
168
 
@@ -162,7 +172,10 @@ export class CursorDeliveryPipeline {
162
172
  * @private
163
173
  */
164
174
  _classify(entries) {
165
- const rules = [], patterns = [], facts = [], documents = [];
175
+ const rules = [],
176
+ patterns = [],
177
+ facts = [],
178
+ documents = [];
166
179
  for (const entry of entries) {
167
180
  if (entry.knowledgeType === 'dev-document') {
168
181
  documents.push(entry);
@@ -171,7 +184,7 @@ export class CursorDeliveryPipeline {
171
184
  } else if (entry.kind === 'fact') {
172
185
  facts.push(entry);
173
186
  } else {
174
- patterns.push(entry); // 无 kind 或 kind='pattern' → pattern
187
+ patterns.push(entry); // 无 kind 或 kind='pattern' → pattern
175
188
  }
176
189
  }
177
190
  return { rules, patterns, facts, documents };
@@ -195,10 +208,16 @@ export class CursorDeliveryPipeline {
195
208
  */
196
209
  _rankScore(entry) {
197
210
  let score = 0;
198
- score += (entry.quality?.confidence || KNOWLEDGE_CONFIDENCE.RANK_DEFAULT) * DELIVERY_RANK.CONFIDENCE_WEIGHT;
211
+ score +=
212
+ (entry.quality?.confidence || KNOWLEDGE_CONFIDENCE.RANK_DEFAULT) *
213
+ DELIVERY_RANK.CONFIDENCE_WEIGHT;
199
214
  score += (entry.quality?.authorityScore || 0) * DELIVERY_RANK.AUTHORITY_WEIGHT;
200
- score += Math.min(entry.stats?.useCount || 0, DELIVERY_RANK.USE_COUNT_MAX) * DELIVERY_RANK.USE_COUNT_WEIGHT;
201
- if (entry.lifecycle === 'active') score += DELIVERY_RANK.ACTIVE_BONUS;
215
+ score +=
216
+ Math.min(entry.stats?.useCount || 0, DELIVERY_RANK.USE_COUNT_MAX) *
217
+ DELIVERY_RANK.USE_COUNT_WEIGHT;
218
+ if (entry.lifecycle === 'active') {
219
+ score += DELIVERY_RANK.ACTIVE_BONUS;
220
+ }
202
221
  return score;
203
222
  }
204
223
 
@@ -216,7 +235,9 @@ export class CursorDeliveryPipeline {
216
235
  }
217
236
 
218
237
  const result = this.rulesGenerator.writeAlwaysOnRules(ruleLines);
219
- this.logger.info?.(`[CursorDelivery] Channel A: ${result.rulesCount} rules → ${result.filePath}`);
238
+ this.logger.info?.(
239
+ `[CursorDelivery] Channel A: ${result.rulesCount} rules → ${result.filePath}`
240
+ );
220
241
  return result;
221
242
  }
222
243
 
@@ -241,7 +262,9 @@ export class CursorDeliveryPipeline {
241
262
 
242
263
  // 压缩为 When/Do/Don't
243
264
  const compressed = this.compressor.compressToWhenDoDont(top);
244
- if (compressed.length === 0) continue;
265
+ if (compressed.length === 0) {
266
+ continue;
267
+ }
245
268
 
246
269
  // 格式化为 Markdown
247
270
  const body = this.compressor.formatWhenDoDont(compressed);
@@ -255,9 +278,14 @@ export class CursorDeliveryPipeline {
255
278
  result.topicCount++;
256
279
  result.patternsCount += compressed.length;
257
280
  result.totalTokens += writeResult.tokensUsed;
258
- result.topics[topic] = { patternsCount: compressed.length, tokensUsed: writeResult.tokensUsed };
281
+ result.topics[topic] = {
282
+ patternsCount: compressed.length,
283
+ tokensUsed: writeResult.tokensUsed,
284
+ };
259
285
 
260
- this.logger.info?.(`[CursorDelivery] Channel B: ${topic} — ${compressed.length} patterns → ${writeResult.filePath}`);
286
+ this.logger.info?.(
287
+ `[CursorDelivery] Channel B: ${topic} — ${compressed.length} patterns → ${writeResult.filePath}`
288
+ );
261
289
  }
262
290
 
263
291
  return result;
@@ -272,7 +300,7 @@ export class CursorDeliveryPipeline {
272
300
  const syncResult = await this.skillsSyncer.sync();
273
301
  this.logger.info?.(
274
302
  `[CursorDelivery] Channel C: ${syncResult.synced.length} synced, ` +
275
- `${syncResult.skipped.length} skipped, ${syncResult.errors.length} errors`
303
+ `${syncResult.skipped.length} skipped, ${syncResult.errors.length} errors`
276
304
  );
277
305
  return {
278
306
  synced: syncResult.synced.length,
@@ -282,7 +310,12 @@ export class CursorDeliveryPipeline {
282
310
  };
283
311
  } catch (err) {
284
312
  this.logger.error?.(`[CursorDelivery] Channel C error: ${err.message}`);
285
- return { synced: 0, skipped: 0, errors: 1, details: { synced: [], skipped: [], errors: [err.message] } };
313
+ return {
314
+ synced: 0,
315
+ skipped: 0,
316
+ errors: 1,
317
+ details: { synced: [], skipped: [], errors: [err.message] },
318
+ };
286
319
  }
287
320
  }
288
321
 
@@ -345,7 +378,9 @@ export class CursorDeliveryPipeline {
345
378
  '---',
346
379
  '',
347
380
  markdown,
348
- ].filter(Boolean).join('\n');
381
+ ]
382
+ .filter(Boolean)
383
+ .join('\n');
349
384
 
350
385
  const docPath = path.join(refsDir, `${slug}.md`);
351
386
  fs.writeFileSync(docPath, docContent, 'utf8');
@@ -359,10 +394,12 @@ export class CursorDeliveryPipeline {
359
394
  skillLines.push('For full-text search across all documents:');
360
395
  skillLines.push('- `autosnippet_search("your query")`');
361
396
 
362
- fs.writeFileSync(path.join(devdocsDir, 'SKILL.md'), skillLines.join('\n') + '\n', 'utf8');
397
+ fs.writeFileSync(path.join(devdocsDir, 'SKILL.md'), `${skillLines.join('\n')}\n`, 'utf8');
363
398
  result.documentsCount = documents.length;
364
399
 
365
- this.logger.info?.(`[CursorDelivery] Channel D: ${result.documentsCount} documents → ${refsDir}`);
400
+ this.logger.info?.(
401
+ `[CursorDelivery] Channel D: ${result.documentsCount} documents → ${refsDir}`
402
+ );
366
403
  return result;
367
404
  }
368
405
 
@@ -371,11 +408,13 @@ export class CursorDeliveryPipeline {
371
408
  * @private
372
409
  */
373
410
  _slugify(text) {
374
- return text
375
- .toLowerCase()
376
- .replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
377
- .replace(/^-+|-+$/g, '')
378
- .substring(0, 80) || 'untitled';
411
+ return (
412
+ text
413
+ .toLowerCase()
414
+ .replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
415
+ .replace(/^-+|-+$/g, '')
416
+ .substring(0, 80) || 'untitled'
417
+ );
379
418
  }
380
419
 
381
420
  /**
@@ -395,9 +434,13 @@ export class CursorDeliveryPipeline {
395
434
  const targetRulesDir = path.join(targetDir, 'rules');
396
435
  fs.mkdirSync(targetRulesDir, { recursive: true });
397
436
  for (const file of fs.readdirSync(cursorRulesDir)) {
398
- if (!file.startsWith('autosnippet-')) continue;
437
+ if (!file.startsWith('autosnippet-')) {
438
+ continue;
439
+ }
399
440
  const src = path.join(cursorRulesDir, file);
400
- if (!fs.statSync(src).isFile()) continue;
441
+ if (!fs.statSync(src).isFile()) {
442
+ continue;
443
+ }
401
444
  // .mdc → .md
402
445
  const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
403
446
  fs.copyFileSync(src, path.join(targetRulesDir, destName));
@@ -409,7 +452,9 @@ export class CursorDeliveryPipeline {
409
452
  if (fs.existsSync(cursorSkillsDir)) {
410
453
  const targetSkillsDir = path.join(targetDir, 'skills');
411
454
  for (const entry of fs.readdirSync(cursorSkillsDir, { withFileTypes: true })) {
412
- if (!entry.isDirectory() || !entry.name.startsWith('autosnippet-')) continue;
455
+ if (!entry.isDirectory() || !entry.name.startsWith('autosnippet-')) {
456
+ continue;
457
+ }
413
458
  this._copyDirRecursive(
414
459
  path.join(cursorSkillsDir, entry.name),
415
460
  path.join(targetSkillsDir, entry.name)
@@ -9,7 +9,6 @@
9
9
  */
10
10
 
11
11
  export class KnowledgeCompressor {
12
-
13
12
  /**
14
13
  * Channel A — 一行式规则
15
14
  * @param {Array<Object>} entries - KnowledgeEntry 数组 (kind='rule')
@@ -17,8 +16,8 @@ export class KnowledgeCompressor {
17
16
  */
18
17
  compressToRuleLine(entries) {
19
18
  return entries
20
- .filter(e => e.doClause) // 无 doClause → 跳过,不猜
21
- .map(e => {
19
+ .filter((e) => e.doClause) // 无 doClause → 跳过,不猜
20
+ .map((e) => {
22
21
  let line = e.doClause;
23
22
  if (e.dontClause) {
24
23
  // AI 可能返回 "Don't ..." / "Do not ..." 开头,去掉冗余前缀
@@ -37,13 +36,15 @@ export class KnowledgeCompressor {
37
36
  compressToWhenDoDont(entries) {
38
37
  const seen = new Set();
39
38
  return entries
40
- .filter(e => e.trigger && e.whenClause && e.doClause) // 缺任一 → 跳过
41
- .map(e => {
39
+ .filter((e) => e.trigger && e.whenClause && e.doClause) // 缺任一 → 跳过
40
+ .map((e) => {
42
41
  let trigger = e.trigger.startsWith('@') ? e.trigger : `@${e.trigger}`;
43
42
  // trigger 去重(AI 应保证唯一,但防御性检查)
44
43
  if (seen.has(trigger)) {
45
44
  let i = 2;
46
- while (seen.has(`${trigger}-${i}`)) i++;
45
+ while (seen.has(`${trigger}-${i}`)) {
46
+ i++;
47
+ }
47
48
  trigger = `${trigger}-${i}`;
48
49
  }
49
50
  seen.add(trigger);
@@ -65,22 +66,24 @@ export class KnowledgeCompressor {
65
66
  */
66
67
  formatWhenDoDont(compressed, language = '') {
67
68
  const lang = language || '';
68
- return compressed.map(item => {
69
- const lines = [`### ${item.trigger}`];
70
- lines.push(`- **When**: ${item.when}`);
71
- lines.push(`- **Do**: ${item.do}`);
72
- if (item.dont) {
73
- const stripped = item.dont.replace(/^(Don't|Do not|Never)\s+/i, '');
74
- lines.push(`- **Don't**: ${stripped}`);
75
- }
76
- if (item.template) {
77
- lines.push('');
78
- lines.push(`\`\`\`${lang}`);
79
- lines.push(item.template);
80
- lines.push('```');
81
- }
82
- return lines.join('\n');
83
- }).join('\n\n');
69
+ return compressed
70
+ .map((item) => {
71
+ const lines = [`### ${item.trigger}`];
72
+ lines.push(`- **When**: ${item.when}`);
73
+ lines.push(`- **Do**: ${item.do}`);
74
+ if (item.dont) {
75
+ const stripped = item.dont.replace(/^(Don't|Do not|Never)\s+/i, '');
76
+ lines.push(`- **Don't**: ${stripped}`);
77
+ }
78
+ if (item.template) {
79
+ lines.push('');
80
+ lines.push(`\`\`\`${lang}`);
81
+ lines.push(item.template);
82
+ lines.push('```');
83
+ }
84
+ return lines.join('\n');
85
+ })
86
+ .join('\n\n');
84
87
  }
85
88
  }
86
89
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
- import { estimateTokens, BUDGET } from './TokenBudget.js';
11
+ import { BUDGET, estimateTokens } from './TokenBudget.js';
12
12
 
13
13
  export class RulesGenerator {
14
14
  /**
@@ -75,7 +75,7 @@ export class RulesGenerator {
75
75
  const truncated = [];
76
76
  let used = estimateTokens(description) + 50;
77
77
  for (const line of lines) {
78
- used += estimateTokens(line + '\n');
78
+ used += estimateTokens(`${line}\n`);
79
79
  if (used <= BUDGET.CHANNEL_B_MAX_PER_FILE) {
80
80
  truncated.push(line);
81
81
  }
@@ -99,14 +99,20 @@ export class RulesGenerator {
99
99
  * 保留静态模板文件(autosnippet-conventions.mdc, autosnippet-skills.mdc)
100
100
  */
101
101
  cleanDynamicFiles() {
102
- if (!fs.existsSync(this.rulesDir)) return;
102
+ if (!fs.existsSync(this.rulesDir)) {
103
+ return;
104
+ }
103
105
 
104
106
  const dynamicPrefixes = ['autosnippet-project-rules', 'autosnippet-patterns-'];
105
107
  const files = fs.readdirSync(this.rulesDir);
106
108
  for (const file of files) {
107
- if (dynamicPrefixes.some(p => file.startsWith(p))) {
109
+ if (dynamicPrefixes.some((p) => file.startsWith(p))) {
108
110
  const filePath = path.join(this.rulesDir, file);
109
- try { fs.unlinkSync(filePath); } catch { /* ignore */ }
111
+ try {
112
+ fs.unlinkSync(filePath);
113
+ } catch {
114
+ /* ignore */
115
+ }
110
116
  }
111
117
  }
112
118
  }
@@ -132,7 +138,7 @@ export class RulesGenerator {
132
138
  '- `autosnippet_search({ query })` — search knowledge base (auto mode: BM25 + semantic)',
133
139
  '- `autosnippet_search({ query, mode: "context" })` — context-aware search with history',
134
140
  ];
135
- return lines.join('\n') + '\n';
141
+ return `${lines.join('\n')}\n`;
136
142
  }
137
143
 
138
144
  /**
@@ -152,7 +158,7 @@ export class RulesGenerator {
152
158
  '',
153
159
  `For full code examples: \`autosnippet_search("${topic}")\``,
154
160
  ];
155
- return lines.join('\n') + '\n';
161
+ return `${lines.join('\n')}\n`;
156
162
  }
157
163
 
158
164
  /**