autosnippet 3.2.1 → 3.2.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.
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * Step 1 .autosnippet/ 运行时目录 + config.json + .gitignore
7
7
  * Step 2 AutoSnippet/ 子仓库(核心数据 + 权限能力 + skills/)
8
- * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules + skills-template)
8
+ * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules
9
+ * + skills-template + cursor-workflow + claude-hooks + guard-ci + pre-commit-hook)
9
10
  * Step 4 SQLite 数据库 + V1 数据迁移
10
11
  * Step 5 平台相关初始化(macOS → Xcode Snippets)
11
12
  *
@@ -432,24 +433,26 @@ export class SetupService {
432
433
  /* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
433
434
 
434
435
  stepIDE() {
436
+ const configured = [];
437
+
438
+ // MCP 配置(总是写入/合并,不会跳过)
435
439
  this._configureVSCodeMCP();
440
+ configured.push('vscode-mcp');
436
441
  this._configureCursorMCP();
437
- this._copyCopilotInstructions();
438
- this._generateAgentInstructionFiles();
439
- this._copyCursorRules();
440
- this._copySkillsTemplate();
442
+ configured.push('cursor-mcp');
443
+
444
+ // 模板文件(可能因已存在/用户文件而跳过)
445
+ if (this._copyCopilotInstructions()) configured.push('copilot-instructions');
446
+ if (this._generateAgentInstructionFiles()) configured.push('agent-instructions');
447
+ if (this._copyCursorRules()) configured.push('cursor-rules');
448
+ if (this._copySkillsTemplate()) configured.push('skills-template');
449
+ if (this._copyCursorWorkflow()) configured.push('cursor-workflow');
450
+ if (this._copyClaudeHooks()) configured.push('claude-hooks');
451
+ if (this._copyGuardCI()) configured.push('guard-ci');
452
+ if (this._installPreCommitHook()) configured.push('pre-commit-hook');
441
453
  // NOTE: .qoder/ .trae/ 不再自动创建,用户可通过 `asd mirror` 按需生成
442
454
 
443
455
  const extResult = this._installVSCodeExtension();
444
-
445
- const configured = [
446
- 'vscode-mcp',
447
- 'cursor-mcp',
448
- 'copilot-instructions',
449
- 'agent-instructions',
450
- 'cursor-rules',
451
- 'skills-template',
452
- ];
453
456
  if (extResult) {
454
457
  configured.push(...extResult);
455
458
  }
@@ -711,23 +714,24 @@ export class SetupService {
711
714
  _copyCopilotInstructions() {
712
715
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
713
716
  if (!existsSync(src)) {
714
- return;
717
+ return false;
715
718
  }
716
719
 
717
720
  const destDir = join(this.projectRoot, '.github');
718
721
  const dest = join(destDir, 'copilot-instructions.md');
719
722
  if (existsSync(dest) && !this.force) {
720
- return;
723
+ return false;
721
724
  }
722
725
 
723
726
  // 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
724
727
  const { canWrite } = checkWriteSafety(dest);
725
728
  if (!canWrite) {
726
- return;
729
+ return false;
727
730
  }
728
731
 
729
732
  mkdirSync(destDir, { recursive: true });
730
733
  copyFileSync(src, dest);
734
+ return true;
731
735
  }
732
736
 
733
737
  /**
@@ -737,6 +741,7 @@ export class SetupService {
737
741
  */
738
742
  _generateAgentInstructionFiles() {
739
743
  const projectName = this.projectRoot.split('/').pop();
744
+ let wrote = false;
740
745
 
741
746
  // AGENTS.md
742
747
  const agentsPath = join(this.projectRoot, 'AGENTS.md');
@@ -771,6 +776,7 @@ export class SetupService {
771
776
  '',
772
777
  ].join('\n');
773
778
  writeFileSync(agentsPath, agentsContent);
779
+ wrote = true;
774
780
  } else {
775
781
  }
776
782
  }
@@ -803,44 +809,169 @@ export class SetupService {
803
809
  '',
804
810
  ].join('\n');
805
811
  writeFileSync(claudePath, claudeContent);
812
+ wrote = true;
806
813
  } else {
807
814
  }
808
815
  }
816
+ return wrote;
809
817
  }
810
818
 
811
819
  /** @private .cursor/rules/autosnippet-conventions.mdc */
812
820
  _copyCursorRules() {
813
821
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
814
822
  if (!existsSync(src)) {
815
- return;
823
+ return false;
816
824
  }
817
825
 
818
826
  const destDir = join(this.projectRoot, '.cursor', 'rules');
819
827
  const dest = join(destDir, 'autosnippet-conventions.mdc');
820
828
  if (existsSync(dest) && !this.force) {
821
- return;
829
+ return false;
822
830
  }
823
831
 
824
832
  mkdirSync(destDir, { recursive: true });
825
833
  copyFileSync(src, dest);
834
+ return true;
826
835
  }
827
836
 
828
837
  /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
829
838
  _copySkillsTemplate() {
830
839
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
831
840
  if (!existsSync(src)) {
832
- return;
841
+ return false;
833
842
  }
834
843
 
835
844
  const destDir = join(this.projectRoot, '.cursor', 'rules');
836
845
  const dest = join(destDir, 'autosnippet-skills.mdc');
837
846
  if (existsSync(dest) && !this.force) {
838
- return;
847
+ return false;
848
+ }
849
+
850
+ mkdirSync(destDir, { recursive: true });
851
+ copyFileSync(src, dest);
852
+ return true;
853
+ }
854
+ /** @private .cursor/rules/autosnippet-workflow.mdc — TaskGraph & Guard 工作流 */
855
+ _copyCursorWorkflow() {
856
+ const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-workflow.mdc');
857
+ if (!existsSync(src)) {
858
+ return false;
859
+ }
860
+
861
+ const destDir = join(this.projectRoot, '.cursor', 'rules');
862
+ const dest = join(destDir, 'autosnippet-workflow.mdc');
863
+ if (existsSync(dest) && !this.force) {
864
+ return false;
865
+ }
866
+
867
+ mkdirSync(destDir, { recursive: true });
868
+ copyFileSync(src, dest);
869
+ return true;
870
+ }
871
+
872
+ /**
873
+ * @private .claude/hooks.yaml — Claude Code SessionStart hook
874
+ * 会话开始时自动 prime TaskGraph 上下文
875
+ */
876
+ _copyClaudeHooks() {
877
+ const src = join(REPO_ROOT, 'templates', 'claude-hooks.yaml');
878
+ if (!existsSync(src)) {
879
+ return false;
880
+ }
881
+
882
+ const claudeDir = join(this.projectRoot, '.claude');
883
+ const dest = join(claudeDir, 'hooks.yaml');
884
+ if (existsSync(dest) && !this.force) {
885
+ return false;
886
+ }
887
+
888
+ // 即使 --force,也不覆盖用户自己的 hooks 文件
889
+ const { canWrite } = checkWriteSafety(dest);
890
+ if (!canWrite) {
891
+ return false;
892
+ }
893
+
894
+ mkdirSync(claudeDir, { recursive: true });
895
+ copyFileSync(src, dest);
896
+ return true;
897
+ }
898
+
899
+ /**
900
+ * @private .github/workflows/autosnippet-guard.yml — Guard CI/CD workflow
901
+ * 在 push/PR 时自动运行 Guard 合规检查
902
+ */
903
+ _copyGuardCI() {
904
+ const src = join(REPO_ROOT, 'templates', 'guard-ci.yml');
905
+ if (!existsSync(src)) {
906
+ return false;
907
+ }
908
+
909
+ const destDir = join(this.projectRoot, '.github', 'workflows');
910
+ const dest = join(destDir, 'autosnippet-guard.yml');
911
+ if (existsSync(dest) && !this.force) {
912
+ return false;
913
+ }
914
+
915
+ const { canWrite } = checkWriteSafety(dest);
916
+ if (!canWrite) {
917
+ return false;
839
918
  }
840
919
 
841
920
  mkdirSync(destDir, { recursive: true });
842
921
  copyFileSync(src, dest);
922
+ return true;
843
923
  }
924
+
925
+ /**
926
+ * @private .git/hooks/pre-commit — Guard pre-commit hook
927
+ * 如果项目使用 husky,则安装到 .husky/pre-commit
928
+ * 否则安装到 .git/hooks/pre-commit
929
+ * 已存在 pre-commit hook 时不覆盖(避免破坏用户现有 hook)
930
+ */
931
+ _installPreCommitHook() {
932
+ const src = join(REPO_ROOT, 'templates', 'pre-commit-guard.sh');
933
+ if (!existsSync(src)) {
934
+ return false;
935
+ }
936
+
937
+ // 优先检查 husky
938
+ const huskyDir = join(this.projectRoot, '.husky');
939
+ const gitHooksDir = join(this.projectRoot, '.git', 'hooks');
940
+
941
+ let dest;
942
+ if (existsSync(huskyDir)) {
943
+ // 项目使用 husky — 安装到 .husky/pre-commit
944
+ dest = join(huskyDir, 'pre-commit');
945
+ } else if (existsSync(join(this.projectRoot, '.git'))) {
946
+ // 普通 git 项目 — 安装到 .git/hooks/pre-commit
947
+ dest = join(gitHooksDir, 'pre-commit');
948
+ mkdirSync(gitHooksDir, { recursive: true });
949
+ } else {
950
+ // 非 git 项目,跳过
951
+ return false;
952
+ }
953
+
954
+ // 已存在 pre-commit hook 时不覆盖(可能是用户自己的 hook)
955
+ if (existsSync(dest) && !this.force) {
956
+ return false;
957
+ }
958
+
959
+ // 即使 --force,也不覆盖用户自己的 pre-commit hook
960
+ const { canWrite } = checkWriteSafety(dest);
961
+ if (!canWrite) {
962
+ return false;
963
+ }
964
+
965
+ copyFileSync(src, dest);
966
+ // 确保可执行
967
+ try {
968
+ execSync(`chmod +x "${dest}"`, { stdio: 'pipe' });
969
+ } catch {
970
+ /* Windows 不需要 chmod */
971
+ }
972
+ return true;
973
+ }
974
+
844
975
  /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
845
976
  * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
846
977
  _mirrorCursorToIDE(targetDirName) {
@@ -5,15 +5,19 @@ import pathGuard from '../../shared/PathGuard.js';
5
5
  /**
6
6
  * Paths — 项目路径解析工具
7
7
  * 提供 Snippet 安装目录、缓存目录、知识库目录等路径计算能力。
8
- * 所有路径均自动确保目录存在。
8
+ *
9
+ * 设计原则:路径解析与目录创建分离
10
+ * - 路径 getter 函数仅返回路径字符串,不产生文件系统副作用
11
+ * - 需要创建目录时,调用方应使用 ensureDir() 显式确保目录存在
12
+ * - 全局非项目目录(Xcode snippets、cache)在获取时自动创建
9
13
  */
10
14
 
11
15
  export const SPEC_FILENAME = 'AutoSnippet.boxspec.json';
12
16
 
13
17
  const USER_HOME = process.env.HOME || process.env.USERPROFILE || '';
14
18
 
15
- /** 确保目录存在(静默处理异常) */
16
- function ensureDir(dirPath) {
19
+ /** 确保目录存在(静默处理异常),供写入前调用 */
20
+ export function ensureDir(dirPath) {
17
21
  try {
18
22
  // 双层路径安全检查 — 阻止在项目允许范围外创建文件夹
19
23
  pathGuard.assertProjectWriteSafe(dirPath);
@@ -86,9 +90,10 @@ export function getKnowledgeBaseDirName(projectRoot) {
86
90
 
87
91
  /**
88
92
  * 知识库根目录 = projectRoot/{dirContainingBoxspec}
93
+ * 注意:仅返回路径,不创建目录
89
94
  */
90
95
  export function getProjectKnowledgePath(projectRoot) {
91
- return ensureDir(path.join(projectRoot, getKnowledgeBaseDirName(projectRoot)));
96
+ return path.join(projectRoot, getKnowledgeBaseDirName(projectRoot));
92
97
  }
93
98
 
94
99
  /**
@@ -100,31 +105,35 @@ export function getProjectSpecPath(projectRoot) {
100
105
 
101
106
  /**
102
107
  * 项目内部隐藏数据目录 = knowledgePath/.autosnippet
108
+ * 注意:仅返回路径,不创建目录
103
109
  */
104
110
  export function getProjectInternalDataPath(projectRoot) {
105
- return ensureDir(path.join(getProjectKnowledgePath(projectRoot), '.autosnippet'));
111
+ return path.join(getProjectKnowledgePath(projectRoot), '.autosnippet');
106
112
  }
107
113
 
108
114
  /**
109
115
  * 上下文存储目录 = internalData/context
116
+ * 注意:仅返回路径,不创建目录
110
117
  */
111
118
  export function getContextStoragePath(projectRoot) {
112
- return ensureDir(path.join(getProjectInternalDataPath(projectRoot), 'context'));
119
+ return path.join(getProjectInternalDataPath(projectRoot), 'context');
113
120
  }
114
121
 
115
122
  /**
116
123
  * 上下文索引目录 = contextStorage/index
124
+ * 注意:仅返回路径,不创建目录
117
125
  */
118
126
  export function getContextIndexPath(projectRoot) {
119
- return ensureDir(path.join(getContextStoragePath(projectRoot), 'index'));
127
+ return path.join(getContextStoragePath(projectRoot), 'index');
120
128
  }
121
129
 
122
130
  /**
123
131
  * 项目级 Skills 目录 = knowledgePath/skills
124
132
  * Skills 放在知识库目录下跟随项目走(Git-tracked,用户可见)
133
+ * 注意:仅返回路径,不创建目录
125
134
  */
126
135
  export function getProjectSkillsPath(projectRoot) {
127
- return ensureDir(path.join(getProjectKnowledgePath(projectRoot), 'skills'));
136
+ return path.join(getProjectKnowledgePath(projectRoot), 'skills');
128
137
  }
129
138
 
130
139
  /**
@@ -143,6 +152,7 @@ export function getProjectRecipesPath(projectRoot, rootSpec) {
143
152
 
144
153
  export default {
145
154
  SPEC_FILENAME,
155
+ ensureDir,
146
156
  getSnippetsPath,
147
157
  getVSCodeSnippetsPath,
148
158
  getCachePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "lib/bootstrap.js",
@@ -1,4 +1,5 @@
1
1
  # AutoSnippet Claude Code Hooks
2
+ # Auto-generated by AutoSnippet. Do not edit manually.
2
3
  #
3
4
  # 用法:将此文件放到项目 .claude/ 目录下
4
5
  # Claude Code 在会话开始时会自动执行 SessionStart hook
@@ -1,5 +1,7 @@
1
1
  # AutoSnippet Copilot Instructions
2
2
 
3
+ > Auto-generated by AutoSnippet. Do not edit manually — regenerated on `asd setup` / `asd bootstrap`.
4
+
3
5
  ## 项目概览
4
6
  - 项目名称:AutoSnippet
5
7
  - 版本:V3(ESM, SQLite, MCP 11 工具 — 整合版)
@@ -1,4 +1,5 @@
1
1
  # AutoSnippet Guard CI/CD Check
2
+ # Auto-generated by AutoSnippet. Do not edit manually.
2
3
  # 在 push 和 PR 时自动运行 Guard 合规检查
3
4
  #
4
5
  # 使用方法:将此文件复制到项目的 .github/workflows/ 目录
@@ -1,6 +1,7 @@
1
1
  #!/bin/sh
2
2
  # AutoSnippet Guard pre-commit hook
3
- #
3
+ # Auto-generated by AutoSnippet. Do not edit manually.
4
+ #
4
5
  # 安装方法:
5
6
  # cp templates/pre-commit-guard.sh .git/hooks/pre-commit
6
7
  # chmod +x .git/hooks/pre-commit