agentic-team-templates 0.5.0 → 0.6.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.
- package/README.md +40 -1
- package/bin/cli.js +4 -1
- package/package.json +8 -3
- package/src/index.js +471 -7
- package/src/index.test.js +947 -0
- package/templates/testing/.cursorrules/advanced-techniques.md +596 -0
- package/templates/testing/.cursorrules/ci-cd-integration.md +603 -0
- package/templates/testing/.cursorrules/overview.md +163 -0
- package/templates/testing/.cursorrules/performance-testing.md +536 -0
- package/templates/testing/.cursorrules/quality-metrics.md +456 -0
- package/templates/testing/.cursorrules/reliability.md +557 -0
- package/templates/testing/.cursorrules/tdd-methodology.md +294 -0
- package/templates/testing/.cursorrules/test-data.md +565 -0
- package/templates/testing/.cursorrules/test-design.md +511 -0
- package/templates/testing/.cursorrules/test-types.md +398 -0
- package/templates/testing/CLAUDE.md +1134 -0
package/README.md
CHANGED
|
@@ -93,6 +93,42 @@ npx cursor-templates web-frontend --ide=codex
|
|
|
93
93
|
npx cursor-templates web-frontend --ide=cursor --ide=codex
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
### Remove Specific Templates
|
|
97
|
+
|
|
98
|
+
Remove templates you no longer need while keeping shared rules and other templates:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Remove a single template
|
|
102
|
+
npx cursor-templates --remove web-frontend
|
|
103
|
+
|
|
104
|
+
# Remove multiple templates
|
|
105
|
+
npx cursor-templates --remove web-frontend web-backend
|
|
106
|
+
|
|
107
|
+
# Remove from specific IDE only
|
|
108
|
+
npx cursor-templates --remove web-frontend --ide=cursor
|
|
109
|
+
|
|
110
|
+
# Skip confirmation prompt
|
|
111
|
+
npx cursor-templates --remove web-frontend --yes
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Reset (Remove Everything)
|
|
115
|
+
|
|
116
|
+
Remove all installed content (shared rules, templates, generated files):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Reset all installed content
|
|
120
|
+
npx cursor-templates --reset
|
|
121
|
+
|
|
122
|
+
# Reset for specific IDE only
|
|
123
|
+
npx cursor-templates --reset --ide=cursor
|
|
124
|
+
|
|
125
|
+
# Skip confirmation prompt
|
|
126
|
+
npx cursor-templates --reset --yes
|
|
127
|
+
|
|
128
|
+
# Force remove modified files
|
|
129
|
+
npx cursor-templates --reset --force
|
|
130
|
+
```
|
|
131
|
+
|
|
96
132
|
### CLI Options
|
|
97
133
|
|
|
98
134
|
| Option | Description |
|
|
@@ -100,7 +136,10 @@ npx cursor-templates web-frontend --ide=cursor --ide=codex
|
|
|
100
136
|
| `--ide=<name>` | Target IDE: `cursor`, `claude`, or `codex` (can be used multiple times) |
|
|
101
137
|
| `--list`, `-l` | List all available templates |
|
|
102
138
|
| `--dry-run` | Preview changes without writing files |
|
|
103
|
-
| `--force`, `-f` | Overwrite
|
|
139
|
+
| `--force`, `-f` | Overwrite/remove even if files were modified |
|
|
140
|
+
| `--remove` | Remove specified templates |
|
|
141
|
+
| `--reset` | Remove ALL installed content |
|
|
142
|
+
| `--yes`, `-y` | Skip confirmation prompt (for `--remove` and `--reset`) |
|
|
104
143
|
| `--help`, `-h` | Show help message |
|
|
105
144
|
|
|
106
145
|
## Available Templates
|
package/bin/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-team-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code. - use at your own risk",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
|
@@ -38,10 +38,15 @@
|
|
|
38
38
|
"node": ">=18.0.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"prepare": "husky"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@commitlint/cli": "^19.0.0",
|
|
45
|
-
"@commitlint/config-conventional": "^19.0.0"
|
|
48
|
+
"@commitlint/config-conventional": "^19.0.0",
|
|
49
|
+
"husky": "^9.1.7",
|
|
50
|
+
"vitest": "^4.0.18"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/src/index.js
CHANGED
|
@@ -74,6 +74,8 @@ function printBanner() {
|
|
|
74
74
|
function printHelp() {
|
|
75
75
|
console.log(`${colors.yellow('Usage:')}
|
|
76
76
|
npx cursor-templates <templates...> [options]
|
|
77
|
+
npx cursor-templates --remove <templates...> [options]
|
|
78
|
+
npx cursor-templates --reset [options]
|
|
77
79
|
|
|
78
80
|
${colors.yellow('Options:')}
|
|
79
81
|
--ide=<name> Install for specific IDE (cursor, claude, codex)
|
|
@@ -81,8 +83,13 @@ ${colors.yellow('Options:')}
|
|
|
81
83
|
Default: all (cursor, claude, codex)
|
|
82
84
|
--list, -l List available templates
|
|
83
85
|
--help, -h Show this help message
|
|
84
|
-
--dry-run Show what would be
|
|
85
|
-
--force, -f Overwrite
|
|
86
|
+
--dry-run Show what would be changed
|
|
87
|
+
--force, -f Overwrite/remove even if files were modified
|
|
88
|
+
--yes, -y Skip confirmation prompt (for --remove and --reset)
|
|
89
|
+
|
|
90
|
+
${colors.yellow('Removal Options:')}
|
|
91
|
+
--remove Remove specified templates (keeps shared rules and other templates)
|
|
92
|
+
--reset Remove ALL installed content (shared rules, templates, generated files)
|
|
86
93
|
|
|
87
94
|
${colors.yellow('IDE Targets:')}
|
|
88
95
|
cursor .cursorrules/ directory (Cursor IDE)
|
|
@@ -96,6 +103,14 @@ ${colors.yellow('Examples:')}
|
|
|
96
103
|
npx cursor-templates fullstack --ide=codex
|
|
97
104
|
npx cursor-templates web-backend --force
|
|
98
105
|
|
|
106
|
+
${colors.yellow('Removal Examples:')}
|
|
107
|
+
npx cursor-templates --remove web-frontend
|
|
108
|
+
npx cursor-templates --remove web-frontend web-backend
|
|
109
|
+
npx cursor-templates --remove web-frontend --ide=cursor
|
|
110
|
+
npx cursor-templates --reset
|
|
111
|
+
npx cursor-templates --reset --ide=cursor
|
|
112
|
+
npx cursor-templates --reset --yes
|
|
113
|
+
|
|
99
114
|
${colors.dim('Shared rules (code-quality, security, git-workflow, etc.) are always included.')}
|
|
100
115
|
${colors.dim('Identical files are skipped. Modified files are preserved; ours saved as *-1.md.')}
|
|
101
116
|
${colors.dim('CLAUDE.md: missing sections are intelligently merged (not overwritten).')}
|
|
@@ -823,11 +838,382 @@ function install(targetDir, templates, dryRun = false, force = false, ides = DEF
|
|
|
823
838
|
console.log();
|
|
824
839
|
}
|
|
825
840
|
|
|
826
|
-
|
|
841
|
+
/**
|
|
842
|
+
* Prompt user for confirmation
|
|
843
|
+
* @param {string} message - The prompt message
|
|
844
|
+
* @returns {Promise<boolean>}
|
|
845
|
+
*/
|
|
846
|
+
async function confirm(message) {
|
|
847
|
+
const readline = await import('readline');
|
|
848
|
+
const rl = readline.createInterface({
|
|
849
|
+
input: process.stdin,
|
|
850
|
+
output: process.stdout
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
return new Promise((resolve) => {
|
|
854
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
855
|
+
rl.close();
|
|
856
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Check if a file was created by our installer (matches template content)
|
|
863
|
+
* @param {string} filePath - Path to the file
|
|
864
|
+
* @param {string} templatePath - Path to the template file
|
|
865
|
+
* @returns {boolean}
|
|
866
|
+
*/
|
|
867
|
+
function isOurFile(filePath, templatePath) {
|
|
868
|
+
if (!fs.existsSync(filePath)) return false;
|
|
869
|
+
if (!fs.existsSync(templatePath)) return true; // No template to compare, assume ours
|
|
870
|
+
return filesMatch(filePath, templatePath);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Remove specific templates from the installation
|
|
875
|
+
*/
|
|
876
|
+
async function remove(targetDir, templates, dryRun = false, force = false, skipConfirm = false, ides = DEFAULT_IDES) {
|
|
877
|
+
const stats = { removed: 0, skipped: 0, notFound: 0 };
|
|
878
|
+
const filesToRemove = [];
|
|
879
|
+
const modifiedFiles = [];
|
|
880
|
+
|
|
881
|
+
console.log(`${colors.blue('Removing from:')} ${targetDir}`);
|
|
882
|
+
console.log(`${colors.blue('Target IDEs:')} ${ides.join(', ')}`);
|
|
883
|
+
console.log(`${colors.blue('Templates:')} ${templates.join(', ')}`);
|
|
884
|
+
console.log();
|
|
885
|
+
|
|
886
|
+
// 1. Collect files to remove from .cursorrules/
|
|
887
|
+
if (ides.includes('cursor')) {
|
|
888
|
+
const cursorrules = path.join(targetDir, '.cursorrules');
|
|
889
|
+
|
|
890
|
+
if (fs.existsSync(cursorrules)) {
|
|
891
|
+
for (const template of templates) {
|
|
892
|
+
console.log(colors.yellow(`► Scanning ${template} template files...`));
|
|
893
|
+
|
|
894
|
+
for (const rule of TEMPLATES[template].rules) {
|
|
895
|
+
const destName = `${template}-${rule}`;
|
|
896
|
+
const destPath = path.join(cursorrules, destName);
|
|
897
|
+
const srcPath = path.join(TEMPLATES_DIR, template, '.cursorrules', rule);
|
|
898
|
+
|
|
899
|
+
if (!fs.existsSync(destPath)) {
|
|
900
|
+
console.log(` ${colors.dim('[not found]')} ${destName}`);
|
|
901
|
+
stats.notFound++;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
906
|
+
|
|
907
|
+
if (!isUnmodified && !force) {
|
|
908
|
+
console.log(` ${colors.yellow('[modified]')} ${destName} (use --force to remove)`);
|
|
909
|
+
modifiedFiles.push(destName);
|
|
910
|
+
stats.skipped++;
|
|
911
|
+
} else {
|
|
912
|
+
console.log(` ${colors.red('[remove]')} ${destName}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
913
|
+
filesToRemove.push({ path: destPath, name: destName });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Also check for -1 variant files
|
|
918
|
+
for (const rule of TEMPLATES[template].rules) {
|
|
919
|
+
const altName = `${template}-${rule.replace('.md', '-1.md')}`;
|
|
920
|
+
const altPath = path.join(cursorrules, altName);
|
|
921
|
+
|
|
922
|
+
if (fs.existsSync(altPath)) {
|
|
923
|
+
console.log(` ${colors.red('[remove]')} ${altName} (alternate file)`);
|
|
924
|
+
filesToRemove.push({ path: altPath, name: altName });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
console.log();
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
console.log(colors.dim('No .cursorrules/ directory found.\n'));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 2. Note about CLAUDE.md and copilot-instructions.md
|
|
936
|
+
// These are regenerated, not patched, so we can't easily remove just one template's content
|
|
937
|
+
// We'll warn the user about this
|
|
938
|
+
if (ides.includes('claude') || ides.includes('codex')) {
|
|
939
|
+
console.log(colors.yellow('Note: CLAUDE.md and copilot-instructions.md contain merged content.'));
|
|
940
|
+
console.log(colors.dim('To update these files, re-run the installer with the remaining templates.\n'));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (filesToRemove.length === 0) {
|
|
944
|
+
console.log(colors.yellow('Nothing to remove.\n'));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Confirmation
|
|
949
|
+
if (!dryRun && !skipConfirm) {
|
|
950
|
+
console.log(colors.yellow(`\nAbout to remove ${filesToRemove.length} file(s).`));
|
|
951
|
+
const confirmed = await confirm(colors.red('Proceed with removal?'));
|
|
952
|
+
if (!confirmed) {
|
|
953
|
+
console.log(colors.dim('\nAborted.\n'));
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Execute removal
|
|
960
|
+
if (dryRun) {
|
|
961
|
+
console.log(colors.yellow('DRY RUN - No files were removed.\n'));
|
|
962
|
+
} else {
|
|
963
|
+
for (const file of filesToRemove) {
|
|
964
|
+
try {
|
|
965
|
+
fs.unlinkSync(file.path);
|
|
966
|
+
stats.removed++;
|
|
967
|
+
} catch (err) {
|
|
968
|
+
console.error(colors.red(`Failed to remove ${file.name}: ${err.message}`));
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Summary
|
|
974
|
+
console.log(colors.green('════════════════════════════════════════════════════════════'));
|
|
975
|
+
console.log(colors.green(`✓ Removal complete!\n`));
|
|
976
|
+
|
|
977
|
+
console.log(colors.yellow('Summary:'));
|
|
978
|
+
console.log(` - ${stats.removed} files removed`);
|
|
979
|
+
if (stats.skipped > 0) {
|
|
980
|
+
console.log(` - ${stats.skipped} files skipped (modified, use --force)`);
|
|
981
|
+
}
|
|
982
|
+
if (stats.notFound > 0) {
|
|
983
|
+
console.log(` - ${stats.notFound} files not found`);
|
|
984
|
+
}
|
|
985
|
+
console.log();
|
|
986
|
+
|
|
987
|
+
if (modifiedFiles.length > 0) {
|
|
988
|
+
console.log(colors.yellow('Modified files preserved:'));
|
|
989
|
+
for (const file of modifiedFiles) {
|
|
990
|
+
console.log(` - ${file}`);
|
|
991
|
+
}
|
|
992
|
+
console.log(colors.dim('\nUse --force to remove modified files.\n'));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Reset - remove all installed content
|
|
998
|
+
*/
|
|
999
|
+
async function reset(targetDir, dryRun = false, force = false, skipConfirm = false, ides = DEFAULT_IDES) {
|
|
1000
|
+
const stats = { removed: 0, skipped: 0 };
|
|
1001
|
+
const filesToRemove = [];
|
|
1002
|
+
const modifiedFiles = [];
|
|
1003
|
+
const dirsToRemove = [];
|
|
1004
|
+
|
|
1005
|
+
console.log(`${colors.blue('Resetting:')} ${targetDir}`);
|
|
1006
|
+
console.log(`${colors.blue('Target IDEs:')} ${ides.join(', ')}`);
|
|
1007
|
+
console.log();
|
|
1008
|
+
|
|
1009
|
+
// 1. Remove .cursorrules/ contents for Cursor
|
|
1010
|
+
if (ides.includes('cursor')) {
|
|
1011
|
+
const cursorrules = path.join(targetDir, '.cursorrules');
|
|
1012
|
+
|
|
1013
|
+
if (fs.existsSync(cursorrules)) {
|
|
1014
|
+
console.log(colors.yellow('► Scanning .cursorrules/ directory...'));
|
|
1015
|
+
|
|
1016
|
+
// Check shared rules
|
|
1017
|
+
for (const rule of SHARED_RULES) {
|
|
1018
|
+
const destPath = path.join(cursorrules, rule);
|
|
1019
|
+
const srcPath = path.join(TEMPLATES_DIR, '_shared', rule);
|
|
1020
|
+
|
|
1021
|
+
if (!fs.existsSync(destPath)) continue;
|
|
1022
|
+
|
|
1023
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
1024
|
+
|
|
1025
|
+
if (!isUnmodified && !force) {
|
|
1026
|
+
console.log(` ${colors.yellow('[modified]')} ${rule} (use --force to remove)`);
|
|
1027
|
+
modifiedFiles.push(rule);
|
|
1028
|
+
stats.skipped++;
|
|
1029
|
+
} else {
|
|
1030
|
+
console.log(` ${colors.red('[remove]')} ${rule}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
1031
|
+
filesToRemove.push({ path: destPath, name: rule });
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Check for -1 variant
|
|
1035
|
+
const altPath = path.join(cursorrules, rule.replace('.md', '-1.md'));
|
|
1036
|
+
if (fs.existsSync(altPath)) {
|
|
1037
|
+
console.log(` ${colors.red('[remove]')} ${rule.replace('.md', '-1.md')} (alternate file)`);
|
|
1038
|
+
filesToRemove.push({ path: altPath, name: rule.replace('.md', '-1.md') });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Check template-specific rules
|
|
1043
|
+
for (const [templateName, templateInfo] of Object.entries(TEMPLATES)) {
|
|
1044
|
+
for (const rule of templateInfo.rules) {
|
|
1045
|
+
const destName = `${templateName}-${rule}`;
|
|
1046
|
+
const destPath = path.join(cursorrules, destName);
|
|
1047
|
+
const srcPath = path.join(TEMPLATES_DIR, templateName, '.cursorrules', rule);
|
|
1048
|
+
|
|
1049
|
+
if (!fs.existsSync(destPath)) continue;
|
|
1050
|
+
|
|
1051
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
1052
|
+
|
|
1053
|
+
if (!isUnmodified && !force) {
|
|
1054
|
+
console.log(` ${colors.yellow('[modified]')} ${destName} (use --force to remove)`);
|
|
1055
|
+
modifiedFiles.push(destName);
|
|
1056
|
+
stats.skipped++;
|
|
1057
|
+
} else {
|
|
1058
|
+
console.log(` ${colors.red('[remove]')} ${destName}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
1059
|
+
filesToRemove.push({ path: destPath, name: destName });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Check for -1 variant
|
|
1063
|
+
const altName = destName.replace('.md', '-1.md');
|
|
1064
|
+
const altPath = path.join(cursorrules, altName);
|
|
1065
|
+
if (fs.existsSync(altPath)) {
|
|
1066
|
+
console.log(` ${colors.red('[remove]')} ${altName} (alternate file)`);
|
|
1067
|
+
filesToRemove.push({ path: altPath, name: altName });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Check if we should remove the directory itself (only if it would be empty)
|
|
1073
|
+
const remainingFiles = fs.readdirSync(cursorrules).filter(f => {
|
|
1074
|
+
const fullPath = path.join(cursorrules, f);
|
|
1075
|
+
const willBeRemoved = filesToRemove.some(fr => fr.path === fullPath);
|
|
1076
|
+
return !willBeRemoved;
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
if (remainingFiles.length === 0 || force) {
|
|
1080
|
+
console.log(` ${colors.red('[remove]')} .cursorrules/ directory`);
|
|
1081
|
+
dirsToRemove.push(cursorrules);
|
|
1082
|
+
} else if (remainingFiles.length > 0) {
|
|
1083
|
+
console.log(colors.dim(` .cursorrules/ will be kept (${remainingFiles.length} non-template file(s) remain)`));
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
console.log();
|
|
1087
|
+
} else {
|
|
1088
|
+
console.log(colors.dim('No .cursorrules/ directory found.\n'));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// 2. Remove CLAUDE.md for Claude
|
|
1093
|
+
if (ides.includes('claude')) {
|
|
1094
|
+
const claudePath = path.join(targetDir, 'CLAUDE.md');
|
|
1095
|
+
|
|
1096
|
+
if (fs.existsSync(claudePath)) {
|
|
1097
|
+
console.log(colors.yellow('► Checking CLAUDE.md...'));
|
|
1098
|
+
|
|
1099
|
+
// Check if it contains our signature content
|
|
1100
|
+
const content = fs.readFileSync(claudePath, 'utf8');
|
|
1101
|
+
const isOurs = content.includes('# CLAUDE.md - Development Guide') &&
|
|
1102
|
+
content.includes('.cursorrules/');
|
|
1103
|
+
|
|
1104
|
+
if (!isOurs && !force) {
|
|
1105
|
+
console.log(` ${colors.yellow('[modified]')} CLAUDE.md (doesn't match template, use --force)`);
|
|
1106
|
+
modifiedFiles.push('CLAUDE.md');
|
|
1107
|
+
stats.skipped++;
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(` ${colors.red('[remove]')} CLAUDE.md${!isOurs ? ' (modified, --force)' : ''}`);
|
|
1110
|
+
filesToRemove.push({ path: claudePath, name: 'CLAUDE.md' });
|
|
1111
|
+
}
|
|
1112
|
+
console.log();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// 3. Remove .github/copilot-instructions.md for Codex
|
|
1117
|
+
if (ides.includes('codex')) {
|
|
1118
|
+
const copilotPath = path.join(targetDir, '.github', 'copilot-instructions.md');
|
|
1119
|
+
|
|
1120
|
+
if (fs.existsSync(copilotPath)) {
|
|
1121
|
+
console.log(colors.yellow('► Checking .github/copilot-instructions.md...'));
|
|
1122
|
+
|
|
1123
|
+
// Check if it contains our signature content
|
|
1124
|
+
const content = fs.readFileSync(copilotPath, 'utf8');
|
|
1125
|
+
const isOurs = content.includes('# Copilot Instructions') &&
|
|
1126
|
+
content.includes('Installed Templates:');
|
|
1127
|
+
|
|
1128
|
+
if (!isOurs && !force) {
|
|
1129
|
+
console.log(` ${colors.yellow('[modified]')} .github/copilot-instructions.md (doesn't match template, use --force)`);
|
|
1130
|
+
modifiedFiles.push('.github/copilot-instructions.md');
|
|
1131
|
+
stats.skipped++;
|
|
1132
|
+
} else {
|
|
1133
|
+
console.log(` ${colors.red('[remove]')} .github/copilot-instructions.md${!isOurs ? ' (modified, --force)' : ''}`);
|
|
1134
|
+
filesToRemove.push({ path: copilotPath, name: '.github/copilot-instructions.md' });
|
|
1135
|
+
}
|
|
1136
|
+
console.log();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (filesToRemove.length === 0 && dirsToRemove.length === 0) {
|
|
1141
|
+
console.log(colors.yellow('Nothing to remove.\n'));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Confirmation
|
|
1146
|
+
if (!dryRun && !skipConfirm) {
|
|
1147
|
+
const totalItems = filesToRemove.length + dirsToRemove.length;
|
|
1148
|
+
console.log(colors.yellow(`\nAbout to remove ${totalItems} item(s).`));
|
|
1149
|
+
const confirmed = await confirm(colors.red('Proceed with reset?'));
|
|
1150
|
+
if (!confirmed) {
|
|
1151
|
+
console.log(colors.dim('\nAborted.\n'));
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
console.log();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Execute removal
|
|
1158
|
+
if (dryRun) {
|
|
1159
|
+
console.log(colors.yellow('DRY RUN - No files were removed.\n'));
|
|
1160
|
+
} else {
|
|
1161
|
+
// Remove files first
|
|
1162
|
+
for (const file of filesToRemove) {
|
|
1163
|
+
try {
|
|
1164
|
+
fs.unlinkSync(file.path);
|
|
1165
|
+
stats.removed++;
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
console.error(colors.red(`Failed to remove ${file.name}: ${err.message}`));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Then remove directories
|
|
1172
|
+
for (const dir of dirsToRemove) {
|
|
1173
|
+
try {
|
|
1174
|
+
// Check if directory is now empty
|
|
1175
|
+
const remaining = fs.existsSync(dir) ? fs.readdirSync(dir) : [];
|
|
1176
|
+
if (remaining.length === 0) {
|
|
1177
|
+
fs.rmdirSync(dir);
|
|
1178
|
+
stats.removed++;
|
|
1179
|
+
} else if (force) {
|
|
1180
|
+
fs.rmSync(dir, { recursive: true });
|
|
1181
|
+
stats.removed++;
|
|
1182
|
+
}
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
console.error(colors.red(`Failed to remove directory: ${err.message}`));
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Summary
|
|
1190
|
+
console.log(colors.green('════════════════════════════════════════════════════════════'));
|
|
1191
|
+
console.log(colors.green(`✓ Reset complete!\n`));
|
|
1192
|
+
|
|
1193
|
+
console.log(colors.yellow('Summary:'));
|
|
1194
|
+
console.log(` - ${stats.removed} items removed`);
|
|
1195
|
+
if (stats.skipped > 0) {
|
|
1196
|
+
console.log(` - ${stats.skipped} files skipped (modified, use --force)`);
|
|
1197
|
+
}
|
|
1198
|
+
console.log();
|
|
1199
|
+
|
|
1200
|
+
if (modifiedFiles.length > 0) {
|
|
1201
|
+
console.log(colors.yellow('Modified files preserved:'));
|
|
1202
|
+
for (const file of modifiedFiles) {
|
|
1203
|
+
console.log(` - ${file}`);
|
|
1204
|
+
}
|
|
1205
|
+
console.log(colors.dim('\nUse --force to remove modified files.\n'));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
export async function run(args) {
|
|
827
1210
|
const templates = [];
|
|
828
1211
|
const ides = [];
|
|
829
1212
|
let dryRun = false;
|
|
830
1213
|
let force = false;
|
|
1214
|
+
let skipConfirm = false;
|
|
1215
|
+
let removeMode = false;
|
|
1216
|
+
let resetMode = false;
|
|
831
1217
|
|
|
832
1218
|
// Parse arguments
|
|
833
1219
|
for (const arg of args) {
|
|
@@ -843,6 +1229,12 @@ export function run(args) {
|
|
|
843
1229
|
dryRun = true;
|
|
844
1230
|
} else if (arg === '--force' || arg === '-f') {
|
|
845
1231
|
force = true;
|
|
1232
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
1233
|
+
skipConfirm = true;
|
|
1234
|
+
} else if (arg === '--remove') {
|
|
1235
|
+
removeMode = true;
|
|
1236
|
+
} else if (arg === '--reset') {
|
|
1237
|
+
resetMode = true;
|
|
846
1238
|
} else if (arg.startsWith('--ide=')) {
|
|
847
1239
|
const ide = arg.slice(6).toLowerCase();
|
|
848
1240
|
if (!SUPPORTED_IDES.includes(ide)) {
|
|
@@ -864,7 +1256,61 @@ export function run(args) {
|
|
|
864
1256
|
|
|
865
1257
|
printBanner();
|
|
866
1258
|
|
|
867
|
-
//
|
|
1259
|
+
// Use default IDEs if none specified
|
|
1260
|
+
const targetIdes = ides.length > 0 ? ides : DEFAULT_IDES;
|
|
1261
|
+
|
|
1262
|
+
// Handle reset mode
|
|
1263
|
+
if (resetMode) {
|
|
1264
|
+
if (removeMode) {
|
|
1265
|
+
console.error(colors.red('Error: Cannot use --remove and --reset together\n'));
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
if (templates.length > 0) {
|
|
1269
|
+
console.error(colors.red('Error: --reset does not accept template arguments\n'));
|
|
1270
|
+
console.error(colors.dim('Use --remove <templates...> to remove specific templates.\n'));
|
|
1271
|
+
process.exit(1);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (dryRun) {
|
|
1275
|
+
console.log(colors.yellow('DRY RUN - No changes will be made\n'));
|
|
1276
|
+
}
|
|
1277
|
+
if (force) {
|
|
1278
|
+
console.log(colors.yellow('FORCE MODE - Modified files will be removed\n'));
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
await reset(process.cwd(), dryRun, force, skipConfirm, targetIdes);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Handle remove mode
|
|
1286
|
+
if (removeMode) {
|
|
1287
|
+
if (templates.length === 0) {
|
|
1288
|
+
console.error(colors.red('Error: No templates specified for removal\n'));
|
|
1289
|
+
console.error(colors.dim('Usage: npx cursor-templates --remove <templates...>\n'));
|
|
1290
|
+
printTemplates();
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
for (const template of templates) {
|
|
1295
|
+
if (!TEMPLATES[template]) {
|
|
1296
|
+
console.error(colors.red(`Error: Unknown template '${template}'\n`));
|
|
1297
|
+
printTemplates();
|
|
1298
|
+
process.exit(1);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (dryRun) {
|
|
1303
|
+
console.log(colors.yellow('DRY RUN - No changes will be made\n'));
|
|
1304
|
+
}
|
|
1305
|
+
if (force) {
|
|
1306
|
+
console.log(colors.yellow('FORCE MODE - Modified files will be removed\n'));
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
await remove(process.cwd(), templates, dryRun, force, skipConfirm, targetIdes);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Install mode (default)
|
|
868
1314
|
if (templates.length === 0) {
|
|
869
1315
|
console.error(colors.red('Error: No templates specified\n'));
|
|
870
1316
|
printHelp();
|
|
@@ -879,9 +1325,6 @@ export function run(args) {
|
|
|
879
1325
|
}
|
|
880
1326
|
}
|
|
881
1327
|
|
|
882
|
-
// Use default IDEs if none specified
|
|
883
|
-
const targetIdes = ides.length > 0 ? ides : DEFAULT_IDES;
|
|
884
|
-
|
|
885
1328
|
if (dryRun) {
|
|
886
1329
|
console.log(colors.yellow('DRY RUN - No changes will be made\n'));
|
|
887
1330
|
}
|
|
@@ -893,3 +1336,24 @@ export function run(args) {
|
|
|
893
1336
|
// Install to current directory
|
|
894
1337
|
install(process.cwd(), templates, dryRun, force, targetIdes);
|
|
895
1338
|
}
|
|
1339
|
+
|
|
1340
|
+
// Export internals for testing
|
|
1341
|
+
export const _internals = {
|
|
1342
|
+
TEMPLATES,
|
|
1343
|
+
SHARED_RULES,
|
|
1344
|
+
SUPPORTED_IDES,
|
|
1345
|
+
DEFAULT_IDES,
|
|
1346
|
+
filesMatch,
|
|
1347
|
+
parseMarkdownSections,
|
|
1348
|
+
generateSectionSignature,
|
|
1349
|
+
findMissingSections,
|
|
1350
|
+
mergeClaudeContent,
|
|
1351
|
+
getAlternateFilename,
|
|
1352
|
+
copyFile,
|
|
1353
|
+
generateClaudeMdContent,
|
|
1354
|
+
generateCopilotInstructionsContent,
|
|
1355
|
+
isOurFile,
|
|
1356
|
+
install,
|
|
1357
|
+
remove,
|
|
1358
|
+
reset,
|
|
1359
|
+
};
|