autosnippet 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -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,190 @@ 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
620
  /** @private VSCode settings.json → Copilot MCP */
448
621
  _configureVSCodeMCP(mcpServerPath) {
449
- const vscodeDir = join(this.projectRoot, '.vscode');
622
+ const vscodeDir = join(this.projectRoot, '.vscode');
450
623
  const settingsPath = join(vscodeDir, 'settings.json');
451
624
  mkdirSync(vscodeDir, { recursive: true });
452
625
 
453
626
  let settings = {};
454
627
  if (existsSync(settingsPath)) {
455
- try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { /* ignore */ }
628
+ try {
629
+ settings = JSON.parse(readFileSync(settingsPath, '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 (!settings['github.copilot.mcp']) {
636
+ settings['github.copilot.mcp'] = {};
637
+ }
638
+ if (!settings['github.copilot.mcp'].servers) {
639
+ settings['github.copilot.mcp'].servers = {};
640
+ }
641
+ settings['github.copilot.mcp'].servers.autosnippet = {
461
642
  type: 'stdio',
462
643
  command: 'node',
463
644
  args: [mcpServerPath],
@@ -468,22 +649,27 @@ export class SetupService {
468
649
  };
469
650
 
470
651
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
471
- console.log(' ✅ .vscode/settings.json (Copilot MCP)');
472
652
  }
473
653
 
474
654
  /** @private .cursor/mcp.json */
475
655
  _configureCursorMCP(mcpServerPath) {
476
- const cursorDir = join(this.projectRoot, '.cursor');
656
+ const cursorDir = join(this.projectRoot, '.cursor');
477
657
  const configPath = join(cursorDir, 'mcp.json');
478
658
  mkdirSync(cursorDir, { recursive: true });
479
659
 
480
660
  let existing = {};
481
661
  if (existsSync(configPath)) {
482
- try { existing = JSON.parse(readFileSync(configPath, 'utf8')); } catch { /* ignore */ }
662
+ try {
663
+ existing = JSON.parse(readFileSync(configPath, 'utf8'));
664
+ } catch {
665
+ /* ignore */
666
+ }
483
667
  }
484
668
 
485
- if (!existing.mcpServers) existing.mcpServers = {};
486
- existing.mcpServers['autosnippet'] = {
669
+ if (!existing.mcpServers) {
670
+ existing.mcpServers = {};
671
+ }
672
+ existing.mcpServers.autosnippet = {
487
673
  command: 'node',
488
674
  args: [mcpServerPath],
489
675
  env: {
@@ -493,83 +679,83 @@ export class SetupService {
493
679
  };
494
680
 
495
681
  writeFileSync(configPath, JSON.stringify(existing, null, 2));
496
- console.log(' ✅ .cursor/mcp.json');
497
682
  }
498
683
 
499
684
  /** @private .github/copilot-instructions.md */
500
685
  _copyCopilotInstructions() {
501
686
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
502
- if (!existsSync(src)) return;
687
+ if (!existsSync(src)) {
688
+ return;
689
+ }
503
690
 
504
691
  const destDir = join(this.projectRoot, '.github');
505
- const dest = join(destDir, 'copilot-instructions.md');
692
+ const dest = join(destDir, 'copilot-instructions.md');
506
693
  if (existsSync(dest) && !this.force) {
507
- console.log(' ℹ️ copilot-instructions.md 已存在');
508
694
  return;
509
695
  }
510
696
 
511
697
  mkdirSync(destDir, { recursive: true });
512
698
  copyFileSync(src, dest);
513
- console.log(' ✅ .github/copilot-instructions.md');
514
699
  }
515
700
 
516
701
  /** @private .cursor/rules/autosnippet-conventions.mdc */
517
702
  _copyCursorRules() {
518
703
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
519
- if (!existsSync(src)) return;
704
+ if (!existsSync(src)) {
705
+ return;
706
+ }
520
707
 
521
708
  const destDir = join(this.projectRoot, '.cursor', 'rules');
522
- const dest = join(destDir, 'autosnippet-conventions.mdc');
709
+ const dest = join(destDir, 'autosnippet-conventions.mdc');
523
710
  if (existsSync(dest) && !this.force) {
524
- console.log(' ℹ️ cursor rules 已存在');
525
711
  return;
526
712
  }
527
713
 
528
714
  mkdirSync(destDir, { recursive: true });
529
715
  copyFileSync(src, dest);
530
- console.log(' ✅ .cursor/rules/autosnippet-conventions.mdc');
531
716
  }
532
717
 
533
718
  /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
534
719
  _copySkillsTemplate() {
535
720
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
536
- if (!existsSync(src)) return;
721
+ if (!existsSync(src)) {
722
+ return;
723
+ }
537
724
 
538
725
  const destDir = join(this.projectRoot, '.cursor', 'rules');
539
- const dest = join(destDir, 'autosnippet-skills.mdc');
726
+ const dest = join(destDir, 'autosnippet-skills.mdc');
540
727
  if (existsSync(dest) && !this.force) {
541
- console.log(' ℹ️ skills template 已存在');
542
728
  return;
543
729
  }
544
730
 
545
731
  mkdirSync(destDir, { recursive: true });
546
732
  copyFileSync(src, dest);
547
- console.log(' ✅ .cursor/rules/autosnippet-skills.mdc');
548
733
  }
549
734
  /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
550
735
  * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
551
736
  _mirrorCursorToIDE(targetDirName) {
552
737
  const cursorRulesDir = join(this.projectRoot, '.cursor', 'rules');
553
- if (!existsSync(cursorRulesDir)) return;
738
+ if (!existsSync(cursorRulesDir)) {
739
+ return;
740
+ }
554
741
 
555
742
  const targetRulesDir = join(this.projectRoot, targetDirName, 'rules');
556
743
  mkdirSync(targetRulesDir, { recursive: true });
557
744
 
558
745
  // 只镜像 autosnippet- 前缀的文件,保留目标目录中用户已有的其他文件
559
- const files = readdirSync(cursorRulesDir).filter(f =>
560
- (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
746
+ const files = readdirSync(cursorRulesDir).filter(
747
+ (f) => (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
561
748
  );
562
749
  for (const file of files) {
563
750
  const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
564
751
  copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
565
752
  }
566
- console.log(` ✅ ${targetDirName}/rules/ (镜像 ${files.length} 个 autosnippet 规则文件)`);
567
753
  }
568
754
  /* ═══ Step 4: 数据库初始化 ═══════════════════════════ */
569
755
 
570
756
  async stepDatabase() {
571
757
  const ConfigLoader = (await import('../infrastructure/config/ConfigLoader.js')).default;
572
- const Bootstrap = (await import('../bootstrap.js')).default;
758
+ const Bootstrap = (await import('../bootstrap.js')).default;
573
759
 
574
760
  const env = process.env.NODE_ENV || 'development';
575
761
  ConfigLoader.load(env);
@@ -577,7 +763,6 @@ export class SetupService {
577
763
 
578
764
  const bootstrap = new Bootstrap({ env });
579
765
  await bootstrap.initialize();
580
- console.log(' ✅ 数据库已初始化(migrations applied)');
581
766
 
582
767
  const db = bootstrap.components?.db?.getDb?.();
583
768
  if (db) {
@@ -586,7 +771,7 @@ export class SetupService {
586
771
  }
587
772
 
588
773
  await bootstrap.shutdown();
589
- ConfigLoader.config = null; // 重置静态状态
774
+ ConfigLoader.config = null; // 重置静态状态
590
775
  return { dbPath: this.dbPath };
591
776
  }
592
777
 
@@ -600,45 +785,32 @@ export class SetupService {
600
785
  const report = syncService.sync(db, { skipViolations: true });
601
786
 
602
787
  if (report.synced > 0) {
603
- console.log(` ✅ 已同步 ${report.synced} 个知识文件到 DB 缓存(新增 ${report.created},更新 ${report.updated})`);
604
788
  } else {
605
- console.log(' ℹ️ 暂无 .md 文件,跳过同步');
606
789
  }
607
790
 
608
791
  if (report.orphaned.length > 0) {
609
- console.log(` ℹ️ ${report.orphaned.length} 个孤儿条目已标记 deprecated`);
610
792
  }
611
793
  }
612
794
 
613
- /* ═══ Step 5: 平台初始化 ═════════════════════════════ */
795
+ /* ═══ Step 5: Snippet 初始化 (Xcode + VSCode) ═══════ */
614
796
 
615
797
  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');
798
+ const initScript = join(REPO_ROOT, 'scripts', 'init-snippets.js');
622
799
  if (!existsSync(initScript)) {
623
- console.log(' ℹ️ init-xcode-snippets 脚本不存在,跳过');
624
800
  return { skipped: true };
625
801
  }
626
802
 
627
803
  try {
628
- const mod = await import(initScript);
804
+ const mod = await import(initScript);
629
805
  const initFn = mod.initialize || mod.default?.initialize || mod.default;
630
806
  if (typeof initFn !== 'function') {
631
- console.log(' ℹ️ init-xcode-snippets 格式不兼容,跳过');
632
807
  return { skipped: true };
633
808
  }
634
809
 
635
- const ok = await initFn();
636
- console.log(ok
637
- ? ' ✅ Xcode Snippets 已添加'
638
- : ' ℹ️ Xcode Snippets 未更新(可能已存在)');
639
- return { xcode: ok };
810
+ const result = await initFn(this.projectRoot, 'all');
811
+ return result;
640
812
  } catch (e) {
641
- console.warn(` ⚠️ Xcode 初始化失败:${e.message}`);
813
+ console.warn(` ⚠️ Snippet 初始化失败:${e.message}`);
642
814
  return { error: e.message };
643
815
  }
644
816
  }
@@ -652,7 +824,6 @@ export class SetupService {
652
824
  _ensureEnvFile() {
653
825
  const envPath = join(this.projectRoot, '.env');
654
826
  if (existsSync(envPath)) {
655
- console.log(' ℹ️ .env 已存在,跳过写入。如需配置 AI,请手动编辑或通过 Dashboard 设置');
656
827
  return;
657
828
  }
658
829
 
@@ -661,17 +832,19 @@ export class SetupService {
661
832
  copyFileSync(examplePath, envPath);
662
833
  } else {
663
834
  // 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'));
835
+ writeFileSync(
836
+ envPath,
837
+ [
838
+ '# AutoSnippet AI 配置(由 asd setup 自动生成)',
839
+ '# 完整配置说明见 .env.example',
840
+ '',
841
+ 'ASD_AI_PROVIDER=google',
842
+ 'ASD_AI_MODEL=gemini-3-flash-preview',
843
+ '# ASD_GOOGLE_API_KEY=',
844
+ '',
845
+ ].join('\n')
846
+ );
673
847
  }
674
- console.log(' ✅ .env(已从 .env.example 复制,请填写 API Key 后使用)');
675
848
  }
676
849
 
677
850
  /** @private 确保项目 .gitignore 正确配置 AutoSnippet 相关规则 */
@@ -684,71 +857,58 @@ export class SetupService {
684
857
  // 旧格式会忽略整个目录(git 不遍历内部),导致 skills/ 和 config.json 无法被 negation 恢复
685
858
  // 新格式忽略目录内所有文件,允许 negation 模式取消特定子路径
686
859
  if (content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
687
- content = content.replace(
688
- /^\.autosnippet\/$/m,
689
- '.autosnippet/*',
690
- );
860
+ content = content.replace(/^\.autosnippet\/$/m, '.autosnippet/*');
691
861
  changed = true;
692
- console.log(' ✅ .gitignore: .autosnippet/ → .autosnippet/*(升级为精细忽略)');
693
862
  }
694
863
 
695
864
  // ── 必须忽略:.autosnippet/*(运行时缓存、DB、向量索引、memory) ──
696
865
  if (!content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
697
866
  content += `\n# AutoSnippet 运行时缓存(不入库)\n.autosnippet/*\n`;
698
867
  changed = true;
699
- console.log(' ✅ .gitignore += .autosnippet/*');
700
868
  }
701
869
 
702
870
  // ── 必须跟踪:.autosnippet/config.json(项目配置) ──
703
871
  if (!content.includes('!.autosnippet/config.json')) {
704
872
  content += `!.autosnippet/config.json\n`;
705
873
  changed = true;
706
- console.log(' ✅ .gitignore += !.autosnippet/config.json');
707
874
  }
708
875
 
709
876
  // ── 必须忽略:.env(包含 API Key 等敏感信息) ──
710
877
  if (!content.includes('.env') || (!content.match(/^\.env$/m) && !content.match(/^\.env\s/m))) {
711
878
  content += `\n# AutoSnippet 环境变量(含 API Key,不入库)\n.env\n`;
712
879
  changed = true;
713
- console.log(' ✅ .gitignore += .env');
714
880
  }
715
881
 
716
882
  // ── 必须忽略:logs/(winston 运行日志,可达数十 MB) ──
717
883
  if (!content.match(/^logs\/?$/m)) {
718
884
  content += `\n# AutoSnippet 运行日志\nlogs/\n`;
719
885
  changed = true;
720
- console.log(' ✅ .gitignore += logs/');
721
886
  }
722
887
 
723
888
  // ── 必须忽略:.autosnippet-drafts/(AI 草稿临时目录) ──
724
889
  if (!content.includes('.autosnippet-drafts')) {
725
890
  content += `\n# AutoSnippet AI 草稿(临时)\n.autosnippet-drafts/\n`;
726
891
  changed = true;
727
- console.log(' ✅ .gitignore += .autosnippet-drafts/');
728
892
  }
729
893
 
730
894
  // ── 必须忽略:_draft_*.md(AI Agent 在项目根目录创建的草稿文件) ──
731
895
  if (!content.includes('_draft_*.md')) {
732
896
  content += `\n# AutoSnippet AI 草稿文件(项目根目录临时文件)\n_draft_*.md\n`;
733
897
  changed = true;
734
- console.log(' ✅ .gitignore += _draft_*.md');
735
898
  }
736
899
 
737
900
  // ── 必须忽略:常见系统 / 编辑器临时文件 ──
738
901
  if (!content.includes('.DS_Store')) {
739
902
  content += `\n# macOS 元数据\n.DS_Store\n`;
740
903
  changed = true;
741
- console.log(' ✅ .gitignore += .DS_Store');
742
904
  }
743
905
  if (!content.includes('nohup.out')) {
744
906
  content += `nohup.out\n`;
745
907
  changed = true;
746
- console.log(' ✅ .gitignore += nohup.out');
747
908
  }
748
909
  if (!content.match(/\*\.sw[a-p]/)) {
749
910
  content += `*.sw[a-p]\n`;
750
911
  changed = true;
751
- console.log(' ✅ .gitignore += *.sw[a-p]');
752
912
  }
753
913
 
754
914
  // Skills 已迁移到 AutoSnippet/skills/(知识库目录内),自动跟随 Git
@@ -757,22 +917,22 @@ export class SetupService {
757
917
  if (content.includes('!.autosnippet/skills/')) {
758
918
  content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
759
919
  changed = true;
760
- console.log(' ✅ .gitignore: 移除旧版 .autosnippet/skills/ 规则(已迁移到 AutoSnippet/skills/)');
761
920
  }
762
921
 
763
922
  // ── 必须跟踪:AutoSnippet/(知识库子仓库)──
764
923
  // 如果用户误将 AutoSnippet/ 加入忽略,追加 !AutoSnippet/ 取消忽略
765
924
  const lines = content.split('\n');
766
- const hasIgnoreAS = lines.some(l => {
925
+ const hasIgnoreAS = lines.some((l) => {
767
926
  const t = l.trim();
768
- return (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!');
927
+ return (
928
+ (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!')
929
+ );
769
930
  });
770
- const hasNegation = lines.some(l => l.trim() === '!AutoSnippet/');
931
+ const hasNegation = lines.some((l) => l.trim() === '!AutoSnippet/');
771
932
 
772
933
  if (hasIgnoreAS && !hasNegation) {
773
934
  content += `\n# AutoSnippet 知识库必须入库(取消上方忽略)\n!AutoSnippet/\n`;
774
935
  changed = true;
775
- console.log(' ✅ .gitignore += !AutoSnippet/ (取消忽略)');
776
936
  }
777
937
 
778
938
  if (changed) {
@@ -789,7 +949,9 @@ export class SetupService {
789
949
  encoding: 'utf8',
790
950
  }).trim();
791
951
  } catch (e) {
792
- if (args[0] === 'commit' && e.status === 1) return '';
952
+ if (args[0] === 'commit' && e.status === 1) {
953
+ return '';
954
+ }
793
955
  throw e;
794
956
  }
795
957
  }