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
|
@@ -28,16 +28,29 @@
|
|
|
28
28
|
|
|
29
29
|
const fs = require('fs');
|
|
30
30
|
const path = require('path');
|
|
31
|
-
const { execSync } = require('child_process');
|
|
32
31
|
const { isValidProfileName, isValidFeatureName, parseIntBounded } = require('../lib/validate');
|
|
33
32
|
|
|
33
|
+
// Import extracted modules
|
|
34
|
+
const { c, log, success, header } = require('./lib/configure-utils');
|
|
35
|
+
const { detectConfig, printStatus } = require('./lib/configure-detect');
|
|
36
|
+
const {
|
|
37
|
+
PROFILES,
|
|
38
|
+
enableFeature,
|
|
39
|
+
disableFeature,
|
|
40
|
+
applyProfile,
|
|
41
|
+
setStatuslineComponents,
|
|
42
|
+
listStatuslineComponents,
|
|
43
|
+
migrateSettings,
|
|
44
|
+
upgradeFeatures,
|
|
45
|
+
} = require('./lib/configure-features');
|
|
46
|
+
const { listScripts, showVersionInfo, repairScripts } = require('./lib/configure-repair');
|
|
47
|
+
|
|
34
48
|
// ============================================================================
|
|
35
|
-
//
|
|
49
|
+
// VERSION
|
|
36
50
|
// ============================================================================
|
|
37
51
|
|
|
38
|
-
// Get version dynamically from metadata or package.json
|
|
39
52
|
function getVersion() {
|
|
40
|
-
// Try agileflow-metadata.json first
|
|
53
|
+
// Try agileflow-metadata.json first
|
|
41
54
|
try {
|
|
42
55
|
const metaPath = path.join(process.cwd(), 'docs/00-meta/agileflow-metadata.json');
|
|
43
56
|
if (fs.existsSync(metaPath)) {
|
|
@@ -55,7 +68,7 @@ function getVersion() {
|
|
|
55
68
|
}
|
|
56
69
|
} catch {}
|
|
57
70
|
|
|
58
|
-
// Fallback to script's own package.json
|
|
71
|
+
// Fallback to script's own package.json
|
|
59
72
|
try {
|
|
60
73
|
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
61
74
|
if (fs.existsSync(pkgPath)) {
|
|
@@ -69,1411 +82,31 @@ function getVersion() {
|
|
|
69
82
|
|
|
70
83
|
const VERSION = getVersion();
|
|
71
84
|
|
|
72
|
-
const FEATURES = {
|
|
73
|
-
sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
|
|
74
|
-
precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
|
|
75
|
-
ralphloop: { hook: 'Stop', script: 'ralph-loop.js', type: 'node' },
|
|
76
|
-
selfimprove: { hook: 'Stop', script: 'auto-self-improve.js', type: 'node' },
|
|
77
|
-
archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
|
|
78
|
-
statusline: { script: 'agileflow-statusline.sh' },
|
|
79
|
-
autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
|
|
80
|
-
damagecontrol: {
|
|
81
|
-
preToolUseHooks: true,
|
|
82
|
-
scripts: ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'],
|
|
83
|
-
patternsFile: 'damage-control-patterns.yaml',
|
|
84
|
-
},
|
|
85
|
-
askuserquestion: { metadataOnly: true }, // Stored in metadata.features.askUserQuestion
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Complete registry of all scripts that may need repair
|
|
89
|
-
const ALL_SCRIPTS = {
|
|
90
|
-
// Core feature scripts (linked to FEATURES)
|
|
91
|
-
'agileflow-welcome.js': { feature: 'sessionstart', required: true },
|
|
92
|
-
'precompact-context.sh': { feature: 'precompact', required: true },
|
|
93
|
-
'ralph-loop.js': { feature: 'ralphloop', required: true },
|
|
94
|
-
'auto-self-improve.js': { feature: 'selfimprove', required: true },
|
|
95
|
-
'archive-completed-stories.sh': { feature: 'archival', required: true },
|
|
96
|
-
'agileflow-statusline.sh': { feature: 'statusline', required: true },
|
|
97
|
-
'damage-control-bash.js': { feature: 'damagecontrol', required: true },
|
|
98
|
-
'damage-control-edit.js': { feature: 'damagecontrol', required: true },
|
|
99
|
-
'damage-control-write.js': { feature: 'damagecontrol', required: true },
|
|
100
|
-
|
|
101
|
-
// Support scripts (used by commands/agents)
|
|
102
|
-
'obtain-context.js': { usedBy: ['/babysit', '/mentor', '/sprint'] },
|
|
103
|
-
'session-manager.js': { usedBy: ['/session:new', '/session:resume'] },
|
|
104
|
-
'check-update.js': { usedBy: ['SessionStart hook'] },
|
|
105
|
-
'get-env.js': { usedBy: ['SessionStart hook'] },
|
|
106
|
-
'clear-active-command.js': { usedBy: ['session commands'] },
|
|
107
|
-
|
|
108
|
-
// Utility scripts
|
|
109
|
-
'compress-status.sh': { usedBy: ['/compress'] },
|
|
110
|
-
'validate-expertise.sh': { usedBy: ['/validate-expertise'] },
|
|
111
|
-
'expertise-metrics.sh': { usedBy: ['agent experts'] },
|
|
112
|
-
'session-coordinator.sh': { usedBy: ['session management'] },
|
|
113
|
-
'validate-tokens.sh': { usedBy: ['token validation'] },
|
|
114
|
-
'worktree-create.sh': { usedBy: ['/session:new'] },
|
|
115
|
-
'resume-session.sh': { usedBy: ['/session:resume'] },
|
|
116
|
-
'init.sh': { usedBy: ['/session:init'] },
|
|
117
|
-
'agileflow-configure.js': { usedBy: ['/configure'] },
|
|
118
|
-
'generate-all.sh': { usedBy: ['content generation'] },
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Statusline component names
|
|
122
|
-
const STATUSLINE_COMPONENTS = [
|
|
123
|
-
'agileflow',
|
|
124
|
-
'model',
|
|
125
|
-
'story',
|
|
126
|
-
'epic',
|
|
127
|
-
'wip',
|
|
128
|
-
'context',
|
|
129
|
-
'cost',
|
|
130
|
-
'git',
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
const PROFILES = {
|
|
134
|
-
full: {
|
|
135
|
-
description: 'All features enabled (including experimental Stop hooks)',
|
|
136
|
-
enable: [
|
|
137
|
-
'sessionstart',
|
|
138
|
-
'precompact',
|
|
139
|
-
'archival',
|
|
140
|
-
'statusline',
|
|
141
|
-
'ralphloop',
|
|
142
|
-
'selfimprove',
|
|
143
|
-
'askuserquestion',
|
|
144
|
-
],
|
|
145
|
-
archivalDays: 30,
|
|
146
|
-
},
|
|
147
|
-
basic: {
|
|
148
|
-
description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
|
|
149
|
-
enable: ['sessionstart', 'precompact', 'archival', 'askuserquestion'],
|
|
150
|
-
disable: ['statusline', 'ralphloop', 'selfimprove'],
|
|
151
|
-
archivalDays: 30,
|
|
152
|
-
},
|
|
153
|
-
minimal: {
|
|
154
|
-
description: 'SessionStart + archival only',
|
|
155
|
-
enable: ['sessionstart', 'archival'],
|
|
156
|
-
disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove', 'askuserquestion'],
|
|
157
|
-
archivalDays: 30,
|
|
158
|
-
},
|
|
159
|
-
none: {
|
|
160
|
-
description: 'Disable all AgileFlow features',
|
|
161
|
-
disable: [
|
|
162
|
-
'sessionstart',
|
|
163
|
-
'precompact',
|
|
164
|
-
'archival',
|
|
165
|
-
'statusline',
|
|
166
|
-
'ralphloop',
|
|
167
|
-
'selfimprove',
|
|
168
|
-
'askuserquestion',
|
|
169
|
-
],
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// ============================================================================
|
|
174
|
-
// COLORS & LOGGING
|
|
175
|
-
// ============================================================================
|
|
176
|
-
|
|
177
|
-
const c = {
|
|
178
|
-
reset: '\x1b[0m',
|
|
179
|
-
dim: '\x1b[2m',
|
|
180
|
-
bold: '\x1b[1m',
|
|
181
|
-
green: '\x1b[32m',
|
|
182
|
-
yellow: '\x1b[33m',
|
|
183
|
-
red: '\x1b[31m',
|
|
184
|
-
cyan: '\x1b[36m',
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const log = (msg, color = '') => console.log(`${color}${msg}${c.reset}`);
|
|
188
|
-
const success = msg => log(`✅ ${msg}`, c.green);
|
|
189
|
-
const warn = msg => log(`⚠️ ${msg}`, c.yellow);
|
|
190
|
-
const error = msg => log(`❌ ${msg}`, c.red);
|
|
191
|
-
const info = msg => log(`ℹ️ ${msg}`, c.dim);
|
|
192
|
-
const header = msg => log(`\n${msg}`, c.bold + c.cyan);
|
|
193
|
-
|
|
194
|
-
// ============================================================================
|
|
195
|
-
// FILE UTILITIES
|
|
196
|
-
// ============================================================================
|
|
197
|
-
|
|
198
|
-
const ensureDir = dir => {
|
|
199
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const readJSON = filePath => {
|
|
203
|
-
try {
|
|
204
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
205
|
-
} catch {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const writeJSON = (filePath, data) => {
|
|
211
|
-
ensureDir(path.dirname(filePath));
|
|
212
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// Scripts are located in .agileflow/scripts/ (installed by AgileFlow)
|
|
216
|
-
const SCRIPTS_DIR = path.join(process.cwd(), '.agileflow', 'scripts');
|
|
217
|
-
|
|
218
|
-
const scriptExists = scriptName => {
|
|
219
|
-
const scriptPath = path.join(SCRIPTS_DIR, scriptName);
|
|
220
|
-
return fs.existsSync(scriptPath);
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const getScriptPath = scriptName => {
|
|
224
|
-
return `.agileflow/scripts/${scriptName}`;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
// ============================================================================
|
|
228
|
-
// DETECTION & VALIDATION
|
|
229
|
-
// ============================================================================
|
|
230
|
-
|
|
231
|
-
function detectConfig() {
|
|
232
|
-
const status = {
|
|
233
|
-
git: { initialized: false, remote: null },
|
|
234
|
-
settingsExists: false,
|
|
235
|
-
settingsValid: true,
|
|
236
|
-
settingsIssues: [],
|
|
237
|
-
features: {
|
|
238
|
-
sessionstart: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
239
|
-
precompact: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
240
|
-
ralphloop: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
241
|
-
selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
242
|
-
archival: { enabled: false, threshold: null, version: null, outdated: false },
|
|
243
|
-
statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
244
|
-
damagecontrol: {
|
|
245
|
-
enabled: false,
|
|
246
|
-
valid: true,
|
|
247
|
-
issues: [],
|
|
248
|
-
version: null,
|
|
249
|
-
outdated: false,
|
|
250
|
-
level: null,
|
|
251
|
-
patternCount: 0,
|
|
252
|
-
},
|
|
253
|
-
askuserquestion: {
|
|
254
|
-
enabled: false,
|
|
255
|
-
valid: true,
|
|
256
|
-
issues: [],
|
|
257
|
-
version: null,
|
|
258
|
-
outdated: false,
|
|
259
|
-
mode: null,
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
metadata: { exists: false, version: null },
|
|
263
|
-
currentVersion: VERSION,
|
|
264
|
-
hasOutdated: false,
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// Git
|
|
268
|
-
if (fs.existsSync('.git')) {
|
|
269
|
-
status.git.initialized = true;
|
|
270
|
-
try {
|
|
271
|
-
status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
272
|
-
encoding: 'utf8',
|
|
273
|
-
}).trim();
|
|
274
|
-
} catch {}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Settings file
|
|
278
|
-
if (fs.existsSync('.claude/settings.json')) {
|
|
279
|
-
status.settingsExists = true;
|
|
280
|
-
const settings = readJSON('.claude/settings.json');
|
|
281
|
-
|
|
282
|
-
if (!settings) {
|
|
283
|
-
status.settingsValid = false;
|
|
284
|
-
status.settingsIssues.push('Invalid JSON in settings.json');
|
|
285
|
-
} else {
|
|
286
|
-
// Check hooks
|
|
287
|
-
if (settings.hooks) {
|
|
288
|
-
// SessionStart
|
|
289
|
-
if (settings.hooks.SessionStart) {
|
|
290
|
-
if (
|
|
291
|
-
Array.isArray(settings.hooks.SessionStart) &&
|
|
292
|
-
settings.hooks.SessionStart.length > 0
|
|
293
|
-
) {
|
|
294
|
-
const hook = settings.hooks.SessionStart[0];
|
|
295
|
-
if (hook.matcher !== undefined && hook.hooks) {
|
|
296
|
-
status.features.sessionstart.enabled = true;
|
|
297
|
-
} else {
|
|
298
|
-
status.features.sessionstart.enabled = true;
|
|
299
|
-
status.features.sessionstart.valid = false;
|
|
300
|
-
status.features.sessionstart.issues.push('Old format - needs migration');
|
|
301
|
-
}
|
|
302
|
-
} else if (typeof settings.hooks.SessionStart === 'string') {
|
|
303
|
-
status.features.sessionstart.enabled = true;
|
|
304
|
-
status.features.sessionstart.valid = false;
|
|
305
|
-
status.features.sessionstart.issues.push('String format - needs migration');
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// PreCompact
|
|
310
|
-
if (settings.hooks.PreCompact) {
|
|
311
|
-
if (Array.isArray(settings.hooks.PreCompact) && settings.hooks.PreCompact.length > 0) {
|
|
312
|
-
const hook = settings.hooks.PreCompact[0];
|
|
313
|
-
if (hook.matcher !== undefined && hook.hooks) {
|
|
314
|
-
status.features.precompact.enabled = true;
|
|
315
|
-
} else {
|
|
316
|
-
status.features.precompact.enabled = true;
|
|
317
|
-
status.features.precompact.valid = false;
|
|
318
|
-
status.features.precompact.issues.push('Old format - needs migration');
|
|
319
|
-
}
|
|
320
|
-
} else if (typeof settings.hooks.PreCompact === 'string') {
|
|
321
|
-
status.features.precompact.enabled = true;
|
|
322
|
-
status.features.precompact.valid = false;
|
|
323
|
-
status.features.precompact.issues.push('String format - needs migration');
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Stop hooks (ralphloop and selfimprove)
|
|
328
|
-
if (settings.hooks.Stop) {
|
|
329
|
-
if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
|
|
330
|
-
const hook = settings.hooks.Stop[0];
|
|
331
|
-
if (hook.matcher !== undefined && hook.hooks) {
|
|
332
|
-
// Check for each Stop hook feature
|
|
333
|
-
for (const h of hook.hooks) {
|
|
334
|
-
if (h.command?.includes('ralph-loop')) {
|
|
335
|
-
status.features.ralphloop.enabled = true;
|
|
336
|
-
}
|
|
337
|
-
if (h.command?.includes('auto-self-improve')) {
|
|
338
|
-
status.features.selfimprove.enabled = true;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// PreToolUse hooks (damage control)
|
|
346
|
-
if (settings.hooks.PreToolUse) {
|
|
347
|
-
if (Array.isArray(settings.hooks.PreToolUse) && settings.hooks.PreToolUse.length > 0) {
|
|
348
|
-
// Check for damage-control hooks by looking for damage-control scripts
|
|
349
|
-
const hasBashHook = settings.hooks.PreToolUse.some(
|
|
350
|
-
h =>
|
|
351
|
-
h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
352
|
-
);
|
|
353
|
-
const hasEditHook = settings.hooks.PreToolUse.some(
|
|
354
|
-
h =>
|
|
355
|
-
h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
356
|
-
);
|
|
357
|
-
const hasWriteHook = settings.hooks.PreToolUse.some(
|
|
358
|
-
h =>
|
|
359
|
-
h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
if (hasBashHook || hasEditHook || hasWriteHook) {
|
|
363
|
-
status.features.damagecontrol.enabled = true;
|
|
364
|
-
// Count how many of the 3 hooks are present
|
|
365
|
-
const hookCount = [hasBashHook, hasEditHook, hasWriteHook].filter(Boolean).length;
|
|
366
|
-
if (hookCount < 3) {
|
|
367
|
-
status.features.damagecontrol.valid = false;
|
|
368
|
-
status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// StatusLine
|
|
376
|
-
if (settings.statusLine) {
|
|
377
|
-
status.features.statusline.enabled = true;
|
|
378
|
-
if (typeof settings.statusLine === 'string') {
|
|
379
|
-
status.features.statusline.valid = false;
|
|
380
|
-
status.features.statusline.issues.push('String format - needs type:command');
|
|
381
|
-
} else if (!settings.statusLine.type) {
|
|
382
|
-
status.features.statusline.valid = false;
|
|
383
|
-
status.features.statusline.issues.push('Missing type:command');
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Metadata
|
|
390
|
-
const metaPath = 'docs/00-meta/agileflow-metadata.json';
|
|
391
|
-
if (fs.existsSync(metaPath)) {
|
|
392
|
-
status.metadata.exists = true;
|
|
393
|
-
const meta = readJSON(metaPath);
|
|
394
|
-
if (meta) {
|
|
395
|
-
status.metadata.version = meta.version;
|
|
396
|
-
if (meta.archival?.enabled) {
|
|
397
|
-
status.features.archival.enabled = true;
|
|
398
|
-
status.features.archival.threshold = meta.archival.threshold_days;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Damage control metadata
|
|
402
|
-
if (meta.features?.damagecontrol?.enabled) {
|
|
403
|
-
status.features.damagecontrol.level =
|
|
404
|
-
meta.features.damagecontrol.protectionLevel || 'standard';
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// AskUserQuestion metadata
|
|
408
|
-
if (meta.features?.askUserQuestion?.enabled) {
|
|
409
|
-
status.features.askuserquestion.enabled = true;
|
|
410
|
-
status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Read feature versions from metadata and check if outdated
|
|
414
|
-
if (meta.features) {
|
|
415
|
-
// Map metadata keys to status keys (handle camelCase differences)
|
|
416
|
-
const featureKeyMap = {
|
|
417
|
-
askUserQuestion: 'askuserquestion',
|
|
418
|
-
};
|
|
419
|
-
Object.entries(meta.features).forEach(([feature, data]) => {
|
|
420
|
-
// Use mapped key if exists, otherwise lowercase
|
|
421
|
-
const statusKey = featureKeyMap[feature] || feature.toLowerCase();
|
|
422
|
-
if (status.features[statusKey] && data.version) {
|
|
423
|
-
status.features[statusKey].version = data.version;
|
|
424
|
-
// Check if feature version differs from current VERSION
|
|
425
|
-
if (data.version !== VERSION && status.features[statusKey].enabled) {
|
|
426
|
-
status.features[statusKey].outdated = true;
|
|
427
|
-
status.hasOutdated = true;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return status;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function printStatus(status) {
|
|
439
|
-
header('📊 Current Configuration');
|
|
440
|
-
|
|
441
|
-
// Git
|
|
442
|
-
log(
|
|
443
|
-
`Git: ${status.git.initialized ? '✅' : '❌'} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
|
|
444
|
-
status.git.initialized ? c.green : c.dim
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
// Settings
|
|
448
|
-
if (!status.settingsExists) {
|
|
449
|
-
log('Settings: ❌ .claude/settings.json not found', c.dim);
|
|
450
|
-
} else if (!status.settingsValid) {
|
|
451
|
-
log('Settings: ❌ Invalid JSON', c.red);
|
|
452
|
-
} else {
|
|
453
|
-
log('Settings: ✅ .claude/settings.json exists', c.green);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Features
|
|
457
|
-
header('Features:');
|
|
458
|
-
|
|
459
|
-
const printFeature = (name, label) => {
|
|
460
|
-
const f = status.features[name];
|
|
461
|
-
let statusIcon = f.enabled ? '✅' : '❌';
|
|
462
|
-
let statusText = f.enabled ? 'enabled' : 'disabled';
|
|
463
|
-
let color = f.enabled ? c.green : c.dim;
|
|
464
|
-
|
|
465
|
-
if (f.enabled && !f.valid) {
|
|
466
|
-
statusIcon = '⚠️';
|
|
467
|
-
statusText = 'INVALID FORMAT';
|
|
468
|
-
color = c.yellow;
|
|
469
|
-
} else if (f.enabled && f.outdated) {
|
|
470
|
-
statusIcon = '🔄';
|
|
471
|
-
statusText = `outdated (v${f.version} → v${status.currentVersion})`;
|
|
472
|
-
color = c.yellow;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
log(` ${statusIcon} ${label}: ${statusText}`, color);
|
|
476
|
-
|
|
477
|
-
if (f.issues?.length > 0) {
|
|
478
|
-
f.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
printFeature('sessionstart', 'SessionStart Hook');
|
|
483
|
-
printFeature('precompact', 'PreCompact Hook');
|
|
484
|
-
printFeature('ralphloop', 'RalphLoop (Stop)');
|
|
485
|
-
printFeature('selfimprove', 'SelfImprove (Stop)');
|
|
486
|
-
|
|
487
|
-
const arch = status.features.archival;
|
|
488
|
-
log(
|
|
489
|
-
` ${arch.enabled ? '✅' : '❌'} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
|
|
490
|
-
arch.enabled ? c.green : c.dim
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
printFeature('statusline', 'Status Line');
|
|
494
|
-
|
|
495
|
-
// Damage Control (special display with level info)
|
|
496
|
-
const dc = status.features.damagecontrol;
|
|
497
|
-
if (dc.enabled) {
|
|
498
|
-
let dcStatusText = 'enabled';
|
|
499
|
-
if (dc.level) dcStatusText += ` (${dc.level})`;
|
|
500
|
-
if (!dc.valid) dcStatusText = 'INCOMPLETE';
|
|
501
|
-
const dcIcon = dc.enabled && dc.valid ? '🛡️' : '⚠️';
|
|
502
|
-
const dcColor = dc.enabled && dc.valid ? c.green : c.yellow;
|
|
503
|
-
log(` ${dcIcon} Damage Control: ${dcStatusText}`, dcColor);
|
|
504
|
-
if (dc.issues?.length > 0) {
|
|
505
|
-
dc.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
log(` ❌ Damage Control: disabled`, c.dim);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// AskUserQuestion
|
|
512
|
-
const auq = status.features.askuserquestion;
|
|
513
|
-
if (auq.enabled) {
|
|
514
|
-
let auqStatusText = 'enabled';
|
|
515
|
-
if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
|
|
516
|
-
log(` 💬 AskUserQuestion: ${auqStatusText}`, c.green);
|
|
517
|
-
} else {
|
|
518
|
-
log(` ❌ AskUserQuestion: disabled`, c.dim);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Metadata
|
|
522
|
-
if (status.metadata.exists) {
|
|
523
|
-
log(`\nMetadata: v${status.metadata.version}`, c.dim);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Issues summary
|
|
527
|
-
const hasIssues = Object.values(status.features).some(f => f.issues?.length > 0);
|
|
528
|
-
if (hasIssues) {
|
|
529
|
-
log('\n⚠️ Format issues detected! Run with --migrate to fix.', c.yellow);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (status.hasOutdated) {
|
|
533
|
-
log('\n🔄 Outdated scripts detected! Run with --upgrade to update.', c.yellow);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return { hasIssues, hasOutdated: status.hasOutdated };
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// ============================================================================
|
|
540
|
-
// MIGRATION
|
|
541
|
-
// ============================================================================
|
|
542
|
-
|
|
543
|
-
function migrateSettings() {
|
|
544
|
-
header('🔧 Migrating Settings...');
|
|
545
|
-
|
|
546
|
-
if (!fs.existsSync('.claude/settings.json')) {
|
|
547
|
-
warn('No settings.json to migrate');
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const settings = readJSON('.claude/settings.json');
|
|
552
|
-
if (!settings) {
|
|
553
|
-
error('Cannot parse settings.json');
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
let migrated = false;
|
|
558
|
-
|
|
559
|
-
// Migrate hooks to new format
|
|
560
|
-
if (settings.hooks) {
|
|
561
|
-
['SessionStart', 'PreCompact', 'UserPromptSubmit', 'Stop'].forEach(hookName => {
|
|
562
|
-
const hook = settings.hooks[hookName];
|
|
563
|
-
if (!hook) return;
|
|
564
|
-
|
|
565
|
-
// String format → array format
|
|
566
|
-
if (typeof hook === 'string') {
|
|
567
|
-
const isNode = hook.includes('node ') || hook.endsWith('.js');
|
|
568
|
-
settings.hooks[hookName] = [
|
|
569
|
-
{
|
|
570
|
-
matcher: '',
|
|
571
|
-
hooks: [{ type: 'command', command: isNode ? hook : `bash ${hook}` }],
|
|
572
|
-
},
|
|
573
|
-
];
|
|
574
|
-
success(`Migrated ${hookName} from string format`);
|
|
575
|
-
migrated = true;
|
|
576
|
-
}
|
|
577
|
-
// Old object format → new format
|
|
578
|
-
else if (Array.isArray(hook) && hook.length > 0) {
|
|
579
|
-
const first = hook[0];
|
|
580
|
-
if (first.enabled !== undefined || first.command !== undefined) {
|
|
581
|
-
// Old format with enabled/command
|
|
582
|
-
if (first.command) {
|
|
583
|
-
settings.hooks[hookName] = [
|
|
584
|
-
{
|
|
585
|
-
matcher: '',
|
|
586
|
-
hooks: [{ type: 'command', command: first.command }],
|
|
587
|
-
},
|
|
588
|
-
];
|
|
589
|
-
success(`Migrated ${hookName} from old object format`);
|
|
590
|
-
migrated = true;
|
|
591
|
-
}
|
|
592
|
-
} else if (first.matcher === undefined) {
|
|
593
|
-
// Missing matcher
|
|
594
|
-
settings.hooks[hookName] = [
|
|
595
|
-
{
|
|
596
|
-
matcher: '',
|
|
597
|
-
hooks: first.hooks || [{ type: 'command', command: 'echo "hook"' }],
|
|
598
|
-
},
|
|
599
|
-
];
|
|
600
|
-
success(`Migrated ${hookName} - added matcher`);
|
|
601
|
-
migrated = true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Migrate statusLine
|
|
608
|
-
if (settings.statusLine) {
|
|
609
|
-
if (typeof settings.statusLine === 'string') {
|
|
610
|
-
settings.statusLine = {
|
|
611
|
-
type: 'command',
|
|
612
|
-
command: settings.statusLine,
|
|
613
|
-
padding: 0,
|
|
614
|
-
};
|
|
615
|
-
success('Migrated statusLine from string format');
|
|
616
|
-
migrated = true;
|
|
617
|
-
} else if (!settings.statusLine.type) {
|
|
618
|
-
settings.statusLine.type = 'command';
|
|
619
|
-
if (settings.statusLine.refreshInterval) {
|
|
620
|
-
delete settings.statusLine.refreshInterval;
|
|
621
|
-
settings.statusLine.padding = 0;
|
|
622
|
-
}
|
|
623
|
-
success('Migrated statusLine - added type:command');
|
|
624
|
-
migrated = true;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (migrated) {
|
|
629
|
-
// Backup original
|
|
630
|
-
fs.copyFileSync('.claude/settings.json', '.claude/settings.json.backup');
|
|
631
|
-
info('Backed up to .claude/settings.json.backup');
|
|
632
|
-
|
|
633
|
-
writeJSON('.claude/settings.json', settings);
|
|
634
|
-
success('Settings migrated successfully!');
|
|
635
|
-
} else {
|
|
636
|
-
info('No migration needed - formats are correct');
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
return migrated;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// ============================================================================
|
|
643
|
-
// UPGRADE FEATURES
|
|
644
|
-
// ============================================================================
|
|
645
|
-
|
|
646
|
-
function upgradeFeatures(status) {
|
|
647
|
-
header('🔄 Upgrading Outdated Features...');
|
|
648
|
-
|
|
649
|
-
let upgraded = 0;
|
|
650
|
-
|
|
651
|
-
Object.entries(status.features).forEach(([feature, data]) => {
|
|
652
|
-
if (data.enabled && data.outdated) {
|
|
653
|
-
log(`\nUpgrading ${feature}...`, c.cyan);
|
|
654
|
-
// Re-enable the feature to deploy latest scripts
|
|
655
|
-
if (enableFeature(feature, { archivalDays: data.threshold || 30, isUpgrade: true })) {
|
|
656
|
-
upgraded++;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
if (upgraded === 0) {
|
|
662
|
-
info('No features needed upgrading');
|
|
663
|
-
} else {
|
|
664
|
-
success(`Upgraded ${upgraded} feature(s) to v${VERSION}`);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return upgraded > 0;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// ============================================================================
|
|
671
|
-
// ENABLE/DISABLE FEATURES
|
|
672
|
-
// ============================================================================
|
|
673
|
-
|
|
674
|
-
function enableFeature(feature, options = {}) {
|
|
675
|
-
const config = FEATURES[feature];
|
|
676
|
-
if (!config) {
|
|
677
|
-
error(`Unknown feature: ${feature}`);
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
ensureDir('.claude');
|
|
682
|
-
|
|
683
|
-
const settings = readJSON('.claude/settings.json') || {};
|
|
684
|
-
settings.hooks = settings.hooks || {};
|
|
685
|
-
settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
|
|
686
|
-
|
|
687
|
-
// Handle hook-based features
|
|
688
|
-
if (config.hook) {
|
|
689
|
-
const scriptPath = getScriptPath(config.script);
|
|
690
|
-
|
|
691
|
-
// Verify script exists
|
|
692
|
-
if (!scriptExists(config.script)) {
|
|
693
|
-
error(`Script not found: ${scriptPath}`);
|
|
694
|
-
info('Run "npx agileflow update" to reinstall scripts');
|
|
695
|
-
return false;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Use absolute path so hooks work from any subdirectory
|
|
699
|
-
const absoluteScriptPath = path.join(process.cwd(), scriptPath);
|
|
700
|
-
|
|
701
|
-
// Stop hooks use error suppression to avoid blocking Claude
|
|
702
|
-
const isStoHook = config.hook === 'Stop';
|
|
703
|
-
const command =
|
|
704
|
-
config.type === 'node'
|
|
705
|
-
? `node ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`
|
|
706
|
-
: `bash ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`;
|
|
707
|
-
|
|
708
|
-
if (isStoHook) {
|
|
709
|
-
// Stop hooks stack - add to existing hooks instead of replacing
|
|
710
|
-
if (!settings.hooks.Stop) {
|
|
711
|
-
settings.hooks.Stop = [{ matcher: '', hooks: [] }];
|
|
712
|
-
} else if (!Array.isArray(settings.hooks.Stop) || settings.hooks.Stop.length === 0) {
|
|
713
|
-
settings.hooks.Stop = [{ matcher: '', hooks: [] }];
|
|
714
|
-
} else if (!settings.hooks.Stop[0].hooks) {
|
|
715
|
-
settings.hooks.Stop[0].hooks = [];
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Check if this script is already added
|
|
719
|
-
const hasHook = settings.hooks.Stop[0].hooks.some(h => h.command?.includes(config.script));
|
|
720
|
-
|
|
721
|
-
if (!hasHook) {
|
|
722
|
-
settings.hooks.Stop[0].hooks.push({ type: 'command', command });
|
|
723
|
-
success(`Stop hook added (${config.script})`);
|
|
724
|
-
} else {
|
|
725
|
-
info(`${feature} already enabled`);
|
|
726
|
-
}
|
|
727
|
-
} else {
|
|
728
|
-
// Other hooks (SessionStart, PreCompact) replace entirely
|
|
729
|
-
settings.hooks[config.hook] = [
|
|
730
|
-
{
|
|
731
|
-
matcher: '',
|
|
732
|
-
hooks: [{ type: 'command', command }],
|
|
733
|
-
},
|
|
734
|
-
];
|
|
735
|
-
success(`${config.hook} hook enabled (${config.script})`);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Handle archival
|
|
740
|
-
if (feature === 'archival') {
|
|
741
|
-
const days = options.archivalDays || 30;
|
|
742
|
-
const scriptPath = getScriptPath('archive-completed-stories.sh');
|
|
743
|
-
|
|
744
|
-
if (!scriptExists('archive-completed-stories.sh')) {
|
|
745
|
-
error(`Script not found: ${scriptPath}`);
|
|
746
|
-
info('Run "npx agileflow update" to reinstall scripts');
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Use absolute path so hooks work from any subdirectory
|
|
751
|
-
const absoluteScriptPath = path.join(process.cwd(), scriptPath);
|
|
752
|
-
if (settings.hooks.SessionStart?.[0]?.hooks) {
|
|
753
|
-
const hasArchival = settings.hooks.SessionStart[0].hooks.some(h =>
|
|
754
|
-
h.command?.includes('archive-completed-stories')
|
|
755
|
-
);
|
|
756
|
-
if (!hasArchival) {
|
|
757
|
-
settings.hooks.SessionStart[0].hooks.push({
|
|
758
|
-
type: 'command',
|
|
759
|
-
command: `bash ${absoluteScriptPath} --quiet`,
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// Update metadata
|
|
765
|
-
updateMetadata({ archival: { enabled: true, threshold_days: days } });
|
|
766
|
-
success(`Archival enabled (${days} days)`);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Handle statusLine
|
|
770
|
-
if (feature === 'statusline') {
|
|
771
|
-
const scriptPath = getScriptPath('agileflow-statusline.sh');
|
|
772
|
-
|
|
773
|
-
if (!scriptExists('agileflow-statusline.sh')) {
|
|
774
|
-
error(`Script not found: ${scriptPath}`);
|
|
775
|
-
info('Run "npx agileflow update" to reinstall scripts');
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Use absolute path so hooks work from any subdirectory
|
|
780
|
-
const absoluteScriptPath = path.join(process.cwd(), scriptPath);
|
|
781
|
-
settings.statusLine = {
|
|
782
|
-
type: 'command',
|
|
783
|
-
command: `bash ${absoluteScriptPath}`,
|
|
784
|
-
padding: 0,
|
|
785
|
-
};
|
|
786
|
-
success('Status line enabled');
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Handle autoupdate (metadata only, no hooks needed)
|
|
790
|
-
if (feature === 'autoupdate') {
|
|
791
|
-
updateMetadata({
|
|
792
|
-
updates: {
|
|
793
|
-
autoUpdate: true,
|
|
794
|
-
showChangelog: true,
|
|
795
|
-
},
|
|
796
|
-
});
|
|
797
|
-
success('Auto-update enabled');
|
|
798
|
-
info('AgileFlow will check for updates every session and update automatically');
|
|
799
|
-
return true; // Skip settings.json write for this feature
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// Handle askuserquestion (metadata only, no hooks needed)
|
|
803
|
-
if (feature === 'askuserquestion') {
|
|
804
|
-
const mode = options.mode || 'all';
|
|
805
|
-
updateMetadata({
|
|
806
|
-
features: {
|
|
807
|
-
askUserQuestion: {
|
|
808
|
-
enabled: true,
|
|
809
|
-
mode: mode,
|
|
810
|
-
version: VERSION,
|
|
811
|
-
at: new Date().toISOString(),
|
|
812
|
-
},
|
|
813
|
-
},
|
|
814
|
-
});
|
|
815
|
-
success(`AskUserQuestion enabled (mode: ${mode})`);
|
|
816
|
-
info('All commands will end with AskUserQuestion tool for guided interaction');
|
|
817
|
-
return true; // Skip settings.json write for this feature
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// Handle damage control (PreToolUse hooks)
|
|
821
|
-
if (feature === 'damagecontrol') {
|
|
822
|
-
const level = options.protectionLevel || 'standard';
|
|
823
|
-
|
|
824
|
-
// Verify all required scripts exist
|
|
825
|
-
const requiredScripts = [
|
|
826
|
-
'damage-control-bash.js',
|
|
827
|
-
'damage-control-edit.js',
|
|
828
|
-
'damage-control-write.js',
|
|
829
|
-
];
|
|
830
|
-
for (const script of requiredScripts) {
|
|
831
|
-
if (!scriptExists(script)) {
|
|
832
|
-
error(`Script not found: ${getScriptPath(script)}`);
|
|
833
|
-
info('Run "npx agileflow update" to reinstall scripts');
|
|
834
|
-
return false;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Deploy patterns file if not exists
|
|
839
|
-
const patternsDir = path.join(process.cwd(), '.agileflow', 'config');
|
|
840
|
-
const patternsDest = path.join(patternsDir, 'damage-control-patterns.yaml');
|
|
841
|
-
if (!fs.existsSync(patternsDest)) {
|
|
842
|
-
ensureDir(patternsDir);
|
|
843
|
-
// Try to copy from templates
|
|
844
|
-
const templatePath = path.join(
|
|
845
|
-
process.cwd(),
|
|
846
|
-
'.agileflow',
|
|
847
|
-
'templates',
|
|
848
|
-
'damage-control-patterns.yaml'
|
|
849
|
-
);
|
|
850
|
-
if (fs.existsSync(templatePath)) {
|
|
851
|
-
fs.copyFileSync(templatePath, patternsDest);
|
|
852
|
-
success('Deployed damage control patterns');
|
|
853
|
-
} else {
|
|
854
|
-
warn('No patterns template found - hooks will use defaults');
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// Initialize PreToolUse array if not exists
|
|
859
|
-
if (!settings.hooks.PreToolUse) {
|
|
860
|
-
settings.hooks.PreToolUse = [];
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// Helper to add or update a PreToolUse hook
|
|
864
|
-
const addPreToolUseHook = (matcher, scriptName) => {
|
|
865
|
-
const scriptPath = path.join(process.cwd(), '.agileflow', 'scripts', scriptName);
|
|
866
|
-
|
|
867
|
-
// Remove existing hook for this matcher if present
|
|
868
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => h.matcher !== matcher);
|
|
869
|
-
|
|
870
|
-
// Add new hook
|
|
871
|
-
settings.hooks.PreToolUse.push({
|
|
872
|
-
matcher,
|
|
873
|
-
hooks: [{ type: 'command', command: `node ${scriptPath}`, timeout: 5 }],
|
|
874
|
-
});
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
// Add hooks for Bash, Edit, Write tools
|
|
878
|
-
addPreToolUseHook('Bash', 'damage-control-bash.js');
|
|
879
|
-
addPreToolUseHook('Edit', 'damage-control-edit.js');
|
|
880
|
-
addPreToolUseHook('Write', 'damage-control-write.js');
|
|
881
|
-
|
|
882
|
-
success('Damage control PreToolUse hooks enabled');
|
|
883
|
-
|
|
884
|
-
// Update metadata with protection level
|
|
885
|
-
updateMetadata({
|
|
886
|
-
features: {
|
|
887
|
-
damagecontrol: {
|
|
888
|
-
enabled: true,
|
|
889
|
-
protectionLevel: level,
|
|
890
|
-
version: VERSION,
|
|
891
|
-
at: new Date().toISOString(),
|
|
892
|
-
},
|
|
893
|
-
},
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
writeJSON('.claude/settings.json', settings);
|
|
897
|
-
updateGitignore();
|
|
898
|
-
|
|
899
|
-
return true;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
writeJSON('.claude/settings.json', settings);
|
|
903
|
-
updateMetadata({
|
|
904
|
-
features: { [feature]: { enabled: true, version: VERSION, at: new Date().toISOString() } },
|
|
905
|
-
});
|
|
906
|
-
updateGitignore();
|
|
907
|
-
|
|
908
|
-
return true;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
function disableFeature(feature) {
|
|
912
|
-
const config = FEATURES[feature];
|
|
913
|
-
if (!config) {
|
|
914
|
-
error(`Unknown feature: ${feature}`);
|
|
915
|
-
return false;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (!fs.existsSync('.claude/settings.json')) {
|
|
919
|
-
info(`${feature} already disabled (no settings file)`);
|
|
920
|
-
return true;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
const settings = readJSON('.claude/settings.json');
|
|
924
|
-
if (!settings) return false;
|
|
925
|
-
|
|
926
|
-
// Disable hook
|
|
927
|
-
if (config.hook && settings.hooks?.[config.hook]) {
|
|
928
|
-
if (config.hook === 'Stop') {
|
|
929
|
-
// Stop hooks stack - remove only this script, not the entire hook
|
|
930
|
-
if (settings.hooks.Stop?.[0]?.hooks) {
|
|
931
|
-
const before = settings.hooks.Stop[0].hooks.length;
|
|
932
|
-
settings.hooks.Stop[0].hooks = settings.hooks.Stop[0].hooks.filter(
|
|
933
|
-
h => !h.command?.includes(config.script)
|
|
934
|
-
);
|
|
935
|
-
const after = settings.hooks.Stop[0].hooks.length;
|
|
936
|
-
|
|
937
|
-
if (before > after) {
|
|
938
|
-
success(`Stop hook removed (${config.script})`);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// If no more Stop hooks, remove the entire Stop hook
|
|
942
|
-
if (settings.hooks.Stop[0].hooks.length === 0) {
|
|
943
|
-
delete settings.hooks.Stop;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
} else {
|
|
947
|
-
delete settings.hooks[config.hook];
|
|
948
|
-
success(`${config.hook} hook disabled`);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Disable archival
|
|
953
|
-
if (feature === 'archival') {
|
|
954
|
-
// Remove from SessionStart
|
|
955
|
-
if (settings.hooks?.SessionStart?.[0]?.hooks) {
|
|
956
|
-
settings.hooks.SessionStart[0].hooks = settings.hooks.SessionStart[0].hooks.filter(
|
|
957
|
-
h => !h.command?.includes('archive-completed-stories')
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
updateMetadata({ archival: { enabled: false } });
|
|
961
|
-
success('Archival disabled');
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// Disable statusLine
|
|
965
|
-
if (feature === 'statusline' && settings.statusLine) {
|
|
966
|
-
delete settings.statusLine;
|
|
967
|
-
success('Status line disabled');
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// Disable autoupdate
|
|
971
|
-
if (feature === 'autoupdate') {
|
|
972
|
-
updateMetadata({
|
|
973
|
-
updates: {
|
|
974
|
-
autoUpdate: false,
|
|
975
|
-
},
|
|
976
|
-
});
|
|
977
|
-
success('Auto-update disabled');
|
|
978
|
-
return true; // Skip settings.json write for this feature
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Disable askuserquestion
|
|
982
|
-
if (feature === 'askuserquestion') {
|
|
983
|
-
updateMetadata({
|
|
984
|
-
features: {
|
|
985
|
-
askUserQuestion: {
|
|
986
|
-
enabled: false,
|
|
987
|
-
version: VERSION,
|
|
988
|
-
at: new Date().toISOString(),
|
|
989
|
-
},
|
|
990
|
-
},
|
|
991
|
-
});
|
|
992
|
-
success('AskUserQuestion disabled');
|
|
993
|
-
info('Commands will end with natural text questions instead of AskUserQuestion tool');
|
|
994
|
-
return true; // Skip settings.json write for this feature
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// Disable damage control (PreToolUse hooks)
|
|
998
|
-
if (feature === 'damagecontrol') {
|
|
999
|
-
if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
|
|
1000
|
-
const before = settings.hooks.PreToolUse.length;
|
|
1001
|
-
|
|
1002
|
-
// Remove damage-control hooks (Bash, Edit, Write matchers with damage-control scripts)
|
|
1003
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => {
|
|
1004
|
-
const isDamageControlHook = h.hooks?.some(hk => hk.command?.includes('damage-control'));
|
|
1005
|
-
return !isDamageControlHook;
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
const after = settings.hooks.PreToolUse.length;
|
|
1009
|
-
|
|
1010
|
-
if (before > after) {
|
|
1011
|
-
success(`Removed ${before - after} damage control PreToolUse hook(s)`);
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// If no more PreToolUse hooks, remove the entire array
|
|
1015
|
-
if (settings.hooks.PreToolUse.length === 0) {
|
|
1016
|
-
delete settings.hooks.PreToolUse;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Update metadata
|
|
1021
|
-
updateMetadata({
|
|
1022
|
-
features: {
|
|
1023
|
-
damagecontrol: {
|
|
1024
|
-
enabled: false,
|
|
1025
|
-
version: VERSION,
|
|
1026
|
-
at: new Date().toISOString(),
|
|
1027
|
-
},
|
|
1028
|
-
},
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
writeJSON('.claude/settings.json', settings);
|
|
1032
|
-
success('Damage control disabled');
|
|
1033
|
-
return true;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
writeJSON('.claude/settings.json', settings);
|
|
1037
|
-
updateMetadata({
|
|
1038
|
-
features: { [feature]: { enabled: false, version: VERSION, at: new Date().toISOString() } },
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
return true;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// ============================================================================
|
|
1045
|
-
// METADATA
|
|
1046
|
-
// ============================================================================
|
|
1047
|
-
|
|
1048
|
-
function updateMetadata(updates) {
|
|
1049
|
-
const metaPath = 'docs/00-meta/agileflow-metadata.json';
|
|
1050
|
-
|
|
1051
|
-
if (!fs.existsSync(metaPath)) {
|
|
1052
|
-
ensureDir('docs/00-meta');
|
|
1053
|
-
writeJSON(metaPath, { version: VERSION, created: new Date().toISOString() });
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
const meta = readJSON(metaPath) || {};
|
|
1057
|
-
|
|
1058
|
-
// Deep merge
|
|
1059
|
-
if (updates.archival) {
|
|
1060
|
-
meta.archival = { ...meta.archival, ...updates.archival };
|
|
1061
|
-
}
|
|
1062
|
-
if (updates.features) {
|
|
1063
|
-
meta.features = meta.features || {};
|
|
1064
|
-
Object.entries(updates.features).forEach(([key, value]) => {
|
|
1065
|
-
meta.features[key] = { ...meta.features[key], ...value };
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
if (updates.updates) {
|
|
1069
|
-
meta.updates = { ...meta.updates, ...updates.updates };
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
meta.version = VERSION;
|
|
1073
|
-
meta.updated = new Date().toISOString();
|
|
1074
|
-
|
|
1075
|
-
writeJSON(metaPath, meta);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
function updateGitignore() {
|
|
1079
|
-
const entries = [
|
|
1080
|
-
'.claude/settings.local.json',
|
|
1081
|
-
'.claude/activity.log',
|
|
1082
|
-
'.claude/context.log',
|
|
1083
|
-
'.claude/hook.log',
|
|
1084
|
-
'.claude/prompt-log.txt',
|
|
1085
|
-
'.claude/session.log',
|
|
1086
|
-
];
|
|
1087
|
-
|
|
1088
|
-
let content = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
|
|
1089
|
-
let added = false;
|
|
1090
|
-
|
|
1091
|
-
entries.forEach(entry => {
|
|
1092
|
-
if (!content.includes(entry)) {
|
|
1093
|
-
content += `\n${entry}`;
|
|
1094
|
-
added = true;
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
if (added) {
|
|
1099
|
-
fs.writeFileSync('.gitignore', content.trimEnd() + '\n');
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// ============================================================================
|
|
1104
|
-
// STATUSLINE COMPONENTS
|
|
1105
|
-
// ============================================================================
|
|
1106
|
-
|
|
1107
|
-
function setStatuslineComponents(enableComponents = [], disableComponents = []) {
|
|
1108
|
-
const metaPath = 'docs/00-meta/agileflow-metadata.json';
|
|
1109
|
-
|
|
1110
|
-
if (!fs.existsSync(metaPath)) {
|
|
1111
|
-
warn('No metadata file found - run with --enable=statusline first');
|
|
1112
|
-
return false;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
const meta = readJSON(metaPath);
|
|
1116
|
-
if (!meta) {
|
|
1117
|
-
error('Cannot parse metadata file');
|
|
1118
|
-
return false;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// Ensure statusline.components structure exists
|
|
1122
|
-
meta.features = meta.features || {};
|
|
1123
|
-
meta.features.statusline = meta.features.statusline || {};
|
|
1124
|
-
meta.features.statusline.components = meta.features.statusline.components || {};
|
|
1125
|
-
|
|
1126
|
-
// Set defaults for any missing components
|
|
1127
|
-
STATUSLINE_COMPONENTS.forEach(comp => {
|
|
1128
|
-
if (meta.features.statusline.components[comp] === undefined) {
|
|
1129
|
-
meta.features.statusline.components[comp] = true;
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
// Enable specified components
|
|
1134
|
-
enableComponents.forEach(comp => {
|
|
1135
|
-
if (STATUSLINE_COMPONENTS.includes(comp)) {
|
|
1136
|
-
meta.features.statusline.components[comp] = true;
|
|
1137
|
-
success(`Statusline component enabled: ${comp}`);
|
|
1138
|
-
} else {
|
|
1139
|
-
warn(`Unknown component: ${comp} (available: ${STATUSLINE_COMPONENTS.join(', ')})`);
|
|
1140
|
-
}
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
// Disable specified components
|
|
1144
|
-
disableComponents.forEach(comp => {
|
|
1145
|
-
if (STATUSLINE_COMPONENTS.includes(comp)) {
|
|
1146
|
-
meta.features.statusline.components[comp] = false;
|
|
1147
|
-
success(`Statusline component disabled: ${comp}`);
|
|
1148
|
-
} else {
|
|
1149
|
-
warn(`Unknown component: ${comp} (available: ${STATUSLINE_COMPONENTS.join(', ')})`);
|
|
1150
|
-
}
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
meta.updated = new Date().toISOString();
|
|
1154
|
-
writeJSON(metaPath, meta);
|
|
1155
|
-
|
|
1156
|
-
return true;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
function listStatuslineComponents() {
|
|
1160
|
-
const metaPath = 'docs/00-meta/agileflow-metadata.json';
|
|
1161
|
-
|
|
1162
|
-
header('📊 Statusline Components');
|
|
1163
|
-
|
|
1164
|
-
if (!fs.existsSync(metaPath)) {
|
|
1165
|
-
log(' No configuration found (defaults: all enabled)', c.dim);
|
|
1166
|
-
STATUSLINE_COMPONENTS.forEach(comp => {
|
|
1167
|
-
log(` ✅ ${comp}: enabled (default)`, c.green);
|
|
1168
|
-
});
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
const meta = readJSON(metaPath);
|
|
1173
|
-
const components = meta?.features?.statusline?.components || {};
|
|
1174
|
-
|
|
1175
|
-
STATUSLINE_COMPONENTS.forEach(comp => {
|
|
1176
|
-
const enabled = components[comp] !== false; // default true
|
|
1177
|
-
const icon = enabled ? '✅' : '❌';
|
|
1178
|
-
const color = enabled ? c.green : c.dim;
|
|
1179
|
-
log(` ${icon} ${comp}: ${enabled ? 'enabled' : 'disabled'}`, color);
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
log('\nTo toggle: --show=<component> or --hide=<component>', c.dim);
|
|
1183
|
-
log(`Components: ${STATUSLINE_COMPONENTS.join(', ')}`, c.dim);
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// ============================================================================
|
|
1187
|
-
// REPAIR & DIAGNOSTICS
|
|
1188
|
-
// ============================================================================
|
|
1189
|
-
|
|
1190
|
-
const crypto = require('crypto');
|
|
1191
|
-
|
|
1192
|
-
/**
|
|
1193
|
-
* Calculate SHA256 hash of a file
|
|
1194
|
-
*/
|
|
1195
|
-
function sha256(data) {
|
|
1196
|
-
return crypto.createHash('sha256').update(data).digest('hex');
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Get the source scripts directory (from npm package)
|
|
1201
|
-
*/
|
|
1202
|
-
function getSourceScriptsDir() {
|
|
1203
|
-
// When running from installed package, __dirname is .agileflow/scripts
|
|
1204
|
-
// The source is the same directory since it was copied during install
|
|
1205
|
-
// But for repair, we need the npm package source
|
|
1206
|
-
|
|
1207
|
-
// Try to find the npm package (when run via npx or global install)
|
|
1208
|
-
const possiblePaths = [
|
|
1209
|
-
path.join(__dirname, '..', '..', 'scripts'), // npm package structure
|
|
1210
|
-
path.join(__dirname), // Same directory (for development)
|
|
1211
|
-
];
|
|
1212
|
-
|
|
1213
|
-
for (const p of possiblePaths) {
|
|
1214
|
-
if (fs.existsSync(p) && fs.existsSync(path.join(p, 'agileflow-welcome.js'))) {
|
|
1215
|
-
return p;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
return null;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
/**
|
|
1223
|
-
* List all scripts with their status (present/missing/modified)
|
|
1224
|
-
*/
|
|
1225
|
-
function listScripts() {
|
|
1226
|
-
header('📋 Installed Scripts');
|
|
1227
|
-
|
|
1228
|
-
const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
|
|
1229
|
-
const fileIndexPath = path.join(process.cwd(), '.agileflow', '_cfg', 'files.json');
|
|
1230
|
-
const fileIndex = readJSON(fileIndexPath);
|
|
1231
|
-
|
|
1232
|
-
let missing = 0;
|
|
1233
|
-
let modified = 0;
|
|
1234
|
-
let present = 0;
|
|
1235
|
-
|
|
1236
|
-
Object.entries(ALL_SCRIPTS).forEach(([script, info]) => {
|
|
1237
|
-
const scriptPath = path.join(scriptsDir, script);
|
|
1238
|
-
const exists = fs.existsSync(scriptPath);
|
|
1239
|
-
|
|
1240
|
-
// Check if modified (compare to file index hash)
|
|
1241
|
-
let isModified = false;
|
|
1242
|
-
if (exists && fileIndex?.files?.[`scripts/${script}`]) {
|
|
1243
|
-
try {
|
|
1244
|
-
const currentHash = sha256(fs.readFileSync(scriptPath));
|
|
1245
|
-
const indexHash = fileIndex.files[`scripts/${script}`].sha256;
|
|
1246
|
-
isModified = currentHash !== indexHash;
|
|
1247
|
-
} catch {}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// Print status
|
|
1251
|
-
if (!exists) {
|
|
1252
|
-
log(` ❌ ${script}: MISSING`, c.red);
|
|
1253
|
-
if (info.usedBy) log(` └─ Used by: ${info.usedBy.join(', ')}`, c.dim);
|
|
1254
|
-
if (info.feature) log(` └─ Feature: ${info.feature}`, c.dim);
|
|
1255
|
-
missing++;
|
|
1256
|
-
} else if (isModified) {
|
|
1257
|
-
log(` ⚠️ ${script}: modified (local changes)`, c.yellow);
|
|
1258
|
-
modified++;
|
|
1259
|
-
} else {
|
|
1260
|
-
log(` ✅ ${script}: present`, c.green);
|
|
1261
|
-
present++;
|
|
1262
|
-
}
|
|
1263
|
-
});
|
|
1264
|
-
|
|
1265
|
-
// Summary
|
|
1266
|
-
log('');
|
|
1267
|
-
log(`Summary: ${present} present, ${modified} modified, ${missing} missing`, c.dim);
|
|
1268
|
-
|
|
1269
|
-
if (missing > 0) {
|
|
1270
|
-
log('\n💡 Run with --repair to restore missing scripts', c.yellow);
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* Show version information
|
|
1276
|
-
*/
|
|
1277
|
-
function showVersionInfo() {
|
|
1278
|
-
header('📊 Version Information');
|
|
1279
|
-
|
|
1280
|
-
const meta = readJSON('docs/00-meta/agileflow-metadata.json') || {};
|
|
1281
|
-
const manifest = readJSON('.agileflow/_cfg/manifest.yaml');
|
|
1282
|
-
|
|
1283
|
-
const installedVersion = meta.version || 'unknown';
|
|
1284
|
-
|
|
1285
|
-
log(`Installed: v${installedVersion}`);
|
|
1286
|
-
log(`CLI: v${VERSION}`);
|
|
1287
|
-
|
|
1288
|
-
// Check npm for latest
|
|
1289
|
-
let latestVersion = null;
|
|
1290
|
-
try {
|
|
1291
|
-
latestVersion = execSync('npm view agileflow version 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
1292
|
-
log(`Latest: v${latestVersion}`);
|
|
1293
|
-
|
|
1294
|
-
if (installedVersion !== 'unknown' && latestVersion && installedVersion !== latestVersion) {
|
|
1295
|
-
const installed = installedVersion.split('.').map(Number);
|
|
1296
|
-
const latest = latestVersion.split('.').map(Number);
|
|
1297
|
-
|
|
1298
|
-
if (
|
|
1299
|
-
latest[0] > installed[0] ||
|
|
1300
|
-
(latest[0] === installed[0] && latest[1] > installed[1]) ||
|
|
1301
|
-
(latest[0] === installed[0] && latest[1] === installed[1] && latest[2] > installed[2])
|
|
1302
|
-
) {
|
|
1303
|
-
log('\n🔄 Update available! Run: npx agileflow update', c.yellow);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
} catch {
|
|
1307
|
-
log('Latest: (could not check npm)', c.dim);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
// Show per-feature versions
|
|
1311
|
-
if (meta.features && Object.keys(meta.features).length > 0) {
|
|
1312
|
-
header('Feature Versions:');
|
|
1313
|
-
Object.entries(meta.features).forEach(([feature, data]) => {
|
|
1314
|
-
if (!data) return;
|
|
1315
|
-
const featureVersion = data.version || 'unknown';
|
|
1316
|
-
const enabled = data.enabled !== false;
|
|
1317
|
-
const outdated = featureVersion !== VERSION && enabled;
|
|
1318
|
-
|
|
1319
|
-
let icon = '❌';
|
|
1320
|
-
let color = c.dim;
|
|
1321
|
-
let statusText = `v${featureVersion}`;
|
|
1322
|
-
|
|
1323
|
-
if (!enabled) {
|
|
1324
|
-
statusText = 'disabled';
|
|
1325
|
-
} else if (outdated) {
|
|
1326
|
-
icon = '🔄';
|
|
1327
|
-
color = c.yellow;
|
|
1328
|
-
statusText = `v${featureVersion} → v${VERSION}`;
|
|
1329
|
-
} else {
|
|
1330
|
-
icon = '✅';
|
|
1331
|
-
color = c.green;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
log(` ${icon} ${feature}: ${statusText}`, color);
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// Show installation metadata
|
|
1339
|
-
if (meta.created || meta.updated) {
|
|
1340
|
-
header('Installation:');
|
|
1341
|
-
if (meta.created) log(` Created: ${new Date(meta.created).toLocaleDateString()}`, c.dim);
|
|
1342
|
-
if (meta.updated) log(` Updated: ${new Date(meta.updated).toLocaleDateString()}`, c.dim);
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/**
|
|
1347
|
-
* Repair missing or corrupted scripts
|
|
1348
|
-
*/
|
|
1349
|
-
function repairScripts(targetFeature = null) {
|
|
1350
|
-
header('🔧 Repairing Scripts...');
|
|
1351
|
-
|
|
1352
|
-
const scriptsDir = path.join(process.cwd(), '.agileflow', 'scripts');
|
|
1353
|
-
const sourceDir = getSourceScriptsDir();
|
|
1354
|
-
|
|
1355
|
-
if (!sourceDir) {
|
|
1356
|
-
warn('Could not find source scripts directory');
|
|
1357
|
-
info('Try running: npx agileflow@latest update');
|
|
1358
|
-
return false;
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
let repaired = 0;
|
|
1362
|
-
let errors = 0;
|
|
1363
|
-
let skipped = 0;
|
|
1364
|
-
|
|
1365
|
-
// Determine which scripts to check
|
|
1366
|
-
const scriptsToCheck = targetFeature
|
|
1367
|
-
? Object.entries(ALL_SCRIPTS).filter(([_, info]) => info.feature === targetFeature)
|
|
1368
|
-
: Object.entries(ALL_SCRIPTS);
|
|
1369
|
-
|
|
1370
|
-
if (scriptsToCheck.length === 0 && targetFeature) {
|
|
1371
|
-
error(`Unknown feature: ${targetFeature}`);
|
|
1372
|
-
log(`Available features: ${Object.keys(FEATURES).join(', ')}`, c.dim);
|
|
1373
|
-
return false;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// Ensure scripts directory exists
|
|
1377
|
-
ensureDir(scriptsDir);
|
|
1378
|
-
|
|
1379
|
-
for (const [script, info] of scriptsToCheck) {
|
|
1380
|
-
const destPath = path.join(scriptsDir, script);
|
|
1381
|
-
const srcPath = path.join(sourceDir, script);
|
|
1382
|
-
|
|
1383
|
-
if (!fs.existsSync(destPath)) {
|
|
1384
|
-
// Script is missing - reinstall from source
|
|
1385
|
-
if (fs.existsSync(srcPath)) {
|
|
1386
|
-
try {
|
|
1387
|
-
fs.copyFileSync(srcPath, destPath);
|
|
1388
|
-
// Make executable
|
|
1389
|
-
try {
|
|
1390
|
-
fs.chmodSync(destPath, 0o755);
|
|
1391
|
-
} catch {}
|
|
1392
|
-
success(`Restored ${script}`);
|
|
1393
|
-
repaired++;
|
|
1394
|
-
} catch (err) {
|
|
1395
|
-
error(`Failed to restore ${script}: ${err.message}`);
|
|
1396
|
-
errors++;
|
|
1397
|
-
}
|
|
1398
|
-
} else {
|
|
1399
|
-
warn(`Source not found for ${script}`);
|
|
1400
|
-
errors++;
|
|
1401
|
-
}
|
|
1402
|
-
} else {
|
|
1403
|
-
skipped++;
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Summary
|
|
1408
|
-
log('');
|
|
1409
|
-
if (repaired === 0 && errors === 0) {
|
|
1410
|
-
info('All scripts present - nothing to repair');
|
|
1411
|
-
} else {
|
|
1412
|
-
log(`Repaired: ${repaired}, Errors: ${errors}, Skipped: ${skipped}`, c.dim);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
if (errors > 0) {
|
|
1416
|
-
log('\n💡 For comprehensive repair, run: npx agileflow update --force', c.yellow);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
return repaired > 0;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// ============================================================================
|
|
1423
|
-
// PROFILES
|
|
1424
|
-
// ============================================================================
|
|
1425
|
-
|
|
1426
|
-
function applyProfile(profileName, options = {}) {
|
|
1427
|
-
const profile = PROFILES[profileName];
|
|
1428
|
-
if (!profile) {
|
|
1429
|
-
error(`Unknown profile: ${profileName}`);
|
|
1430
|
-
log('Available: ' + Object.keys(PROFILES).join(', '));
|
|
1431
|
-
return false;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
header(`🚀 Applying "${profileName}" profile`);
|
|
1435
|
-
log(profile.description, c.dim);
|
|
1436
|
-
|
|
1437
|
-
// Enable features
|
|
1438
|
-
if (profile.enable) {
|
|
1439
|
-
profile.enable.forEach(f =>
|
|
1440
|
-
enableFeature(f, { archivalDays: profile.archivalDays || options.archivalDays })
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// Disable features
|
|
1445
|
-
if (profile.disable) {
|
|
1446
|
-
profile.disable.forEach(f => disableFeature(f));
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
return true;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
85
|
// ============================================================================
|
|
1453
86
|
// SUMMARY
|
|
1454
87
|
// ============================================================================
|
|
1455
88
|
|
|
1456
89
|
function printSummary(actions) {
|
|
1457
|
-
header('
|
|
90
|
+
header('Configuration Complete!');
|
|
1458
91
|
|
|
1459
92
|
if (actions.enabled?.length > 0) {
|
|
1460
93
|
log('\nEnabled:', c.green);
|
|
1461
|
-
actions.enabled.forEach(f => log(`
|
|
94
|
+
actions.enabled.forEach(f => log(` ${f}`, c.green));
|
|
1462
95
|
}
|
|
1463
96
|
|
|
1464
97
|
if (actions.disabled?.length > 0) {
|
|
1465
98
|
log('\nDisabled:', c.dim);
|
|
1466
|
-
actions.disabled.forEach(f => log(`
|
|
99
|
+
actions.disabled.forEach(f => log(` ${f}`, c.dim));
|
|
1467
100
|
}
|
|
1468
101
|
|
|
1469
102
|
if (actions.migrated) {
|
|
1470
103
|
log('\nMigrated: Fixed format issues', c.yellow);
|
|
1471
104
|
}
|
|
1472
105
|
|
|
1473
|
-
log('\n' + '
|
|
1474
|
-
log('
|
|
106
|
+
log('\n' + '='.repeat(55), c.red);
|
|
107
|
+
log(' RESTART CLAUDE CODE NOW!', c.red + c.bold);
|
|
1475
108
|
log(' Quit completely, wait 5 seconds, restart', c.red);
|
|
1476
|
-
log('
|
|
109
|
+
log('='.repeat(55), c.red);
|
|
1477
110
|
}
|
|
1478
111
|
|
|
1479
112
|
// ============================================================================
|
|
@@ -1499,10 +132,6 @@ ${c.cyan}Feature Control:${c.reset}
|
|
|
1499
132
|
|
|
1500
133
|
Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion
|
|
1501
134
|
|
|
1502
|
-
Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
|
|
1503
|
-
Damage control (damagecontrol) uses PreToolUse hooks to block dangerous commands
|
|
1504
|
-
AskUserQuestion (askuserquestion) makes all commands end with guided options
|
|
1505
|
-
|
|
1506
135
|
${c.cyan}Statusline Components:${c.reset}
|
|
1507
136
|
--show=<list> Show statusline components (comma-separated)
|
|
1508
137
|
--hide=<list> Hide statusline components (comma-separated)
|
|
@@ -1521,9 +150,9 @@ ${c.cyan}Maintenance:${c.reset}
|
|
|
1521
150
|
|
|
1522
151
|
${c.cyan}Repair & Diagnostics:${c.reset}
|
|
1523
152
|
--repair Check for and restore missing scripts
|
|
1524
|
-
--repair=<feature> Repair scripts for a specific feature
|
|
153
|
+
--repair=<feature> Repair scripts for a specific feature
|
|
1525
154
|
--version Show installed vs latest version info
|
|
1526
|
-
--list-scripts List all scripts with their status
|
|
155
|
+
--list-scripts List all scripts with their status
|
|
1527
156
|
|
|
1528
157
|
${c.cyan}Examples:${c.reset}
|
|
1529
158
|
# Quick setup with all features
|
|
@@ -1538,41 +167,11 @@ ${c.cyan}Examples:${c.reset}
|
|
|
1538
167
|
# Show only agileflow branding and context in statusline
|
|
1539
168
|
node .agileflow/scripts/agileflow-configure.js --hide=model,story,epic,wip,cost,git
|
|
1540
169
|
|
|
1541
|
-
# Re-enable git branch in statusline
|
|
1542
|
-
node .agileflow/scripts/agileflow-configure.js --show=git
|
|
1543
|
-
|
|
1544
|
-
# List component status
|
|
1545
|
-
node .agileflow/scripts/agileflow-configure.js --components
|
|
1546
|
-
|
|
1547
170
|
# Fix format issues
|
|
1548
171
|
node .agileflow/scripts/agileflow-configure.js --migrate
|
|
1549
172
|
|
|
1550
173
|
# Check current status
|
|
1551
174
|
node .agileflow/scripts/agileflow-configure.js --detect
|
|
1552
|
-
|
|
1553
|
-
# Upgrade outdated scripts to latest version
|
|
1554
|
-
node .agileflow/scripts/agileflow-configure.js --upgrade
|
|
1555
|
-
|
|
1556
|
-
# List all scripts with status
|
|
1557
|
-
node .agileflow/scripts/agileflow-configure.js --list-scripts
|
|
1558
|
-
|
|
1559
|
-
# Show version information
|
|
1560
|
-
node .agileflow/scripts/agileflow-configure.js --version
|
|
1561
|
-
|
|
1562
|
-
# Repair missing scripts
|
|
1563
|
-
node .agileflow/scripts/agileflow-configure.js --repair
|
|
1564
|
-
|
|
1565
|
-
# Repair scripts for a specific feature
|
|
1566
|
-
node .agileflow/scripts/agileflow-configure.js --repair=statusline
|
|
1567
|
-
|
|
1568
|
-
# Enable damage control (PreToolUse hooks to block dangerous commands)
|
|
1569
|
-
node .agileflow/scripts/agileflow-configure.js --enable=damagecontrol
|
|
1570
|
-
|
|
1571
|
-
# Enable AskUserQuestion (all commands end with guided options)
|
|
1572
|
-
node .agileflow/scripts/agileflow-configure.js --enable=askuserquestion
|
|
1573
|
-
|
|
1574
|
-
# Disable AskUserQuestion (commands end with natural text questions)
|
|
1575
|
-
node .agileflow/scripts/agileflow-configure.js --disable=askuserquestion
|
|
1576
175
|
`);
|
|
1577
176
|
}
|
|
1578
177
|
|
|
@@ -1637,31 +236,32 @@ function main() {
|
|
|
1637
236
|
else if (arg === '--list-scripts' || arg === '--scripts') listScriptsMode = true;
|
|
1638
237
|
});
|
|
1639
238
|
|
|
239
|
+
// Help mode
|
|
1640
240
|
if (help) {
|
|
1641
241
|
printHelp();
|
|
1642
242
|
return;
|
|
1643
243
|
}
|
|
1644
244
|
|
|
1645
|
-
// List scripts mode
|
|
245
|
+
// List scripts mode
|
|
1646
246
|
if (listScriptsMode) {
|
|
1647
247
|
listScripts();
|
|
1648
248
|
return;
|
|
1649
249
|
}
|
|
1650
250
|
|
|
1651
|
-
// Version info mode
|
|
251
|
+
// Version info mode
|
|
1652
252
|
if (showVersion) {
|
|
1653
|
-
showVersionInfo();
|
|
253
|
+
showVersionInfo(VERSION);
|
|
1654
254
|
return;
|
|
1655
255
|
}
|
|
1656
256
|
|
|
1657
|
-
// Repair mode
|
|
257
|
+
// Repair mode
|
|
1658
258
|
if (repair) {
|
|
1659
259
|
const needsRestart = repairScripts(repairFeature);
|
|
1660
260
|
if (needsRestart) {
|
|
1661
|
-
log('\n' + '
|
|
1662
|
-
log('
|
|
261
|
+
log('\n' + '='.repeat(55), c.red);
|
|
262
|
+
log(' RESTART CLAUDE CODE NOW!', c.red + c.bold);
|
|
1663
263
|
log(' Quit completely, wait 5 seconds, restart', c.red);
|
|
1664
|
-
log('
|
|
264
|
+
log('='.repeat(55), c.red);
|
|
1665
265
|
}
|
|
1666
266
|
return;
|
|
1667
267
|
}
|
|
@@ -1680,7 +280,7 @@ function main() {
|
|
|
1680
280
|
}
|
|
1681
281
|
|
|
1682
282
|
// Always detect first
|
|
1683
|
-
const status = detectConfig();
|
|
283
|
+
const status = detectConfig(VERSION);
|
|
1684
284
|
const { hasIssues, hasOutdated } = printStatus(status);
|
|
1685
285
|
|
|
1686
286
|
// Detect only mode
|
|
@@ -1690,7 +290,7 @@ function main() {
|
|
|
1690
290
|
|
|
1691
291
|
// Upgrade mode
|
|
1692
292
|
if (upgrade) {
|
|
1693
|
-
upgradeFeatures(status);
|
|
293
|
+
upgradeFeatures(status, VERSION);
|
|
1694
294
|
return;
|
|
1695
295
|
}
|
|
1696
296
|
|
|
@@ -1702,7 +302,7 @@ function main() {
|
|
|
1702
302
|
|
|
1703
303
|
// Auto-migrate if issues detected
|
|
1704
304
|
if (hasIssues && (profile || enable.length > 0)) {
|
|
1705
|
-
log('\n
|
|
305
|
+
log('\n Auto-migrating invalid formats...', c.yellow);
|
|
1706
306
|
migrateSettings();
|
|
1707
307
|
}
|
|
1708
308
|
|
|
@@ -1710,7 +310,7 @@ function main() {
|
|
|
1710
310
|
|
|
1711
311
|
// Apply profile
|
|
1712
312
|
if (profile) {
|
|
1713
|
-
applyProfile(profile, { archivalDays });
|
|
313
|
+
applyProfile(profile, { archivalDays }, VERSION);
|
|
1714
314
|
const p = PROFILES[profile];
|
|
1715
315
|
actions.enabled = p.enable || [];
|
|
1716
316
|
actions.disabled = p.disable || [];
|
|
@@ -1718,14 +318,14 @@ function main() {
|
|
|
1718
318
|
|
|
1719
319
|
// Enable specific features
|
|
1720
320
|
enable.forEach(f => {
|
|
1721
|
-
if (enableFeature(f, { archivalDays })) {
|
|
321
|
+
if (enableFeature(f, { archivalDays }, VERSION)) {
|
|
1722
322
|
actions.enabled.push(f);
|
|
1723
323
|
}
|
|
1724
324
|
});
|
|
1725
325
|
|
|
1726
326
|
// Disable specific features
|
|
1727
327
|
disable.forEach(f => {
|
|
1728
|
-
if (disableFeature(f)) {
|
|
328
|
+
if (disableFeature(f, VERSION)) {
|
|
1729
329
|
actions.disabled.push(f);
|
|
1730
330
|
}
|
|
1731
331
|
});
|