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
@@ -20,9 +20,9 @@
20
20
 
21
21
  import Logger from '../../infrastructure/logging/Logger.js';
22
22
 
23
- const DEFAULT_WINDOW_MS = 5000; // 5 秒聚合窗口
24
- const DEFAULT_MAX_BATCH = 50; // 单次 batch 最大事件数
25
- const DEFAULT_DEDUPE_MS = 60_000; // 60 秒去重窗口
23
+ const DEFAULT_WINDOW_MS = 5000; // 5 秒聚合窗口
24
+ const DEFAULT_MAX_BATCH = 50; // 单次 batch 最大事件数
25
+ const DEFAULT_DEDUPE_MS = 60_000; // 60 秒去重窗口
26
26
 
27
27
  export class EventAggregator {
28
28
  /** @type {Map<string, { events: any[], timer: ReturnType<typeof setTimeout> }>} */
@@ -63,7 +63,7 @@ export class EventAggregator {
63
63
  // 去重检查
64
64
  const dedupe = dedupeId || this.#hashEvent(key, event);
65
65
  const lastSeen = this.#dedupeMap.get(dedupe);
66
- if (lastSeen && (Date.now() - lastSeen) < this.#dedupeMs) {
66
+ if (lastSeen && Date.now() - lastSeen < this.#dedupeMs) {
67
67
  this.#logger.debug(`[EventAggregator] dedup skip: ${key}/${dedupe}`);
68
68
  return;
69
69
  }
@@ -83,7 +83,9 @@ export class EventAggregator {
83
83
  }
84
84
 
85
85
  // 重置窗口计时器
86
- if (bucket.timer) clearTimeout(bucket.timer);
86
+ if (bucket.timer) {
87
+ clearTimeout(bucket.timer);
88
+ }
87
89
  bucket.timer = setTimeout(() => this.#flush(key), this.#windowMs);
88
90
  }
89
91
 
@@ -113,7 +115,9 @@ export class EventAggregator {
113
115
  */
114
116
  destroy() {
115
117
  for (const [, bucket] of this.#buckets) {
116
- if (bucket.timer) clearTimeout(bucket.timer);
118
+ if (bucket.timer) {
119
+ clearTimeout(bucket.timer);
120
+ }
117
121
  }
118
122
  this.#buckets.clear();
119
123
  this.#dedupeMap.clear();
@@ -125,7 +129,9 @@ export class EventAggregator {
125
129
  */
126
130
  get pendingCount() {
127
131
  let count = 0;
128
- for (const [, bucket] of this.#buckets) count += bucket.events.length;
132
+ for (const [, bucket] of this.#buckets) {
133
+ count += bucket.events.length;
134
+ }
129
135
  return count;
130
136
  }
131
137
 
@@ -133,7 +139,9 @@ export class EventAggregator {
133
139
 
134
140
  #flush(key) {
135
141
  const bucket = this.#buckets.get(key);
136
- if (!bucket || bucket.events.length === 0) return;
142
+ if (!bucket || bucket.events.length === 0) {
143
+ return;
144
+ }
137
145
 
138
146
  if (bucket.timer) {
139
147
  clearTimeout(bucket.timer);
@@ -155,8 +163,9 @@ export class EventAggregator {
155
163
  // 通知监听器
156
164
  const listeners = this.#listeners.get('batch') || [];
157
165
  for (const fn of listeners) {
158
- try { fn(key, events); }
159
- catch (err) {
166
+ try {
167
+ fn(key, events);
168
+ } catch (err) {
160
169
  this.#logger.warn(`[EventAggregator] listener error: ${err.message}`);
161
170
  }
162
171
  }
@@ -167,17 +176,25 @@ export class EventAggregator {
167
176
  #hashEvent(key, event) {
168
177
  // 简单 hash: key + 事件关键字段
169
178
  const significant = { key };
170
- if (event.filePath) significant.f = event.filePath;
171
- if (event.ruleName) significant.r = event.ruleName;
172
- if (event.action) significant.a = event.action;
173
- if (event.id) significant.i = event.id;
179
+ if (event.filePath) {
180
+ significant.f = event.filePath;
181
+ }
182
+ if (event.ruleName) {
183
+ significant.r = event.ruleName;
184
+ }
185
+ if (event.action) {
186
+ significant.a = event.action;
187
+ }
188
+ if (event.id) {
189
+ significant.i = event.id;
190
+ }
174
191
  return JSON.stringify(significant);
175
192
  }
176
193
 
177
194
  #cleanupDedupe() {
178
195
  const now = Date.now();
179
196
  for (const [hash, ts] of this.#dedupeMap) {
180
- if ((now - ts) > this.#dedupeMs) {
197
+ if (now - ts > this.#dedupeMs) {
181
198
  this.#dedupeMap.delete(hash);
182
199
  }
183
200
  }
@@ -37,30 +37,30 @@
37
37
  * new SignalCollector(opts) → instance.start() → ... → instance.stop()
38
38
  */
39
39
 
40
+ import { execSync } from 'node:child_process';
40
41
  import fs from 'node:fs';
41
42
  import path from 'node:path';
42
- import { execSync } from 'node:child_process';
43
43
  import Logger from '../../infrastructure/logging/Logger.js';
44
- import { EventAggregator } from './EventAggregator.js';
45
44
  import pathGuard from '../../shared/PathGuard.js';
45
+ import { EventAggregator } from './EventAggregator.js';
46
46
 
47
- const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 小时(初始值,AI 可动态调整)
48
- const MIN_INTERVAL_MS = 5 * 60 * 1000; // 最短 5 分钟
47
+ const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 小时(初始值,AI 可动态调整)
48
+ const MIN_INTERVAL_MS = 5 * 60 * 1000; // 最短 5 分钟
49
49
  const MAX_INTERVAL_MS = 24 * 60 * 60 * 1000; // 最长 24 小时
50
50
  const SNAPSHOT_FILE = 'signal-snapshot.json';
51
51
 
52
52
  export class SignalCollector {
53
53
  #projectRoot;
54
54
  #db;
55
- #chatAgent; // ChatAgent 实例 — AI 核心
56
- #mode; // 'off' | 'suggest' | 'auto'
55
+ #chatAgent; // ChatAgent 实例 — AI 核心
56
+ #mode; // 'off' | 'suggest' | 'auto'
57
57
  #intervalMs;
58
58
  #timer = null;
59
59
  #running = false;
60
60
  #logger;
61
61
  #snapshotPath;
62
62
  #snapshot;
63
- #onSuggestions; // callback(suggestions[]) — 由外部注入(如 RealtimeService 推送)
63
+ #onSuggestions; // callback(suggestions[]) — 由外部注入(如 RealtimeService 推送)
64
64
  /** @type {EventAggregator} 信号聚类引擎 */
65
65
  #aggregator;
66
66
 
@@ -151,7 +151,9 @@ export class SignalCollector {
151
151
  * @param {object} event — 事件数据
152
152
  */
153
153
  pushEvent(key, event) {
154
- if (this.#mode === 'off') return;
154
+ if (this.#mode === 'off') {
155
+ return;
156
+ }
155
157
  this.#aggregator.push(key, event);
156
158
  }
157
159
 
@@ -159,13 +161,21 @@ export class SignalCollector {
159
161
  return this.#tick();
160
162
  }
161
163
 
162
- getSnapshot() { return { ...this.#snapshot }; }
163
- getMode() { return this.#mode; }
164
+ getSnapshot() {
165
+ return { ...this.#snapshot };
166
+ }
167
+ getMode() {
168
+ return this.#mode;
169
+ }
164
170
 
165
171
  /** 从 pendingSuggestions 中移除已创建的 Skill */
166
172
  removePendingSuggestion(name) {
167
- if (!this.#snapshot.pendingSuggestions?.length) return;
168
- this.#snapshot.pendingSuggestions = this.#snapshot.pendingSuggestions.filter(s => s.name !== name);
173
+ if (!this.#snapshot.pendingSuggestions?.length) {
174
+ return;
175
+ }
176
+ this.#snapshot.pendingSuggestions = this.#snapshot.pendingSuggestions.filter(
177
+ (s) => s.name !== name
178
+ );
169
179
  if (this.#snapshot.lastResult) {
170
180
  this.#snapshot.lastResult.newSuggestions = this.#snapshot.pendingSuggestions.length;
171
181
  }
@@ -173,10 +183,14 @@ export class SignalCollector {
173
183
  }
174
184
 
175
185
  setMode(mode) {
176
- if (!['off', 'suggest', 'auto'].includes(mode)) return;
186
+ if (!['off', 'suggest', 'auto'].includes(mode)) {
187
+ return;
188
+ }
177
189
  this.#mode = mode;
178
190
  this.#logger.info(`[SignalCollector] mode changed to ${mode}`);
179
- if (mode === 'off') this.stop();
191
+ if (mode === 'off') {
192
+ this.stop();
193
+ }
180
194
  }
181
195
 
182
196
  // ═══════════════════════════════════════════════════════
@@ -184,17 +198,19 @@ export class SignalCollector {
184
198
  // ═══════════════════════════════════════════════════════
185
199
 
186
200
  async #tick() {
187
- if (this.#running) return null;
201
+ if (this.#running) {
202
+ return null;
203
+ }
188
204
  this.#running = true;
189
205
 
190
206
  try {
191
207
  // 1. 多维度收集信号
192
208
  const signals = {
193
- guard: this.#collectGuardSignals(),
194
- memory: this.#collectMemorySignals(),
195
- recipes: this.#collectRecipeSignals(),
196
- candidates: this.#collectCandidateSignals(),
197
- actions: this.#collectRecentActions(),
209
+ guard: this.#collectGuardSignals(),
210
+ memory: this.#collectMemorySignals(),
211
+ recipes: this.#collectRecipeSignals(),
212
+ candidates: this.#collectCandidateSignals(),
213
+ actions: this.#collectRecentActions(),
198
214
  codeChanges: this.#collectCodeChangeSignals(),
199
215
  };
200
216
 
@@ -203,7 +219,10 @@ export class SignalCollector {
203
219
 
204
220
  // 3. 调用 ChatAgent AI 分析(source: 'system' 确保 Memory 隔离)
205
221
  this.#logger.debug('[SignalCollector] invoking ChatAgent for analysis...');
206
- const { reply, toolCalls } = await this.#chatAgent.execute(prompt, { history: [], source: 'system' });
222
+ const { reply, toolCalls } = await this.#chatAgent.execute(prompt, {
223
+ history: [],
224
+ source: 'system',
225
+ });
207
226
 
208
227
  // 4. 解析 AI 响应 — 使用 AiProvider.extractJSON 统一 structured output 解析
209
228
  const parsed = this.#parseStructuredReply(reply);
@@ -211,7 +230,7 @@ export class SignalCollector {
211
230
 
212
231
  // 5. 过滤已推送
213
232
  const newSuggestions = suggestions.filter(
214
- s => !this.#snapshot.pushedNames.includes(s.name),
233
+ (s) => !this.#snapshot.pushedNames.includes(s.name)
215
234
  );
216
235
 
217
236
  // 6. 更新快照
@@ -225,7 +244,7 @@ export class SignalCollector {
225
244
  };
226
245
  // 持久化 AI 生成的建议,供前端直接读取
227
246
  if (newSuggestions.length > 0) {
228
- this.#snapshot.pendingSuggestions = newSuggestions.map(s => ({
247
+ this.#snapshot.pendingSuggestions = newSuggestions.map((s) => ({
229
248
  name: s.name,
230
249
  description: s.description || s.reason || '',
231
250
  rationale: s.rationale || s.reason || '',
@@ -244,17 +263,20 @@ export class SignalCollector {
244
263
 
245
264
  // 推送建议
246
265
  if (this.#onSuggestions) {
247
- try { this.#onSuggestions(newSuggestions); }
248
- catch (err) {
266
+ try {
267
+ this.#onSuggestions(newSuggestions);
268
+ } catch (err) {
249
269
  this.#logger.warn(`[SignalCollector] onSuggestions callback error: ${err.message}`);
250
270
  }
251
271
  }
252
272
 
253
273
  // 检测 AI 是否在 auto 模式下自主调用了 create_skill
254
274
  if (this.#mode === 'auto' && toolCalls?.length) {
255
- const created = toolCalls.filter(tc => tc.tool === 'create_skill');
275
+ const created = toolCalls.filter((tc) => tc.tool === 'create_skill');
256
276
  if (created.length > 0) {
257
- if (!this.#snapshot.autoCreated) this.#snapshot.autoCreated = [];
277
+ if (!this.#snapshot.autoCreated) {
278
+ this.#snapshot.autoCreated = [];
279
+ }
258
280
  for (const tc of created) {
259
281
  this.#snapshot.autoCreated.push({
260
282
  name: tc.params?.name || 'unknown',
@@ -274,7 +296,9 @@ export class SignalCollector {
274
296
  if (parsed.nextIntervalMinutes && typeof parsed.nextIntervalMinutes === 'number') {
275
297
  const aiMs = parsed.nextIntervalMinutes * 60 * 1000;
276
298
  this.#intervalMs = Math.max(MIN_INTERVAL_MS, Math.min(aiMs, MAX_INTERVAL_MS));
277
- this.#logger.info(`[SignalCollector] AI adjusted next interval to ${parsed.nextIntervalMinutes}min`);
299
+ this.#logger.info(
300
+ `[SignalCollector] AI adjusted next interval to ${parsed.nextIntervalMinutes}min`
301
+ );
278
302
  }
279
303
 
280
304
  // 8. 持久化快照
@@ -295,7 +319,9 @@ export class SignalCollector {
295
319
  }
296
320
 
297
321
  #scheduleNext(delayMs) {
298
- if (this.#mode === 'off') return;
322
+ if (this.#mode === 'off') {
323
+ return;
324
+ }
299
325
  this.#timer = setTimeout(() => this.#tick(), delayMs);
300
326
  }
301
327
 
@@ -305,10 +331,13 @@ export class SignalCollector {
305
331
 
306
332
  #collectGuardSignals() {
307
333
  try {
308
- if (!this.#db) return [];
334
+ if (!this.#db) {
335
+ return [];
336
+ }
309
337
  // audit_logs 中 action='guard:check' + result='violation' 的记录
310
- const rows = this.#db.prepare(
311
- `SELECT json_extract(operation_data, '$.ruleName') as ruleName,
338
+ const rows = this.#db
339
+ .prepare(
340
+ `SELECT json_extract(operation_data, '$.ruleName') as ruleName,
312
341
  COUNT(*) as cnt,
313
342
  MAX(timestamp) as last_at
314
343
  FROM audit_logs
@@ -317,28 +346,45 @@ export class SignalCollector {
317
346
  GROUP BY ruleName
318
347
  HAVING cnt > 0
319
348
  ORDER BY cnt DESC LIMIT 20`
320
- ).all();
349
+ )
350
+ .all();
321
351
  return rows;
322
- } catch { return []; }
352
+ } catch {
353
+ return [];
354
+ }
323
355
  }
324
356
 
325
357
  #collectMemorySignals() {
326
358
  try {
327
359
  const memoryFile = path.join(this.#projectRoot, '.autosnippet', 'memory.jsonl');
328
- if (!fs.existsSync(memoryFile)) return [];
360
+ if (!fs.existsSync(memoryFile)) {
361
+ return [];
362
+ }
329
363
  const lines = fs.readFileSync(memoryFile, 'utf-8').trim().split('\n');
330
- return lines.slice(-20).map(line => {
331
- try { return JSON.parse(line); } catch { return null; }
332
- }).filter(Boolean);
333
- } catch { return []; }
364
+ return lines
365
+ .slice(-20)
366
+ .map((line) => {
367
+ try {
368
+ return JSON.parse(line);
369
+ } catch {
370
+ return null;
371
+ }
372
+ })
373
+ .filter(Boolean);
374
+ } catch {
375
+ return [];
376
+ }
334
377
  }
335
378
 
336
379
  #collectRecipeSignals() {
337
380
  try {
338
- if (!this.#db) return [];
381
+ if (!this.#db) {
382
+ return [];
383
+ }
339
384
  // V3: knowledge_entries 统一表,统计字段在 stats/quality JSON 中
340
- const rows = this.#db.prepare(
341
- `SELECT id, title, knowledgeType, category, language,
385
+ const rows = this.#db
386
+ .prepare(
387
+ `SELECT id, title, knowledgeType, category, language,
342
388
  json_extract(stats, '$.adoptions') as adoption_count,
343
389
  json_extract(stats, '$.applications') as application_count,
344
390
  json_extract(quality, '$.overall') as quality_overall,
@@ -346,40 +392,55 @@ export class SignalCollector {
346
392
  FROM knowledge_entries
347
393
  WHERE lifecycle = 'active'
348
394
  ORDER BY updatedAt DESC LIMIT 30`
349
- ).all();
395
+ )
396
+ .all();
350
397
  return rows;
351
- } catch { return []; }
398
+ } catch {
399
+ return [];
400
+ }
352
401
  }
353
402
 
354
403
  #collectCandidateSignals() {
355
404
  try {
356
- if (!this.#db) return [];
405
+ if (!this.#db) {
406
+ return [];
407
+ }
357
408
  // V3: candidates 已合并到 knowledge_entries,lifecycle='pending' 即为候选
358
- const rows = this.#db.prepare(
359
- `SELECT id, source, lifecycle as status, language, category,
409
+ const rows = this.#db
410
+ .prepare(
411
+ `SELECT id, source, lifecycle as status, language, category,
360
412
  title, createdAt
361
413
  FROM knowledge_entries WHERE lifecycle = 'pending'
362
414
  ORDER BY createdAt DESC LIMIT 30`
363
- ).all();
415
+ )
416
+ .all();
364
417
  return rows;
365
- } catch { return []; }
418
+ } catch {
419
+ return [];
420
+ }
366
421
  }
367
422
 
368
423
  #collectRecentActions() {
369
424
  try {
370
- if (!this.#db) return [];
425
+ if (!this.#db) {
426
+ return [];
427
+ }
371
428
  // audit_logs.timestamp 是 INTEGER (epoch seconds)
372
429
  const sinceStr = this.#snapshot.lastRun;
373
430
  const sinceTs = sinceStr
374
431
  ? Math.floor(new Date(sinceStr).getTime() / 1000)
375
432
  : Math.floor((Date.now() - 24 * 3600 * 1000) / 1000);
376
- const rows = this.#db.prepare(
377
- `SELECT actor, action, resource, result, timestamp
433
+ const rows = this.#db
434
+ .prepare(
435
+ `SELECT actor, action, resource, result, timestamp
378
436
  FROM audit_logs WHERE timestamp > ?
379
437
  ORDER BY timestamp DESC LIMIT 50`
380
- ).all(sinceTs);
438
+ )
439
+ .all(sinceTs);
381
440
  return rows;
382
- } catch { return []; }
441
+ } catch {
442
+ return [];
443
+ }
383
444
  }
384
445
 
385
446
  #collectCodeChangeSignals() {
@@ -389,9 +450,13 @@ export class SignalCollector {
389
450
  encoding: 'utf-8',
390
451
  timeout: 5000,
391
452
  }).trim();
392
- if (!diff) return [];
453
+ if (!diff) {
454
+ return [];
455
+ }
393
456
  return diff.split('\n').slice(0, 20);
394
- } catch { return []; }
457
+ } catch {
458
+ return [];
459
+ }
395
460
  }
396
461
 
397
462
  // ═══════════════════════════════════════════════════════
@@ -399,9 +464,10 @@ export class SignalCollector {
399
464
  // ═══════════════════════════════════════════════════════
400
465
 
401
466
  #buildAnalysisPrompt(signals) {
402
- const modeInstruction = this.#mode === 'auto'
403
- ? '你处于 auto 模式:除了推荐之外,对于高优先级的建议,请直接调用 create_skill 工具自动创建 Skill。'
404
- : '你处于 suggest 模式:只输出推荐,不要自动创建 Skill。';
467
+ const modeInstruction =
468
+ this.#mode === 'auto'
469
+ ? '你处于 auto 模式:除了推荐之外,对于高优先级的建议,请直接调用 create_skill 工具自动创建 Skill。'
470
+ : '你处于 suggest 模式:只输出推荐,不要自动创建 Skill。';
405
471
 
406
472
  return `你是 AutoSnippet 的后台行为分析 AI。你的任务是分析以下多维度信号,判断用户当前的开发状态,并给出 Skill 推荐建议。
407
473
 
@@ -459,14 +525,18 @@ ${JSON.stringify(signals.codeChanges, null, 2)}
459
525
  */
460
526
  #parseStructuredReply(reply) {
461
527
  const defaultResult = { suggestions: [], nextIntervalMinutes: null, summary: '' };
462
- if (!reply) return defaultResult;
528
+ if (!reply) {
529
+ return defaultResult;
530
+ }
463
531
 
464
532
  try {
465
533
  // 策略 1: 通过 AiProvider.extractJSON 统一解析
466
534
  const aiProvider = this.#chatAgent?.aiProvider;
467
535
  if (aiProvider && typeof aiProvider.extractJSON === 'function') {
468
536
  const obj = aiProvider.extractJSON(reply, '{', '}');
469
- if (obj && Array.isArray(obj.suggestions)) return obj;
537
+ if (obj && Array.isArray(obj.suggestions)) {
538
+ return obj;
539
+ }
470
540
  }
471
541
 
472
542
  // 策略 2: 回退 — 从最后一行提取 JSON (兼容 prompt 指令)
@@ -476,8 +546,12 @@ ${JSON.stringify(signals.codeChanges, null, 2)}
476
546
  if (line.startsWith('{') && line.endsWith('}')) {
477
547
  try {
478
548
  const obj = JSON.parse(line);
479
- if (obj.suggestions) return obj;
480
- } catch { /* 继续 */ }
549
+ if (obj.suggestions) {
550
+ return obj;
551
+ }
552
+ } catch {
553
+ /* 继续 */
554
+ }
481
555
  }
482
556
  }
483
557
  } catch {
@@ -506,7 +580,9 @@ ${JSON.stringify(signals.codeChanges, null, 2)}
506
580
  pendingSuggestions: Array.isArray(data.pendingSuggestions) ? data.pendingSuggestions : [],
507
581
  };
508
582
  }
509
- } catch { /* corrupt — reset */ }
583
+ } catch {
584
+ /* corrupt — reset */
585
+ }
510
586
 
511
587
  return {
512
588
  lastRun: null,