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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cli-advanced-starter-pack",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Advanced Claude Code CLI toolkit - agents, hooks, skills, MCP servers, phased development, and GitHub integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli/menu.js CHANGED
@@ -20,11 +20,13 @@ import { runCreatePhaseDev, showPhasDevMainMenu } from '../commands/create-phase
20
20
  import { runExploreMcp, showExploreMcpMenu } from '../commands/explore-mcp.js';
21
21
  import { runClaudeAudit, showClaudeAuditMenu } from '../commands/claude-audit.js';
22
22
  import { runRoadmap, showRoadmapMenu } from '../commands/roadmap.js';
23
- import { launchPanel } from '../commands/panel.js';
23
+ import { launchPanel, launchPanelInline } from '../commands/panel.js';
24
24
  import { hasTestingConfig } from '../testing/config.js';
25
25
  import { showHelp } from '../commands/help.js';
26
26
  import { hasValidConfig, getVersion, loadTechStack, saveTechStack } from '../utils.js';
27
27
  import { performVersionCheck, formatUpdateBanner } from '../utils/version-check.js';
28
+ import { isHappyMode, shouldUseMobileUI } from '../utils/happy-detect.js';
29
+ import { showMobileMenu, showMobilePanel, showMobileSettings, mobileReturnPrompt } from './mobile-menu.js';
28
30
 
29
31
  /**
30
32
  * Get bypass permissions status from settings.json
@@ -655,8 +657,15 @@ async function configureHappy(techStack) {
655
657
 
656
658
  /**
657
659
  * Show main interactive menu
660
+ * Automatically detects Happy CLI and switches to mobile-optimized UI
658
661
  */
659
662
  export async function showMainMenu() {
663
+ // Check if we should use mobile UI (Happy CLI detected or happyMode.enabled)
664
+ const techStack = loadTechStack();
665
+ if (shouldUseMobileUI(techStack)) {
666
+ return showMobileMainMenu();
667
+ }
668
+
660
669
  console.clear();
661
670
  console.log(chalk.cyan(BANNER));
662
671
  console.log('');
@@ -790,7 +799,7 @@ export async function showMainMenu() {
790
799
  name: 'action',
791
800
  message: 'Select an option:',
792
801
  choices,
793
- pageSize: 10,
802
+ pageSize: 20,
794
803
  },
795
804
  ]);
796
805
 
@@ -1063,3 +1072,164 @@ export function showWarning(message) {
1063
1072
  export function showInfo(message) {
1064
1073
  console.log(chalk.blue(`ℹ ${message}`));
1065
1074
  }
1075
+
1076
+ /**
1077
+ * Mobile-optimized main menu handler
1078
+ * Routes actions from the mobile menu to their handlers
1079
+ */
1080
+ async function showMobileMainMenu() {
1081
+ const action = await showMobileMenu();
1082
+
1083
+ switch (action) {
1084
+ case 'create':
1085
+ const configured = hasValidConfig();
1086
+ if (!configured) {
1087
+ console.log(chalk.yellow('Setup required first.'));
1088
+ const { proceed } = await inquirer.prompt([
1089
+ { type: 'confirm', name: 'proceed', message: 'Run setup?', default: true }
1090
+ ]);
1091
+ if (proceed) await runSetup({});
1092
+ } else {
1093
+ await runCreate({});
1094
+ }
1095
+ break;
1096
+
1097
+ case 'decompose':
1098
+ if (!hasValidConfig()) {
1099
+ console.log(chalk.yellow('Setup required first.'));
1100
+ } else {
1101
+ await runDecompose({});
1102
+ }
1103
+ break;
1104
+
1105
+ case 'sync':
1106
+ if (!hasValidConfig()) {
1107
+ console.log(chalk.yellow('Setup required first.'));
1108
+ } else {
1109
+ await runSync({ subcommand: 'status' });
1110
+ }
1111
+ break;
1112
+
1113
+ case 'setup':
1114
+ await runSetup({});
1115
+ break;
1116
+
1117
+ case 'list':
1118
+ if (!hasValidConfig()) {
1119
+ console.log(chalk.yellow('Setup required first.'));
1120
+ } else {
1121
+ await runList({});
1122
+ }
1123
+ break;
1124
+
1125
+ case 'install':
1126
+ await runInstall({});
1127
+ break;
1128
+
1129
+ case 'panel-inline':
1130
+ // Show panel inline instead of launching new window
1131
+ await showMobilePanelLoop();
1132
+ break;
1133
+
1134
+ case 'test-setup':
1135
+ await runTestSetup({});
1136
+ break;
1137
+
1138
+ case 'agent-creator':
1139
+ await runCreateAgent({});
1140
+ break;
1141
+
1142
+ case 'explore-mcp':
1143
+ await showExploreMcpMenu();
1144
+ break;
1145
+
1146
+ case 'project-settings':
1147
+ await showMobileSettingsLoop();
1148
+ break;
1149
+
1150
+ case 'help':
1151
+ showHelp();
1152
+ break;
1153
+
1154
+ case 'exit':
1155
+ console.log(chalk.dim('Goodbye!'));
1156
+ process.exit(0);
1157
+ }
1158
+
1159
+ // Return to menu unless exiting
1160
+ if (action !== 'exit') {
1161
+ const back = await mobileReturnPrompt();
1162
+ if (back) {
1163
+ await showMobileMainMenu();
1164
+ } else {
1165
+ console.log(chalk.dim('Goodbye!'));
1166
+ process.exit(0);
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ /**
1172
+ * Mobile panel loop - inline panel without new window
1173
+ */
1174
+ async function showMobilePanelLoop() {
1175
+ while (true) {
1176
+ const action = await showMobilePanel();
1177
+
1178
+ if (action === 'back') {
1179
+ return;
1180
+ }
1181
+
1182
+ // Copy command to clipboard and show instructions
1183
+ console.log('');
1184
+ console.log(chalk.cyan(`Command: ${action}`));
1185
+ console.log(chalk.dim('Paste in Claude Code'));
1186
+ console.log('');
1187
+
1188
+ // Try to copy to clipboard
1189
+ try {
1190
+ const { copyToClipboard } = await import('../panel/queue.js');
1191
+ if (copyToClipboard(action)) {
1192
+ console.log(chalk.green('✓ Copied to clipboard'));
1193
+ }
1194
+ } catch {
1195
+ // Clipboard not available
1196
+ }
1197
+
1198
+ await inquirer.prompt([
1199
+ { type: 'input', name: 'continue', message: 'Enter to continue...' }
1200
+ ]);
1201
+ }
1202
+ }
1203
+
1204
+ /**
1205
+ * Mobile settings loop
1206
+ */
1207
+ async function showMobileSettingsLoop() {
1208
+ while (true) {
1209
+ const action = await showMobileSettings();
1210
+
1211
+ if (action === 'back') {
1212
+ return;
1213
+ }
1214
+
1215
+ const techStack = loadTechStack();
1216
+
1217
+ switch (action) {
1218
+ case 'github':
1219
+ await configureGitHub(techStack);
1220
+ break;
1221
+ case 'deployment':
1222
+ await configureDeployment(techStack);
1223
+ break;
1224
+ case 'tunnel':
1225
+ await configureTunnel(techStack);
1226
+ break;
1227
+ case 'token':
1228
+ await configureToken(techStack);
1229
+ break;
1230
+ case 'happy':
1231
+ await configureHappy(techStack);
1232
+ break;
1233
+ }
1234
+ }
1235
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Mobile-Optimized Menu System for Happy CLI
3
+ *
4
+ * Renders menus optimized for mobile screens (max 40 chars).
5
+ * Single-column layout, no overflow, minimal decorations.
6
+ */
7
+
8
+ import chalk from 'chalk';
9
+ import inquirer from 'inquirer';
10
+ import { getVersion, loadTechStack, hasValidConfig } from '../utils.js';
11
+ import { isHappyMode, getMobileWidth } from '../utils/happy-detect.js';
12
+
13
+ // Mobile-friendly banner (40 chars max)
14
+ const MOBILE_BANNER = `
15
+ ${chalk.cyan('╔══════════════════════════════════╗')}
16
+ ${chalk.cyan('║')} ${chalk.bold('CCASP')} ${chalk.dim('v' + getVersion().slice(0, 5))}${' '.repeat(17)}${chalk.cyan('║')}
17
+ ${chalk.cyan('║')} ${chalk.dim('Mobile Menu')}${' '.repeat(21)}${chalk.cyan('║')}
18
+ ${chalk.cyan('╚══════════════════════════════════╝')}`;
19
+
20
+ // Compact panel banner for mobile
21
+ const MOBILE_PANEL_BANNER = `
22
+ ${chalk.cyan('╔══════════════════════════════════╗')}
23
+ ${chalk.cyan('║')} ${chalk.bold('CCASP Panel')}${' '.repeat(20)}${chalk.cyan('║')}
24
+ ${chalk.cyan('╚══════════════════════════════════╝')}`;
25
+
26
+ /**
27
+ * Truncate text to fit mobile width
28
+ * @param {string} text - Text to truncate
29
+ * @param {number} maxLen - Maximum length
30
+ * @returns {string} Truncated text
31
+ */
32
+ function truncate(text, maxLen = 30) {
33
+ if (!text) return '';
34
+ // Strip ANSI codes for length calculation
35
+ const plainText = text.replace(/\x1B\[[0-9;]*m/g, '');
36
+ if (plainText.length <= maxLen) return text;
37
+ return text.slice(0, maxLen - 2) + '..';
38
+ }
39
+
40
+ /**
41
+ * Format a menu item for mobile (single line)
42
+ * @param {string} key - Shortcut key
43
+ * @param {string} label - Item label
44
+ * @returns {string} Formatted menu item
45
+ */
46
+ function formatMobileItem(key, label) {
47
+ const truncLabel = truncate(label, 28);
48
+ return `${chalk.yellow(key + ')')} ${truncLabel}`;
49
+ }
50
+
51
+ /**
52
+ * Show mobile-optimized main menu
53
+ */
54
+ export async function showMobileMenu() {
55
+ console.clear();
56
+ console.log(MOBILE_BANNER);
57
+
58
+ const configured = hasValidConfig();
59
+
60
+ // Status line
61
+ if (configured) {
62
+ console.log(chalk.green(' ✓ Configured'));
63
+ } else {
64
+ console.log(chalk.yellow(' ⚠ Not configured'));
65
+ }
66
+ console.log('');
67
+
68
+ // Single-column menu items
69
+ const choices = [
70
+ { name: formatMobileItem('1', 'Create Task'), value: 'create' },
71
+ { name: formatMobileItem('2', 'Decompose Issue'), value: 'decompose' },
72
+ { name: formatMobileItem('3', 'Sync Tasks'), value: 'sync' },
73
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
74
+ { name: formatMobileItem('4', 'Setup'), value: 'setup' },
75
+ { name: formatMobileItem('5', 'List Tasks'), value: 'list' },
76
+ { name: formatMobileItem('6', 'Install Command'), value: 'install' },
77
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
78
+ { name: formatMobileItem('P', 'Panel (inline)'), value: 'panel-inline' },
79
+ { name: formatMobileItem('T', 'Test Setup'), value: 'test-setup' },
80
+ { name: formatMobileItem('A', 'Agent Creator'), value: 'agent-creator' },
81
+ { name: formatMobileItem('M', 'MCP Explorer'), value: 'explore-mcp' },
82
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
83
+ { name: formatMobileItem('S', 'Settings'), value: 'project-settings' },
84
+ { name: formatMobileItem('?', 'Help'), value: 'help' },
85
+ { name: formatMobileItem('Q', 'Exit'), value: 'exit' },
86
+ ];
87
+
88
+ const { action } = await inquirer.prompt([
89
+ {
90
+ type: 'list',
91
+ name: 'action',
92
+ message: 'Select:',
93
+ choices,
94
+ pageSize: 12,
95
+ },
96
+ ]);
97
+
98
+ return action;
99
+ }
100
+
101
+ /**
102
+ * Show mobile-optimized panel menu (inline, no new window)
103
+ */
104
+ export async function showMobilePanel() {
105
+ console.clear();
106
+ console.log(MOBILE_PANEL_BANNER);
107
+ console.log('');
108
+
109
+ // Single-column panel items
110
+ const choices = [
111
+ new inquirer.Separator(chalk.cyan(' Agents & Skills')),
112
+ { name: formatMobileItem('A', 'Create Agent'), value: '/create-agent' },
113
+ { name: formatMobileItem('H', 'Create Hook'), value: '/create-hook' },
114
+ { name: formatMobileItem('S', 'Create Skill'), value: '/create-skill' },
115
+ { name: formatMobileItem('M', 'Explore MCP'), value: '/explore-mcp' },
116
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
117
+ new inquirer.Separator(chalk.cyan(' Resources')),
118
+ { name: formatMobileItem('1', 'View Agents'), value: '/view-agents' },
119
+ { name: formatMobileItem('2', 'View Skills'), value: '/view-skills' },
120
+ { name: formatMobileItem('3', 'View Hooks'), value: '/view-hooks' },
121
+ { name: formatMobileItem('4', 'All Commands'), value: '/INDEX' },
122
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
123
+ new inquirer.Separator(chalk.cyan(' Quick Actions')),
124
+ { name: formatMobileItem('P', 'Phase Dev Plan'), value: '/phase-dev-plan' },
125
+ { name: formatMobileItem('G', 'GitHub Task'), value: '/github-task' },
126
+ { name: formatMobileItem('T', 'Run E2E Tests'), value: '/e2e-test' },
127
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
128
+ { name: formatMobileItem('B', 'Back'), value: 'back' },
129
+ ];
130
+
131
+ const { action } = await inquirer.prompt([
132
+ {
133
+ type: 'list',
134
+ name: 'action',
135
+ message: 'Select:',
136
+ choices,
137
+ pageSize: 15,
138
+ },
139
+ ]);
140
+
141
+ return action;
142
+ }
143
+
144
+ /**
145
+ * Show mobile-optimized project settings
146
+ */
147
+ export async function showMobileSettings() {
148
+ console.clear();
149
+ console.log(chalk.cyan('╔══════════════════════════════════╗'));
150
+ console.log(chalk.cyan('║') + chalk.bold(' Settings') + ' '.repeat(23) + chalk.cyan('║'));
151
+ console.log(chalk.cyan('╚══════════════════════════════════╝'));
152
+ console.log('');
153
+
154
+ const choices = [
155
+ { name: formatMobileItem('1', 'GitHub Board'), value: 'github' },
156
+ { name: formatMobileItem('2', 'Deployment'), value: 'deployment' },
157
+ { name: formatMobileItem('3', 'Tunnel'), value: 'tunnel' },
158
+ { name: formatMobileItem('4', 'Token Budget'), value: 'token' },
159
+ { name: formatMobileItem('5', 'Happy Mode'), value: 'happy' },
160
+ new inquirer.Separator(chalk.dim('─'.repeat(34))),
161
+ { name: formatMobileItem('B', 'Back'), value: 'back' },
162
+ ];
163
+
164
+ const { action } = await inquirer.prompt([
165
+ {
166
+ type: 'list',
167
+ name: 'action',
168
+ message: 'Select:',
169
+ choices,
170
+ pageSize: 10,
171
+ },
172
+ ]);
173
+
174
+ return action;
175
+ }
176
+
177
+ /**
178
+ * Show a mobile-friendly success message
179
+ * @param {string} message - Success message
180
+ */
181
+ export function showMobileSuccess(message) {
182
+ console.log('');
183
+ console.log(chalk.green(`✓ ${truncate(message, 32)}`));
184
+ console.log('');
185
+ }
186
+
187
+ /**
188
+ * Show a mobile-friendly error message
189
+ * @param {string} message - Error message
190
+ */
191
+ export function showMobileError(message) {
192
+ console.log('');
193
+ console.log(chalk.red(`✗ ${truncate(message, 32)}`));
194
+ console.log('');
195
+ }
196
+
197
+ /**
198
+ * Show a mobile-friendly info box
199
+ * @param {string} title - Box title
200
+ * @param {string[]} lines - Content lines
201
+ */
202
+ export function showMobileBox(title, lines = []) {
203
+ const width = getMobileWidth() - 4; // Account for borders
204
+ console.log('');
205
+ console.log(chalk.cyan('┌' + '─'.repeat(width) + '┐'));
206
+ console.log(chalk.cyan('│') + ` ${truncate(title, width - 2)}`.padEnd(width) + chalk.cyan('│'));
207
+ console.log(chalk.cyan('├' + '─'.repeat(width) + '┤'));
208
+ for (const line of lines) {
209
+ console.log(chalk.cyan('│') + ` ${truncate(line, width - 2)}`.padEnd(width) + chalk.cyan('│'));
210
+ }
211
+ console.log(chalk.cyan('└' + '─'.repeat(width) + '┘'));
212
+ console.log('');
213
+ }
214
+
215
+ /**
216
+ * Prompt to return to menu (mobile version)
217
+ * @returns {Promise<boolean>} True if user wants to return
218
+ */
219
+ export async function mobileReturnPrompt() {
220
+ console.log('');
221
+ const { back } = await inquirer.prompt([
222
+ {
223
+ type: 'confirm',
224
+ name: 'back',
225
+ message: 'Back to menu?',
226
+ default: true,
227
+ },
228
+ ]);
229
+ return back;
230
+ }
@@ -7,8 +7,33 @@
7
7
 
8
8
  import chalk from 'chalk';
9
9
  import ora from 'ora';
10
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
11
- import { join } from 'path';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
11
+ import { join, basename } from 'path';
12
+
13
+ /**
14
+ * Create a backup of CLAUDE.md before modification
15
+ * @param {string} filePath - Path to CLAUDE.md
16
+ * @param {string} cwd - Working directory
17
+ * @returns {string|null} Backup path or null if failed
18
+ */
19
+ function backupClaudeMd(filePath, cwd) {
20
+ if (!existsSync(filePath)) return null;
21
+
22
+ const backupDir = join(cwd, '.claude', 'backups');
23
+ if (!existsSync(backupDir)) {
24
+ mkdirSync(backupDir, { recursive: true });
25
+ }
26
+
27
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
28
+ const backupPath = join(backupDir, `CLAUDE.md.${timestamp}.bak`);
29
+
30
+ try {
31
+ copyFileSync(filePath, backupPath);
32
+ return backupPath;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
12
37
 
13
38
  /**
14
39
  * MCP section marker
@@ -158,6 +183,12 @@ ${mcpSection}
158
183
  };
159
184
  }
160
185
 
186
+ // Backup existing CLAUDE.md before modification
187
+ const backupPath = backupClaudeMd(claudeMdPath, cwd);
188
+ if (backupPath) {
189
+ spinner.text = 'Backed up CLAUDE.md...';
190
+ }
191
+
161
192
  // Check if MCP section exists
162
193
  const hasStartMarker = content.includes(MCP_SECTION_START);
163
194
  const hasEndMarker = content.includes(MCP_SECTION_END);
@@ -193,12 +224,13 @@ ${mcpSection}
193
224
  }
194
225
 
195
226
  writeFileSync(claudeMdPath, content, 'utf8');
196
- spinner.succeed('Updated CLAUDE.md with MCP documentation');
227
+ spinner.succeed(`Updated CLAUDE.md with MCP documentation${backupPath ? ' (backup created)' : ''}`);
197
228
 
198
229
  return {
199
230
  success: true,
200
231
  action: hasStartMarker ? 'updated' : 'appended',
201
232
  path: claudeMdPath,
233
+ backupPath: backupPath || null,
202
234
  };
203
235
  } catch (error) {
204
236
  spinner.fail(`Failed to update CLAUDE.md: ${error.message}`);
@@ -225,6 +257,9 @@ export function removeMcpSection(cwd = process.cwd()) {
225
257
  const hasEndMarker = content.includes(MCP_SECTION_END);
226
258
 
227
259
  if (hasStartMarker && hasEndMarker) {
260
+ // Backup before removal
261
+ backupClaudeMd(claudeMdPath, cwd);
262
+
228
263
  const regex = new RegExp(
229
264
  `\\n?${MCP_SECTION_START}[\\s\\S]*?${MCP_SECTION_END}\\n?`,
230
265
  'g'
@@ -219,6 +219,12 @@ const AVAILABLE_COMMANDS = [
219
219
  category: 'Claude Code',
220
220
  selected: true,
221
221
  },
222
+ {
223
+ name: 'detect-tech-stack',
224
+ description: 'Re-run tech stack detection and update configuration',
225
+ category: 'Analysis',
226
+ selected: true,
227
+ },
222
228
  {
223
229
  name: 'roadmap-sync',
224
230
  description: 'Sync roadmaps with GitHub Project Board',
@@ -2795,3 +2801,86 @@ npx claude-cli-advanced-starter-pack init --force
2795
2801
 
2796
2802
  return content;
2797
2803
  }
2804
+
2805
+ /**
2806
+ * Verify and fix legacy installations (pre-v1.0.8)
2807
+ * Issue #8: Ensures update-check hook is properly configured
2808
+ *
2809
+ * @param {string} projectDir - Project directory to verify
2810
+ * @returns {Object} Verification result with fixes applied
2811
+ */
2812
+ export async function verifyLegacyInstallation(projectDir = process.cwd()) {
2813
+ const fixes = [];
2814
+ const issues = [];
2815
+
2816
+ const claudeDir = join(projectDir, '.claude');
2817
+ const hooksDir = join(claudeDir, 'hooks');
2818
+ const settingsPath = join(claudeDir, 'settings.json');
2819
+ const updateCheckHookPath = join(hooksDir, 'ccasp-update-check.js');
2820
+
2821
+ // Check if this is a CCASP installation
2822
+ if (!existsSync(claudeDir)) {
2823
+ return { isLegacy: false, message: 'No .claude folder found' };
2824
+ }
2825
+
2826
+ // Check 1: Does the update-check hook file exist?
2827
+ if (!existsSync(updateCheckHookPath)) {
2828
+ issues.push('Missing ccasp-update-check.js hook file');
2829
+
2830
+ // Fix: Create the hook file
2831
+ if (!existsSync(hooksDir)) {
2832
+ mkdirSync(hooksDir, { recursive: true });
2833
+ }
2834
+
2835
+ const templatePath = join(__dirname, '..', '..', 'templates', 'hooks', 'ccasp-update-check.template.js');
2836
+ if (existsSync(templatePath)) {
2837
+ const hookContent = readFileSync(templatePath, 'utf8');
2838
+ writeFileSync(updateCheckHookPath, hookContent, 'utf8');
2839
+ fixes.push('Created ccasp-update-check.js hook file');
2840
+ }
2841
+ }
2842
+
2843
+ // Check 2: Is the hook registered in settings.json?
2844
+ if (existsSync(settingsPath)) {
2845
+ try {
2846
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
2847
+
2848
+ // Check if UserPromptSubmit hook exists with update-check
2849
+ const hasUpdateHook = settings.hooks?.UserPromptSubmit?.some(
2850
+ (h) => h.hooks?.some((hook) => hook.command?.includes('ccasp-update-check'))
2851
+ );
2852
+
2853
+ if (!hasUpdateHook) {
2854
+ issues.push('Update-check hook not registered in settings.json');
2855
+
2856
+ // Fix: Add the hook to settings.json
2857
+ if (!settings.hooks) settings.hooks = {};
2858
+ if (!settings.hooks.UserPromptSubmit) {
2859
+ settings.hooks.UserPromptSubmit = [];
2860
+ }
2861
+
2862
+ settings.hooks.UserPromptSubmit.push({
2863
+ matcher: '',
2864
+ hooks: [{
2865
+ type: 'command',
2866
+ command: 'node .claude/hooks/ccasp-update-check.js',
2867
+ }],
2868
+ });
2869
+
2870
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
2871
+ fixes.push('Registered update-check hook in settings.json');
2872
+ }
2873
+ } catch {
2874
+ issues.push('Could not parse settings.json');
2875
+ }
2876
+ }
2877
+
2878
+ return {
2879
+ isLegacy: issues.length > 0,
2880
+ issues,
2881
+ fixes,
2882
+ message: fixes.length > 0
2883
+ ? `Fixed ${fixes.length} legacy installation issue(s)`
2884
+ : 'Installation is up to date',
2885
+ };
2886
+ }