autosnippet 2.6.0 → 2.7.1

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 (78) hide show
  1. package/README.md +137 -65
  2. package/bin/api-server.js +5 -0
  3. package/bin/cli.js +6 -1
  4. package/dashboard/dist/assets/{icons-rnn04CvH.js → icons-B_Xg4B-s.js} +148 -88
  5. package/dashboard/dist/assets/index-BjfUm8p9.js +197 -0
  6. package/dashboard/dist/assets/index-CkIih2CC.css +1 -0
  7. package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
  8. package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
  9. package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
  10. package/dashboard/dist/index.html +6 -6
  11. package/lib/bootstrap.js +18 -1
  12. package/lib/cli/SetupService.js +86 -8
  13. package/lib/cli/UpgradeService.js +139 -2
  14. package/lib/core/ast/ProjectGraph.js +599 -0
  15. package/lib/core/gateway/GatewayActionRegistry.js +2 -2
  16. package/lib/domain/recipe/Recipe.js +3 -0
  17. package/lib/external/ai/AiProvider.js +83 -20
  18. package/lib/external/ai/providers/ClaudeProvider.js +208 -0
  19. package/lib/external/ai/providers/GoogleGeminiProvider.js +247 -1
  20. package/lib/external/ai/providers/OpenAiProvider.js +141 -0
  21. package/lib/external/mcp/McpServer.js +6 -1
  22. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
  23. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +657 -0
  24. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +160 -0
  25. package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
  26. package/lib/external/mcp/handlers/bootstrap.js +159 -1634
  27. package/lib/external/mcp/handlers/browse.js +1 -1
  28. package/lib/external/mcp/handlers/candidate.js +1 -33
  29. package/lib/external/mcp/handlers/skill.js +58 -17
  30. package/lib/external/mcp/tools.js +4 -3
  31. package/lib/http/middleware/requestLogger.js +23 -4
  32. package/lib/http/routes/ai.js +158 -2
  33. package/lib/http/routes/auth.js +3 -2
  34. package/lib/http/routes/candidates.js +49 -25
  35. package/lib/http/routes/commands.js +0 -8
  36. package/lib/http/routes/guardRules.js +1 -16
  37. package/lib/http/routes/recipes.js +4 -17
  38. package/lib/http/routes/search.js +11 -19
  39. package/lib/http/routes/skills.js +2 -0
  40. package/lib/http/routes/snippets.js +0 -33
  41. package/lib/http/routes/spm.js +37 -63
  42. package/lib/http/utils/routeHelpers.js +31 -0
  43. package/lib/infrastructure/config/Paths.js +12 -0
  44. package/lib/infrastructure/database/DatabaseConnection.js +6 -1
  45. package/lib/infrastructure/logging/Logger.js +86 -3
  46. package/lib/infrastructure/realtime/RealtimeService.js +2 -5
  47. package/lib/infrastructure/vector/JsonVectorAdapter.js +26 -1
  48. package/lib/injection/ServiceContainer.js +55 -2
  49. package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
  50. package/lib/service/candidate/CandidateFileWriter.js +72 -27
  51. package/lib/service/candidate/CandidateService.js +156 -10
  52. package/lib/service/chat/AnalystAgent.js +245 -0
  53. package/lib/service/chat/CandidateGuardrail.js +134 -0
  54. package/lib/service/chat/ChatAgent.js +1055 -167
  55. package/lib/service/chat/ContextWindow.js +730 -0
  56. package/lib/service/chat/ConversationStore.js +3 -0
  57. package/lib/service/chat/HandoffProtocol.js +181 -0
  58. package/lib/service/chat/Memory.js +3 -0
  59. package/lib/service/chat/ProducerAgent.js +293 -0
  60. package/lib/service/chat/ToolRegistry.js +149 -5
  61. package/lib/service/chat/tools.js +1404 -61
  62. package/lib/service/guard/ExclusionManager.js +2 -0
  63. package/lib/service/guard/RuleLearner.js +2 -0
  64. package/lib/service/quality/FeedbackCollector.js +2 -0
  65. package/lib/service/recipe/RecipeFileWriter.js +16 -1
  66. package/lib/service/recipe/RecipeStatsTracker.js +2 -0
  67. package/lib/service/skills/SignalCollector.js +33 -6
  68. package/lib/service/skills/SkillAdvisor.js +2 -1
  69. package/lib/service/skills/SkillHooks.js +13 -5
  70. package/lib/service/spm/SpmService.js +2 -2
  71. package/lib/shared/PathGuard.js +314 -0
  72. package/package.json +1 -1
  73. package/resources/native-ui/combined-window.swift +494 -0
  74. package/templates/copilot-instructions.md +20 -3
  75. package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
  76. package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
  77. package/dashboard/dist/assets/index-BBKa3Dgi.js +0 -195
  78. package/dashboard/dist/assets/index-DLsECfzW.css +0 -1
@@ -5,14 +5,14 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AutoSnippet Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-BBKa3Dgi.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BjfUm8p9.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-f83ah6cm.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-BotF760a.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
12
- <link rel="modulepreload" crossorigin href="/assets/icons-rnn04CvH.js">
13
- <link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-CJ2drQQb.js">
14
- <link rel="modulepreload" crossorigin href="/assets/react-markdown-CWxUbOf4.js">
15
- <link rel="stylesheet" crossorigin href="/assets/index-DLsECfzW.css">
12
+ <link rel="modulepreload" crossorigin href="/assets/icons-B_Xg4B-s.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-CVLHn9O5.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/react-markdown-BA6FB2NP.js">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CkIih2CC.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="root"></div>
package/lib/bootstrap.js CHANGED
@@ -11,6 +11,7 @@ import Gateway from './core/gateway/Gateway.js';
11
11
  import AuditLogger from './infrastructure/audit/AuditLogger.js';
12
12
  import AuditStore from './infrastructure/audit/AuditStore.js';
13
13
  import { SkillHooks } from './service/skills/SkillHooks.js';
14
+ import pathGuard from './shared/PathGuard.js';
14
15
 
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = path.dirname(__filename);
@@ -24,6 +25,22 @@ export class Bootstrap {
24
25
  this.components = {};
25
26
  }
26
27
 
28
+ /**
29
+ * 配置 PathGuard 路径安全守卫
30
+ * 必须在任何文件写操作前调用
31
+ * @param {string} projectRoot - 用户项目的绝对路径
32
+ * @param {string} [knowledgeBaseDir] - 知识库目录名(如 'AutoSnippet')
33
+ */
34
+ static configurePathGuard(projectRoot, knowledgeBaseDir) {
35
+ if (!pathGuard.configured && projectRoot) {
36
+ const packageRoot = path.resolve(__dirname, '..');
37
+ pathGuard.configure({ projectRoot, packageRoot, knowledgeBaseDir });
38
+ } else if (knowledgeBaseDir) {
39
+ // 已配置但知识库目录名可能后续才知道
40
+ pathGuard.setKnowledgeBaseDir(knowledgeBaseDir);
41
+ }
42
+ }
43
+
27
44
  /**
28
45
  * 初始化应用程序
29
46
  */
@@ -152,7 +169,7 @@ export class Bootstrap {
152
169
  this.components.auditLogger = auditLogger;
153
170
  logger.info('Audit system initialized');
154
171
 
155
- // Skill Hooks (扫描 skills/*/hooks.js + .autosnippet/skills/*/hooks.js)
172
+ // Skill Hooks (扫描 skills/*/hooks.js + AutoSnippet/skills/*/hooks.js)
156
173
  const skillHooks = new SkillHooks();
157
174
  await skillHooks.load();
158
175
  this.components.skillHooks = skillHooks;
@@ -4,8 +4,8 @@
4
4
  * 一键初始化 AutoSnippet V2 工作空间,5 步完成:
5
5
  *
6
6
  * Step 1 .autosnippet/ 运行时目录 + config.json + .gitignore
7
- * Step 2 AutoSnippet/ 子仓库(核心数据 + 权限能力)
8
- * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules)
7
+ * Step 2 AutoSnippet/ 子仓库(核心数据 + 权限能力 + skills/)
8
+ * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules + skills-template
9
9
  * Step 4 SQLite 数据库 + V1 数据迁移
10
10
  * Step 5 平台相关初始化(macOS → Xcode Snippets)
11
11
  *
@@ -17,6 +17,7 @@
17
17
  * ├─ constitution.yaml 权限宪法:角色 + 权限矩阵 + 治理规则 + 能力探测
18
18
  * ├─ boxspec.json 项目规格定义
19
19
  * ├─ recipes/*.md 统一知识实体(代码规范/模式/架构/调用链/数据流/...)
20
+ * ├─ skills/ Project Skills(冷启动自动生成 + 手动创建)
20
21
  * └─ README.md
21
22
  *
22
23
  * .autosnippet/ (运行时缓存,gitignored)
@@ -76,6 +77,7 @@ export class SetupService {
76
77
  this.coreDir = join(this.projectRoot, 'AutoSnippet');
77
78
  this.recipesDir = join(this.coreDir, 'recipes');
78
79
  this.candidatesDir = join(this.coreDir, 'candidates');
80
+ this.skillsDir = join(this.coreDir, 'skills');
79
81
  }
80
82
 
81
83
  /* ═══ 公共入口 ═══════════════════════════════════════ */
@@ -121,6 +123,7 @@ export class SetupService {
121
123
  console.log(' ├─ boxspec.json 项目规格');
122
124
  console.log(' ├─ recipes/*.md 统一知识实体 ← 受 git push 保护');
123
125
  console.log(' ├─ candidates/*.md 候选代码片段 ← 受 git push 保护');
126
+ console.log(' ├─ skills/ Project Skills(冷启动自动生成 + 手动创建)');
124
127
  console.log(' └─ *.json 运行数据(统计/反馈/规则学习/排除策略)');
125
128
  console.log(' .autosnippet/ 运行时缓存(gitignored)');
126
129
  console.log(' ├─ config.json 项目配置');
@@ -185,6 +188,9 @@ export class SetupService {
185
188
  // 确保 .autosnippet/ 在主仓库 .gitignore 中
186
189
  this._ensureGitignore();
187
190
 
191
+ // .env — AI 配置模板
192
+ this._ensureEnvFile();
193
+
188
194
  return { created: 'runtime' };
189
195
  }
190
196
 
@@ -195,7 +201,7 @@ export class SetupService {
195
201
  const alreadyRepo = existsSync(coreGit);
196
202
 
197
203
  // 创建目录结构
198
- for (const d of [this.coreDir, this.recipesDir, this.candidatesDir]) {
204
+ for (const d of [this.coreDir, this.recipesDir, this.candidatesDir, this.skillsDir]) {
199
205
  mkdirSync(d, { recursive: true });
200
206
  }
201
207
 
@@ -366,6 +372,8 @@ export class SetupService {
366
372
  '│ ├── naming-rules.md 代码规范示例',
367
373
  '│ ├── mvvm-arch.md 架构模式示例',
368
374
  '│ └── ... 代码模式/调用链/数据流/约束/风格/...',
375
+ '├── skills/ Project Skills(冷启动自动生成 + 手动创建)',
376
+ '│ └── <name>/SKILL.md AI Agent 知识增强文档',
369
377
  '└── README.md',
370
378
  '```',
371
379
  '',
@@ -429,8 +437,9 @@ export class SetupService {
429
437
  this._configureCursorMCP(mcpServerPath);
430
438
  this._copyCopilotInstructions();
431
439
  this._copyCursorRules();
440
+ this._copySkillsTemplate();
432
441
 
433
- return { configured: ['vscode-mcp', 'cursor-mcp', 'copilot-instructions', 'cursor-rules'] };
442
+ return { configured: ['vscode-mcp', 'cursor-mcp', 'copilot-instructions', 'cursor-rules', 'skills-template'] };
434
443
  }
435
444
 
436
445
  /** @private VSCode settings.json → Copilot MCP */
@@ -519,6 +528,23 @@ export class SetupService {
519
528
  console.log(' ✅ .cursor/rules/autosnippet-conventions.mdc');
520
529
  }
521
530
 
531
+ /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
532
+ _copySkillsTemplate() {
533
+ const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
534
+ if (!existsSync(src)) return;
535
+
536
+ const destDir = join(this.projectRoot, '.cursor', 'rules');
537
+ const dest = join(destDir, 'autosnippet-skills.mdc');
538
+ if (existsSync(dest) && !this.force) {
539
+ console.log(' ℹ️ skills template 已存在');
540
+ return;
541
+ }
542
+
543
+ mkdirSync(destDir, { recursive: true });
544
+ copyFileSync(src, dest);
545
+ console.log(' ✅ .cursor/rules/autosnippet-skills.mdc');
546
+ }
547
+
522
548
  /* ═══ Step 4: 数据库初始化 ═══════════════════════════ */
523
549
 
524
550
  async stepDatabase() {
@@ -617,6 +643,35 @@ export class SetupService {
617
643
 
618
644
  /* ═══ Helpers ════════════════════════════════════════ */
619
645
 
646
+ /**
647
+ * @private 在项目根目录创建 .env 文件(从 .env.example 复制)
648
+ * 如果 .env 已存在则跳过并提示用户手动配置。
649
+ */
650
+ _ensureEnvFile() {
651
+ const envPath = join(this.projectRoot, '.env');
652
+ if (existsSync(envPath)) {
653
+ console.log(' ℹ️ .env 已存在,跳过写入。如需配置 AI,请手动编辑或通过 Dashboard 设置');
654
+ return;
655
+ }
656
+
657
+ const examplePath = join(REPO_ROOT, '.env.example');
658
+ if (existsSync(examplePath)) {
659
+ copyFileSync(examplePath, envPath);
660
+ } else {
661
+ // fallback: .env.example 缺失时写入最小模板
662
+ writeFileSync(envPath, [
663
+ '# AutoSnippet AI 配置(由 asd setup 自动生成)',
664
+ '# 完整配置说明见 .env.example',
665
+ '',
666
+ 'ASD_AI_PROVIDER=google',
667
+ 'ASD_AI_MODEL=gemini-2.0-flash',
668
+ '# ASD_GOOGLE_API_KEY=',
669
+ '',
670
+ ].join('\n'));
671
+ }
672
+ console.log(' ✅ .env(已从 .env.example 复制,请填写 API Key 后使用)');
673
+ }
674
+
620
675
  /** @private 确保项目 .gitignore 正确配置 AutoSnippet 相关规则 */
621
676
  _ensureGitignore() {
622
677
  const giPath = join(this.projectRoot, '.gitignore');
@@ -649,11 +704,34 @@ export class SetupService {
649
704
  console.log(' ✅ .gitignore += !.autosnippet/config.json');
650
705
  }
651
706
 
652
- // ── 必须跟踪:.autosnippet/skills/(项目级 Skill 文档) ──
653
- if (!content.includes('!.autosnippet/skills/')) {
654
- content += `!.autosnippet/skills/\n`;
707
+ // ── 必须忽略:.env(包含 API Key 等敏感信息) ──
708
+ if (!content.includes('.env') || (!content.match(/^\.env$/m) && !content.match(/^\.env\s/m))) {
709
+ content += `\n# AutoSnippet 环境变量(含 API Key,不入库)\n.env\n`;
710
+ changed = true;
711
+ console.log(' ✅ .gitignore += .env');
712
+ }
713
+
714
+ // ── 必须忽略:logs/(winston 运行日志,可达数十 MB) ──
715
+ if (!content.match(/^logs\/?$/m)) {
716
+ content += `\n# AutoSnippet 运行日志\nlogs/\n`;
717
+ changed = true;
718
+ console.log(' ✅ .gitignore += logs/');
719
+ }
720
+
721
+ // ── 必须忽略:.autosnippet-drafts/(AI 草稿临时目录) ──
722
+ if (!content.includes('.autosnippet-drafts')) {
723
+ content += `\n# AutoSnippet AI 草稿(临时)\n.autosnippet-drafts/\n`;
724
+ changed = true;
725
+ console.log(' ✅ .gitignore += .autosnippet-drafts/');
726
+ }
727
+
728
+ // Skills 已迁移到 AutoSnippet/skills/(知识库目录内),自动跟随 Git
729
+
730
+ // ── 清理旧版本的 .autosnippet/skills/ negation(已迁移,不再需要)──
731
+ if (content.includes('!.autosnippet/skills/')) {
732
+ content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
655
733
  changed = true;
656
- console.log(' ✅ .gitignore += !.autosnippet/skills/');
734
+ console.log(' ✅ .gitignore: 移除旧版 .autosnippet/skills/ 规则(已迁移到 AutoSnippet/skills/)');
657
735
  }
658
736
 
659
737
  // ── 必须跟踪:AutoSnippet/(知识库子仓库)──
@@ -4,14 +4,17 @@
4
4
  * 当 AutoSnippet 发布新版本后,老用户执行 `asd upgrade` 即可更新:
5
5
  * ① MCP 配置(.cursor/mcp.json + .vscode/settings.json)
6
6
  * ② Cursor Skills(.cursor/skills/)
7
- * ③ Cursor Rules(.cursor/rules/autosnippet-conventions.mdc)
7
+ * ③ Cursor Rules(.cursor/rules/autosnippet-conventions.mdc + autosnippet-skills.mdc
8
8
  * ④ Copilot Instructions(.github/copilot-instructions.md)
9
+ * ⑤ Constitution(AutoSnippet/constitution.yaml)
10
+ * ⑥ .gitignore(升级规则 + 清理旧版本残留)
11
+ * ⑦ Skills 路径迁移(.autosnippet/skills/ → AutoSnippet/skills/)
9
12
  *
10
13
  * 不会重建数据库、子仓库或运行时目录。
11
14
  */
12
15
 
13
16
  import {
14
- existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync,
17
+ existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, readdirSync,
15
18
  } from 'fs';
16
19
  import { join, resolve, dirname } from 'path';
17
20
  import { fileURLToPath } from 'url';
@@ -39,8 +42,12 @@ export class UpgradeService {
39
42
  }
40
43
  if (!skillsOnly && !mcpOnly) {
41
44
  results.push(this._upgradeCursorRules());
45
+ results.push(this._upgradeSkillsTemplate());
42
46
  results.push(this._upgradeCopilotInstructions());
43
47
  results.push(this._upgradeConstitution());
48
+ results.push(this._upgradeGitignore());
49
+ results.push(this._migrateSkillsPath());
50
+ results.push(this._ensureSkillsDir());
44
51
  }
45
52
 
46
53
  console.log('');
@@ -217,6 +224,136 @@ export class UpgradeService {
217
224
  console.log(' cd AutoSnippet && git add constitution.yaml && git commit -m "Upgrade constitution" && git push');
218
225
  }
219
226
  }
227
+ /* ═══ Skills Template ════════════════════════════════ */
228
+
229
+ _upgradeSkillsTemplate() {
230
+ console.log('[Skills Template] 更新 autosnippet-skills.mdc...');
231
+
232
+ const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
233
+ if (!existsSync(src)) {
234
+ console.log(' ⚠️ 模板不存在,跳过');
235
+ return;
236
+ }
237
+
238
+ const destDir = join(this.projectRoot, '.cursor', 'rules');
239
+ const dest = join(destDir, 'autosnippet-skills.mdc');
240
+ mkdirSync(destDir, { recursive: true });
241
+ copyFileSync(src, dest);
242
+ console.log(' ✅ .cursor/rules/autosnippet-skills.mdc');
243
+ }
244
+
245
+ /* ═══ .gitignore ════════════════════════════════════ */
246
+
247
+ _upgradeGitignore() {
248
+ console.log('[Gitignore] 更新 .gitignore 规则...');
249
+
250
+ const giPath = join(this.projectRoot, '.gitignore');
251
+ if (!existsSync(giPath)) {
252
+ console.log(' ℹ️ .gitignore 不存在,跳过');
253
+ return;
254
+ }
255
+
256
+ let content = readFileSync(giPath, 'utf8');
257
+ let changed = false;
258
+
259
+ // v2.4.0 迁移:旧格式 ".autosnippet/" → 新格式 ".autosnippet/*"
260
+ if (content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
261
+ content = content.replace(/^\.autosnippet\/$/m, '.autosnippet/*');
262
+ changed = true;
263
+ console.log(' ✅ .autosnippet/ → .autosnippet/*(升级为精细忽略)');
264
+ }
265
+
266
+ // 确保有 .autosnippet/*
267
+ if (!content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
268
+ content += `\n# AutoSnippet 运行时缓存(不入库)\n.autosnippet/*\n`;
269
+ changed = true;
270
+ console.log(' ✅ += .autosnippet/*');
271
+ }
272
+
273
+ // 确保 config.json 跟踪
274
+ if (!content.includes('!.autosnippet/config.json')) {
275
+ content += `!.autosnippet/config.json\n`;
276
+ changed = true;
277
+ console.log(' ✅ += !.autosnippet/config.json');
278
+ }
279
+
280
+ // 清理旧版本的 .autosnippet/skills/ negation(已迁移到 AutoSnippet/skills/)
281
+ if (content.includes('!.autosnippet/skills/')) {
282
+ content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
283
+ changed = true;
284
+ console.log(' ✅ 移除旧版 .autosnippet/skills/ 规则(已迁移到 AutoSnippet/skills/)');
285
+ }
286
+
287
+ // 确保 AutoSnippet/ 不被忽略
288
+ const lines = content.split('\n');
289
+ const hasIgnoreAS = lines.some(l => {
290
+ const t = l.trim();
291
+ return (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!');
292
+ });
293
+ const hasNegation = lines.some(l => l.trim() === '!AutoSnippet/');
294
+ if (hasIgnoreAS && !hasNegation) {
295
+ content += `\n# AutoSnippet 知识库必须入库(取消上方忽略)\n!AutoSnippet/\n`;
296
+ changed = true;
297
+ console.log(' ✅ += !AutoSnippet/ (取消忽略)');
298
+ }
299
+
300
+ if (changed) {
301
+ writeFileSync(giPath, content);
302
+ } else {
303
+ console.log(' ℹ️ .gitignore 已是最新版本');
304
+ }
305
+ }
306
+
307
+ /* ═══ Skills 路径迁移 ═══════════════════════════════ */
308
+
309
+ _migrateSkillsPath() {
310
+ const oldSkillsDir = join(this.projectRoot, '.autosnippet', 'skills');
311
+ const newSkillsDir = join(this.projectRoot, 'AutoSnippet', 'skills');
312
+
313
+ if (!existsSync(oldSkillsDir)) return;
314
+ if (!existsSync(join(this.projectRoot, 'AutoSnippet'))) return;
315
+
316
+ console.log('[Migration] 迁移 Skills: .autosnippet/skills/ → AutoSnippet/skills/...');
317
+
318
+ try {
319
+ mkdirSync(newSkillsDir, { recursive: true });
320
+ const entries = readdirSync(oldSkillsDir, { withFileTypes: true });
321
+ let migrated = 0;
322
+
323
+ for (const entry of entries) {
324
+ if (!entry.isDirectory()) continue;
325
+ const src = join(oldSkillsDir, entry.name);
326
+ const dest = join(newSkillsDir, entry.name);
327
+ if (existsSync(dest)) {
328
+ console.log(` ℹ️ ${entry.name} 已存在于新路径,跳过`);
329
+ continue;
330
+ }
331
+ // 复制目录
332
+ execSync(`cp -r "${src}" "${dest}"`, { stdio: 'pipe' });
333
+ migrated++;
334
+ }
335
+
336
+ if (migrated > 0) {
337
+ console.log(` ✅ 已迁移 ${migrated} 个 Skill 到 AutoSnippet/skills/`);
338
+ console.log(' 💡 确认迁移无误后可删除旧目录: rm -rf .autosnippet/skills/');
339
+ } else {
340
+ console.log(' ℹ️ 无需迁移(所有 Skill 已存在于新路径)');
341
+ }
342
+ } catch (e) {
343
+ console.error(` ❌ 迁移失败: ${e.message}`);
344
+ }
345
+ }
346
+
347
+ /* ═══ 确保 Skills 目录存在 ══════════════════════════ */
348
+
349
+ _ensureSkillsDir() {
350
+ const skillsDir = join(this.projectRoot, 'AutoSnippet', 'skills');
351
+ if (!existsSync(join(this.projectRoot, 'AutoSnippet'))) return;
352
+ if (existsSync(skillsDir)) return;
353
+
354
+ mkdirSync(skillsDir, { recursive: true });
355
+ console.log('[Skills] ✅ 创建 AutoSnippet/skills/ 目录');
356
+ }
220
357
  }
221
358
 
222
359
  export default UpgradeService;