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
@@ -0,0 +1,314 @@
1
+ /**
2
+ * configure-repair.js - Repair and diagnostic functions 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 crypto = require('crypto');
10
+ const { execSync } = require('child_process');
11
+ const {
12
+ c,
13
+ log,
14
+ success,
15
+ warn,
16
+ error,
17
+ info,
18
+ header,
19
+ ensureDir,
20
+ readJSON,
21
+ } = require('./configure-utils');
22
+ const { FEATURES } = require('./configure-features');
23
+
24
+ // ============================================================================
25
+ // SCRIPT REGISTRY
26
+ // ============================================================================
27
+
28
+ const ALL_SCRIPTS = {
29
+ // Core feature scripts (linked to FEATURES)
30
+ 'agileflow-welcome.js': { feature: 'sessionstart', required: true },
31
+ 'precompact-context.sh': { feature: 'precompact', required: true },
32
+ 'ralph-loop.js': { feature: 'ralphloop', required: true },
33
+ 'auto-self-improve.js': { feature: 'selfimprove', required: true },
34
+ 'archive-completed-stories.sh': { feature: 'archival', required: true },
35
+ 'agileflow-statusline.sh': { feature: 'statusline', required: true },
36
+ 'damage-control-bash.js': { feature: 'damagecontrol', required: true },
37
+ 'damage-control-edit.js': { feature: 'damagecontrol', required: true },
38
+ 'damage-control-write.js': { feature: 'damagecontrol', required: true },
39
+
40
+ // Support scripts (used by commands/agents)
41
+ 'obtain-context.js': { usedBy: ['/babysit', '/mentor', '/sprint'] },
42
+ 'session-manager.js': { usedBy: ['/session:new', '/session:resume'] },
43
+ 'check-update.js': { usedBy: ['SessionStart hook'] },
44
+ 'get-env.js': { usedBy: ['SessionStart hook'] },
45
+ 'clear-active-command.js': { usedBy: ['session commands'] },
46
+
47
+ // Utility scripts
48
+ 'compress-status.sh': { usedBy: ['/compress'] },
49
+ 'validate-expertise.sh': { usedBy: ['/validate-expertise'] },
50
+ 'expertise-metrics.sh': { usedBy: ['agent experts'] },
51
+ 'session-coordinator.sh': { usedBy: ['session management'] },
52
+ 'validate-tokens.sh': { usedBy: ['token validation'] },
53
+ 'worktree-create.sh': { usedBy: ['/session:new'] },
54
+ 'resume-session.sh': { usedBy: ['/session:resume'] },
55
+ 'init.sh': { usedBy: ['/session:init'] },
56
+ 'agileflow-configure.js': { usedBy: ['/configure'] },
57
+ 'generate-all.sh': { usedBy: ['content generation'] },
58
+ };
59
+
60
+ // ============================================================================
61
+ // UTILITY FUNCTIONS
62
+ // ============================================================================
63
+
64
+ /**
65
+ * Calculate SHA256 hash of data
66
+ * @param {Buffer|string} data - Data to hash
67
+ * @returns {string} Hex hash string
68
+ */
69
+ function sha256(data) {
70
+ return crypto.createHash('sha256').update(data).digest('hex');
71
+ }
72
+
73
+ /**
74
+ * Get the source scripts directory (from npm package)
75
+ * @returns {string|null} Path to source scripts or null
76
+ */
77
+ function getSourceScriptsDir() {
78
+ const possiblePaths = [
79
+ path.join(__dirname, '..', '..', 'scripts'), // npm package structure
80
+ path.join(__dirname, '..'), // Same directory (for development)
81
+ ];
82
+
83
+ for (const p of possiblePaths) {
84
+ if (fs.existsSync(p) && fs.existsSync(path.join(p, 'agileflow-welcome.js'))) {
85
+ return p;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ // ============================================================================
93
+ // LIST SCRIPTS
94
+ // ============================================================================
95
+
96
+ /**
97
+ * List all scripts with their status
98
+ */
99
+ function listScripts() {
100
+ header('Installed Scripts');
101
+
102
+ const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
103
+ const fileIndexPath = path.join(process.cwd(), '.agileflow', '_cfg', 'files.json');
104
+ const fileIndex = readJSON(fileIndexPath);
105
+
106
+ let missing = 0;
107
+ let modified = 0;
108
+ let present = 0;
109
+
110
+ Object.entries(ALL_SCRIPTS).forEach(([script, info]) => {
111
+ const scriptPath = path.join(scriptsDir, script);
112
+ const exists = fs.existsSync(scriptPath);
113
+
114
+ // Check if modified
115
+ let isModified = false;
116
+ if (exists && fileIndex?.files?.[`scripts/${script}`]) {
117
+ try {
118
+ const currentHash = sha256(fs.readFileSync(scriptPath));
119
+ const indexHash = fileIndex.files[`scripts/${script}`].sha256;
120
+ isModified = currentHash !== indexHash;
121
+ } catch {}
122
+ }
123
+
124
+ // Print status
125
+ if (!exists) {
126
+ log(` ${script}: MISSING`, c.red);
127
+ if (info.usedBy) log(` - Used by: ${info.usedBy.join(', ')}`, c.dim);
128
+ if (info.feature) log(` - Feature: ${info.feature}`, c.dim);
129
+ missing++;
130
+ } else if (isModified) {
131
+ log(` ${script}: modified (local changes)`, c.yellow);
132
+ modified++;
133
+ } else {
134
+ log(` ${script}: present`, c.green);
135
+ present++;
136
+ }
137
+ });
138
+
139
+ // Summary
140
+ log('');
141
+ log(`Summary: ${present} present, ${modified} modified, ${missing} missing`, c.dim);
142
+
143
+ if (missing > 0) {
144
+ log('\n Run with --repair to restore missing scripts', c.yellow);
145
+ }
146
+ }
147
+
148
+ // ============================================================================
149
+ // VERSION INFO
150
+ // ============================================================================
151
+
152
+ /**
153
+ * Show version information
154
+ * @param {string} version - Current CLI version
155
+ */
156
+ function showVersionInfo(version) {
157
+ header('Version Information');
158
+
159
+ const meta = readJSON('docs/00-meta/agileflow-metadata.json') || {};
160
+ const installedVersion = meta.version || 'unknown';
161
+
162
+ log(`Installed: v${installedVersion}`);
163
+ log(`CLI: v${version}`);
164
+
165
+ // Check npm for latest
166
+ let latestVersion = null;
167
+ try {
168
+ latestVersion = execSync('npm view agileflow version 2>/dev/null', { encoding: 'utf8' }).trim();
169
+ log(`Latest: v${latestVersion}`);
170
+
171
+ if (installedVersion !== 'unknown' && latestVersion && installedVersion !== latestVersion) {
172
+ const installed = installedVersion.split('.').map(Number);
173
+ const latest = latestVersion.split('.').map(Number);
174
+
175
+ if (
176
+ latest[0] > installed[0] ||
177
+ (latest[0] === installed[0] && latest[1] > installed[1]) ||
178
+ (latest[0] === installed[0] && latest[1] === installed[1] && latest[2] > installed[2])
179
+ ) {
180
+ log('\n Update available! Run: npx agileflow update', c.yellow);
181
+ }
182
+ }
183
+ } catch {
184
+ log('Latest: (could not check npm)', c.dim);
185
+ }
186
+
187
+ // Show per-feature versions
188
+ if (meta.features && Object.keys(meta.features).length > 0) {
189
+ header('Feature Versions:');
190
+ Object.entries(meta.features).forEach(([feature, data]) => {
191
+ if (!data) return;
192
+ const featureVersion = data.version || 'unknown';
193
+ const enabled = data.enabled !== false;
194
+ const outdated = featureVersion !== version && enabled;
195
+
196
+ let icon = '';
197
+ let color = c.dim;
198
+ let statusText = `v${featureVersion}`;
199
+
200
+ if (!enabled) {
201
+ statusText = 'disabled';
202
+ } else if (outdated) {
203
+ icon = '';
204
+ color = c.yellow;
205
+ statusText = `v${featureVersion} -> v${version}`;
206
+ } else {
207
+ icon = '';
208
+ color = c.green;
209
+ }
210
+
211
+ log(` ${icon} ${feature}: ${statusText}`, color);
212
+ });
213
+ }
214
+
215
+ // Show installation metadata
216
+ if (meta.created || meta.updated) {
217
+ header('Installation:');
218
+ if (meta.created) log(` Created: ${new Date(meta.created).toLocaleDateString()}`, c.dim);
219
+ if (meta.updated) log(` Updated: ${new Date(meta.updated).toLocaleDateString()}`, c.dim);
220
+ }
221
+ }
222
+
223
+ // ============================================================================
224
+ // REPAIR SCRIPTS
225
+ // ============================================================================
226
+
227
+ /**
228
+ * Repair missing or corrupted scripts
229
+ * @param {string|null} targetFeature - Specific feature to repair, or null for all
230
+ * @returns {boolean} Whether any repairs were made
231
+ */
232
+ function repairScripts(targetFeature = null) {
233
+ header('Repairing Scripts...');
234
+
235
+ const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
236
+ const sourceDir = getSourceScriptsDir();
237
+
238
+ if (!sourceDir) {
239
+ warn('Could not find source scripts directory');
240
+ info('Try running: npx agileflow@latest update');
241
+ return false;
242
+ }
243
+
244
+ let repaired = 0;
245
+ let errors = 0;
246
+ let skipped = 0;
247
+
248
+ // Determine which scripts to check
249
+ const scriptsToCheck = targetFeature
250
+ ? Object.entries(ALL_SCRIPTS).filter(([_, info]) => info.feature === targetFeature)
251
+ : Object.entries(ALL_SCRIPTS);
252
+
253
+ if (scriptsToCheck.length === 0 && targetFeature) {
254
+ error(`Unknown feature: ${targetFeature}`);
255
+ log(`Available features: ${Object.keys(FEATURES).join(', ')}`, c.dim);
256
+ return false;
257
+ }
258
+
259
+ // Ensure scripts directory exists
260
+ ensureDir(scriptsDir);
261
+
262
+ for (const [script, info] of scriptsToCheck) {
263
+ const destPath = path.join(scriptsDir, script);
264
+ const srcPath = path.join(sourceDir, script);
265
+
266
+ if (!fs.existsSync(destPath)) {
267
+ // Script is missing - reinstall from source
268
+ if (fs.existsSync(srcPath)) {
269
+ try {
270
+ fs.copyFileSync(srcPath, destPath);
271
+ try {
272
+ fs.chmodSync(destPath, 0o755);
273
+ } catch {}
274
+ success(`Restored ${script}`);
275
+ repaired++;
276
+ } catch (err) {
277
+ error(`Failed to restore ${script}: ${err.message}`);
278
+ errors++;
279
+ }
280
+ } else {
281
+ warn(`Source not found for ${script}`);
282
+ errors++;
283
+ }
284
+ } else {
285
+ skipped++;
286
+ }
287
+ }
288
+
289
+ // Summary
290
+ log('');
291
+ if (repaired === 0 && errors === 0) {
292
+ info('All scripts present - nothing to repair');
293
+ } else {
294
+ log(`Repaired: ${repaired}, Errors: ${errors}, Skipped: ${skipped}`, c.dim);
295
+ }
296
+
297
+ if (errors > 0) {
298
+ log('\n For comprehensive repair, run: npx agileflow update --force', c.yellow);
299
+ }
300
+
301
+ return repaired > 0;
302
+ }
303
+
304
+ module.exports = {
305
+ // Script registry
306
+ ALL_SCRIPTS,
307
+ // Utility functions
308
+ sha256,
309
+ getSourceScriptsDir,
310
+ // Main functions
311
+ listScripts,
312
+ showVersionInfo,
313
+ repairScripts,
314
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * configure-utils.js - File and logging utilities 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
+
10
+ // ============================================================================
11
+ // COLORS & LOGGING
12
+ // ============================================================================
13
+
14
+ const c = {
15
+ reset: '\x1b[0m',
16
+ dim: '\x1b[2m',
17
+ bold: '\x1b[1m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ red: '\x1b[31m',
21
+ cyan: '\x1b[36m',
22
+ };
23
+
24
+ const log = (msg, color = '') => console.log(`${color}${msg}${c.reset}`);
25
+ const success = msg => log(`✅ ${msg}`, c.green);
26
+ const warn = msg => log(`⚠️ ${msg}`, c.yellow);
27
+ const error = msg => log(`❌ ${msg}`, c.red);
28
+ const info = msg => log(`ℹ️ ${msg}`, c.dim);
29
+ const header = msg => log(`\n${msg}`, c.bold + c.cyan);
30
+
31
+ // ============================================================================
32
+ // FILE UTILITIES
33
+ // ============================================================================
34
+
35
+ const ensureDir = dir => {
36
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
37
+ };
38
+
39
+ const readJSON = filePath => {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
42
+ } catch {
43
+ return null;
44
+ }
45
+ };
46
+
47
+ const writeJSON = (filePath, data) => {
48
+ ensureDir(path.dirname(filePath));
49
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
50
+ };
51
+
52
+ const copyTemplate = (templateName, destPath) => {
53
+ const sources = [
54
+ path.join(process.cwd(), '.agileflow', 'templates', templateName),
55
+ path.join(__dirname, '..', templateName),
56
+ path.join(__dirname, '..', '..', 'templates', templateName),
57
+ ];
58
+ for (const src of sources) {
59
+ if (fs.existsSync(src)) {
60
+ fs.copyFileSync(src, destPath);
61
+ try {
62
+ fs.chmodSync(destPath, '755');
63
+ } catch {}
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ };
69
+
70
+ // ============================================================================
71
+ // GITIGNORE
72
+ // ============================================================================
73
+
74
+ function updateGitignore() {
75
+ const entries = [
76
+ '.claude/settings.local.json',
77
+ '.claude/activity.log',
78
+ '.claude/context.log',
79
+ '.claude/hook.log',
80
+ '.claude/prompt-log.txt',
81
+ '.claude/session.log',
82
+ ];
83
+
84
+ let content = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
85
+ let added = false;
86
+
87
+ entries.forEach(entry => {
88
+ if (!content.includes(entry)) {
89
+ content += `\n${entry}`;
90
+ added = true;
91
+ }
92
+ });
93
+
94
+ if (added) {
95
+ fs.writeFileSync('.gitignore', content.trimEnd() + '\n');
96
+ }
97
+ }
98
+
99
+ module.exports = {
100
+ // Colors
101
+ c,
102
+ // Logging
103
+ log,
104
+ success,
105
+ warn,
106
+ error,
107
+ info,
108
+ header,
109
+ // File utilities
110
+ ensureDir,
111
+ readJSON,
112
+ writeJSON,
113
+ copyTemplate,
114
+ updateGitignore,
115
+ };
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  const fs = require('fs');
14
- const yaml = require('js-yaml');
14
+ const { safeLoad } = require('../../lib/yaml-utils');
15
15
 
16
16
  /**
17
17
  * Parse YAML frontmatter from markdown content
@@ -26,8 +26,8 @@ function parseFrontmatter(content) {
26
26
  }
27
27
 
28
28
  try {
29
- const parsed = yaml.load(match[1]);
30
- // Return empty object if yaml.load returns null/undefined
29
+ const parsed = safeLoad(match[1]);
30
+ // Return empty object if safeLoad returns null/undefined
31
31
  return parsed && typeof parsed === 'object' ? parsed : {};
32
32
  } catch (err) {
33
33
  // Return empty object on parse error (invalid YAML)