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
|
@@ -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}
|
|
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
86
|
preferred: true,
|
|
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,61 @@ 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
|
|
291
|
+
? ide.labels
|
|
292
|
+
: { commands: 'commands', agents: 'agents' };
|
|
293
|
+
}
|
|
181
294
|
}
|
|
182
295
|
|
|
183
296
|
module.exports = {
|
package/tools/cli/lib/ui.js
CHANGED
|
@@ -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
|
|
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,12 @@ 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: 'Add /babysit AskUserQuestion rules to CLAUDE.md? (recommended for context preservation)',
|
|
178
|
+
default: true,
|
|
179
|
+
},
|
|
194
180
|
]);
|
|
195
181
|
|
|
196
182
|
return {
|
|
@@ -200,6 +186,7 @@ async function promptInstall() {
|
|
|
200
186
|
agileflowFolder: answers.agileflowFolder,
|
|
201
187
|
docsFolder: answers.docsFolder,
|
|
202
188
|
updateGitignore: answers.updateGitignore,
|
|
189
|
+
claudeMdReinforcement: answers.claudeMdReinforcement,
|
|
203
190
|
};
|
|
204
191
|
}
|
|
205
192
|
|
|
@@ -223,11 +210,13 @@ async function confirm(message, defaultValue = true) {
|
|
|
223
210
|
|
|
224
211
|
/**
|
|
225
212
|
* Get IDE configuration by name
|
|
213
|
+
* Uses IdeRegistry for consistent configuration
|
|
226
214
|
* @param {string} ideName - IDE name
|
|
227
215
|
* @returns {Object|null}
|
|
228
216
|
*/
|
|
229
217
|
function getIdeConfig(ideName) {
|
|
230
|
-
|
|
218
|
+
// Use IdeRegistry as the single source of truth
|
|
219
|
+
return IdeRegistry.get(ideName);
|
|
231
220
|
}
|
|
232
221
|
|
|
233
222
|
module.exports = {
|