autosnippet 3.2.3 → 3.2.6

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 (64) hide show
  1. package/README.md +2 -4
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +3 -0
  4. package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
  5. package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
  6. package/dashboard/dist/index.html +2 -2
  7. package/lib/cli/CliLogger.js +78 -0
  8. package/lib/cli/SetupService.js +9 -719
  9. package/lib/cli/UpgradeService.js +23 -398
  10. package/lib/cli/deploy/FileDeployer.js +562 -0
  11. package/lib/cli/deploy/FileManifest.js +272 -0
  12. package/lib/external/mcp/McpServer.js +22 -26
  13. package/lib/external/mcp/autoApproveInjector.js +1 -0
  14. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
  19. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
  20. package/lib/external/mcp/handlers/consolidated.js +8 -9
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
  22. package/lib/external/mcp/handlers/guard.js +283 -5
  23. package/lib/external/mcp/handlers/task.js +183 -9
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/routes/task.js +55 -0
  26. package/lib/service/chat/AnalystAgent.js +12 -12
  27. package/lib/service/chat/ChatAgent.js +227 -545
  28. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  29. package/lib/service/chat/ContextWindow.js +2 -296
  30. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  31. package/lib/service/chat/ExplorationTracker.js +1262 -0
  32. package/lib/service/chat/HandoffProtocol.js +8 -9
  33. package/lib/service/chat/Memory.js +4 -0
  34. package/lib/service/chat/ProducerAgent.js +9 -6
  35. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  36. package/lib/service/chat/ReasoningTrace.js +182 -0
  37. package/lib/service/chat/WorkingMemory.js +4 -0
  38. package/lib/service/chat/memory/ActiveContext.js +910 -0
  39. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  40. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  41. package/lib/service/chat/memory/SessionStore.js +896 -0
  42. package/lib/service/chat/memory/index.js +13 -0
  43. package/lib/service/chat/tools/ast-graph.js +17 -16
  44. package/lib/service/cursor/AgentInstructionsGenerator.js +75 -40
  45. package/lib/service/cursor/FileProtection.js +4 -1
  46. package/lib/service/guard/GuardCheckEngine.js +10 -3
  47. package/lib/service/task/TaskGraphService.js +3 -3
  48. package/lib/shared/LanguageService.js +2 -1
  49. package/package.json +1 -1
  50. package/skills/autosnippet-intent/SKILL.md +1 -3
  51. package/skills/autosnippet-recipes/SKILL.md +1 -3
  52. package/templates/claude-code/commands/prime.md +19 -0
  53. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  54. package/templates/claude-code/settings.json +21 -0
  55. package/templates/copilot-instructions.md +66 -177
  56. package/templates/cursor-hooks/commands/prime.md +12 -0
  57. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  58. package/templates/cursor-hooks/hooks.json +11 -0
  59. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  60. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  61. package/lib/external/mcp/handlers/decide.js +0 -109
  62. package/lib/external/mcp/handlers/ready.js +0 -42
  63. package/lib/service/chat/ReasoningLayer.js +0 -888
  64. package/templates/claude-hooks.yaml +0 -19
@@ -48,13 +48,11 @@ import {
48
48
  existsSync,
49
49
  mkdirSync,
50
50
  readdirSync,
51
- readFileSync,
52
- realpathSync,
53
51
  writeFileSync,
54
52
  } from 'node:fs';
55
53
  import { dirname, join, resolve } from 'node:path';
56
54
  import { fileURLToPath } from 'node:url';
57
- import { checkWriteSafety } from '../service/cursor/FileProtection.js';
55
+ import { FileDeployer } from './deploy/FileDeployer.js';
58
56
 
59
57
  const __filename = fileURLToPath(import.meta.url);
60
58
  const __dirname = dirname(__filename);
@@ -172,9 +170,6 @@ export class SetupService {
172
170
  writeFileSync(configPath, JSON.stringify(config, null, 2));
173
171
  }
174
172
 
175
- // 确保 .autosnippet/ 在主仓库 .gitignore 中
176
- this._ensureGitignore();
177
-
178
173
  // .env — AI 配置模板
179
174
  this._ensureEnvFile();
180
175
 
@@ -433,633 +428,21 @@ export class SetupService {
433
428
  /* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
434
429
 
435
430
  stepIDE() {
436
- const configured = [];
437
-
438
- // MCP 配置(总是写入/合并,不会跳过)
439
- this._configureVSCodeMCP();
440
- configured.push('vscode-mcp');
441
- this._configureCursorMCP();
442
- configured.push('cursor-mcp');
443
-
444
- // 模板文件(可能因已存在/用户文件而跳过)
445
- if (this._copyCopilotInstructions()) configured.push('copilot-instructions');
446
- this._ensureTaskGraphInstructions();
447
- if (this._generateAgentInstructionFiles()) configured.push('agent-instructions');
448
- if (this._copyCursorRules()) configured.push('cursor-rules');
449
- if (this._copySkillsTemplate()) configured.push('skills-template');
450
- if (this._copyCursorWorkflow()) configured.push('cursor-workflow');
451
- if (this._copyClaudeHooks()) configured.push('claude-hooks');
452
- if (this._copyGuardCI()) configured.push('guard-ci');
453
- if (this._installPreCommitHook()) configured.push('pre-commit-hook');
454
- // NOTE: .qoder/ .trae/ 不再自动创建,用户可通过 `asd mirror` 按需生成
455
-
456
- const extResult = this._installVSCodeExtension();
457
- if (extResult) {
458
- configured.push(...extResult);
459
- }
460
- return { configured };
461
- }
462
-
463
- /**
464
- * @private 构建 + 安装 VSCode Extension (.vsix)
465
- *
466
- * 流程:
467
- * 1. 编译 TypeScript(tsc)
468
- * 2. 打包 .vsix(vsce package)
469
- * 3. 探测所有可用的 VS Code 兼容 IDE CLI
470
- * 4. 对每个 IDE 执行 --install-extension
471
- *
472
- * 支持:VS Code / Cursor / Codex 等基于 VS Code 的 IDE。
473
- * 找不到任何 IDE CLI 时静默跳过,不阻断 setup 流程。
474
- *
475
- * @returns {string[]|null} 安装成功的 IDE 列表, 或 null
476
- */
477
- _installVSCodeExtension() {
478
- const extDir = join(REPO_ROOT, 'resources', 'vscode-ext');
479
- const pkgJson = join(extDir, 'package.json');
480
-
481
- if (!existsSync(pkgJson)) {
482
- return null;
483
- }
484
-
485
- // ── 1. 编译 TypeScript ──
486
- try {
487
- execSync('npx tsc -p ./tsconfig.json', { cwd: extDir, stdio: 'pipe' });
488
- } catch (e) {
489
- console.error(` ⚠ VSCode Extension 编译失败: ${e.stderr?.toString().trim() || e.message}`);
490
- return null;
491
- }
492
-
493
- // ── 2. 打包 .vsix ──
494
- let vsixPath;
495
- try {
496
- const out = execSync('npx @vscode/vsce package --no-dependencies 2>&1', {
497
- cwd: extDir,
498
- stdio: 'pipe',
499
- encoding: 'utf8',
500
- });
501
- // 从输出中提取 vsix 文件路径: "DONE Packaged: /path/to/autosnippet-0.1.0.vsix ..."
502
- const m = out.match(/Packaged:\s*(.+\.vsix)/);
503
- if (m) {
504
- vsixPath = m[1].trim();
505
- }
506
- } catch (e) {
507
- console.error(` ⚠ VSCode Extension 打包失败: ${e.message}`);
508
- return null;
509
- }
510
-
511
- // fallback: 扫描目录找 .vsix 文件
512
- if (!vsixPath || !existsSync(vsixPath)) {
513
- try {
514
- const files = readdirSync(extDir).filter((f) => f.endsWith('.vsix'));
515
- if (files.length > 0) {
516
- // 取最新的
517
- files.sort().reverse();
518
- vsixPath = join(extDir, files[0]);
519
- }
520
- } catch {
521
- /* ignore */
522
- }
523
- }
524
-
525
- if (!vsixPath || !existsSync(vsixPath)) {
526
- console.error(' ⚠ 找不到 .vsix 文件,跳过 Extension 安装');
527
- return null;
528
- }
529
-
530
- // ── 3. 探测可用的 IDE CLI ──
531
- const cliCandidates = this._discoverIDEClis();
532
- if (cliCandidates.length === 0) {
533
- console.error(' ⚠ 未找到 VS Code / Cursor 等 IDE CLI,跳过 Extension 安装');
534
- console.error(' 提示: 手动安装 → 在 IDE 中 Cmd+Shift+P → "Install from VSIX"');
535
- console.error(` 文件: ${vsixPath}`);
536
- return null;
537
- }
538
-
539
- // ── 4. 逐个安装 ──
540
- const installed = [];
541
- for (const { name, cli } of cliCandidates) {
542
- try {
543
- execSync(`"${cli}" --install-extension "${vsixPath}" --force 2>&1`, {
544
- stdio: 'pipe',
545
- encoding: 'utf8',
546
- timeout: 30_000,
547
- });
548
- installed.push(`vscode-ext:${name}`);
549
- } catch (e) {
550
- console.error(` ⚠ ${name} Extension 安装失败: ${e.message}`);
551
- }
552
- }
553
- return installed.length > 0 ? installed : null;
554
- }
555
-
556
- /**
557
- * @private 探测系统中所有 VS Code 兼容 IDE 的 CLI 路径
558
- * @returns {{ name: string, cli: string }[]}
559
- */
560
- _discoverIDEClis() {
561
- const candidates = [];
562
-
563
- // 1. PATH 中的命令(跨平台: which → Unix, where → Windows)
564
- const whichCmd = process.platform === 'win32' ? 'where' : 'which';
565
- for (const cmd of ['code', 'cursor', 'codex', 'code-insiders']) {
566
- try {
567
- const p = execSync(`${whichCmd} ${cmd}`, {
568
- encoding: 'utf8',
569
- stdio: ['pipe', 'pipe', 'ignore'],
570
- })
571
- .trim()
572
- .split(/\r?\n/)[0];
573
- if (p) {
574
- candidates.push({ name: cmd, cli: p });
575
- }
576
- } catch {
577
- /* not in PATH */
578
- }
579
- }
580
-
581
- // 2. 平台特定的 IDE 安装路径
582
- if (process.platform === 'darwin') {
583
- // macOS: /Applications/xxx.app/Contents/Resources/app/bin/
584
- const appPaths = [
585
- { name: 'vscode', app: '/Applications/Visual Studio Code.app', bin: 'code' },
586
- {
587
- name: 'vscode-insiders',
588
- app: '/Applications/Visual Studio Code - Insiders.app',
589
- bin: 'code-insiders',
590
- },
591
- { name: 'cursor', app: '/Applications/Cursor.app', bin: 'cursor' },
592
- { name: 'codex', app: '/Applications/Codex.app', bin: 'codex' },
593
- ];
594
- for (const { name, app, bin } of appPaths) {
595
- const cli = join(app, 'Contents', 'Resources', 'app', 'bin', bin);
596
- if (existsSync(cli) && !candidates.some((c) => c.name === name)) {
597
- candidates.push({ name, cli });
598
- }
599
- }
600
- } else if (process.platform === 'win32') {
601
- // Windows: %LOCALAPPDATA%\Programs\xxx
602
- const localAppData =
603
- process.env.LOCALAPPDATA || join(process.env.USERPROFILE || '', 'AppData', 'Local');
604
- const winPaths = [
605
- { name: 'vscode', dir: 'Microsoft VS Code', bin: 'bin/code.cmd' },
606
- {
607
- name: 'vscode-insiders',
608
- dir: 'Microsoft VS Code Insiders',
609
- bin: 'bin/code-insiders.cmd',
610
- },
611
- { name: 'cursor', dir: 'cursor', bin: 'cursor.exe' },
612
- ];
613
- for (const { name, dir, bin } of winPaths) {
614
- const cli = join(localAppData, 'Programs', dir, bin);
615
- if (existsSync(cli) && !candidates.some((c) => c.name === name)) {
616
- candidates.push({ name, cli });
617
- }
618
- }
619
- } else {
620
- // Linux: 常见安装路径
621
- const linuxPaths = [
622
- { name: 'vscode', bin: '/usr/share/code/bin/code' },
623
- { name: 'vscode', bin: '/usr/bin/code' },
624
- { name: 'cursor', bin: '/usr/bin/cursor' },
625
- { name: 'cursor', bin: `${process.env.HOME || ''}/.local/bin/cursor` },
626
- ];
627
- for (const { name, bin } of linuxPaths) {
628
- if (existsSync(bin) && !candidates.some((c) => c.name === name)) {
629
- candidates.push({ name, cli: bin });
630
- }
631
- }
632
- }
633
-
634
- // 3. 去重(同一个二进制不重复安装)— 使用 Node.js 原生 realpathSync 替代 readlink -f
635
- const seen = new Set();
636
- return candidates.filter((c) => {
637
- let realPath = c.cli;
638
- try {
639
- realPath = realpathSync(c.cli);
640
- } catch {
641
- /* use as-is */
642
- }
643
- if (seen.has(realPath)) {
644
- return false;
645
- }
646
- seen.add(realPath);
647
- return true;
431
+ const deployer = new FileDeployer({
432
+ projectRoot: this.projectRoot,
433
+ force: this.force,
648
434
  });
649
- }
650
-
651
- /** @private .vscode/mcp.json → VSCode MCP (新标准格式) */
652
- _configureVSCodeMCP() {
653
- const vscodeDir = join(this.projectRoot, '.vscode');
654
- const mcpConfigPath = join(vscodeDir, 'mcp.json');
655
- mkdirSync(vscodeDir, { recursive: true });
435
+ const { deployed, skipped, errors } = deployer.deployAll('setup');
656
436
 
657
- let config = {};
658
- if (existsSync(mcpConfigPath)) {
659
- try {
660
- config = JSON.parse(readFileSync(mcpConfigPath, 'utf8'));
661
- } catch {
662
- /* ignore */
437
+ if (errors.length > 0) {
438
+ for (const { id, error } of errors) {
439
+ console.error(` ⚠ ${id}: ${error}`);
663
440
  }
664
441
  }
665
442
 
666
- if (!config.servers) {
667
- config.servers = {};
668
- }
669
- config.servers.autosnippet = {
670
- type: 'stdio',
671
- command: 'asd-mcp',
672
- env: {
673
- ASD_PROJECT_DIR: this.projectRoot,
674
- },
675
- };
676
-
677
- writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
443
+ return { configured: deployed };
678
444
  }
679
445
 
680
- /**
681
- * @private .cursor/mcp.json
682
- *
683
- * 初始 setup 时不写入 autoApprove — 让用户首次使用时亲眼看到并手动授权每个工具。
684
- * 首次 `autosnippet_bootstrap` 成功后,bootstrap-external.js 会自动注入 autoApprove,
685
- * 后续调用无需再点击授权。升级时 UpgradeService 也会注入。
686
- */
687
- _configureCursorMCP() {
688
- const cursorDir = join(this.projectRoot, '.cursor');
689
- const configPath = join(cursorDir, 'mcp.json');
690
- mkdirSync(cursorDir, { recursive: true });
691
-
692
- let existing = {};
693
- if (existsSync(configPath)) {
694
- try {
695
- existing = JSON.parse(readFileSync(configPath, 'utf8'));
696
- } catch {
697
- /* ignore */
698
- }
699
- }
700
-
701
- if (!existing.mcpServers) {
702
- existing.mcpServers = {};
703
- }
704
- existing.mcpServers.autosnippet = {
705
- command: 'asd-mcp',
706
- env: {
707
- ASD_PROJECT_DIR: this.projectRoot,
708
- },
709
- };
710
-
711
- writeFileSync(configPath, JSON.stringify(existing, null, 2));
712
- }
713
-
714
- /** @private .github/copilot-instructions.md (static template fallback) */
715
- _copyCopilotInstructions() {
716
- const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
717
- if (!existsSync(src)) {
718
- return false;
719
- }
720
-
721
- const destDir = join(this.projectRoot, '.github');
722
- const dest = join(destDir, 'copilot-instructions.md');
723
- if (existsSync(dest) && !this.force) {
724
- return false;
725
- }
726
-
727
- // 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
728
- const { canWrite } = checkWriteSafety(dest);
729
- if (!canWrite) {
730
- return false;
731
- }
732
-
733
- mkdirSync(destDir, { recursive: true });
734
- copyFileSync(src, dest);
735
- return true;
736
- }
737
-
738
- /** @private TaskGraph 决策记忆激活段落注入 */
739
- _ensureTaskGraphInstructions() {
740
- const githubDir = join(this.projectRoot, '.github');
741
- const filePath = join(githubDir, 'copilot-instructions.md');
742
-
743
- // 使用 HTML 注释标记界定段落,避免标题被编辑后重复追加(D3)
744
- const BEGIN_MARKER = '<!-- autosnippet:begin -->';
745
- const END_MARKER = '<!-- autosnippet:end -->';
746
-
747
- const autosnippetSection = `
748
- ${BEGIN_MARKER}
749
- ## AutoSnippet
750
-
751
- This project uses AutoSnippet for knowledge management and decision tracking.
752
- In VS Code Agent Mode, type \`#asd\` before your message to activate project memory.
753
-
754
- ### ⚠️ MANDATORY — Call \`autosnippet_ready\` FIRST
755
-
756
- Before doing ANY work, you MUST call \`autosnippet_ready()\` at the start of every conversation.
757
- This loads **active team decisions** and **pending tasks** that you must respect.
758
- Skipping this step will cause you to contradict team agreements.
759
-
760
- \`\`\`
761
- autosnippet_ready() → Load decisions + context (ALWAYS FIRST)
762
- autosnippet_decide(record) → Save new team agreement
763
- autosnippet_task(create/close) → Task lifecycle
764
- \`\`\`
765
- ${END_MARKER}
766
- `;
767
-
768
- if (!existsSync(githubDir)) {
769
- mkdirSync(githubDir, { recursive: true });
770
- }
771
-
772
- if (existsSync(filePath)) {
773
- const existing = readFileSync(filePath, 'utf8');
774
- // 通过 HTML 注释标记检测,不依赖可编辑的标题文本
775
- if (existing.includes(BEGIN_MARKER)) {
776
- // 替换现有段落(支持升级)
777
- const updated = existing.replace(
778
- new RegExp(`${BEGIN_MARKER}[\\s\\S]*?${END_MARKER}`),
779
- autosnippetSection.trim()
780
- );
781
- writeFileSync(filePath, updated);
782
- return { action: 'updated' };
783
- }
784
- // 追加到末尾
785
- writeFileSync(filePath, existing + '\n' + autosnippetSection);
786
- return { action: 'appended' };
787
- }
788
-
789
- writeFileSync(filePath, autosnippetSection.trimStart());
790
- return { action: 'created' };
791
- }
792
-
793
- /**
794
- * @private 生成 AGENTS.md + CLAUDE.md 静态骨架
795
- * setup 阶段没有知识库数据,所以只生成指向 MCP 的基础版本。
796
- * bootstrap / delivery pipeline 完成后会用动态版本覆盖。
797
- */
798
- _generateAgentInstructionFiles() {
799
- const projectName = this.projectRoot.split('/').pop();
800
- let wrote = false;
801
-
802
- // AGENTS.md
803
- const agentsPath = join(this.projectRoot, 'AGENTS.md');
804
- if (!existsSync(agentsPath) || this.force) {
805
- const { canWrite } = checkWriteSafety(agentsPath);
806
- if (canWrite) {
807
- const agentsContent = [
808
- `# ${projectName} — Agent Instructions`,
809
- '',
810
- '> Auto-generated by AutoSnippet.',
811
- '',
812
- '## AutoSnippet Integration',
813
- '',
814
- 'This project uses **AutoSnippet** for knowledge management and decision tracking.',
815
- '',
816
- '### MCP Tools',
817
- '',
818
- '- `autosnippet_search` — Search knowledge',
819
- '- `autosnippet_knowledge` — Browse/get recipes',
820
- '- `autosnippet_submit_knowledge` — Submit candidate',
821
- '- `autosnippet_guard` — Code compliance check',
822
- '- `autosnippet_health` — Service health & KB stats',
823
- '- `autosnippet_ready` — Session entry point (loads decisions + tasks)',
824
- '- `autosnippet_decide` — Decision management (record/revise/unpin)',
825
- '- `autosnippet_task` — Task CRUD (create/claim/close/fail/defer)',
826
- '',
827
- '### VS Code Agent Mode',
828
- '',
829
- 'Type `#asd` before your message in Agent Mode to activate project memory.',
830
- '',
831
- '### Constraints',
832
- '',
833
- '1. Do NOT modify knowledge base files directly.',
834
- '2. Create or update knowledge only through MCP tools.',
835
- '',
836
- ].join('\n');
837
- writeFileSync(agentsPath, agentsContent);
838
- wrote = true;
839
- } else {
840
- }
841
- }
842
-
843
- // CLAUDE.md
844
- const claudePath = join(this.projectRoot, 'CLAUDE.md');
845
- if (!existsSync(claudePath) || this.force) {
846
- const { canWrite } = checkWriteSafety(claudePath);
847
- if (canWrite) {
848
- const claudeContent = [
849
- `# ${projectName} — Claude Code Instructions`,
850
- '',
851
- '> Auto-generated by AutoSnippet.',
852
- '',
853
- '## AutoSnippet Integration',
854
- '',
855
- 'This project uses AutoSnippet for knowledge management and decision tracking.',
856
- '',
857
- '### MCP Tools',
858
- '',
859
- '- `autosnippet_search` — Search knowledge',
860
- '- `autosnippet_knowledge` — Browse/get recipes',
861
- '- `autosnippet_submit_knowledge` — Submit candidate',
862
- '- `autosnippet_guard` — Code compliance check',
863
- '- `autosnippet_health` — Service health & KB stats',
864
- '- `autosnippet_ready` — Session entry point (loads decisions + tasks)',
865
- '- `autosnippet_decide` — Decision management (record/revise/unpin)',
866
- '- `autosnippet_task` — Task CRUD (create/claim/close/fail/defer)',
867
- '',
868
- '### Claude + VS Code',
869
- '',
870
- 'Claude Code uses MCP tools directly. Run `asd server` to start the API server.',
871
- 'In VS Code Agent Mode, type `#asd` before your message to activate project memory.',
872
- '',
873
- '### Constraints',
874
- '',
875
- '1. Do NOT modify knowledge base files directly.',
876
- '2. Create or update knowledge only through MCP tools.',
877
- '',
878
- ].join('\n');
879
- writeFileSync(claudePath, claudeContent);
880
- wrote = true;
881
- } else {
882
- }
883
- }
884
- return wrote;
885
- }
886
-
887
- /** @private .cursor/rules/autosnippet-conventions.mdc */
888
- _copyCursorRules() {
889
- const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
890
- if (!existsSync(src)) {
891
- return false;
892
- }
893
-
894
- const destDir = join(this.projectRoot, '.cursor', 'rules');
895
- const dest = join(destDir, 'autosnippet-conventions.mdc');
896
- if (existsSync(dest) && !this.force) {
897
- return false;
898
- }
899
-
900
- mkdirSync(destDir, { recursive: true });
901
- copyFileSync(src, dest);
902
- return true;
903
- }
904
-
905
- /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
906
- _copySkillsTemplate() {
907
- const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
908
- if (!existsSync(src)) {
909
- return false;
910
- }
911
-
912
- const destDir = join(this.projectRoot, '.cursor', 'rules');
913
- const dest = join(destDir, 'autosnippet-skills.mdc');
914
- if (existsSync(dest) && !this.force) {
915
- return false;
916
- }
917
-
918
- mkdirSync(destDir, { recursive: true });
919
- copyFileSync(src, dest);
920
- return true;
921
- }
922
- /** @private .cursor/rules/autosnippet-workflow.mdc — TaskGraph & Guard 工作流 */
923
- _copyCursorWorkflow() {
924
- const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-workflow.mdc');
925
- if (!existsSync(src)) {
926
- return false;
927
- }
928
-
929
- const destDir = join(this.projectRoot, '.cursor', 'rules');
930
- const dest = join(destDir, 'autosnippet-workflow.mdc');
931
- if (existsSync(dest) && !this.force) {
932
- return false;
933
- }
934
-
935
- mkdirSync(destDir, { recursive: true });
936
- copyFileSync(src, dest);
937
- return true;
938
- }
939
-
940
- /**
941
- * @private .claude/hooks.yaml — Claude Code SessionStart hook
942
- * 会话开始时自动 prime TaskGraph 上下文
943
- */
944
- _copyClaudeHooks() {
945
- const src = join(REPO_ROOT, 'templates', 'claude-hooks.yaml');
946
- if (!existsSync(src)) {
947
- return false;
948
- }
949
-
950
- const claudeDir = join(this.projectRoot, '.claude');
951
- const dest = join(claudeDir, 'hooks.yaml');
952
- if (existsSync(dest) && !this.force) {
953
- return false;
954
- }
955
-
956
- // 即使 --force,也不覆盖用户自己的 hooks 文件
957
- const { canWrite } = checkWriteSafety(dest);
958
- if (!canWrite) {
959
- return false;
960
- }
961
-
962
- mkdirSync(claudeDir, { recursive: true });
963
- copyFileSync(src, dest);
964
- return true;
965
- }
966
-
967
- /**
968
- * @private .github/workflows/autosnippet-guard.yml — Guard CI/CD workflow
969
- * 在 push/PR 时自动运行 Guard 合规检查
970
- */
971
- _copyGuardCI() {
972
- const src = join(REPO_ROOT, 'templates', 'guard-ci.yml');
973
- if (!existsSync(src)) {
974
- return false;
975
- }
976
-
977
- const destDir = join(this.projectRoot, '.github', 'workflows');
978
- const dest = join(destDir, 'autosnippet-guard.yml');
979
- if (existsSync(dest) && !this.force) {
980
- return false;
981
- }
982
-
983
- const { canWrite } = checkWriteSafety(dest);
984
- if (!canWrite) {
985
- return false;
986
- }
987
-
988
- mkdirSync(destDir, { recursive: true });
989
- copyFileSync(src, dest);
990
- return true;
991
- }
992
-
993
- /**
994
- * @private .git/hooks/pre-commit — Guard pre-commit hook
995
- * 如果项目使用 husky,则安装到 .husky/pre-commit
996
- * 否则安装到 .git/hooks/pre-commit
997
- * 已存在 pre-commit hook 时不覆盖(避免破坏用户现有 hook)
998
- */
999
- _installPreCommitHook() {
1000
- const src = join(REPO_ROOT, 'templates', 'pre-commit-guard.sh');
1001
- if (!existsSync(src)) {
1002
- return false;
1003
- }
1004
-
1005
- // 优先检查 husky
1006
- const huskyDir = join(this.projectRoot, '.husky');
1007
- const gitHooksDir = join(this.projectRoot, '.git', 'hooks');
1008
-
1009
- let dest;
1010
- if (existsSync(huskyDir)) {
1011
- // 项目使用 husky — 安装到 .husky/pre-commit
1012
- dest = join(huskyDir, 'pre-commit');
1013
- } else if (existsSync(join(this.projectRoot, '.git'))) {
1014
- // 普通 git 项目 — 安装到 .git/hooks/pre-commit
1015
- dest = join(gitHooksDir, 'pre-commit');
1016
- mkdirSync(gitHooksDir, { recursive: true });
1017
- } else {
1018
- // 非 git 项目,跳过
1019
- return false;
1020
- }
1021
-
1022
- // 已存在 pre-commit hook 时不覆盖(可能是用户自己的 hook)
1023
- if (existsSync(dest) && !this.force) {
1024
- return false;
1025
- }
1026
-
1027
- // 即使 --force,也不覆盖用户自己的 pre-commit hook
1028
- const { canWrite } = checkWriteSafety(dest);
1029
- if (!canWrite) {
1030
- return false;
1031
- }
1032
-
1033
- copyFileSync(src, dest);
1034
- // 确保可执行
1035
- try {
1036
- execSync(`chmod +x "${dest}"`, { stdio: 'pipe' });
1037
- } catch {
1038
- /* Windows 不需要 chmod */
1039
- }
1040
- return true;
1041
- }
1042
-
1043
- /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
1044
- * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
1045
- _mirrorCursorToIDE(targetDirName) {
1046
- const cursorRulesDir = join(this.projectRoot, '.cursor', 'rules');
1047
- if (!existsSync(cursorRulesDir)) {
1048
- return;
1049
- }
1050
-
1051
- const targetRulesDir = join(this.projectRoot, targetDirName, 'rules');
1052
- mkdirSync(targetRulesDir, { recursive: true });
1053
-
1054
- // 只镜像 autosnippet- 前缀的文件,保留目标目录中用户已有的其他文件
1055
- const files = readdirSync(cursorRulesDir).filter(
1056
- (f) => (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
1057
- );
1058
- for (const file of files) {
1059
- const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
1060
- copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
1061
- }
1062
- }
1063
446
  /* ═══ Step 4: 数据库初始化 ═══════════════════════════ */
1064
447
 
1065
448
  async stepDatabase() {
@@ -1156,99 +539,6 @@ ${END_MARKER}
1156
539
  }
1157
540
  }
1158
541
 
1159
- /** @private 确保项目 .gitignore 正确配置 AutoSnippet 相关规则 */
1160
- _ensureGitignore() {
1161
- const giPath = join(this.projectRoot, '.gitignore');
1162
- let content = existsSync(giPath) ? readFileSync(giPath, 'utf8') : '';
1163
- let changed = false;
1164
-
1165
- // ── v2.4.0 迁移:旧格式 ".autosnippet/" → 新格式 ".autosnippet/*" ──
1166
- // 旧格式会忽略整个目录(git 不遍历内部),导致 skills/ 和 config.json 无法被 negation 恢复
1167
- // 新格式忽略目录内所有文件,允许 negation 模式取消特定子路径
1168
- if (content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
1169
- content = content.replace(/^\.autosnippet\/$/m, '.autosnippet/*');
1170
- changed = true;
1171
- }
1172
-
1173
- // ── 必须忽略:.autosnippet/*(运行时缓存、DB、向量索引、memory) ──
1174
- if (!content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
1175
- content += `\n# AutoSnippet 运行时缓存(不入库)\n.autosnippet/*\n`;
1176
- changed = true;
1177
- }
1178
-
1179
- // ── 必须跟踪:.autosnippet/config.json(项目配置) ──
1180
- if (!content.includes('!.autosnippet/config.json')) {
1181
- content += `!.autosnippet/config.json\n`;
1182
- changed = true;
1183
- }
1184
-
1185
- // ── 必须忽略:.env(包含 API Key 等敏感信息) ──
1186
- if (!content.includes('.env') || (!content.match(/^\.env$/m) && !content.match(/^\.env\s/m))) {
1187
- content += `\n# AutoSnippet 环境变量(含 API Key,不入库)\n.env\n`;
1188
- changed = true;
1189
- }
1190
-
1191
- // ── 必须忽略:logs/(winston 运行日志,可达数十 MB) ──
1192
- if (!content.match(/^logs\/?$/m)) {
1193
- content += `\n# AutoSnippet 运行日志\nlogs/\n`;
1194
- changed = true;
1195
- }
1196
-
1197
- // ── 必须忽略:.autosnippet-drafts/(AI 草稿临时目录) ──
1198
- if (!content.includes('.autosnippet-drafts')) {
1199
- content += `\n# AutoSnippet AI 草稿(临时)\n.autosnippet-drafts/\n`;
1200
- changed = true;
1201
- }
1202
-
1203
- // ── 必须忽略:_draft_*.md(AI Agent 在项目根目录创建的草稿文件) ──
1204
- if (!content.includes('_draft_*.md')) {
1205
- content += `\n# AutoSnippet AI 草稿文件(项目根目录临时文件)\n_draft_*.md\n`;
1206
- changed = true;
1207
- }
1208
-
1209
- // ── 必须忽略:常见系统 / 编辑器临时文件 ──
1210
- if (!content.includes('.DS_Store')) {
1211
- content += `\n# macOS 元数据\n.DS_Store\n`;
1212
- changed = true;
1213
- }
1214
- if (!content.includes('nohup.out')) {
1215
- content += `nohup.out\n`;
1216
- changed = true;
1217
- }
1218
- if (!content.match(/\*\.sw[a-p]/)) {
1219
- content += `*.sw[a-p]\n`;
1220
- changed = true;
1221
- }
1222
-
1223
- // Skills 已迁移到 AutoSnippet/skills/(知识库目录内),自动跟随 Git
1224
-
1225
- // ── 清理旧版本的 .autosnippet/skills/ negation(已迁移,不再需要)──
1226
- if (content.includes('!.autosnippet/skills/')) {
1227
- content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
1228
- changed = true;
1229
- }
1230
-
1231
- // ── 必须跟踪:AutoSnippet/(知识库子仓库)──
1232
- // 如果用户误将 AutoSnippet/ 加入忽略,追加 !AutoSnippet/ 取消忽略
1233
- const lines = content.split('\n');
1234
- const hasIgnoreAS = lines.some((l) => {
1235
- const t = l.trim();
1236
- return (
1237
- (t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!')
1238
- );
1239
- });
1240
- const hasNegation = lines.some((l) => l.trim() === '!AutoSnippet/');
1241
-
1242
- if (hasIgnoreAS && !hasNegation) {
1243
- content += `\n# AutoSnippet 知识库必须入库(取消上方忽略)\n!AutoSnippet/\n`;
1244
- changed = true;
1245
- }
1246
-
1247
- if (changed) {
1248
- writeFileSync(giPath, content);
1249
- }
1250
- }
1251
-
1252
542
  /** @private 在指定目录执行 git 命令 */
1253
543
  _git(args, cwd) {
1254
544
  try {