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.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +818 -0
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate-names.js +3 -3
- package/lib/validate.js +116 -52
- package/package.json +4 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +118 -1048
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +36 -11
- package/scripts/query-codebase.js +538 -0
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +180 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +101 -0
- package/scripts/tui/blessed/panels/sessions.js +150 -0
- package/scripts/tui/blessed/panels/trace.js +97 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +47 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +239 -0
- package/scripts/validators/json-schema-validator.js +186 -0
- package/scripts/validators/markdown-validator.js +152 -0
- package/scripts/validators/migration-validator.js +129 -0
- package/scripts/validators/security-validator.js +380 -0
- package/scripts/validators/story-format-validator.js +197 -0
- package/scripts/validators/test-result-validator.js +114 -0
- package/scripts/validators/workflow-validator.js +247 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +261 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +205 -1
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +76 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +200 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- 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 {
|
|
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:
|
|
59
|
-
metadata:
|
|
60
|
-
settings: path.join(rootDir, '
|
|
61
|
-
sessionState:
|
|
62
|
-
configYaml: path.join(rootDir, '
|
|
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, '
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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, '
|
|
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, '
|
|
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
|
|
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 =
|
|
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, '
|
|
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, '
|
|
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, '
|
|
564
|
-
path.join(rootDir, '
|
|
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, '
|
|
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, '
|
|
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 =
|
|
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);
|