autosnippet 3.0.1 → 3.0.3

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 +231 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +17 -4
  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 +441 -282
  14. package/lib/cli/UpgradeService.js +68 -107
  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 +106 -170
  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 +62 -72
  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
@@ -41,21 +41,25 @@
41
41
  * 子仓库 git 权限只是"一种能力(capability)",最终裁决权在 Constitution YAML。
42
42
  */
43
43
 
44
+ import { execSync } from 'node:child_process';
44
45
  import {
45
- existsSync, mkdirSync, writeFileSync,
46
- readFileSync, copyFileSync, readdirSync,
47
- } from 'fs';
48
- import { join, resolve, dirname } from 'path';
49
- import { fileURLToPath } from 'url';
50
- import { execSync } from 'child_process';
46
+ copyFileSync,
47
+ existsSync,
48
+ mkdirSync,
49
+ readdirSync,
50
+ readFileSync,
51
+ writeFileSync,
52
+ } from 'node:fs';
53
+ import { dirname, join, resolve } from 'node:path';
54
+ import { fileURLToPath } from 'node:url';
51
55
 
52
56
  const __filename = fileURLToPath(import.meta.url);
53
- const __dirname = dirname(__filename);
57
+ const __dirname = dirname(__filename);
54
58
 
55
59
  /** AutoSnippet 源码仓库根目录(定位 templates/ 等资源) */
56
60
  const REPO_ROOT = resolve(__dirname, '..', '..');
57
61
  /** V2 子项目根目录(定位 bin/mcp-server.js 等) */
58
- const V2_ROOT = resolve(__dirname, '..', '..');
62
+ const V2_ROOT = resolve(__dirname, '..', '..');
59
63
 
60
64
  // ─────────────────────────────────────────────────────
61
65
 
@@ -66,92 +70,89 @@ export class SetupService {
66
70
  constructor(options) {
67
71
  this.projectRoot = resolve(options.projectRoot);
68
72
  this.projectName = this.projectRoot.split('/').pop();
69
- this.force = options.force || false;
70
- this.seed = options.seed || false;
73
+ this.force = options.force || false;
74
+ this.seed = options.seed || false;
71
75
 
72
76
  // 运行时目录(gitignored)
73
- this.runtimeDir = join(this.projectRoot, '.autosnippet');
74
- this.dbPath = join(this.runtimeDir, 'autosnippet.db');
77
+ this.runtimeDir = join(this.projectRoot, '.autosnippet');
78
+ this.dbPath = join(this.runtimeDir, 'autosnippet.db');
75
79
 
76
80
  // 核心数据目录(子仓库)
77
- this.coreDir = join(this.projectRoot, 'AutoSnippet');
78
- this.recipesDir = join(this.coreDir, 'recipes');
79
- this.candidatesDir = join(this.coreDir, 'candidates');
80
- this.skillsDir = join(this.coreDir, 'skills');
81
+ this.coreDir = join(this.projectRoot, 'AutoSnippet');
82
+ this.recipesDir = join(this.coreDir, 'recipes');
83
+ this.candidatesDir = join(this.coreDir, 'candidates');
84
+ this.skillsDir = join(this.coreDir, 'skills');
81
85
  }
82
86
 
83
87
  /* ═══ 公共入口 ═══════════════════════════════════════ */
84
88
 
85
89
  getSteps() {
86
90
  return [
87
- { label: '创建运行时目录与配置', fn: () => this.stepRuntime() },
88
- { label: '初始化核心数据子仓库', fn: () => this.stepCoreRepo() },
89
- { label: '配置 IDE 集成', fn: () => this.stepIDE() },
90
- { label: '初始化数据库', fn: () => this.stepDatabase() },
91
- { label: '平台相关初始化', fn: () => this.stepPlatform() },
91
+ { label: '创建运行时目录与配置', fn: () => this.stepRuntime() },
92
+ { label: '初始化核心数据子仓库', fn: () => this.stepCoreRepo() },
93
+ { label: '配置 IDE 集成', fn: () => this.stepIDE() },
94
+ { label: '初始化数据库', fn: () => this.stepDatabase() },
95
+ { label: '平台相关初始化', fn: () => this.stepPlatform() },
92
96
  ];
93
97
  }
94
98
 
95
99
  async run() {
96
100
  const steps = this.getSteps();
97
101
  const results = [];
102
+ const total = steps.length;
98
103
 
99
- for (let i = 0; i < steps.length; i++) {
104
+ console.log('');
105
+ console.log(` ⚙ AutoSnippet Setup — ${this.projectName}`);
106
+ console.log(` ${'─'.repeat(44)}`);
107
+
108
+ for (let i = 0; i < total; i++) {
100
109
  const { label, fn } = steps[i];
101
- console.log(`\n[${i + 1}/${steps.length}] ${label}`);
110
+ const tag = `[${i + 1}/${total}]`;
111
+ process.stdout.write(` ${tag} ${label}...`);
102
112
  try {
103
113
  const r = await fn();
114
+ const detail = this._formatStepDetail(r);
115
+ console.log(` ✅${detail}`);
104
116
  results.push({ step: i + 1, label, ok: true, ...(r || {}) });
105
117
  } catch (err) {
106
- console.error(` ${err.message}`);
118
+ console.log(` ❌`);
119
+ console.error(` ${err.message}`);
107
120
  results.push({ step: i + 1, label, ok: false, error: err.message });
108
121
  }
109
122
  }
110
123
 
111
- console.log('');
124
+ this._results = results;
112
125
  return results;
113
126
  }
114
127
 
115
- printSummary() {
116
- console.log('════════════════════════════════════════');
117
- console.log('✅ AutoSnippet V2 工作空间初始化完成');
118
- console.log('════════════════════════════════════════\n');
119
-
120
- console.log('📂 目录结构');
121
- console.log(' AutoSnippet/ 核心数据 — Git 子仓库(Source of Truth)');
122
- console.log(' ├─ constitution.yaml 权限宪法(角色 + 能力 + 治理规则)');
123
- console.log(' ├─ boxspec.json 项目规格');
124
- console.log(' ├─ recipes/*.md 统一知识实体 ← 受 git push 保护');
125
- console.log(' ├─ candidates/*.md 候选代码片段 ← 受 git push 保护');
126
- console.log(' ├─ skills/ Project Skills(冷启动自动生成 + 手动创建)');
127
- console.log(' └─ *.json 运行数据(统计/反馈/规则学习/排除策略)');
128
- console.log(' .autosnippet/ 运行时缓存(gitignored)');
129
- console.log(' ├─ config.json 项目配置');
130
- console.log(' └─ autosnippet.db SQLite 索引缓存\n');
131
-
132
- console.log('🔐 权限模型(三层架构)');
133
- console.log(' ① 能力层 git push --dry-run → 子仓库物理写权限');
134
- console.log(' ② 角色层 constitution.yaml → 角色权限矩阵');
135
- console.log(' ③ 治理层 constitution.yaml → 优先级规则引擎');
136
- console.log(' 核心数据(统一 Recipe 实体)必须通过 git 修改,DB 只是缓存\n');
137
-
138
- console.log('📊 数据流');
139
- console.log(' 写入: 编辑 AutoSnippet/ 文件 → git push → asd sync → DB 缓存更新');
140
- console.log(' 读取: 查询 .autosnippet/autosnippet.db(快速索引)\n');
141
-
142
- console.log('🎯 后续步骤');
143
- console.log(' 1. 团队协作 — 为子仓库添加远程仓库:');
144
- console.log(' cd AutoSnippet && git remote add origin <url>');
145
- console.log(' 2. 或使用 git submodule(推荐):');
146
- console.log(' rm -rf AutoSnippet');
147
- console.log(' git submodule add <url> AutoSnippet');
148
- console.log(' 3. 同步子仓库数据到 DB 缓存: asd sync');
149
- console.log(' 4. 重启编辑器(VSCode / Cursor / Xcode)');
150
- console.log(' 5. 测试 MCP: @autosnippet search <关键词>');
151
- console.log(' 6. 启动面板: asd ui -d .');
152
- if (process.platform === 'darwin') {
153
- console.log(' 6. 在 Xcode 中输入 "ass" 尝试 Snippet');
128
+ /** @private 格式化步骤结果的简要信息 */
129
+ _formatStepDetail(r) {
130
+ if (!r) return '';
131
+ const parts = [];
132
+ if (r.configured) {
133
+ parts.push(r.configured.join(', '));
134
+ }
135
+ if (r.entries !== undefined) {
136
+ parts.push(`${r.entries} entries`);
154
137
  }
138
+ if (r.migrated !== undefined) {
139
+ parts.push(`migrated ${r.migrated}`);
140
+ }
141
+ return parts.length > 0 ? ` (${parts.join('; ')})` : '';
142
+ }
143
+
144
+ printSummary() {
145
+ const results = this._results || [];
146
+ const ok = results.filter((r) => r.ok).length;
147
+ const fail = results.filter((r) => !r.ok).length;
148
+
149
+ console.log(` ${'─'.repeat(44)}`);
150
+ console.log(` ✨ Setup 完成: ${ok} 成功${fail > 0 ? `, ${fail} 失败` : ''}`);
151
+ console.log('');
152
+ console.log(' 后续操作:');
153
+ console.log(' asd coldstart 扫描项目、AI 生成知识库');
154
+ console.log(' asd ui 启动 Dashboard + API Server');
155
+ console.log(' asd watch 启动 Xcode 文件监听');
155
156
  console.log('');
156
157
  }
157
158
 
@@ -163,7 +164,6 @@ export class SetupService {
163
164
  // config.json
164
165
  const configPath = join(this.runtimeDir, 'config.json');
165
166
  if (existsSync(configPath) && !this.force) {
166
- console.log(' ℹ️ config.json 已存在,跳过');
167
167
  } else {
168
168
  const config = {
169
169
  version: 2,
@@ -182,7 +182,6 @@ export class SetupService {
182
182
  },
183
183
  };
184
184
  writeFileSync(configPath, JSON.stringify(config, null, 2));
185
- console.log(' ✅ .autosnippet/config.json');
186
185
  }
187
186
 
188
187
  // 确保 .autosnippet/ 在主仓库 .gitignore 中
@@ -208,9 +207,7 @@ export class SetupService {
208
207
  // 初始化 git(如果还不是 git 仓库)
209
208
  if (!alreadyRepo) {
210
209
  this._git(['init'], this.coreDir);
211
- console.log(' ✅ git init AutoSnippet/');
212
210
  } else {
213
- console.log(' ℹ️ AutoSnippet/ 已是 git 仓库');
214
211
  }
215
212
 
216
213
  // constitution.yaml — 权限宪法
@@ -240,7 +237,6 @@ export class SetupService {
240
237
  if (!alreadyRepo) {
241
238
  this._git(['add', '.'], this.coreDir);
242
239
  this._git(['commit', '-m', 'Init AutoSnippet knowledge base'], this.coreDir);
243
- console.log(' ✅ 初始提交完成(本地模式,无 remote → 允许写入)');
244
240
  }
245
241
 
246
242
  return { coreInit: true, alreadyRepo };
@@ -250,7 +246,6 @@ export class SetupService {
250
246
  _writeConstitution() {
251
247
  const dest = join(this.coreDir, 'constitution.yaml');
252
248
  if (existsSync(dest) && !this.force) {
253
- console.log(' ℹ️ constitution.yaml 已存在');
254
249
  return;
255
250
  }
256
251
 
@@ -259,173 +254,192 @@ export class SetupService {
259
254
  copyFileSync(tmpl, dest);
260
255
  } else {
261
256
  // 内联生成最小宪法(模板文件不可用时的 fallback)
262
- writeFileSync(dest, [
263
- '# AutoSnippet Constitution',
264
- 'version: "2.0"',
265
- '',
266
- 'capabilities:',
267
- ' git_write:',
268
- ' description: "子仓库 git push 权限"',
269
- ' probe: "git push --dry-run"',
270
- ' no_subrepo: "allow"',
271
- ' no_remote: "allow"',
272
- ' cache_ttl: 86400',
273
- '',
274
- 'rules:',
275
- ' - id: destructive_confirm',
276
- ' check: "删除操作必须有 confirmed: true"',
277
- ' - id: content_required',
278
- ' check: "创建 candidate/recipe 必须提供 code 或 content"',
279
- ' - id: ai_no_direct_recipe',
280
- ' check: "AI actor 不能直接创建或批准 Recipe"',
281
- ' - id: batch_authorized',
282
- ' check: "批量操作必须有 authorized: true"',
283
- '',
284
- 'roles:',
285
- ' - id: "developer"',
286
- ' name: "Developer"',
287
- ' permissions: ["*"]',
288
- ' requires_capability: ["git_write"]',
289
- ' - id: "external_agent"',
290
- ' name: "External Agent"',
291
- ' permissions: ["read:recipes", "read:guard_rules", "create:candidates", "submit:knowledge"]',
292
- ' - id: "chat_agent"',
293
- ' name: "ChatAgent"',
294
- ' permissions: ["read:recipes", "read:candidates", "create:candidates", "read:guard_rules"]',
295
- '',
296
- ].join('\n'));
257
+ writeFileSync(
258
+ dest,
259
+ [
260
+ '# AutoSnippet Constitution',
261
+ 'version: "2.0"',
262
+ '',
263
+ 'capabilities:',
264
+ ' git_write:',
265
+ ' description: "子仓库 git push 权限"',
266
+ ' probe: "git push --dry-run"',
267
+ ' no_subrepo: "allow"',
268
+ ' no_remote: "allow"',
269
+ ' cache_ttl: 86400',
270
+ '',
271
+ 'rules:',
272
+ ' - id: destructive_confirm',
273
+ ' check: "删除操作必须有 confirmed: true"',
274
+ ' - id: content_required',
275
+ ' check: "创建 candidate/recipe 必须提供 code 或 content"',
276
+ ' - id: ai_no_direct_recipe',
277
+ ' check: "AI actor 不能直接创建或批准 Recipe"',
278
+ ' - id: batch_authorized',
279
+ ' check: "批量操作必须有 authorized: true"',
280
+ '',
281
+ 'roles:',
282
+ ' - id: "developer"',
283
+ ' name: "Developer"',
284
+ ' permissions: ["*"]',
285
+ ' requires_capability: ["git_write"]',
286
+ ' - id: "external_agent"',
287
+ ' name: "External Agent"',
288
+ ' permissions: ["read:recipes", "read:guard_rules", "create:candidates", "submit:knowledge"]',
289
+ ' - id: "chat_agent"',
290
+ ' name: "ChatAgent"',
291
+ ' permissions: ["read:recipes", "read:candidates", "create:candidates", "read:guard_rules"]',
292
+ '',
293
+ ].join('\n')
294
+ );
297
295
  }
298
- console.log(' ✅ AutoSnippet/constitution.yaml');
299
296
  }
300
297
 
301
298
  /** @private 写入 boxspec.json */
302
299
  _writeBoxspec() {
303
300
  const dest = join(this.coreDir, 'boxspec.json');
304
301
  if (existsSync(dest) && !this.force) {
305
- console.log(' ℹ️ boxspec.json 已存在');
306
302
  return;
307
303
  }
308
304
 
309
- writeFileSync(dest, JSON.stringify({
310
- name: this.projectName,
311
- schemaVersion: 2,
312
- kind: 'root',
313
- root: true,
314
- knowledgeBase: { dir: 'AutoSnippet' },
315
- module: { rootDir: 'AutoSnippet' },
316
- }, null, 2));
317
- console.log('AutoSnippet/boxspec.json');
305
+ writeFileSync(
306
+ dest,
307
+ JSON.stringify(
308
+ {
309
+ name: this.projectName,
310
+ schemaVersion: 2,
311
+ kind: 'root',
312
+ root: true,
313
+ knowledgeBase: { dir: 'AutoSnippet' },
314
+ module: { rootDir: 'AutoSnippet' },
315
+ },
316
+ null,
317
+ 2
318
+ )
319
+ );
318
320
  }
319
321
 
320
322
  /** @private 复制 _template.md 到 recipes/ */
321
323
  _copyRecipeTemplate() {
322
324
  const src = join(REPO_ROOT, 'templates', 'recipes-setup', '_template.md');
323
- if (!existsSync(src)) return;
325
+ if (!existsSync(src)) {
326
+ return;
327
+ }
324
328
 
325
329
  const dest = join(this.recipesDir, '_template.md');
326
- if (existsSync(dest) && !this.force) return;
330
+ if (existsSync(dest) && !this.force) {
331
+ return;
332
+ }
327
333
  copyFileSync(src, dest);
328
- console.log(' ✅ AutoSnippet/recipes/_template.md(格式参考)');
329
334
  }
330
335
 
331
336
  /** @private 复制示例 Recipe(冷启动推荐) */
332
337
  _copySeedRecipes() {
333
338
  const seedDir = join(REPO_ROOT, 'templates', 'recipes-setup');
334
- if (!existsSync(seedDir)) return;
339
+ if (!existsSync(seedDir)) {
340
+ return;
341
+ }
335
342
 
336
343
  // 匹配 seed-*.md 文件
337
344
  let files;
338
345
  try {
339
- files = readdirSync(seedDir).filter(f => f.startsWith('seed-') && f.endsWith('.md'));
340
- } catch { return; }
346
+ files = readdirSync(seedDir).filter((f) => f.startsWith('seed-') && f.endsWith('.md'));
347
+ } catch {
348
+ return;
349
+ }
341
350
 
342
351
  let count = 0;
343
352
  for (const file of files) {
344
353
  const dest = join(this.recipesDir, file.replace('seed-', ''));
345
- if (existsSync(dest) && !this.force) continue;
354
+ if (existsSync(dest) && !this.force) {
355
+ continue;
356
+ }
346
357
  copyFileSync(join(seedDir, file), dest);
347
358
  count++;
348
359
  }
349
360
  if (count > 0) {
350
- console.log(` ✅ 预置 ${count} 个示例 Recipe(冷启动数据)`);
351
361
  }
352
362
  }
353
363
 
354
364
  /** @private 写入核心目录 README */
355
365
  _writeCoreReadme() {
356
366
  const dest = join(this.coreDir, 'README.md');
357
- if (existsSync(dest) && !this.force) return;
358
-
359
- writeFileSync(dest, [
360
- `# ${this.projectName} — AutoSnippet Knowledge Base`,
361
- '',
362
- '此目录是项目的 **核心知识库**,通过 Git 子仓库管理,同时承载数据存储与权限控制。',
363
- '',
364
- '## 目录结构',
365
- '',
366
- '```',
367
- 'AutoSnippet/',
368
- '├── constitution.yaml 权限宪法(角色 + 权限 + 治理规则 + 能力探测)',
369
- '├── boxspec.json 项目规格',
370
- '├── recipes/ 统一知识实体(Markdown + YAML front-matter)',
371
- '│ ├── _template.md 格式参考',
372
- '├── naming-rules.md 代码规范示例',
373
- '├── mvvm-arch.md 架构模式示例',
374
- '│ └── ... 代码模式/调用链/数据流/约束/风格/...',
375
- '├── skills/ Project Skills(冷启动自动生成 + 手动创建)',
376
- '│ └── <name>/SKILL.md AI Agent 知识增强文档',
377
- '└── README.md',
378
- '```',
379
- '',
380
- '## 统一知识模型',
381
- '',
382
- '所有知识统一为 **Recipe** 实体,由 `knowledgeType` 区分维度:',
383
- '',
384
- '| knowledgeType | 说明 |',
385
- '|---------------|------|',
386
- '| code-standard | 代码规范 |',
387
- '| code-pattern | 代码模式 |',
388
- '| code-relation | 代码关联 |',
389
- '| inheritance | 继承与接口 |',
390
- '| call-chain | 调用链路 |',
391
- '| data-flow | 数据流向 |',
392
- '| module-dependency | 模块与依赖 |',
393
- '| architecture | 模式与架构 |',
394
- '| best-practice | 最佳实践 |',
395
- '| boundary-constraint | 边界约束(含 Guard 规则) |',
396
- '| code-style | 代码风格 |',
397
- '| solution | 问题解决方案 |',
398
- '',
399
- '## 权限模型',
400
- '',
401
- 'AutoSnippet 使用 **三层权限架构**:',
402
- '',
403
- '| 层级 | 机制 | 职责 |',
404
- '|------|------|------|',
405
- '| 能力层 | `git push --dry-run` | 探测子仓库物理写权限 |',
406
- '| ② 角色层 | `constitution.yaml` roles | 角色权限矩阵 (action:resource) |',
407
- '| 治理层 | `constitution.yaml` priorities | 业务规则引擎 |',
408
- '',
409
- 'git 权限只是"能力信号",**最终裁决权在 Constitution YAML**。',
410
- '',
411
- '## 团队使用',
412
- '',
413
- '```bash',
414
- '# 方式 1: 添加远程仓库',
415
- 'cd AutoSnippet',
416
- 'git remote add origin <your-repo-url>',
417
- '',
418
- '# 方式 2: 使用 git submodule(推荐)',
419
- 'cd ..',
420
- 'rm -rf AutoSnippet',
421
- 'git submodule add <your-repo-url> AutoSnippet',
422
- '```',
423
- '',
424
- '> 运行时缓存(DB 索引、Candidates、Snippets、审计日志)在 `.autosnippet/autosnippet.db`。',
425
- '> **核心数据的唯一真实来源是此目录中的文件**,DB 仅做缓存。修改 Recipe/Guard 规则必须通过 git。',
426
- '',
427
- ].join('\n'));
428
- console.log(' AutoSnippet/README.md');
367
+ if (existsSync(dest) && !this.force) {
368
+ return;
369
+ }
370
+
371
+ writeFileSync(
372
+ dest,
373
+ [
374
+ `# ${this.projectName} — AutoSnippet Knowledge Base`,
375
+ '',
376
+ '此目录是项目的 **核心知识库**,通过 Git 子仓库管理,同时承载数据存储与权限控制。',
377
+ '',
378
+ '## 目录结构',
379
+ '',
380
+ '```',
381
+ 'AutoSnippet/',
382
+ '├── constitution.yaml 权限宪法(角色 + 权限 + 治理规则 + 能力探测)',
383
+ '├── boxspec.json 项目规格',
384
+ '├── recipes/ 统一知识实体(Markdown + YAML front-matter)',
385
+ '├── _template.md 格式参考',
386
+ '│ ├── naming-rules.md 代码规范示例',
387
+ '│ ├── mvvm-arch.md 架构模式示例',
388
+ '│ └── ... 代码模式/调用链/数据流/约束/风格/...',
389
+ '├── skills/ Project Skills(冷启动自动生成 + 手动创建)',
390
+ '│ └── <name>/SKILL.md AI Agent 知识增强文档',
391
+ '└── README.md',
392
+ '```',
393
+ '',
394
+ '## 统一知识模型',
395
+ '',
396
+ '所有知识统一为 **Recipe** 实体,由 `knowledgeType` 区分维度:',
397
+ '',
398
+ '| knowledgeType | 说明 |',
399
+ '|---------------|------|',
400
+ '| code-standard | 代码规范 |',
401
+ '| code-pattern | 代码模式 |',
402
+ '| code-relation | 代码关联 |',
403
+ '| inheritance | 继承与接口 |',
404
+ '| call-chain | 调用链路 |',
405
+ '| data-flow | 数据流向 |',
406
+ '| module-dependency | 模块与依赖 |',
407
+ '| architecture | 模式与架构 |',
408
+ '| best-practice | 最佳实践 |',
409
+ '| boundary-constraint | 边界约束(含 Guard 规则) |',
410
+ '| code-style | 代码风格 |',
411
+ '| solution | 问题解决方案 |',
412
+ '',
413
+ '## 权限模型',
414
+ '',
415
+ 'AutoSnippet 使用 **三层权限架构**:',
416
+ '',
417
+ '| 层级 | 机制 | 职责 |',
418
+ '|------|------|------|',
419
+ '| ① 能力层 | `git push --dry-run` | 探测子仓库物理写权限 |',
420
+ '| ② 角色层 | `constitution.yaml` roles | 角色权限矩阵 (action:resource) |',
421
+ '| ③ 治理层 | `constitution.yaml` priorities | 业务规则引擎 |',
422
+ '',
423
+ 'git 权限只是"能力信号",**最终裁决权在 Constitution YAML**。',
424
+ '',
425
+ '## 团队使用',
426
+ '',
427
+ '```bash',
428
+ '# 方式 1: 添加远程仓库',
429
+ 'cd AutoSnippet',
430
+ 'git remote add origin <your-repo-url>',
431
+ '',
432
+ '# 方式 2: 使用 git submodule(推荐)',
433
+ 'cd ..',
434
+ 'rm -rf AutoSnippet',
435
+ 'git submodule add <your-repo-url> AutoSnippet',
436
+ '```',
437
+ '',
438
+ '> 运行时缓存(DB 索引、Candidates、Snippets、审计日志)在 `.autosnippet/autosnippet.db`。',
439
+ '> **核心数据的唯一真实来源是此目录中的文件**,DB 仅做缓存。修改 Recipe/Guard 规则必须通过 git。',
440
+ '',
441
+ ].join('\n')
442
+ );
429
443
  }
430
444
 
431
445
  /* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
@@ -441,23 +455,187 @@ export class SetupService {
441
455
  this._mirrorCursorToIDE('.qoder');
442
456
  this._mirrorCursorToIDE('.trae');
443
457
 
444
- return { configured: ['vscode-mcp', 'cursor-mcp', 'copilot-instructions', 'cursor-rules', 'skills-template', 'qoder-rules', 'trae-rules'] };
458
+ const extResult = this._installVSCodeExtension();
459
+
460
+ const configured = [
461
+ 'vscode-mcp',
462
+ 'cursor-mcp',
463
+ 'copilot-instructions',
464
+ 'cursor-rules',
465
+ 'skills-template',
466
+ 'qoder-rules',
467
+ 'trae-rules',
468
+ ];
469
+ if (extResult) {
470
+ configured.push(...extResult);
471
+ }
472
+ return { configured };
473
+ }
474
+
475
+ /**
476
+ * @private 构建 + 安装 VSCode Extension (.vsix)
477
+ *
478
+ * 流程:
479
+ * 1. 编译 TypeScript(tsc)
480
+ * 2. 打包 .vsix(vsce package)
481
+ * 3. 探测所有可用的 VS Code 兼容 IDE CLI
482
+ * 4. 对每个 IDE 执行 --install-extension
483
+ *
484
+ * 支持:VS Code / Cursor / Codex 等基于 VS Code 的 IDE。
485
+ * 找不到任何 IDE CLI 时静默跳过,不阻断 setup 流程。
486
+ *
487
+ * @returns {string[]|null} 安装成功的 IDE 列表, 或 null
488
+ */
489
+ _installVSCodeExtension() {
490
+ const extDir = join(REPO_ROOT, 'resources', 'vscode-ext');
491
+ const pkgJson = join(extDir, 'package.json');
492
+
493
+ if (!existsSync(pkgJson)) {
494
+ return null;
495
+ }
496
+
497
+ // ── 1. 编译 TypeScript ──
498
+ try {
499
+ execSync('npx tsc -p ./tsconfig.json', { cwd: extDir, stdio: 'pipe' });
500
+ } catch (e) {
501
+ console.error(` ⚠ VSCode Extension 编译失败: ${e.stderr?.toString().trim() || e.message}`);
502
+ return null;
503
+ }
504
+
505
+ // ── 2. 打包 .vsix ──
506
+ let vsixPath;
507
+ try {
508
+ const out = execSync('npx @vscode/vsce package --no-dependencies 2>&1', {
509
+ cwd: extDir,
510
+ stdio: 'pipe',
511
+ encoding: 'utf8',
512
+ });
513
+ // 从输出中提取 vsix 文件路径: "DONE Packaged: /path/to/autosnippet-0.1.0.vsix ..."
514
+ const m = out.match(/Packaged:\s*(.+\.vsix)/);
515
+ if (m) {
516
+ vsixPath = m[1].trim();
517
+ }
518
+ } catch (e) {
519
+ console.error(` ⚠ VSCode Extension 打包失败: ${e.message}`);
520
+ return null;
521
+ }
522
+
523
+ // fallback: 扫描目录找 .vsix 文件
524
+ if (!vsixPath || !existsSync(vsixPath)) {
525
+ try {
526
+ const files = readdirSync(extDir).filter((f) => f.endsWith('.vsix'));
527
+ if (files.length > 0) {
528
+ // 取最新的
529
+ files.sort().reverse();
530
+ vsixPath = join(extDir, files[0]);
531
+ }
532
+ } catch {
533
+ /* ignore */
534
+ }
535
+ }
536
+
537
+ if (!vsixPath || !existsSync(vsixPath)) {
538
+ console.error(' ⚠ 找不到 .vsix 文件,跳过 Extension 安装');
539
+ return null;
540
+ }
541
+
542
+ // ── 3. 探测可用的 IDE CLI ──
543
+ const cliCandidates = this._discoverIDEClis();
544
+ if (cliCandidates.length === 0) {
545
+ console.error(' ⚠ 未找到 VS Code / Cursor 等 IDE CLI,跳过 Extension 安装');
546
+ console.error(' 提示: 手动安装 → 在 IDE 中 Cmd+Shift+P → "Install from VSIX"');
547
+ console.error(` 文件: ${vsixPath}`);
548
+ return null;
549
+ }
550
+
551
+ // ── 4. 逐个安装 ──
552
+ const installed = [];
553
+ for (const { name, cli } of cliCandidates) {
554
+ try {
555
+ execSync(`"${cli}" --install-extension "${vsixPath}" --force 2>&1`, {
556
+ stdio: 'pipe',
557
+ encoding: 'utf8',
558
+ timeout: 30_000,
559
+ });
560
+ installed.push(`vscode-ext:${name}`);
561
+ } catch (e) {
562
+ console.error(` ⚠ ${name} Extension 安装失败: ${e.message}`);
563
+ }
564
+ }
565
+ return installed.length > 0 ? installed : null;
566
+ }
567
+
568
+ /**
569
+ * @private 探测系统中所有 VS Code 兼容 IDE 的 CLI 路径
570
+ * @returns {{ name: string, cli: string }[]}
571
+ */
572
+ _discoverIDEClis() {
573
+ const candidates = [];
574
+
575
+ // 1. PATH 中的命令
576
+ for (const cmd of ['code', 'cursor', 'codex', 'code-insiders']) {
577
+ try {
578
+ const p = execSync(`which ${cmd} 2>/dev/null`, { encoding: 'utf8' }).trim();
579
+ if (p) {
580
+ candidates.push({ name: cmd, cli: p });
581
+ }
582
+ } catch {
583
+ /* not in PATH */
584
+ }
585
+ }
586
+
587
+ // 2. macOS 应用内置 CLI(/Applications/xxx.app/Contents/Resources/app/bin/)
588
+ if (process.platform === 'darwin') {
589
+ const appPaths = [
590
+ { name: 'vscode', app: '/Applications/Visual Studio Code.app', bin: 'code' },
591
+ { name: 'vscode-insiders', app: '/Applications/Visual Studio Code - Insiders.app', bin: 'code-insiders' },
592
+ { name: 'cursor', app: '/Applications/Cursor.app', bin: 'cursor' },
593
+ { name: 'codex', app: '/Applications/Codex.app', bin: 'codex' },
594
+ ];
595
+ for (const { name, app, bin } of appPaths) {
596
+ const cli = join(app, 'Contents', 'Resources', 'app', 'bin', bin);
597
+ if (existsSync(cli) && !candidates.some((c) => c.name === name)) {
598
+ candidates.push({ name, cli });
599
+ }
600
+ }
601
+ }
602
+
603
+ // 3. 去重(同一个二进制不重复安装)
604
+ const seen = new Set();
605
+ return candidates.filter((c) => {
606
+ let realPath = c.cli;
607
+ try {
608
+ realPath = execSync(`readlink -f "${c.cli}" 2>/dev/null || echo "${c.cli}"`, {
609
+ encoding: 'utf8',
610
+ }).trim();
611
+ } catch {
612
+ /* use as-is */
613
+ }
614
+ if (seen.has(realPath)) return false;
615
+ seen.add(realPath);
616
+ return true;
617
+ });
445
618
  }
446
619
 
447
- /** @private VSCode settings.json → Copilot MCP */
620
+ /** @private .vscode/mcp.json → VSCode MCP (新标准格式) */
448
621
  _configureVSCodeMCP(mcpServerPath) {
449
- const vscodeDir = join(this.projectRoot, '.vscode');
450
- const settingsPath = join(vscodeDir, 'settings.json');
622
+ const vscodeDir = join(this.projectRoot, '.vscode');
623
+ const mcpConfigPath = join(vscodeDir, 'mcp.json');
451
624
  mkdirSync(vscodeDir, { recursive: true });
452
625
 
453
- let settings = {};
454
- if (existsSync(settingsPath)) {
455
- try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { /* ignore */ }
626
+ let config = {};
627
+ if (existsSync(mcpConfigPath)) {
628
+ try {
629
+ config = JSON.parse(readFileSync(mcpConfigPath, 'utf8'));
630
+ } catch {
631
+ /* ignore */
632
+ }
456
633
  }
457
634
 
458
- if (!settings['github.copilot.mcp']) settings['github.copilot.mcp'] = {};
459
- if (!settings['github.copilot.mcp'].servers) settings['github.copilot.mcp'].servers = {};
460
- settings['github.copilot.mcp'].servers['autosnippet'] = {
635
+ if (!config.servers) {
636
+ config.servers = {};
637
+ }
638
+ config.servers.autosnippet = {
461
639
  type: 'stdio',
462
640
  command: 'node',
463
641
  args: [mcpServerPath],
@@ -467,23 +645,28 @@ export class SetupService {
467
645
  },
468
646
  };
469
647
 
470
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
471
- console.log(' ✅ .vscode/settings.json (Copilot MCP)');
648
+ writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
472
649
  }
473
650
 
474
651
  /** @private .cursor/mcp.json */
475
652
  _configureCursorMCP(mcpServerPath) {
476
- const cursorDir = join(this.projectRoot, '.cursor');
653
+ const cursorDir = join(this.projectRoot, '.cursor');
477
654
  const configPath = join(cursorDir, 'mcp.json');
478
655
  mkdirSync(cursorDir, { recursive: true });
479
656
 
480
657
  let existing = {};
481
658
  if (existsSync(configPath)) {
482
- try { existing = JSON.parse(readFileSync(configPath, 'utf8')); } catch { /* ignore */ }
659
+ try {
660
+ existing = JSON.parse(readFileSync(configPath, 'utf8'));
661
+ } catch {
662
+ /* ignore */
663
+ }
483
664
  }
484
665
 
485
- if (!existing.mcpServers) existing.mcpServers = {};
486
- existing.mcpServers['autosnippet'] = {
666
+ if (!existing.mcpServers) {
667
+ existing.mcpServers = {};
668
+ }
669
+ existing.mcpServers.autosnippet = {
487
670
  command: 'node',
488
671
  args: [mcpServerPath],
489
672
  env: {
@@ -493,83 +676,83 @@ export class SetupService {
493
676
  };
494
677
 
495
678
  writeFileSync(configPath, JSON.stringify(existing, null, 2));
496
- console.log(' ✅ .cursor/mcp.json');
497
679
  }
498
680
 
499
681
  /** @private .github/copilot-instructions.md */
500
682
  _copyCopilotInstructions() {
501
683
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
502
- if (!existsSync(src)) return;
684
+ if (!existsSync(src)) {
685
+ return;
686
+ }
503
687
 
504
688
  const destDir = join(this.projectRoot, '.github');
505
- const dest = join(destDir, 'copilot-instructions.md');
689
+ const dest = join(destDir, 'copilot-instructions.md');
506
690
  if (existsSync(dest) && !this.force) {
507
- console.log(' ℹ️ copilot-instructions.md 已存在');
508
691
  return;
509
692
  }
510
693
 
511
694
  mkdirSync(destDir, { recursive: true });
512
695
  copyFileSync(src, dest);
513
- console.log(' ✅ .github/copilot-instructions.md');
514
696
  }
515
697
 
516
698
  /** @private .cursor/rules/autosnippet-conventions.mdc */
517
699
  _copyCursorRules() {
518
700
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
519
- if (!existsSync(src)) return;
701
+ if (!existsSync(src)) {
702
+ return;
703
+ }
520
704
 
521
705
  const destDir = join(this.projectRoot, '.cursor', 'rules');
522
- const dest = join(destDir, 'autosnippet-conventions.mdc');
706
+ const dest = join(destDir, 'autosnippet-conventions.mdc');
523
707
  if (existsSync(dest) && !this.force) {
524
- console.log(' ℹ️ cursor rules 已存在');
525
708
  return;
526
709
  }
527
710
 
528
711
  mkdirSync(destDir, { recursive: true });
529
712
  copyFileSync(src, dest);
530
- console.log(' ✅ .cursor/rules/autosnippet-conventions.mdc');
531
713
  }
532
714
 
533
715
  /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
534
716
  _copySkillsTemplate() {
535
717
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
536
- if (!existsSync(src)) return;
718
+ if (!existsSync(src)) {
719
+ return;
720
+ }
537
721
 
538
722
  const destDir = join(this.projectRoot, '.cursor', 'rules');
539
- const dest = join(destDir, 'autosnippet-skills.mdc');
723
+ const dest = join(destDir, 'autosnippet-skills.mdc');
540
724
  if (existsSync(dest) && !this.force) {
541
- console.log(' ℹ️ skills template 已存在');
542
725
  return;
543
726
  }
544
727
 
545
728
  mkdirSync(destDir, { recursive: true });
546
729
  copyFileSync(src, dest);
547
- console.log(' ✅ .cursor/rules/autosnippet-skills.mdc');
548
730
  }
549
731
  /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
550
732
  * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
551
733
  _mirrorCursorToIDE(targetDirName) {
552
734
  const cursorRulesDir = join(this.projectRoot, '.cursor', 'rules');
553
- if (!existsSync(cursorRulesDir)) return;
735
+ if (!existsSync(cursorRulesDir)) {
736
+ return;
737
+ }
554
738
 
555
739
  const targetRulesDir = join(this.projectRoot, targetDirName, 'rules');
556
740
  mkdirSync(targetRulesDir, { recursive: true });
557
741
 
558
742
  // 只镜像 autosnippet- 前缀的文件,保留目标目录中用户已有的其他文件
559
- const files = readdirSync(cursorRulesDir).filter(f =>
560
- (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
743
+ const files = readdirSync(cursorRulesDir).filter(
744
+ (f) => (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
561
745
  );
562
746
  for (const file of files) {
563
747
  const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
564
748
  copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
565
749
  }
566
- console.log(` ✅ ${targetDirName}/rules/ (镜像 ${files.length} 个 autosnippet 规则文件)`);
567
750
  }
568
751
  /* ═══ Step 4: 数据库初始化 ═══════════════════════════ */
569
752
 
570
753
  async stepDatabase() {
571
754
  const ConfigLoader = (await import('../infrastructure/config/ConfigLoader.js')).default;
572
- const Bootstrap = (await import('../bootstrap.js')).default;
755
+ const Bootstrap = (await import('../bootstrap.js')).default;
573
756
 
574
757
  const env = process.env.NODE_ENV || 'development';
575
758
  ConfigLoader.load(env);
@@ -577,7 +760,6 @@ export class SetupService {
577
760
 
578
761
  const bootstrap = new Bootstrap({ env });
579
762
  await bootstrap.initialize();
580
- console.log(' ✅ 数据库已初始化(migrations applied)');
581
763
 
582
764
  const db = bootstrap.components?.db?.getDb?.();
583
765
  if (db) {
@@ -586,7 +768,7 @@ export class SetupService {
586
768
  }
587
769
 
588
770
  await bootstrap.shutdown();
589
- ConfigLoader.config = null; // 重置静态状态
771
+ ConfigLoader.config = null; // 重置静态状态
590
772
  return { dbPath: this.dbPath };
591
773
  }
592
774
 
@@ -600,45 +782,32 @@ export class SetupService {
600
782
  const report = syncService.sync(db, { skipViolations: true });
601
783
 
602
784
  if (report.synced > 0) {
603
- console.log(` ✅ 已同步 ${report.synced} 个知识文件到 DB 缓存(新增 ${report.created},更新 ${report.updated})`);
604
785
  } else {
605
- console.log(' ℹ️ 暂无 .md 文件,跳过同步');
606
786
  }
607
787
 
608
788
  if (report.orphaned.length > 0) {
609
- console.log(` ℹ️ ${report.orphaned.length} 个孤儿条目已标记 deprecated`);
610
789
  }
611
790
  }
612
791
 
613
- /* ═══ Step 5: 平台初始化 ═════════════════════════════ */
792
+ /* ═══ Step 5: Snippet 初始化 (Xcode + VSCode) ═══════ */
614
793
 
615
794
  async stepPlatform() {
616
- if (process.platform !== 'darwin') {
617
- console.log(' ℹ️ 非 macOS,跳过 Xcode 初始化');
618
- return { skipped: true };
619
- }
620
-
621
- const initScript = join(REPO_ROOT, 'scripts', 'init-xcode-snippets.js');
795
+ const initScript = join(REPO_ROOT, 'scripts', 'init-snippets.js');
622
796
  if (!existsSync(initScript)) {
623
- console.log(' ℹ️ init-xcode-snippets 脚本不存在,跳过');
624
797
  return { skipped: true };
625
798
  }
626
799
 
627
800
  try {
628
- const mod = await import(initScript);
801
+ const mod = await import(initScript);
629
802
  const initFn = mod.initialize || mod.default?.initialize || mod.default;
630
803
  if (typeof initFn !== 'function') {
631
- console.log(' ℹ️ init-xcode-snippets 格式不兼容,跳过');
632
804
  return { skipped: true };
633
805
  }
634
806
 
635
- const ok = await initFn();
636
- console.log(ok
637
- ? ' ✅ Xcode Snippets 已添加'
638
- : ' ℹ️ Xcode Snippets 未更新(可能已存在)');
639
- return { xcode: ok };
807
+ const result = await initFn(this.projectRoot, 'all');
808
+ return result;
640
809
  } catch (e) {
641
- console.warn(` ⚠️ Xcode 初始化失败:${e.message}`);
810
+ console.warn(` ⚠️ Snippet 初始化失败:${e.message}`);
642
811
  return { error: e.message };
643
812
  }
644
813
  }
@@ -652,7 +821,6 @@ export class SetupService {
652
821
  _ensureEnvFile() {
653
822
  const envPath = join(this.projectRoot, '.env');
654
823
  if (existsSync(envPath)) {
655
- console.log(' ℹ️ .env 已存在,跳过写入。如需配置 AI,请手动编辑或通过 Dashboard 设置');
656
824
  return;
657
825
  }
658
826
 
@@ -661,17 +829,19 @@ export class SetupService {
661
829
  copyFileSync(examplePath, envPath);
662
830
  } else {
663
831
  // fallback: .env.example 缺失时写入最小模板
664
- writeFileSync(envPath, [
665
- '# AutoSnippet AI 配置(由 asd setup 自动生成)',
666
- '# 完整配置说明见 .env.example',
667
- '',
668
- 'ASD_AI_PROVIDER=google',
669
- 'ASD_AI_MODEL=gemini-3-flash-preview',
670
- '# ASD_GOOGLE_API_KEY=',
671
- '',
672
- ].join('\n'));
832
+ writeFileSync(
833
+ envPath,
834
+ [
835
+ '# AutoSnippet AI 配置(由 asd setup 自动生成)',
836
+ '# 完整配置说明见 .env.example',
837
+ '',
838
+ 'ASD_AI_PROVIDER=google',
839
+ 'ASD_AI_MODEL=gemini-3-flash-preview',
840
+ '# ASD_GOOGLE_API_KEY=',
841
+ '',
842
+ ].join('\n')
843
+ );
673
844
  }
674
- console.log(' ✅ .env(已从 .env.example 复制,请填写 API Key 后使用)');
675
845
  }
676
846
 
677
847
  /** @private 确保项目 .gitignore 正确配置 AutoSnippet 相关规则 */
@@ -684,71 +854,58 @@ export class SetupService {
684
854
  // 旧格式会忽略整个目录(git 不遍历内部),导致 skills/ 和 config.json 无法被 negation 恢复
685
855
  // 新格式忽略目录内所有文件,允许 negation 模式取消特定子路径
686
856
  if (content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
687
- content = content.replace(
688
- /^\.autosnippet\/$/m,
689
- '.autosnippet/*',
690
- );
857
+ content = content.replace(/^\.autosnippet\/$/m, '.autosnippet/*');
691
858
  changed = true;
692
- console.log(' ✅ .gitignore: .autosnippet/ → .autosnippet/*(升级为精细忽略)');
693
859
  }
694
860
 
695
861
  // ── 必须忽略:.autosnippet/*(运行时缓存、DB、向量索引、memory) ──
696
862
  if (!content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
697
863
  content += `\n# AutoSnippet 运行时缓存(不入库)\n.autosnippet/*\n`;
698
864
  changed = true;
699
- console.log(' ✅ .gitignore += .autosnippet/*');
700
865
  }
701
866
 
702
867
  // ── 必须跟踪:.autosnippet/config.json(项目配置) ──
703
868
  if (!content.includes('!.autosnippet/config.json')) {
704
869
  content += `!.autosnippet/config.json\n`;
705
870
  changed = true;
706
- console.log(' ✅ .gitignore += !.autosnippet/config.json');
707
871
  }
708
872
 
709
873
  // ── 必须忽略:.env(包含 API Key 等敏感信息) ──
710
874
  if (!content.includes('.env') || (!content.match(/^\.env$/m) && !content.match(/^\.env\s/m))) {
711
875
  content += `\n# AutoSnippet 环境变量(含 API Key,不入库)\n.env\n`;
712
876
  changed = true;
713
- console.log(' ✅ .gitignore += .env');
714
877
  }
715
878
 
716
879
  // ── 必须忽略:logs/(winston 运行日志,可达数十 MB) ──
717
880
  if (!content.match(/^logs\/?$/m)) {
718
881
  content += `\n# AutoSnippet 运行日志\nlogs/\n`;
719
882
  changed = true;
720
- console.log(' ✅ .gitignore += logs/');
721
883
  }
722
884
 
723
885
  // ── 必须忽略:.autosnippet-drafts/(AI 草稿临时目录) ──
724
886
  if (!content.includes('.autosnippet-drafts')) {
725
887
  content += `\n# AutoSnippet AI 草稿(临时)\n.autosnippet-drafts/\n`;
726
888
  changed = true;
727
- console.log(' ✅ .gitignore += .autosnippet-drafts/');
728
889
  }
729
890
 
730
891
  // ── 必须忽略:_draft_*.md(AI Agent 在项目根目录创建的草稿文件) ──
731
892
  if (!content.includes('_draft_*.md')) {
732
893
  content += `\n# AutoSnippet AI 草稿文件(项目根目录临时文件)\n_draft_*.md\n`;
733
894
  changed = true;
734
- console.log(' ✅ .gitignore += _draft_*.md');
735
895
  }
736
896
 
737
897
  // ── 必须忽略:常见系统 / 编辑器临时文件 ──
738
898
  if (!content.includes('.DS_Store')) {
739
899
  content += `\n# macOS 元数据\n.DS_Store\n`;
740
900
  changed = true;
741
- console.log(' ✅ .gitignore += .DS_Store');
742
901
  }
743
902
  if (!content.includes('nohup.out')) {
744
903
  content += `nohup.out\n`;
745
904
  changed = true;
746
- console.log(' ✅ .gitignore += nohup.out');
747
905
  }
748
906
  if (!content.match(/\*\.sw[a-p]/)) {
749
907
  content += `*.sw[a-p]\n`;
750
908
  changed = true;
751
- console.log(' ✅ .gitignore += *.sw[a-p]');
752
909
  }
753
910
 
754
911
  // Skills 已迁移到 AutoSnippet/skills/(知识库目录内),自动跟随 Git
@@ -757,22 +914,22 @@ export class SetupService {
757
914
  if (content.includes('!.autosnippet/skills/')) {
758
915
  content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
759
916
  changed = true;
760
- console.log(' ✅ .gitignore: 移除旧版 .autosnippet/skills/ 规则(已迁移到 AutoSnippet/skills/)');
761
917
  }
762
918
 
763
919
  // ── 必须跟踪:AutoSnippet/(知识库子仓库)──
764
920
  // 如果用户误将 AutoSnippet/ 加入忽略,追加 !AutoSnippet/ 取消忽略
765
921
  const lines = content.split('\n');
766
- const hasIgnoreAS = lines.some(l => {
922
+ const hasIgnoreAS = lines.some((l) => {
767
923
  const t = l.trim();
768
- return (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!');
924
+ return (
925
+ (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!')
926
+ );
769
927
  });
770
- const hasNegation = lines.some(l => l.trim() === '!AutoSnippet/');
928
+ const hasNegation = lines.some((l) => l.trim() === '!AutoSnippet/');
771
929
 
772
930
  if (hasIgnoreAS && !hasNegation) {
773
931
  content += `\n# AutoSnippet 知识库必须入库(取消上方忽略)\n!AutoSnippet/\n`;
774
932
  changed = true;
775
- console.log(' ✅ .gitignore += !AutoSnippet/ (取消忽略)');
776
933
  }
777
934
 
778
935
  if (changed) {
@@ -789,7 +946,9 @@ export class SetupService {
789
946
  encoding: 'utf8',
790
947
  }).trim();
791
948
  } catch (e) {
792
- if (args[0] === 'commit' && e.status === 1) return '';
949
+ if (args[0] === 'commit' && e.status === 1) {
950
+ return '';
951
+ }
793
952
  throw e;
794
953
  }
795
954
  }