agileflow 2.41.0 → 2.42.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.
@@ -0,0 +1,806 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agileflow-configure.js - Comprehensive configuration management
4
+ *
5
+ * Features:
6
+ * - DETECT: Show current configuration status
7
+ * - MIGRATE: Fix old/invalid formats automatically
8
+ * - ENABLE: Turn on features
9
+ * - DISABLE: Turn off features
10
+ * - PROFILES: Quick presets (full, basic, minimal, none)
11
+ * - RECONFIGURE: Change settings (archival days, etc.)
12
+ *
13
+ * Usage:
14
+ * node scripts/agileflow-configure.js [options]
15
+ *
16
+ * Options:
17
+ * --profile=full|basic|minimal|none Apply a preset
18
+ * --enable=<features> Enable specific features
19
+ * --disable=<features> Disable specific features
20
+ * --archival-days=<N> Set archival threshold
21
+ * --migrate Fix old formats without changing features
22
+ * --validate Check for issues
23
+ * --detect Show current status
24
+ * --help Show help
25
+ *
26
+ * Features: sessionstart, precompact, stop, archival, statusline
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+ const { execSync } = require('child_process');
32
+
33
+ // ============================================================================
34
+ // CONFIGURATION
35
+ // ============================================================================
36
+
37
+ const VERSION = '2.41.0';
38
+
39
+ const FEATURES = {
40
+ sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
41
+ precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
42
+ stop: { hook: 'Stop', script: 'agileflow-stop.sh', type: 'bash' },
43
+ archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
44
+ statusline: { script: 'agileflow-statusline.sh' }
45
+ };
46
+
47
+ const PROFILES = {
48
+ full: {
49
+ description: 'All features enabled',
50
+ enable: ['sessionstart', 'precompact', 'stop', 'archival', 'statusline'],
51
+ archivalDays: 7
52
+ },
53
+ basic: {
54
+ description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
55
+ enable: ['sessionstart', 'precompact', 'archival'],
56
+ disable: ['stop', 'statusline'],
57
+ archivalDays: 7
58
+ },
59
+ minimal: {
60
+ description: 'SessionStart + archival only',
61
+ enable: ['sessionstart', 'archival'],
62
+ disable: ['precompact', 'stop', 'statusline'],
63
+ archivalDays: 7
64
+ },
65
+ none: {
66
+ description: 'Disable all AgileFlow features',
67
+ disable: ['sessionstart', 'precompact', 'stop', 'archival', 'statusline']
68
+ }
69
+ };
70
+
71
+ // ============================================================================
72
+ // COLORS & LOGGING
73
+ // ============================================================================
74
+
75
+ const c = {
76
+ reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
77
+ green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m', cyan: '\x1b[36m'
78
+ };
79
+
80
+ const log = (msg, color = '') => console.log(`${color}${msg}${c.reset}`);
81
+ const success = msg => log(`✅ ${msg}`, c.green);
82
+ const warn = msg => log(`⚠️ ${msg}`, c.yellow);
83
+ const error = msg => log(`❌ ${msg}`, c.red);
84
+ const info = msg => log(`ℹ️ ${msg}`, c.dim);
85
+ const header = msg => log(`\n${msg}`, c.bold + c.cyan);
86
+
87
+ // ============================================================================
88
+ // FILE UTILITIES
89
+ // ============================================================================
90
+
91
+ const ensureDir = dir => { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); };
92
+
93
+ const readJSON = filePath => {
94
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
95
+ catch { return null; }
96
+ };
97
+
98
+ const writeJSON = (filePath, data) => {
99
+ ensureDir(path.dirname(filePath));
100
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
101
+ };
102
+
103
+ const copyTemplate = (templateName, destPath) => {
104
+ const sources = [
105
+ path.join(process.cwd(), '.agileflow', 'templates', templateName),
106
+ path.join(__dirname, templateName),
107
+ path.join(__dirname, '..', 'templates', templateName)
108
+ ];
109
+ for (const src of sources) {
110
+ if (fs.existsSync(src)) {
111
+ fs.copyFileSync(src, destPath);
112
+ try { fs.chmodSync(destPath, '755'); } catch {}
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ };
118
+
119
+ // ============================================================================
120
+ // DETECTION & VALIDATION
121
+ // ============================================================================
122
+
123
+ function detectConfig() {
124
+ const status = {
125
+ git: { initialized: false, remote: null },
126
+ settingsExists: false,
127
+ settingsValid: true,
128
+ settingsIssues: [],
129
+ features: {
130
+ sessionstart: { enabled: false, valid: true, issues: [] },
131
+ precompact: { enabled: false, valid: true, issues: [] },
132
+ stop: { enabled: false, valid: true, issues: [] },
133
+ archival: { enabled: false, threshold: null },
134
+ statusline: { enabled: false, valid: true, issues: [] }
135
+ },
136
+ metadata: { exists: false, version: null }
137
+ };
138
+
139
+ // Git
140
+ if (fs.existsSync('.git')) {
141
+ status.git.initialized = true;
142
+ try {
143
+ status.git.remote = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim();
144
+ } catch {}
145
+ }
146
+
147
+ // Settings file
148
+ if (fs.existsSync('.claude/settings.json')) {
149
+ status.settingsExists = true;
150
+ const settings = readJSON('.claude/settings.json');
151
+
152
+ if (!settings) {
153
+ status.settingsValid = false;
154
+ status.settingsIssues.push('Invalid JSON in settings.json');
155
+ } else {
156
+ // Check hooks
157
+ if (settings.hooks) {
158
+ // SessionStart
159
+ if (settings.hooks.SessionStart) {
160
+ if (Array.isArray(settings.hooks.SessionStart) && settings.hooks.SessionStart.length > 0) {
161
+ const hook = settings.hooks.SessionStart[0];
162
+ if (hook.matcher !== undefined && hook.hooks) {
163
+ status.features.sessionstart.enabled = true;
164
+ } else {
165
+ status.features.sessionstart.enabled = true;
166
+ status.features.sessionstart.valid = false;
167
+ status.features.sessionstart.issues.push('Old format - needs migration');
168
+ }
169
+ } else if (typeof settings.hooks.SessionStart === 'string') {
170
+ status.features.sessionstart.enabled = true;
171
+ status.features.sessionstart.valid = false;
172
+ status.features.sessionstart.issues.push('String format - needs migration');
173
+ }
174
+ }
175
+
176
+ // PreCompact
177
+ if (settings.hooks.PreCompact) {
178
+ if (Array.isArray(settings.hooks.PreCompact) && settings.hooks.PreCompact.length > 0) {
179
+ const hook = settings.hooks.PreCompact[0];
180
+ if (hook.matcher !== undefined && hook.hooks) {
181
+ status.features.precompact.enabled = true;
182
+ } else {
183
+ status.features.precompact.enabled = true;
184
+ status.features.precompact.valid = false;
185
+ status.features.precompact.issues.push('Old format - needs migration');
186
+ }
187
+ } else if (typeof settings.hooks.PreCompact === 'string') {
188
+ status.features.precompact.enabled = true;
189
+ status.features.precompact.valid = false;
190
+ status.features.precompact.issues.push('String format - needs migration');
191
+ }
192
+ }
193
+
194
+ // Stop
195
+ if (settings.hooks.Stop) {
196
+ if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
197
+ const hook = settings.hooks.Stop[0];
198
+ if (hook.matcher !== undefined && hook.hooks) {
199
+ status.features.stop.enabled = true;
200
+ } else {
201
+ status.features.stop.enabled = true;
202
+ status.features.stop.valid = false;
203
+ status.features.stop.issues.push('Old format - needs migration');
204
+ }
205
+ } else if (typeof settings.hooks.Stop === 'string') {
206
+ status.features.stop.enabled = true;
207
+ status.features.stop.valid = false;
208
+ status.features.stop.issues.push('String format - needs migration');
209
+ }
210
+ }
211
+ }
212
+
213
+ // StatusLine
214
+ if (settings.statusLine) {
215
+ status.features.statusline.enabled = true;
216
+ if (typeof settings.statusLine === 'string') {
217
+ status.features.statusline.valid = false;
218
+ status.features.statusline.issues.push('String format - needs type:command');
219
+ } else if (!settings.statusLine.type) {
220
+ status.features.statusline.valid = false;
221
+ status.features.statusline.issues.push('Missing type:command');
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ // Metadata
228
+ const metaPath = 'docs/00-meta/agileflow-metadata.json';
229
+ if (fs.existsSync(metaPath)) {
230
+ status.metadata.exists = true;
231
+ const meta = readJSON(metaPath);
232
+ if (meta) {
233
+ status.metadata.version = meta.version;
234
+ if (meta.archival?.enabled) {
235
+ status.features.archival.enabled = true;
236
+ status.features.archival.threshold = meta.archival.threshold_days;
237
+ }
238
+ }
239
+ }
240
+
241
+ return status;
242
+ }
243
+
244
+ function printStatus(status) {
245
+ header('📊 Current Configuration');
246
+
247
+ // Git
248
+ log(`Git: ${status.git.initialized ? '✅' : '❌'} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
249
+ status.git.initialized ? c.green : c.dim);
250
+
251
+ // Settings
252
+ if (!status.settingsExists) {
253
+ log('Settings: ❌ .claude/settings.json not found', c.dim);
254
+ } else if (!status.settingsValid) {
255
+ log('Settings: ❌ Invalid JSON', c.red);
256
+ } else {
257
+ log('Settings: ✅ .claude/settings.json exists', c.green);
258
+ }
259
+
260
+ // Features
261
+ header('Features:');
262
+
263
+ const printFeature = (name, label) => {
264
+ const f = status.features[name];
265
+ let statusIcon = f.enabled ? '✅' : '❌';
266
+ let statusText = f.enabled ? 'enabled' : 'disabled';
267
+ let color = f.enabled ? c.green : c.dim;
268
+
269
+ if (f.enabled && !f.valid) {
270
+ statusIcon = '⚠️';
271
+ statusText = 'INVALID FORMAT';
272
+ color = c.yellow;
273
+ }
274
+
275
+ log(` ${statusIcon} ${label}: ${statusText}`, color);
276
+
277
+ if (f.issues?.length > 0) {
278
+ f.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
279
+ }
280
+ };
281
+
282
+ printFeature('sessionstart', 'SessionStart Hook');
283
+ printFeature('precompact', 'PreCompact Hook');
284
+ printFeature('stop', 'Stop Hook');
285
+
286
+ const arch = status.features.archival;
287
+ log(` ${arch.enabled ? '✅' : '❌'} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
288
+ arch.enabled ? c.green : c.dim);
289
+
290
+ printFeature('statusline', 'Status Line');
291
+
292
+ // Metadata
293
+ if (status.metadata.exists) {
294
+ log(`\nMetadata: v${status.metadata.version}`, c.dim);
295
+ }
296
+
297
+ // Issues summary
298
+ const hasIssues = Object.values(status.features).some(f => f.issues?.length > 0);
299
+ if (hasIssues) {
300
+ log('\n⚠️ Format issues detected! Run with --migrate to fix.', c.yellow);
301
+ }
302
+
303
+ return hasIssues;
304
+ }
305
+
306
+ // ============================================================================
307
+ // MIGRATION
308
+ // ============================================================================
309
+
310
+ function migrateSettings() {
311
+ header('🔧 Migrating Settings...');
312
+
313
+ if (!fs.existsSync('.claude/settings.json')) {
314
+ warn('No settings.json to migrate');
315
+ return false;
316
+ }
317
+
318
+ const settings = readJSON('.claude/settings.json');
319
+ if (!settings) {
320
+ error('Cannot parse settings.json');
321
+ return false;
322
+ }
323
+
324
+ let migrated = false;
325
+
326
+ // Migrate hooks
327
+ if (settings.hooks) {
328
+ ['SessionStart', 'PreCompact', 'Stop', 'UserPromptSubmit'].forEach(hookName => {
329
+ const hook = settings.hooks[hookName];
330
+ if (!hook) return;
331
+
332
+ // String format → array format
333
+ if (typeof hook === 'string') {
334
+ const isNode = hook.includes('node ') || hook.endsWith('.js');
335
+ settings.hooks[hookName] = [{
336
+ matcher: '',
337
+ hooks: [{ type: 'command', command: isNode ? hook : `bash ${hook}` }]
338
+ }];
339
+ success(`Migrated ${hookName} from string format`);
340
+ migrated = true;
341
+ }
342
+ // Old object format → new format
343
+ else if (Array.isArray(hook) && hook.length > 0) {
344
+ const first = hook[0];
345
+ if (first.enabled !== undefined || first.command !== undefined) {
346
+ // Old format with enabled/command
347
+ if (first.command) {
348
+ settings.hooks[hookName] = [{
349
+ matcher: '',
350
+ hooks: [{ type: 'command', command: first.command }]
351
+ }];
352
+ success(`Migrated ${hookName} from old object format`);
353
+ migrated = true;
354
+ }
355
+ } else if (first.matcher === undefined) {
356
+ // Missing matcher
357
+ settings.hooks[hookName] = [{
358
+ matcher: '',
359
+ hooks: first.hooks || [{ type: 'command', command: 'echo "hook"' }]
360
+ }];
361
+ success(`Migrated ${hookName} - added matcher`);
362
+ migrated = true;
363
+ }
364
+ }
365
+ });
366
+ }
367
+
368
+ // Migrate statusLine
369
+ if (settings.statusLine) {
370
+ if (typeof settings.statusLine === 'string') {
371
+ settings.statusLine = {
372
+ type: 'command',
373
+ command: settings.statusLine,
374
+ padding: 0
375
+ };
376
+ success('Migrated statusLine from string format');
377
+ migrated = true;
378
+ } else if (!settings.statusLine.type) {
379
+ settings.statusLine.type = 'command';
380
+ if (settings.statusLine.refreshInterval) {
381
+ delete settings.statusLine.refreshInterval;
382
+ settings.statusLine.padding = 0;
383
+ }
384
+ success('Migrated statusLine - added type:command');
385
+ migrated = true;
386
+ }
387
+ }
388
+
389
+ if (migrated) {
390
+ // Backup original
391
+ fs.copyFileSync('.claude/settings.json', '.claude/settings.json.backup');
392
+ info('Backed up to .claude/settings.json.backup');
393
+
394
+ writeJSON('.claude/settings.json', settings);
395
+ success('Settings migrated successfully!');
396
+ } else {
397
+ info('No migration needed - formats are correct');
398
+ }
399
+
400
+ return migrated;
401
+ }
402
+
403
+ // ============================================================================
404
+ // ENABLE/DISABLE FEATURES
405
+ // ============================================================================
406
+
407
+ function enableFeature(feature, options = {}) {
408
+ const config = FEATURES[feature];
409
+ if (!config) {
410
+ error(`Unknown feature: ${feature}`);
411
+ return false;
412
+ }
413
+
414
+ ensureDir('.claude');
415
+ ensureDir('scripts');
416
+
417
+ let settings = readJSON('.claude/settings.json') || {};
418
+ settings.hooks = settings.hooks || {};
419
+ settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
420
+
421
+ // Handle hook-based features
422
+ if (config.hook) {
423
+ const scriptPath = `scripts/${config.script}`;
424
+
425
+ // Deploy script
426
+ if (!copyTemplate(config.script, scriptPath)) {
427
+ // Create minimal version
428
+ if (feature === 'sessionstart') {
429
+ fs.writeFileSync(scriptPath, `#!/usr/bin/env node\nconsole.log('AgileFlow v${VERSION} loaded');\n`);
430
+ } else if (feature === 'precompact') {
431
+ fs.writeFileSync(scriptPath, '#!/bin/bash\necho "PreCompact: preserving context"\n');
432
+ } else if (feature === 'stop') {
433
+ fs.writeFileSync(scriptPath, `#!/bin/bash
434
+ git rev-parse --git-dir > /dev/null 2>&1 || exit 0
435
+ CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
436
+ [ "$CHANGES" -gt 0 ] && echo -e "\\n\\033[33m$CHANGES uncommitted change(s)\\033[0m"
437
+ `);
438
+ }
439
+ try { fs.chmodSync(scriptPath, '755'); } catch {}
440
+ warn(`Created minimal ${config.script}`);
441
+ } else {
442
+ success(`Deployed ${config.script}`);
443
+ }
444
+
445
+ // Configure hook
446
+ const command = config.type === 'node'
447
+ ? `node ${scriptPath}`
448
+ : `bash ${scriptPath}`;
449
+
450
+ settings.hooks[config.hook] = [{
451
+ matcher: '',
452
+ hooks: [{ type: 'command', command }]
453
+ }];
454
+ success(`${config.hook} hook enabled`);
455
+ }
456
+
457
+ // Handle archival
458
+ if (feature === 'archival') {
459
+ const days = options.archivalDays || 7;
460
+ const scriptPath = 'scripts/archive-completed-stories.sh';
461
+
462
+ if (!copyTemplate('archive-completed-stories.sh', scriptPath)) {
463
+ warn('Archival script template not found');
464
+ } else {
465
+ success('Deployed archive-completed-stories.sh');
466
+ }
467
+
468
+ // Add to SessionStart hook
469
+ if (settings.hooks.SessionStart?.[0]?.hooks) {
470
+ const hasArchival = settings.hooks.SessionStart[0].hooks.some(
471
+ h => h.command?.includes('archive-completed-stories')
472
+ );
473
+ if (!hasArchival) {
474
+ settings.hooks.SessionStart[0].hooks.push({
475
+ type: 'command',
476
+ command: 'bash scripts/archive-completed-stories.sh --quiet'
477
+ });
478
+ }
479
+ }
480
+
481
+ // Update metadata
482
+ updateMetadata({ archival: { enabled: true, threshold_days: days } });
483
+ success(`Archival enabled (${days} days)`);
484
+ }
485
+
486
+ // Handle statusLine
487
+ if (feature === 'statusline') {
488
+ const scriptPath = 'scripts/agileflow-statusline.sh';
489
+
490
+ if (!copyTemplate('agileflow-statusline.sh', scriptPath)) {
491
+ fs.writeFileSync(scriptPath, `#!/bin/bash
492
+ input=$(cat)
493
+ MODEL=$(echo "$input" | jq -r '.model.display_name // "Claude"')
494
+ echo "[$MODEL] AgileFlow"
495
+ `);
496
+ try { fs.chmodSync(scriptPath, '755'); } catch {}
497
+ warn('Created minimal statusline script');
498
+ } else {
499
+ success('Deployed agileflow-statusline.sh');
500
+ }
501
+
502
+ settings.statusLine = {
503
+ type: 'command',
504
+ command: 'bash scripts/agileflow-statusline.sh',
505
+ padding: 0
506
+ };
507
+ success('Status line enabled');
508
+ }
509
+
510
+ writeJSON('.claude/settings.json', settings);
511
+ updateMetadata({ features: { [feature]: { enabled: true, version: VERSION, at: new Date().toISOString() } } });
512
+ updateGitignore();
513
+
514
+ return true;
515
+ }
516
+
517
+ function disableFeature(feature) {
518
+ const config = FEATURES[feature];
519
+ if (!config) {
520
+ error(`Unknown feature: ${feature}`);
521
+ return false;
522
+ }
523
+
524
+ if (!fs.existsSync('.claude/settings.json')) {
525
+ info(`${feature} already disabled (no settings file)`);
526
+ return true;
527
+ }
528
+
529
+ const settings = readJSON('.claude/settings.json');
530
+ if (!settings) return false;
531
+
532
+ // Disable hook
533
+ if (config.hook && settings.hooks?.[config.hook]) {
534
+ delete settings.hooks[config.hook];
535
+ success(`${config.hook} hook disabled`);
536
+ }
537
+
538
+ // Disable archival
539
+ if (feature === 'archival') {
540
+ // Remove from SessionStart
541
+ if (settings.hooks?.SessionStart?.[0]?.hooks) {
542
+ settings.hooks.SessionStart[0].hooks = settings.hooks.SessionStart[0].hooks.filter(
543
+ h => !h.command?.includes('archive-completed-stories')
544
+ );
545
+ }
546
+ updateMetadata({ archival: { enabled: false } });
547
+ success('Archival disabled');
548
+ }
549
+
550
+ // Disable statusLine
551
+ if (feature === 'statusline' && settings.statusLine) {
552
+ delete settings.statusLine;
553
+ success('Status line disabled');
554
+ }
555
+
556
+ writeJSON('.claude/settings.json', settings);
557
+ updateMetadata({ features: { [feature]: { enabled: false, version: VERSION, at: new Date().toISOString() } } });
558
+
559
+ return true;
560
+ }
561
+
562
+ // ============================================================================
563
+ // METADATA
564
+ // ============================================================================
565
+
566
+ function updateMetadata(updates) {
567
+ const metaPath = 'docs/00-meta/agileflow-metadata.json';
568
+
569
+ if (!fs.existsSync(metaPath)) {
570
+ ensureDir('docs/00-meta');
571
+ writeJSON(metaPath, { version: VERSION, created: new Date().toISOString() });
572
+ }
573
+
574
+ const meta = readJSON(metaPath) || {};
575
+
576
+ // Deep merge
577
+ if (updates.archival) {
578
+ meta.archival = { ...meta.archival, ...updates.archival };
579
+ }
580
+ if (updates.features) {
581
+ meta.features = meta.features || {};
582
+ Object.entries(updates.features).forEach(([key, value]) => {
583
+ meta.features[key] = { ...meta.features[key], ...value };
584
+ });
585
+ }
586
+
587
+ meta.version = VERSION;
588
+ meta.updated = new Date().toISOString();
589
+
590
+ writeJSON(metaPath, meta);
591
+ }
592
+
593
+ function updateGitignore() {
594
+ const entries = [
595
+ '.claude/settings.local.json',
596
+ '.claude/activity.log',
597
+ '.claude/context.log',
598
+ '.claude/hook.log',
599
+ '.claude/prompt-log.txt',
600
+ '.claude/session.log'
601
+ ];
602
+
603
+ let content = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
604
+ let added = false;
605
+
606
+ entries.forEach(entry => {
607
+ if (!content.includes(entry)) {
608
+ content += `\n${entry}`;
609
+ added = true;
610
+ }
611
+ });
612
+
613
+ if (added) {
614
+ fs.writeFileSync('.gitignore', content.trimEnd() + '\n');
615
+ }
616
+ }
617
+
618
+ // ============================================================================
619
+ // PROFILES
620
+ // ============================================================================
621
+
622
+ function applyProfile(profileName, options = {}) {
623
+ const profile = PROFILES[profileName];
624
+ if (!profile) {
625
+ error(`Unknown profile: ${profileName}`);
626
+ log('Available: ' + Object.keys(PROFILES).join(', '));
627
+ return false;
628
+ }
629
+
630
+ header(`🚀 Applying "${profileName}" profile`);
631
+ log(profile.description, c.dim);
632
+
633
+ // Enable features
634
+ if (profile.enable) {
635
+ profile.enable.forEach(f => enableFeature(f, { archivalDays: profile.archivalDays || options.archivalDays }));
636
+ }
637
+
638
+ // Disable features
639
+ if (profile.disable) {
640
+ profile.disable.forEach(f => disableFeature(f));
641
+ }
642
+
643
+ return true;
644
+ }
645
+
646
+ // ============================================================================
647
+ // SUMMARY
648
+ // ============================================================================
649
+
650
+ function printSummary(actions) {
651
+ header('✅ Configuration Complete!');
652
+
653
+ if (actions.enabled?.length > 0) {
654
+ log('\nEnabled:', c.green);
655
+ actions.enabled.forEach(f => log(` ✅ ${f}`, c.green));
656
+ }
657
+
658
+ if (actions.disabled?.length > 0) {
659
+ log('\nDisabled:', c.dim);
660
+ actions.disabled.forEach(f => log(` ❌ ${f}`, c.dim));
661
+ }
662
+
663
+ if (actions.migrated) {
664
+ log('\nMigrated: Fixed format issues', c.yellow);
665
+ }
666
+
667
+ log('\n' + '═'.repeat(55), c.red);
668
+ log('🔴 RESTART CLAUDE CODE NOW!', c.red + c.bold);
669
+ log(' Quit completely, wait 5 seconds, restart', c.red);
670
+ log('═'.repeat(55), c.red);
671
+ }
672
+
673
+ // ============================================================================
674
+ // HELP
675
+ // ============================================================================
676
+
677
+ function printHelp() {
678
+ log(`
679
+ ${c.bold}AgileFlow Configure${c.reset} - Manage AgileFlow features
680
+
681
+ ${c.cyan}Usage:${c.reset}
682
+ node scripts/agileflow-configure.js [options]
683
+
684
+ ${c.cyan}Profiles:${c.reset}
685
+ --profile=full All features (hooks, archival, statusline)
686
+ --profile=basic SessionStart + PreCompact hooks only
687
+ --profile=minimal Just SessionStart hook
688
+ --profile=none Disable all AgileFlow features
689
+
690
+ ${c.cyan}Feature Control:${c.reset}
691
+ --enable=<list> Enable features (comma-separated)
692
+ --disable=<list> Disable features (comma-separated)
693
+
694
+ Features: sessionstart, precompact, stop, archival, statusline
695
+
696
+ ${c.cyan}Settings:${c.reset}
697
+ --archival-days=N Set archival threshold (default: 7)
698
+
699
+ ${c.cyan}Maintenance:${c.reset}
700
+ --migrate Fix old/invalid formats
701
+ --validate Check for issues (same as --detect)
702
+ --detect Show current configuration
703
+
704
+ ${c.cyan}Examples:${c.reset}
705
+ # Quick setup with all features
706
+ node scripts/agileflow-configure.js --profile=full
707
+
708
+ # Enable specific features
709
+ node scripts/agileflow-configure.js --enable=sessionstart,precompact,stop
710
+
711
+ # Disable a feature
712
+ node scripts/agileflow-configure.js --disable=statusline
713
+
714
+ # Fix format issues
715
+ node scripts/agileflow-configure.js --migrate
716
+
717
+ # Check current status
718
+ node scripts/agileflow-configure.js --detect
719
+ `);
720
+ }
721
+
722
+ // ============================================================================
723
+ // MAIN
724
+ // ============================================================================
725
+
726
+ function main() {
727
+ const args = process.argv.slice(2);
728
+
729
+ // Parse arguments
730
+ let profile = null;
731
+ let enable = [];
732
+ let disable = [];
733
+ let archivalDays = 7;
734
+ let migrate = false;
735
+ let detect = false;
736
+ let help = false;
737
+
738
+ args.forEach(arg => {
739
+ if (arg.startsWith('--profile=')) profile = arg.split('=')[1];
740
+ else if (arg.startsWith('--enable=')) enable = arg.split('=')[1].split(',').map(s => s.trim().toLowerCase());
741
+ else if (arg.startsWith('--disable=')) disable = arg.split('=')[1].split(',').map(s => s.trim().toLowerCase());
742
+ else if (arg.startsWith('--archival-days=')) archivalDays = parseInt(arg.split('=')[1]) || 7;
743
+ else if (arg === '--migrate') migrate = true;
744
+ else if (arg === '--detect' || arg === '--validate') detect = true;
745
+ else if (arg === '--help' || arg === '-h') help = true;
746
+ });
747
+
748
+ if (help) {
749
+ printHelp();
750
+ return;
751
+ }
752
+
753
+ // Always detect first
754
+ const status = detectConfig();
755
+ const hasIssues = printStatus(status);
756
+
757
+ // Detect only mode
758
+ if (detect && !migrate && !profile && enable.length === 0 && disable.length === 0) {
759
+ return;
760
+ }
761
+
762
+ // Migrate mode
763
+ if (migrate) {
764
+ migrateSettings();
765
+ return;
766
+ }
767
+
768
+ // Auto-migrate if issues detected
769
+ if (hasIssues && (profile || enable.length > 0)) {
770
+ log('\n⚠️ Auto-migrating invalid formats...', c.yellow);
771
+ migrateSettings();
772
+ }
773
+
774
+ const actions = { enabled: [], disabled: [], migrated: hasIssues };
775
+
776
+ // Apply profile
777
+ if (profile) {
778
+ applyProfile(profile, { archivalDays });
779
+ const p = PROFILES[profile];
780
+ actions.enabled = p.enable || [];
781
+ actions.disabled = p.disable || [];
782
+ }
783
+
784
+ // Enable specific features
785
+ enable.forEach(f => {
786
+ if (enableFeature(f, { archivalDays })) {
787
+ actions.enabled.push(f);
788
+ }
789
+ });
790
+
791
+ // Disable specific features
792
+ disable.forEach(f => {
793
+ if (disableFeature(f)) {
794
+ actions.disabled.push(f);
795
+ }
796
+ });
797
+
798
+ // Print summary if anything changed
799
+ if (actions.enabled.length > 0 || actions.disabled.length > 0) {
800
+ printSummary(actions);
801
+ } else if (!detect) {
802
+ log('\nNo changes made. Use --help for usage.', c.dim);
803
+ }
804
+ }
805
+
806
+ main();