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.
package/lib/cli/SetupService.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
package/templates/guard-ci.yml
CHANGED