agileflow 2.33.1 → 2.35.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/LICENSE +22 -0
- package/README.md +536 -0
- package/package.json +1 -1
- package/src/core/agents/adr-writer.md +3 -19
- package/src/core/agents/api.md +9 -43
- package/src/core/agents/ci.md +8 -40
- package/src/core/agents/configuration/archival.md +301 -0
- package/src/core/agents/configuration/attribution.md +318 -0
- package/src/core/agents/configuration/ci.md +1077 -0
- package/src/core/agents/configuration/git-config.md +511 -0
- package/src/core/agents/configuration/hooks.md +507 -0
- package/src/core/agents/configuration/verify.md +540 -0
- package/src/core/agents/devops.md +7 -35
- package/src/core/agents/documentation.md +0 -1
- package/src/core/agents/epic-planner.md +3 -22
- package/src/core/agents/mentor.md +8 -24
- package/src/core/agents/research.md +0 -7
- package/src/core/agents/security.md +0 -5
- package/src/core/agents/ui.md +8 -42
- package/src/core/commands/PATTERNS-AskUserQuestion.md +474 -0
- package/src/core/commands/adr.md +5 -0
- package/src/core/commands/agent.md +4 -0
- package/src/core/commands/assign.md +1 -0
- package/src/core/commands/auto.md +1 -1
- package/src/core/commands/babysit.md +147 -31
- package/src/core/commands/baseline.md +7 -0
- package/src/core/commands/blockers.md +2 -0
- package/src/core/commands/board.md +9 -0
- package/src/core/commands/configure.md +415 -0
- package/src/core/commands/context.md +1 -0
- package/src/core/commands/deps.md +2 -0
- package/src/core/commands/diagnose.md +0 -41
- package/src/core/commands/epic.md +8 -0
- package/src/core/commands/handoff.md +4 -0
- package/src/core/commands/impact.md +1 -1
- package/src/core/commands/metrics.md +10 -0
- package/src/core/commands/research.md +3 -0
- package/src/core/commands/retro.md +11 -1
- package/src/core/commands/sprint.md +2 -1
- package/src/core/commands/status.md +1 -0
- package/src/core/commands/story-validate.md +1 -1
- package/src/core/commands/story.md +29 -2
- package/src/core/commands/template.md +8 -0
- package/src/core/commands/update.md +1 -1
- package/src/core/commands/velocity.md +9 -0
- package/src/core/commands/verify.md +6 -0
- package/src/core/templates/validate-tokens.sh +0 -15
- package/src/core/templates/worktrees-guide.md +0 -4
- package/tools/agileflow-npx.js +21 -9
- package/tools/cli/commands/config.js +284 -0
- package/tools/cli/commands/doctor.js +221 -4
- package/tools/cli/commands/setup.js +4 -1
- package/tools/cli/commands/update.js +59 -15
- package/tools/cli/installers/core/installer.js +369 -37
- package/tools/cli/installers/ide/claude-code.js +1 -1
- package/tools/cli/installers/ide/cursor.js +1 -1
- package/tools/cli/installers/ide/windsurf.js +1 -1
- package/tools/cli/lib/docs-setup.js +52 -28
- package/tools/cli/lib/npm-utils.js +62 -0
- package/tools/cli/lib/ui.js +9 -2
- package/tools/postinstall.js +71 -13
- package/src/core/agents/context7.md +0 -164
- package/src/core/commands/setup.md +0 -708
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgileFlow CLI - Config Command
|
|
3
|
+
*
|
|
4
|
+
* Manage AgileFlow configuration without re-running setup.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
const { Installer } = require('../installers/core/installer');
|
|
12
|
+
const { IdeManager } = require('../installers/ide/manager');
|
|
13
|
+
const { displayLogo, displaySection, success, warning, error, info } = require('../lib/ui');
|
|
14
|
+
|
|
15
|
+
const installer = new Installer();
|
|
16
|
+
const ideManager = new IdeManager();
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
name: 'config',
|
|
20
|
+
description: 'Manage AgileFlow configuration',
|
|
21
|
+
arguments: [
|
|
22
|
+
['<subcommand>', 'Subcommand: list, get, set'],
|
|
23
|
+
['[key]', 'Config key (for get/set)'],
|
|
24
|
+
['[value]', 'Config value (for set)'],
|
|
25
|
+
],
|
|
26
|
+
options: [
|
|
27
|
+
['-d, --directory <path>', 'Project directory (default: current directory)'],
|
|
28
|
+
],
|
|
29
|
+
action: async (subcommand, keyOrValue, valueOrUndefined, options) => {
|
|
30
|
+
try {
|
|
31
|
+
const directory = path.resolve(options.directory || '.');
|
|
32
|
+
|
|
33
|
+
// Get installation status
|
|
34
|
+
const status = await installer.getStatus(directory);
|
|
35
|
+
|
|
36
|
+
if (!status.installed) {
|
|
37
|
+
displayLogo();
|
|
38
|
+
warning('No AgileFlow installation found');
|
|
39
|
+
console.log(chalk.dim(`\nRun 'npx agileflow setup' to set up AgileFlow\n`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const manifestPath = path.join(status.path, '_cfg', 'manifest.yaml');
|
|
44
|
+
|
|
45
|
+
// Handle subcommands
|
|
46
|
+
switch (subcommand) {
|
|
47
|
+
case 'list':
|
|
48
|
+
await handleList(status);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'get':
|
|
52
|
+
await handleGet(status, keyOrValue);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 'set':
|
|
56
|
+
await handleSet(directory, status, manifestPath, keyOrValue, valueOrUndefined);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
default:
|
|
60
|
+
displayLogo();
|
|
61
|
+
console.log(chalk.bold('Usage:\n'));
|
|
62
|
+
console.log(' npx agileflow config list');
|
|
63
|
+
console.log(' npx agileflow config get <key>');
|
|
64
|
+
console.log(' npx agileflow config set <key> <value>\n');
|
|
65
|
+
console.log(chalk.bold('Keys:\n'));
|
|
66
|
+
console.log(' userName Your name for config files');
|
|
67
|
+
console.log(' ides Comma-separated IDE list (claude-code,cursor,windsurf)');
|
|
68
|
+
console.log(' agileflowFolder AgileFlow folder name (e.g., .agileflow)');
|
|
69
|
+
console.log(' docsFolder Documentation folder name (e.g., docs)\n');
|
|
70
|
+
console.log(chalk.bold('Examples:\n'));
|
|
71
|
+
console.log(' npx agileflow config get userName');
|
|
72
|
+
console.log(' npx agileflow config set userName "Jane Developer"');
|
|
73
|
+
console.log(' npx agileflow config set ides "claude-code,cursor"\n');
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.exit(0);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(chalk.red('Error:'), err.message);
|
|
80
|
+
if (process.env.DEBUG) {
|
|
81
|
+
console.error(err.stack);
|
|
82
|
+
}
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle list subcommand
|
|
90
|
+
*/
|
|
91
|
+
async function handleList(status) {
|
|
92
|
+
displayLogo();
|
|
93
|
+
displaySection('AgileFlow Configuration');
|
|
94
|
+
|
|
95
|
+
console.log(chalk.bold('User Settings:'));
|
|
96
|
+
console.log(` userName: ${chalk.cyan(status.userName || 'Developer')}`);
|
|
97
|
+
console.log();
|
|
98
|
+
|
|
99
|
+
console.log(chalk.bold('IDE Settings:'));
|
|
100
|
+
const ides = status.ides || ['claude-code'];
|
|
101
|
+
console.log(` ides: ${chalk.cyan(ides.join(', '))}`);
|
|
102
|
+
console.log();
|
|
103
|
+
|
|
104
|
+
console.log(chalk.bold('Folder Settings:'));
|
|
105
|
+
console.log(` agileflowFolder: ${chalk.cyan(status.agileflowFolder || '.agileflow')}`);
|
|
106
|
+
console.log(` docsFolder: ${chalk.cyan(status.docsFolder || 'docs')}`);
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
console.log(chalk.bold('System Info:'));
|
|
110
|
+
console.log(` version: ${chalk.cyan(status.version || 'unknown')}`);
|
|
111
|
+
console.log(` installed: ${chalk.cyan(status.installedAt ? new Date(status.installedAt).toLocaleDateString() : 'unknown')}`);
|
|
112
|
+
console.log(` updated: ${chalk.cyan(status.updatedAt ? new Date(status.updatedAt).toLocaleDateString() : 'unknown')}`);
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Handle get subcommand
|
|
118
|
+
*/
|
|
119
|
+
async function handleGet(status, key) {
|
|
120
|
+
if (!key) {
|
|
121
|
+
error('Missing key. Usage: npx agileflow config get <key>');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const validKeys = ['userName', 'ides', 'agileflowFolder', 'docsFolder', 'version'];
|
|
126
|
+
|
|
127
|
+
if (!validKeys.includes(key)) {
|
|
128
|
+
error(`Invalid key: ${key}`);
|
|
129
|
+
console.log(chalk.dim(`Valid keys: ${validKeys.join(', ')}\n`));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let value;
|
|
134
|
+
switch (key) {
|
|
135
|
+
case 'userName':
|
|
136
|
+
value = status.userName || 'Developer';
|
|
137
|
+
break;
|
|
138
|
+
case 'ides':
|
|
139
|
+
value = (status.ides || ['claude-code']).join(',');
|
|
140
|
+
break;
|
|
141
|
+
case 'agileflowFolder':
|
|
142
|
+
value = status.agileflowFolder || '.agileflow';
|
|
143
|
+
break;
|
|
144
|
+
case 'docsFolder':
|
|
145
|
+
value = status.docsFolder || 'docs';
|
|
146
|
+
break;
|
|
147
|
+
case 'version':
|
|
148
|
+
value = status.version || 'unknown';
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Handle set subcommand
|
|
157
|
+
*/
|
|
158
|
+
async function handleSet(directory, status, manifestPath, key, value) {
|
|
159
|
+
if (!key || value === undefined) {
|
|
160
|
+
error('Missing arguments. Usage: npx agileflow config set <key> <value>');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const validKeys = ['userName', 'ides', 'agileflowFolder', 'docsFolder'];
|
|
165
|
+
|
|
166
|
+
if (!validKeys.includes(key)) {
|
|
167
|
+
error(`Invalid key: ${key}`);
|
|
168
|
+
console.log(chalk.dim(`Valid keys: ${validKeys.join(', ')}\n`));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
displayLogo();
|
|
173
|
+
displaySection('Updating Configuration');
|
|
174
|
+
|
|
175
|
+
// Read current manifest
|
|
176
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
177
|
+
const manifest = yaml.load(manifestContent);
|
|
178
|
+
|
|
179
|
+
// Track if we need to update IDE configs
|
|
180
|
+
let needsIdeUpdate = false;
|
|
181
|
+
const oldIdes = manifest.ides || [];
|
|
182
|
+
|
|
183
|
+
// Update the value
|
|
184
|
+
switch (key) {
|
|
185
|
+
case 'userName':
|
|
186
|
+
manifest.user_name = value;
|
|
187
|
+
info(`Setting userName to: ${chalk.cyan(value)}`);
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case 'ides':
|
|
191
|
+
const newIdes = value.split(',').map((ide) => ide.trim());
|
|
192
|
+
const validIdes = ['claude-code', 'cursor', 'windsurf'];
|
|
193
|
+
|
|
194
|
+
// Validate IDEs
|
|
195
|
+
for (const ide of newIdes) {
|
|
196
|
+
if (!validIdes.includes(ide)) {
|
|
197
|
+
error(`Invalid IDE: ${ide}`);
|
|
198
|
+
console.log(chalk.dim(`Valid IDEs: ${validIdes.join(', ')}\n`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
manifest.ides = newIdes;
|
|
204
|
+
needsIdeUpdate = true;
|
|
205
|
+
info(`Setting ides to: ${chalk.cyan(newIdes.join(', '))}`);
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'agileflowFolder':
|
|
209
|
+
warning('Changing agileflowFolder requires moving the installation directory.');
|
|
210
|
+
console.log(chalk.dim('This change will only update the config - you must move files manually.\n'));
|
|
211
|
+
manifest.agileflow_folder = value;
|
|
212
|
+
info(`Setting agileflowFolder to: ${chalk.cyan(value)}`);
|
|
213
|
+
break;
|
|
214
|
+
|
|
215
|
+
case 'docsFolder':
|
|
216
|
+
manifest.docs_folder = value;
|
|
217
|
+
info(`Setting docsFolder to: ${chalk.cyan(value)}`);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Update timestamp
|
|
222
|
+
manifest.updated_at = new Date().toISOString();
|
|
223
|
+
|
|
224
|
+
// Write manifest
|
|
225
|
+
await fs.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
|
|
226
|
+
success('Configuration updated');
|
|
227
|
+
|
|
228
|
+
// Update IDE configs if needed
|
|
229
|
+
if (needsIdeUpdate) {
|
|
230
|
+
console.log();
|
|
231
|
+
info('Updating IDE configurations...');
|
|
232
|
+
|
|
233
|
+
ideManager.setAgileflowFolder(manifest.agileflow_folder || '.agileflow');
|
|
234
|
+
ideManager.setDocsFolder(manifest.docs_folder || 'docs');
|
|
235
|
+
|
|
236
|
+
// Remove old IDE configs
|
|
237
|
+
for (const ide of oldIdes) {
|
|
238
|
+
if (!manifest.ides.includes(ide)) {
|
|
239
|
+
const configPath = getIdeConfigPath(directory, ide);
|
|
240
|
+
if (await fs.pathExists(configPath)) {
|
|
241
|
+
await fs.remove(configPath);
|
|
242
|
+
info(`Removed ${formatIdeName(ide)} configuration`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add new IDE configs
|
|
248
|
+
for (const ide of manifest.ides) {
|
|
249
|
+
await ideManager.setup(ide, directory, status.path);
|
|
250
|
+
success(`Updated ${formatIdeName(ide)} configuration`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log();
|
|
254
|
+
success('IDE configurations updated');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get IDE config path
|
|
262
|
+
*/
|
|
263
|
+
function getIdeConfigPath(projectDir, ide) {
|
|
264
|
+
const paths = {
|
|
265
|
+
'claude-code': '.claude/commands/AgileFlow',
|
|
266
|
+
'cursor': '.cursor/rules/agileflow',
|
|
267
|
+
'windsurf': '.windsurf/workflows/agileflow',
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return path.join(projectDir, paths[ide] || '');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Format IDE name for display
|
|
275
|
+
*/
|
|
276
|
+
function formatIdeName(ide) {
|
|
277
|
+
const names = {
|
|
278
|
+
'claude-code': 'Claude Code',
|
|
279
|
+
'cursor': 'Cursor',
|
|
280
|
+
'windsurf': 'Windsurf',
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return names[ide] || ide;
|
|
284
|
+
}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const path = require('node:path');
|
|
9
|
+
const crypto = require('node:crypto');
|
|
9
10
|
const fs = require('fs-extra');
|
|
10
11
|
const { Installer } = require('../installers/core/installer');
|
|
11
|
-
const { displayLogo, displaySection, success, warning, error, info } = require('../lib/ui');
|
|
12
|
+
const { displayLogo, displaySection, success, warning, error, info, confirm } = require('../lib/ui');
|
|
13
|
+
const { IdeManager } = require('../installers/ide/manager');
|
|
12
14
|
const { getCurrentVersion } = require('../lib/version-checker');
|
|
13
15
|
|
|
14
16
|
const installer = new Installer();
|
|
@@ -18,16 +20,18 @@ module.exports = {
|
|
|
18
20
|
description: 'Diagnose AgileFlow installation issues',
|
|
19
21
|
options: [
|
|
20
22
|
['-d, --directory <path>', 'Project directory (default: current directory)'],
|
|
23
|
+
['--fix', 'Automatically fix detected issues'],
|
|
21
24
|
],
|
|
22
25
|
action: async (options) => {
|
|
23
26
|
try {
|
|
24
27
|
const directory = path.resolve(options.directory || '.');
|
|
25
28
|
|
|
26
29
|
displayLogo();
|
|
27
|
-
displaySection('AgileFlow Diagnostics');
|
|
30
|
+
displaySection(options.fix ? 'AgileFlow Auto-Repair' : 'AgileFlow Diagnostics');
|
|
28
31
|
|
|
29
32
|
let issues = 0;
|
|
30
33
|
let warnings = 0;
|
|
34
|
+
const repairs = []; // Track fixable issues
|
|
31
35
|
|
|
32
36
|
// Check Node.js version
|
|
33
37
|
console.log(chalk.bold('Environment:'));
|
|
@@ -73,16 +77,98 @@ module.exports = {
|
|
|
73
77
|
} else {
|
|
74
78
|
error('manifest.yaml missing');
|
|
75
79
|
issues++;
|
|
80
|
+
repairs.push({
|
|
81
|
+
type: 'missing-manifest',
|
|
82
|
+
message: 'Recreate missing manifest.yaml',
|
|
83
|
+
fix: async () => {
|
|
84
|
+
info('Recreating manifest.yaml...');
|
|
85
|
+
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
86
|
+
const cfgDir = path.join(status.path, '_cfg');
|
|
87
|
+
await fs.ensureDir(cfgDir);
|
|
88
|
+
|
|
89
|
+
const yaml = require('js-yaml');
|
|
90
|
+
const manifest = {
|
|
91
|
+
version: packageJson.version,
|
|
92
|
+
installed_at: new Date().toISOString(),
|
|
93
|
+
updated_at: new Date().toISOString(),
|
|
94
|
+
ides: status.ides || ['claude-code'],
|
|
95
|
+
modules: ['core'],
|
|
96
|
+
user_name: status.userName || 'Developer',
|
|
97
|
+
agileflow_folder: path.basename(status.path),
|
|
98
|
+
docs_folder: 'docs',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
await fs.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
|
|
102
|
+
success('Created manifest.yaml');
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check safe update tracking and pending preserved updates
|
|
108
|
+
console.log(chalk.bold('\nSafe Updates:'));
|
|
109
|
+
const cfgDir = path.join(status.path, '_cfg');
|
|
110
|
+
const fileIndexPath = path.join(cfgDir, 'files.json');
|
|
111
|
+
let fileIndex = null;
|
|
112
|
+
|
|
113
|
+
if (await fs.pathExists(fileIndexPath)) {
|
|
114
|
+
try {
|
|
115
|
+
fileIndex = await fs.readJson(fileIndexPath);
|
|
116
|
+
if (!fileIndex || fileIndex.schema !== 1 || !fileIndex.files || typeof fileIndex.files !== 'object') {
|
|
117
|
+
throw new Error('invalid format');
|
|
118
|
+
}
|
|
119
|
+
success('files.json present');
|
|
120
|
+
|
|
121
|
+
const protectedCount = countProtectedFiles(fileIndex);
|
|
122
|
+
if (protectedCount > 0) {
|
|
123
|
+
warning(`Protected files: ${protectedCount} (local changes preserved)`);
|
|
124
|
+
warnings++;
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
warning(`files.json invalid (${err.message})`);
|
|
128
|
+
warnings++;
|
|
129
|
+
repairs.push({
|
|
130
|
+
type: 'invalid-file-index',
|
|
131
|
+
message: 'Recreate files.json safe-update index',
|
|
132
|
+
fix: async () => {
|
|
133
|
+
await createProtectedFileIndex(status.path, fileIndexPath);
|
|
134
|
+
success('Recreated files.json (all files protected)');
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
warning('files.json missing (safe updates disabled)');
|
|
140
|
+
warnings++;
|
|
141
|
+
repairs.push({
|
|
142
|
+
type: 'missing-file-index',
|
|
143
|
+
message: 'Create files.json safe-update index',
|
|
144
|
+
fix: async () => {
|
|
145
|
+
await createProtectedFileIndex(status.path, fileIndexPath);
|
|
146
|
+
success('Created files.json (all files protected)');
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const updatesDir = path.join(cfgDir, 'updates');
|
|
152
|
+
const updateSets = await listSubdirectories(updatesDir);
|
|
153
|
+
if (updateSets.length > 0) {
|
|
154
|
+
warning(`Pending preserved updates: ${updateSets.length} set(s)`);
|
|
155
|
+
warnings++;
|
|
156
|
+
info(`Review: ${updatesDir}/`);
|
|
157
|
+
} else {
|
|
158
|
+
success('No pending preserved updates');
|
|
76
159
|
}
|
|
77
160
|
|
|
78
161
|
// Check core content
|
|
79
162
|
const counts = await installer.countInstalledItems(status.path);
|
|
80
163
|
|
|
164
|
+
let missingCore = false;
|
|
165
|
+
|
|
81
166
|
if (counts.agents > 0) {
|
|
82
167
|
success(`Core agents: ${counts.agents} files`);
|
|
83
168
|
} else {
|
|
84
169
|
error('Core agents: Missing');
|
|
85
170
|
issues++;
|
|
171
|
+
missingCore = true;
|
|
86
172
|
}
|
|
87
173
|
|
|
88
174
|
if (counts.commands > 0) {
|
|
@@ -90,6 +176,27 @@ module.exports = {
|
|
|
90
176
|
} else {
|
|
91
177
|
error('Commands: Missing');
|
|
92
178
|
issues++;
|
|
179
|
+
missingCore = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (missingCore) {
|
|
183
|
+
repairs.push({
|
|
184
|
+
type: 'missing-core',
|
|
185
|
+
message: 'Reinstall missing core content',
|
|
186
|
+
fix: async () => {
|
|
187
|
+
info('Reinstalling core content...');
|
|
188
|
+
const config = {
|
|
189
|
+
directory,
|
|
190
|
+
ides: status.ides || ['claude-code'],
|
|
191
|
+
userName: status.userName || 'Developer',
|
|
192
|
+
agileflowFolder: path.basename(status.path),
|
|
193
|
+
docsFolder: status.docsFolder || 'docs',
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
await installer.install(config);
|
|
197
|
+
success('Reinstalled core content');
|
|
198
|
+
},
|
|
199
|
+
});
|
|
93
200
|
}
|
|
94
201
|
|
|
95
202
|
if (counts.skills > 0) {
|
|
@@ -103,6 +210,10 @@ module.exports = {
|
|
|
103
210
|
if (status.ides && status.ides.length > 0) {
|
|
104
211
|
console.log(chalk.bold('\nIDE Configurations:'));
|
|
105
212
|
|
|
213
|
+
const ideManager = new IdeManager();
|
|
214
|
+
ideManager.setAgileflowFolder(path.basename(status.path));
|
|
215
|
+
ideManager.setDocsFolder(status.docsFolder || 'docs');
|
|
216
|
+
|
|
106
217
|
for (const ide of status.ides) {
|
|
107
218
|
const configPath = getIdeConfigPath(directory, ide);
|
|
108
219
|
const ideName = formatIdeName(ide);
|
|
@@ -112,8 +223,17 @@ module.exports = {
|
|
|
112
223
|
const files = await countFilesInDir(configPath);
|
|
113
224
|
success(`${ideName}: ${files} files`);
|
|
114
225
|
} else {
|
|
115
|
-
warning(`${ideName}: Config missing
|
|
226
|
+
warning(`${ideName}: Config missing`);
|
|
116
227
|
warnings++;
|
|
228
|
+
repairs.push({
|
|
229
|
+
type: 'missing-ide-config',
|
|
230
|
+
message: `Reinstall ${ideName} configuration`,
|
|
231
|
+
fix: async () => {
|
|
232
|
+
info(`Reinstalling ${ideName} configuration...`);
|
|
233
|
+
await ideManager.setup(ide, directory, status.path);
|
|
234
|
+
success(`Reinstalled ${ideName} configuration`);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
117
237
|
}
|
|
118
238
|
}
|
|
119
239
|
}
|
|
@@ -127,9 +247,19 @@ module.exports = {
|
|
|
127
247
|
if (!status.ides || !status.ides.includes(ide)) {
|
|
128
248
|
const configPath = getIdeConfigPath(directory, ide);
|
|
129
249
|
if (await fs.pathExists(configPath)) {
|
|
130
|
-
|
|
250
|
+
const ideName = formatIdeName(ide);
|
|
251
|
+
warning(`${ideName}: Config exists but not in manifest`);
|
|
131
252
|
orphansFound = true;
|
|
132
253
|
warnings++;
|
|
254
|
+
repairs.push({
|
|
255
|
+
type: 'orphaned-config',
|
|
256
|
+
message: `Remove orphaned ${ideName} configuration`,
|
|
257
|
+
fix: async () => {
|
|
258
|
+
info(`Removing orphaned ${ideName} configuration...`);
|
|
259
|
+
await fs.remove(configPath);
|
|
260
|
+
success(`Removed ${ideName} configuration`);
|
|
261
|
+
},
|
|
262
|
+
});
|
|
133
263
|
}
|
|
134
264
|
}
|
|
135
265
|
}
|
|
@@ -138,6 +268,26 @@ module.exports = {
|
|
|
138
268
|
success('No orphaned configurations');
|
|
139
269
|
}
|
|
140
270
|
|
|
271
|
+
// Execute repairs if --fix is enabled
|
|
272
|
+
if (options.fix && repairs.length > 0) {
|
|
273
|
+
console.log();
|
|
274
|
+
displaySection('Applying Fixes');
|
|
275
|
+
|
|
276
|
+
for (const repair of repairs) {
|
|
277
|
+
try {
|
|
278
|
+
await repair.fix();
|
|
279
|
+
} catch (err) {
|
|
280
|
+
error(`Failed to ${repair.message.toLowerCase()}: ${err.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log();
|
|
285
|
+
success(`Applied ${repairs.length} fix(es)`);
|
|
286
|
+
} else if (repairs.length > 0 && !options.fix) {
|
|
287
|
+
console.log();
|
|
288
|
+
info(`Found ${repairs.length} fixable issue(s). Run with --fix to auto-repair.`);
|
|
289
|
+
}
|
|
290
|
+
|
|
141
291
|
// Print summary
|
|
142
292
|
printSummary(issues, warnings);
|
|
143
293
|
|
|
@@ -241,3 +391,70 @@ function printSummary(issues, warnings) {
|
|
|
241
391
|
console.log(chalk.red(`${issues} issue(s), ${warnings} warning(s) found.\n`));
|
|
242
392
|
}
|
|
243
393
|
}
|
|
394
|
+
|
|
395
|
+
function sha256Hex(data) {
|
|
396
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function toPosixPath(filePath) {
|
|
400
|
+
return filePath.split(path.sep).join('/');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function countProtectedFiles(fileIndex) {
|
|
404
|
+
if (!fileIndex || !fileIndex.files || typeof fileIndex.files !== 'object') return 0;
|
|
405
|
+
return Object.values(fileIndex.files).filter((record) => record && record.protected).length;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function listSubdirectories(dirPath) {
|
|
409
|
+
if (!(await fs.pathExists(dirPath))) return [];
|
|
410
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
411
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function createProtectedFileIndex(agileflowDir, fileIndexPath) {
|
|
415
|
+
const candidates = ['agents', 'commands', 'skills', 'templates', 'config.yaml'];
|
|
416
|
+
const files = {};
|
|
417
|
+
|
|
418
|
+
for (const candidate of candidates) {
|
|
419
|
+
const candidatePath = path.join(agileflowDir, candidate);
|
|
420
|
+
if (!(await fs.pathExists(candidatePath))) continue;
|
|
421
|
+
|
|
422
|
+
const stat = await fs.stat(candidatePath);
|
|
423
|
+
if (stat.isFile()) {
|
|
424
|
+
const data = await fs.readFile(candidatePath);
|
|
425
|
+
const relativePath = toPosixPath(path.relative(agileflowDir, candidatePath));
|
|
426
|
+
files[relativePath] = { sha256: sha256Hex(data), protected: true };
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
await indexDirectory(candidatePath, agileflowDir, files);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const index = {
|
|
434
|
+
schema: 1,
|
|
435
|
+
generated_at: new Date().toISOString(),
|
|
436
|
+
version: getCurrentVersion(),
|
|
437
|
+
files,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
await fs.ensureDir(path.dirname(fileIndexPath));
|
|
441
|
+
await fs.writeJson(fileIndexPath, index, { spaces: 2 });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function indexDirectory(dirPath, rootDir, filesOut) {
|
|
445
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
446
|
+
|
|
447
|
+
for (const entry of entries) {
|
|
448
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
449
|
+
if (entry.isDirectory()) {
|
|
450
|
+
await indexDirectory(entryPath, rootDir, filesOut);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (entry.isFile()) {
|
|
455
|
+
const data = await fs.readFile(entryPath);
|
|
456
|
+
const relativePath = toPosixPath(path.relative(rootDir, entryPath));
|
|
457
|
+
filesOut[relativePath] = { sha256: sha256Hex(data), protected: true };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -33,6 +33,7 @@ module.exports = {
|
|
|
33
33
|
userName: 'Developer',
|
|
34
34
|
agileflowFolder: '.agileflow',
|
|
35
35
|
docsFolder: 'docs',
|
|
36
|
+
updateGitignore: true,
|
|
36
37
|
};
|
|
37
38
|
} else {
|
|
38
39
|
// Interactive prompts
|
|
@@ -65,7 +66,9 @@ module.exports = {
|
|
|
65
66
|
|
|
66
67
|
// Create docs structure
|
|
67
68
|
displaySection('Creating Documentation Structure', `Folder: ${config.docsFolder}/`);
|
|
68
|
-
const docsResult = await createDocsStructure(config.directory, config.docsFolder
|
|
69
|
+
const docsResult = await createDocsStructure(config.directory, config.docsFolder, {
|
|
70
|
+
updateGitignore: config.updateGitignore,
|
|
71
|
+
});
|
|
69
72
|
|
|
70
73
|
if (!docsResult.success) {
|
|
71
74
|
error('Failed to create docs structure');
|