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
@@ -4,13 +4,13 @@
4
4
  * 持久化到 AutoSnippet/feedback.json(Git 友好)
5
5
  */
6
6
 
7
- import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
8
- import { join, dirname } from 'node:path';
7
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
9
  import pathGuard from '../../shared/PathGuard.js';
10
10
 
11
11
  export class FeedbackCollector {
12
12
  #feedbackPath;
13
- #events; // [{ type, recipeId, data, timestamp }]
13
+ #events; // [{ type, recipeId, data, timestamp }]
14
14
  #maxEvents;
15
15
 
16
16
  constructor(projectRoot, options = {}) {
@@ -49,15 +49,17 @@ export class FeedbackCollector {
49
49
  * @returns {{ views: number, clicks: number, copies: number, avgRating: number, feedbackCount: number }}
50
50
  */
51
51
  getRecipeStats(recipeId) {
52
- const events = this.#events.filter(e => e.recipeId === recipeId);
53
- const ratings = events.filter(e => e.type === 'rate' && e.data.rating).map(e => e.data.rating);
52
+ const events = this.#events.filter((e) => e.recipeId === recipeId);
53
+ const ratings = events
54
+ .filter((e) => e.type === 'rate' && e.data.rating)
55
+ .map((e) => e.data.rating);
54
56
 
55
57
  return {
56
- views: events.filter(e => e.type === 'view').length,
57
- clicks: events.filter(e => e.type === 'click').length,
58
- copies: events.filter(e => e.type === 'copy' || e.type === 'insert').length,
58
+ views: events.filter((e) => e.type === 'view').length,
59
+ clicks: events.filter((e) => e.type === 'click').length,
60
+ copies: events.filter((e) => e.type === 'copy' || e.type === 'insert').length,
59
61
  avgRating: ratings.length > 0 ? ratings.reduce((a, b) => a + b, 0) / ratings.length : 0,
60
- feedbackCount: events.filter(e => e.type === 'feedback').length,
62
+ feedbackCount: events.filter((e) => e.type === 'feedback').length,
61
63
  totalEvents: events.length,
62
64
  };
63
65
  }
@@ -73,7 +75,7 @@ export class FeedbackCollector {
73
75
  return {
74
76
  totalEvents: this.#events.length,
75
77
  byType,
76
- uniqueRecipes: new Set(this.#events.map(e => e.recipeId)).size,
78
+ uniqueRecipes: new Set(this.#events.map((e) => e.recipeId)).size,
77
79
  };
78
80
  }
79
81
 
@@ -107,16 +109,22 @@ export class FeedbackCollector {
107
109
  const data = JSON.parse(readFileSync(this.#feedbackPath, 'utf-8'));
108
110
  return Array.isArray(data) ? data : data.events || [];
109
111
  }
110
- } catch { /* silent */ }
112
+ } catch {
113
+ /* silent */
114
+ }
111
115
  return [];
112
116
  }
113
117
 
114
118
  #save() {
115
119
  try {
116
120
  const dir = dirname(this.#feedbackPath);
117
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
121
+ if (!existsSync(dir)) {
122
+ mkdirSync(dir, { recursive: true });
123
+ }
118
124
  writeFileSync(this.#feedbackPath, JSON.stringify(this.#events, null, 2));
119
- } catch { /* silent */ }
125
+ } catch {
126
+ /* silent */
127
+ }
120
128
  }
121
129
 
122
130
  #migrateOldPath(projectRoot, internalDir) {
@@ -125,10 +133,14 @@ export class FeedbackCollector {
125
133
  if (existsSync(oldPath) && !existsSync(this.#feedbackPath)) {
126
134
  const content = readFileSync(oldPath, 'utf-8');
127
135
  const dir = dirname(this.#feedbackPath);
128
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
136
+ if (!existsSync(dir)) {
137
+ mkdirSync(dir, { recursive: true });
138
+ }
129
139
  writeFileSync(this.#feedbackPath, content);
130
140
  unlinkSync(oldPath);
131
141
  }
132
- } catch { /* 迁移失败不阻断启动 */ }
142
+ } catch {
143
+ /* 迁移失败不阻断启动 */
144
+ }
133
145
  }
134
146
  }
@@ -4,7 +4,7 @@
4
4
  * 每个维度评分 0-1,加权求和
5
5
  */
6
6
 
7
- import { QUALITY_WEIGHTS, QUALITY_GRADES, CODE_LENGTH } from '../../shared/constants.js';
7
+ import { CODE_LENGTH, QUALITY_GRADES, QUALITY_WEIGHTS } from '../../shared/constants.js';
8
8
 
9
9
  const DEFAULT_WEIGHTS = QUALITY_WEIGHTS;
10
10
 
@@ -47,7 +47,7 @@ export class QualityScorer {
47
47
  * 批量评分
48
48
  */
49
49
  scoreBatch(recipes) {
50
- return recipes.map(r => ({ recipe: r, ...this.score(r) }));
50
+ return recipes.map((r) => ({ recipe: r, ...this.score(r) }));
51
51
  }
52
52
 
53
53
  /**
@@ -64,10 +64,18 @@ export class QualityScorer {
64
64
  */
65
65
  #scoreCompleteness(r) {
66
66
  let s = 0;
67
- if (r.title && r.title.trim()) s += 0.25;
68
- if (r.trigger && r.trigger.trim()) s += 0.25;
69
- if (r.code && r.code.trim().length > 5) s += 0.30;
70
- if (r.usageGuide && r.usageGuide.trim()) s += 0.20;
67
+ if (r.title?.trim()) {
68
+ s += 0.25;
69
+ }
70
+ if (r.trigger?.trim()) {
71
+ s += 0.25;
72
+ }
73
+ if (r.code && r.code.trim().length > 5) {
74
+ s += 0.3;
75
+ }
76
+ if (r.usageGuide?.trim()) {
77
+ s += 0.2;
78
+ }
71
79
  return s;
72
80
  }
73
81
 
@@ -77,14 +85,29 @@ export class QualityScorer {
77
85
  #scoreFormat(r) {
78
86
  let s = 0;
79
87
  if (r.trigger) {
80
- if (/^[a-zA-Z0-9_\-:.]+$/.test(r.trigger) && r.trigger.length >= 2 && r.trigger.length <= 64) {
88
+ if (
89
+ /^[a-zA-Z0-9_\-:.]+$/.test(r.trigger) &&
90
+ r.trigger.length >= 2 &&
91
+ r.trigger.length <= 64
92
+ ) {
81
93
  s += 0.5;
82
94
  } else if (r.trigger.length >= 2) {
83
95
  s += 0.25;
84
96
  }
85
97
  }
86
98
  if (r.language) {
87
- const valid = new Set(['swift', 'objective-c', 'objc', 'javascript', 'typescript', 'python', 'c', 'cpp', 'shell', 'markdown']);
99
+ const valid = new Set([
100
+ 'swift',
101
+ 'objective-c',
102
+ 'objc',
103
+ 'javascript',
104
+ 'typescript',
105
+ 'python',
106
+ 'c',
107
+ 'cpp',
108
+ 'shell',
109
+ 'markdown',
110
+ ]);
88
111
  s += valid.has(r.language.toLowerCase()) ? 0.5 : 0.25;
89
112
  }
90
113
  return s;
@@ -94,22 +117,33 @@ export class QualityScorer {
94
117
  * 代码质量: 长度适中(0.3) + 无 TODO(0.2) + 有注释(0.3) + 有错误处理(0.2)
95
118
  */
96
119
  #scoreCodeQuality(r) {
97
- if (!r.code) return 0;
120
+ if (!r.code) {
121
+ return 0;
122
+ }
98
123
  let s = 0;
99
124
  const code = r.code;
100
125
 
101
126
  // 长度适中
102
- if (code.length >= CODE_LENGTH.MIN && code.length <= CODE_LENGTH.MAX) s += 0.3;
103
- else if (code.length > CODE_LENGTH.MAX) s += 0.15;
127
+ if (code.length >= CODE_LENGTH.MIN && code.length <= CODE_LENGTH.MAX) {
128
+ s += 0.3;
129
+ } else if (code.length > CODE_LENGTH.MAX) {
130
+ s += 0.15;
131
+ }
104
132
 
105
133
  // 无 TODO/FIXME/HACK
106
- if (!/\b(TODO|FIXME|HACK|XXX)\b/.test(code)) s += 0.2;
134
+ if (!/\b(TODO|FIXME|HACK|XXX)\b/.test(code)) {
135
+ s += 0.2;
136
+ }
107
137
 
108
138
  // 有注释
109
- if (/\/\/|\/\*|#\s/.test(code)) s += 0.3;
139
+ if (/\/\/|\/\*|#\s/.test(code)) {
140
+ s += 0.3;
141
+ }
110
142
 
111
143
  // 有错误处理
112
- if (/try|catch|throw|guard|if\s+let|do\s*\{|\.catch/.test(code)) s += 0.2;
144
+ if (/try|catch|throw|guard|if\s+let|do\s*\{|\.catch/.test(code)) {
145
+ s += 0.2;
146
+ }
113
147
 
114
148
  return s;
115
149
  }
@@ -119,9 +153,15 @@ export class QualityScorer {
119
153
  */
120
154
  #scoreMetadata(r) {
121
155
  let s = 0;
122
- if (r.category && r.category.trim()) s += 0.35;
123
- if ((r.tags && r.tags.length > 0) || (r.headers && r.headers.length > 0)) s += 0.35;
124
- if (r.summary && r.summary.trim()) s += 0.30;
156
+ if (r.category?.trim()) {
157
+ s += 0.35;
158
+ }
159
+ if ((r.tags && r.tags.length > 0) || (r.headers && r.headers.length > 0)) {
160
+ s += 0.35;
161
+ }
162
+ if (r.summary?.trim()) {
163
+ s += 0.3;
164
+ }
125
165
  return s;
126
166
  }
127
167
 
@@ -130,9 +170,15 @@ export class QualityScorer {
130
170
  */
131
171
  #scoreEngagement(r) {
132
172
  let s = 0;
133
- if (r.views && r.views > 0) s += Math.min(0.3, (r.views / 100) * 0.3);
134
- if (r.clicks && r.clicks > 0) s += Math.min(0.3, (r.clicks / 50) * 0.3);
135
- if (r.rating && r.rating > 0) s += (r.rating / 5) * 0.4;
173
+ if (r.views && r.views > 0) {
174
+ s += Math.min(0.3, (r.views / 100) * 0.3);
175
+ }
176
+ if (r.clicks && r.clicks > 0) {
177
+ s += Math.min(0.3, (r.clicks / 50) * 0.3);
178
+ }
179
+ if (r.rating && r.rating > 0) {
180
+ s += (r.rating / 5) * 0.4;
181
+ }
136
182
  return s;
137
183
  }
138
184
 
@@ -140,10 +186,18 @@ export class QualityScorer {
140
186
  * 分数转等级
141
187
  */
142
188
  #toGrade(score) {
143
- if (score >= QUALITY_GRADES.A) return 'A';
144
- if (score >= QUALITY_GRADES.B) return 'B';
145
- if (score >= QUALITY_GRADES.C) return 'C';
146
- if (score >= QUALITY_GRADES.D) return 'D';
189
+ if (score >= QUALITY_GRADES.A) {
190
+ return 'A';
191
+ }
192
+ if (score >= QUALITY_GRADES.B) {
193
+ return 'B';
194
+ }
195
+ if (score >= QUALITY_GRADES.C) {
196
+ return 'C';
197
+ }
198
+ if (score >= QUALITY_GRADES.D) {
199
+ return 'D';
200
+ }
147
201
  return 'F';
148
202
  }
149
203
  }
@@ -1,26 +1,42 @@
1
1
  /**
2
- * RecipeCandidateValidator — Recipe 候选校验器
3
- * 验证候选 Recipe 是否满足最低字段要求
2
+ * RecipeCandidateValidator — Recipe 候选校验器 (V3)
3
+ *
4
+ * 验证候选是否满足 V3 结构化字段要求。
5
+ * 核心变更:用 content 对象替代旧版 code 字符串。
4
6
  */
5
7
 
6
- const REQUIRED_FIELDS = ['title', 'trigger', 'category', 'language', 'code'];
8
+ import { LanguageService } from '../../shared/LanguageService.js';
9
+
10
+ /* ── V3 必填字段 ── */
11
+ const REQUIRED_FIELDS = [
12
+ 'title', // 中文简短标题
13
+ 'trigger', // @前缀 kebab-case
14
+ 'category', // View/Service/Tool/Model/Network/Storage/UI/Utility
15
+ 'language', // 编程语言标识
16
+ 'kind', // rule / pattern / fact
17
+ 'doClause', // 英文祈使句正向指令
18
+ 'description', // 中文简述 ≤80字
19
+ ];
20
+
21
+ /* ── 需要 content 子对象有内容 ── */
22
+ const REQUIRED_CONTENT_FIELDS = ['pattern', 'markdown', 'rationale'];
7
23
 
8
24
  const VALID_CATEGORIES = new Set([
9
- 'swift', 'objc', 'javascript', 'typescript', 'python',
10
- 'swiftui', 'uikit', 'combine', 'concurrency', 'testing',
11
- 'networking', 'persistence', 'security', 'architecture',
12
- 'performance', 'debugging', 'general',
25
+ 'view',
26
+ 'service',
27
+ 'tool',
28
+ 'model',
29
+ 'network',
30
+ 'storage',
31
+ 'ui',
32
+ 'utility',
13
33
  ]);
14
34
 
15
- const VALID_LANGUAGES = new Set([
16
- 'swift', 'objective-c', 'objc', 'javascript', 'typescript',
17
- 'python', 'c', 'cpp', 'c++', 'shell', 'bash', 'markdown',
18
- ]);
35
+ const VALID_KINDS = new Set(['rule', 'pattern', 'fact']);
19
36
 
20
37
  export class RecipeCandidateValidator {
21
-
22
38
  /**
23
- * 验证单个候选
39
+ * 验证单个候选(V3 结构)
24
40
  * @param {object} candidate
25
41
  * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
26
42
  */
@@ -32,55 +48,81 @@ export class RecipeCandidateValidator {
32
48
  return { valid: false, errors: ['候选为空或类型错误'], warnings: [] };
33
49
  }
34
50
 
35
- // 必填字段
51
+ // ── V3 必填字段 ──
36
52
  for (const field of REQUIRED_FIELDS) {
37
- if (!candidate[field] || (typeof candidate[field] === 'string' && !candidate[field].trim())) {
53
+ const val = candidate[field];
54
+ if (!val || (typeof val === 'string' && !val.trim())) {
38
55
  errors.push(`缺少必填字段: ${field}`);
39
56
  }
40
57
  }
41
58
 
42
- // trigger 格式 建议以 as: 或 asd- 开头
59
+ // ── content 对象必须包含有效内容 ──
60
+ const content = candidate.content;
61
+ if (!content || typeof content !== 'object') {
62
+ errors.push('缺少必填字段: content(需为 { pattern, markdown, rationale } 对象)');
63
+ } else {
64
+ const hasPattern = !!(content.pattern && String(content.pattern).trim());
65
+ const hasMarkdown = !!(content.markdown && String(content.markdown).trim());
66
+ if (!hasPattern && !hasMarkdown) {
67
+ errors.push('content.pattern 或 content.markdown 至少需要一个非空');
68
+ }
69
+ if (!content.rationale || !String(content.rationale).trim()) {
70
+ errors.push('缺少必填字段: content.rationale(设计原理)');
71
+ }
72
+ }
73
+
74
+ // ── trigger 格式 ──
43
75
  if (candidate.trigger && typeof candidate.trigger === 'string') {
44
- if (candidate.trigger.length < 2) errors.push('trigger 过短');
45
- if (candidate.trigger.length > 64) errors.push('trigger 过长 (>64)');
46
- if (!/^[a-zA-Z0-9_\-:.]+$/.test(candidate.trigger)) {
76
+ if (candidate.trigger.length < 2) {
77
+ errors.push('trigger 过短');
78
+ }
79
+ if (candidate.trigger.length > 64) {
80
+ errors.push('trigger 过长 (>64)');
81
+ }
82
+ if (!candidate.trigger.startsWith('@')) {
83
+ warnings.push('trigger 应以 @ 开头');
84
+ }
85
+ if (!/^@?[a-zA-Z0-9_\-:.]+$/.test(candidate.trigger)) {
47
86
  warnings.push('trigger 含特殊字符,建议仅使用字母/数字/下划线/连字符');
48
87
  }
49
88
  }
50
89
 
51
- // category 合法性
52
- if (candidate.category && !VALID_CATEGORIES.has(candidate.category.toLowerCase())) {
53
- warnings.push(`category "${candidate.category}" 不在推荐列表中`);
90
+ // ── kind 合法性 ──
91
+ if (candidate.kind && !VALID_KINDS.has(candidate.kind)) {
92
+ errors.push(`kind "${candidate.kind}" 无效 — 必须为 rule/pattern/fact`);
54
93
  }
55
94
 
56
- // language 合法性
57
- if (candidate.language && !VALID_LANGUAGES.has(candidate.language.toLowerCase())) {
58
- warnings.push(`language "${candidate.language}" 不在推荐列表中`);
95
+ // ── category 合法性 ──
96
+ if (candidate.category && !VALID_CATEGORIES.has(candidate.category.toLowerCase())) {
97
+ warnings.push(`category "${candidate.category}" 不在标准列表(View/Service/Tool/Model/Network/Storage/UI/Utility)`);
59
98
  }
60
99
 
61
- // code 质量检查
62
- if (candidate.code && typeof candidate.code === 'string') {
63
- if (candidate.code.trim().length < 10) {
64
- warnings.push('code 内容过短 (<10 字符)');
65
- }
66
- if (candidate.code.length > 50000) {
67
- warnings.push('code 内容过长 (>50000 字符),建议拆分');
100
+ // ── language 合法性 ──
101
+ if (candidate.language) {
102
+ const lang = candidate.language.toLowerCase();
103
+ if (!LanguageService.isKnownLang(lang) && lang !== 'objc' && lang !== 'markdown') {
104
+ warnings.push(`language "${candidate.language}" 不在已知语言列表`);
68
105
  }
69
106
  }
70
107
 
71
- // 摘要/描述(非必填但建议)
72
- if (!candidate.summary && !candidate.description) {
73
- warnings.push('建议提供 summary description');
108
+ // ── headers 必填 ──
109
+ if (!Array.isArray(candidate.headers)) {
110
+ errors.push('缺少必填字段: headers(需为 import 语句数组,无 import 时传 [])');
74
111
  }
75
112
 
76
- // 标签
77
- if (candidate.tags && !Array.isArray(candidate.tags)) {
78
- warnings.push('tags 应为数组');
113
+ // ── knowledgeType 必填 ──
114
+ if (!candidate.knowledgeType || !String(candidate.knowledgeType).trim()) {
115
+ errors.push('缺少必填字段: knowledgeType');
79
116
  }
80
117
 
81
- // 推理依据 (reasoning)
118
+ // ── usageGuide 必填 ──
119
+ if (!candidate.usageGuide || !String(candidate.usageGuide).trim()) {
120
+ errors.push('缺少必填字段: usageGuide(使用指南,### 章节格式)');
121
+ }
122
+
123
+ // ── 推理依据 (reasoning) 必填 ──
82
124
  if (!candidate.reasoning) {
83
- warnings.push('缺少 reasoning(推理依据)— 需要 whyStandard + sources + confidence');
125
+ errors.push('缺少必填字段: reasoning(需包含 whyStandard + sources + confidence');
84
126
  } else {
85
127
  if (!candidate.reasoning.whyStandard?.trim()) {
86
128
  errors.push('reasoning.whyStandard 不能为空');
@@ -88,11 +130,20 @@ export class RecipeCandidateValidator {
88
130
  if (!Array.isArray(candidate.reasoning.sources) || candidate.reasoning.sources.length === 0) {
89
131
  errors.push('reasoning.sources 至少包含一项来源');
90
132
  }
91
- if (typeof candidate.reasoning.confidence !== 'number' || candidate.reasoning.confidence < 0 || candidate.reasoning.confidence > 1) {
133
+ if (
134
+ typeof candidate.reasoning.confidence !== 'number' ||
135
+ candidate.reasoning.confidence < 0 ||
136
+ candidate.reasoning.confidence > 1
137
+ ) {
92
138
  warnings.push('reasoning.confidence 应为 0-1 的数字');
93
139
  }
94
140
  }
95
141
 
142
+ // ── 标签 ──
143
+ if (candidate.tags && !Array.isArray(candidate.tags)) {
144
+ warnings.push('tags 应为数组');
145
+ }
146
+
96
147
  return {
97
148
  valid: errors.length === 0,
98
149
  errors,
@@ -137,9 +188,24 @@ export class RecipeCandidateValidator {
137
188
  }
138
189
 
139
190
  /**
140
- * 获取有效语言列表
191
+ * 获取有效 kind 列表
192
+ */
193
+ getValidKinds() {
194
+ return [...VALID_KINDS];
195
+ }
196
+
197
+ /**
198
+ * 获取所有必填字段名列表
141
199
  */
142
- getValidLanguages() {
143
- return [...VALID_LANGUAGES];
200
+ getRequiredFields() {
201
+ return [
202
+ ...REQUIRED_FIELDS,
203
+ 'content',
204
+ 'content.rationale',
205
+ 'headers',
206
+ 'knowledgeType',
207
+ 'usageGuide',
208
+ 'reasoning',
209
+ ];
144
210
  }
145
211
  }