agileflow 2.89.1 → 2.89.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,16 +28,29 @@
28
28
 
29
29
  const fs = require('fs');
30
30
  const path = require('path');
31
- const { execSync } = require('child_process');
32
31
  const { isValidProfileName, isValidFeatureName, parseIntBounded } = require('../lib/validate');
33
32
 
33
+ // Import extracted modules
34
+ const { c, log, success, header } = require('./lib/configure-utils');
35
+ const { detectConfig, printStatus } = require('./lib/configure-detect');
36
+ const {
37
+ PROFILES,
38
+ enableFeature,
39
+ disableFeature,
40
+ applyProfile,
41
+ setStatuslineComponents,
42
+ listStatuslineComponents,
43
+ migrateSettings,
44
+ upgradeFeatures,
45
+ } = require('./lib/configure-features');
46
+ const { listScripts, showVersionInfo, repairScripts } = require('./lib/configure-repair');
47
+
34
48
  // ============================================================================
35
- // CONFIGURATION
49
+ // VERSION
36
50
  // ============================================================================
37
51
 
38
- // Get version dynamically from metadata or package.json
39
52
  function getVersion() {
40
- // Try agileflow-metadata.json first (user's installed version)
53
+ // Try agileflow-metadata.json first
41
54
  try {
42
55
  const metaPath = path.join(process.cwd(), 'docs/00-meta/agileflow-metadata.json');
43
56
  if (fs.existsSync(metaPath)) {
@@ -55,7 +68,7 @@ function getVersion() {
55
68
  }
56
69
  } catch {}
57
70
 
58
- // Fallback to script's own package.json (when running from npm package)
71
+ // Fallback to script's own package.json
59
72
  try {
60
73
  const pkgPath = path.join(__dirname, '..', 'package.json');
61
74
  if (fs.existsSync(pkgPath)) {
@@ -69,1411 +82,31 @@ function getVersion() {
69
82
 
70
83
  const VERSION = getVersion();
71
84
 
72
- const FEATURES = {
73
- sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
74
- precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
75
- ralphloop: { hook: 'Stop', script: 'ralph-loop.js', type: 'node' },
76
- selfimprove: { hook: 'Stop', script: 'auto-self-improve.js', type: 'node' },
77
- archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
78
- statusline: { script: 'agileflow-statusline.sh' },
79
- autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
80
- damagecontrol: {
81
- preToolUseHooks: true,
82
- scripts: ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'],
83
- patternsFile: 'damage-control-patterns.yaml',
84
- },
85
- askuserquestion: { metadataOnly: true }, // Stored in metadata.features.askUserQuestion
86
- };
87
-
88
- // Complete registry of all scripts that may need repair
89
- const ALL_SCRIPTS = {
90
- // Core feature scripts (linked to FEATURES)
91
- 'agileflow-welcome.js': { feature: 'sessionstart', required: true },
92
- 'precompact-context.sh': { feature: 'precompact', required: true },
93
- 'ralph-loop.js': { feature: 'ralphloop', required: true },
94
- 'auto-self-improve.js': { feature: 'selfimprove', required: true },
95
- 'archive-completed-stories.sh': { feature: 'archival', required: true },
96
- 'agileflow-statusline.sh': { feature: 'statusline', required: true },
97
- 'damage-control-bash.js': { feature: 'damagecontrol', required: true },
98
- 'damage-control-edit.js': { feature: 'damagecontrol', required: true },
99
- 'damage-control-write.js': { feature: 'damagecontrol', required: true },
100
-
101
- // Support scripts (used by commands/agents)
102
- 'obtain-context.js': { usedBy: ['/babysit', '/mentor', '/sprint'] },
103
- 'session-manager.js': { usedBy: ['/session:new', '/session:resume'] },
104
- 'check-update.js': { usedBy: ['SessionStart hook'] },
105
- 'get-env.js': { usedBy: ['SessionStart hook'] },
106
- 'clear-active-command.js': { usedBy: ['session commands'] },
107
-
108
- // Utility scripts
109
- 'compress-status.sh': { usedBy: ['/compress'] },
110
- 'validate-expertise.sh': { usedBy: ['/validate-expertise'] },
111
- 'expertise-metrics.sh': { usedBy: ['agent experts'] },
112
- 'session-coordinator.sh': { usedBy: ['session management'] },
113
- 'validate-tokens.sh': { usedBy: ['token validation'] },
114
- 'worktree-create.sh': { usedBy: ['/session:new'] },
115
- 'resume-session.sh': { usedBy: ['/session:resume'] },
116
- 'init.sh': { usedBy: ['/session:init'] },
117
- 'agileflow-configure.js': { usedBy: ['/configure'] },
118
- 'generate-all.sh': { usedBy: ['content generation'] },
119
- };
120
-
121
- // Statusline component names
122
- const STATUSLINE_COMPONENTS = [
123
- 'agileflow',
124
- 'model',
125
- 'story',
126
- 'epic',
127
- 'wip',
128
- 'context',
129
- 'cost',
130
- 'git',
131
- ];
132
-
133
- const PROFILES = {
134
- full: {
135
- description: 'All features enabled (including experimental Stop hooks)',
136
- enable: [
137
- 'sessionstart',
138
- 'precompact',
139
- 'archival',
140
- 'statusline',
141
- 'ralphloop',
142
- 'selfimprove',
143
- 'askuserquestion',
144
- ],
145
- archivalDays: 30,
146
- },
147
- basic: {
148
- description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
149
- enable: ['sessionstart', 'precompact', 'archival', 'askuserquestion'],
150
- disable: ['statusline', 'ralphloop', 'selfimprove'],
151
- archivalDays: 30,
152
- },
153
- minimal: {
154
- description: 'SessionStart + archival only',
155
- enable: ['sessionstart', 'archival'],
156
- disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove', 'askuserquestion'],
157
- archivalDays: 30,
158
- },
159
- none: {
160
- description: 'Disable all AgileFlow features',
161
- disable: [
162
- 'sessionstart',
163
- 'precompact',
164
- 'archival',
165
- 'statusline',
166
- 'ralphloop',
167
- 'selfimprove',
168
- 'askuserquestion',
169
- ],
170
- },
171
- };
172
-
173
- // ============================================================================
174
- // COLORS & LOGGING
175
- // ============================================================================
176
-
177
- const c = {
178
- reset: '\x1b[0m',
179
- dim: '\x1b[2m',
180
- bold: '\x1b[1m',
181
- green: '\x1b[32m',
182
- yellow: '\x1b[33m',
183
- red: '\x1b[31m',
184
- cyan: '\x1b[36m',
185
- };
186
-
187
- const log = (msg, color = '') => console.log(`${color}${msg}${c.reset}`);
188
- const success = msg => log(`✅ ${msg}`, c.green);
189
- const warn = msg => log(`⚠️ ${msg}`, c.yellow);
190
- const error = msg => log(`❌ ${msg}`, c.red);
191
- const info = msg => log(`ℹ️ ${msg}`, c.dim);
192
- const header = msg => log(`\n${msg}`, c.bold + c.cyan);
193
-
194
- // ============================================================================
195
- // FILE UTILITIES
196
- // ============================================================================
197
-
198
- const ensureDir = dir => {
199
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
200
- };
201
-
202
- const readJSON = filePath => {
203
- try {
204
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
205
- } catch {
206
- return null;
207
- }
208
- };
209
-
210
- const writeJSON = (filePath, data) => {
211
- ensureDir(path.dirname(filePath));
212
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
213
- };
214
-
215
- // Scripts are located in .agileflow/scripts/ (installed by AgileFlow)
216
- const SCRIPTS_DIR = path.join(process.cwd(), '.agileflow', 'scripts');
217
-
218
- const scriptExists = scriptName => {
219
- const scriptPath = path.join(SCRIPTS_DIR, scriptName);
220
- return fs.existsSync(scriptPath);
221
- };
222
-
223
- const getScriptPath = scriptName => {
224
- return `.agileflow/scripts/${scriptName}`;
225
- };
226
-
227
- // ============================================================================
228
- // DETECTION & VALIDATION
229
- // ============================================================================
230
-
231
- function detectConfig() {
232
- const status = {
233
- git: { initialized: false, remote: null },
234
- settingsExists: false,
235
- settingsValid: true,
236
- settingsIssues: [],
237
- features: {
238
- sessionstart: { enabled: false, valid: true, issues: [], version: null, outdated: false },
239
- precompact: { enabled: false, valid: true, issues: [], version: null, outdated: false },
240
- ralphloop: { enabled: false, valid: true, issues: [], version: null, outdated: false },
241
- selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
242
- archival: { enabled: false, threshold: null, version: null, outdated: false },
243
- statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
244
- damagecontrol: {
245
- enabled: false,
246
- valid: true,
247
- issues: [],
248
- version: null,
249
- outdated: false,
250
- level: null,
251
- patternCount: 0,
252
- },
253
- askuserquestion: {
254
- enabled: false,
255
- valid: true,
256
- issues: [],
257
- version: null,
258
- outdated: false,
259
- mode: null,
260
- },
261
- },
262
- metadata: { exists: false, version: null },
263
- currentVersion: VERSION,
264
- hasOutdated: false,
265
- };
266
-
267
- // Git
268
- if (fs.existsSync('.git')) {
269
- status.git.initialized = true;
270
- try {
271
- status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
272
- encoding: 'utf8',
273
- }).trim();
274
- } catch {}
275
- }
276
-
277
- // Settings file
278
- if (fs.existsSync('.claude/settings.json')) {
279
- status.settingsExists = true;
280
- const settings = readJSON('.claude/settings.json');
281
-
282
- if (!settings) {
283
- status.settingsValid = false;
284
- status.settingsIssues.push('Invalid JSON in settings.json');
285
- } else {
286
- // Check hooks
287
- if (settings.hooks) {
288
- // SessionStart
289
- if (settings.hooks.SessionStart) {
290
- if (
291
- Array.isArray(settings.hooks.SessionStart) &&
292
- settings.hooks.SessionStart.length > 0
293
- ) {
294
- const hook = settings.hooks.SessionStart[0];
295
- if (hook.matcher !== undefined && hook.hooks) {
296
- status.features.sessionstart.enabled = true;
297
- } else {
298
- status.features.sessionstart.enabled = true;
299
- status.features.sessionstart.valid = false;
300
- status.features.sessionstart.issues.push('Old format - needs migration');
301
- }
302
- } else if (typeof settings.hooks.SessionStart === 'string') {
303
- status.features.sessionstart.enabled = true;
304
- status.features.sessionstart.valid = false;
305
- status.features.sessionstart.issues.push('String format - needs migration');
306
- }
307
- }
308
-
309
- // PreCompact
310
- if (settings.hooks.PreCompact) {
311
- if (Array.isArray(settings.hooks.PreCompact) && settings.hooks.PreCompact.length > 0) {
312
- const hook = settings.hooks.PreCompact[0];
313
- if (hook.matcher !== undefined && hook.hooks) {
314
- status.features.precompact.enabled = true;
315
- } else {
316
- status.features.precompact.enabled = true;
317
- status.features.precompact.valid = false;
318
- status.features.precompact.issues.push('Old format - needs migration');
319
- }
320
- } else if (typeof settings.hooks.PreCompact === 'string') {
321
- status.features.precompact.enabled = true;
322
- status.features.precompact.valid = false;
323
- status.features.precompact.issues.push('String format - needs migration');
324
- }
325
- }
326
-
327
- // Stop hooks (ralphloop and selfimprove)
328
- if (settings.hooks.Stop) {
329
- if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
330
- const hook = settings.hooks.Stop[0];
331
- if (hook.matcher !== undefined && hook.hooks) {
332
- // Check for each Stop hook feature
333
- for (const h of hook.hooks) {
334
- if (h.command?.includes('ralph-loop')) {
335
- status.features.ralphloop.enabled = true;
336
- }
337
- if (h.command?.includes('auto-self-improve')) {
338
- status.features.selfimprove.enabled = true;
339
- }
340
- }
341
- }
342
- }
343
- }
344
-
345
- // PreToolUse hooks (damage control)
346
- if (settings.hooks.PreToolUse) {
347
- if (Array.isArray(settings.hooks.PreToolUse) && settings.hooks.PreToolUse.length > 0) {
348
- // Check for damage-control hooks by looking for damage-control scripts
349
- const hasBashHook = settings.hooks.PreToolUse.some(
350
- h =>
351
- h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
352
- );
353
- const hasEditHook = settings.hooks.PreToolUse.some(
354
- h =>
355
- h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
356
- );
357
- const hasWriteHook = settings.hooks.PreToolUse.some(
358
- h =>
359
- h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
360
- );
361
-
362
- if (hasBashHook || hasEditHook || hasWriteHook) {
363
- status.features.damagecontrol.enabled = true;
364
- // Count how many of the 3 hooks are present
365
- const hookCount = [hasBashHook, hasEditHook, hasWriteHook].filter(Boolean).length;
366
- if (hookCount < 3) {
367
- status.features.damagecontrol.valid = false;
368
- status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
369
- }
370
- }
371
- }
372
- }
373
- }
374
-
375
- // StatusLine
376
- if (settings.statusLine) {
377
- status.features.statusline.enabled = true;
378
- if (typeof settings.statusLine === 'string') {
379
- status.features.statusline.valid = false;
380
- status.features.statusline.issues.push('String format - needs type:command');
381
- } else if (!settings.statusLine.type) {
382
- status.features.statusline.valid = false;
383
- status.features.statusline.issues.push('Missing type:command');
384
- }
385
- }
386
- }
387
- }
388
-
389
- // Metadata
390
- const metaPath = 'docs/00-meta/agileflow-metadata.json';
391
- if (fs.existsSync(metaPath)) {
392
- status.metadata.exists = true;
393
- const meta = readJSON(metaPath);
394
- if (meta) {
395
- status.metadata.version = meta.version;
396
- if (meta.archival?.enabled) {
397
- status.features.archival.enabled = true;
398
- status.features.archival.threshold = meta.archival.threshold_days;
399
- }
400
-
401
- // Damage control metadata
402
- if (meta.features?.damagecontrol?.enabled) {
403
- status.features.damagecontrol.level =
404
- meta.features.damagecontrol.protectionLevel || 'standard';
405
- }
406
-
407
- // AskUserQuestion metadata
408
- if (meta.features?.askUserQuestion?.enabled) {
409
- status.features.askuserquestion.enabled = true;
410
- status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
411
- }
412
-
413
- // Read feature versions from metadata and check if outdated
414
- if (meta.features) {
415
- // Map metadata keys to status keys (handle camelCase differences)
416
- const featureKeyMap = {
417
- askUserQuestion: 'askuserquestion',
418
- };
419
- Object.entries(meta.features).forEach(([feature, data]) => {
420
- // Use mapped key if exists, otherwise lowercase
421
- const statusKey = featureKeyMap[feature] || feature.toLowerCase();
422
- if (status.features[statusKey] && data.version) {
423
- status.features[statusKey].version = data.version;
424
- // Check if feature version differs from current VERSION
425
- if (data.version !== VERSION && status.features[statusKey].enabled) {
426
- status.features[statusKey].outdated = true;
427
- status.hasOutdated = true;
428
- }
429
- }
430
- });
431
- }
432
- }
433
- }
434
-
435
- return status;
436
- }
437
-
438
- function printStatus(status) {
439
- header('📊 Current Configuration');
440
-
441
- // Git
442
- log(
443
- `Git: ${status.git.initialized ? '✅' : '❌'} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
444
- status.git.initialized ? c.green : c.dim
445
- );
446
-
447
- // Settings
448
- if (!status.settingsExists) {
449
- log('Settings: ❌ .claude/settings.json not found', c.dim);
450
- } else if (!status.settingsValid) {
451
- log('Settings: ❌ Invalid JSON', c.red);
452
- } else {
453
- log('Settings: ✅ .claude/settings.json exists', c.green);
454
- }
455
-
456
- // Features
457
- header('Features:');
458
-
459
- const printFeature = (name, label) => {
460
- const f = status.features[name];
461
- let statusIcon = f.enabled ? '✅' : '❌';
462
- let statusText = f.enabled ? 'enabled' : 'disabled';
463
- let color = f.enabled ? c.green : c.dim;
464
-
465
- if (f.enabled && !f.valid) {
466
- statusIcon = '⚠️';
467
- statusText = 'INVALID FORMAT';
468
- color = c.yellow;
469
- } else if (f.enabled && f.outdated) {
470
- statusIcon = '🔄';
471
- statusText = `outdated (v${f.version} → v${status.currentVersion})`;
472
- color = c.yellow;
473
- }
474
-
475
- log(` ${statusIcon} ${label}: ${statusText}`, color);
476
-
477
- if (f.issues?.length > 0) {
478
- f.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
479
- }
480
- };
481
-
482
- printFeature('sessionstart', 'SessionStart Hook');
483
- printFeature('precompact', 'PreCompact Hook');
484
- printFeature('ralphloop', 'RalphLoop (Stop)');
485
- printFeature('selfimprove', 'SelfImprove (Stop)');
486
-
487
- const arch = status.features.archival;
488
- log(
489
- ` ${arch.enabled ? '✅' : '❌'} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
490
- arch.enabled ? c.green : c.dim
491
- );
492
-
493
- printFeature('statusline', 'Status Line');
494
-
495
- // Damage Control (special display with level info)
496
- const dc = status.features.damagecontrol;
497
- if (dc.enabled) {
498
- let dcStatusText = 'enabled';
499
- if (dc.level) dcStatusText += ` (${dc.level})`;
500
- if (!dc.valid) dcStatusText = 'INCOMPLETE';
501
- const dcIcon = dc.enabled && dc.valid ? '🛡️' : '⚠️';
502
- const dcColor = dc.enabled && dc.valid ? c.green : c.yellow;
503
- log(` ${dcIcon} Damage Control: ${dcStatusText}`, dcColor);
504
- if (dc.issues?.length > 0) {
505
- dc.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
506
- }
507
- } else {
508
- log(` ❌ Damage Control: disabled`, c.dim);
509
- }
510
-
511
- // AskUserQuestion
512
- const auq = status.features.askuserquestion;
513
- if (auq.enabled) {
514
- let auqStatusText = 'enabled';
515
- if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
516
- log(` 💬 AskUserQuestion: ${auqStatusText}`, c.green);
517
- } else {
518
- log(` ❌ AskUserQuestion: disabled`, c.dim);
519
- }
520
-
521
- // Metadata
522
- if (status.metadata.exists) {
523
- log(`\nMetadata: v${status.metadata.version}`, c.dim);
524
- }
525
-
526
- // Issues summary
527
- const hasIssues = Object.values(status.features).some(f => f.issues?.length > 0);
528
- if (hasIssues) {
529
- log('\n⚠️ Format issues detected! Run with --migrate to fix.', c.yellow);
530
- }
531
-
532
- if (status.hasOutdated) {
533
- log('\n🔄 Outdated scripts detected! Run with --upgrade to update.', c.yellow);
534
- }
535
-
536
- return { hasIssues, hasOutdated: status.hasOutdated };
537
- }
538
-
539
- // ============================================================================
540
- // MIGRATION
541
- // ============================================================================
542
-
543
- function migrateSettings() {
544
- header('🔧 Migrating Settings...');
545
-
546
- if (!fs.existsSync('.claude/settings.json')) {
547
- warn('No settings.json to migrate');
548
- return false;
549
- }
550
-
551
- const settings = readJSON('.claude/settings.json');
552
- if (!settings) {
553
- error('Cannot parse settings.json');
554
- return false;
555
- }
556
-
557
- let migrated = false;
558
-
559
- // Migrate hooks to new format
560
- if (settings.hooks) {
561
- ['SessionStart', 'PreCompact', 'UserPromptSubmit', 'Stop'].forEach(hookName => {
562
- const hook = settings.hooks[hookName];
563
- if (!hook) return;
564
-
565
- // String format → array format
566
- if (typeof hook === 'string') {
567
- const isNode = hook.includes('node ') || hook.endsWith('.js');
568
- settings.hooks[hookName] = [
569
- {
570
- matcher: '',
571
- hooks: [{ type: 'command', command: isNode ? hook : `bash ${hook}` }],
572
- },
573
- ];
574
- success(`Migrated ${hookName} from string format`);
575
- migrated = true;
576
- }
577
- // Old object format → new format
578
- else if (Array.isArray(hook) && hook.length > 0) {
579
- const first = hook[0];
580
- if (first.enabled !== undefined || first.command !== undefined) {
581
- // Old format with enabled/command
582
- if (first.command) {
583
- settings.hooks[hookName] = [
584
- {
585
- matcher: '',
586
- hooks: [{ type: 'command', command: first.command }],
587
- },
588
- ];
589
- success(`Migrated ${hookName} from old object format`);
590
- migrated = true;
591
- }
592
- } else if (first.matcher === undefined) {
593
- // Missing matcher
594
- settings.hooks[hookName] = [
595
- {
596
- matcher: '',
597
- hooks: first.hooks || [{ type: 'command', command: 'echo "hook"' }],
598
- },
599
- ];
600
- success(`Migrated ${hookName} - added matcher`);
601
- migrated = true;
602
- }
603
- }
604
- });
605
- }
606
-
607
- // Migrate statusLine
608
- if (settings.statusLine) {
609
- if (typeof settings.statusLine === 'string') {
610
- settings.statusLine = {
611
- type: 'command',
612
- command: settings.statusLine,
613
- padding: 0,
614
- };
615
- success('Migrated statusLine from string format');
616
- migrated = true;
617
- } else if (!settings.statusLine.type) {
618
- settings.statusLine.type = 'command';
619
- if (settings.statusLine.refreshInterval) {
620
- delete settings.statusLine.refreshInterval;
621
- settings.statusLine.padding = 0;
622
- }
623
- success('Migrated statusLine - added type:command');
624
- migrated = true;
625
- }
626
- }
627
-
628
- if (migrated) {
629
- // Backup original
630
- fs.copyFileSync('.claude/settings.json', '.claude/settings.json.backup');
631
- info('Backed up to .claude/settings.json.backup');
632
-
633
- writeJSON('.claude/settings.json', settings);
634
- success('Settings migrated successfully!');
635
- } else {
636
- info('No migration needed - formats are correct');
637
- }
638
-
639
- return migrated;
640
- }
641
-
642
- // ============================================================================
643
- // UPGRADE FEATURES
644
- // ============================================================================
645
-
646
- function upgradeFeatures(status) {
647
- header('🔄 Upgrading Outdated Features...');
648
-
649
- let upgraded = 0;
650
-
651
- Object.entries(status.features).forEach(([feature, data]) => {
652
- if (data.enabled && data.outdated) {
653
- log(`\nUpgrading ${feature}...`, c.cyan);
654
- // Re-enable the feature to deploy latest scripts
655
- if (enableFeature(feature, { archivalDays: data.threshold || 30, isUpgrade: true })) {
656
- upgraded++;
657
- }
658
- }
659
- });
660
-
661
- if (upgraded === 0) {
662
- info('No features needed upgrading');
663
- } else {
664
- success(`Upgraded ${upgraded} feature(s) to v${VERSION}`);
665
- }
666
-
667
- return upgraded > 0;
668
- }
669
-
670
- // ============================================================================
671
- // ENABLE/DISABLE FEATURES
672
- // ============================================================================
673
-
674
- function enableFeature(feature, options = {}) {
675
- const config = FEATURES[feature];
676
- if (!config) {
677
- error(`Unknown feature: ${feature}`);
678
- return false;
679
- }
680
-
681
- ensureDir('.claude');
682
-
683
- const settings = readJSON('.claude/settings.json') || {};
684
- settings.hooks = settings.hooks || {};
685
- settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
686
-
687
- // Handle hook-based features
688
- if (config.hook) {
689
- const scriptPath = getScriptPath(config.script);
690
-
691
- // Verify script exists
692
- if (!scriptExists(config.script)) {
693
- error(`Script not found: ${scriptPath}`);
694
- info('Run "npx agileflow update" to reinstall scripts');
695
- return false;
696
- }
697
-
698
- // Use absolute path so hooks work from any subdirectory
699
- const absoluteScriptPath = path.join(process.cwd(), scriptPath);
700
-
701
- // Stop hooks use error suppression to avoid blocking Claude
702
- const isStoHook = config.hook === 'Stop';
703
- const command =
704
- config.type === 'node'
705
- ? `node ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`
706
- : `bash ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`;
707
-
708
- if (isStoHook) {
709
- // Stop hooks stack - add to existing hooks instead of replacing
710
- if (!settings.hooks.Stop) {
711
- settings.hooks.Stop = [{ matcher: '', hooks: [] }];
712
- } else if (!Array.isArray(settings.hooks.Stop) || settings.hooks.Stop.length === 0) {
713
- settings.hooks.Stop = [{ matcher: '', hooks: [] }];
714
- } else if (!settings.hooks.Stop[0].hooks) {
715
- settings.hooks.Stop[0].hooks = [];
716
- }
717
-
718
- // Check if this script is already added
719
- const hasHook = settings.hooks.Stop[0].hooks.some(h => h.command?.includes(config.script));
720
-
721
- if (!hasHook) {
722
- settings.hooks.Stop[0].hooks.push({ type: 'command', command });
723
- success(`Stop hook added (${config.script})`);
724
- } else {
725
- info(`${feature} already enabled`);
726
- }
727
- } else {
728
- // Other hooks (SessionStart, PreCompact) replace entirely
729
- settings.hooks[config.hook] = [
730
- {
731
- matcher: '',
732
- hooks: [{ type: 'command', command }],
733
- },
734
- ];
735
- success(`${config.hook} hook enabled (${config.script})`);
736
- }
737
- }
738
-
739
- // Handle archival
740
- if (feature === 'archival') {
741
- const days = options.archivalDays || 30;
742
- const scriptPath = getScriptPath('archive-completed-stories.sh');
743
-
744
- if (!scriptExists('archive-completed-stories.sh')) {
745
- error(`Script not found: ${scriptPath}`);
746
- info('Run "npx agileflow update" to reinstall scripts');
747
- return false;
748
- }
749
-
750
- // Use absolute path so hooks work from any subdirectory
751
- const absoluteScriptPath = path.join(process.cwd(), scriptPath);
752
- if (settings.hooks.SessionStart?.[0]?.hooks) {
753
- const hasArchival = settings.hooks.SessionStart[0].hooks.some(h =>
754
- h.command?.includes('archive-completed-stories')
755
- );
756
- if (!hasArchival) {
757
- settings.hooks.SessionStart[0].hooks.push({
758
- type: 'command',
759
- command: `bash ${absoluteScriptPath} --quiet`,
760
- });
761
- }
762
- }
763
-
764
- // Update metadata
765
- updateMetadata({ archival: { enabled: true, threshold_days: days } });
766
- success(`Archival enabled (${days} days)`);
767
- }
768
-
769
- // Handle statusLine
770
- if (feature === 'statusline') {
771
- const scriptPath = getScriptPath('agileflow-statusline.sh');
772
-
773
- if (!scriptExists('agileflow-statusline.sh')) {
774
- error(`Script not found: ${scriptPath}`);
775
- info('Run "npx agileflow update" to reinstall scripts');
776
- return false;
777
- }
778
-
779
- // Use absolute path so hooks work from any subdirectory
780
- const absoluteScriptPath = path.join(process.cwd(), scriptPath);
781
- settings.statusLine = {
782
- type: 'command',
783
- command: `bash ${absoluteScriptPath}`,
784
- padding: 0,
785
- };
786
- success('Status line enabled');
787
- }
788
-
789
- // Handle autoupdate (metadata only, no hooks needed)
790
- if (feature === 'autoupdate') {
791
- updateMetadata({
792
- updates: {
793
- autoUpdate: true,
794
- showChangelog: true,
795
- },
796
- });
797
- success('Auto-update enabled');
798
- info('AgileFlow will check for updates every session and update automatically');
799
- return true; // Skip settings.json write for this feature
800
- }
801
-
802
- // Handle askuserquestion (metadata only, no hooks needed)
803
- if (feature === 'askuserquestion') {
804
- const mode = options.mode || 'all';
805
- updateMetadata({
806
- features: {
807
- askUserQuestion: {
808
- enabled: true,
809
- mode: mode,
810
- version: VERSION,
811
- at: new Date().toISOString(),
812
- },
813
- },
814
- });
815
- success(`AskUserQuestion enabled (mode: ${mode})`);
816
- info('All commands will end with AskUserQuestion tool for guided interaction');
817
- return true; // Skip settings.json write for this feature
818
- }
819
-
820
- // Handle damage control (PreToolUse hooks)
821
- if (feature === 'damagecontrol') {
822
- const level = options.protectionLevel || 'standard';
823
-
824
- // Verify all required scripts exist
825
- const requiredScripts = [
826
- 'damage-control-bash.js',
827
- 'damage-control-edit.js',
828
- 'damage-control-write.js',
829
- ];
830
- for (const script of requiredScripts) {
831
- if (!scriptExists(script)) {
832
- error(`Script not found: ${getScriptPath(script)}`);
833
- info('Run "npx agileflow update" to reinstall scripts');
834
- return false;
835
- }
836
- }
837
-
838
- // Deploy patterns file if not exists
839
- const patternsDir = path.join(process.cwd(), '.agileflow', 'config');
840
- const patternsDest = path.join(patternsDir, 'damage-control-patterns.yaml');
841
- if (!fs.existsSync(patternsDest)) {
842
- ensureDir(patternsDir);
843
- // Try to copy from templates
844
- const templatePath = path.join(
845
- process.cwd(),
846
- '.agileflow',
847
- 'templates',
848
- 'damage-control-patterns.yaml'
849
- );
850
- if (fs.existsSync(templatePath)) {
851
- fs.copyFileSync(templatePath, patternsDest);
852
- success('Deployed damage control patterns');
853
- } else {
854
- warn('No patterns template found - hooks will use defaults');
855
- }
856
- }
857
-
858
- // Initialize PreToolUse array if not exists
859
- if (!settings.hooks.PreToolUse) {
860
- settings.hooks.PreToolUse = [];
861
- }
862
-
863
- // Helper to add or update a PreToolUse hook
864
- const addPreToolUseHook = (matcher, scriptName) => {
865
- const scriptPath = path.join(process.cwd(), '.agileflow', 'scripts', scriptName);
866
-
867
- // Remove existing hook for this matcher if present
868
- settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => h.matcher !== matcher);
869
-
870
- // Add new hook
871
- settings.hooks.PreToolUse.push({
872
- matcher,
873
- hooks: [{ type: 'command', command: `node ${scriptPath}`, timeout: 5 }],
874
- });
875
- };
876
-
877
- // Add hooks for Bash, Edit, Write tools
878
- addPreToolUseHook('Bash', 'damage-control-bash.js');
879
- addPreToolUseHook('Edit', 'damage-control-edit.js');
880
- addPreToolUseHook('Write', 'damage-control-write.js');
881
-
882
- success('Damage control PreToolUse hooks enabled');
883
-
884
- // Update metadata with protection level
885
- updateMetadata({
886
- features: {
887
- damagecontrol: {
888
- enabled: true,
889
- protectionLevel: level,
890
- version: VERSION,
891
- at: new Date().toISOString(),
892
- },
893
- },
894
- });
895
-
896
- writeJSON('.claude/settings.json', settings);
897
- updateGitignore();
898
-
899
- return true;
900
- }
901
-
902
- writeJSON('.claude/settings.json', settings);
903
- updateMetadata({
904
- features: { [feature]: { enabled: true, version: VERSION, at: new Date().toISOString() } },
905
- });
906
- updateGitignore();
907
-
908
- return true;
909
- }
910
-
911
- function disableFeature(feature) {
912
- const config = FEATURES[feature];
913
- if (!config) {
914
- error(`Unknown feature: ${feature}`);
915
- return false;
916
- }
917
-
918
- if (!fs.existsSync('.claude/settings.json')) {
919
- info(`${feature} already disabled (no settings file)`);
920
- return true;
921
- }
922
-
923
- const settings = readJSON('.claude/settings.json');
924
- if (!settings) return false;
925
-
926
- // Disable hook
927
- if (config.hook && settings.hooks?.[config.hook]) {
928
- if (config.hook === 'Stop') {
929
- // Stop hooks stack - remove only this script, not the entire hook
930
- if (settings.hooks.Stop?.[0]?.hooks) {
931
- const before = settings.hooks.Stop[0].hooks.length;
932
- settings.hooks.Stop[0].hooks = settings.hooks.Stop[0].hooks.filter(
933
- h => !h.command?.includes(config.script)
934
- );
935
- const after = settings.hooks.Stop[0].hooks.length;
936
-
937
- if (before > after) {
938
- success(`Stop hook removed (${config.script})`);
939
- }
940
-
941
- // If no more Stop hooks, remove the entire Stop hook
942
- if (settings.hooks.Stop[0].hooks.length === 0) {
943
- delete settings.hooks.Stop;
944
- }
945
- }
946
- } else {
947
- delete settings.hooks[config.hook];
948
- success(`${config.hook} hook disabled`);
949
- }
950
- }
951
-
952
- // Disable archival
953
- if (feature === 'archival') {
954
- // Remove from SessionStart
955
- if (settings.hooks?.SessionStart?.[0]?.hooks) {
956
- settings.hooks.SessionStart[0].hooks = settings.hooks.SessionStart[0].hooks.filter(
957
- h => !h.command?.includes('archive-completed-stories')
958
- );
959
- }
960
- updateMetadata({ archival: { enabled: false } });
961
- success('Archival disabled');
962
- }
963
-
964
- // Disable statusLine
965
- if (feature === 'statusline' && settings.statusLine) {
966
- delete settings.statusLine;
967
- success('Status line disabled');
968
- }
969
-
970
- // Disable autoupdate
971
- if (feature === 'autoupdate') {
972
- updateMetadata({
973
- updates: {
974
- autoUpdate: false,
975
- },
976
- });
977
- success('Auto-update disabled');
978
- return true; // Skip settings.json write for this feature
979
- }
980
-
981
- // Disable askuserquestion
982
- if (feature === 'askuserquestion') {
983
- updateMetadata({
984
- features: {
985
- askUserQuestion: {
986
- enabled: false,
987
- version: VERSION,
988
- at: new Date().toISOString(),
989
- },
990
- },
991
- });
992
- success('AskUserQuestion disabled');
993
- info('Commands will end with natural text questions instead of AskUserQuestion tool');
994
- return true; // Skip settings.json write for this feature
995
- }
996
-
997
- // Disable damage control (PreToolUse hooks)
998
- if (feature === 'damagecontrol') {
999
- if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
1000
- const before = settings.hooks.PreToolUse.length;
1001
-
1002
- // Remove damage-control hooks (Bash, Edit, Write matchers with damage-control scripts)
1003
- settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => {
1004
- const isDamageControlHook = h.hooks?.some(hk => hk.command?.includes('damage-control'));
1005
- return !isDamageControlHook;
1006
- });
1007
-
1008
- const after = settings.hooks.PreToolUse.length;
1009
-
1010
- if (before > after) {
1011
- success(`Removed ${before - after} damage control PreToolUse hook(s)`);
1012
- }
1013
-
1014
- // If no more PreToolUse hooks, remove the entire array
1015
- if (settings.hooks.PreToolUse.length === 0) {
1016
- delete settings.hooks.PreToolUse;
1017
- }
1018
- }
1019
-
1020
- // Update metadata
1021
- updateMetadata({
1022
- features: {
1023
- damagecontrol: {
1024
- enabled: false,
1025
- version: VERSION,
1026
- at: new Date().toISOString(),
1027
- },
1028
- },
1029
- });
1030
-
1031
- writeJSON('.claude/settings.json', settings);
1032
- success('Damage control disabled');
1033
- return true;
1034
- }
1035
-
1036
- writeJSON('.claude/settings.json', settings);
1037
- updateMetadata({
1038
- features: { [feature]: { enabled: false, version: VERSION, at: new Date().toISOString() } },
1039
- });
1040
-
1041
- return true;
1042
- }
1043
-
1044
- // ============================================================================
1045
- // METADATA
1046
- // ============================================================================
1047
-
1048
- function updateMetadata(updates) {
1049
- const metaPath = 'docs/00-meta/agileflow-metadata.json';
1050
-
1051
- if (!fs.existsSync(metaPath)) {
1052
- ensureDir('docs/00-meta');
1053
- writeJSON(metaPath, { version: VERSION, created: new Date().toISOString() });
1054
- }
1055
-
1056
- const meta = readJSON(metaPath) || {};
1057
-
1058
- // Deep merge
1059
- if (updates.archival) {
1060
- meta.archival = { ...meta.archival, ...updates.archival };
1061
- }
1062
- if (updates.features) {
1063
- meta.features = meta.features || {};
1064
- Object.entries(updates.features).forEach(([key, value]) => {
1065
- meta.features[key] = { ...meta.features[key], ...value };
1066
- });
1067
- }
1068
- if (updates.updates) {
1069
- meta.updates = { ...meta.updates, ...updates.updates };
1070
- }
1071
-
1072
- meta.version = VERSION;
1073
- meta.updated = new Date().toISOString();
1074
-
1075
- writeJSON(metaPath, meta);
1076
- }
1077
-
1078
- function updateGitignore() {
1079
- const entries = [
1080
- '.claude/settings.local.json',
1081
- '.claude/activity.log',
1082
- '.claude/context.log',
1083
- '.claude/hook.log',
1084
- '.claude/prompt-log.txt',
1085
- '.claude/session.log',
1086
- ];
1087
-
1088
- let content = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
1089
- let added = false;
1090
-
1091
- entries.forEach(entry => {
1092
- if (!content.includes(entry)) {
1093
- content += `\n${entry}`;
1094
- added = true;
1095
- }
1096
- });
1097
-
1098
- if (added) {
1099
- fs.writeFileSync('.gitignore', content.trimEnd() + '\n');
1100
- }
1101
- }
1102
-
1103
- // ============================================================================
1104
- // STATUSLINE COMPONENTS
1105
- // ============================================================================
1106
-
1107
- function setStatuslineComponents(enableComponents = [], disableComponents = []) {
1108
- const metaPath = 'docs/00-meta/agileflow-metadata.json';
1109
-
1110
- if (!fs.existsSync(metaPath)) {
1111
- warn('No metadata file found - run with --enable=statusline first');
1112
- return false;
1113
- }
1114
-
1115
- const meta = readJSON(metaPath);
1116
- if (!meta) {
1117
- error('Cannot parse metadata file');
1118
- return false;
1119
- }
1120
-
1121
- // Ensure statusline.components structure exists
1122
- meta.features = meta.features || {};
1123
- meta.features.statusline = meta.features.statusline || {};
1124
- meta.features.statusline.components = meta.features.statusline.components || {};
1125
-
1126
- // Set defaults for any missing components
1127
- STATUSLINE_COMPONENTS.forEach(comp => {
1128
- if (meta.features.statusline.components[comp] === undefined) {
1129
- meta.features.statusline.components[comp] = true;
1130
- }
1131
- });
1132
-
1133
- // Enable specified components
1134
- enableComponents.forEach(comp => {
1135
- if (STATUSLINE_COMPONENTS.includes(comp)) {
1136
- meta.features.statusline.components[comp] = true;
1137
- success(`Statusline component enabled: ${comp}`);
1138
- } else {
1139
- warn(`Unknown component: ${comp} (available: ${STATUSLINE_COMPONENTS.join(', ')})`);
1140
- }
1141
- });
1142
-
1143
- // Disable specified components
1144
- disableComponents.forEach(comp => {
1145
- if (STATUSLINE_COMPONENTS.includes(comp)) {
1146
- meta.features.statusline.components[comp] = false;
1147
- success(`Statusline component disabled: ${comp}`);
1148
- } else {
1149
- warn(`Unknown component: ${comp} (available: ${STATUSLINE_COMPONENTS.join(', ')})`);
1150
- }
1151
- });
1152
-
1153
- meta.updated = new Date().toISOString();
1154
- writeJSON(metaPath, meta);
1155
-
1156
- return true;
1157
- }
1158
-
1159
- function listStatuslineComponents() {
1160
- const metaPath = 'docs/00-meta/agileflow-metadata.json';
1161
-
1162
- header('📊 Statusline Components');
1163
-
1164
- if (!fs.existsSync(metaPath)) {
1165
- log(' No configuration found (defaults: all enabled)', c.dim);
1166
- STATUSLINE_COMPONENTS.forEach(comp => {
1167
- log(` ✅ ${comp}: enabled (default)`, c.green);
1168
- });
1169
- return;
1170
- }
1171
-
1172
- const meta = readJSON(metaPath);
1173
- const components = meta?.features?.statusline?.components || {};
1174
-
1175
- STATUSLINE_COMPONENTS.forEach(comp => {
1176
- const enabled = components[comp] !== false; // default true
1177
- const icon = enabled ? '✅' : '❌';
1178
- const color = enabled ? c.green : c.dim;
1179
- log(` ${icon} ${comp}: ${enabled ? 'enabled' : 'disabled'}`, color);
1180
- });
1181
-
1182
- log('\nTo toggle: --show=<component> or --hide=<component>', c.dim);
1183
- log(`Components: ${STATUSLINE_COMPONENTS.join(', ')}`, c.dim);
1184
- }
1185
-
1186
- // ============================================================================
1187
- // REPAIR & DIAGNOSTICS
1188
- // ============================================================================
1189
-
1190
- const crypto = require('crypto');
1191
-
1192
- /**
1193
- * Calculate SHA256 hash of a file
1194
- */
1195
- function sha256(data) {
1196
- return crypto.createHash('sha256').update(data).digest('hex');
1197
- }
1198
-
1199
- /**
1200
- * Get the source scripts directory (from npm package)
1201
- */
1202
- function getSourceScriptsDir() {
1203
- // When running from installed package, __dirname is .agileflow/scripts
1204
- // The source is the same directory since it was copied during install
1205
- // But for repair, we need the npm package source
1206
-
1207
- // Try to find the npm package (when run via npx or global install)
1208
- const possiblePaths = [
1209
- path.join(__dirname, '..', '..', 'scripts'), // npm package structure
1210
- path.join(__dirname), // Same directory (for development)
1211
- ];
1212
-
1213
- for (const p of possiblePaths) {
1214
- if (fs.existsSync(p) && fs.existsSync(path.join(p, 'agileflow-welcome.js'))) {
1215
- return p;
1216
- }
1217
- }
1218
-
1219
- return null;
1220
- }
1221
-
1222
- /**
1223
- * List all scripts with their status (present/missing/modified)
1224
- */
1225
- function listScripts() {
1226
- header('📋 Installed Scripts');
1227
-
1228
- const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
1229
- const fileIndexPath = path.join(process.cwd(), '.agileflow', '_cfg', 'files.json');
1230
- const fileIndex = readJSON(fileIndexPath);
1231
-
1232
- let missing = 0;
1233
- let modified = 0;
1234
- let present = 0;
1235
-
1236
- Object.entries(ALL_SCRIPTS).forEach(([script, info]) => {
1237
- const scriptPath = path.join(scriptsDir, script);
1238
- const exists = fs.existsSync(scriptPath);
1239
-
1240
- // Check if modified (compare to file index hash)
1241
- let isModified = false;
1242
- if (exists && fileIndex?.files?.[`scripts/${script}`]) {
1243
- try {
1244
- const currentHash = sha256(fs.readFileSync(scriptPath));
1245
- const indexHash = fileIndex.files[`scripts/${script}`].sha256;
1246
- isModified = currentHash !== indexHash;
1247
- } catch {}
1248
- }
1249
-
1250
- // Print status
1251
- if (!exists) {
1252
- log(` ❌ ${script}: MISSING`, c.red);
1253
- if (info.usedBy) log(` └─ Used by: ${info.usedBy.join(', ')}`, c.dim);
1254
- if (info.feature) log(` └─ Feature: ${info.feature}`, c.dim);
1255
- missing++;
1256
- } else if (isModified) {
1257
- log(` ⚠️ ${script}: modified (local changes)`, c.yellow);
1258
- modified++;
1259
- } else {
1260
- log(` ✅ ${script}: present`, c.green);
1261
- present++;
1262
- }
1263
- });
1264
-
1265
- // Summary
1266
- log('');
1267
- log(`Summary: ${present} present, ${modified} modified, ${missing} missing`, c.dim);
1268
-
1269
- if (missing > 0) {
1270
- log('\n💡 Run with --repair to restore missing scripts', c.yellow);
1271
- }
1272
- }
1273
-
1274
- /**
1275
- * Show version information
1276
- */
1277
- function showVersionInfo() {
1278
- header('📊 Version Information');
1279
-
1280
- const meta = readJSON('docs/00-meta/agileflow-metadata.json') || {};
1281
- const manifest = readJSON('.agileflow/_cfg/manifest.yaml');
1282
-
1283
- const installedVersion = meta.version || 'unknown';
1284
-
1285
- log(`Installed: v${installedVersion}`);
1286
- log(`CLI: v${VERSION}`);
1287
-
1288
- // Check npm for latest
1289
- let latestVersion = null;
1290
- try {
1291
- latestVersion = execSync('npm view agileflow version 2>/dev/null', { encoding: 'utf8' }).trim();
1292
- log(`Latest: v${latestVersion}`);
1293
-
1294
- if (installedVersion !== 'unknown' && latestVersion && installedVersion !== latestVersion) {
1295
- const installed = installedVersion.split('.').map(Number);
1296
- const latest = latestVersion.split('.').map(Number);
1297
-
1298
- if (
1299
- latest[0] > installed[0] ||
1300
- (latest[0] === installed[0] && latest[1] > installed[1]) ||
1301
- (latest[0] === installed[0] && latest[1] === installed[1] && latest[2] > installed[2])
1302
- ) {
1303
- log('\n🔄 Update available! Run: npx agileflow update', c.yellow);
1304
- }
1305
- }
1306
- } catch {
1307
- log('Latest: (could not check npm)', c.dim);
1308
- }
1309
-
1310
- // Show per-feature versions
1311
- if (meta.features && Object.keys(meta.features).length > 0) {
1312
- header('Feature Versions:');
1313
- Object.entries(meta.features).forEach(([feature, data]) => {
1314
- if (!data) return;
1315
- const featureVersion = data.version || 'unknown';
1316
- const enabled = data.enabled !== false;
1317
- const outdated = featureVersion !== VERSION && enabled;
1318
-
1319
- let icon = '❌';
1320
- let color = c.dim;
1321
- let statusText = `v${featureVersion}`;
1322
-
1323
- if (!enabled) {
1324
- statusText = 'disabled';
1325
- } else if (outdated) {
1326
- icon = '🔄';
1327
- color = c.yellow;
1328
- statusText = `v${featureVersion} → v${VERSION}`;
1329
- } else {
1330
- icon = '✅';
1331
- color = c.green;
1332
- }
1333
-
1334
- log(` ${icon} ${feature}: ${statusText}`, color);
1335
- });
1336
- }
1337
-
1338
- // Show installation metadata
1339
- if (meta.created || meta.updated) {
1340
- header('Installation:');
1341
- if (meta.created) log(` Created: ${new Date(meta.created).toLocaleDateString()}`, c.dim);
1342
- if (meta.updated) log(` Updated: ${new Date(meta.updated).toLocaleDateString()}`, c.dim);
1343
- }
1344
- }
1345
-
1346
- /**
1347
- * Repair missing or corrupted scripts
1348
- */
1349
- function repairScripts(targetFeature = null) {
1350
- header('🔧 Repairing Scripts...');
1351
-
1352
- const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
1353
- const sourceDir = getSourceScriptsDir();
1354
-
1355
- if (!sourceDir) {
1356
- warn('Could not find source scripts directory');
1357
- info('Try running: npx agileflow@latest update');
1358
- return false;
1359
- }
1360
-
1361
- let repaired = 0;
1362
- let errors = 0;
1363
- let skipped = 0;
1364
-
1365
- // Determine which scripts to check
1366
- const scriptsToCheck = targetFeature
1367
- ? Object.entries(ALL_SCRIPTS).filter(([_, info]) => info.feature === targetFeature)
1368
- : Object.entries(ALL_SCRIPTS);
1369
-
1370
- if (scriptsToCheck.length === 0 && targetFeature) {
1371
- error(`Unknown feature: ${targetFeature}`);
1372
- log(`Available features: ${Object.keys(FEATURES).join(', ')}`, c.dim);
1373
- return false;
1374
- }
1375
-
1376
- // Ensure scripts directory exists
1377
- ensureDir(scriptsDir);
1378
-
1379
- for (const [script, info] of scriptsToCheck) {
1380
- const destPath = path.join(scriptsDir, script);
1381
- const srcPath = path.join(sourceDir, script);
1382
-
1383
- if (!fs.existsSync(destPath)) {
1384
- // Script is missing - reinstall from source
1385
- if (fs.existsSync(srcPath)) {
1386
- try {
1387
- fs.copyFileSync(srcPath, destPath);
1388
- // Make executable
1389
- try {
1390
- fs.chmodSync(destPath, 0o755);
1391
- } catch {}
1392
- success(`Restored ${script}`);
1393
- repaired++;
1394
- } catch (err) {
1395
- error(`Failed to restore ${script}: ${err.message}`);
1396
- errors++;
1397
- }
1398
- } else {
1399
- warn(`Source not found for ${script}`);
1400
- errors++;
1401
- }
1402
- } else {
1403
- skipped++;
1404
- }
1405
- }
1406
-
1407
- // Summary
1408
- log('');
1409
- if (repaired === 0 && errors === 0) {
1410
- info('All scripts present - nothing to repair');
1411
- } else {
1412
- log(`Repaired: ${repaired}, Errors: ${errors}, Skipped: ${skipped}`, c.dim);
1413
- }
1414
-
1415
- if (errors > 0) {
1416
- log('\n💡 For comprehensive repair, run: npx agileflow update --force', c.yellow);
1417
- }
1418
-
1419
- return repaired > 0;
1420
- }
1421
-
1422
- // ============================================================================
1423
- // PROFILES
1424
- // ============================================================================
1425
-
1426
- function applyProfile(profileName, options = {}) {
1427
- const profile = PROFILES[profileName];
1428
- if (!profile) {
1429
- error(`Unknown profile: ${profileName}`);
1430
- log('Available: ' + Object.keys(PROFILES).join(', '));
1431
- return false;
1432
- }
1433
-
1434
- header(`🚀 Applying "${profileName}" profile`);
1435
- log(profile.description, c.dim);
1436
-
1437
- // Enable features
1438
- if (profile.enable) {
1439
- profile.enable.forEach(f =>
1440
- enableFeature(f, { archivalDays: profile.archivalDays || options.archivalDays })
1441
- );
1442
- }
1443
-
1444
- // Disable features
1445
- if (profile.disable) {
1446
- profile.disable.forEach(f => disableFeature(f));
1447
- }
1448
-
1449
- return true;
1450
- }
1451
-
1452
85
  // ============================================================================
1453
86
  // SUMMARY
1454
87
  // ============================================================================
1455
88
 
1456
89
  function printSummary(actions) {
1457
- header('Configuration Complete!');
90
+ header('Configuration Complete!');
1458
91
 
1459
92
  if (actions.enabled?.length > 0) {
1460
93
  log('\nEnabled:', c.green);
1461
- actions.enabled.forEach(f => log(`${f}`, c.green));
94
+ actions.enabled.forEach(f => log(` ${f}`, c.green));
1462
95
  }
1463
96
 
1464
97
  if (actions.disabled?.length > 0) {
1465
98
  log('\nDisabled:', c.dim);
1466
- actions.disabled.forEach(f => log(`${f}`, c.dim));
99
+ actions.disabled.forEach(f => log(` ${f}`, c.dim));
1467
100
  }
1468
101
 
1469
102
  if (actions.migrated) {
1470
103
  log('\nMigrated: Fixed format issues', c.yellow);
1471
104
  }
1472
105
 
1473
- log('\n' + ''.repeat(55), c.red);
1474
- log('🔴 RESTART CLAUDE CODE NOW!', c.red + c.bold);
106
+ log('\n' + '='.repeat(55), c.red);
107
+ log(' RESTART CLAUDE CODE NOW!', c.red + c.bold);
1475
108
  log(' Quit completely, wait 5 seconds, restart', c.red);
1476
- log(''.repeat(55), c.red);
109
+ log('='.repeat(55), c.red);
1477
110
  }
1478
111
 
1479
112
  // ============================================================================
@@ -1499,10 +132,6 @@ ${c.cyan}Feature Control:${c.reset}
1499
132
 
1500
133
  Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion
1501
134
 
1502
- Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
1503
- Damage control (damagecontrol) uses PreToolUse hooks to block dangerous commands
1504
- AskUserQuestion (askuserquestion) makes all commands end with guided options
1505
-
1506
135
  ${c.cyan}Statusline Components:${c.reset}
1507
136
  --show=<list> Show statusline components (comma-separated)
1508
137
  --hide=<list> Hide statusline components (comma-separated)
@@ -1521,9 +150,9 @@ ${c.cyan}Maintenance:${c.reset}
1521
150
 
1522
151
  ${c.cyan}Repair & Diagnostics:${c.reset}
1523
152
  --repair Check for and restore missing scripts
1524
- --repair=<feature> Repair scripts for a specific feature (e.g., statusline)
153
+ --repair=<feature> Repair scripts for a specific feature
1525
154
  --version Show installed vs latest version info
1526
- --list-scripts List all scripts with their status (missing/present/modified)
155
+ --list-scripts List all scripts with their status
1527
156
 
1528
157
  ${c.cyan}Examples:${c.reset}
1529
158
  # Quick setup with all features
@@ -1538,41 +167,11 @@ ${c.cyan}Examples:${c.reset}
1538
167
  # Show only agileflow branding and context in statusline
1539
168
  node .agileflow/scripts/agileflow-configure.js --hide=model,story,epic,wip,cost,git
1540
169
 
1541
- # Re-enable git branch in statusline
1542
- node .agileflow/scripts/agileflow-configure.js --show=git
1543
-
1544
- # List component status
1545
- node .agileflow/scripts/agileflow-configure.js --components
1546
-
1547
170
  # Fix format issues
1548
171
  node .agileflow/scripts/agileflow-configure.js --migrate
1549
172
 
1550
173
  # Check current status
1551
174
  node .agileflow/scripts/agileflow-configure.js --detect
1552
-
1553
- # Upgrade outdated scripts to latest version
1554
- node .agileflow/scripts/agileflow-configure.js --upgrade
1555
-
1556
- # List all scripts with status
1557
- node .agileflow/scripts/agileflow-configure.js --list-scripts
1558
-
1559
- # Show version information
1560
- node .agileflow/scripts/agileflow-configure.js --version
1561
-
1562
- # Repair missing scripts
1563
- node .agileflow/scripts/agileflow-configure.js --repair
1564
-
1565
- # Repair scripts for a specific feature
1566
- node .agileflow/scripts/agileflow-configure.js --repair=statusline
1567
-
1568
- # Enable damage control (PreToolUse hooks to block dangerous commands)
1569
- node .agileflow/scripts/agileflow-configure.js --enable=damagecontrol
1570
-
1571
- # Enable AskUserQuestion (all commands end with guided options)
1572
- node .agileflow/scripts/agileflow-configure.js --enable=askuserquestion
1573
-
1574
- # Disable AskUserQuestion (commands end with natural text questions)
1575
- node .agileflow/scripts/agileflow-configure.js --disable=askuserquestion
1576
175
  `);
1577
176
  }
1578
177
 
@@ -1637,31 +236,32 @@ function main() {
1637
236
  else if (arg === '--list-scripts' || arg === '--scripts') listScriptsMode = true;
1638
237
  });
1639
238
 
239
+ // Help mode
1640
240
  if (help) {
1641
241
  printHelp();
1642
242
  return;
1643
243
  }
1644
244
 
1645
- // List scripts mode (standalone, doesn't need detection)
245
+ // List scripts mode
1646
246
  if (listScriptsMode) {
1647
247
  listScripts();
1648
248
  return;
1649
249
  }
1650
250
 
1651
- // Version info mode (standalone, doesn't need detection)
251
+ // Version info mode
1652
252
  if (showVersion) {
1653
- showVersionInfo();
253
+ showVersionInfo(VERSION);
1654
254
  return;
1655
255
  }
1656
256
 
1657
- // Repair mode (standalone, doesn't need detection)
257
+ // Repair mode
1658
258
  if (repair) {
1659
259
  const needsRestart = repairScripts(repairFeature);
1660
260
  if (needsRestart) {
1661
- log('\n' + ''.repeat(55), c.red);
1662
- log('🔴 RESTART CLAUDE CODE NOW!', c.red + c.bold);
261
+ log('\n' + '='.repeat(55), c.red);
262
+ log(' RESTART CLAUDE CODE NOW!', c.red + c.bold);
1663
263
  log(' Quit completely, wait 5 seconds, restart', c.red);
1664
- log(''.repeat(55), c.red);
264
+ log('='.repeat(55), c.red);
1665
265
  }
1666
266
  return;
1667
267
  }
@@ -1680,7 +280,7 @@ function main() {
1680
280
  }
1681
281
 
1682
282
  // Always detect first
1683
- const status = detectConfig();
283
+ const status = detectConfig(VERSION);
1684
284
  const { hasIssues, hasOutdated } = printStatus(status);
1685
285
 
1686
286
  // Detect only mode
@@ -1690,7 +290,7 @@ function main() {
1690
290
 
1691
291
  // Upgrade mode
1692
292
  if (upgrade) {
1693
- upgradeFeatures(status);
293
+ upgradeFeatures(status, VERSION);
1694
294
  return;
1695
295
  }
1696
296
 
@@ -1702,7 +302,7 @@ function main() {
1702
302
 
1703
303
  // Auto-migrate if issues detected
1704
304
  if (hasIssues && (profile || enable.length > 0)) {
1705
- log('\n⚠️ Auto-migrating invalid formats...', c.yellow);
305
+ log('\n Auto-migrating invalid formats...', c.yellow);
1706
306
  migrateSettings();
1707
307
  }
1708
308
 
@@ -1710,7 +310,7 @@ function main() {
1710
310
 
1711
311
  // Apply profile
1712
312
  if (profile) {
1713
- applyProfile(profile, { archivalDays });
313
+ applyProfile(profile, { archivalDays }, VERSION);
1714
314
  const p = PROFILES[profile];
1715
315
  actions.enabled = p.enable || [];
1716
316
  actions.disabled = p.disable || [];
@@ -1718,14 +318,14 @@ function main() {
1718
318
 
1719
319
  // Enable specific features
1720
320
  enable.forEach(f => {
1721
- if (enableFeature(f, { archivalDays })) {
321
+ if (enableFeature(f, { archivalDays }, VERSION)) {
1722
322
  actions.enabled.push(f);
1723
323
  }
1724
324
  });
1725
325
 
1726
326
  // Disable specific features
1727
327
  disable.forEach(f => {
1728
- if (disableFeature(f)) {
328
+ if (disableFeature(f, VERSION)) {
1729
329
  actions.disabled.push(f);
1730
330
  }
1731
331
  });