autosnippet 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -4,9 +4,9 @@
4
4
  */
5
5
 
6
6
  import express from 'express';
7
- import { asyncHandler } from '../middleware/errorHandler.js';
8
7
  import { getServiceContainer } from '../../injection/ServiceContainer.js';
9
8
  import { NotFoundError, ValidationError } from '../../shared/errors/index.js';
9
+ import { asyncHandler } from '../middleware/errorHandler.js';
10
10
  import { getContext, safeInt } from '../utils/routeHelpers.js';
11
11
 
12
12
  const router = express.Router();
@@ -25,7 +25,7 @@ function mapRecipeToGuardRule(r) {
25
25
  message: firstGuard.message || r.description || r.title || '',
26
26
  severity: firstGuard.severity || 'warning',
27
27
  pattern: firstGuard.pattern || r.content?.pattern || '',
28
- languages: r.tags?.length > 0 ? r.tags : (r.language ? [r.language] : []),
28
+ languages: r.tags?.length > 0 ? r.tags : r.language ? [r.language] : [],
29
29
  note: r.content?.rationale || '',
30
30
  dimension: r.scope || 'file',
31
31
  rationale: r.content?.rationale || '',
@@ -39,89 +39,141 @@ function mapRecipeToGuardRule(r) {
39
39
  * 获取防护规则列表(支持筛选和分页)
40
40
  * 同时包含内置规则 + 数据库规则
41
41
  */
42
- router.get('/', asyncHandler(async (req, res) => {
43
- const { severity, category, enabled, sourceRecipe, keyword } = req.query;
44
- const page = safeInt(req.query.page, 1);
45
- const pageSize = safeInt(req.query.limit, 20, 1, 100);
46
-
47
- const container = getServiceContainer();
48
- const guardService = container.get('guardService');
49
-
50
- // 获取数据库中的 boundary-constraint 规则
51
- let result;
52
- if (keyword) {
53
- result = await guardService.searchRules(keyword, { page, pageSize });
54
- } else {
55
- const filters = {};
56
- if (severity) filters.severity = severity;
57
- if (category) filters.category = category;
58
- if (enabled !== undefined) filters.enabled = enabled === 'true';
59
- if (sourceRecipe) filters.sourceRecipe = sourceRecipe;
60
- result = await guardService.listRules(filters, { page, pageSize });
61
- }
62
-
63
- // 将 Recipe 实体映射为 Guard 规则扁平格式
64
- const dbItems = result?.data || [];
65
- const mappedDbRules = dbItems.map(mapRecipeToGuardRule);
66
-
67
- // 合并内置规则(GuardCheckEngine 内置 9 条 iOS 规则)
68
- let guardCheckEngine;
69
- try { guardCheckEngine = container.get('guardCheckEngine'); } catch { /* not registered */ }
70
- const builtInEntries = guardCheckEngine ? Object.entries(guardCheckEngine.getBuiltInRules()) : [];
71
- const dbRuleIds = new Set(mappedDbRules.map(r => r.id));
72
- const builtInRules = builtInEntries
73
- .filter(([id]) => !dbRuleIds.has(id))
74
- .map(([id, r]) => ({
75
- id,
76
- ruleId: id,
77
- message: r.message,
78
- severity: r.severity,
79
- pattern: r.pattern,
80
- languages: r.languages || [],
81
- dimension: r.dimension || 'file',
82
- note: '',
83
- enabled: true,
84
- source: 'built-in',
85
- }));
86
-
87
- const allRules = [...mappedDbRules, ...builtInRules];
88
-
89
- res.json({
90
- success: true,
91
- data: {
92
- data: allRules,
93
- pagination: result?.pagination || { page, pageSize, total: allRules.length, pages: 1 },
94
- },
95
- });
96
- }));
42
+ router.get(
43
+ '/',
44
+ asyncHandler(async (req, res) => {
45
+ const { severity, category, enabled, sourceRecipe, keyword } = req.query;
46
+ const page = safeInt(req.query.page, 1);
47
+ const pageSize = safeInt(req.query.limit, 20, 1, 100);
48
+
49
+ const container = getServiceContainer();
50
+ const guardService = container.get('guardService');
51
+
52
+ // 获取数据库中的 boundary-constraint 规则
53
+ let result;
54
+ if (keyword) {
55
+ result = await guardService.searchRules(keyword, { page, pageSize });
56
+ } else {
57
+ const filters = {};
58
+ if (severity) {
59
+ filters.severity = severity;
60
+ }
61
+ if (category) {
62
+ filters.category = category;
63
+ }
64
+ if (enabled !== undefined) {
65
+ filters.enabled = enabled === 'true';
66
+ }
67
+ if (sourceRecipe) {
68
+ filters.sourceRecipe = sourceRecipe;
69
+ }
70
+ result = await guardService.listRules(filters, { page, pageSize });
71
+ }
72
+
73
+ // Recipe 实体映射为 Guard 规则扁平格式
74
+ const dbItems = result?.data || [];
75
+ const mappedDbRules = dbItems.map(mapRecipeToGuardRule);
76
+
77
+ // 合并内置规则(GuardCheckEngine 内置 9 条 iOS 规则)
78
+ let guardCheckEngine;
79
+ try {
80
+ guardCheckEngine = container.get('guardCheckEngine');
81
+ } catch {
82
+ /* not registered */
83
+ }
84
+ const builtInEntries = guardCheckEngine
85
+ ? Object.entries(guardCheckEngine.getBuiltInRules())
86
+ : [];
87
+ const dbRuleIds = new Set(mappedDbRules.map((r) => r.id));
88
+ const builtInRules = builtInEntries
89
+ .filter(([id]) => !dbRuleIds.has(id))
90
+ .map(([id, r]) => ({
91
+ id,
92
+ ruleId: id,
93
+ message: r.message,
94
+ severity: r.severity,
95
+ pattern: r.pattern,
96
+ languages: r.languages || [],
97
+ dimension: r.dimension || 'file',
98
+ category: r.category || '',
99
+ fixSuggestion: r.fixSuggestion || '',
100
+ note: '',
101
+ enabled: true,
102
+ source: 'built-in',
103
+ }));
104
+
105
+ const allRules = [...mappedDbRules, ...builtInRules];
106
+
107
+ // 获取当前项目检测到的语言列表,供前端按项目语言筛选
108
+ // discoverer ID → 对应的 guard 规则语言
109
+ const DISCOVERER_TO_GUARD_LANGS = {
110
+ spm: ['swift', 'objc'],
111
+ node: ['javascript', 'typescript'],
112
+ go: ['go'],
113
+ jvm: ['java', 'kotlin'],
114
+ python: ['python'],
115
+ dart: ['dart'],
116
+ };
117
+ let projectLanguages = [];
118
+ try {
119
+ const moduleService = container.get('moduleService');
120
+ const info = moduleService.getProjectInfo();
121
+ const discovererIds = info.languages || [];
122
+ const langSet = new Set();
123
+ for (const did of discovererIds) {
124
+ for (const lang of DISCOVERER_TO_GUARD_LANGS[did] || []) {
125
+ langSet.add(lang);
126
+ }
127
+ }
128
+ projectLanguages = [...langSet];
129
+ } catch {
130
+ /* moduleService not loaded yet */
131
+ }
132
+
133
+ res.json({
134
+ success: true,
135
+ data: {
136
+ data: allRules,
137
+ projectLanguages,
138
+ pagination: result?.pagination || { page, pageSize, total: allRules.length, pages: 1 },
139
+ },
140
+ });
141
+ })
142
+ );
97
143
 
98
144
  /**
99
145
  * GET /api/v1/rules/stats
100
146
  * 获取防护规则统计
101
147
  */
102
- router.get('/stats', asyncHandler(async (req, res) => {
103
- const container = getServiceContainer();
104
- const guardService = container.get('guardService');
105
- const stats = await guardService.getRuleStats();
106
- res.json({ success: true, data: stats });
107
- }));
148
+ router.get(
149
+ '/stats',
150
+ asyncHandler(async (req, res) => {
151
+ const container = getServiceContainer();
152
+ const guardService = container.get('guardService');
153
+ const stats = await guardService.getRuleStats();
154
+ res.json({ success: true, data: stats });
155
+ })
156
+ );
108
157
 
109
158
  /**
110
159
  * GET /api/v1/rules/:id
111
160
  * 获取防护规则详情
112
161
  */
113
- router.get('/:id', asyncHandler(async (req, res) => {
114
- const { id } = req.params;
115
- const container = getServiceContainer();
116
- const recipeRepo = container.get('recipeRepository');
117
- const rule = await recipeRepo.findById(id);
118
-
119
- if (!rule) {
120
- throw new NotFoundError('Guard rule not found', 'recipe', id);
121
- }
122
-
123
- res.json({ success: true, data: rule });
124
- }));
162
+ router.get(
163
+ '/:id',
164
+ asyncHandler(async (req, res) => {
165
+ const { id } = req.params;
166
+ const container = getServiceContainer();
167
+ const recipeRepo = container.get('knowledgeRepository');
168
+ const rule = await recipeRepo.findById(id);
169
+
170
+ if (!rule) {
171
+ throw new NotFoundError('Guard rule not found', 'recipe', id);
172
+ }
173
+
174
+ res.json({ success: true, data: rule });
175
+ })
176
+ );
125
177
 
126
178
  /**
127
179
  * POST /api/v1/rules
@@ -129,167 +181,204 @@ router.get('/:id', asyncHandler(async (req, res) => {
129
181
  * 兼容前端字段: { ruleId, message, pattern, languages, note, dimension }
130
182
  * 同时兼容 V2 字段: { name, description, pattern, severity, category }
131
183
  */
132
- router.post('/', asyncHandler(async (req, res) => {
133
- // 兼容前端 GuardView 发来的字段名
134
- const name = req.body.name || req.body.ruleId;
135
- const description = req.body.description || req.body.message || '';
136
- const { pattern, severity, category, sourceRecipeId, sourceReason } = req.body;
137
- const note = req.body.note || sourceReason || '';
138
- const languages = req.body.languages || (category ? [category] : []);
139
- const dimension = req.body.dimension || null;
140
-
141
- if (!name || !pattern) {
142
- throw new ValidationError('name/ruleId and pattern are required');
143
- }
144
-
145
- const result = await req.gw('guard_rule:create', 'guard_rules', {
146
- name, description, pattern, severity: severity || 'warning',
147
- category: languages[0] || category || 'guard',
148
- languages, note, dimension,
149
- sourceRecipeId, sourceReason: note,
150
- });
151
-
152
- res.status(201).json({ success: true, data: result.data, requestId: result.requestId });
153
- }));
184
+ router.post(
185
+ '/',
186
+ asyncHandler(async (req, res) => {
187
+ // 兼容前端 GuardView 发来的字段名
188
+ const name = req.body.name || req.body.ruleId;
189
+ const description = req.body.description || req.body.message || '';
190
+ const { pattern, severity, category, sourceRecipeId, sourceReason } = req.body;
191
+ const note = req.body.note || sourceReason || '';
192
+ const languages = req.body.languages || (category ? [category] : []);
193
+ const dimension = req.body.dimension || null;
194
+
195
+ if (!name || !pattern) {
196
+ throw new ValidationError('name/ruleId and pattern are required');
197
+ }
198
+
199
+ const result = await req.gw('guard_rule:create', 'guard_rules', {
200
+ name,
201
+ description,
202
+ pattern,
203
+ severity: severity || 'warning',
204
+ category: languages[0] || category || 'guard',
205
+ languages,
206
+ note,
207
+ dimension,
208
+ sourceRecipeId,
209
+ sourceReason: note,
210
+ });
211
+
212
+ res.status(201).json({ success: true, data: result.data, requestId: result.requestId });
213
+ })
214
+ );
154
215
 
155
216
  /**
156
217
  * POST /api/v1/rules/batch-enable
157
218
  * 批量启用防护规则
158
219
  */
159
- router.post('/batch-enable', asyncHandler(async (req, res) => {
160
- const { ids } = req.body;
161
-
162
- if (!Array.isArray(ids) || ids.length === 0) {
163
- throw new ValidationError('ids array is required and must not be empty');
164
- }
165
- if (ids.length > MAX_BATCH_SIZE) {
166
- throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
167
- }
168
-
169
- const container = getServiceContainer();
170
- const guardService = container.get('guardService');
171
- const context = getContext(req);
172
-
173
- const results = await Promise.allSettled(
174
- ids.map(id => guardService.enableRule(id, context)),
175
- );
176
-
177
- const enabled = results.filter(r => r.status === 'fulfilled').map(r => r.value);
178
- const failed = results
179
- .map((r, i) => r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null)
180
- .filter(Boolean);
181
-
182
- res.json({
183
- success: true,
184
- data: { enabled, failed, total: ids.length, successCount: enabled.length, failureCount: failed.length },
185
- });
186
- }));
220
+ router.post(
221
+ '/batch-enable',
222
+ asyncHandler(async (req, res) => {
223
+ const { ids } = req.body;
224
+
225
+ if (!Array.isArray(ids) || ids.length === 0) {
226
+ throw new ValidationError('ids array is required and must not be empty');
227
+ }
228
+ if (ids.length > MAX_BATCH_SIZE) {
229
+ throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
230
+ }
231
+
232
+ const container = getServiceContainer();
233
+ const guardService = container.get('guardService');
234
+ const context = getContext(req);
235
+
236
+ const results = await Promise.allSettled(ids.map((id) => guardService.enableRule(id, context)));
237
+
238
+ const enabled = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
239
+ const failed = results
240
+ .map((r, i) => (r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null))
241
+ .filter(Boolean);
242
+
243
+ res.json({
244
+ success: true,
245
+ data: {
246
+ enabled,
247
+ failed,
248
+ total: ids.length,
249
+ successCount: enabled.length,
250
+ failureCount: failed.length,
251
+ },
252
+ });
253
+ })
254
+ );
187
255
 
188
256
  /**
189
257
  * POST /api/v1/rules/batch-disable
190
258
  * 批量禁用防护规则
191
259
  */
192
- router.post('/batch-disable', asyncHandler(async (req, res) => {
193
- const { ids, reason } = req.body;
194
-
195
- if (!Array.isArray(ids) || ids.length === 0) {
196
- throw new ValidationError('ids array is required and must not be empty');
197
- }
198
- if (ids.length > MAX_BATCH_SIZE) {
199
- throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
200
- }
201
-
202
- const container = getServiceContainer();
203
- const guardService = container.get('guardService');
204
- const context = getContext(req);
205
-
206
- const results = await Promise.allSettled(
207
- ids.map(id => guardService.disableRule(id, reason || '', context)),
208
- );
209
-
210
- const disabled = results.filter(r => r.status === 'fulfilled').map(r => r.value);
211
- const failed = results
212
- .map((r, i) => r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null)
213
- .filter(Boolean);
214
-
215
- res.json({
216
- success: true,
217
- data: { disabled, failed, total: ids.length, successCount: disabled.length, failureCount: failed.length },
218
- });
219
- }));
260
+ router.post(
261
+ '/batch-disable',
262
+ asyncHandler(async (req, res) => {
263
+ const { ids, reason } = req.body;
264
+
265
+ if (!Array.isArray(ids) || ids.length === 0) {
266
+ throw new ValidationError('ids array is required and must not be empty');
267
+ }
268
+ if (ids.length > MAX_BATCH_SIZE) {
269
+ throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
270
+ }
271
+
272
+ const container = getServiceContainer();
273
+ const guardService = container.get('guardService');
274
+ const context = getContext(req);
275
+
276
+ const results = await Promise.allSettled(
277
+ ids.map((id) => guardService.disableRule(id, reason || '', context))
278
+ );
279
+
280
+ const disabled = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
281
+ const failed = results
282
+ .map((r, i) => (r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null))
283
+ .filter(Boolean);
284
+
285
+ res.json({
286
+ success: true,
287
+ data: {
288
+ disabled,
289
+ failed,
290
+ total: ids.length,
291
+ successCount: disabled.length,
292
+ failureCount: failed.length,
293
+ },
294
+ });
295
+ })
296
+ );
220
297
 
221
298
  /**
222
299
  * PATCH /api/v1/rules/:id/enable
223
300
  * 启用防护规则
224
301
  */
225
- router.patch('/:id/enable', asyncHandler(async (req, res) => {
226
- const { id } = req.params;
227
- const container = getServiceContainer();
228
- const guardService = container.get('guardService');
229
- const context = getContext(req);
230
-
231
- const rule = await guardService.enableRule(id, context);
232
- res.json({ success: true, data: rule });
233
- }));
302
+ router.patch(
303
+ '/:id/enable',
304
+ asyncHandler(async (req, res) => {
305
+ const { id } = req.params;
306
+ const container = getServiceContainer();
307
+ const guardService = container.get('guardService');
308
+ const context = getContext(req);
309
+
310
+ const rule = await guardService.enableRule(id, context);
311
+ res.json({ success: true, data: rule });
312
+ })
313
+ );
234
314
 
235
315
  /**
236
316
  * PATCH /api/v1/rules/:id/disable
237
317
  * 禁用防护规则
238
318
  */
239
- router.patch('/:id/disable', asyncHandler(async (req, res) => {
240
- const { id } = req.params;
241
- const { reason } = req.body;
319
+ router.patch(
320
+ '/:id/disable',
321
+ asyncHandler(async (req, res) => {
322
+ const { id } = req.params;
323
+ const { reason } = req.body;
242
324
 
243
- const container = getServiceContainer();
244
- const guardService = container.get('guardService');
245
- const context = getContext(req);
325
+ const container = getServiceContainer();
326
+ const guardService = container.get('guardService');
327
+ const context = getContext(req);
246
328
 
247
- const rule = await guardService.disableRule(id, reason || '', context);
248
- res.json({ success: true, data: rule });
249
- }));
329
+ const rule = await guardService.disableRule(id, reason || '', context);
330
+ res.json({ success: true, data: rule });
331
+ })
332
+ );
250
333
 
251
334
  /**
252
335
  * POST /api/v1/rules/check
253
336
  * 检查代码是否违反规则
254
337
  */
255
- router.post('/check', asyncHandler(async (req, res) => {
256
- const { code, language, ruleIds } = req.body;
338
+ router.post(
339
+ '/check',
340
+ asyncHandler(async (req, res) => {
341
+ const { code, language, ruleIds } = req.body;
257
342
 
258
- if (!code) {
259
- throw new ValidationError('code is required');
260
- }
343
+ if (!code) {
344
+ throw new ValidationError('code is required');
345
+ }
261
346
 
262
- const container = getServiceContainer();
263
- const guardService = container.get('guardService');
347
+ const container = getServiceContainer();
348
+ const guardService = container.get('guardService');
264
349
 
265
- const result = await guardService.checkCode(code, { language, ruleIds });
266
- res.json({ success: true, data: result });
267
- }));
350
+ const result = await guardService.checkCode(code, { language, ruleIds });
351
+ res.json({ success: true, data: result });
352
+ })
353
+ );
268
354
 
269
355
  /**
270
356
  * POST /api/v1/rules/import-from-recipe
271
357
  * 从 Recipe 导入防护规则
272
358
  */
273
- router.post('/import-from-recipe', asyncHandler(async (req, res) => {
274
- const { recipeId, rules } = req.body;
275
-
276
- if (!recipeId) {
277
- throw new ValidationError('recipeId is required');
278
- }
279
- if (!Array.isArray(rules) || rules.length === 0) {
280
- throw new ValidationError('rules array is required and must not be empty');
281
- }
282
-
283
- const container = getServiceContainer();
284
- const guardService = container.get('guardService');
285
- const context = getContext(req);
286
-
287
- const importedRules = await guardService.importRulesFromRecipe(recipeId, rules, context);
288
- res.status(201).json({
289
- success: true,
290
- data: { importedRules, count: importedRules.length },
291
- });
292
- }));
359
+ router.post(
360
+ '/import-from-recipe',
361
+ asyncHandler(async (req, res) => {
362
+ const { recipeId, rules } = req.body;
363
+
364
+ if (!recipeId) {
365
+ throw new ValidationError('recipeId is required');
366
+ }
367
+ if (!Array.isArray(rules) || rules.length === 0) {
368
+ throw new ValidationError('rules array is required and must not be empty');
369
+ }
370
+
371
+ const container = getServiceContainer();
372
+ const guardService = container.get('guardService');
373
+ const context = getContext(req);
374
+
375
+ const importedRules = await guardService.importRulesFromRecipe(recipeId, rules, context);
376
+ res.status(201).json({
377
+ success: true,
378
+ data: { importedRules, count: importedRules.length },
379
+ });
380
+ })
381
+ );
293
382
 
294
383
  /**
295
384
  * GET /api/v1/rules/compliance
@@ -301,21 +390,24 @@ router.post('/import-from-recipe', asyncHandler(async (req, res) => {
301
390
  * - minScore: Quality Gate 最低分(默认 70)
302
391
  * - maxFiles: 最大扫描文件数(默认 500)
303
392
  */
304
- router.get('/compliance', asyncHandler(async (req, res) => {
305
- const container = getServiceContainer();
306
- const reporter = container.get('complianceReporter');
307
- const projectRoot = req.query.path || process.env.ASD_PROJECT_DIR || process.cwd();
308
-
309
- const report = await reporter.generate(projectRoot, {
310
- qualityGate: {
311
- maxErrors: parseInt(req.query.maxErrors) || 0,
312
- maxWarnings: parseInt(req.query.maxWarnings) || 20,
313
- minScore: parseInt(req.query.minScore) || 70,
314
- },
315
- maxFiles: parseInt(req.query.maxFiles) || 500,
316
- });
317
-
318
- res.json({ success: true, data: report });
319
- }));
393
+ router.get(
394
+ '/compliance',
395
+ asyncHandler(async (req, res) => {
396
+ const container = getServiceContainer();
397
+ const reporter = container.get('complianceReporter');
398
+ const projectRoot = req.query.path || process.env.ASD_PROJECT_DIR || process.cwd();
399
+
400
+ const report = await reporter.generate(projectRoot, {
401
+ qualityGate: {
402
+ maxErrors: parseInt(req.query.maxErrors) || 0,
403
+ maxWarnings: parseInt(req.query.maxWarnings) || 20,
404
+ minScore: parseInt(req.query.minScore) || 70,
405
+ },
406
+ maxFiles: parseInt(req.query.maxFiles) || 500,
407
+ });
408
+
409
+ res.json({ success: true, data: report });
410
+ })
411
+ );
320
412
 
321
413
  export default router;