agileflow 2.91.0 → 2.92.1

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 (100) 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 +32 -23
  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.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +491 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +50 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +127 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +408 -55
  48. package/scripts/spawn-parallel.js +666 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +132 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +95 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/installers/ide/windsurf.js +1 -1
  94. package/tools/cli/lib/config-manager.js +17 -2
  95. package/tools/cli/lib/content-transformer.js +271 -0
  96. package/tools/cli/lib/error-handler.js +14 -22
  97. package/tools/cli/lib/ide-error-factory.js +421 -0
  98. package/tools/cli/lib/ide-health-monitor.js +364 -0
  99. package/tools/cli/lib/ide-registry.js +113 -2
  100. package/tools/cli/lib/ui.js +15 -25
@@ -0,0 +1,364 @@
1
+ /**
2
+ * AgileFlow CLI - IDE Health Monitor
3
+ *
4
+ * Monitors IDE detection health with caching and circuit-breaker pattern.
5
+ * Reduces filesystem operations during repeated IDE checks.
6
+ *
7
+ * Features:
8
+ * - LRU cache for IDE detection results (300s TTL by default)
9
+ * - Circuit-breaker pattern to stop checking failing IDEs
10
+ * - Health metrics logging to .agileflow/cache/ide-health.json
11
+ */
12
+
13
+ const fs = require('fs-extra');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * IDE Health Monitor - Caching and circuit-breaker for IDE detection
18
+ */
19
+ class IdeHealthMonitor {
20
+ /**
21
+ * Create a new health monitor
22
+ * @param {Object} options
23
+ * @param {number} [options.cacheTtlMs=300000] - Cache TTL in milliseconds (default 5 minutes)
24
+ * @param {number} [options.maxFailures=3] - Failures before circuit opens
25
+ * @param {number} [options.circuitResetMs=600000] - Time before circuit resets (default 10 minutes)
26
+ */
27
+ constructor(options = {}) {
28
+ this.cacheTtlMs = options.cacheTtlMs || 300000; // 5 minutes default
29
+ this.maxFailures = options.maxFailures || 3;
30
+ this.circuitResetMs = options.circuitResetMs || 600000; // 10 minutes default
31
+
32
+ // Detection cache: { ideName: { result: boolean, cachedAt: timestamp, expiresAt: timestamp } }
33
+ this.detectionCache = new Map();
34
+
35
+ // Circuit breaker state: { ideName: { failures: number, openedAt: timestamp | null } }
36
+ this.circuitBreakers = new Map();
37
+
38
+ // Health metrics
39
+ this.metrics = {
40
+ totalChecks: 0,
41
+ cacheHits: 0,
42
+ cacheMisses: 0,
43
+ failures: 0,
44
+ circuitOpens: 0,
45
+ lastUpdated: null,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Check if an IDE is detected with caching
51
+ * @param {string} ideName - IDE identifier
52
+ * @param {string} projectDir - Project directory
53
+ * @param {Function} detectFn - Async function to perform actual detection
54
+ * @returns {Promise<{ detected: boolean, cached: boolean, circuitOpen: boolean }>}
55
+ */
56
+ async checkIde(ideName, projectDir, detectFn) {
57
+ this.metrics.totalChecks++;
58
+ const cacheKey = `${ideName}:${projectDir}`;
59
+
60
+ // Check circuit breaker first
61
+ const circuit = this.circuitBreakers.get(ideName);
62
+ if (circuit && circuit.openedAt) {
63
+ // Circuit is open - check if it should reset
64
+ if (Date.now() - circuit.openedAt > this.circuitResetMs) {
65
+ // Reset the circuit - allow half-open state
66
+ circuit.failures = 0;
67
+ circuit.openedAt = null;
68
+ } else {
69
+ // Circuit still open - return cached result or false
70
+ const cached = this.detectionCache.get(cacheKey);
71
+ return {
72
+ detected: cached ? cached.result : false,
73
+ cached: true,
74
+ circuitOpen: true,
75
+ };
76
+ }
77
+ }
78
+
79
+ // Check cache
80
+ const cachedEntry = this.detectionCache.get(cacheKey);
81
+ if (cachedEntry && Date.now() < cachedEntry.expiresAt) {
82
+ this.metrics.cacheHits++;
83
+ return {
84
+ detected: cachedEntry.result,
85
+ cached: true,
86
+ circuitOpen: false,
87
+ };
88
+ }
89
+
90
+ // Cache miss - perform actual detection
91
+ this.metrics.cacheMisses++;
92
+
93
+ try {
94
+ const startTime = Date.now();
95
+ const detected = await detectFn();
96
+ const duration = Date.now() - startTime;
97
+
98
+ // Cache the result
99
+ this.detectionCache.set(cacheKey, {
100
+ result: detected,
101
+ cachedAt: Date.now(),
102
+ expiresAt: Date.now() + this.cacheTtlMs,
103
+ duration,
104
+ });
105
+
106
+ // Reset failure count on success
107
+ if (this.circuitBreakers.has(ideName)) {
108
+ this.circuitBreakers.get(ideName).failures = 0;
109
+ }
110
+
111
+ return {
112
+ detected,
113
+ cached: false,
114
+ circuitOpen: false,
115
+ duration,
116
+ };
117
+ } catch (error) {
118
+ this.metrics.failures++;
119
+ this.recordFailure(ideName, error);
120
+
121
+ // Return cached value if available, otherwise false
122
+ const cached = this.detectionCache.get(cacheKey);
123
+ return {
124
+ detected: cached ? cached.result : false,
125
+ cached: !!cached,
126
+ circuitOpen: false,
127
+ error: error.message,
128
+ };
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Record a detection failure for circuit breaker
134
+ * @param {string} ideName - IDE identifier
135
+ * @param {Error} error - The error that occurred
136
+ */
137
+ recordFailure(ideName, error) {
138
+ let circuit = this.circuitBreakers.get(ideName);
139
+ if (!circuit) {
140
+ circuit = { failures: 0, openedAt: null, lastError: null };
141
+ this.circuitBreakers.set(ideName, circuit);
142
+ }
143
+
144
+ circuit.failures++;
145
+ circuit.lastError = error.message;
146
+
147
+ // Check if we should open the circuit
148
+ if (circuit.failures >= this.maxFailures && !circuit.openedAt) {
149
+ circuit.openedAt = Date.now();
150
+ this.metrics.circuitOpens++;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Get circuit state for an IDE
156
+ * @param {string} ideName - IDE identifier
157
+ * @returns {{ open: boolean, failures: number, lastError: string | null }}
158
+ */
159
+ getCircuitState(ideName) {
160
+ const circuit = this.circuitBreakers.get(ideName);
161
+ if (!circuit) {
162
+ return { open: false, failures: 0, lastError: null };
163
+ }
164
+
165
+ // Check if circuit should auto-reset
166
+ if (circuit.openedAt && Date.now() - circuit.openedAt > this.circuitResetMs) {
167
+ return { open: false, failures: 0, lastError: circuit.lastError };
168
+ }
169
+
170
+ return {
171
+ open: !!circuit.openedAt,
172
+ failures: circuit.failures,
173
+ lastError: circuit.lastError,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Invalidate cache for a specific IDE/project
179
+ * @param {string} ideName - IDE identifier (or '*' for all)
180
+ * @param {string} [projectDir] - Project directory (optional)
181
+ */
182
+ invalidate(ideName, projectDir) {
183
+ if (ideName === '*') {
184
+ this.detectionCache.clear();
185
+ return;
186
+ }
187
+
188
+ if (projectDir) {
189
+ const cacheKey = `${ideName}:${projectDir}`;
190
+ this.detectionCache.delete(cacheKey);
191
+ } else {
192
+ // Remove all entries for this IDE
193
+ for (const key of this.detectionCache.keys()) {
194
+ if (key.startsWith(`${ideName}:`)) {
195
+ this.detectionCache.delete(key);
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Get current health metrics
203
+ * @returns {Object} Health metrics
204
+ */
205
+ getMetrics() {
206
+ const hitRate =
207
+ this.metrics.cacheHits + this.metrics.cacheMisses > 0
208
+ ? (
209
+ (this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses)) *
210
+ 100
211
+ ).toFixed(1) + '%'
212
+ : '0%';
213
+
214
+ return {
215
+ ...this.metrics,
216
+ cacheSize: this.detectionCache.size,
217
+ openCircuits: [...this.circuitBreakers.values()].filter(c => c.openedAt).length,
218
+ hitRate,
219
+ lastUpdated: new Date().toISOString(),
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Save health metrics to file
225
+ * @param {string} projectDir - Project directory
226
+ * @returns {Promise<void>}
227
+ */
228
+ async saveMetrics(projectDir) {
229
+ const cacheDir = path.join(projectDir, '.agileflow', 'cache');
230
+ const metricsPath = path.join(cacheDir, 'ide-health.json');
231
+
232
+ try {
233
+ await fs.ensureDir(cacheDir);
234
+
235
+ const data = {
236
+ metrics: this.getMetrics(),
237
+ cache: this.getCacheSnapshot(),
238
+ circuits: this.getCircuitSnapshot(),
239
+ savedAt: new Date().toISOString(),
240
+ };
241
+
242
+ await fs.writeJson(metricsPath, data, { spaces: 2 });
243
+ this.metrics.lastUpdated = data.savedAt;
244
+ } catch {
245
+ // Silently ignore save errors - metrics are not critical
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Load health metrics from file
251
+ * @param {string} projectDir - Project directory
252
+ * @returns {Promise<boolean>} True if loaded successfully
253
+ */
254
+ async loadMetrics(projectDir) {
255
+ const metricsPath = path.join(projectDir, '.agileflow', 'cache', 'ide-health.json');
256
+
257
+ try {
258
+ if (await fs.pathExists(metricsPath)) {
259
+ const data = await fs.readJson(metricsPath);
260
+
261
+ // Restore metrics (additive)
262
+ if (data.metrics) {
263
+ this.metrics.totalChecks += data.metrics.totalChecks || 0;
264
+ this.metrics.cacheHits += data.metrics.cacheHits || 0;
265
+ this.metrics.cacheMisses += data.metrics.cacheMisses || 0;
266
+ this.metrics.failures += data.metrics.failures || 0;
267
+ this.metrics.circuitOpens += data.metrics.circuitOpens || 0;
268
+ }
269
+
270
+ // Restore cache entries that haven't expired
271
+ if (data.cache) {
272
+ for (const [key, entry] of Object.entries(data.cache)) {
273
+ if (entry.expiresAt > Date.now()) {
274
+ this.detectionCache.set(key, entry);
275
+ }
276
+ }
277
+ }
278
+
279
+ // Restore circuit breaker state
280
+ if (data.circuits) {
281
+ for (const [key, state] of Object.entries(data.circuits)) {
282
+ this.circuitBreakers.set(key, state);
283
+ }
284
+ }
285
+
286
+ return true;
287
+ }
288
+ } catch {
289
+ // Ignore load errors
290
+ }
291
+ return false;
292
+ }
293
+
294
+ /**
295
+ * Get cache snapshot for persistence
296
+ * @returns {Object}
297
+ */
298
+ getCacheSnapshot() {
299
+ const snapshot = {};
300
+ for (const [key, entry] of this.detectionCache) {
301
+ snapshot[key] = entry;
302
+ }
303
+ return snapshot;
304
+ }
305
+
306
+ /**
307
+ * Get circuit breaker snapshot for persistence
308
+ * @returns {Object}
309
+ */
310
+ getCircuitSnapshot() {
311
+ const snapshot = {};
312
+ for (const [key, state] of this.circuitBreakers) {
313
+ snapshot[key] = state;
314
+ }
315
+ return snapshot;
316
+ }
317
+
318
+ /**
319
+ * Reset all state (for testing)
320
+ */
321
+ reset() {
322
+ this.detectionCache.clear();
323
+ this.circuitBreakers.clear();
324
+ this.metrics = {
325
+ totalChecks: 0,
326
+ cacheHits: 0,
327
+ cacheMisses: 0,
328
+ failures: 0,
329
+ circuitOpens: 0,
330
+ lastUpdated: null,
331
+ };
332
+ }
333
+ }
334
+
335
+ // Singleton instance for global usage
336
+ let globalMonitor = null;
337
+
338
+ /**
339
+ * Get the global health monitor instance
340
+ * @param {Object} [options] - Options for new instance
341
+ * @returns {IdeHealthMonitor}
342
+ */
343
+ function getHealthMonitor(options) {
344
+ if (!globalMonitor) {
345
+ globalMonitor = new IdeHealthMonitor(options);
346
+ }
347
+ return globalMonitor;
348
+ }
349
+
350
+ /**
351
+ * Reset the global monitor (for testing)
352
+ */
353
+ function resetHealthMonitor() {
354
+ if (globalMonitor) {
355
+ globalMonitor.reset();
356
+ }
357
+ globalMonitor = null;
358
+ }
359
+
360
+ module.exports = {
361
+ IdeHealthMonitor,
362
+ getHealthMonitor,
363
+ resetHealthMonitor,
364
+ };
@@ -4,10 +4,13 @@
4
4
  * Centralized registry of supported IDEs with their metadata.
5
5
  * This eliminates duplicate IDE configuration scattered across commands.
6
6
  *
7
+ * Enhanced as part of US-0178: Consolidate IDE installer config into unified registry pattern
8
+ *
7
9
  * Usage:
8
10
  * const { IdeRegistry } = require('./lib/ide-registry');
9
11
  * const configPath = IdeRegistry.getConfigPath('claude-code', projectDir);
10
12
  * const displayName = IdeRegistry.getDisplayName('cursor');
13
+ * const config = IdeRegistry.get('windsurf');
11
14
  */
12
15
 
13
16
  const path = require('path');
@@ -18,9 +21,14 @@ const path = require('path');
18
21
  * @property {string} name - Internal IDE name (e.g., 'claude-code')
19
22
  * @property {string} displayName - Human-readable name (e.g., 'Claude Code')
20
23
  * @property {string} configDir - Base config directory (e.g., '.claude')
21
- * @property {string} targetSubdir - Target subdirectory for commands (e.g., 'commands/agileflow')
24
+ * @property {string} commandsSubdir - Subdirectory for commands (e.g., 'commands', 'workflows')
25
+ * @property {string} agileflowFolder - AgileFlow folder name (e.g., 'agileflow', 'AgileFlow')
26
+ * @property {string} targetSubdir - Full target path (computed from commandsSubdir + agileflowFolder)
22
27
  * @property {boolean} preferred - Whether this is a preferred IDE
28
+ * @property {string} description - Short description for UI
23
29
  * @property {string} [handler] - Handler class name (e.g., 'ClaudeCodeSetup')
30
+ * @property {Object} labels - Custom labels for installed content
31
+ * @property {Object} features - Feature flags for this IDE
24
32
  */
25
33
 
26
34
  /**
@@ -32,33 +40,83 @@ const IDE_REGISTRY = {
32
40
  name: 'claude-code',
33
41
  displayName: 'Claude Code',
34
42
  configDir: '.claude',
43
+ commandsSubdir: 'commands',
44
+ agileflowFolder: 'agileflow',
35
45
  targetSubdir: 'commands/agileflow', // lowercase
36
46
  preferred: true,
47
+ description: "Anthropic's Claude Code IDE",
37
48
  handler: 'ClaudeCodeSetup',
49
+ labels: {
50
+ commands: 'commands',
51
+ agents: 'agents',
52
+ },
53
+ features: {
54
+ spawnableAgents: true, // Has .claude/agents/ for Task tool
55
+ skills: true, // Has .claude/skills/ for user skills
56
+ damageControl: true, // Supports PreToolUse hooks
57
+ },
38
58
  },
39
59
  cursor: {
40
60
  name: 'cursor',
41
61
  displayName: 'Cursor',
42
62
  configDir: '.cursor',
63
+ commandsSubdir: 'commands',
64
+ agileflowFolder: 'AgileFlow',
43
65
  targetSubdir: 'commands/AgileFlow', // PascalCase
44
66
  preferred: false,
67
+ description: 'AI-powered code editor',
45
68
  handler: 'CursorSetup',
69
+ labels: {
70
+ commands: 'commands',
71
+ agents: 'agents',
72
+ },
73
+ features: {
74
+ spawnableAgents: false,
75
+ skills: false,
76
+ damageControl: false,
77
+ },
46
78
  },
47
79
  windsurf: {
48
80
  name: 'windsurf',
49
81
  displayName: 'Windsurf',
50
82
  configDir: '.windsurf',
83
+ commandsSubdir: 'workflows',
84
+ agileflowFolder: 'agileflow',
51
85
  targetSubdir: 'workflows/agileflow', // lowercase
52
- preferred: true,
86
+ preferred: false,
87
+ description: "Codeium's AI IDE",
53
88
  handler: 'WindsurfSetup',
89
+ labels: {
90
+ commands: 'workflows',
91
+ agents: 'agent workflows',
92
+ },
93
+ features: {
94
+ spawnableAgents: false,
95
+ skills: false,
96
+ damageControl: false,
97
+ },
54
98
  },
55
99
  codex: {
56
100
  name: 'codex',
57
101
  displayName: 'OpenAI Codex CLI',
58
102
  configDir: '.codex',
103
+ commandsSubdir: 'skills',
104
+ agileflowFolder: 'agileflow',
59
105
  targetSubdir: 'skills', // Codex uses skills directory
60
106
  preferred: false,
107
+ description: "OpenAI's Codex CLI",
61
108
  handler: 'CodexSetup',
109
+ labels: {
110
+ commands: 'prompts',
111
+ agents: 'skills',
112
+ },
113
+ features: {
114
+ spawnableAgents: false,
115
+ skills: true, // Has .codex/skills/
116
+ damageControl: false,
117
+ customInstall: true, // Uses custom install flow (not setupStandard)
118
+ agentsMd: true, // Creates AGENTS.md at project root
119
+ },
62
120
  },
63
121
  };
64
122
 
@@ -178,6 +236,59 @@ class IdeRegistry {
178
236
  const ide = IDE_REGISTRY[ideName];
179
237
  return ide ? ide.handler : null;
180
238
  }
239
+
240
+ /**
241
+ * Get IDE choices formatted for UI selection (inquirer checkbox format)
242
+ * @returns {Array} Array of {name, value, checked, configDir, description}
243
+ */
244
+ static getChoices() {
245
+ return Object.values(IDE_REGISTRY)
246
+ .sort((a, b) => {
247
+ // Preferred first, then alphabetical
248
+ if (a.preferred && !b.preferred) return -1;
249
+ if (!a.preferred && b.preferred) return 1;
250
+ return a.displayName.localeCompare(b.displayName);
251
+ })
252
+ .map((ide, index) => ({
253
+ name: ide.displayName,
254
+ value: ide.name,
255
+ checked: ide.preferred || index === 0,
256
+ configDir: `${ide.configDir}/${ide.commandsSubdir}`,
257
+ description: ide.description,
258
+ }));
259
+ }
260
+
261
+ /**
262
+ * Check if an IDE has a specific feature
263
+ * @param {string} ideName - IDE name
264
+ * @param {string} featureName - Feature to check
265
+ * @returns {boolean}
266
+ */
267
+ static hasFeature(ideName, featureName) {
268
+ const ide = IDE_REGISTRY[ideName];
269
+ return !!(ide && ide.features && ide.features[featureName]);
270
+ }
271
+
272
+ /**
273
+ * Get all IDEs with a specific feature
274
+ * @param {string} featureName - Feature to filter by
275
+ * @returns {string[]} Array of IDE names
276
+ */
277
+ static getWithFeature(featureName) {
278
+ return Object.entries(IDE_REGISTRY)
279
+ .filter(([, meta]) => meta.features && meta.features[featureName])
280
+ .map(([name]) => name);
281
+ }
282
+
283
+ /**
284
+ * Get labels for an IDE (e.g., 'commands' vs 'workflows')
285
+ * @param {string} ideName - IDE name
286
+ * @returns {Object} Labels object {commands, agents}
287
+ */
288
+ static getLabels(ideName) {
289
+ const ide = IDE_REGISTRY[ideName];
290
+ return ide && ide.labels ? ide.labels : { commands: 'commands', agents: 'agents' };
291
+ }
181
292
  }
182
293
 
183
294
  module.exports = {
@@ -9,6 +9,7 @@ const inquirer = require('inquirer');
9
9
  const path = require('node:path');
10
10
  const fs = require('node:fs');
11
11
  const { IdeManager } = require('../installers/ide/manager');
12
+ const { IdeRegistry } = require('./ide-registry');
12
13
  const { BRAND_HEX } = require('../../../lib/colors');
13
14
 
14
15
  // Load package.json for version
@@ -95,31 +96,10 @@ function getIdeChoices() {
95
96
 
96
97
  /**
97
98
  * @deprecated Use getIdeChoices() instead - dynamically loaded from IDE handlers
98
- * Legacy hardcoded IDE choices kept for backward compatibility
99
+ * Legacy hardcoded IDE choices - now sourced from IdeRegistry for consistency
100
+ * Kept for backward compatibility with code that imports IDE_CHOICES directly
99
101
  */
100
- const IDE_CHOICES = [
101
- {
102
- name: 'Claude Code',
103
- value: 'claude-code',
104
- checked: true,
105
- configDir: '.claude/commands',
106
- description: "Anthropic's Claude Code IDE",
107
- },
108
- {
109
- name: 'Cursor',
110
- value: 'cursor',
111
- checked: false,
112
- configDir: '.cursor/rules',
113
- description: 'AI-powered code editor',
114
- },
115
- {
116
- name: 'Windsurf',
117
- value: 'windsurf',
118
- checked: false,
119
- configDir: '.windsurf/workflows',
120
- description: "Codeium's AI IDE",
121
- },
122
- ];
102
+ const IDE_CHOICES = IdeRegistry.getChoices();
123
103
 
124
104
  /**
125
105
  * Prompt for installation configuration
@@ -191,6 +171,13 @@ async function promptInstall() {
191
171
  message: 'Create/update .gitignore with recommended entries?',
192
172
  default: true,
193
173
  },
174
+ {
175
+ type: 'confirm',
176
+ name: 'claudeMdReinforcement',
177
+ message:
178
+ 'Add /babysit AskUserQuestion rules to CLAUDE.md? (recommended for context preservation)',
179
+ default: true,
180
+ },
194
181
  ]);
195
182
 
196
183
  return {
@@ -200,6 +187,7 @@ async function promptInstall() {
200
187
  agileflowFolder: answers.agileflowFolder,
201
188
  docsFolder: answers.docsFolder,
202
189
  updateGitignore: answers.updateGitignore,
190
+ claudeMdReinforcement: answers.claudeMdReinforcement,
203
191
  };
204
192
  }
205
193
 
@@ -223,11 +211,13 @@ async function confirm(message, defaultValue = true) {
223
211
 
224
212
  /**
225
213
  * Get IDE configuration by name
214
+ * Uses IdeRegistry for consistent configuration
226
215
  * @param {string} ideName - IDE name
227
216
  * @returns {Object|null}
228
217
  */
229
218
  function getIdeConfig(ideName) {
230
- return IDE_CHOICES.find(ide => ide.value === ideName) || null;
219
+ // Use IdeRegistry as the single source of truth
220
+ return IdeRegistry.get(ideName);
231
221
  }
232
222
 
233
223
  module.exports = {