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