agileflow 2.89.2 → 2.90.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 (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -3
  3. package/lib/content-sanitizer.js +463 -0
  4. package/lib/error-codes.js +544 -0
  5. package/lib/errors.js +336 -5
  6. package/lib/feedback.js +561 -0
  7. package/lib/path-resolver.js +396 -0
  8. package/lib/placeholder-registry.js +617 -0
  9. package/lib/session-registry.js +461 -0
  10. package/lib/smart-json-file.js +653 -0
  11. package/lib/table-formatter.js +504 -0
  12. package/lib/transient-status.js +374 -0
  13. package/lib/ui-manager.js +612 -0
  14. package/lib/validate-args.js +213 -0
  15. package/lib/validate-names.js +143 -0
  16. package/lib/validate-paths.js +434 -0
  17. package/lib/validate.js +38 -584
  18. package/package.json +4 -1
  19. package/scripts/agileflow-configure.js +40 -1440
  20. package/scripts/agileflow-welcome.js +2 -1
  21. package/scripts/check-update.js +16 -3
  22. package/scripts/lib/configure-detect.js +383 -0
  23. package/scripts/lib/configure-features.js +811 -0
  24. package/scripts/lib/configure-repair.js +314 -0
  25. package/scripts/lib/configure-utils.js +115 -0
  26. package/scripts/lib/frontmatter-parser.js +3 -3
  27. package/scripts/lib/sessionRegistry.js +682 -0
  28. package/scripts/obtain-context.js +417 -113
  29. package/scripts/ralph-loop.js +1 -1
  30. package/scripts/session-manager.js +77 -10
  31. package/scripts/tui/App.js +176 -0
  32. package/scripts/tui/index.js +75 -0
  33. package/scripts/tui/lib/crashRecovery.js +302 -0
  34. package/scripts/tui/lib/eventStream.js +316 -0
  35. package/scripts/tui/lib/keyboard.js +252 -0
  36. package/scripts/tui/lib/loopControl.js +371 -0
  37. package/scripts/tui/panels/OutputPanel.js +278 -0
  38. package/scripts/tui/panels/SessionPanel.js +178 -0
  39. package/scripts/tui/panels/TracePanel.js +333 -0
  40. package/src/core/commands/tui.md +91 -0
  41. package/tools/cli/commands/config.js +10 -33
  42. package/tools/cli/commands/doctor.js +48 -40
  43. package/tools/cli/commands/list.js +49 -37
  44. package/tools/cli/commands/status.js +13 -37
  45. package/tools/cli/commands/uninstall.js +12 -41
  46. package/tools/cli/installers/core/installer.js +75 -12
  47. package/tools/cli/installers/ide/_interface.js +238 -0
  48. package/tools/cli/installers/ide/codex.js +2 -2
  49. package/tools/cli/installers/ide/manager.js +15 -0
  50. package/tools/cli/lib/command-context.js +374 -0
  51. package/tools/cli/lib/config-manager.js +394 -0
  52. package/tools/cli/lib/content-injector.js +69 -16
  53. package/tools/cli/lib/ide-errors.js +163 -29
  54. package/tools/cli/lib/ide-registry.js +186 -0
  55. package/tools/cli/lib/npm-utils.js +16 -3
  56. package/tools/cli/lib/self-update.js +148 -0
  57. package/tools/cli/lib/validation-middleware.js +491 -0
@@ -298,7 +298,8 @@ function clearActiveCommands(rootDir, cache = null) {
298
298
  const now = Date.now();
299
299
  const secondsSincePrecompact = (now - precompactTime) / 1000;
300
300
 
301
- if (secondsSincePrecompact < 600) { // 10 minutes - compacts can take a while with background tasks
301
+ if (secondsSincePrecompact < 600) {
302
+ // 10 minutes - compacts can take a while with background tasks
302
303
  // This is a post-compact session start - preserve active commands
303
304
  result.preserved = true;
304
305
  // Capture command names for display (but don't clear)
@@ -112,6 +112,9 @@ async function fetchLatestVersion() {
112
112
  headers: {
113
113
  'User-Agent': 'agileflow-cli',
114
114
  },
115
+ // Security: Explicitly enable TLS certificate validation
116
+ // Prevents MITM attacks on npm registry requests
117
+ rejectUnauthorized: true,
115
118
  };
116
119
 
117
120
  debugLog('Fetching from npm registry');
@@ -141,12 +144,22 @@ async function fetchLatestVersion() {
141
144
  });
142
145
 
143
146
  req.on('error', err => {
144
- debugLog('Network error', err.message);
147
+ // Enhanced error logging with retry guidance
148
+ const errorInfo = {
149
+ error: err.message,
150
+ code: err.code,
151
+ suggestion: 'Check network connection. If error persists, try: npm cache clean --force',
152
+ };
153
+ if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
154
+ errorInfo.suggestion = 'TLS certificate error - check system time or update CA certificates';
155
+ }
156
+ debugLog('Network error', errorInfo);
145
157
  resolve(null);
146
158
  });
147
159
 
148
- req.setTimeout(5000, () => {
149
- debugLog('Request timeout');
160
+ // 10 second timeout for registry requests
161
+ req.setTimeout(10000, () => {
162
+ debugLog('Request timeout (10s)', { suggestion: 'npm registry may be slow. Retry later.' });
150
163
  req.destroy();
151
164
  resolve(null);
152
165
  });
@@ -0,0 +1,383 @@
1
+ /**
2
+ * configure-detect.js - Detection and validation for agileflow-configure
3
+ *
4
+ * Extracted from agileflow-configure.js (US-0094)
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execSync } = require('child_process');
10
+ const { c, log, header, readJSON } = require('./configure-utils');
11
+
12
+ // ============================================================================
13
+ // DETECTION
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Detect current AgileFlow configuration status
18
+ * @param {string} version - Current VERSION string
19
+ * @returns {object} Configuration status object
20
+ */
21
+ function detectConfig(version) {
22
+ const status = {
23
+ git: { initialized: false, remote: null },
24
+ settingsExists: false,
25
+ settingsValid: true,
26
+ settingsIssues: [],
27
+ features: {
28
+ sessionstart: { enabled: false, valid: true, issues: [], version: null, outdated: false },
29
+ precompact: { enabled: false, valid: true, issues: [], version: null, outdated: false },
30
+ ralphloop: { enabled: false, valid: true, issues: [], version: null, outdated: false },
31
+ selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
32
+ archival: { enabled: false, threshold: null, version: null, outdated: false },
33
+ statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
34
+ damagecontrol: {
35
+ enabled: false,
36
+ valid: true,
37
+ issues: [],
38
+ version: null,
39
+ outdated: false,
40
+ level: null,
41
+ patternCount: 0,
42
+ },
43
+ askuserquestion: {
44
+ enabled: false,
45
+ valid: true,
46
+ issues: [],
47
+ version: null,
48
+ outdated: false,
49
+ mode: null,
50
+ },
51
+ },
52
+ metadata: { exists: false, version: null },
53
+ currentVersion: version,
54
+ hasOutdated: false,
55
+ };
56
+
57
+ // Git detection
58
+ if (fs.existsSync('.git')) {
59
+ status.git.initialized = true;
60
+ try {
61
+ status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
62
+ encoding: 'utf8',
63
+ }).trim();
64
+ } catch {}
65
+ }
66
+
67
+ // Settings file detection
68
+ if (fs.existsSync('.claude/settings.json')) {
69
+ status.settingsExists = true;
70
+ const settings = readJSON('.claude/settings.json');
71
+
72
+ if (!settings) {
73
+ status.settingsValid = false;
74
+ status.settingsIssues.push('Invalid JSON in settings.json');
75
+ } else {
76
+ detectHooks(settings, status);
77
+ detectStatusLine(settings, status);
78
+ }
79
+ }
80
+
81
+ // Metadata detection
82
+ detectMetadata(status, version);
83
+
84
+ return status;
85
+ }
86
+
87
+ /**
88
+ * Detect hook configurations in settings
89
+ */
90
+ function detectHooks(settings, status) {
91
+ if (!settings.hooks) return;
92
+
93
+ // SessionStart detection
94
+ if (settings.hooks.SessionStart) {
95
+ detectSessionStartHook(settings.hooks.SessionStart, status);
96
+ }
97
+
98
+ // PreCompact detection
99
+ if (settings.hooks.PreCompact) {
100
+ detectPreCompactHook(settings.hooks.PreCompact, status);
101
+ }
102
+
103
+ // Stop hooks detection (ralphloop and selfimprove)
104
+ if (settings.hooks.Stop) {
105
+ detectStopHooks(settings.hooks.Stop, status);
106
+ }
107
+
108
+ // PreToolUse hooks detection (damage control)
109
+ if (settings.hooks.PreToolUse) {
110
+ detectPreToolUseHooks(settings.hooks.PreToolUse, status);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Detect SessionStart hook configuration
116
+ */
117
+ function detectSessionStartHook(hook, status) {
118
+ if (Array.isArray(hook) && hook.length > 0) {
119
+ const first = hook[0];
120
+ if (first.matcher !== undefined && first.hooks) {
121
+ status.features.sessionstart.enabled = true;
122
+ } else {
123
+ status.features.sessionstart.enabled = true;
124
+ status.features.sessionstart.valid = false;
125
+ status.features.sessionstart.issues.push('Old format - needs migration');
126
+ }
127
+ } else if (typeof hook === 'string') {
128
+ status.features.sessionstart.enabled = true;
129
+ status.features.sessionstart.valid = false;
130
+ status.features.sessionstart.issues.push('String format - needs migration');
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Detect PreCompact hook configuration
136
+ */
137
+ function detectPreCompactHook(hook, status) {
138
+ if (Array.isArray(hook) && hook.length > 0) {
139
+ const first = hook[0];
140
+ if (first.matcher !== undefined && first.hooks) {
141
+ status.features.precompact.enabled = true;
142
+ } else {
143
+ status.features.precompact.enabled = true;
144
+ status.features.precompact.valid = false;
145
+ status.features.precompact.issues.push('Old format - needs migration');
146
+ }
147
+ } else if (typeof hook === 'string') {
148
+ status.features.precompact.enabled = true;
149
+ status.features.precompact.valid = false;
150
+ status.features.precompact.issues.push('String format - needs migration');
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Detect Stop hook configuration (ralphloop, selfimprove)
156
+ */
157
+ function detectStopHooks(hook, status) {
158
+ if (Array.isArray(hook) && hook.length > 0) {
159
+ const first = hook[0];
160
+ if (first.matcher !== undefined && first.hooks) {
161
+ for (const h of first.hooks) {
162
+ if (h.command?.includes('ralph-loop')) {
163
+ status.features.ralphloop.enabled = true;
164
+ }
165
+ if (h.command?.includes('auto-self-improve')) {
166
+ status.features.selfimprove.enabled = true;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Detect PreToolUse hooks (damage control)
175
+ */
176
+ function detectPreToolUseHooks(hooks, status) {
177
+ if (!Array.isArray(hooks) || hooks.length === 0) return;
178
+
179
+ const hasBashHook = hooks.some(
180
+ h => h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
181
+ );
182
+ const hasEditHook = hooks.some(
183
+ h => h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
184
+ );
185
+ const hasWriteHook = hooks.some(
186
+ h => h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
187
+ );
188
+
189
+ if (hasBashHook || hasEditHook || hasWriteHook) {
190
+ status.features.damagecontrol.enabled = true;
191
+ const hookCount = [hasBashHook, hasEditHook, hasWriteHook].filter(Boolean).length;
192
+ if (hookCount < 3) {
193
+ status.features.damagecontrol.valid = false;
194
+ status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
195
+ }
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Detect statusLine configuration
201
+ */
202
+ function detectStatusLine(settings, status) {
203
+ if (!settings.statusLine) return;
204
+
205
+ status.features.statusline.enabled = true;
206
+ if (typeof settings.statusLine === 'string') {
207
+ status.features.statusline.valid = false;
208
+ status.features.statusline.issues.push('String format - needs type:command');
209
+ } else if (!settings.statusLine.type) {
210
+ status.features.statusline.valid = false;
211
+ status.features.statusline.issues.push('Missing type:command');
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Detect metadata file configuration
217
+ */
218
+ function detectMetadata(status, version) {
219
+ const metaPath = 'docs/00-meta/agileflow-metadata.json';
220
+ if (!fs.existsSync(metaPath)) return;
221
+
222
+ status.metadata.exists = true;
223
+ const meta = readJSON(metaPath);
224
+ if (!meta) return;
225
+
226
+ status.metadata.version = meta.version;
227
+
228
+ // Archival settings
229
+ if (meta.archival?.enabled) {
230
+ status.features.archival.enabled = true;
231
+ status.features.archival.threshold = meta.archival.threshold_days;
232
+ }
233
+
234
+ // Damage control metadata
235
+ if (meta.features?.damagecontrol?.enabled) {
236
+ status.features.damagecontrol.level = meta.features.damagecontrol.protectionLevel || 'standard';
237
+ }
238
+
239
+ // AskUserQuestion metadata
240
+ if (meta.features?.askUserQuestion?.enabled) {
241
+ status.features.askuserquestion.enabled = true;
242
+ status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
243
+ }
244
+
245
+ // Read feature versions and check if outdated
246
+ if (meta.features) {
247
+ const featureKeyMap = { askUserQuestion: 'askuserquestion' };
248
+ Object.entries(meta.features).forEach(([feature, data]) => {
249
+ const statusKey = featureKeyMap[feature] || feature.toLowerCase();
250
+ if (status.features[statusKey] && data.version) {
251
+ status.features[statusKey].version = data.version;
252
+ if (data.version !== version && status.features[statusKey].enabled) {
253
+ status.features[statusKey].outdated = true;
254
+ status.hasOutdated = true;
255
+ }
256
+ }
257
+ });
258
+ }
259
+ }
260
+
261
+ // ============================================================================
262
+ // STATUS PRINTING
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Print configuration status to console
267
+ * @param {object} status - Status object from detectConfig
268
+ * @returns {{ hasIssues: boolean, hasOutdated: boolean }}
269
+ */
270
+ function printStatus(status) {
271
+ header('Current Configuration');
272
+
273
+ // Git status
274
+ log(
275
+ `Git: ${status.git.initialized ? '' : ''} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
276
+ status.git.initialized ? c.green : c.dim
277
+ );
278
+
279
+ // Settings status
280
+ if (!status.settingsExists) {
281
+ log('Settings: .claude/settings.json not found', c.dim);
282
+ } else if (!status.settingsValid) {
283
+ log('Settings: Invalid JSON', c.red);
284
+ } else {
285
+ log('Settings: .claude/settings.json exists', c.green);
286
+ }
287
+
288
+ // Features status
289
+ header('Features:');
290
+
291
+ const printFeature = (name, label) => {
292
+ const f = status.features[name];
293
+ let statusIcon = f.enabled ? '' : '';
294
+ let statusText = f.enabled ? 'enabled' : 'disabled';
295
+ let color = f.enabled ? c.green : c.dim;
296
+
297
+ if (f.enabled && !f.valid) {
298
+ statusIcon = '';
299
+ statusText = 'INVALID FORMAT';
300
+ color = c.yellow;
301
+ } else if (f.enabled && f.outdated) {
302
+ statusIcon = '';
303
+ statusText = `outdated (v${f.version} -> v${status.currentVersion})`;
304
+ color = c.yellow;
305
+ }
306
+
307
+ log(` ${statusIcon} ${label}: ${statusText}`, color);
308
+
309
+ if (f.issues?.length > 0) {
310
+ f.issues.forEach(issue => log(` - ${issue}`, c.yellow));
311
+ }
312
+ };
313
+
314
+ printFeature('sessionstart', 'SessionStart Hook');
315
+ printFeature('precompact', 'PreCompact Hook');
316
+ printFeature('ralphloop', 'RalphLoop (Stop)');
317
+ printFeature('selfimprove', 'SelfImprove (Stop)');
318
+
319
+ // Archival (special display)
320
+ const arch = status.features.archival;
321
+ log(
322
+ ` ${arch.enabled ? '' : ''} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
323
+ arch.enabled ? c.green : c.dim
324
+ );
325
+
326
+ printFeature('statusline', 'Status Line');
327
+
328
+ // Damage Control (special display)
329
+ const dc = status.features.damagecontrol;
330
+ if (dc.enabled) {
331
+ let dcStatusText = 'enabled';
332
+ if (dc.level) dcStatusText += ` (${dc.level})`;
333
+ if (!dc.valid) dcStatusText = 'INCOMPLETE';
334
+ const dcIcon = dc.enabled && dc.valid ? '' : '';
335
+ const dcColor = dc.enabled && dc.valid ? c.green : c.yellow;
336
+ log(` ${dcIcon} Damage Control: ${dcStatusText}`, dcColor);
337
+ if (dc.issues?.length > 0) {
338
+ dc.issues.forEach(issue => log(` - ${issue}`, c.yellow));
339
+ }
340
+ } else {
341
+ log(` Damage Control: disabled`, c.dim);
342
+ }
343
+
344
+ // AskUserQuestion
345
+ const auq = status.features.askuserquestion;
346
+ if (auq.enabled) {
347
+ let auqStatusText = 'enabled';
348
+ if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
349
+ log(` AskUserQuestion: ${auqStatusText}`, c.green);
350
+ } else {
351
+ log(` AskUserQuestion: disabled`, c.dim);
352
+ }
353
+
354
+ // Metadata version
355
+ if (status.metadata.exists) {
356
+ log(`\nMetadata: v${status.metadata.version}`, c.dim);
357
+ }
358
+
359
+ // Issues summary
360
+ const hasIssues = Object.values(status.features).some(f => f.issues?.length > 0);
361
+ if (hasIssues) {
362
+ log('\n Format issues detected! Run with --migrate to fix.', c.yellow);
363
+ }
364
+
365
+ if (status.hasOutdated) {
366
+ log('\n Outdated scripts detected! Run with --upgrade to update.', c.yellow);
367
+ }
368
+
369
+ return { hasIssues, hasOutdated: status.hasOutdated };
370
+ }
371
+
372
+ module.exports = {
373
+ detectConfig,
374
+ printStatus,
375
+ // Export helper functions for testing
376
+ detectHooks,
377
+ detectSessionStartHook,
378
+ detectPreCompactHook,
379
+ detectStopHooks,
380
+ detectPreToolUseHooks,
381
+ detectStatusLine,
382
+ detectMetadata,
383
+ };