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
@@ -0,0 +1,437 @@
1
+ /**
2
+ * SPM API 路由 — 向后兼容层
3
+ *
4
+ * 所有端点统一委派到 ModuleService(语言无关模块扫描服务)。
5
+ * SPM Discoverer 作为 ModuleService 的一个 discoverer 自动匹配 Swift/SPM 项目。
6
+ * 新代码应直接使用 /api/v1/modules/* 端点。
7
+ */
8
+
9
+ import express from 'express';
10
+ import Logger from '../../../infrastructure/logging/Logger.js';
11
+ import { getServiceContainer } from '../../../injection/ServiceContainer.js';
12
+ import { ValidationError } from '../../../shared/errors/index.js';
13
+ import { asyncHandler } from '../../../http/middleware/errorHandler.js';
14
+ import { createStreamSession, getStreamSession } from '../../../http/utils/sse-sessions.js';
15
+
16
+ const router = express.Router();
17
+ const logger = Logger.getInstance();
18
+
19
+ /** 获取 moduleService 并确保已加载 */
20
+ async function getModuleService() {
21
+ const container = getServiceContainer();
22
+ const moduleService = container.get('moduleService');
23
+ await moduleService.load();
24
+ return moduleService;
25
+ }
26
+
27
+ /**
28
+ * GET /api/v1/spm/targets
29
+ */
30
+ router.get(
31
+ '/targets',
32
+ asyncHandler(async (req, res) => {
33
+ const moduleService = await getModuleService();
34
+ const targets = await moduleService.listTargets();
35
+
36
+ res.json({
37
+ success: true,
38
+ data: { targets, total: targets.length },
39
+ });
40
+ })
41
+ );
42
+
43
+ /**
44
+ * GET /api/v1/spm/dep-graph
45
+ */
46
+ router.get(
47
+ '/dep-graph',
48
+ asyncHandler(async (req, res) => {
49
+ const moduleService = await getModuleService();
50
+ const level = req.query.level || 'package';
51
+ const graph = await moduleService.getDependencyGraph({ level });
52
+
53
+ if (!graph || (!graph.nodes && !graph.packages)) {
54
+ return res.json({
55
+ success: true,
56
+ data: { nodes: [], edges: [], projectRoot: null },
57
+ });
58
+ }
59
+
60
+ // 标准化为 { nodes, edges } 格式
61
+ let nodes = [];
62
+ let edges = [];
63
+
64
+ if (graph.nodes && graph.edges) {
65
+ // 已经是标准格式
66
+ nodes = graph.nodes;
67
+ edges = graph.edges;
68
+ } else if (graph.packages) {
69
+ // 从 packages 构建图
70
+ if (level === 'target') {
71
+ for (const [pkgName, pkgInfo] of Object.entries(graph.packages)) {
72
+ const targetsInfo = pkgInfo?.targetsInfo || {};
73
+ for (const [targetName, info] of Object.entries(targetsInfo)) {
74
+ const id = `${pkgName}::${targetName}`;
75
+ nodes.push({
76
+ id,
77
+ label: targetName,
78
+ type: 'target',
79
+ packageName: pkgName,
80
+ });
81
+ for (const d of info?.dependencies || []) {
82
+ if (!d?.name) {
83
+ continue;
84
+ }
85
+ const depPkg = d?.package || pkgName;
86
+ edges.push({ from: id, to: `${depPkg}::${d.name}`, source: 'base' });
87
+ }
88
+ }
89
+ }
90
+ } else {
91
+ nodes = Object.keys(graph.packages).map((id) => ({
92
+ id,
93
+ label: id,
94
+ type: 'package',
95
+ packageDir: graph.packages[id]?.packageDir,
96
+ targets: graph.packages[id]?.targets,
97
+ }));
98
+ for (const [from, tos] of Object.entries(graph.edges || {})) {
99
+ for (const to of tos || []) {
100
+ edges.push({ from, to, source: 'base' });
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ res.json({
107
+ success: true,
108
+ data: {
109
+ nodes,
110
+ edges,
111
+ projectRoot: graph.projectRoot || null,
112
+ generatedAt: graph.generatedAt || null,
113
+ },
114
+ });
115
+ })
116
+ );
117
+
118
+ /**
119
+ * POST /api/v1/spm/target-files
120
+ * 获取 Target 的文件列表
121
+ */
122
+ router.post(
123
+ '/target-files',
124
+ asyncHandler(async (req, res) => {
125
+ const { target, targetName } = req.body;
126
+
127
+ if (!target && !targetName) {
128
+ throw new ValidationError('target object or targetName is required');
129
+ }
130
+
131
+ const moduleService = await getModuleService();
132
+
133
+ let resolvedTarget = target;
134
+ if (!resolvedTarget && targetName) {
135
+ const targets = await moduleService.listTargets();
136
+ resolvedTarget = targets.find((t) => t.name === targetName);
137
+ if (!resolvedTarget) {
138
+ return res.status(404).json({
139
+ success: false,
140
+ error: { code: 'NOT_FOUND', message: `Target not found: ${targetName}` },
141
+ });
142
+ }
143
+ }
144
+
145
+ const files = await moduleService.getTargetFiles(resolvedTarget);
146
+
147
+ res.json({
148
+ success: true,
149
+ data: {
150
+ target: resolvedTarget.name || targetName,
151
+ files,
152
+ total: files.length,
153
+ },
154
+ });
155
+ })
156
+ );
157
+
158
+ /**
159
+ * POST /api/v1/spm/scan
160
+ * AI 扫描 Target,发现候选项
161
+ */
162
+ router.post(
163
+ '/scan',
164
+ asyncHandler(async (req, res) => {
165
+ const { target, targetName, options = {} } = req.body;
166
+
167
+ if (!target && !targetName) {
168
+ throw new ValidationError('target object or targetName is required');
169
+ }
170
+
171
+ const moduleService = await getModuleService();
172
+
173
+ let resolvedTarget = target;
174
+ if (!resolvedTarget && targetName) {
175
+ const targets = await moduleService.listTargets();
176
+ resolvedTarget = targets.find((t) => t.name === targetName);
177
+ if (!resolvedTarget) {
178
+ return res.status(404).json({
179
+ success: false,
180
+ error: { code: 'NOT_FOUND', message: `Target not found: ${targetName}` },
181
+ });
182
+ }
183
+ }
184
+
185
+ logger.info('Module scan started via /spm/', { target: resolvedTarget.name });
186
+ const result = await moduleService.scanTarget(resolvedTarget, options);
187
+
188
+ res.json({
189
+ success: true,
190
+ data: result,
191
+ });
192
+ })
193
+ );
194
+
195
+ // ── 流式 Target 扫描(SSE Session + EventSource 架构) ─────────
196
+
197
+ /**
198
+ * POST /api/v1/spm/scan/stream
199
+ * 创建流式扫描会话,后台异步执行 AI 扫描
200
+ *
201
+ * 协议事件(通过 SSE session 缓冲 + EventSource 交付):
202
+ * scan:started — 扫描启动
203
+ * scan:files-loaded — 文件列表就绪,含 files[] + count
204
+ * scan:reading — 读取文件内容中
205
+ * scan:ai-extracting — AI 提取开始(耗时阶段)
206
+ * scan:enriching — 后处理阶段
207
+ * scan:completed — 最终结果 {recipes, scannedFiles, recipeCount, fileCount}
208
+ * scan:error — 发生错误
209
+ * stream:done — 会话结束标记
210
+ */
211
+ router.post(
212
+ '/scan/stream',
213
+ asyncHandler(async (req, res) => {
214
+ const { target, targetName, options = {} } = req.body;
215
+
216
+ if (!target && !targetName) {
217
+ throw new ValidationError('target object or targetName is required');
218
+ }
219
+
220
+ const moduleService = await getModuleService();
221
+
222
+ let resolvedTarget = target;
223
+ if (!resolvedTarget && targetName) {
224
+ const targets = await moduleService.listTargets();
225
+ resolvedTarget = targets.find((t) => t.name === targetName);
226
+ if (!resolvedTarget) {
227
+ return res.status(404).json({
228
+ success: false,
229
+ error: { code: 'NOT_FOUND', message: `Target not found: ${targetName}` },
230
+ });
231
+ }
232
+ }
233
+
234
+ // 创建 SSE session
235
+ const session = createStreamSession('scan');
236
+ const tName = resolvedTarget.name || targetName;
237
+
238
+ // 立即返回 sessionId
239
+ res.json({ sessionId: session.sessionId });
240
+
241
+ // 异步执行扫描,通过 session 推送进度事件
242
+ setImmediate(async () => {
243
+ try {
244
+ logger.info('Module stream scan started via /spm/', { target: tName, sessionId: session.sessionId });
245
+ const result = await moduleService.scanTarget(resolvedTarget, {
246
+ ...options,
247
+ onProgress(event) {
248
+ session.send(event);
249
+ },
250
+ });
251
+
252
+ // 发送最终结果
253
+ session.send({
254
+ type: 'scan:result',
255
+ recipes: result.recipes || [],
256
+ scannedFiles: result.scannedFiles || [],
257
+ message: result.message || '',
258
+ recipeCount: (result.recipes || []).length,
259
+ fileCount: (result.scannedFiles || []).length,
260
+ });
261
+ session.end();
262
+ } catch (err) {
263
+ logger.error('Module stream scan failed via /spm/', { target: tName, error: err.message });
264
+ session.error(err.message, 'SCAN_ERROR');
265
+ }
266
+ });
267
+ })
268
+ );
269
+
270
+ /**
271
+ * GET /api/v1/spm/scan/events/:sessionId
272
+ * EventSource SSE 端点 — 消费扫描进度事件
273
+ *
274
+ * 复用 chat/events 相同的 SSE 交付模式:回放缓冲 → 订阅实时 → 心跳保活
275
+ */
276
+ router.get('/scan/events/:sessionId', (req, res) => {
277
+ const session = getStreamSession(req.params.sessionId);
278
+ if (!session) {
279
+ return res.status(404).json({ success: false, error: 'Session not found or expired' });
280
+ }
281
+
282
+ // ─── SSE Headers ───
283
+ res.setHeader('Content-Type', 'text/event-stream');
284
+ res.setHeader('Cache-Control', 'no-cache');
285
+ res.setHeader('Connection', 'keep-alive');
286
+ res.setHeader('X-Accel-Buffering', 'no');
287
+ res.flushHeaders();
288
+
289
+ if (res.socket) {
290
+ res.socket.setNoDelay(true);
291
+ res.socket.setTimeout(0);
292
+ }
293
+
294
+ function writeEvent(event) {
295
+ if (res.writableEnded) {
296
+ return;
297
+ }
298
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
299
+ }
300
+
301
+ // 1) 回放缓冲区
302
+ let isDone = false;
303
+ for (const event of session.buffer) {
304
+ writeEvent(event);
305
+ if (event.type === 'stream:done' || event.type === 'stream:error') {
306
+ isDone = true;
307
+ }
308
+ }
309
+
310
+ if (isDone || session.completed) {
311
+ res.end();
312
+ return;
313
+ }
314
+
315
+ // 2) 订阅实时事件
316
+ const unsubscribe = session.on((event) => {
317
+ writeEvent(event);
318
+ if (event.type === 'stream:done' || event.type === 'stream:error') {
319
+ unsubscribe();
320
+ clearInterval(heartbeat);
321
+ res.end();
322
+ }
323
+ });
324
+
325
+ // 心跳保活 (每 15 秒)
326
+ const heartbeat = setInterval(() => {
327
+ if (res.writableEnded) {
328
+ clearInterval(heartbeat);
329
+ return;
330
+ }
331
+ res.write(`: ping ${Date.now()}\n\n`);
332
+ }, 15_000);
333
+
334
+ // 客户端断开连接时清理
335
+ res.on('close', () => {
336
+ unsubscribe();
337
+ clearInterval(heartbeat);
338
+ });
339
+ });
340
+
341
+ /**
342
+ * POST /api/v1/spm/scan-project
343
+ * 全项目扫描:AI 提取候选 + Guard 审计
344
+ */
345
+ router.post(
346
+ '/scan-project',
347
+ asyncHandler(async (req, res) => {
348
+ const { options = {} } = req.body;
349
+
350
+ const moduleService = await getModuleService();
351
+
352
+ logger.info('Full project scan started via /spm/');
353
+ const result = await moduleService.scanProject(options);
354
+
355
+ res.json({
356
+ success: true,
357
+ data: result,
358
+ });
359
+ })
360
+ );
361
+
362
+ /**
363
+ * POST /api/v1/spm/bootstrap
364
+ * 冷启动:快速骨架 + 异步逐维度填充(v5)
365
+ *
366
+ * 执行策略:
367
+ * ① 同步阶段: Phase 1-4(文件收集 + AST + SPM + Guard + 骨架响应)→ 立即返回
368
+ * ② 异步阶段: Phase 5/5.5(逐维度提取 + Candidate/Skill 创建)→ 后台逐一执行
369
+ * ③ 进度推送: 通过 Socket.io 实时推送每个维度的完成状态
370
+ *
371
+ * 前端立即获得骨架 + 任务清单,每个维度完成后通过 Socket.io 推送更新。
372
+ */
373
+ router.post(
374
+ '/bootstrap',
375
+ asyncHandler(async (req, res) => {
376
+ const { maxFiles, skipGuard, contentMaxLines } = req.body || {};
377
+
378
+ const container = getServiceContainer();
379
+ const chatAgent = container.get('chatAgent');
380
+
381
+ logger.info('Bootstrap cold start initiated (v5: async fill mode)');
382
+
383
+ // ── 同步阶段: 快速执行 Phase 1-4 → 返回骨架 ──
384
+ const bootstrapResult = await chatAgent.executeTool('bootstrap_knowledge', {
385
+ maxFiles: maxFiles || 500,
386
+ skipGuard: skipGuard || false,
387
+ contentMaxLines: contentMaxLines || 120,
388
+ loadSkills: true,
389
+ });
390
+
391
+ // 立即返回骨架结果给前端
392
+ res.json({
393
+ success: true,
394
+ data: {
395
+ ...bootstrapResult,
396
+ asyncFill: true, // 告知前端:内容正在异步填充中
397
+ },
398
+ });
399
+
400
+ // 注意:Phase 5/5.5 异步填充已在 bootstrapKnowledge() 内部通过 setImmediate 启动
401
+ // 进度通过 BootstrapTaskManager → Socket.io 推送到前端
402
+ })
403
+ );
404
+
405
+ /**
406
+ * GET /api/v1/spm/bootstrap/status
407
+ * 查询当前 bootstrap 异步填充进度
408
+ *
409
+ * 返回当前 session 的任务状态列表,供前端轮询(Socket.io 不可用时的 fallback)
410
+ */
411
+ router.get(
412
+ '/bootstrap/status',
413
+ asyncHandler(async (req, res) => {
414
+ const container = getServiceContainer();
415
+
416
+ // 从容器获取 BootstrapTaskManager(正式 DI 注册)
417
+ let taskManager = null;
418
+ try {
419
+ taskManager = container.get('bootstrapTaskManager');
420
+ } catch {
421
+ /* not registered */
422
+ }
423
+ if (!taskManager) {
424
+ return res.json({
425
+ success: true,
426
+ data: { status: 'idle', message: 'No bootstrap task manager initialized' },
427
+ });
428
+ }
429
+
430
+ res.json({
431
+ success: true,
432
+ data: taskManager.getSessionStatus(),
433
+ });
434
+ })
435
+ );
436
+
437
+ export default router;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * PlaceholderConverter — Xcode <#…#> ↔ VSCode ${N:…} 双向占位符转换
3
+ *
4
+ * 转换规则:
5
+ * Xcode → VSCode:
6
+ * <#name#> → ${1:name} (序号自动递增)
7
+ * <#T##Type#> → ${1:Type} (取最后一段)
8
+ * <#T##Label##Type#> → ${1:Label} (取 Label 段)
9
+ *
10
+ * VSCode → Xcode:
11
+ * ${1:name} → <#name#>
12
+ * ${1} → <#value#>
13
+ * $1 → <#value#>
14
+ * $0 → (移除,Xcode 无终止光标)
15
+ */
16
+ export class PlaceholderConverter {
17
+ /**
18
+ * Xcode → VSCode 占位符转换
19
+ * @param {string} code
20
+ * @returns {string}
21
+ */
22
+ static xcodeToVSCode(code) {
23
+ if (!code) return '';
24
+ let index = 0;
25
+ return code.replace(/<#(.*?)#>/g, (_match, inner) => {
26
+ index++;
27
+ // <#T##Label##Type#> → Label (parts[1])
28
+ // <#T##Type#> → Type (parts[1])
29
+ // <#name#> → name (inner)
30
+ const parts = inner.split('##');
31
+ const label = parts.length >= 2 ? parts[1] : inner;
32
+ return `\${${index}:${label}}`;
33
+ });
34
+ }
35
+
36
+ /**
37
+ * VSCode → Xcode 占位符转换
38
+ * @param {string} code
39
+ * @returns {string}
40
+ */
41
+ static vscodeToXcode(code) {
42
+ if (!code) return '';
43
+ return (
44
+ code
45
+ // ${1:name} → <#name#>
46
+ .replace(/\$\{(\d+):([^}]*)}/g, (_m, _n, label) => `<#${label}#>`)
47
+ // ${1} → <#value#>
48
+ .replace(/\$\{(\d+)}/g, '<#value#>')
49
+ // $0 → remove (Xcode has no final cursor)
50
+ .replace(/\$0/g, '')
51
+ // $1..$9 → <#value#>
52
+ .replace(/\$([1-9]\d*)/g, '<#value#>')
53
+ );
54
+ }
55
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * XcodeCodec — Xcode .codesnippet (plist XML) 生成器
3
+ *
4
+ * 从原 SnippetFactory 提取的 Xcode 专用逻辑:
5
+ * - XML plist 模板
6
+ * - Xcode 语言 ID 映射
7
+ * - XML 转义
8
+ */
9
+
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+ import { SnippetCodec } from '../../../service/snippet/codecs/SnippetCodec.js';
13
+
14
+ const XCODE_LANGUAGE_MAP = {
15
+ swift: 'Xcode.SourceCodeLanguage.Swift',
16
+ 'objective-c': 'Xcode.SourceCodeLanguage.Objective-C',
17
+ objc: 'Xcode.SourceCodeLanguage.Objective-C',
18
+ c: 'Xcode.SourceCodeLanguage.C',
19
+ 'c++': 'Xcode.SourceCodeLanguage.C-Plus-Plus',
20
+ javascript: 'Xcode.SourceCodeLanguage.JavaScript',
21
+ };
22
+
23
+ const PLIST_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
24
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
25
+ <plist version="1.0">
26
+ <dict>
27
+ \t<key>IDECodeSnippetCompletionPrefix</key>
28
+ \t<string>{completion}</string>
29
+ \t<key>IDECodeSnippetCompletionScopes</key>
30
+ \t<array>
31
+ \t\t<string>All</string>
32
+ \t</array>
33
+ \t<key>IDECodeSnippetContents</key>
34
+ \t<string>{content}</string>
35
+ \t<key>IDECodeSnippetIdentifier</key>
36
+ \t<string>{identifier}</string>
37
+ \t<key>IDECodeSnippetLanguage</key>
38
+ \t<string>{language}</string>
39
+ \t<key>IDECodeSnippetSummary</key>
40
+ \t<string>{summary}</string>
41
+ \t<key>IDECodeSnippetTitle</key>
42
+ \t<string>{title}</string>
43
+ \t<key>IDECodeSnippetUserSnippet</key>
44
+ \t<true/>
45
+ \t<key>IDECodeSnippetVersion</key>
46
+ \t<integer>2</integer>
47
+ </dict>
48
+ </plist>`;
49
+
50
+ export class XcodeCodec extends SnippetCodec {
51
+ get id() {
52
+ return 'xcode';
53
+ }
54
+
55
+ get fileExtension() {
56
+ return '.codesnippet';
57
+ }
58
+
59
+ /**
60
+ * SnippetSpec → plist XML 字符串
61
+ */
62
+ generate(spec) {
63
+ if (!spec?.identifier || !spec?.code) {
64
+ throw new Error('Snippet spec must have identifier and code');
65
+ }
66
+
67
+ const content = Array.isArray(spec.code) ? spec.code.join('\n') : spec.code;
68
+ const languageKey = this.mapLanguage(spec.language);
69
+
70
+ let xml = PLIST_TEMPLATE;
71
+ xml = xml.replace('{identifier}', escapeXml(spec.identifier));
72
+ xml = xml.replace('{title}', escapeXml(spec.title || spec.identifier));
73
+ xml = xml.replace('{completion}', escapeXml(spec.completion || spec.identifier));
74
+ xml = xml.replace('{summary}', escapeXml(spec.summary || ''));
75
+ xml = xml.replace('{content}', escapeXml(content));
76
+ xml = xml.replace('{language}', languageKey);
77
+
78
+ return xml;
79
+ }
80
+
81
+ /**
82
+ * Xcode: 每个 snippet 一个文件 → 返回 Array<{ filename, content }>
83
+ */
84
+ generateBundle(specs) {
85
+ return specs.map((spec) => ({
86
+ filename: `${spec.identifier}${this.fileExtension}`,
87
+ content: this.generate(spec),
88
+ }));
89
+ }
90
+
91
+ /**
92
+ * Xcode snippets 全局目录 (macOS only)
93
+ */
94
+ getInstallDir(_projectRoot) {
95
+ return join(homedir(), 'Library/Developer/Xcode/UserData/CodeSnippets');
96
+ }
97
+
98
+ mapLanguage(lang) {
99
+ return XCODE_LANGUAGE_MAP[lang?.toLowerCase()] || XCODE_LANGUAGE_MAP.swift;
100
+ }
101
+ }
102
+
103
+ /** XML 特殊字符转义 */
104
+ function escapeXml(str) {
105
+ if (!str) return '';
106
+ return String(str)
107
+ .replace(/&/g, '&amp;')
108
+ .replace(/</g, '&lt;')
109
+ .replace(/>/g, '&gt;')
110
+ .replace(/"/g, '&quot;')
111
+ .replace(/'/g, '&apos;');
112
+ }