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