claude-cli-advanced-starter-pack 1.8.2 → 1.8.4

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.
@@ -28,6 +28,8 @@ import {
28
28
  generateMergeExplanation,
29
29
  formatMergeOptions,
30
30
  } from '../utils/smart-merge.js';
31
+ import { registerProject } from '../utils/global-registry.js';
32
+ import { replacePlaceholders } from '../utils/template-engine.js';
31
33
 
32
34
  const __filename = fileURLToPath(import.meta.url);
33
35
  const __dirname = dirname(__filename);
@@ -169,6 +171,18 @@ const AVAILABLE_COMMANDS = [
169
171
  category: 'GitHub',
170
172
  selected: true,
171
173
  },
174
+ {
175
+ name: 'menu-issues-list',
176
+ description: 'Mobile-friendly menu of open GitHub issues',
177
+ category: 'GitHub',
178
+ selected: true,
179
+ },
180
+ {
181
+ name: 'create-task-list-for-issue',
182
+ description: 'Start working on a GitHub issue by number',
183
+ category: 'GitHub',
184
+ selected: true,
185
+ },
172
186
  {
173
187
  name: 'phase-dev-plan',
174
188
  description: 'Create phased development plans (95%+ success rate)',
@@ -1410,10 +1424,206 @@ function generateSettingsLocalJson() {
1410
1424
  }, null, 2);
1411
1425
  }
1412
1426
 
1427
+ /**
1428
+ * Run dev mode - rapid template testing workflow
1429
+ * Loads existing tech-stack.json, processes templates, overwrites commands
1430
+ */
1431
+ async function runDevMode(options = {}) {
1432
+ const cwd = process.cwd();
1433
+ const projectName = basename(cwd);
1434
+ const claudeDir = join(cwd, '.claude');
1435
+ const commandsDir = join(claudeDir, 'commands');
1436
+ const hooksDir = join(claudeDir, 'hooks');
1437
+ const configDir = join(claudeDir, 'config');
1438
+ const techStackPath = join(configDir, 'tech-stack.json');
1439
+
1440
+ console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1441
+ console.log(chalk.magenta.bold(' 🔧 DEV MODE - Template Testing'));
1442
+ console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1443
+ console.log('');
1444
+ console.log(chalk.cyan(` Project: ${chalk.bold(projectName)}`));
1445
+ console.log(chalk.cyan(` Location: ${cwd}`));
1446
+ console.log('');
1447
+
1448
+ // Load existing tech-stack.json
1449
+ let techStack = {};
1450
+ if (existsSync(techStackPath)) {
1451
+ try {
1452
+ techStack = JSON.parse(readFileSync(techStackPath, 'utf8'));
1453
+ console.log(chalk.green(' ✓ Loaded existing tech-stack.json'));
1454
+ } catch (err) {
1455
+ console.log(chalk.yellow(` ⚠ Could not parse tech-stack.json: ${err.message}`));
1456
+ }
1457
+ } else {
1458
+ console.log(chalk.yellow(' ⚠ No tech-stack.json found - templates will have unprocessed placeholders'));
1459
+ }
1460
+
1461
+ // Ensure directories exist
1462
+ if (!existsSync(commandsDir)) {
1463
+ mkdirSync(commandsDir, { recursive: true });
1464
+ console.log(chalk.green(' ✓ Created .claude/commands/'));
1465
+ }
1466
+ if (!existsSync(hooksDir)) {
1467
+ mkdirSync(hooksDir, { recursive: true });
1468
+ console.log(chalk.green(' ✓ Created .claude/hooks/'));
1469
+ }
1470
+
1471
+ // Identify custom commands (no matching template) to preserve
1472
+ const templatesDir = join(__dirname, '..', '..', 'templates', 'commands');
1473
+ const hooksTemplatesDir = join(__dirname, '..', '..', 'templates', 'hooks');
1474
+
1475
+ const templateCommandNames = existsSync(templatesDir)
1476
+ ? readdirSync(templatesDir).filter(f => f.endsWith('.template.md')).map(f => f.replace('.template.md', ''))
1477
+ : [];
1478
+ const templateHookNames = existsSync(hooksTemplatesDir)
1479
+ ? readdirSync(hooksTemplatesDir).filter(f => f.endsWith('.template.js')).map(f => f.replace('.template.js', ''))
1480
+ : [];
1481
+
1482
+ // Find existing custom commands (those without matching templates)
1483
+ const existingCommands = existsSync(commandsDir)
1484
+ ? readdirSync(commandsDir).filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
1485
+ : [];
1486
+ const customCommands = existingCommands.filter(cmd =>
1487
+ !templateCommandNames.includes(cmd) &&
1488
+ cmd !== 'menu' &&
1489
+ cmd !== 'INDEX' &&
1490
+ cmd !== 'README'
1491
+ );
1492
+
1493
+ if (customCommands.length > 0) {
1494
+ console.log(chalk.blue(` 📌 Preserving ${customCommands.length} custom command(s):`));
1495
+ for (const cmd of customCommands) {
1496
+ console.log(chalk.dim(` • /${cmd}`));
1497
+ }
1498
+ console.log('');
1499
+ }
1500
+
1501
+ console.log(chalk.bold('Processing and deploying templates...\n'));
1502
+
1503
+ const spinner = ora('Processing templates...').start();
1504
+ const deployed = { commands: [], hooks: [], preserved: customCommands };
1505
+ const failed = [];
1506
+
1507
+ // Get all command templates
1508
+ if (existsSync(templatesDir)) {
1509
+ const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.template.md'));
1510
+
1511
+ for (const templateFile of templateFiles) {
1512
+ const cmdName = templateFile.replace('.template.md', '');
1513
+ const templatePath = join(templatesDir, templateFile);
1514
+ const outputPath = join(commandsDir, `${cmdName}.md`);
1515
+
1516
+ try {
1517
+ let content = readFileSync(templatePath, 'utf8');
1518
+
1519
+ // Process template with tech-stack values
1520
+ const { content: processed, warnings } = replacePlaceholders(content, techStack, {
1521
+ preserveUnknown: false,
1522
+ warnOnMissing: false,
1523
+ });
1524
+
1525
+ writeFileSync(outputPath, processed, 'utf8');
1526
+ deployed.commands.push(cmdName);
1527
+ } catch (err) {
1528
+ failed.push({ name: cmdName, type: 'command', error: err.message });
1529
+ }
1530
+ }
1531
+ }
1532
+
1533
+ // Also process hook templates
1534
+ if (existsSync(hooksTemplatesDir)) {
1535
+ const hookFiles = readdirSync(hooksTemplatesDir).filter(f => f.endsWith('.template.js'));
1536
+
1537
+ for (const hookFile of hookFiles) {
1538
+ const hookName = hookFile.replace('.template.js', '');
1539
+ const templatePath = join(hooksTemplatesDir, hookFile);
1540
+ const outputPath = join(hooksDir, `${hookName}.js`);
1541
+
1542
+ try {
1543
+ let content = readFileSync(templatePath, 'utf8');
1544
+
1545
+ // Process template with tech-stack values
1546
+ const { content: processed } = replacePlaceholders(content, techStack, {
1547
+ preserveUnknown: false,
1548
+ warnOnMissing: false,
1549
+ });
1550
+
1551
+ writeFileSync(outputPath, processed, 'utf8');
1552
+ deployed.hooks.push(hookName);
1553
+ } catch (err) {
1554
+ failed.push({ name: hookName, type: 'hook', error: err.message });
1555
+ }
1556
+ }
1557
+ }
1558
+
1559
+ // Generate menu command from scratch (uses COMMAND_TEMPLATES)
1560
+ const menuTemplate = COMMAND_TEMPLATES['menu'];
1561
+ if (menuTemplate) {
1562
+ const installedAgents = existsSync(join(claudeDir, 'agents'))
1563
+ ? readdirSync(join(claudeDir, 'agents')).filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
1564
+ : [];
1565
+ const installedSkills = existsSync(join(claudeDir, 'skills'))
1566
+ ? readdirSync(join(claudeDir, 'skills')).filter(f => !f.startsWith('.'))
1567
+ : [];
1568
+ const installedHooks = existsSync(hooksDir)
1569
+ ? readdirSync(hooksDir).filter(f => f.endsWith('.js')).map(f => f.replace('.js', ''))
1570
+ : [];
1571
+
1572
+ const menuContent = generateMenuCommand(projectName, deployed.commands, installedAgents, installedSkills, installedHooks);
1573
+ writeFileSync(join(commandsDir, 'menu.md'), menuContent, 'utf8');
1574
+ deployed.commands.push('menu');
1575
+ }
1576
+
1577
+ // Generate INDEX.md
1578
+ const indexContent = generateIndexFile(deployed.commands, projectName);
1579
+ writeFileSync(join(commandsDir, 'INDEX.md'), indexContent, 'utf8');
1580
+
1581
+ // Generate README.md
1582
+ const readmeContent = generateReadmeFile(deployed.commands, projectName);
1583
+ writeFileSync(join(commandsDir, 'README.md'), readmeContent, 'utf8');
1584
+
1585
+ spinner.stop();
1586
+
1587
+ // Summary
1588
+ console.log('');
1589
+ console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1590
+ console.log(chalk.green.bold(' ✓ DEV MODE: Templates Deployed'));
1591
+ console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
1592
+ console.log('');
1593
+ console.log(chalk.cyan(` Commands: ${deployed.commands.length} deployed`));
1594
+ console.log(chalk.cyan(` Hooks: ${deployed.hooks.length} deployed`));
1595
+ if (deployed.preserved && deployed.preserved.length > 0) {
1596
+ console.log(chalk.blue(` Custom: ${deployed.preserved.length} preserved`));
1597
+ }
1598
+ if (failed.length > 0) {
1599
+ console.log(chalk.yellow(` Failed: ${failed.length}`));
1600
+ for (const f of failed) {
1601
+ console.log(chalk.red(` • ${f.type}/${f.name}: ${f.error}`));
1602
+ }
1603
+ }
1604
+ console.log('');
1605
+ console.log(chalk.dim(' tech-stack.json: Preserved'));
1606
+ console.log(chalk.dim(' settings.json: Preserved'));
1607
+ if (deployed.preserved && deployed.preserved.length > 0) {
1608
+ console.log(chalk.dim(` Custom commands: ${deployed.preserved.join(', ')}`));
1609
+ }
1610
+ console.log('');
1611
+ console.log(chalk.yellow.bold(' ⚠ Restart Claude Code CLI to use new commands'));
1612
+ console.log('');
1613
+
1614
+ return { deployed, failed };
1615
+ }
1616
+
1413
1617
  /**
1414
1618
  * Run the init wizard
1415
1619
  */
1416
1620
  export async function runInit(options = {}) {
1621
+ // DEV MODE: Fast path for template testing
1622
+ if (options.dev) {
1623
+ showHeader('Claude CLI Advanced Starter Pack - DEV MODE');
1624
+ return runDevMode(options);
1625
+ }
1626
+
1417
1627
  showHeader('Claude CLI Advanced Starter Pack - Project Setup');
1418
1628
 
1419
1629
  const cwd = process.cwd();
@@ -2416,6 +2626,20 @@ export async function runInit(options = {}) {
2416
2626
  writeFileSync(ccaspStatePath, JSON.stringify(ccaspState, null, 2), 'utf8');
2417
2627
  console.log(chalk.green(` ✓ Updated ccasp-state.json (v${currentVersion})`));
2418
2628
 
2629
+ // Register project in global registry (unless --no-register flag is set)
2630
+ if (!options.noRegister) {
2631
+ const isNewProject = registerProject(cwd, {
2632
+ name: projectName,
2633
+ version: currentVersion,
2634
+ features: selectedFeatures
2635
+ });
2636
+ if (isNewProject) {
2637
+ console.log(chalk.green(` ✓ Registered project in global CCASP registry`));
2638
+ } else {
2639
+ console.log(chalk.dim(` ○ Updated project in global CCASP registry`));
2640
+ }
2641
+ }
2642
+
2419
2643
  // Show next steps
2420
2644
  console.log(chalk.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
2421
2645
  console.log(chalk.bold('Next Steps:\n'));
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Uninstall Command
3
+ *
4
+ * Fully removes CCASP from a project directory and restores backups.
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import { existsSync, readFileSync, readdirSync, unlinkSync, rmdirSync, copyFileSync, statSync } from 'fs';
9
+ import { join, basename, dirname } from 'path';
10
+ import { createInterface } from 'readline';
11
+ import { unregisterProject } from '../utils/global-registry.js';
12
+
13
+ /**
14
+ * Prompt user for confirmation
15
+ */
16
+ function confirm(question) {
17
+ const rl = createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout
20
+ });
21
+
22
+ return new Promise((resolve) => {
23
+ rl.question(question, (answer) => {
24
+ rl.close();
25
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
26
+ });
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Find all backup files in .claude/backups/
32
+ * Returns map of original filename -> most recent backup path
33
+ */
34
+ function findBackups(projectDir) {
35
+ const backupDir = join(projectDir, '.claude', 'backups');
36
+ const backups = new Map();
37
+
38
+ if (!existsSync(backupDir)) {
39
+ return backups;
40
+ }
41
+
42
+ try {
43
+ const files = readdirSync(backupDir);
44
+
45
+ for (const file of files) {
46
+ if (!file.endsWith('.bak')) continue;
47
+
48
+ // Parse filename: original.YYYY-MM-DDTHH-MM-SS.bak
49
+ const match = file.match(/^(.+)\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.bak$/);
50
+ if (!match) continue;
51
+
52
+ const [, originalName, timestamp] = match;
53
+ const backupPath = join(backupDir, file);
54
+
55
+ // Keep only the most recent backup for each file
56
+ if (!backups.has(originalName) || backups.get(originalName).timestamp < timestamp) {
57
+ backups.set(originalName, {
58
+ path: backupPath,
59
+ timestamp,
60
+ originalName
61
+ });
62
+ }
63
+ }
64
+ } catch (err) {
65
+ console.log(chalk.yellow(` Warning: Could not read backups directory: ${err.message}`));
66
+ }
67
+
68
+ return backups;
69
+ }
70
+
71
+ /**
72
+ * Get list of CCASP-installed files by checking ccasp-state.json or common patterns
73
+ */
74
+ function findCcaspFiles(projectDir) {
75
+ const files = {
76
+ commands: [],
77
+ hooks: [],
78
+ skills: [],
79
+ config: [],
80
+ docs: [],
81
+ agents: []
82
+ };
83
+
84
+ const claudeDir = join(projectDir, '.claude');
85
+ if (!existsSync(claudeDir)) {
86
+ return files;
87
+ }
88
+
89
+ // Commands
90
+ const commandsDir = join(claudeDir, 'commands');
91
+ if (existsSync(commandsDir)) {
92
+ try {
93
+ const entries = readdirSync(commandsDir);
94
+ files.commands = entries
95
+ .filter(f => f.endsWith('.md'))
96
+ .map(f => join(commandsDir, f));
97
+ } catch {}
98
+ }
99
+
100
+ // Hooks
101
+ const hooksDir = join(claudeDir, 'hooks');
102
+ if (existsSync(hooksDir)) {
103
+ try {
104
+ const entries = readdirSync(hooksDir);
105
+ files.hooks = entries
106
+ .filter(f => f.endsWith('.js'))
107
+ .map(f => join(hooksDir, f));
108
+ } catch {}
109
+ }
110
+
111
+ // Skills (directories)
112
+ const skillsDir = join(claudeDir, 'skills');
113
+ if (existsSync(skillsDir)) {
114
+ try {
115
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
116
+ files.skills = entries
117
+ .filter(e => e.isDirectory())
118
+ .map(e => join(skillsDir, e.name));
119
+ } catch {}
120
+ }
121
+
122
+ // Config
123
+ const configDir = join(claudeDir, 'config');
124
+ if (existsSync(configDir)) {
125
+ try {
126
+ const entries = readdirSync(configDir);
127
+ files.config = entries.map(f => join(configDir, f));
128
+ } catch {}
129
+ }
130
+
131
+ // Docs
132
+ const docsDir = join(claudeDir, 'docs');
133
+ if (existsSync(docsDir)) {
134
+ files.docs.push(docsDir);
135
+ }
136
+
137
+ // Agents
138
+ const agentsDir = join(claudeDir, 'agents');
139
+ if (existsSync(agentsDir)) {
140
+ try {
141
+ const entries = readdirSync(agentsDir);
142
+ files.agents = entries
143
+ .filter(f => f.endsWith('.md'))
144
+ .map(f => join(agentsDir, f));
145
+ } catch {}
146
+ }
147
+
148
+ return files;
149
+ }
150
+
151
+ /**
152
+ * Recursively remove a directory
153
+ */
154
+ function removeDir(dirPath) {
155
+ if (!existsSync(dirPath)) return;
156
+
157
+ const entries = readdirSync(dirPath, { withFileTypes: true });
158
+ for (const entry of entries) {
159
+ const fullPath = join(dirPath, entry.name);
160
+ if (entry.isDirectory()) {
161
+ removeDir(fullPath);
162
+ } else {
163
+ unlinkSync(fullPath);
164
+ }
165
+ }
166
+ rmdirSync(dirPath);
167
+ }
168
+
169
+ /**
170
+ * Check if CCASP is installed in the project
171
+ */
172
+ function isCcaspInstalled(projectDir) {
173
+ const markers = [
174
+ join(projectDir, '.claude', 'config', 'ccasp-state.json'),
175
+ join(projectDir, '.claude', 'commands', 'menu.md'),
176
+ join(projectDir, '.claude', 'commands', 'ccasp-setup.md')
177
+ ];
178
+
179
+ return markers.some(m => existsSync(m));
180
+ }
181
+
182
+ /**
183
+ * Run the uninstall command
184
+ */
185
+ export async function runUninstall(options = {}) {
186
+ const projectDir = process.cwd();
187
+
188
+ console.log(chalk.cyan('\n CCASP Uninstall\n'));
189
+ console.log(chalk.dim(` Project: ${projectDir}\n`));
190
+
191
+ // Check if CCASP is installed
192
+ if (!isCcaspInstalled(projectDir)) {
193
+ console.log(chalk.yellow(' CCASP does not appear to be installed in this directory.'));
194
+ console.log(chalk.dim(' No .claude/config/ccasp-state.json or CCASP commands found.\n'));
195
+ return;
196
+ }
197
+
198
+ // Find what's installed
199
+ const ccaspFiles = findCcaspFiles(projectDir);
200
+ const backups = findBackups(projectDir);
201
+
202
+ // Show what will be affected
203
+ console.log(chalk.white.bold(' Found CCASP Installation:\n'));
204
+
205
+ const totalCommands = ccaspFiles.commands.length;
206
+ const totalHooks = ccaspFiles.hooks.length;
207
+ const totalSkills = ccaspFiles.skills.length;
208
+ const totalConfig = ccaspFiles.config.length;
209
+ const totalAgents = ccaspFiles.agents.length;
210
+
211
+ if (totalCommands > 0) {
212
+ console.log(chalk.white(` Commands: ${totalCommands}`));
213
+ }
214
+ if (totalHooks > 0) {
215
+ console.log(chalk.white(` Hooks: ${totalHooks}`));
216
+ }
217
+ if (totalSkills > 0) {
218
+ console.log(chalk.white(` Skills: ${totalSkills}`));
219
+ }
220
+ if (totalConfig > 0) {
221
+ console.log(chalk.white(` Config files: ${totalConfig}`));
222
+ }
223
+ if (totalAgents > 0) {
224
+ console.log(chalk.white(` Agents: ${totalAgents}`));
225
+ }
226
+
227
+ if (backups.size > 0) {
228
+ console.log(chalk.green(`\n Backups found: ${backups.size} (will be restored)`));
229
+ for (const [name, backup] of backups) {
230
+ console.log(chalk.dim(` - ${name} (${backup.timestamp})`));
231
+ }
232
+ }
233
+
234
+ console.log('');
235
+
236
+ // Confirm unless --force
237
+ if (!options.force) {
238
+ const shouldContinue = await confirm(chalk.yellow(' Remove CCASP and restore backups? (y/N): '));
239
+ if (!shouldContinue) {
240
+ console.log(chalk.dim('\n Uninstall cancelled.\n'));
241
+ return;
242
+ }
243
+ }
244
+
245
+ console.log('');
246
+
247
+ // Step 1: Restore backups
248
+ if (backups.size > 0) {
249
+ console.log(chalk.cyan(' Restoring backups...\n'));
250
+
251
+ for (const [originalName, backup] of backups) {
252
+ // Determine where to restore based on file extension
253
+ let targetDir;
254
+ if (originalName.endsWith('.md')) {
255
+ // Could be command, agent, or doc
256
+ if (ccaspFiles.commands.some(c => basename(c) === originalName)) {
257
+ targetDir = join(projectDir, '.claude', 'commands');
258
+ } else if (ccaspFiles.agents.some(a => basename(a) === originalName)) {
259
+ targetDir = join(projectDir, '.claude', 'agents');
260
+ } else {
261
+ targetDir = join(projectDir, '.claude', 'commands'); // Default
262
+ }
263
+ } else if (originalName.endsWith('.js')) {
264
+ targetDir = join(projectDir, '.claude', 'hooks');
265
+ } else if (originalName.endsWith('.json')) {
266
+ targetDir = join(projectDir, '.claude', 'config');
267
+ } else {
268
+ targetDir = join(projectDir, '.claude');
269
+ }
270
+
271
+ const targetPath = join(targetDir, originalName);
272
+
273
+ try {
274
+ copyFileSync(backup.path, targetPath);
275
+ console.log(chalk.green(` ✓ Restored: ${originalName}`));
276
+ } catch (err) {
277
+ console.log(chalk.red(` ✗ Failed to restore ${originalName}: ${err.message}`));
278
+ }
279
+ }
280
+
281
+ console.log('');
282
+ }
283
+
284
+ // Step 2: Remove CCASP files (that weren't backed up)
285
+ console.log(chalk.cyan(' Removing CCASP files...\n'));
286
+
287
+ let removedCount = 0;
288
+
289
+ // Remove commands (except those that were restored from backup)
290
+ for (const cmdPath of ccaspFiles.commands) {
291
+ const name = basename(cmdPath);
292
+ if (!backups.has(name)) {
293
+ try {
294
+ unlinkSync(cmdPath);
295
+ removedCount++;
296
+ } catch {}
297
+ }
298
+ }
299
+
300
+ // Remove hooks (except those that were restored from backup)
301
+ for (const hookPath of ccaspFiles.hooks) {
302
+ const name = basename(hookPath);
303
+ if (!backups.has(name)) {
304
+ try {
305
+ unlinkSync(hookPath);
306
+ removedCount++;
307
+ } catch {}
308
+ }
309
+ }
310
+
311
+ // Remove skills directories
312
+ for (const skillDir of ccaspFiles.skills) {
313
+ try {
314
+ removeDir(skillDir);
315
+ removedCount++;
316
+ } catch {}
317
+ }
318
+
319
+ // Remove config files
320
+ for (const configPath of ccaspFiles.config) {
321
+ try {
322
+ unlinkSync(configPath);
323
+ removedCount++;
324
+ } catch {}
325
+ }
326
+
327
+ // Remove agents (except those restored)
328
+ for (const agentPath of ccaspFiles.agents) {
329
+ const name = basename(agentPath);
330
+ if (!backups.has(name)) {
331
+ try {
332
+ unlinkSync(agentPath);
333
+ removedCount++;
334
+ } catch {}
335
+ }
336
+ }
337
+
338
+ console.log(chalk.green(` ✓ Removed ${removedCount} CCASP file(s)\n`));
339
+
340
+ // Step 3: Remove backup directory
341
+ const backupDir = join(projectDir, '.claude', 'backups');
342
+ if (existsSync(backupDir)) {
343
+ try {
344
+ removeDir(backupDir);
345
+ console.log(chalk.green(' ✓ Removed backups directory\n'));
346
+ } catch {}
347
+ }
348
+
349
+ // Step 4: Clean up empty directories
350
+ const dirsToCheck = [
351
+ join(projectDir, '.claude', 'commands'),
352
+ join(projectDir, '.claude', 'hooks'),
353
+ join(projectDir, '.claude', 'skills'),
354
+ join(projectDir, '.claude', 'config'),
355
+ join(projectDir, '.claude', 'agents'),
356
+ join(projectDir, '.claude', 'docs')
357
+ ];
358
+
359
+ for (const dir of dirsToCheck) {
360
+ if (existsSync(dir)) {
361
+ try {
362
+ const entries = readdirSync(dir);
363
+ if (entries.length === 0) {
364
+ rmdirSync(dir);
365
+ }
366
+ } catch {}
367
+ }
368
+ }
369
+
370
+ // Step 5: Optionally remove entire .claude directory if empty or --all flag
371
+ const claudeDir = join(projectDir, '.claude');
372
+ if (options.all) {
373
+ if (existsSync(claudeDir)) {
374
+ const shouldRemoveAll = options.force || await confirm(chalk.yellow(' Remove entire .claude/ directory? (y/N): '));
375
+ if (shouldRemoveAll) {
376
+ try {
377
+ removeDir(claudeDir);
378
+ console.log(chalk.green(' ✓ Removed .claude/ directory\n'));
379
+ } catch (err) {
380
+ console.log(chalk.red(` ✗ Could not remove .claude/: ${err.message}\n`));
381
+ }
382
+ }
383
+ }
384
+ } else {
385
+ // Check if .claude is empty
386
+ if (existsSync(claudeDir)) {
387
+ try {
388
+ const remaining = readdirSync(claudeDir);
389
+ if (remaining.length === 0) {
390
+ rmdirSync(claudeDir);
391
+ console.log(chalk.green(' ✓ Removed empty .claude/ directory\n'));
392
+ } else {
393
+ console.log(chalk.dim(` .claude/ directory kept (${remaining.length} items remain)`));
394
+ console.log(chalk.dim(' Use --all to remove entire .claude/ directory\n'));
395
+ }
396
+ } catch {}
397
+ }
398
+ }
399
+
400
+ // Remove from global registry
401
+ const wasRegistered = unregisterProject(projectDir);
402
+ if (wasRegistered) {
403
+ console.log(chalk.green(' ✓ Removed from global CCASP registry'));
404
+ }
405
+
406
+ // Done
407
+ console.log(chalk.green.bold('\n ✓ CCASP uninstalled successfully!\n'));
408
+
409
+ if (backups.size > 0) {
410
+ console.log(chalk.dim(' Your backed-up files have been restored.'));
411
+ }
412
+
413
+ console.log(chalk.dim(' To reinstall, run: ccasp init\n'));
414
+ }
@@ -1,5 +1,20 @@
1
1
  {
2
2
  "releases": [
3
+ {
4
+ "version": "1.8.4",
5
+ "date": "2026-01-31",
6
+ "summary": "Release notes pending",
7
+ "highlights": [],
8
+ "newFeatures": {
9
+ "commands": [],
10
+ "agents": [],
11
+ "skills": [],
12
+ "hooks": [],
13
+ "other": []
14
+ },
15
+ "breaking": [],
16
+ "deprecated": []
17
+ },
3
18
  {
4
19
  "version": "1.5.0",
5
20
  "date": "2026-01-30",