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,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* path-resolver.js - Unified Path Resolution Service
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized path resolution that respects user configuration.
|
|
5
|
+
* Loads folder names from manifest.yaml and provides consistent path
|
|
6
|
+
* resolution across all AgileFlow scripts and commands.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Loads folder names from manifest.yaml
|
|
10
|
+
* - Caches configuration for performance
|
|
11
|
+
* - Validates paths before shell execution
|
|
12
|
+
* - Provides consistent API for all path operations
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const PathResolver = require('./path-resolver');
|
|
16
|
+
* const resolver = new PathResolver('/path/to/project');
|
|
17
|
+
*
|
|
18
|
+
* // Get paths
|
|
19
|
+
* const agileflowDir = resolver.getAgileflowDir();
|
|
20
|
+
* const docsDir = resolver.getDocsDir();
|
|
21
|
+
* const statusPath = resolver.getStatusPath();
|
|
22
|
+
*
|
|
23
|
+
* // Get all paths at once
|
|
24
|
+
* const paths = resolver.getAllPaths();
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const { safeLoad } = require('./yaml-utils');
|
|
30
|
+
const { validatePath } = require('./validate');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Unified Path Resolution Service
|
|
34
|
+
*/
|
|
35
|
+
class PathResolver {
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} [projectRoot] - Project root directory (auto-detected if not provided)
|
|
38
|
+
* @param {Object} [options={}] - Configuration options
|
|
39
|
+
* @param {boolean} [options.autoDetect=true] - Auto-detect project root if not found
|
|
40
|
+
* @param {string} [options.agileflowFolder='.agileflow'] - Default agileflow folder name
|
|
41
|
+
* @param {string} [options.docsFolder='docs'] - Default docs folder name
|
|
42
|
+
*/
|
|
43
|
+
constructor(projectRoot, options = {}) {
|
|
44
|
+
const { autoDetect = true, agileflowFolder = '.agileflow', docsFolder = 'docs' } = options;
|
|
45
|
+
|
|
46
|
+
// Store defaults
|
|
47
|
+
this._defaultAgileflowFolder = agileflowFolder;
|
|
48
|
+
this._defaultDocsFolder = docsFolder;
|
|
49
|
+
|
|
50
|
+
// Find project root
|
|
51
|
+
if (projectRoot) {
|
|
52
|
+
this._projectRoot = projectRoot;
|
|
53
|
+
} else if (autoDetect) {
|
|
54
|
+
this._projectRoot = this._findProjectRoot(process.cwd());
|
|
55
|
+
} else {
|
|
56
|
+
this._projectRoot = process.cwd();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Cache for manifest data
|
|
60
|
+
this._manifestCache = null;
|
|
61
|
+
this._manifestCacheTime = 0;
|
|
62
|
+
this._cacheMaxAge = 5000; // 5 second cache
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Find project root by looking for common project markers
|
|
67
|
+
* @param {string} startDir - Directory to start searching from
|
|
68
|
+
* @returns {string} Project root path
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
_findProjectRoot(startDir) {
|
|
72
|
+
const markers = ['.agileflow', 'agileflow', '.aflow', '.git', 'package.json'];
|
|
73
|
+
let dir = startDir;
|
|
74
|
+
|
|
75
|
+
while (dir !== path.dirname(dir)) {
|
|
76
|
+
for (const marker of markers) {
|
|
77
|
+
if (fs.existsSync(path.join(dir, marker))) {
|
|
78
|
+
// If we found .agileflow, that's definitely our root
|
|
79
|
+
if (['.agileflow', 'agileflow', '.aflow'].includes(marker)) {
|
|
80
|
+
return dir;
|
|
81
|
+
}
|
|
82
|
+
// For .git and package.json, only use if no agileflow dir found above
|
|
83
|
+
if (!this._hasAgileflowAbove(dir)) {
|
|
84
|
+
return dir;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
dir = path.dirname(dir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return startDir;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if any agileflow directory exists above this directory
|
|
96
|
+
* @param {string} dir - Directory to check from
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
_hasAgileflowAbove(dir) {
|
|
101
|
+
let current = path.dirname(dir);
|
|
102
|
+
while (current !== path.dirname(current)) {
|
|
103
|
+
if (
|
|
104
|
+
fs.existsSync(path.join(current, '.agileflow')) ||
|
|
105
|
+
fs.existsSync(path.join(current, 'agileflow')) ||
|
|
106
|
+
fs.existsSync(path.join(current, '.aflow'))
|
|
107
|
+
) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
current = path.dirname(current);
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load manifest configuration with caching
|
|
117
|
+
* @returns {{agileflowFolder: string, docsFolder: string}}
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
_loadManifest() {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
|
|
123
|
+
// Return cached if still valid
|
|
124
|
+
if (this._manifestCache && now - this._manifestCacheTime < this._cacheMaxAge) {
|
|
125
|
+
return this._manifestCache;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find the agileflow directory
|
|
129
|
+
const possibleFolders = ['.agileflow', 'agileflow', '.aflow'];
|
|
130
|
+
let manifestPath = null;
|
|
131
|
+
|
|
132
|
+
for (const folder of possibleFolders) {
|
|
133
|
+
const candidate = path.join(this._projectRoot, folder, '_cfg', 'manifest.yaml');
|
|
134
|
+
if (fs.existsSync(candidate)) {
|
|
135
|
+
manifestPath = candidate;
|
|
136
|
+
this._actualAgileflowFolder = folder;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Default values
|
|
142
|
+
const result = {
|
|
143
|
+
agileflowFolder: this._actualAgileflowFolder || this._defaultAgileflowFolder,
|
|
144
|
+
docsFolder: this._defaultDocsFolder,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (manifestPath) {
|
|
148
|
+
try {
|
|
149
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
150
|
+
const manifest = safeLoad(content);
|
|
151
|
+
|
|
152
|
+
if (manifest && typeof manifest === 'object') {
|
|
153
|
+
result.agileflowFolder = manifest.agileflow_folder || result.agileflowFolder;
|
|
154
|
+
result.docsFolder = manifest.docs_folder || result.docsFolder;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Use defaults on error
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Cache the result
|
|
162
|
+
this._manifestCache = result;
|
|
163
|
+
this._manifestCacheTime = now;
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get the project root directory
|
|
170
|
+
* @returns {string}
|
|
171
|
+
*/
|
|
172
|
+
getProjectRoot() {
|
|
173
|
+
return this._projectRoot;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the AgileFlow directory path
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
getAgileflowDir() {
|
|
181
|
+
const { agileflowFolder } = this._loadManifest();
|
|
182
|
+
return path.join(this._projectRoot, agileflowFolder);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get the docs directory path
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
getDocsDir() {
|
|
190
|
+
const { docsFolder } = this._loadManifest();
|
|
191
|
+
return path.join(this._projectRoot, docsFolder);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the .claude directory path
|
|
196
|
+
* @returns {string}
|
|
197
|
+
*/
|
|
198
|
+
getClaudeDir() {
|
|
199
|
+
return path.join(this._projectRoot, '.claude');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get the status.json path
|
|
204
|
+
* @returns {string}
|
|
205
|
+
*/
|
|
206
|
+
getStatusPath() {
|
|
207
|
+
return path.join(this.getDocsDir(), '09-agents', 'status.json');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the session-state.json path
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
getSessionStatePath() {
|
|
215
|
+
return path.join(this.getDocsDir(), '09-agents', 'session-state.json');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get the agileflow-metadata.json path
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
222
|
+
getMetadataPath() {
|
|
223
|
+
return path.join(this.getDocsDir(), '00-meta', 'agileflow-metadata.json');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the config.yaml path
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
getConfigPath() {
|
|
231
|
+
return path.join(this.getAgileflowDir(), 'config.yaml');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get the manifest.yaml path
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
getManifestPath() {
|
|
239
|
+
return path.join(this.getAgileflowDir(), '_cfg', 'manifest.yaml');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get the scripts directory path
|
|
244
|
+
* @returns {string}
|
|
245
|
+
*/
|
|
246
|
+
getScriptsDir() {
|
|
247
|
+
return path.join(this.getAgileflowDir(), 'scripts');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get the commands directory path
|
|
252
|
+
* @returns {string}
|
|
253
|
+
*/
|
|
254
|
+
getCommandsDir() {
|
|
255
|
+
return path.join(this.getAgileflowDir(), 'commands');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get the agents directory path
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
getAgentsDir() {
|
|
263
|
+
return path.join(this.getAgileflowDir(), 'agents');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get all paths at once
|
|
268
|
+
* @returns {Object} Object with all path values
|
|
269
|
+
*/
|
|
270
|
+
getAllPaths() {
|
|
271
|
+
return {
|
|
272
|
+
projectRoot: this.getProjectRoot(),
|
|
273
|
+
agileflowDir: this.getAgileflowDir(),
|
|
274
|
+
docsDir: this.getDocsDir(),
|
|
275
|
+
claudeDir: this.getClaudeDir(),
|
|
276
|
+
statusPath: this.getStatusPath(),
|
|
277
|
+
sessionStatePath: this.getSessionStatePath(),
|
|
278
|
+
metadataPath: this.getMetadataPath(),
|
|
279
|
+
configPath: this.getConfigPath(),
|
|
280
|
+
manifestPath: this.getManifestPath(),
|
|
281
|
+
scriptsDir: this.getScriptsDir(),
|
|
282
|
+
commandsDir: this.getCommandsDir(),
|
|
283
|
+
agentsDir: this.getAgentsDir(),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if we're in an AgileFlow project
|
|
289
|
+
* @returns {boolean}
|
|
290
|
+
*/
|
|
291
|
+
isAgileflowProject() {
|
|
292
|
+
return fs.existsSync(this.getAgileflowDir());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Validate a path is within the project root (prevents path traversal)
|
|
297
|
+
* @param {string} targetPath - Path to validate
|
|
298
|
+
* @returns {{ok: boolean, resolvedPath?: string, error?: Error}}
|
|
299
|
+
*/
|
|
300
|
+
validatePath(targetPath) {
|
|
301
|
+
return validatePath(targetPath, this._projectRoot, { allowSymlinks: false });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Resolve a relative path within the project
|
|
306
|
+
* @param {...string} segments - Path segments to join
|
|
307
|
+
* @returns {string} Resolved absolute path
|
|
308
|
+
*/
|
|
309
|
+
resolve(...segments) {
|
|
310
|
+
return path.join(this._projectRoot, ...segments);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get a path relative to project root
|
|
315
|
+
* @param {string} absolutePath - Absolute path
|
|
316
|
+
* @returns {string} Relative path
|
|
317
|
+
*/
|
|
318
|
+
relative(absolutePath) {
|
|
319
|
+
return path.relative(this._projectRoot, absolutePath);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Clear the manifest cache (useful for testing or after config changes)
|
|
324
|
+
*/
|
|
325
|
+
clearCache() {
|
|
326
|
+
this._manifestCache = null;
|
|
327
|
+
this._manifestCacheTime = 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get the folder names from configuration
|
|
332
|
+
* @returns {{agileflowFolder: string, docsFolder: string}}
|
|
333
|
+
*/
|
|
334
|
+
getFolderNames() {
|
|
335
|
+
return this._loadManifest();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Singleton instance for convenience
|
|
341
|
+
* Uses auto-detection for project root
|
|
342
|
+
*/
|
|
343
|
+
let defaultInstance = null;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get the default PathResolver instance (singleton)
|
|
347
|
+
* @param {boolean} [forceNew=false] - Force creation of new instance
|
|
348
|
+
* @returns {PathResolver}
|
|
349
|
+
*/
|
|
350
|
+
function getDefaultResolver(forceNew = false) {
|
|
351
|
+
if (!defaultInstance || forceNew) {
|
|
352
|
+
defaultInstance = new PathResolver();
|
|
353
|
+
}
|
|
354
|
+
return defaultInstance;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Convenience function to get all paths using default resolver
|
|
359
|
+
* @returns {Object}
|
|
360
|
+
*/
|
|
361
|
+
function getAllPaths() {
|
|
362
|
+
return getDefaultResolver().getAllPaths();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Convenience function to get project root using default resolver
|
|
367
|
+
* @returns {string}
|
|
368
|
+
*/
|
|
369
|
+
function getProjectRoot() {
|
|
370
|
+
return getDefaultResolver().getProjectRoot();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Convenience function to get agileflow dir using default resolver
|
|
375
|
+
* @returns {string}
|
|
376
|
+
*/
|
|
377
|
+
function getAgileflowDir() {
|
|
378
|
+
return getDefaultResolver().getAgileflowDir();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Convenience function to get docs dir using default resolver
|
|
383
|
+
* @returns {string}
|
|
384
|
+
*/
|
|
385
|
+
function getDocsDir() {
|
|
386
|
+
return getDefaultResolver().getDocsDir();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
module.exports = {
|
|
390
|
+
PathResolver,
|
|
391
|
+
getDefaultResolver,
|
|
392
|
+
getAllPaths,
|
|
393
|
+
getProjectRoot,
|
|
394
|
+
getAgileflowDir,
|
|
395
|
+
getDocsDir,
|
|
396
|
+
};
|