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,5 +1,5 @@
1
- import { ConstitutionViolation } from '../../shared/errors/BaseError.js';
2
1
  import Logger from '../../infrastructure/logging/Logger.js';
2
+ import { ConstitutionViolation } from '../../shared/errors/BaseError.js';
3
3
 
4
4
  /**
5
5
  * ConstitutionValidator — 数据守护验证器
@@ -32,7 +32,9 @@ export class ConstitutionValidator {
32
32
  const checker = this.checkers[rule.check];
33
33
  if (checker) {
34
34
  const v = checker(request, rule);
35
- if (v) violations.push(v);
35
+ if (v) {
36
+ violations.push(v);
37
+ }
36
38
  }
37
39
  }
38
40
 
@@ -72,8 +74,12 @@ export class ConstitutionValidator {
72
74
  /** 删除操作需要确认 */
73
75
  _checkDestructive(req, rule) {
74
76
  const destructive = ['delete', 'remove', 'destroy', 'purge', 'batch_delete'];
75
- if (!destructive.some(w => req.action?.toLowerCase().includes(w))) return null;
76
- if (req.data?.confirmed || req.confirmed) return null;
77
+ if (!destructive.some((w) => req.action?.toLowerCase().includes(w))) {
78
+ return null;
79
+ }
80
+ if (req.data?.confirmed || req.confirmed) {
81
+ return null;
82
+ }
77
83
  return { rule: rule.id, reason: '操作未经确认', suggestion: '添加 confirmed: true' };
78
84
  }
79
85
 
@@ -83,43 +89,70 @@ export class ConstitutionValidator {
83
89
  const res = this._resource(req.action, req.resource);
84
90
  const isCreation =
85
91
  (verb === 'create' && (res.includes('candidate') || res.includes('recipe'))) ||
86
- req.action === 'create_candidate' || req.action === 'create_recipe';
87
- if (!isCreation) return null;
88
- const ok = req.data?.code || req.code || req.data?.content
89
- || (Array.isArray(req.data?.items) && req.data.items.length > 0)
90
- || req.data?.filePaths;
91
- if (ok) return null;
92
+ req.action === 'create_candidate' ||
93
+ req.action === 'create_recipe';
94
+ if (!isCreation) {
95
+ return null;
96
+ }
97
+ const ok =
98
+ req.data?.code ||
99
+ req.code ||
100
+ req.data?.content ||
101
+ (Array.isArray(req.data?.items) && req.data.items.length > 0) ||
102
+ req.data?.filePaths;
103
+ if (ok) {
104
+ return null;
105
+ }
92
106
  return { rule: rule.id, reason: '缺少 code/content', suggestion: '提供代码或内容' };
93
107
  }
94
108
 
95
109
  /** AI 不能直接创建/批准 Recipe */
96
110
  _checkAiRecipe(req, rule) {
97
- if (!this._isAI(req.actor)) return null;
111
+ if (!this._isAI(req.actor)) {
112
+ return null;
113
+ }
98
114
  const verb = this._verb(req.action);
99
115
  const res = this._resource(req.action, req.resource);
100
116
  const isRecipeMod =
101
- (verb === 'create' && res.includes('recipe')) || verb === 'approve' || verb === 'publish'
102
- || req.action === 'approve_candidate' || req.action === 'create_recipe';
103
- if (!isRecipeMod) return null;
104
- return { rule: rule.id, reason: 'AI 不能直接操作 Recipe', suggestion: '通过 Dashboard 人工审批' };
117
+ (verb === 'create' && res.includes('recipe')) ||
118
+ verb === 'approve' ||
119
+ verb === 'publish' ||
120
+ req.action === 'approve_candidate' ||
121
+ req.action === 'create_recipe';
122
+ if (!isRecipeMod) {
123
+ return null;
124
+ }
125
+ return {
126
+ rule: rule.id,
127
+ reason: 'AI 不能直接操作 Recipe',
128
+ suggestion: '通过 Dashboard 人工审批',
129
+ };
105
130
  }
106
131
 
107
132
  /** 批量操作需要授权 */
108
133
  _checkBatch(req, rule) {
109
- if (!req.action?.includes('batch_')) return null;
110
- if (req.data?.authorized || req.authorized) return null;
134
+ if (!req.action?.includes('batch_')) {
135
+ return null;
136
+ }
137
+ if (req.data?.authorized || req.authorized) {
138
+ return null;
139
+ }
111
140
  return { rule: rule.id, reason: '缺少授权标志', suggestion: '添加 authorized: true' };
112
141
  }
113
142
 
114
143
  // ─── 辅助方法 ──────────────────────────────────────────
115
144
 
116
145
  _verb(action) {
117
- if (!action) return '';
146
+ if (!action) {
147
+ return '';
148
+ }
118
149
  return action.includes(':') ? action.split(':').pop() : action;
119
150
  }
120
151
 
121
152
  _resource(action, resource) {
122
- if (action?.includes(':')) return action.split(':')[0];
153
+ if (action?.includes(':')) {
154
+ return action.split(':')[0];
155
+ }
123
156
  if (typeof resource === 'string' && resource.startsWith('/')) {
124
157
  const m = resource.match(/^\/([^/]+)/);
125
158
  return m ? m[1] : resource;
@@ -128,7 +161,7 @@ export class ConstitutionValidator {
128
161
  }
129
162
 
130
163
  _isAI(actor) {
131
- return ['external_agent', 'chat_agent'].some(a => actor?.toLowerCase().includes(a));
164
+ return ['external_agent', 'chat_agent'].some((a) => actor?.toLowerCase().includes(a));
132
165
  }
133
166
 
134
167
  // ─── 旧格式兼容 ───────────────────────────────────────
@@ -138,15 +171,23 @@ export class ConstitutionValidator {
138
171
  const violations = [];
139
172
  if (priority.id === 1) {
140
173
  const v = this._checkDestructive(request, { id: 'destructive_confirm' });
141
- if (v) violations.push(v);
174
+ if (v) {
175
+ violations.push(v);
176
+ }
142
177
  const v2 = this._checkContent(request, { id: 'content_required' });
143
- if (v2) violations.push(v2);
178
+ if (v2) {
179
+ violations.push(v2);
180
+ }
144
181
  }
145
182
  if (priority.id === 2) {
146
183
  const v = this._checkAiRecipe(request, { id: 'ai_no_direct_recipe' });
147
- if (v) violations.push(v);
184
+ if (v) {
185
+ violations.push(v);
186
+ }
148
187
  const v2 = this._checkBatch(request, { id: 'batch_authorized' });
149
- if (v2) violations.push(v2);
188
+ if (v2) {
189
+ violations.push(v2);
190
+ }
150
191
  }
151
192
  return violations;
152
193
  }
@@ -0,0 +1,534 @@
1
+ /**
2
+ * @module DartDiscoverer
3
+ * @description Dart / Flutter 项目结构发现器
4
+ *
5
+ * 检测信号: pubspec.yaml, pubspec.lock, .dart_tool/, *.dart
6
+ * 支持: 单 Package 项目、Flutter 应用、Melos 多包工作区
7
+ */
8
+
9
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
10
+ import { basename, extname, join, relative } from 'node:path';
11
+ import { ProjectDiscoverer } from './ProjectDiscoverer.js';
12
+
13
+ const SOURCE_EXTENSIONS = new Set(['.dart']);
14
+
15
+ const EXCLUDE_DIRS = new Set([
16
+ '.git',
17
+ '.dart_tool',
18
+ '.fvm',
19
+ 'build',
20
+ 'node_modules',
21
+ '.idea',
22
+ '.vscode',
23
+ 'ios',
24
+ 'android',
25
+ 'macos',
26
+ 'windows',
27
+ 'linux',
28
+ 'web',
29
+ '.pub-cache',
30
+ '.pub',
31
+ ]);
32
+
33
+ export class DartDiscoverer extends ProjectDiscoverer {
34
+ #projectRoot = null;
35
+ #targets = [];
36
+ #depGraph = { nodes: [], edges: [] };
37
+ #packageName = null;
38
+
39
+ get id() {
40
+ return 'dart';
41
+ }
42
+ get displayName() {
43
+ return 'Dart / Flutter';
44
+ }
45
+
46
+ async detect(projectRoot) {
47
+ let confidence = 0;
48
+ const reasons = [];
49
+
50
+ if (existsSync(join(projectRoot, 'pubspec.yaml'))) {
51
+ confidence = 0.92;
52
+ reasons.push('pubspec.yaml exists');
53
+ }
54
+ if (existsSync(join(projectRoot, 'pubspec.lock'))) {
55
+ confidence = Math.max(confidence, 0.7);
56
+ if (confidence < 0.92) {
57
+ confidence += 0.1;
58
+ }
59
+ reasons.push('pubspec.lock exists');
60
+ }
61
+ if (existsSync(join(projectRoot, '.dart_tool'))) {
62
+ confidence = Math.max(confidence, 0.6);
63
+ reasons.push('.dart_tool exists');
64
+ }
65
+ // Melos workspace
66
+ if (existsSync(join(projectRoot, 'melos.yaml'))) {
67
+ confidence = Math.max(confidence, 0.95);
68
+ reasons.push('melos.yaml exists (workspace)');
69
+ }
70
+
71
+ // 兜底: 根目录有 .dart 文件
72
+ if (confidence === 0) {
73
+ try {
74
+ const entries = readdirSync(projectRoot);
75
+ if (entries.some((e) => e.endsWith('.dart'))) {
76
+ confidence = 0.5;
77
+ reasons.push('*.dart files found at root');
78
+ }
79
+ } catch {
80
+ /* skip */
81
+ }
82
+ }
83
+
84
+ return {
85
+ match: confidence > 0,
86
+ confidence: Math.min(confidence, 1.0),
87
+ reason: reasons.join(', ') || 'No Dart markers found',
88
+ };
89
+ }
90
+
91
+ async load(projectRoot) {
92
+ this.#projectRoot = projectRoot;
93
+ this.#targets = [];
94
+ this.#depGraph = { nodes: [], edges: [] };
95
+
96
+ // 解析 pubspec.yaml
97
+ const pubspec = this.#parsePubspec(projectRoot);
98
+ this.#packageName = pubspec?.name || basename(projectRoot);
99
+
100
+ const framework = this.#detectFramework(pubspec);
101
+ const isFlutter = framework === 'flutter' || !!pubspec?.dependencies?.flutter;
102
+
103
+ // 主 Target — lib/
104
+ this.#targets.push({
105
+ name: this.#packageName,
106
+ path: join(projectRoot, 'lib'),
107
+ type: 'library',
108
+ language: 'dart',
109
+ framework,
110
+ metadata: {
111
+ packageName: this.#packageName,
112
+ isFlutter,
113
+ sdkVersion: pubspec?.environment?.sdk || null,
114
+ flutterVersion: pubspec?.environment?.flutter || null,
115
+ },
116
+ });
117
+ this.#depGraph.nodes.push(this.#packageName);
118
+
119
+ // bin/ — CLI 应用入口
120
+ const binDir = join(projectRoot, 'bin');
121
+ if (existsSync(binDir)) {
122
+ this.#targets.push({
123
+ name: 'bin',
124
+ path: binDir,
125
+ type: 'application',
126
+ language: 'dart',
127
+ framework,
128
+ });
129
+ }
130
+
131
+ // test/ — 测试目录
132
+ for (const testDir of ['test', 'test_driver', 'integration_test']) {
133
+ const testPath = join(projectRoot, testDir);
134
+ if (existsSync(testPath)) {
135
+ this.#targets.push({
136
+ name: testDir,
137
+ path: testPath,
138
+ type: 'test',
139
+ language: 'dart',
140
+ });
141
+ }
142
+ }
143
+
144
+ // example/ — 示例项目
145
+ const exampleDir = join(projectRoot, 'example');
146
+ if (existsSync(exampleDir) && existsSync(join(exampleDir, 'pubspec.yaml'))) {
147
+ this.#targets.push({
148
+ name: 'example',
149
+ path: exampleDir,
150
+ type: 'example',
151
+ language: 'dart',
152
+ framework,
153
+ });
154
+ }
155
+
156
+ // Melos 多包工作区
157
+ this.#discoverMelosPackages(projectRoot);
158
+
159
+ // 解析依赖图
160
+ this.#parseDependencies(pubspec);
161
+
162
+ // 解析内部 import 关系
163
+ this.#parseInternalImports(projectRoot);
164
+ }
165
+
166
+ async listTargets() {
167
+ return this.#targets;
168
+ }
169
+
170
+ async getTargetFiles(target) {
171
+ const targetPath =
172
+ typeof target === 'string'
173
+ ? this.#targets.find((t) => t.name === target)?.path || this.#projectRoot
174
+ : target.path;
175
+
176
+ if (!targetPath || !existsSync(targetPath)) {
177
+ return [];
178
+ }
179
+
180
+ const files = [];
181
+ this.#collectDartFiles(targetPath, targetPath, files);
182
+ return files;
183
+ }
184
+
185
+ async getDependencyGraph() {
186
+ return this.#depGraph;
187
+ }
188
+
189
+ // ── 内部实现 ──
190
+
191
+ /**
192
+ * 解析 pubspec.yaml(简易 YAML 解析,不引入三方依赖)
193
+ */
194
+ #parsePubspec(projectRoot) {
195
+ const pubspecPath = join(projectRoot, 'pubspec.yaml');
196
+ if (!existsSync(pubspecPath)) {
197
+ return null;
198
+ }
199
+ try {
200
+ const content = readFileSync(pubspecPath, 'utf8');
201
+ return this.#parseSimpleYaml(content);
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * 极简 YAML 解析器 — 仅支持顶层和一层嵌套的 key: value
209
+ * 用于解析 pubspec.yaml 中的 name, dependencies, environment 等
210
+ */
211
+ #parseSimpleYaml(content) {
212
+ const result = {};
213
+ let currentSection = null;
214
+
215
+ for (const line of content.split('\n')) {
216
+ // 跳过注释和空行
217
+ if (/^\s*#/.test(line) || /^\s*$/.test(line)) {
218
+ continue;
219
+ }
220
+
221
+ // 顶层 key(无缩进)
222
+ const topMatch = line.match(/^(\w[\w-]*):\s*(.*)/);
223
+ if (topMatch) {
224
+ const key = topMatch[1];
225
+ const value = topMatch[2].trim();
226
+ if (value) {
227
+ result[key] = value;
228
+ currentSection = null;
229
+ } else {
230
+ result[key] = {};
231
+ currentSection = key;
232
+ }
233
+ continue;
234
+ }
235
+
236
+ // 嵌套 key(有缩进)
237
+ if (currentSection) {
238
+ const nestedMatch = line.match(/^\s+(\w[\w-]*):\s*(.*)/);
239
+ if (nestedMatch) {
240
+ const key = nestedMatch[1];
241
+ const value = nestedMatch[2].trim();
242
+ if (typeof result[currentSection] === 'object') {
243
+ result[currentSection][key] = value || true;
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ return result;
250
+ }
251
+
252
+ /**
253
+ * 检测 Flutter/Dart 框架
254
+ */
255
+ #detectFramework(pubspec) {
256
+ if (!pubspec) {
257
+ return null;
258
+ }
259
+
260
+ const deps = {
261
+ ...(typeof pubspec.dependencies === 'object' ? pubspec.dependencies : {}),
262
+ ...(typeof pubspec.dev_dependencies === 'object' ? pubspec.dev_dependencies : {}),
263
+ };
264
+
265
+ // Flutter SDK
266
+ if (deps.flutter || deps.flutter_test) {
267
+ // Sub-framework detection
268
+ if (deps.flutter_riverpod || deps.hooks_riverpod || deps.riverpod) {
269
+ return 'flutter-riverpod';
270
+ }
271
+ if (deps.flutter_bloc || deps.bloc) {
272
+ return 'flutter-bloc';
273
+ }
274
+ if (deps.get || deps.getx) {
275
+ return 'flutter-getx';
276
+ }
277
+ if (deps.provider) {
278
+ return 'flutter-provider';
279
+ }
280
+ return 'flutter';
281
+ }
282
+
283
+ // Pure Dart server/CLI
284
+ if (deps.shelf || deps.shelf_router) {
285
+ return 'shelf';
286
+ }
287
+ if (deps.dart_frog) {
288
+ return 'dart-frog';
289
+ }
290
+ if (deps.serverpod) {
291
+ return 'serverpod';
292
+ }
293
+ if (deps.args || deps.cli_util) {
294
+ return 'dart-cli';
295
+ }
296
+
297
+ return null;
298
+ }
299
+
300
+ /**
301
+ * 发现 Melos 多包工作区中的子包
302
+ */
303
+ #discoverMelosPackages(projectRoot) {
304
+ const melosPath = join(projectRoot, 'melos.yaml');
305
+ if (!existsSync(melosPath)) {
306
+ return;
307
+ }
308
+
309
+ try {
310
+ const content = readFileSync(melosPath, 'utf8');
311
+ const melos = this.#parseSimpleYaml(content);
312
+
313
+ // Melos packages 字段(简化处理: 扫描 packages/ 目录)
314
+ const packagesDir = join(projectRoot, 'packages');
315
+ if (existsSync(packagesDir)) {
316
+ const entries = readdirSync(packagesDir, { withFileTypes: true });
317
+ for (const entry of entries) {
318
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
319
+ continue;
320
+ }
321
+ const pkgDir = join(packagesDir, entry.name);
322
+ if (existsSync(join(pkgDir, 'pubspec.yaml'))) {
323
+ const subPubspec = this.#parsePubspec(pkgDir);
324
+ const pkgName = subPubspec?.name || entry.name;
325
+ this.#targets.push({
326
+ name: `packages/${pkgName}`,
327
+ path: join(pkgDir, 'lib'),
328
+ type: 'library',
329
+ language: 'dart',
330
+ metadata: { isMelosPackage: true, packageName: pkgName },
331
+ });
332
+ this.#depGraph.nodes.push({ id: pkgName, label: pkgName, type: 'internal' });
333
+ }
334
+ }
335
+ }
336
+
337
+ // 也检查 apps/ 目录(部分 Melos 工作区的约定)
338
+ const appsDir = join(projectRoot, 'apps');
339
+ if (existsSync(appsDir)) {
340
+ const entries = readdirSync(appsDir, { withFileTypes: true });
341
+ for (const entry of entries) {
342
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
343
+ continue;
344
+ }
345
+ const appDir = join(appsDir, entry.name);
346
+ if (existsSync(join(appDir, 'pubspec.yaml'))) {
347
+ const subPubspec = this.#parsePubspec(appDir);
348
+ const appName = subPubspec?.name || entry.name;
349
+ this.#targets.push({
350
+ name: `apps/${appName}`,
351
+ path: join(appDir, 'lib'),
352
+ type: 'application',
353
+ language: 'dart',
354
+ metadata: { isMelosPackage: true, packageName: appName },
355
+ });
356
+ this.#depGraph.nodes.push({ id: appName, label: appName, type: 'internal' });
357
+ }
358
+ }
359
+ }
360
+ } catch {
361
+ /* skip */
362
+ }
363
+ }
364
+
365
+ /**
366
+ * 解析 pubspec.yaml 依赖到 depGraph
367
+ */
368
+ #parseDependencies(pubspec) {
369
+ if (!pubspec) {
370
+ return;
371
+ }
372
+
373
+ const nodeSet = new Set(
374
+ this.#depGraph.nodes.map((n) => (typeof n === 'string' ? n : n.id))
375
+ );
376
+ const rootNode = this.#packageName;
377
+
378
+ const addDep = (name, isDev) => {
379
+ if (!nodeSet.has(name)) {
380
+ this.#depGraph.nodes.push({
381
+ id: name,
382
+ label: name,
383
+ type: 'external',
384
+ isDev,
385
+ });
386
+ nodeSet.add(name);
387
+ }
388
+ this.#depGraph.edges.push({
389
+ from: rootNode,
390
+ to: name,
391
+ type: isDev ? 'dev-dependency' : 'dependency',
392
+ });
393
+ };
394
+
395
+ if (typeof pubspec.dependencies === 'object') {
396
+ for (const dep of Object.keys(pubspec.dependencies)) {
397
+ if (dep === 'flutter' || dep === 'flutter_localizations') {
398
+ continue; // SDK 依赖,不记为外部包
399
+ }
400
+ addDep(dep, false);
401
+ }
402
+ }
403
+
404
+ if (typeof pubspec.dev_dependencies === 'object') {
405
+ for (const dep of Object.keys(pubspec.dev_dependencies)) {
406
+ if (dep === 'flutter_test' || dep === 'flutter_lints' || dep === 'flutter_driver') {
407
+ continue;
408
+ }
409
+ addDep(dep, true);
410
+ }
411
+ }
412
+ }
413
+
414
+ /**
415
+ * 解析内部 Dart import 语句,构建包内模块依赖关系
416
+ */
417
+ #parseInternalImports(projectRoot) {
418
+ const libDir = join(projectRoot, 'lib');
419
+ if (!existsSync(libDir)) {
420
+ return;
421
+ }
422
+
423
+ const nodeSet = new Set(
424
+ this.#depGraph.nodes.map((n) => (typeof n === 'string' ? n : n.id))
425
+ );
426
+ const edgeSet = new Set();
427
+
428
+ // 收集 lib/ 下的子目录作为内部模块
429
+ try {
430
+ const entries = readdirSync(libDir, { withFileTypes: true });
431
+ for (const entry of entries) {
432
+ if (entry.isDirectory() && !entry.name.startsWith('.') && !entry.name.startsWith('_')) {
433
+ const moduleId = `lib/${entry.name}`;
434
+ if (!nodeSet.has(moduleId)) {
435
+ this.#depGraph.nodes.push({ id: moduleId, label: entry.name, type: 'internal' });
436
+ nodeSet.add(moduleId);
437
+ }
438
+ }
439
+ }
440
+ } catch {
441
+ /* skip */
442
+ }
443
+
444
+ // 扫描 import 语句
445
+ const scanDir = (dir) => {
446
+ try {
447
+ const entries = readdirSync(dir, { withFileTypes: true });
448
+ for (const entry of entries) {
449
+ if (entry.isDirectory()) {
450
+ if (!entry.name.startsWith('.') && !EXCLUDE_DIRS.has(entry.name)) {
451
+ scanDir(join(dir, entry.name));
452
+ }
453
+ } else if (entry.isFile() && entry.name.endsWith('.dart')) {
454
+ try {
455
+ const content = readFileSync(join(dir, entry.name), 'utf8');
456
+ const relDir = relative(libDir, dir);
457
+ const fromModule = relDir ? `lib/${relDir.split('/')[0]}` : this.#packageName;
458
+
459
+ // 匹配 import 'package:xxx/yyy.dart'
460
+ const imports = content.matchAll(
461
+ /import\s+['"]package:(\w+)\/([^'"]+)['"]/g
462
+ );
463
+ for (const m of imports) {
464
+ const pkg = m[1];
465
+ const filePath = m[2];
466
+ if (pkg === this.#packageName) {
467
+ // 内部 import
468
+ const targetModule = `lib/${filePath.split('/')[0]}`;
469
+ if (targetModule !== fromModule && nodeSet.has(targetModule)) {
470
+ const edgeKey = `${fromModule}->${targetModule}`;
471
+ if (!edgeSet.has(edgeKey)) {
472
+ edgeSet.add(edgeKey);
473
+ this.#depGraph.edges.push({
474
+ from: fromModule,
475
+ to: targetModule,
476
+ type: 'internal',
477
+ });
478
+ }
479
+ }
480
+ }
481
+ }
482
+ } catch {
483
+ /* skip */
484
+ }
485
+ }
486
+ }
487
+ } catch {
488
+ /* skip */
489
+ }
490
+ };
491
+
492
+ scanDir(libDir);
493
+ }
494
+
495
+ /**
496
+ * 递归收集 .dart 文件
497
+ */
498
+ #collectDartFiles(dir, rootDir, files, depth = 0) {
499
+ if (depth > 15) {
500
+ return;
501
+ }
502
+
503
+ try {
504
+ const entries = readdirSync(dir, { withFileTypes: true });
505
+ for (const entry of entries) {
506
+ if (entry.name.startsWith('.')) {
507
+ continue;
508
+ }
509
+
510
+ if (entry.isDirectory()) {
511
+ if (EXCLUDE_DIRS.has(entry.name)) {
512
+ continue;
513
+ }
514
+ this.#collectDartFiles(join(dir, entry.name), rootDir, files, depth + 1);
515
+ } else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
516
+ const fullPath = join(dir, entry.name);
517
+ try {
518
+ const content = readFileSync(fullPath, 'utf8');
519
+ files.push({
520
+ name: entry.name,
521
+ path: fullPath,
522
+ relativePath: relative(rootDir, fullPath),
523
+ content,
524
+ });
525
+ } catch {
526
+ /* unreadable */
527
+ }
528
+ }
529
+ }
530
+ } catch {
531
+ /* permission error */
532
+ }
533
+ }
534
+ }