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
@@ -1,24 +1,38 @@
1
1
  /**
2
2
  * GuardCheckEngine - Guard 规则检查引擎
3
- *
3
+ *
4
4
  * 从 V1 guard/ios 迁移,适配 V2 架构
5
5
  * 支持: 正则模式匹配 + AST 语义规则 + code-level 检查 + 多维度审计
6
6
  */
7
7
 
8
- import Logger from '../../infrastructure/logging/Logger.js';
9
8
  import * as AstAnalyzerModule from '../../core/AstAnalyzer.js';
9
+ import Logger from '../../infrastructure/logging/Logger.js';
10
+ import { LanguageService } from '../../shared/LanguageService.js';
10
11
 
11
12
  /**
12
- * 内置默认规则集(iOS: ObjC + Swift)
13
- * 用于在数据库为空时提供基础检查能力
13
+ * 内置默认规则集 多语言基础规则
14
+ *
15
+ * 每条规则包含:
16
+ * - message: 违反时的中文提示
17
+ * - severity: 'error' | 'warning' | 'info'
18
+ * - pattern: 行级正则(不跨行)
19
+ * - languages: 适用语言数组
20
+ * - dimension: 'file' | 'target' | 'project'
21
+ * - category: 规则分类 (安全 / 性能 / 风格 / 正确性)
22
+ * - fixSuggestion?: 修复建议
14
23
  */
15
24
  const BUILT_IN_RULES = {
25
+ // ══════════════════════════════════════════════════════════
26
+ // ObjC / Swift — iOS 核心规则
27
+ // ══════════════════════════════════════════════════════════
28
+
16
29
  'no-main-thread-sync': {
17
30
  message: '禁止在主线程上使用 dispatch_sync(main),易死锁',
18
31
  severity: 'error',
19
32
  pattern: 'dispatch_sync\\s*\\([^)]*main',
20
33
  languages: ['objc', 'swift'],
21
34
  dimension: 'file',
35
+ category: 'correctness',
22
36
  },
23
37
  'main-thread-sync-swift': {
24
38
  message: '禁止在主线程上使用 DispatchQueue.main.sync,易死锁',
@@ -26,13 +40,16 @@ const BUILT_IN_RULES = {
26
40
  pattern: 'DispatchQueue\\.main\\.sync',
27
41
  languages: ['swift'],
28
42
  dimension: 'file',
43
+ category: 'correctness',
29
44
  },
30
45
  'objc-dealloc-async': {
31
46
  message: 'dealloc 内禁止使用 dispatch_async/dispatch_after/postNotification 等',
32
47
  severity: 'error',
33
- pattern: '(dealloc.*(dispatch_async|dispatch_after|postNotification|performSelector.*afterDelay))',
48
+ pattern:
49
+ '(dealloc.*(dispatch_async|dispatch_after|postNotification|performSelector.*afterDelay))',
34
50
  languages: ['objc'],
35
51
  dimension: 'file',
52
+ category: 'correctness',
36
53
  },
37
54
  'objc-block-retain-cycle': {
38
55
  message: 'block 内直接使用 self 可能循环引用,建议 weakSelf',
@@ -40,6 +57,8 @@ const BUILT_IN_RULES = {
40
57
  pattern: '\\^\\s*[({][^}]*\\bself\\b',
41
58
  languages: ['objc'],
42
59
  dimension: 'file',
60
+ category: 'correctness',
61
+ fixSuggestion: '声明 __weak typeof(self) weakSelf = self; 后在 block 内使用 weakSelf',
43
62
  },
44
63
  'objc-assign-object': {
45
64
  message: 'assign 用于对象类型会产生悬垂指针,建议改为 weak 或 strong',
@@ -47,6 +66,7 @@ const BUILT_IN_RULES = {
47
66
  pattern: '@property\\s*\\([^)]*\\bassign\\b[^)]*\\)[^;]*(\\*|id\\s*<|\\bid\\s+)',
48
67
  languages: ['objc'],
49
68
  dimension: 'file',
69
+ category: 'correctness',
50
70
  },
51
71
  'swift-force-cast': {
52
72
  message: '强制类型转换 as! 在失败时崩溃,建议 as? 或 guard let',
@@ -54,6 +74,8 @@ const BUILT_IN_RULES = {
54
74
  pattern: 'as\\s*!',
55
75
  languages: ['swift'],
56
76
  dimension: 'file',
77
+ category: 'safety',
78
+ fixSuggestion: '使用 as? 配合 guard let / if let 进行安全转换',
57
79
  },
58
80
  'swift-force-try': {
59
81
  message: 'try! 在异常时崩溃,建议 do-catch 或 try?',
@@ -61,13 +83,16 @@ const BUILT_IN_RULES = {
61
83
  pattern: 'try\\s*!',
62
84
  languages: ['swift'],
63
85
  dimension: 'file',
86
+ category: 'safety',
64
87
  },
65
88
  'objc-timer-retain-cycle': {
66
- message: 'NSTimer 以 self 为 target 会强引用 self,需在 dealloc 前 invalidate 或使用 block 形式',
89
+ message:
90
+ 'NSTimer 以 self 为 target 会强引用 self,需在 dealloc 前 invalidate 或使用 block 形式',
67
91
  severity: 'warning',
68
92
  pattern: '(scheduledTimerWithTimeInterval|timerWithTimeInterval)[^;]*target\\s*:\\s*self',
69
93
  languages: ['objc'],
70
94
  dimension: 'file',
95
+ category: 'correctness',
71
96
  },
72
97
  'objc-possible-main-thread-blocking': {
73
98
  message: 'sleep/usleep 可能造成主线程阻塞',
@@ -75,9 +100,12 @@ const BUILT_IN_RULES = {
75
100
  pattern: '\\b(sleep|usleep)\\s*\\(',
76
101
  languages: ['objc'],
77
102
  dimension: 'file',
103
+ category: 'performance',
78
104
  },
79
105
 
80
- // ── 通用 JavaScript / TypeScript 规则 ──────────────────
106
+ // ══════════════════════════════════════════════════════════
107
+ // JavaScript / TypeScript
108
+ // ══════════════════════════════════════════════════════════
81
109
 
82
110
  'js-no-eval': {
83
111
  message: 'eval() 存在安全风险和性能问题,应避免使用',
@@ -85,6 +113,7 @@ const BUILT_IN_RULES = {
85
113
  pattern: '\\beval\\s*\\(',
86
114
  languages: ['javascript', 'typescript'],
87
115
  dimension: 'file',
116
+ category: 'safety',
88
117
  },
89
118
  'js-no-var': {
90
119
  message: '使用 let/const 替代 var,避免变量提升问题',
@@ -92,6 +121,7 @@ const BUILT_IN_RULES = {
92
121
  pattern: '\\bvar\\s+\\w+',
93
122
  languages: ['javascript', 'typescript'],
94
123
  dimension: 'file',
124
+ category: 'style',
95
125
  },
96
126
  'js-no-console-log': {
97
127
  message: '生产代码应移除 console.log,使用专用日志库',
@@ -99,6 +129,7 @@ const BUILT_IN_RULES = {
99
129
  pattern: 'console\\.log\\s*\\(',
100
130
  languages: ['javascript', 'typescript'],
101
131
  dimension: 'file',
132
+ category: 'style',
102
133
  },
103
134
  'js-no-debugger': {
104
135
  message: '生产代码中不应包含 debugger 语句',
@@ -106,13 +137,15 @@ const BUILT_IN_RULES = {
106
137
  pattern: '\\bdebugger\\b',
107
138
  languages: ['javascript', 'typescript'],
108
139
  dimension: 'file',
140
+ category: 'style',
109
141
  },
110
- 'ts-no-any': {
111
- message: '避免使用 any 类型,使用 unknown 或具体类型',
142
+ 'js-no-alert': {
143
+ message: '生产代码中不应使用 alert(),影响用户体验',
112
144
  severity: 'warning',
113
- pattern: ':\\s*any\\b',
114
- languages: ['typescript'],
145
+ pattern: '\\balert\\s*\\(',
146
+ languages: ['javascript', 'typescript'],
115
147
  dimension: 'file',
148
+ category: 'style',
116
149
  },
117
150
  'ts-no-non-null-assertion': {
118
151
  message: '非空断言 ! 可能掩盖 null/undefined 错误',
@@ -120,9 +153,12 @@ const BUILT_IN_RULES = {
120
153
  pattern: '\\w+!\\.',
121
154
  languages: ['typescript'],
122
155
  dimension: 'file',
156
+ category: 'safety',
123
157
  },
124
158
 
125
- // ── Python 规则 ──────────────────────────────────────
159
+ // ══════════════════════════════════════════════════════════
160
+ // Python
161
+ // ══════════════════════════════════════════════════════════
126
162
 
127
163
  'py-no-bare-except': {
128
164
  message: '裸 except: 会捕获所有异常(含 SystemExit),应指定异常类型',
@@ -130,6 +166,7 @@ const BUILT_IN_RULES = {
130
166
  pattern: 'except\\s*:',
131
167
  languages: ['python'],
132
168
  dimension: 'file',
169
+ category: 'correctness',
133
170
  },
134
171
  'py-no-exec': {
135
172
  message: 'exec() 存在安全风险,应避免使用',
@@ -137,16 +174,37 @@ const BUILT_IN_RULES = {
137
174
  pattern: '\\bexec\\s*\\(',
138
175
  languages: ['python'],
139
176
  dimension: 'file',
177
+ category: 'safety',
140
178
  },
141
179
  'py-no-mutable-default': {
142
180
  message: '函数默认参数使用可变对象(list/dict/set)会导致共享状态 bug',
143
181
  severity: 'warning',
144
- pattern: 'def\\s+\\w+\\s*\\([^)]*=\\s*\\[\\]',
182
+ pattern: 'def\\s+\\w+\\s*\\([^)]*=\\s*(?:\\[\\]|\\{\\}|set\\(\\))',
183
+ languages: ['python'],
184
+ dimension: 'file',
185
+ category: 'correctness',
186
+ },
187
+ 'py-no-star-import': {
188
+ message: 'from module import * 导致命名空间污染,应显式导入',
189
+ severity: 'warning',
190
+ pattern: 'from\\s+\\S+\\s+import\\s+\\*',
145
191
  languages: ['python'],
146
192
  dimension: 'file',
193
+ category: 'style',
194
+ },
195
+ 'py-no-assert-in-prod': {
196
+ message: 'assert 在 -O 模式下会被移除,不应用于生产逻辑校验',
197
+ severity: 'info',
198
+ pattern: '^\\s*assert\\s+',
199
+ languages: ['python'],
200
+ dimension: 'file',
201
+ category: 'correctness',
202
+ excludePaths: /(?:^|[\/\\])tests?[\/\\]|[\/\\]test_[^\/\\]*\.py$|_test\.py$/,
147
203
  },
148
204
 
149
- // ── Java / Kotlin 规则 ──────────────────────────────
205
+ // ══════════════════════════════════════════════════════════
206
+ // Java / Kotlin
207
+ // ══════════════════════════════════════════════════════════
150
208
 
151
209
  'java-no-system-exit': {
152
210
  message: 'System.exit() 直接终止 JVM,应抛异常或返回状态码',
@@ -154,6 +212,7 @@ const BUILT_IN_RULES = {
154
212
  pattern: 'System\\.exit\\s*\\(',
155
213
  languages: ['java', 'kotlin'],
156
214
  dimension: 'file',
215
+ category: 'correctness',
157
216
  },
158
217
  'java-no-raw-type': {
159
218
  message: '使用泛型集合替代原始类型 (如 List<String> 替代 List)',
@@ -161,9 +220,37 @@ const BUILT_IN_RULES = {
161
220
  pattern: '(List|Map|Set|Collection|Iterable)\\s+\\w+\\s*[=;]',
162
221
  languages: ['java'],
163
222
  dimension: 'file',
223
+ category: 'style',
224
+ },
225
+ 'java-no-empty-catch': {
226
+ message: '空 catch 块会静默吞掉异常,至少应记录日志',
227
+ severity: 'warning',
228
+ pattern: 'catch\\s*\\([^)]+\\)\\s*\\{\\s*\\}',
229
+ languages: ['java', 'kotlin'],
230
+ dimension: 'file',
231
+ category: 'correctness',
232
+ },
233
+ 'java-no-thread-stop': {
234
+ message: 'Thread.stop() 已废弃且不安全,使用 interrupt() 协作式终止',
235
+ severity: 'error',
236
+ pattern: '\\.stop\\s*\\(\\)',
237
+ languages: ['java'],
238
+ dimension: 'file',
239
+ category: 'safety',
240
+ },
241
+ 'kotlin-no-force-unwrap': {
242
+ message: '!! 非空断言在值为 null 时抛 NPE,应使用 ?. 或 ?: 安全访问',
243
+ severity: 'warning',
244
+ pattern: '\\w+!!',
245
+ languages: ['kotlin'],
246
+ dimension: 'file',
247
+ category: 'safety',
248
+ fixSuggestion: '使用 ?. 安全调用或 ?: 提供默认值',
164
249
  },
165
250
 
166
- // ── Go 规则 ──────────────────────────────────────────
251
+ // ══════════════════════════════════════════════════════════
252
+ // Go
253
+ // ══════════════════════════════════════════════════════════
167
254
 
168
255
  'go-no-panic': {
169
256
  message: 'panic 应仅用于不可恢复错误,库代码应返回 error',
@@ -171,52 +258,120 @@ const BUILT_IN_RULES = {
171
258
  pattern: '\\bpanic\\s*\\(',
172
259
  languages: ['go'],
173
260
  dimension: 'file',
261
+ category: 'correctness',
174
262
  },
175
263
  'go-no-err-ignored': {
176
264
  message: '错误值不应用 _ 忽略,应处理或明确标注',
177
265
  severity: 'warning',
178
- pattern: '\\b_\\s*,\\s*err\\s*:?=|_\\s*=\\s*\\w+\\(',
266
+ pattern: '\\w+\\s*,\\s*_\\s*:?=\\s*\\w|_\\s*=\\s*\\w+\\.[A-Z]\\w*\\(',
267
+ languages: ['go'],
268
+ dimension: 'file',
269
+ category: 'correctness',
270
+ },
271
+ 'go-no-init-abuse': {
272
+ message: 'init() 函数副作用难以追踪,避免在 init 中执行复杂逻辑',
273
+ severity: 'info',
274
+ pattern: 'func\\s+init\\s*\\(\\s*\\)',
179
275
  languages: ['go'],
180
276
  dimension: 'file',
277
+ category: 'style',
278
+ },
279
+ 'go-no-global-var': {
280
+ message: '全局可变变量导致并发安全问题,考虑使用依赖注入',
281
+ severity: 'info',
282
+ pattern: '^var\\s+\\w+\\s+',
283
+ languages: ['go'],
284
+ dimension: 'file',
285
+ category: 'style',
181
286
  },
182
287
 
183
- // ── Rust 规则 ────────────────────────────────────────
288
+ // ══════════════════════════════════════════════════════════
289
+ // Dart (Flutter)
290
+ // ══════════════════════════════════════════════════════════
291
+
292
+ 'dart-no-print': {
293
+ message: '生产代码应使用 logger 替代 print(),便于日志分级和关闭',
294
+ severity: 'info',
295
+ pattern: '\\bprint\\s*\\(',
296
+ languages: ['dart'],
297
+ dimension: 'file',
298
+ category: 'style',
299
+ },
300
+ 'dart-avoid-dynamic': {
301
+ message: '避免使用 dynamic 类型,使用具体类型或泛型提升类型安全',
302
+ severity: 'warning',
303
+ pattern: '\\bdynamic\\b',
304
+ languages: ['dart'],
305
+ dimension: 'file',
306
+ category: 'style',
307
+ },
308
+ 'dart-no-set-state-after-dispose': {
309
+ message: 'setState 调用前应检查 mounted 状态,避免 disposed 后调用',
310
+ severity: 'warning',
311
+ pattern: 'setState\\s*\\(',
312
+ languages: ['dart'],
313
+ dimension: 'file',
314
+ category: 'correctness',
315
+ fixSuggestion: '使用 if (mounted) setState(...) 守卫',
316
+ },
184
317
 
185
- 'rust-no-unwrap': {
186
- message: 'unwrap() None/Err panic,应使用 ? 或模式匹配',
318
+ 'dart-avoid-bang-operator': {
319
+ message: '避免使用 ! 空断言操作符,优先使用 ?? 默认值或 ?. 安全调用',
187
320
  severity: 'warning',
188
- pattern: '\\.unwrap\\s*\\(',
189
- languages: ['rust'],
321
+ pattern: '\\w+!\\.',
322
+ languages: ['dart'],
323
+ dimension: 'file',
324
+ category: 'correctness',
325
+ fixSuggestion: '使用 ?. 安全调用或 ?? 提供默认值',
326
+ },
327
+ 'dart-prefer-const-constructor': {
328
+ message: '当所有字段均为 final 时,构造函数应声明为 const 以优化 Widget 重建',
329
+ severity: 'info',
330
+ pattern: '(?<!const\\s)\\bnew\\s+\\w+\\(',
331
+ languages: ['dart'],
190
332
  dimension: 'file',
333
+ category: 'performance',
334
+ fixSuggestion: '移除 new 关键字,并在 Widget 构造调用前加 const',
191
335
  },
192
- 'rust-no-unsafe': {
193
- message: 'unsafe 代码需严格审查,确认必要性并添加安全注释',
336
+ 'dart-no-relative-import': {
337
+ message: 'lib/ 目录内应使用 package: 形式的绝对导入,避免相对路径导入',
194
338
  severity: 'info',
195
- pattern: '\\bunsafe\\s*\\{',
196
- languages: ['rust'],
339
+ pattern: "import\\s+['\"]\\.\\.?/",
340
+ languages: ['dart'],
341
+ dimension: 'file',
342
+ category: 'style',
343
+ },
344
+ 'dart-dispose-controller': {
345
+ message: 'TextEditingController/AnimationController 等须在 dispose() 中释放',
346
+ severity: 'warning',
347
+ pattern: '(?:TextEditingController|AnimationController|ScrollController|FocusNode|TabController)\\(',
348
+ languages: ['dart'],
197
349
  dimension: 'file',
350
+ category: 'correctness',
351
+ fixSuggestion: '在 State.dispose() 中调用 controller.dispose()',
198
352
  },
353
+ 'dart-no-build-context-across-async': {
354
+ message: 'BuildContext 不应跨越 async gap 使用,可能导致引用已卸载的 Widget',
355
+ severity: 'warning',
356
+ pattern: 'await\\s+.*\\n.*context\\.',
357
+ languages: ['dart'],
358
+ dimension: 'file',
359
+ category: 'correctness',
360
+ fixSuggestion: '在 await 前缓存所需数据,或在 await 后检查 mounted',
361
+ },
362
+
199
363
  };
200
364
 
201
365
  /**
202
366
  * 从文件扩展名推断语言
203
367
  */
204
368
  export function detectLanguage(filePath) {
205
- if (!filePath) return 'unknown';
206
- const ext = filePath.split('.').pop()?.toLowerCase();
207
- switch (ext) {
208
- case 'swift': return 'swift';
209
- case 'm': case 'mm': case 'h': return 'objc';
210
- case 'js': case 'mjs': case 'cjs': return 'javascript';
211
- case 'ts': case 'tsx': return 'typescript';
212
- case 'py': return 'python';
213
- case 'java': return 'java';
214
- case 'kt': case 'kts': return 'kotlin';
215
- case 'rb': return 'ruby';
216
- case 'go': return 'go';
217
- case 'rs': return 'rust';
218
- default: return 'unknown';
369
+ if (!filePath) {
370
+ return 'unknown';
219
371
  }
372
+ const lang = LanguageService.inferLang(filePath);
373
+ // 向后兼容: objectivec → objc
374
+ return lang === 'objectivec' ? 'objc' : lang;
220
375
  }
221
376
 
222
377
  /**
@@ -246,13 +401,17 @@ export class GuardCheckEngine {
246
401
  if (!this._customRulesCache || now - this._cacheTime > this._cacheTTL) {
247
402
  let rows = [];
248
403
  try {
249
- rows = this.db.prepare(
250
- `SELECT id, title, description, language, scope, constraints
404
+ rows = this.db
405
+ .prepare(
406
+ `SELECT id, title, description, language, scope, constraints
251
407
  FROM knowledge_entries
252
408
  WHERE (kind = 'rule' OR knowledgeType = 'boundary-constraint')
253
409
  AND lifecycle = 'active'`
254
- ).all();
255
- } catch { /* table may not exist */ }
410
+ )
411
+ .all();
412
+ } catch {
413
+ /* table may not exist */
414
+ }
256
415
 
257
416
  const regexRules = [];
258
417
  const astRules = [];
@@ -262,7 +421,9 @@ export class GuardCheckEngine {
262
421
  try {
263
422
  const constraints = JSON.parse(r.constraints || '{}');
264
423
  guards = constraints.guards || [];
265
- } catch { /* ignore */ }
424
+ } catch {
425
+ /* ignore */
426
+ }
266
427
 
267
428
  for (const g of guards) {
268
429
  const ruleType = g.type || 'regex';
@@ -295,7 +456,7 @@ export class GuardCheckEngine {
295
456
  }
296
457
 
297
458
  // 合并内置规则(不覆盖同名数据库规则)
298
- const existingIds = new Set(rules.map(r => r.id || r.name));
459
+ const existingIds = new Set(rules.map((r) => r.id || r.name));
299
460
  for (const [ruleId, rule] of Object.entries(this._builtInRules)) {
300
461
  if (!existingIds.has(ruleId)) {
301
462
  rules.push({
@@ -306,23 +467,25 @@ export class GuardCheckEngine {
306
467
  languages: rule.languages,
307
468
  severity: rule.severity,
308
469
  dimension: rule.dimension || 'file',
470
+ category: rule.category || '',
309
471
  source: 'built-in',
310
472
  type: 'regex',
311
473
  fixSuggestion: rule.fixSuggestion || null,
474
+ ...(rule.excludePaths ? { excludePaths: rule.excludePaths } : {}),
312
475
  });
313
476
  }
314
477
  }
315
478
 
316
479
  // 按语言过滤
317
480
  if (language) {
318
- rules = rules.filter(r => !r.languages?.length || r.languages.includes(language));
481
+ rules = rules.filter((r) => !r.languages?.length || r.languages.includes(language));
319
482
  }
320
483
 
321
484
  // 合并 AST 规则(供外部调用者使用,如 GuardFeedbackLoop.查找 fixSuggestion)
322
485
  if (this._astRulesCache?.length) {
323
486
  let astRules = this._astRulesCache;
324
487
  if (language) {
325
- astRules = astRules.filter(r => !r.languages?.length || r.languages.includes(language));
488
+ astRules = astRules.filter((r) => !r.languages?.length || r.languages.includes(language));
326
489
  }
327
490
  rules.push(...astRules);
328
491
  }
@@ -338,25 +501,40 @@ export class GuardCheckEngine {
338
501
  * @returns {Array<{ruleId, message, severity, line, snippet, dimension?, fixSuggestion?}>}
339
502
  */
340
503
  checkCode(code, language, options = {}) {
341
- const { scope = null } = options;
504
+ const { scope = null, filePath = '' } = options;
342
505
  const violations = [];
343
506
 
344
507
  // 获取匹配语言的规则
345
508
  let rules = this.getRules(language);
346
509
 
510
+ // 按 excludePaths 过滤(测试文件排除等)
511
+ if (filePath) {
512
+ rules = rules.filter((r) => {
513
+ if (!r.excludePaths) return true;
514
+ const re = r.excludePaths instanceof RegExp ? r.excludePaths : new RegExp(r.excludePaths);
515
+ return !re.test(filePath);
516
+ });
517
+ }
518
+
347
519
  // 如果有 scope,按层级过滤:project ⊇ target ⊇ file
348
520
  // project 范围包含所有维度的规则;target 包含 file+target;file 仅匹配 file
349
521
  if (scope) {
350
- const SCOPE_HIERARCHY = { project: ['file', 'target', 'project'], target: ['file', 'target'], file: ['file'] };
522
+ const SCOPE_HIERARCHY = {
523
+ project: ['file', 'target', 'project'],
524
+ target: ['file', 'target'],
525
+ file: ['file'],
526
+ };
351
527
  const allowedDimensions = SCOPE_HIERARCHY[scope] || [scope];
352
- rules = rules.filter(r => !r.dimension || allowedDimensions.includes(r.dimension));
528
+ rules = rules.filter((r) => !r.dimension || allowedDimensions.includes(r.dimension));
353
529
  }
354
530
 
355
531
  const lines = (code || '').split(/\r?\n/);
356
532
 
357
533
  for (const rule of rules) {
358
534
  // 跳过空模式或特殊标记 (?!) — 由 code-level 检查接管
359
- if (!rule.pattern || rule.pattern === '(?!)') continue;
535
+ if (!rule.pattern || rule.pattern === '(?!)') {
536
+ continue;
537
+ }
360
538
 
361
539
  let re;
362
540
  try {
@@ -391,7 +569,7 @@ export class GuardCheckEngine {
391
569
  this.trackGuardHits(violations);
392
570
 
393
571
  // ── Reasoning Enrichment: 推理信息跟随数据流动 ──
394
- return violations.map(v => ({
572
+ return violations.map((v) => ({
395
573
  ...v,
396
574
  reasoning: {
397
575
  whatViolated: v.ruleId,
@@ -410,16 +588,23 @@ export class GuardCheckEngine {
410
588
  * @returns {Array} violations
411
589
  */
412
590
  _runAstRuleChecks(code, language) {
413
- // AST 规则仅支持 ObjC + Swift
414
- const astLangMap = { objc: 'objectivec', swift: 'swift' };
415
- const astLang = astLangMap[language];
416
- if (!astLang) return [];
591
+ // AST 语言标准化 通过 LanguageService 判断是否为已知编程语言
592
+ const astLang = LanguageService.isKnownLang(language)
593
+ ? language
594
+ : language === 'objc'
595
+ ? 'objectivec'
596
+ : language;
597
+ if (!LanguageService.isKnownLang(astLang)) {
598
+ return [];
599
+ }
417
600
 
418
601
  // 获取缓存中的 AST 规则
419
- const astRules = (this._astRulesCache || []).filter(r =>
420
- !r.languages?.length || r.languages.includes(language)
602
+ const astRules = (this._astRulesCache || []).filter(
603
+ (r) => !r.languages?.length || r.languages.includes(language)
421
604
  );
422
- if (astRules.length === 0) return [];
605
+ if (astRules.length === 0) {
606
+ return [];
607
+ }
423
608
 
424
609
  // 延迟加载 AstAnalyzer
425
610
  let AstAnalyzer;
@@ -427,7 +612,9 @@ export class GuardCheckEngine {
427
612
  // 使用 dynamic import 会是 async,这里用 require 风格同步加载
428
613
  // AstAnalyzer 作为 ESM 模块,在 constructor 时已被引入
429
614
  AstAnalyzer = this._getAstAnalyzer();
430
- if (!AstAnalyzer || !AstAnalyzer.isAvailable()) return [];
615
+ if (!AstAnalyzer || !AstAnalyzer.isAvailable()) {
616
+ return [];
617
+ }
431
618
  } catch {
432
619
  this.logger.debug('AstAnalyzer not available, skipping AST rules');
433
620
  return [];
@@ -437,14 +624,18 @@ export class GuardCheckEngine {
437
624
 
438
625
  for (const rule of astRules) {
439
626
  const { astQuery } = rule;
440
- if (!astQuery?.queryType) continue;
627
+ if (!astQuery?.queryType) {
628
+ continue;
629
+ }
441
630
 
442
631
  try {
443
632
  switch (astQuery.queryType) {
444
633
  case 'mustCallThrough': {
445
634
  // 检查某 API 是否只在指定 wrapper 类中调用
446
635
  const { targetAPI, wrapperClass } = astQuery.params || {};
447
- if (!targetAPI || !wrapperClass) break;
636
+ if (!targetAPI || !wrapperClass) {
637
+ break;
638
+ }
448
639
 
449
640
  const calls = AstAnalyzer.findCallExpressions(code, astLang, targetAPI);
450
641
  for (const call of calls) {
@@ -466,7 +657,9 @@ export class GuardCheckEngine {
466
657
  case 'mustNotUseInContext': {
467
658
  // 在特定上下文中禁止使用某模式
468
659
  const { pattern: textPattern, forbiddenContext } = astQuery.params || {};
469
- if (!textPattern || !forbiddenContext) break;
660
+ if (!textPattern || !forbiddenContext) {
661
+ break;
662
+ }
470
663
 
471
664
  const matches = AstAnalyzer.findPatternInContext(code, astLang, textPattern, {
472
665
  forbiddenContext,
@@ -488,9 +681,16 @@ export class GuardCheckEngine {
488
681
  case 'mustConformToProtocol': {
489
682
  // 检查类是否实现了指定协议
490
683
  const { className, protocolName } = astQuery.params || {};
491
- if (!className || !protocolName) break;
684
+ if (!className || !protocolName) {
685
+ break;
686
+ }
492
687
 
493
- const result = AstAnalyzer.checkProtocolConformance(code, astLang, className, protocolName);
688
+ const result = AstAnalyzer.checkProtocolConformance(
689
+ code,
690
+ astLang,
691
+ className,
692
+ protocolName
693
+ );
494
694
  if (result.classFound && !result.conforms) {
495
695
  violations.push({
496
696
  ruleId: rule.id,
@@ -528,7 +728,9 @@ export class GuardCheckEngine {
528
728
  * @param {Array<{ruleId: string}>} violations
529
729
  */
530
730
  trackGuardHits(violations) {
531
- if (!violations?.length || !this.db) return;
731
+ if (!violations?.length || !this.db) {
732
+ return;
733
+ }
532
734
 
533
735
  try {
534
736
  // 收集来自数据库规则的 ruleId → 命中次数
@@ -547,13 +749,17 @@ export class GuardCheckEngine {
547
749
  updatedAt = ?
548
750
  WHERE id = ?`
549
751
  );
550
- } catch { /* table may not exist */ }
752
+ } catch {
753
+ /* table may not exist */
754
+ }
551
755
  const now = Math.floor(Date.now() / 1000);
552
756
 
553
757
  for (const [ruleId, count] of hitMap) {
554
758
  try {
555
759
  updateStmt.run(count, now, ruleId);
556
- } catch { /* 非 Recipe 规则(内置规则)忽略 */ }
760
+ } catch {
761
+ /* 非 Recipe 规则(内置规则)忽略 */
762
+ }
557
763
  }
558
764
  } catch (err) {
559
765
  this.logger.debug('trackGuardHits failed', { error: err.message });
@@ -561,15 +767,17 @@ export class GuardCheckEngine {
561
767
  }
562
768
 
563
769
  /**
564
- * 代码级别检查 - 需要上下文理解的检查
770
+ * 代码级别检查 - 需要上下文理解的检查(跨行 / 配对检查)
771
+ * 按语言分发到各自的检查逻辑
565
772
  */
566
773
  _runCodeLevelChecks(code, language, lines) {
567
774
  const violations = [];
568
775
 
776
+ // ── ObjC ──
569
777
  if (language === 'objc') {
570
778
  // KVO 观察者未移除检查
571
779
  if (code.includes('addObserver') && !code.includes('removeObserver')) {
572
- const lineIdx = lines.findIndex(l => /addObserver/.test(l));
780
+ const lineIdx = lines.findIndex((l) => /addObserver/.test(l));
573
781
  violations.push({
574
782
  ruleId: 'objc-kvo-missing-remove',
575
783
  message: '存在 addObserver 未发现配对 removeObserver,请在 dealloc 或合适时机移除',
@@ -586,13 +794,19 @@ export class GuardCheckEngine {
586
794
  for (let i = 0; i < lines.length; i++) {
587
795
  categoryRegex.lastIndex = 0;
588
796
  const m = categoryRegex.exec(lines[i]);
589
- if (!m) continue;
797
+ if (!m) {
798
+ continue;
799
+ }
590
800
  const key = `${m[1]}(${m[2]})`;
591
- if (!categories[key]) categories[key] = [];
801
+ if (!categories[key]) {
802
+ categories[key] = [];
803
+ }
592
804
  categories[key].push({ line: i + 1, snippet: lines[i].trim().slice(0, 120) });
593
805
  }
594
806
  for (const [key, occs] of Object.entries(categories)) {
595
- if (occs.length <= 1) continue;
807
+ if (occs.length <= 1) {
808
+ continue;
809
+ }
596
810
  for (let j = 1; j < occs.length; j++) {
597
811
  violations.push({
598
812
  ruleId: 'objc-duplicate-category',
@@ -606,6 +820,78 @@ export class GuardCheckEngine {
606
820
  }
607
821
  }
608
822
 
823
+ // ── JavaScript / TypeScript ──
824
+ if (language === 'javascript' || language === 'typescript') {
825
+ // Promise 未处理 rejection 检查
826
+ if (code.includes('.then(') && !code.includes('.catch(') && !code.includes('.then(') === false) {
827
+ // 简化: 检查 new Promise 或 .then() 链没有 .catch()
828
+ const thenLines = [];
829
+ for (let i = 0; i < lines.length; i++) {
830
+ if (/\.then\s*\(/.test(lines[i]) && !/\.catch\s*\(/.test(code)) {
831
+ thenLines.push(i);
832
+ }
833
+ }
834
+ if (thenLines.length > 0 && !code.includes('.catch(')) {
835
+ violations.push({
836
+ ruleId: 'js-unhandled-promise',
837
+ message: 'Promise 链缺少 .catch() 错误处理,未捕获的 rejection 可能导致静默失败',
838
+ severity: 'warning',
839
+ line: thenLines[0] + 1,
840
+ snippet: lines[thenLines[0]].trim().slice(0, 120),
841
+ dimension: 'file',
842
+ });
843
+ }
844
+ }
845
+ }
846
+
847
+ // ── Go ──
848
+ if (language === 'go') {
849
+ // defer 在循环内检查 — defer 在函数结束时才执行,循环内 defer 可能资源泄露
850
+ let inLoop = false;
851
+ for (let i = 0; i < lines.length; i++) {
852
+ const trimmed = lines[i].trim();
853
+ if (/^for\s/.test(trimmed) || /^for\s*\{/.test(trimmed)) {
854
+ inLoop = true;
855
+ }
856
+ if (inLoop && /^\s*defer\s/.test(lines[i])) {
857
+ violations.push({
858
+ ruleId: 'go-defer-in-loop',
859
+ message: 'defer 在循环内会延迟到函数返回时才执行,可能导致资源泄露或大量堆积',
860
+ severity: 'warning',
861
+ line: i + 1,
862
+ snippet: lines[i].trim().slice(0, 120),
863
+ dimension: 'file',
864
+ fixSuggestion: '将循环体提取到独立函数中,或手动调用 Close()',
865
+ });
866
+ }
867
+ // 简化: 遇到 } 且缩进回到顶层,认为循环结束
868
+ if (inLoop && trimmed === '}' && (lines[i].match(/^\t/) || lines[i].match(/^}/))) {
869
+ inLoop = false;
870
+ }
871
+ }
872
+ }
873
+
874
+ // ── Python ──
875
+ if (language === 'python') {
876
+ // 文件中同时存在 tab 和 space 缩进
877
+ let hasTab = false;
878
+ let hasSpace = false;
879
+ for (let i = 0; i < Math.min(lines.length, 200); i++) {
880
+ if (/^\t/.test(lines[i])) hasTab = true;
881
+ if (/^ {2,}/.test(lines[i]) && !/^\t/.test(lines[i])) hasSpace = true;
882
+ }
883
+ if (hasTab && hasSpace) {
884
+ violations.push({
885
+ ruleId: 'py-mixed-indentation',
886
+ message: '文件混用 tab 和 space 缩进,Python 对此敏感,请统一使用 space',
887
+ severity: 'warning',
888
+ line: 1,
889
+ snippet: '',
890
+ dimension: 'file',
891
+ });
892
+ }
893
+ }
894
+
609
895
  return violations;
610
896
  }
611
897
 
@@ -624,8 +910,8 @@ export class GuardCheckEngine {
624
910
  violations,
625
911
  summary: {
626
912
  total: violations.length,
627
- errors: violations.filter(v => v.severity === 'error').length,
628
- warnings: violations.filter(v => v.severity === 'warning').length,
913
+ errors: violations.filter((v) => v.severity === 'error').length,
914
+ warnings: violations.filter((v) => v.severity === 'warning').length,
629
915
  },
630
916
  };
631
917
  }
@@ -651,7 +937,7 @@ export class GuardCheckEngine {
651
937
  // ── 跨文件检查 ──
652
938
  const crossFileViolations = this._runCrossFileChecks(files);
653
939
  totalViolations += crossFileViolations.length;
654
- totalErrors += crossFileViolations.filter(v => v.severity === 'error').length;
940
+ totalErrors += crossFileViolations.filter((v) => v.severity === 'error').length;
655
941
 
656
942
  return {
657
943
  files: results,
@@ -660,7 +946,7 @@ export class GuardCheckEngine {
660
946
  filesChecked: results.length,
661
947
  totalViolations,
662
948
  totalErrors,
663
- filesWithViolations: results.filter(r => r.summary.total > 0).length,
949
+ filesWithViolations: results.filter((r) => r.summary.total > 0).length,
664
950
  },
665
951
  };
666
952
  }
@@ -680,7 +966,9 @@ export class GuardCheckEngine {
680
966
 
681
967
  for (const { path: filePath, content } of files) {
682
968
  const ext = filePath.split('.').pop()?.toLowerCase();
683
- if (ext !== 'm' && ext !== 'mm' && ext !== 'h') continue;
969
+ if (ext !== 'm' && ext !== 'mm' && ext !== 'h') {
970
+ continue;
971
+ }
684
972
 
685
973
  const lines = content.split(/\r?\n/);
686
974
  for (let i = 0; i < lines.length; i++) {
@@ -688,7 +976,9 @@ export class GuardCheckEngine {
688
976
  let m;
689
977
  while ((m = categoryRegex.exec(lines[i])) !== null) {
690
978
  const key = `${m[1]}(${m[2]})`;
691
- if (!categoryMap.has(key)) categoryMap.set(key, []);
979
+ if (!categoryMap.has(key)) {
980
+ categoryMap.set(key, []);
981
+ }
692
982
  categoryMap.get(key).push({
693
983
  filePath,
694
984
  line: i + 1,
@@ -701,11 +991,13 @@ export class GuardCheckEngine {
701
991
  // .h 和 .m 成对出现是正常的(声明 + 实现),只有同类型文件重名才是问题
702
992
  // 或者超过 2 处声明就一定有问题
703
993
  for (const [key, locations] of categoryMap) {
704
- if (locations.length <= 1) continue;
994
+ if (locations.length <= 1) {
995
+ continue;
996
+ }
705
997
 
706
998
  // 按文件扩展名分组: .h 和 .m/.mm 各一个是合法的
707
- const hFiles = locations.filter(l => l.filePath.endsWith('.h'));
708
- const mFiles = locations.filter(l => !l.filePath.endsWith('.h'));
999
+ const hFiles = locations.filter((l) => l.filePath.endsWith('.h'));
1000
+ const mFiles = locations.filter((l) => !l.filePath.endsWith('.h'));
709
1001
 
710
1002
  // 同类型文件中有多个声明 → 重名冲突
711
1003
  const hasDuplicateH = hFiles.length > 1;
@@ -715,10 +1007,13 @@ export class GuardCheckEngine {
715
1007
 
716
1008
  if (hasDuplicateH || hasDuplicateM || tooMany) {
717
1009
  // 收集冲突的那些位置
718
- const conflictLocations = tooMany ? locations
719
- : hasDuplicateH && hasDuplicateM ? locations
720
- : hasDuplicateH ? hFiles
721
- : mFiles;
1010
+ const conflictLocations = tooMany
1011
+ ? locations
1012
+ : hasDuplicateH && hasDuplicateM
1013
+ ? locations
1014
+ : hasDuplicateH
1015
+ ? hFiles
1016
+ : mFiles;
722
1017
 
723
1018
  violations.push({
724
1019
  ruleId: 'objc-cross-file-duplicate-category',