agileflow 2.90.7 → 2.92.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +818 -0
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate-names.js +3 -3
  22. package/lib/validate.js +116 -52
  23. package/package.json +4 -1
  24. package/scripts/af +34 -0
  25. package/scripts/agent-loop.js +63 -9
  26. package/scripts/agileflow-configure.js +2 -2
  27. package/scripts/agileflow-welcome.js +435 -23
  28. package/scripts/archive-completed-stories.sh +57 -11
  29. package/scripts/claude-tmux.sh +102 -0
  30. package/scripts/damage-control-bash.js +3 -70
  31. package/scripts/damage-control-edit.js +3 -20
  32. package/scripts/damage-control-write.js +3 -20
  33. package/scripts/dependency-check.js +310 -0
  34. package/scripts/get-env.js +11 -4
  35. package/scripts/lib/configure-detect.js +23 -1
  36. package/scripts/lib/configure-features.js +43 -2
  37. package/scripts/lib/context-formatter.js +771 -0
  38. package/scripts/lib/context-loader.js +699 -0
  39. package/scripts/lib/damage-control-utils.js +107 -0
  40. package/scripts/lib/json-utils.sh +162 -0
  41. package/scripts/lib/state-migrator.js +353 -0
  42. package/scripts/lib/story-state-machine.js +437 -0
  43. package/scripts/obtain-context.js +118 -1048
  44. package/scripts/pre-push-check.sh +46 -0
  45. package/scripts/precompact-context.sh +36 -11
  46. package/scripts/query-codebase.js +538 -0
  47. package/scripts/ralph-loop.js +5 -5
  48. package/scripts/session-manager.js +220 -42
  49. package/scripts/spawn-parallel.js +651 -0
  50. package/scripts/tui/blessed/data/watcher.js +180 -0
  51. package/scripts/tui/blessed/index.js +244 -0
  52. package/scripts/tui/blessed/panels/output.js +101 -0
  53. package/scripts/tui/blessed/panels/sessions.js +150 -0
  54. package/scripts/tui/blessed/panels/trace.js +97 -0
  55. package/scripts/tui/blessed/ui/help.js +77 -0
  56. package/scripts/tui/blessed/ui/screen.js +52 -0
  57. package/scripts/tui/blessed/ui/statusbar.js +47 -0
  58. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  59. package/scripts/tui/index.js +38 -30
  60. package/scripts/validators/README.md +143 -0
  61. package/scripts/validators/component-validator.js +239 -0
  62. package/scripts/validators/json-schema-validator.js +186 -0
  63. package/scripts/validators/markdown-validator.js +152 -0
  64. package/scripts/validators/migration-validator.js +129 -0
  65. package/scripts/validators/security-validator.js +380 -0
  66. package/scripts/validators/story-format-validator.js +197 -0
  67. package/scripts/validators/test-result-validator.js +114 -0
  68. package/scripts/validators/workflow-validator.js +247 -0
  69. package/src/core/agents/accessibility.md +6 -0
  70. package/src/core/agents/adr-writer.md +6 -0
  71. package/src/core/agents/analytics.md +6 -0
  72. package/src/core/agents/api.md +6 -0
  73. package/src/core/agents/ci.md +6 -0
  74. package/src/core/agents/codebase-query.md +261 -0
  75. package/src/core/agents/compliance.md +6 -0
  76. package/src/core/agents/configuration-damage-control.md +6 -0
  77. package/src/core/agents/configuration-visual-e2e.md +6 -0
  78. package/src/core/agents/database.md +10 -0
  79. package/src/core/agents/datamigration.md +6 -0
  80. package/src/core/agents/design.md +6 -0
  81. package/src/core/agents/devops.md +6 -0
  82. package/src/core/agents/documentation.md +6 -0
  83. package/src/core/agents/epic-planner.md +6 -0
  84. package/src/core/agents/integrations.md +6 -0
  85. package/src/core/agents/mentor.md +6 -0
  86. package/src/core/agents/mobile.md +6 -0
  87. package/src/core/agents/monitoring.md +6 -0
  88. package/src/core/agents/multi-expert.md +6 -0
  89. package/src/core/agents/performance.md +6 -0
  90. package/src/core/agents/product.md +6 -0
  91. package/src/core/agents/qa.md +6 -0
  92. package/src/core/agents/readme-updater.md +6 -0
  93. package/src/core/agents/refactor.md +6 -0
  94. package/src/core/agents/research.md +6 -0
  95. package/src/core/agents/security.md +6 -0
  96. package/src/core/agents/testing.md +10 -0
  97. package/src/core/agents/ui.md +6 -0
  98. package/src/core/commands/adr.md +114 -0
  99. package/src/core/commands/agent.md +120 -0
  100. package/src/core/commands/assign.md +145 -0
  101. package/src/core/commands/audit.md +401 -0
  102. package/src/core/commands/babysit.md +32 -5
  103. package/src/core/commands/board.md +1 -0
  104. package/src/core/commands/changelog.md +118 -0
  105. package/src/core/commands/configure.md +42 -6
  106. package/src/core/commands/diagnose.md +114 -0
  107. package/src/core/commands/epic.md +205 -1
  108. package/src/core/commands/handoff.md +128 -0
  109. package/src/core/commands/help.md +76 -0
  110. package/src/core/commands/metrics.md +1 -0
  111. package/src/core/commands/pr.md +96 -0
  112. package/src/core/commands/research/analyze.md +1 -0
  113. package/src/core/commands/research/ask.md +2 -0
  114. package/src/core/commands/research/import.md +1 -0
  115. package/src/core/commands/research/list.md +2 -0
  116. package/src/core/commands/research/synthesize.md +584 -0
  117. package/src/core/commands/research/view.md +2 -0
  118. package/src/core/commands/roadmap/analyze.md +400 -0
  119. package/src/core/commands/session/new.md +113 -6
  120. package/src/core/commands/session/spawn.md +197 -0
  121. package/src/core/commands/sprint.md +22 -0
  122. package/src/core/commands/status.md +200 -1
  123. package/src/core/commands/story/list.md +9 -9
  124. package/src/core/commands/story/view.md +1 -0
  125. package/src/core/commands/story.md +143 -4
  126. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  127. package/src/core/experts/codebase-query/question.md +73 -0
  128. package/src/core/experts/codebase-query/self-improve.md +105 -0
  129. package/src/core/templates/agileflow-metadata.json +55 -2
  130. package/src/core/templates/plan-template.md +125 -0
  131. package/src/core/templates/story-lifecycle.md +213 -0
  132. package/src/core/templates/story-template.md +4 -0
  133. package/src/core/templates/tdd-test-template.js +241 -0
  134. package/tools/cli/commands/setup.js +86 -0
  135. package/tools/cli/installers/core/installer.js +94 -0
  136. package/tools/cli/installers/ide/_base-ide.js +20 -11
  137. package/tools/cli/installers/ide/codex.js +29 -47
  138. package/tools/cli/lib/config-manager.js +17 -2
  139. package/tools/cli/lib/content-transformer.js +271 -0
  140. package/tools/cli/lib/error-handler.js +14 -22
  141. package/tools/cli/lib/ide-error-factory.js +421 -0
  142. package/tools/cli/lib/ide-health-monitor.js +364 -0
  143. package/tools/cli/lib/ide-registry.js +114 -1
  144. package/tools/cli/lib/ui.js +14 -25
@@ -17,7 +17,14 @@ const { execSync, spawnSync } = require('child_process');
17
17
 
18
18
  // Shared utilities
19
19
  const { c, box } = require('../lib/colors');
20
- const { getProjectRoot } = require('../lib/paths');
20
+ const {
21
+ getProjectRoot,
22
+ getStatusPath,
23
+ getMetadataPath,
24
+ getSessionStatePath,
25
+ getAgileflowDir,
26
+ getClaudeDir,
27
+ } = require('../lib/paths');
21
28
  const { readJSONCached, readFileCached } = require('../lib/file-cache');
22
29
 
23
30
  // Session manager path (relative to script location)
@@ -31,6 +38,14 @@ try {
31
38
  // Story claiming not available
32
39
  }
33
40
 
41
+ // Story state machine (for epic completion check)
42
+ let storyStateMachine;
43
+ try {
44
+ storyStateMachine = require('./lib/story-state-machine.js');
45
+ } catch (e) {
46
+ // Story state machine not available
47
+ }
48
+
34
49
  // File tracking module
35
50
  let fileTracking;
36
51
  try {
@@ -55,11 +70,11 @@ try {
55
70
  */
56
71
  function loadProjectFiles(rootDir) {
57
72
  const paths = {
58
- status: path.join(rootDir, 'docs', '09-agents', 'status.json'),
59
- metadata: path.join(rootDir, 'docs', '00-meta', 'agileflow-metadata.json'),
60
- settings: path.join(rootDir, '.claude', 'settings.json'),
61
- sessionState: path.join(rootDir, 'docs', '09-agents', 'session-state.json'),
62
- configYaml: path.join(rootDir, '.agileflow', 'config.yaml'),
73
+ status: getStatusPath(rootDir),
74
+ metadata: getMetadataPath(rootDir),
75
+ settings: path.join(getClaudeDir(rootDir), 'settings.json'),
76
+ sessionState: getSessionStatePath(rootDir),
77
+ configYaml: path.join(getAgileflowDir(rootDir), 'config.yaml'),
63
78
  cliPackage: path.join(rootDir, 'packages', 'cli', 'package.json'),
64
79
  };
65
80
 
@@ -73,6 +88,57 @@ function loadProjectFiles(rootDir) {
73
88
  };
74
89
  }
75
90
 
91
+ /**
92
+ * Detect the user's platform for install guidance
93
+ */
94
+ function detectPlatform() {
95
+ const platform = process.platform;
96
+
97
+ if (platform === 'darwin') {
98
+ return { os: 'macOS', installCmd: 'brew install tmux', hasSudo: true };
99
+ }
100
+
101
+ if (platform === 'linux') {
102
+ // Try to detect Linux distribution
103
+ try {
104
+ const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
105
+ if (osRelease.includes('Ubuntu') || osRelease.includes('Debian') || osRelease.includes('Pop!_OS') || osRelease.includes('Mint')) {
106
+ return { os: 'Ubuntu/Debian', installCmd: 'sudo apt install tmux', hasSudo: true };
107
+ }
108
+ if (osRelease.includes('Fedora') || osRelease.includes('Red Hat') || osRelease.includes('CentOS') || osRelease.includes('Rocky')) {
109
+ return { os: 'Fedora/RHEL', installCmd: 'sudo dnf install tmux', hasSudo: true };
110
+ }
111
+ if (osRelease.includes('Arch')) {
112
+ return { os: 'Arch', installCmd: 'sudo pacman -S tmux', hasSudo: true };
113
+ }
114
+ } catch (e) {
115
+ // Can't read /etc/os-release
116
+ }
117
+ return { os: 'Linux', installCmd: 'sudo apt install tmux', hasSudo: true };
118
+ }
119
+
120
+ // Windows WSL or unknown
121
+ return { os: 'Unknown', installCmd: null, hasSudo: false };
122
+ }
123
+
124
+ /**
125
+ * Check if tmux is installed
126
+ * Returns object with availability info and platform-specific install suggestion
127
+ */
128
+ function checkTmuxAvailability() {
129
+ try {
130
+ execSync('which tmux', { encoding: 'utf8', stdio: 'pipe' });
131
+ return { available: true };
132
+ } catch (e) {
133
+ const platform = detectPlatform();
134
+ return {
135
+ available: false,
136
+ platform,
137
+ noSudoCmd: 'conda install -c conda-forge tmux',
138
+ };
139
+ }
140
+ }
141
+
76
142
  /**
77
143
  * PERFORMANCE OPTIMIZATION: Batch git commands into single call
78
144
  * Reduces subprocess overhead from 3 calls to 1.
@@ -129,7 +195,7 @@ function getProjectInfo(rootDir, cache = null) {
129
195
  info.version = cache.cliPackage.version;
130
196
  } else {
131
197
  // No cache - fall back to file reads (for backwards compatibility)
132
- const configPath = path.join(rootDir, '.agileflow', 'config.yaml');
198
+ const configPath = path.join(getAgileflowDir(rootDir), 'config.yaml');
133
199
  if (fs.existsSync(configPath)) {
134
200
  const content = fs.readFileSync(configPath, 'utf8');
135
201
  const versionMatch = content.match(/^version:\s*['"]?([0-9.]+)/m);
@@ -137,7 +203,7 @@ function getProjectInfo(rootDir, cache = null) {
137
203
  info.version = versionMatch[1];
138
204
  }
139
205
  } else {
140
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
206
+ const metadataPath = getMetadataPath(rootDir);
141
207
  if (fs.existsSync(metadataPath)) {
142
208
  const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
143
209
  info.version = metadata.version || info.version;
@@ -180,7 +246,7 @@ function getProjectInfo(rootDir, cache = null) {
180
246
  }
181
247
  } else if (!cache) {
182
248
  // No cache - fall back to file read
183
- const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
249
+ const statusPath = getStatusPath(rootDir);
184
250
  if (fs.existsSync(statusPath)) {
185
251
  const statusData = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
186
252
  if (statusData.stories) {
@@ -221,7 +287,7 @@ function runArchival(rootDir, cache = null) {
221
287
  result.threshold = metadata.archival?.threshold_days || 7;
222
288
  } else {
223
289
  // No cache - fall back to file read
224
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
290
+ const metadataPath = getMetadataPath(rootDir);
225
291
  if (fs.existsSync(metadataPath)) {
226
292
  const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
227
293
  if (metadataData.archival?.enabled === false) {
@@ -235,7 +301,7 @@ function runArchival(rootDir, cache = null) {
235
301
  // Use cached status if available
236
302
  const status = cache?.status;
237
303
  if (!status && !cache) {
238
- const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
304
+ const statusPath = getStatusPath(rootDir);
239
305
  if (!fs.existsSync(statusPath)) return result;
240
306
  }
241
307
 
@@ -277,7 +343,7 @@ function clearActiveCommands(rootDir, cache = null) {
277
343
  const result = { ran: false, cleared: 0, commandNames: [], preserved: false };
278
344
 
279
345
  try {
280
- const sessionStatePath = path.join(rootDir, 'docs/09-agents/session-state.json');
346
+ const sessionStatePath = getSessionStatePath(rootDir);
281
347
 
282
348
  // Use cached sessionState if available, but we still need to read fresh for clearing
283
349
  // because we need to write back. Cache is only useful to check if file exists.
@@ -364,7 +430,7 @@ function checkParallelSessions(rootDir) {
364
430
 
365
431
  try {
366
432
  // Check if session manager exists
367
- const managerPath = path.join(rootDir, '.agileflow', 'scripts', 'session-manager.js');
433
+ const managerPath = path.join(getAgileflowDir(rootDir), 'scripts', 'session-manager.js');
368
434
  if (!fs.existsSync(managerPath) && !fs.existsSync(SESSION_MANAGER_PATH)) {
369
435
  return result;
370
436
  }
@@ -458,7 +524,7 @@ function checkPreCompact(rootDir, cache = null) {
458
524
  }
459
525
  } else {
460
526
  // No cache - fall back to file read
461
- const settingsPath = path.join(rootDir, '.claude/settings.json');
527
+ const settingsPath = path.join(getClaudeDir(rootDir), 'settings.json');
462
528
  if (fs.existsSync(settingsPath)) {
463
529
  const settingsData = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
464
530
  if (settingsData.hooks?.PreCompact?.length > 0) {
@@ -468,7 +534,7 @@ function checkPreCompact(rootDir, cache = null) {
468
534
  }
469
535
 
470
536
  // Check if the script exists (must always check filesystem)
471
- const scriptPath = path.join(rootDir, 'scripts/precompact-context.sh');
537
+ const scriptPath = path.join(rootDir, 'scripts', 'precompact-context.sh');
472
538
  if (fs.existsSync(scriptPath)) {
473
539
  result.scriptExists = true;
474
540
  }
@@ -487,7 +553,7 @@ function checkPreCompact(rootDir, cache = null) {
487
553
  }
488
554
  } else if (!cache) {
489
555
  // No cache - fall back to file read
490
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
556
+ const metadataPath = getMetadataPath(rootDir);
491
557
  if (fs.existsSync(metadataPath)) {
492
558
  const metadataData = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
493
559
  if (metadataData.features?.precompact?.configured_version) {
@@ -512,7 +578,7 @@ function checkDamageControl(rootDir, cache = null) {
512
578
  let settings = cache?.settings;
513
579
  if (!settings && !cache) {
514
580
  // No cache - fall back to file read
515
- const settingsPath = path.join(rootDir, '.claude/settings.json');
581
+ const settingsPath = path.join(getClaudeDir(rootDir), 'settings.json');
516
582
  if (fs.existsSync(settingsPath)) {
517
583
  settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
518
584
  }
@@ -542,7 +608,7 @@ function checkDamageControl(rootDir, cache = null) {
542
608
  }
543
609
 
544
610
  // Check if all required scripts exist (in .claude/hooks/damage-control/)
545
- const hooksDir = path.join(rootDir, '.claude', 'hooks', 'damage-control');
611
+ const hooksDir = path.join(getClaudeDir(rootDir), 'hooks', 'damage-control');
546
612
  const requiredScripts = [
547
613
  'bash-tool-damage-control.js',
548
614
  'edit-tool-damage-control.js',
@@ -560,8 +626,8 @@ function checkDamageControl(rootDir, cache = null) {
560
626
 
561
627
  // Count patterns in patterns.yaml
562
628
  const patternsLocations = [
563
- path.join(rootDir, '.claude', 'hooks', 'damage-control', 'patterns.yaml'),
564
- path.join(rootDir, '.agileflow', 'scripts', 'damage-control', 'patterns.yaml'),
629
+ path.join(getClaudeDir(rootDir), 'hooks', 'damage-control', 'patterns.yaml'),
630
+ path.join(getAgileflowDir(rootDir), 'scripts', 'damage-control', 'patterns.yaml'),
565
631
  ];
566
632
  for (const patternsPath of patternsLocations) {
567
633
  if (fs.existsSync(patternsPath)) {
@@ -591,6 +657,257 @@ function compareVersions(a, b) {
591
657
  return 0;
592
658
  }
593
659
 
660
+ /**
661
+ * All available config options with their version requirements
662
+ * These are the options that can be configured through /agileflow:configure
663
+ */
664
+ const ALL_CONFIG_OPTIONS = {
665
+ claudeMdReinforcement: { since: '2.92.0', description: 'Add /babysit rules to CLAUDE.md', autoApplyable: true },
666
+ sessionStartHook: { since: '2.35.0', description: 'Welcome display on session start', autoApplyable: false },
667
+ precompactHook: { since: '2.40.0', description: 'Context preservation during /compact', autoApplyable: false },
668
+ damageControlHooks: { since: '2.50.0', description: 'Block destructive commands', autoApplyable: false },
669
+ statusLine: { since: '2.35.0', description: 'Custom status bar display', autoApplyable: false },
670
+ autoArchival: { since: '2.35.0', description: 'Auto-archive completed stories', autoApplyable: false },
671
+ autoUpdate: { since: '2.70.0', description: 'Auto-update on session start', autoApplyable: false },
672
+ ralphLoop: { since: '2.60.0', description: 'Autonomous story processing', autoApplyable: false },
673
+ tmuxAutoSpawn: { since: '2.92.0', description: 'Auto-start Claude in tmux session', autoApplyable: true },
674
+ };
675
+
676
+ /**
677
+ * Check for new config options that haven't been presented to the user
678
+ * Returns info about outdated config and whether to auto-apply (for "full" profile)
679
+ */
680
+ function checkConfigStaleness(rootDir, currentVersion, cache = null) {
681
+ const result = {
682
+ outdated: false,
683
+ newOptionsCount: 0,
684
+ newOptions: [],
685
+ configSchemaVersion: null,
686
+ activeProfile: null,
687
+ autoApply: false,
688
+ };
689
+
690
+ try {
691
+ const metadata = cache?.metadata;
692
+ if (!metadata) return result;
693
+
694
+ result.configSchemaVersion = metadata.config_schema_version || null;
695
+ result.activeProfile = metadata.active_profile || null;
696
+
697
+ const configOptions = metadata.agileflow?.config_options || {};
698
+
699
+ // If no config_schema_version, this is an old installation - all options are "new"
700
+ if (!result.configSchemaVersion) {
701
+ // For old installations, detect which features are actually configured via settings.json
702
+ const settings = cache?.settings || {};
703
+ const hooks = settings.hooks || {};
704
+
705
+ // Check each option against actual configuration
706
+ for (const [name, optionInfo] of Object.entries(ALL_CONFIG_OPTIONS)) {
707
+ const isConfigured = isOptionActuallyConfigured(name, hooks, settings);
708
+ if (!isConfigured) {
709
+ result.outdated = true;
710
+ result.newOptionsCount++;
711
+ result.newOptions.push({
712
+ name,
713
+ description: optionInfo.description,
714
+ autoApplyable: optionInfo.autoApplyable,
715
+ });
716
+ }
717
+ }
718
+ } else {
719
+ // Check for unconfigured options in metadata
720
+ for (const [name, option] of Object.entries(configOptions)) {
721
+ if (option.configured === false) {
722
+ const optionInfo = ALL_CONFIG_OPTIONS[name] || { description: name, autoApplyable: false };
723
+ result.outdated = true;
724
+ result.newOptionsCount++;
725
+ result.newOptions.push({
726
+ name,
727
+ description: optionInfo.description,
728
+ autoApplyable: optionInfo.autoApplyable,
729
+ });
730
+ }
731
+ }
732
+
733
+ // Check for options that might not be in metadata yet (added after their install)
734
+ for (const [name, optionInfo] of Object.entries(ALL_CONFIG_OPTIONS)) {
735
+ if (!configOptions[name]) {
736
+ // Option doesn't exist in metadata - check if it was added after their config_schema_version
737
+ if (compareVersions(result.configSchemaVersion, optionInfo.since) < 0) {
738
+ const alreadyAdded = result.newOptions.some(o => o.name === name);
739
+ if (!alreadyAdded) {
740
+ result.outdated = true;
741
+ result.newOptionsCount++;
742
+ result.newOptions.push({
743
+ name,
744
+ description: optionInfo.description,
745
+ autoApplyable: optionInfo.autoApplyable,
746
+ });
747
+ }
748
+ }
749
+ }
750
+ }
751
+ }
752
+
753
+ // If profile is "full", auto-apply features that support it
754
+ if (result.outdated && result.activeProfile === 'full') {
755
+ const autoApplyableOptions = result.newOptions.filter(o => o.autoApplyable);
756
+ if (autoApplyableOptions.length > 0) {
757
+ result.autoApply = true;
758
+ // Only auto-apply the auto-applyable ones
759
+ result.autoApplyOptions = autoApplyableOptions;
760
+ }
761
+ }
762
+ } catch (e) {
763
+ // Silently fail - config check is non-critical
764
+ }
765
+
766
+ return result;
767
+ }
768
+
769
+ /**
770
+ * Check if a config option is actually configured in settings
771
+ */
772
+ function isOptionActuallyConfigured(optionName, hooks, settings) {
773
+ switch (optionName) {
774
+ case 'sessionStartHook':
775
+ return hooks.SessionStart && hooks.SessionStart.length > 0;
776
+ case 'precompactHook':
777
+ return hooks.PreCompact && hooks.PreCompact.length > 0;
778
+ case 'damageControlHooks':
779
+ return hooks.PreToolUse && hooks.PreToolUse.some(h =>
780
+ h.hooks?.some(hk => hk.command?.includes('damage-control'))
781
+ );
782
+ case 'statusLine':
783
+ return settings.statusLine && settings.statusLine.command;
784
+ case 'autoArchival':
785
+ // Archival is tied to SessionStart hook running archive script
786
+ return hooks.SessionStart && hooks.SessionStart.some(h =>
787
+ h.hooks?.some(hk => hk.command?.includes('archive'))
788
+ );
789
+ case 'autoUpdate':
790
+ // Would need to check metadata for autoUpdate setting
791
+ return false; // Default to not configured
792
+ case 'ralphLoop':
793
+ return hooks.Stop && hooks.Stop.some(h =>
794
+ h.hooks?.some(hk => hk.command?.includes('ralph-loop'))
795
+ );
796
+ case 'claudeMdReinforcement':
797
+ // Check if CLAUDE.md has the marker - can't easily check from here
798
+ return false; // Let welcome script handle this
799
+ case 'tmuxAutoSpawn':
800
+ // Check metadata for tmuxAutoSpawn setting (default is enabled)
801
+ return false; // Let welcome script handle this via metadata
802
+ default:
803
+ return false;
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Auto-apply new config options for "full" profile
809
+ * Returns true if any options were applied
810
+ */
811
+ function autoApplyConfigOptions(rootDir, newOptions) {
812
+ let applied = 0;
813
+
814
+ for (const option of newOptions) {
815
+ try {
816
+ if (option.name === 'claudeMdReinforcement') {
817
+ // Apply CLAUDE.md reinforcement
818
+ const claudeMdPath = path.join(rootDir, 'CLAUDE.md');
819
+ const marker = '<!-- AGILEFLOW_BABYSIT_RULES -->';
820
+ const content = `
821
+
822
+ ${marker}
823
+ ## AgileFlow /babysit Context Preservation Rules
824
+
825
+ When \`/agileflow:babysit\` is active (check session-state.json), these rules are MANDATORY:
826
+
827
+ 1. **ALWAYS end responses with the AskUserQuestion tool** - Not text like "What next?" but the ACTUAL TOOL CALL
828
+ 2. **Use Plan Mode for non-trivial tasks** - Call \`EnterPlanMode\` before complex implementations
829
+ 3. **Delegate complex work to domain experts** - Use \`Task\` tool with appropriate \`subagent_type\`
830
+ 4. **Track progress with TodoWrite** - For any task with 3+ steps
831
+
832
+ These rules persist across conversation compaction. Check \`docs/09-agents/session-state.json\` for active commands.
833
+ ${marker}
834
+ `;
835
+
836
+ let existingContent = '';
837
+ if (fs.existsSync(claudeMdPath)) {
838
+ existingContent = fs.readFileSync(claudeMdPath, 'utf8');
839
+ }
840
+
841
+ if (!existingContent.includes(marker)) {
842
+ fs.appendFileSync(claudeMdPath, content);
843
+ applied++;
844
+ }
845
+ } else if (option.name === 'tmuxAutoSpawn') {
846
+ // Auto-enable tmux auto-spawn via metadata
847
+ const metadataPath = getMetadataPath(rootDir);
848
+ if (fs.existsSync(metadataPath)) {
849
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
850
+ if (!metadata.features) metadata.features = {};
851
+ if (!metadata.features.tmuxAutoSpawn || metadata.features.tmuxAutoSpawn.enabled === undefined) {
852
+ metadata.features.tmuxAutoSpawn = {
853
+ enabled: true,
854
+ version: metadata.version || '2.92.0',
855
+ at: new Date().toISOString(),
856
+ };
857
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
858
+ applied++;
859
+ }
860
+ }
861
+ }
862
+ // Add more option handlers here as new options are added
863
+ } catch (e) {
864
+ // Silently fail individual options
865
+ }
866
+ }
867
+
868
+ // Update metadata to mark options as configured
869
+ if (applied > 0) {
870
+ try {
871
+ const metadataPath = getMetadataPath(rootDir);
872
+ if (fs.existsSync(metadataPath)) {
873
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
874
+
875
+ // Get current CLI version for updating config_schema_version
876
+ let currentVersion = '2.92.0';
877
+ try {
878
+ const pkgPath = path.join(__dirname, '..', 'package.json');
879
+ if (fs.existsSync(pkgPath)) {
880
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
881
+ currentVersion = pkg.version;
882
+ }
883
+ } catch (e) {}
884
+
885
+ // Update config_schema_version
886
+ metadata.config_schema_version = currentVersion;
887
+
888
+ // Mark applied options as configured
889
+ if (!metadata.agileflow) metadata.agileflow = {};
890
+ if (!metadata.agileflow.config_options) metadata.agileflow.config_options = {};
891
+
892
+ for (const option of newOptions) {
893
+ metadata.agileflow.config_options[option.name] = {
894
+ ...metadata.agileflow.config_options[option.name],
895
+ configured: true,
896
+ enabled: true,
897
+ configured_at: new Date().toISOString(),
898
+ };
899
+ }
900
+
901
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
902
+ }
903
+ } catch (e) {
904
+ // Silently fail metadata update
905
+ }
906
+ }
907
+
908
+ return applied;
909
+ }
910
+
594
911
  // Check for updates (async but we'll use sync approach for welcome)
595
912
  async function checkUpdates() {
596
913
  const result = {
@@ -718,7 +1035,7 @@ function getExpertiseCountFast(rootDir) {
718
1035
  const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [], validated: false };
719
1036
 
720
1037
  // Find experts directory
721
- let expertsDir = path.join(rootDir, '.agileflow', 'experts');
1038
+ let expertsDir = path.join(getAgileflowDir(rootDir), 'experts');
722
1039
  if (!fs.existsSync(expertsDir)) {
723
1040
  expertsDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts');
724
1041
  }
@@ -781,7 +1098,7 @@ function validateExpertise(rootDir) {
781
1098
  const result = { total: 0, passed: 0, warnings: 0, failed: 0, issues: [] };
782
1099
 
783
1100
  // Find experts directory
784
- let expertsDir = path.join(rootDir, '.agileflow', 'experts');
1101
+ let expertsDir = path.join(getAgileflowDir(rootDir), 'experts');
785
1102
  if (!fs.existsSync(expertsDir)) {
786
1103
  expertsDir = path.join(rootDir, 'packages', 'cli', 'src', 'core', 'experts');
787
1104
  }
@@ -874,7 +1191,7 @@ function getFeatureVersions(rootDir) {
874
1191
  };
875
1192
 
876
1193
  try {
877
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
1194
+ const metadataPath = getMetadataPath(rootDir);
878
1195
  if (fs.existsSync(metadataPath)) {
879
1196
  const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
880
1197
 
@@ -1268,6 +1585,35 @@ async function main() {
1268
1585
  // Update check failed - continue without it
1269
1586
  }
1270
1587
 
1588
+ // Check for new config options
1589
+ let configStaleness = { outdated: false, autoApply: false };
1590
+ let configAutoApplied = 0;
1591
+ try {
1592
+ configStaleness = checkConfigStaleness(rootDir, info.version, cache);
1593
+
1594
+ // Auto-apply new options if profile is "full" (only auto-applyable ones)
1595
+ if (configStaleness.autoApply && configStaleness.autoApplyOptions?.length > 0) {
1596
+ configAutoApplied = autoApplyConfigOptions(rootDir, configStaleness.autoApplyOptions);
1597
+ if (configAutoApplied > 0) {
1598
+ // Remove auto-applied options from the list, keep non-auto-applyable ones
1599
+ configStaleness.newOptions = configStaleness.newOptions.filter(o => !o.autoApplyable);
1600
+ configStaleness.newOptionsCount = configStaleness.newOptions.length;
1601
+ if (configStaleness.newOptionsCount === 0) {
1602
+ configStaleness.outdated = false;
1603
+ }
1604
+ }
1605
+ }
1606
+ } catch (e) {
1607
+ // Config check failed - continue without it
1608
+ }
1609
+
1610
+ // Check tmux availability (only if tmuxAutoSpawn is enabled)
1611
+ let tmuxCheck = { available: true };
1612
+ const tmuxAutoSpawnEnabled = cache?.metadata?.features?.tmuxAutoSpawn?.enabled !== false;
1613
+ if (tmuxAutoSpawnEnabled) {
1614
+ tmuxCheck = checkTmuxAvailability();
1615
+ }
1616
+
1271
1617
  // Show session banner FIRST if in a non-main session
1272
1618
  const sessionBanner = formatSessionBanner(parallelSessions);
1273
1619
  if (sessionBanner) {
@@ -1287,6 +1633,44 @@ async function main() {
1287
1633
  )
1288
1634
  );
1289
1635
 
1636
+ // Show config auto-apply confirmation (for "full" profile)
1637
+ if (configAutoApplied > 0) {
1638
+ console.log('');
1639
+ console.log(`${c.mintGreen}✨ Auto-applied ${configAutoApplied} new config option(s)${c.reset}`);
1640
+ console.log(` ${c.slate}Profile "full" enables all new features automatically.${c.reset}`);
1641
+ }
1642
+
1643
+ // Show config staleness notification (for custom profiles)
1644
+ if (configStaleness.outdated && configStaleness.newOptionsCount > 0) {
1645
+ console.log('');
1646
+ console.log(`${c.amber}⚙️ ${configStaleness.newOptionsCount} new configuration option(s) available${c.reset}`);
1647
+ for (const opt of configStaleness.newOptions.slice(0, 3)) {
1648
+ console.log(` ${c.dim}• ${opt.description}${c.reset}`);
1649
+ }
1650
+ console.log(` ${c.slate}Run ${c.skyBlue}/agileflow:configure${c.reset}${c.slate} to enable them.${c.reset}`);
1651
+ }
1652
+
1653
+ // Show tmux installation notice if tmux auto-spawn is enabled but tmux not installed
1654
+ if (tmuxAutoSpawnEnabled && !tmuxCheck.available) {
1655
+ console.log('');
1656
+ console.log(`${c.amber}📦 tmux not installed${c.reset} ${c.dim}(enables parallel sessions in one terminal)${c.reset}`);
1657
+
1658
+ // Show platform-specific install command
1659
+ if (tmuxCheck.platform?.installCmd) {
1660
+ console.log(` ${c.slate}Install for ${tmuxCheck.platform.os}:${c.reset}`);
1661
+ console.log(` ${c.cyan}${tmuxCheck.platform.installCmd}${c.reset}`);
1662
+ console.log('');
1663
+ console.log(` ${c.dim}No sudo? Use: ${c.cyan}${tmuxCheck.noSudoCmd}${c.reset}`);
1664
+ } else {
1665
+ // Unknown platform - show all options
1666
+ console.log(` ${c.slate}Install with one of:${c.reset}`);
1667
+ console.log(` ${c.dim}• macOS:${c.reset} ${c.cyan}brew install tmux${c.reset}`);
1668
+ console.log(` ${c.dim}• Ubuntu:${c.reset} ${c.cyan}sudo apt install tmux${c.reset}`);
1669
+ console.log(` ${c.dim}• No sudo:${c.reset} ${c.cyan}${tmuxCheck.noSudoCmd}${c.reset}`);
1670
+ }
1671
+ console.log(` ${c.dim}Or disable this notice: ${c.skyBlue}/agileflow:configure --disable=tmuxautospawn${c.reset}`);
1672
+ }
1673
+
1290
1674
  // Show warning and tip if other sessions are active (vibrant colors)
1291
1675
  if (parallelSessions.otherActive > 0) {
1292
1676
  console.log('');
@@ -1360,6 +1744,34 @@ async function main() {
1360
1744
  // Silently ignore file tracking errors
1361
1745
  }
1362
1746
  }
1747
+
1748
+ // Epic completion check: auto-complete epics where all stories are done
1749
+ if (storyStateMachine && cache.status) {
1750
+ try {
1751
+ const statusPath = getStatusPath(rootDir);
1752
+ const statusData = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
1753
+ const incompleteEpics = storyStateMachine.findIncompleteEpics(statusData);
1754
+
1755
+ if (incompleteEpics.length > 0) {
1756
+ let autoCompleted = 0;
1757
+ for (const { epicId, completed, total } of incompleteEpics) {
1758
+ const result = storyStateMachine.autoCompleteEpic(statusData, epicId);
1759
+ if (result.updated) {
1760
+ autoCompleted++;
1761
+ console.log('');
1762
+ console.log(
1763
+ `${c.mintGreen}✅ Auto-completed ${c.bold}${epicId}${c.reset}${c.mintGreen} (${completed}/${total} stories done)${c.reset}`
1764
+ );
1765
+ }
1766
+ }
1767
+ if (autoCompleted > 0) {
1768
+ fs.writeFileSync(statusPath, JSON.stringify(statusData, null, 2) + '\n');
1769
+ }
1770
+ }
1771
+ } catch (e) {
1772
+ // Silently ignore epic completion errors
1773
+ }
1774
+ }
1363
1775
  }
1364
1776
 
1365
1777
  main().catch(console.error);