agileflow 2.89.1 → 2.89.3
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/lib/content-sanitizer.js +463 -0
- package/lib/error-codes.js +544 -0
- package/lib/errors.js +336 -5
- package/lib/feedback.js +561 -0
- package/lib/path-resolver.js +396 -0
- package/lib/session-registry.js +461 -0
- package/lib/smart-json-file.js +449 -0
- package/lib/validate.js +165 -11
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +40 -1440
- package/scripts/agileflow-welcome.js +2 -1
- package/scripts/lib/configure-detect.js +383 -0
- package/scripts/lib/configure-features.js +811 -0
- package/scripts/lib/configure-repair.js +314 -0
- package/scripts/lib/configure-utils.js +115 -0
- package/scripts/lib/frontmatter-parser.js +3 -3
- package/scripts/obtain-context.js +417 -113
- package/scripts/ralph-loop.js +1 -1
- package/tools/cli/commands/config.js +3 -3
- package/tools/cli/commands/doctor.js +30 -2
- package/tools/cli/commands/list.js +2 -2
- package/tools/cli/commands/uninstall.js +3 -3
- package/tools/cli/installers/core/installer.js +62 -12
- package/tools/cli/installers/ide/_interface.js +238 -0
- package/tools/cli/installers/ide/codex.js +2 -2
- package/tools/cli/installers/ide/manager.js +15 -0
- package/tools/cli/lib/content-injector.js +69 -16
- package/tools/cli/lib/ide-errors.js +163 -29
|
@@ -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
|
|
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 =
|
|
30
|
-
// Return empty object if
|
|
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)
|