@ngxtm/devkit 3.2.0 → 3.4.0

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 (3) hide show
  1. package/cli/index.js +59 -13
  2. package/cli/rules.js +248 -0
  3. package/package.json +2 -3
package/cli/index.js CHANGED
@@ -17,6 +17,7 @@ const { initProject, uninstallProject } = require('./init');
17
17
  const { updateProject, showStatus } = require('./update');
18
18
  const { detectProjectType, getRulesForTypes, printDetectionResults } = require('./detect');
19
19
  const { validatePath } = require('./utils');
20
+ const { listRules, addRules, removeRules } = require('./rules');
20
21
 
21
22
  // Legacy imports for backwards compatibility (will be deprecated)
22
23
  let legacyInstall = null;
@@ -38,6 +39,8 @@ function parseArgs(args) {
38
39
  update: false,
39
40
  clean: false,
40
41
  help: false,
42
+ installed: false,
43
+ ruleArgs: [],
41
44
  // Legacy options (deprecated)
42
45
  tool: null,
43
46
  minimal: false,
@@ -57,6 +60,8 @@ function parseArgs(args) {
57
60
  options.update = true;
58
61
  } else if (arg === '--clean' || arg === '-c') {
59
62
  options.clean = true;
63
+ } else if (arg === '--installed' || arg === '-i') {
64
+ options.installed = true;
60
65
  } else if (arg === '--status' || arg === '-s') {
61
66
  options.command = 'status';
62
67
  } else if (arg.startsWith('--path=')) {
@@ -65,21 +70,15 @@ function parseArgs(args) {
65
70
  // Handle legacy options for backwards compatibility
66
71
  if (arg === '--minimal' || arg === '-m') options.minimal = true;
67
72
  if (arg === '--lite' || arg === '-l') options.lite = true;
68
- if (arg === '--interactive' || arg === '-i') options.interactive = true;
69
- if (arg === '--full') options.fullSkills = true;
70
73
  if (arg.startsWith('--category=')) {
71
74
  const cats = arg.split('=')[1].split(',').map(c => c.trim());
72
75
  options.categories.push(...cats);
73
76
  }
74
77
  } else if (!options.command) {
75
78
  options.command = arg;
76
- } else if (!options.path && !options.tool) {
77
- // Could be a path or a tool name (legacy)
78
- if (['claude', 'cursor', 'copilot', 'gemini'].includes(arg)) {
79
- options.tool = arg;
80
- } else {
81
- options.path = arg;
82
- }
79
+ } else {
80
+ // Additional args (for add/remove commands)
81
+ options.ruleArgs.push(arg);
83
82
  }
84
83
  }
85
84
 
@@ -104,6 +103,15 @@ COMMANDS:
104
103
  detect Show detected technologies for current project
105
104
  Useful to see what devkit will install.
106
105
 
106
+ rules List all available rules
107
+ Use --installed to show only installed rules.
108
+
109
+ add <rule> Add rules manually (space-separated)
110
+ Example: devkit add golang docker
111
+
112
+ remove <rule> Remove rules from project
113
+ Example: devkit remove flutter
114
+
107
115
  status Show installation status
108
116
 
109
117
  uninstall Remove devkit from current project
@@ -115,10 +123,11 @@ COMMANDS:
115
123
  version Show version
116
124
 
117
125
  OPTIONS:
118
- --force, -f Force overwrite existing installation
119
- --clean, -c Remove rules for technologies no longer detected (with update)
120
- --path=DIR Specify project directory (default: current directory)
121
- --help, -h Show this help
126
+ --force, -f Force overwrite existing installation
127
+ --clean, -c Remove rules for technologies no longer detected (with update)
128
+ --installed, -i Show only installed rules (with rules command)
129
+ --path=DIR Specify project directory (default: current directory)
130
+ --help, -h Show this help
122
131
 
123
132
  EXAMPLES:
124
133
  devkit init # Initialize in current project
@@ -126,6 +135,10 @@ EXAMPLES:
126
135
  devkit update # Update and re-detect technologies
127
136
  devkit update --clean # Update and remove old rules
128
137
  devkit detect # Show what would be detected
138
+ devkit rules # List all available rules
139
+ devkit rules --installed # Show installed rules
140
+ devkit add golang docker # Add golang and docker rules
141
+ devkit remove flutter # Remove flutter rule
129
142
  devkit status # Show current installation
130
143
  devkit uninstall # Remove from current project
131
144
 
@@ -211,6 +224,39 @@ const commands = {
211
224
  });
212
225
  },
213
226
 
227
+ // List available rules
228
+ rules: (options) => {
229
+ const projectPath = validatePath(options.path) || process.cwd();
230
+ return listRules({
231
+ path: projectPath,
232
+ installed: options.installed
233
+ });
234
+ },
235
+
236
+ // Add rules manually
237
+ add: (options) => {
238
+ const projectPath = validatePath(options.path) || process.cwd();
239
+ if (options.ruleArgs.length === 0) {
240
+ console.log('\n Usage: devkit add <rule> [rule2 ...]');
241
+ console.log(' Example: devkit add golang docker\n');
242
+ console.log(' Run "devkit rules" to see available rules.\n');
243
+ return { success: false, reason: 'no_rules' };
244
+ }
245
+ return addRules(options.ruleArgs, { path: projectPath });
246
+ },
247
+
248
+ // Remove rules
249
+ remove: (options) => {
250
+ const projectPath = validatePath(options.path) || process.cwd();
251
+ if (options.ruleArgs.length === 0) {
252
+ console.log('\n Usage: devkit remove <rule> [rule2 ...]');
253
+ console.log(' Example: devkit remove flutter\n');
254
+ console.log(' Run "devkit rules --installed" to see installed rules.\n');
255
+ return { success: false, reason: 'no_rules' };
256
+ }
257
+ return removeRules(options.ruleArgs, { path: projectPath });
258
+ },
259
+
214
260
  // List skills (from skills-index.json)
215
261
  list: () => {
216
262
  const indexPath = path.join(__dirname, '..', 'skills-index.json');
package/cli/rules.js ADDED
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Rules Management
3
+ *
4
+ * Add/remove rules manually for projects where auto-detection
5
+ * doesn't cover all needs.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { copyDir } = require('./utils');
11
+
12
+ const PACKAGE_ROOT = path.join(__dirname, '..');
13
+
14
+ /**
15
+ * Get all available rules from the rules/ directory
16
+ */
17
+ function getAvailableRules() {
18
+ const rulesDir = path.join(PACKAGE_ROOT, 'rules');
19
+
20
+ if (!fs.existsSync(rulesDir)) {
21
+ return [];
22
+ }
23
+
24
+ return fs.readdirSync(rulesDir).filter(item => {
25
+ const itemPath = path.join(rulesDir, item);
26
+ return fs.statSync(itemPath).isDirectory() && !item.startsWith('.');
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Get installed rules from devkit.json
32
+ */
33
+ function getInstalledRules(projectDir) {
34
+ const configPath = path.join(projectDir, '.claude', 'devkit.json');
35
+
36
+ if (!fs.existsSync(configPath)) {
37
+ return { rules: [], manualRules: [] };
38
+ }
39
+
40
+ try {
41
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
42
+ return {
43
+ rules: config.installedRules || [],
44
+ manualRules: config.manualRules || [],
45
+ detectedTypes: config.detectedTypes || []
46
+ };
47
+ } catch (e) {
48
+ return { rules: [], manualRules: [] };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Update devkit.json with new rules
54
+ */
55
+ function updateDevkitConfig(projectDir, updates) {
56
+ const configPath = path.join(projectDir, '.claude', 'devkit.json');
57
+
58
+ if (!fs.existsSync(configPath)) {
59
+ console.log(' No devkit.json found. Run "devkit init" first.');
60
+ return false;
61
+ }
62
+
63
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
64
+ Object.assign(config, updates, { updatedAt: new Date().toISOString() });
65
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * List all available rules
71
+ */
72
+ function listRules(options = {}) {
73
+ const projectDir = options.path || process.cwd();
74
+ const available = getAvailableRules();
75
+ const { rules: installed, manualRules } = getInstalledRules(projectDir);
76
+ const allInstalled = [...new Set([...installed, ...manualRules])];
77
+
78
+ console.log('\n' + '='.repeat(50));
79
+ console.log(' AVAILABLE RULES');
80
+ console.log('='.repeat(50));
81
+
82
+ if (options.installed) {
83
+ // Show only installed rules
84
+ console.log('\nInstalled in this project:');
85
+ if (allInstalled.length === 0) {
86
+ console.log(' (none)');
87
+ } else {
88
+ allInstalled.forEach(rule => {
89
+ const isManual = manualRules.includes(rule);
90
+ console.log(` - ${rule}${isManual ? ' (manual)' : ' (auto-detected)'}`);
91
+ });
92
+ }
93
+ } else {
94
+ // Show all available rules
95
+ console.log(`\nTotal: ${available.length} rules\n`);
96
+ available.forEach(rule => {
97
+ const isInstalled = allInstalled.includes(rule);
98
+ const marker = isInstalled ? '[x]' : '[ ]';
99
+ console.log(` ${marker} ${rule}`);
100
+ });
101
+
102
+ console.log('\n [x] = installed in current project');
103
+ }
104
+
105
+ console.log('');
106
+ return { available, installed: allInstalled };
107
+ }
108
+
109
+ /**
110
+ * Add rules to project
111
+ */
112
+ function addRules(ruleNames, options = {}) {
113
+ const projectDir = options.path || process.cwd();
114
+ const claudeDir = path.join(projectDir, '.claude');
115
+
116
+ if (!fs.existsSync(claudeDir)) {
117
+ console.log('\n No .claude/ folder found. Run "devkit init" first.\n');
118
+ return { success: false, reason: 'not_initialized' };
119
+ }
120
+
121
+ const available = getAvailableRules();
122
+ const { rules: autoRules, manualRules = [] } = getInstalledRules(projectDir);
123
+ const allInstalled = [...new Set([...autoRules, ...manualRules])];
124
+
125
+ console.log('\n' + '='.repeat(50));
126
+ console.log(' ADD RULES');
127
+ console.log('='.repeat(50));
128
+
129
+ const added = [];
130
+ const skipped = [];
131
+ const notFound = [];
132
+
133
+ for (const ruleName of ruleNames) {
134
+ const rule = ruleName.toLowerCase();
135
+
136
+ if (!available.includes(rule)) {
137
+ notFound.push(rule);
138
+ console.log(` [!] ${rule} - not found`);
139
+ continue;
140
+ }
141
+
142
+ if (allInstalled.includes(rule)) {
143
+ skipped.push(rule);
144
+ console.log(` [-] ${rule} - already installed`);
145
+ continue;
146
+ }
147
+
148
+ // Copy rule files
149
+ const srcDir = path.join(PACKAGE_ROOT, 'rules', rule);
150
+ const destDir = path.join(claudeDir, 'rules', rule);
151
+
152
+ if (fs.existsSync(srcDir)) {
153
+ const count = copyDir(srcDir, destDir);
154
+ added.push(rule);
155
+ console.log(` [+] ${rule} - added (${count} files)`);
156
+ }
157
+ }
158
+
159
+ // Update devkit.json
160
+ if (added.length > 0) {
161
+ const newManualRules = [...new Set([...manualRules, ...added])];
162
+ updateDevkitConfig(projectDir, { manualRules: newManualRules });
163
+ }
164
+
165
+ // Summary
166
+ console.log('\n' + '-'.repeat(50));
167
+ if (added.length > 0) {
168
+ console.log(` Added: ${added.join(', ')}`);
169
+ }
170
+ if (skipped.length > 0) {
171
+ console.log(` Skipped (already installed): ${skipped.join(', ')}`);
172
+ }
173
+ if (notFound.length > 0) {
174
+ console.log(` Not found: ${notFound.join(', ')}`);
175
+ console.log(` Run "devkit rules" to see available rules.`);
176
+ }
177
+ console.log('');
178
+
179
+ return { success: true, added, skipped, notFound };
180
+ }
181
+
182
+ /**
183
+ * Remove rules from project
184
+ */
185
+ function removeRules(ruleNames, options = {}) {
186
+ const projectDir = options.path || process.cwd();
187
+ const claudeDir = path.join(projectDir, '.claude');
188
+
189
+ if (!fs.existsSync(claudeDir)) {
190
+ console.log('\n No .claude/ folder found.\n');
191
+ return { success: false, reason: 'not_initialized' };
192
+ }
193
+
194
+ const { rules: autoRules, manualRules = [] } = getInstalledRules(projectDir);
195
+
196
+ console.log('\n' + '='.repeat(50));
197
+ console.log(' REMOVE RULES');
198
+ console.log('='.repeat(50));
199
+
200
+ const removed = [];
201
+ const notInstalled = [];
202
+
203
+ for (const ruleName of ruleNames) {
204
+ const rule = ruleName.toLowerCase();
205
+ const ruleDir = path.join(claudeDir, 'rules', rule);
206
+
207
+ if (!fs.existsSync(ruleDir)) {
208
+ notInstalled.push(rule);
209
+ console.log(` [-] ${rule} - not installed`);
210
+ continue;
211
+ }
212
+
213
+ // Remove rule directory
214
+ fs.rmSync(ruleDir, { recursive: true, force: true });
215
+ removed.push(rule);
216
+ console.log(` [x] ${rule} - removed`);
217
+ }
218
+
219
+ // Update devkit.json
220
+ if (removed.length > 0) {
221
+ const newAutoRules = autoRules.filter(r => !removed.includes(r));
222
+ const newManualRules = manualRules.filter(r => !removed.includes(r));
223
+ updateDevkitConfig(projectDir, {
224
+ installedRules: newAutoRules,
225
+ manualRules: newManualRules
226
+ });
227
+ }
228
+
229
+ // Summary
230
+ console.log('\n' + '-'.repeat(50));
231
+ if (removed.length > 0) {
232
+ console.log(` Removed: ${removed.join(', ')}`);
233
+ }
234
+ if (notInstalled.length > 0) {
235
+ console.log(` Not installed: ${notInstalled.join(', ')}`);
236
+ }
237
+ console.log('');
238
+
239
+ return { success: true, removed, notInstalled };
240
+ }
241
+
242
+ module.exports = {
243
+ getAvailableRules,
244
+ getInstalledRules,
245
+ listRules,
246
+ addRules,
247
+ removeRules
248
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngxtm/devkit",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "Per-project AI skills with smart tech detection - lightweight and context-optimized",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -12,8 +12,7 @@
12
12
  "organize-rules": "node scripts/organize-rules.js",
13
13
  "generate-index": "node scripts/generate-index.js",
14
14
  "test": "node cli/index.js detect",
15
- "sync": "python scripts/sync_all.py",
16
- "sync:rules": "git clone --depth 1 https://github.com/ngxtm/skill-rule.git /tmp/skill-rule && cp -r /tmp/skill-rule/rules/* ./rules/ && rm -rf /tmp/skill-rule",
15
+ "sync:upstream": "node scripts/manual-sync.js",
17
16
  "validate": "python scripts/validate_skills.py"
18
17
  },
19
18
  "files": [