@ngxtm/devkit 3.6.0 → 3.7.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.
package/cli/index.js CHANGED
@@ -63,6 +63,8 @@ function parseArgs(args) {
63
63
  options.update = true;
64
64
  } else if (arg === '--clean' || arg === '-c') {
65
65
  options.clean = true;
66
+ } else if (arg === '--no-clean') {
67
+ options.clean = false;
66
68
  } else if (arg === '--installed' || arg === '-i') {
67
69
  options.installed = true;
68
70
  } else if (arg === '--all' || arg === '-a') {
@@ -105,7 +107,7 @@ COMMANDS:
105
107
  Auto-detects tech stack and installs relevant rules.
106
108
 
107
109
  update Update existing installation
108
- Re-detects project type and updates rules accordingly.
110
+ Re-detects project, updates commands, and cleans old rules.
109
111
 
110
112
  detect Show detected technologies for current project
111
113
  Useful to see what devkit will install.
@@ -134,7 +136,7 @@ OPTIONS:
134
136
  --all, -a Install for all supported tools (skip menu)
135
137
  --tools=LIST Install for specific tools (comma-separated)
136
138
  Example: --tools=claude,cursor
137
- --clean, -c Remove rules for technologies no longer detected (with update)
139
+ --no-clean Skip removing rules for technologies no longer detected
138
140
  --installed, -i Show only installed rules (with rules command)
139
141
  --path=DIR Specify project directory (default: current directory)
140
142
  --help, -h Show this help
@@ -150,8 +152,8 @@ EXAMPLES:
150
152
  devkit init --all # Install for all tools
151
153
  devkit init --tools=claude,cursor # Install for specific tools
152
154
  devkit init --force # Overwrite existing installation
153
- devkit update # Update and re-detect technologies
154
- devkit update --clean # Update and remove old rules
155
+ devkit update # Full update (commands + clean old rules)
156
+ devkit update --no-clean # Update without removing old rules
155
157
  devkit detect # Show what would be detected
156
158
  devkit rules # List all available rules
157
159
  devkit rules --installed # Show installed rules
package/cli/update.js CHANGED
@@ -13,7 +13,7 @@ const { execSync } = require('child_process');
13
13
 
14
14
  const { detectProjectType, getRulesForTypes } = require('./detect');
15
15
  const { initProject } = require('./init');
16
- const { copyDir, getDirSize, parseJsonFile } = require('./utils');
16
+ const { copyDir, getDirSize, parseJsonFile, TOOLS } = require('./utils');
17
17
 
18
18
  const VERSION = require('../package.json').version;
19
19
  const PACKAGE_ROOT = path.join(__dirname, '..');
@@ -34,33 +34,134 @@ function checkForUpdates() {
34
34
  }
35
35
 
36
36
  /**
37
- * Update devkit installation
37
+ * Update a single tool installation
38
+ * @param {string} toolId - Tool identifier
39
+ * @param {Object} tool - Tool configuration
40
+ * @param {string} projectDir - Project directory
41
+ * @param {Object} options - Update options
42
+ * @returns {Object} - Update result
43
+ */
44
+ function updateToolInstallation(toolId, tool, projectDir, options = {}) {
45
+ const targetDir = path.join(projectDir, tool.projectPath);
46
+ const configPath = path.join(targetDir, 'devkit.json');
47
+
48
+ if (!fs.existsSync(configPath)) {
49
+ return { success: false, reason: 'not_installed' };
50
+ }
51
+
52
+ const config = parseJsonFile(configPath);
53
+ if (!config) {
54
+ return { success: false, reason: 'corrupted_config' };
55
+ }
56
+
57
+ let updatedCount = 0;
58
+
59
+ // 1. Update commands (if tool supports it)
60
+ if (tool.commandsPath) {
61
+ const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
62
+ const commandsDir = path.join(targetDir, tool.commandsPath);
63
+ if (fs.existsSync(mergedCommandsDir)) {
64
+ updatedCount += copyDir(mergedCommandsDir, commandsDir);
65
+ }
66
+ }
67
+
68
+ // 2. Update rules
69
+ if (tool.rulesPath && options.rules && options.rules.length > 0) {
70
+ const rulesDir = path.join(targetDir, tool.rulesPath);
71
+ for (const ruleType of options.rules) {
72
+ const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
73
+ if (fs.existsSync(srcRulesDir)) {
74
+ const destRulesDir = path.join(rulesDir, ruleType);
75
+ updatedCount += copyDir(srcRulesDir, destRulesDir);
76
+ }
77
+ }
78
+ }
79
+
80
+ // 3. Remove old rules (if clean enabled)
81
+ if (tool.rulesPath && options.removedRules && options.removedRules.length > 0 && options.clean !== false) {
82
+ const rulesDir = path.join(targetDir, tool.rulesPath);
83
+ for (const ruleType of options.removedRules) {
84
+ const ruleDir = path.join(rulesDir, ruleType);
85
+ if (fs.existsSync(ruleDir)) {
86
+ fs.rmSync(ruleDir, { recursive: true, force: true });
87
+ }
88
+ }
89
+ }
90
+
91
+ // 4. Update hooks (if tool supports it)
92
+ if (tool.supportsHooks && tool.hooksPath) {
93
+ const srcHooksDir = path.join(PACKAGE_ROOT, 'hooks');
94
+ const hooksDir = path.join(targetDir, tool.hooksPath);
95
+ if (fs.existsSync(srcHooksDir)) {
96
+ updatedCount += copyDir(srcHooksDir, hooksDir);
97
+ }
98
+ }
99
+
100
+ // 5. Update skills index
101
+ const skillsIndexSrc = path.join(PACKAGE_ROOT, 'skills-index.json');
102
+ if (fs.existsSync(skillsIndexSrc)) {
103
+ fs.copyFileSync(skillsIndexSrc, path.join(targetDir, 'skills-index.json'));
104
+ updatedCount++;
105
+ }
106
+
107
+ // 6. Update devkit.json
108
+ const newConfig = {
109
+ ...config,
110
+ version: VERSION,
111
+ detectedTypes: options.detectedTypes || config.detectedTypes,
112
+ installedRules: options.allRules || config.installedRules,
113
+ updatedAt: new Date().toISOString(),
114
+ stats: {
115
+ totalFiles: config.stats?.totalFiles || 0,
116
+ sizeKB: Math.round(getDirSize(targetDir) / 1024)
117
+ }
118
+ };
119
+ fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
120
+
121
+ return {
122
+ success: true,
123
+ tool: toolId,
124
+ toolName: tool.name,
125
+ path: targetDir,
126
+ updatedFiles: updatedCount,
127
+ sizeKB: newConfig.stats.sizeKB
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Update devkit installation for all installed tools
38
133
  */
39
134
  function updateProject(options = {}) {
40
135
  const projectDir = options.path || process.cwd();
41
- const claudeDir = path.join(projectDir, '.claude');
42
- const configPath = path.join(claudeDir, 'devkit.json');
43
136
 
44
137
  console.log('\n' + '='.repeat(60));
45
138
  console.log(' DEVKIT - UPDATE');
46
139
  console.log('='.repeat(60));
47
140
 
48
- // Check if devkit is installed
49
- if (!fs.existsSync(configPath)) {
141
+ // Find all installed tools
142
+ const installedTools = [];
143
+ let primaryConfig = null;
144
+
145
+ for (const [toolId, tool] of Object.entries(TOOLS)) {
146
+ const targetDir = path.join(projectDir, tool.projectPath);
147
+ const configPath = path.join(targetDir, 'devkit.json');
148
+ if (fs.existsSync(configPath)) {
149
+ const config = parseJsonFile(configPath);
150
+ if (config) {
151
+ installedTools.push({ toolId, tool, config });
152
+ if (!primaryConfig) primaryConfig = config;
153
+ }
154
+ }
155
+ }
156
+
157
+ if (installedTools.length === 0) {
50
158
  console.log('\n No devkit installation found.');
51
159
  console.log(' Run: devkit init\n');
52
160
  return { success: false, reason: 'not_installed' };
53
161
  }
54
162
 
55
- // Read current config with error handling
56
- const config = parseJsonFile(configPath);
57
- if (!config) {
58
- console.log('\n Error: devkit.json is corrupted.');
59
- console.log(' Try reinitializing with: devkit init --force\n');
60
- return { success: false, reason: 'corrupted_config' };
61
- }
62
- console.log(`\n Current version: ${config.version}`);
63
- console.log(` Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
163
+ console.log(`\n Found ${installedTools.length} tool(s): ${installedTools.map(t => t.tool.name).join(', ')}`);
164
+ console.log(` Current version: ${primaryConfig.version}`);
64
165
 
65
166
  // Check for package updates
66
167
  console.log('\n Checking for updates...');
@@ -79,12 +180,11 @@ function updateProject(options = {}) {
79
180
  // Re-detect project type
80
181
  console.log('\n Re-detecting project type...');
81
182
  const newDetected = detectProjectType(projectDir);
82
- const oldDetected = config.detectedTypes || [];
183
+ const oldDetected = primaryConfig.detectedTypes || [];
83
184
 
84
185
  // Find differences
85
186
  const added = newDetected.filter(t => !oldDetected.includes(t));
86
187
  const removed = oldDetected.filter(t => !newDetected.includes(t));
87
- const unchanged = newDetected.filter(t => oldDetected.includes(t));
88
188
 
89
189
  if (added.length > 0) {
90
190
  console.log(` New tech detected: ${added.join(', ')}`);
@@ -92,91 +192,50 @@ function updateProject(options = {}) {
92
192
  if (removed.length > 0) {
93
193
  console.log(` Removed: ${removed.join(', ')}`);
94
194
  }
95
- if (unchanged.length > 0) {
96
- console.log(` Unchanged: ${unchanged.join(', ')}`);
97
- }
98
195
  if (added.length === 0 && removed.length === 0) {
99
196
  console.log(' No changes in detected technologies.');
100
197
  }
101
198
 
102
- // Update rules if needed
103
- if (added.length > 0 || options.force) {
104
- const newRules = getRulesForTypes(added);
105
- const rulesDir = path.join(claudeDir, 'rules');
106
-
107
- console.log('\n Installing new rules...');
108
-
109
- for (const ruleType of newRules) {
110
- const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
111
- if (fs.existsSync(srcRulesDir)) {
112
- const destRulesDir = path.join(rulesDir, ruleType);
113
- const count = copyDir(srcRulesDir, destRulesDir);
114
- console.log(` ${ruleType}: ${count} files`);
115
- }
116
- }
117
- }
118
-
119
- // Remove old rules if needed and user agrees
120
- if (removed.length > 0 && options.clean) {
121
- const rulesDir = path.join(claudeDir, 'rules');
122
-
123
- console.log('\n Removing old rules...');
199
+ // Get all rules to install
200
+ const allRules = getRulesForTypes(newDetected);
201
+ const newRules = getRulesForTypes(added);
202
+
203
+ // Update each tool
204
+ console.log('\n Updating installations...');
205
+ const results = [];
206
+
207
+ for (const { toolId, tool } of installedTools) {
208
+ const result = updateToolInstallation(toolId, tool, projectDir, {
209
+ ...options,
210
+ rules: newRules,
211
+ removedRules: removed,
212
+ allRules,
213
+ detectedTypes: newDetected
214
+ });
124
215
 
125
- for (const ruleType of removed) {
126
- const ruleDir = path.join(rulesDir, ruleType);
127
- if (fs.existsSync(ruleDir)) {
128
- fs.rmSync(ruleDir, { recursive: true, force: true });
129
- console.log(` Removed: ${ruleType}`);
130
- }
216
+ if (result.success) {
217
+ console.log(` [+] ${tool.name}: ${result.updatedFiles} files (${result.sizeKB} KB)`);
218
+ } else {
219
+ console.log(` [-] ${tool.name}: ${result.reason}`);
131
220
  }
132
- }
133
221
 
134
- // Update commands if --force
135
- if (options.force) {
136
- console.log('\n Updating commands...');
137
- const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
138
- const commandsDir = path.join(claudeDir, 'commands');
139
-
140
- if (fs.existsSync(mergedCommandsDir)) {
141
- // Backup existing
142
- const backupDir = path.join(claudeDir, 'commands.backup');
143
- if (fs.existsSync(commandsDir)) {
144
- fs.renameSync(commandsDir, backupDir);
145
- }
146
-
147
- const count = copyDir(mergedCommandsDir, commandsDir);
148
- console.log(` Commands: ${count} files`);
149
-
150
- // Remove backup
151
- if (fs.existsSync(backupDir)) {
152
- fs.rmSync(backupDir, { recursive: true, force: true });
153
- }
154
- }
222
+ results.push(result);
155
223
  }
156
224
 
157
- // Update config
158
- const newConfig = {
159
- ...config,
160
- version: VERSION,
161
- detectedTypes: newDetected,
162
- installedRules: getRulesForTypes(newDetected),
163
- updatedAt: new Date().toISOString(),
164
- stats: {
165
- totalFiles: config.stats?.totalFiles || 0,
166
- sizeKB: Math.round(getDirSize(claudeDir) / 1024)
167
- }
168
- };
169
-
170
- fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
225
+ // Summary
226
+ const successCount = results.filter(r => r.success).length;
227
+ const totalSize = results.reduce((sum, r) => sum + (r.sizeKB || 0), 0);
171
228
 
172
229
  console.log('\n' + '='.repeat(60));
173
230
  console.log(' UPDATE COMPLETE');
174
231
  console.log('='.repeat(60));
175
- console.log(`\n Size: ${newConfig.stats.sizeKB} KB`);
232
+ console.log(`\n Tools updated: ${successCount}/${installedTools.length}`);
233
+ console.log(` Total size: ${totalSize} KB`);
176
234
  console.log(` Detected: ${newDetected.join(', ') || 'generic'}\n`);
177
235
 
178
236
  return {
179
- success: true,
237
+ success: successCount > 0,
238
+ tools: results,
180
239
  added,
181
240
  removed,
182
241
  detected: newDetected
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngxtm/devkit",
3
- "version": "3.6.0",
3
+ "version": "3.7.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": {
@@ -20,7 +20,9 @@ const OUTPUT_FILE = path.join(__dirname, '..', 'SKILLS_INDEX.md');
20
20
  * Parse YAML frontmatter from markdown file
21
21
  */
22
22
  function parseFrontmatter(content) {
23
- const match = content.match(/^---\n([\s\S]*?)\n---/);
23
+ // Normalize line endings to LF
24
+ const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
25
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/);
24
26
  if (!match) return {};
25
27
 
26
28
  const yaml = match[1];