agileflow 2.89.2 → 2.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- 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/placeholder-registry.js +617 -0
- package/lib/session-registry.js +461 -0
- package/lib/smart-json-file.js +653 -0
- package/lib/table-formatter.js +504 -0
- package/lib/transient-status.js +374 -0
- package/lib/ui-manager.js +612 -0
- package/lib/validate-args.js +213 -0
- package/lib/validate-names.js +143 -0
- package/lib/validate-paths.js +434 -0
- package/lib/validate.js +38 -584
- package/package.json +4 -1
- package/scripts/agileflow-configure.js +40 -1440
- package/scripts/agileflow-welcome.js +2 -1
- package/scripts/check-update.js +16 -3
- 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/lib/sessionRegistry.js +682 -0
- package/scripts/obtain-context.js +417 -113
- package/scripts/ralph-loop.js +1 -1
- package/scripts/session-manager.js +77 -10
- package/scripts/tui/App.js +176 -0
- package/scripts/tui/index.js +75 -0
- package/scripts/tui/lib/crashRecovery.js +302 -0
- package/scripts/tui/lib/eventStream.js +316 -0
- package/scripts/tui/lib/keyboard.js +252 -0
- package/scripts/tui/lib/loopControl.js +371 -0
- package/scripts/tui/panels/OutputPanel.js +278 -0
- package/scripts/tui/panels/SessionPanel.js +178 -0
- package/scripts/tui/panels/TracePanel.js +333 -0
- package/src/core/commands/tui.md +91 -0
- package/tools/cli/commands/config.js +10 -33
- package/tools/cli/commands/doctor.js +48 -40
- package/tools/cli/commands/list.js +49 -37
- package/tools/cli/commands/status.js +13 -37
- package/tools/cli/commands/uninstall.js +12 -41
- package/tools/cli/installers/core/installer.js +75 -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/command-context.js +374 -0
- package/tools/cli/lib/config-manager.js +394 -0
- package/tools/cli/lib/content-injector.js +69 -16
- package/tools/cli/lib/ide-errors.js +163 -29
- package/tools/cli/lib/ide-registry.js +186 -0
- package/tools/cli/lib/npm-utils.js +16 -3
- package/tools/cli/lib/self-update.js +148 -0
- package/tools/cli/lib/validation-middleware.js +491 -0
|
@@ -298,7 +298,8 @@ function clearActiveCommands(rootDir, cache = null) {
|
|
|
298
298
|
const now = Date.now();
|
|
299
299
|
const secondsSincePrecompact = (now - precompactTime) / 1000;
|
|
300
300
|
|
|
301
|
-
if (secondsSincePrecompact < 600) {
|
|
301
|
+
if (secondsSincePrecompact < 600) {
|
|
302
|
+
// 10 minutes - compacts can take a while with background tasks
|
|
302
303
|
// This is a post-compact session start - preserve active commands
|
|
303
304
|
result.preserved = true;
|
|
304
305
|
// Capture command names for display (but don't clear)
|
package/scripts/check-update.js
CHANGED
|
@@ -112,6 +112,9 @@ async function fetchLatestVersion() {
|
|
|
112
112
|
headers: {
|
|
113
113
|
'User-Agent': 'agileflow-cli',
|
|
114
114
|
},
|
|
115
|
+
// Security: Explicitly enable TLS certificate validation
|
|
116
|
+
// Prevents MITM attacks on npm registry requests
|
|
117
|
+
rejectUnauthorized: true,
|
|
115
118
|
};
|
|
116
119
|
|
|
117
120
|
debugLog('Fetching from npm registry');
|
|
@@ -141,12 +144,22 @@ async function fetchLatestVersion() {
|
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
req.on('error', err => {
|
|
144
|
-
|
|
147
|
+
// Enhanced error logging with retry guidance
|
|
148
|
+
const errorInfo = {
|
|
149
|
+
error: err.message,
|
|
150
|
+
code: err.code,
|
|
151
|
+
suggestion: 'Check network connection. If error persists, try: npm cache clean --force',
|
|
152
|
+
};
|
|
153
|
+
if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
154
|
+
errorInfo.suggestion = 'TLS certificate error - check system time or update CA certificates';
|
|
155
|
+
}
|
|
156
|
+
debugLog('Network error', errorInfo);
|
|
145
157
|
resolve(null);
|
|
146
158
|
});
|
|
147
159
|
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
// 10 second timeout for registry requests
|
|
161
|
+
req.setTimeout(10000, () => {
|
|
162
|
+
debugLog('Request timeout (10s)', { suggestion: 'npm registry may be slow. Retry later.' });
|
|
150
163
|
req.destroy();
|
|
151
164
|
resolve(null);
|
|
152
165
|
});
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* configure-detect.js - Detection and validation for agileflow-configure
|
|
3
|
+
*
|
|
4
|
+
* Extracted from agileflow-configure.js (US-0094)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const { c, log, header, readJSON } = require('./configure-utils');
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// DETECTION
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect current AgileFlow configuration status
|
|
18
|
+
* @param {string} version - Current VERSION string
|
|
19
|
+
* @returns {object} Configuration status object
|
|
20
|
+
*/
|
|
21
|
+
function detectConfig(version) {
|
|
22
|
+
const status = {
|
|
23
|
+
git: { initialized: false, remote: null },
|
|
24
|
+
settingsExists: false,
|
|
25
|
+
settingsValid: true,
|
|
26
|
+
settingsIssues: [],
|
|
27
|
+
features: {
|
|
28
|
+
sessionstart: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
29
|
+
precompact: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
30
|
+
ralphloop: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
31
|
+
selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
32
|
+
archival: { enabled: false, threshold: null, version: null, outdated: false },
|
|
33
|
+
statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
|
|
34
|
+
damagecontrol: {
|
|
35
|
+
enabled: false,
|
|
36
|
+
valid: true,
|
|
37
|
+
issues: [],
|
|
38
|
+
version: null,
|
|
39
|
+
outdated: false,
|
|
40
|
+
level: null,
|
|
41
|
+
patternCount: 0,
|
|
42
|
+
},
|
|
43
|
+
askuserquestion: {
|
|
44
|
+
enabled: false,
|
|
45
|
+
valid: true,
|
|
46
|
+
issues: [],
|
|
47
|
+
version: null,
|
|
48
|
+
outdated: false,
|
|
49
|
+
mode: null,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
metadata: { exists: false, version: null },
|
|
53
|
+
currentVersion: version,
|
|
54
|
+
hasOutdated: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Git detection
|
|
58
|
+
if (fs.existsSync('.git')) {
|
|
59
|
+
status.git.initialized = true;
|
|
60
|
+
try {
|
|
61
|
+
status.git.remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
}).trim();
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Settings file detection
|
|
68
|
+
if (fs.existsSync('.claude/settings.json')) {
|
|
69
|
+
status.settingsExists = true;
|
|
70
|
+
const settings = readJSON('.claude/settings.json');
|
|
71
|
+
|
|
72
|
+
if (!settings) {
|
|
73
|
+
status.settingsValid = false;
|
|
74
|
+
status.settingsIssues.push('Invalid JSON in settings.json');
|
|
75
|
+
} else {
|
|
76
|
+
detectHooks(settings, status);
|
|
77
|
+
detectStatusLine(settings, status);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Metadata detection
|
|
82
|
+
detectMetadata(status, version);
|
|
83
|
+
|
|
84
|
+
return status;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect hook configurations in settings
|
|
89
|
+
*/
|
|
90
|
+
function detectHooks(settings, status) {
|
|
91
|
+
if (!settings.hooks) return;
|
|
92
|
+
|
|
93
|
+
// SessionStart detection
|
|
94
|
+
if (settings.hooks.SessionStart) {
|
|
95
|
+
detectSessionStartHook(settings.hooks.SessionStart, status);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// PreCompact detection
|
|
99
|
+
if (settings.hooks.PreCompact) {
|
|
100
|
+
detectPreCompactHook(settings.hooks.PreCompact, status);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Stop hooks detection (ralphloop and selfimprove)
|
|
104
|
+
if (settings.hooks.Stop) {
|
|
105
|
+
detectStopHooks(settings.hooks.Stop, status);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// PreToolUse hooks detection (damage control)
|
|
109
|
+
if (settings.hooks.PreToolUse) {
|
|
110
|
+
detectPreToolUseHooks(settings.hooks.PreToolUse, status);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Detect SessionStart hook configuration
|
|
116
|
+
*/
|
|
117
|
+
function detectSessionStartHook(hook, status) {
|
|
118
|
+
if (Array.isArray(hook) && hook.length > 0) {
|
|
119
|
+
const first = hook[0];
|
|
120
|
+
if (first.matcher !== undefined && first.hooks) {
|
|
121
|
+
status.features.sessionstart.enabled = true;
|
|
122
|
+
} else {
|
|
123
|
+
status.features.sessionstart.enabled = true;
|
|
124
|
+
status.features.sessionstart.valid = false;
|
|
125
|
+
status.features.sessionstart.issues.push('Old format - needs migration');
|
|
126
|
+
}
|
|
127
|
+
} else if (typeof hook === 'string') {
|
|
128
|
+
status.features.sessionstart.enabled = true;
|
|
129
|
+
status.features.sessionstart.valid = false;
|
|
130
|
+
status.features.sessionstart.issues.push('String format - needs migration');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detect PreCompact hook configuration
|
|
136
|
+
*/
|
|
137
|
+
function detectPreCompactHook(hook, status) {
|
|
138
|
+
if (Array.isArray(hook) && hook.length > 0) {
|
|
139
|
+
const first = hook[0];
|
|
140
|
+
if (first.matcher !== undefined && first.hooks) {
|
|
141
|
+
status.features.precompact.enabled = true;
|
|
142
|
+
} else {
|
|
143
|
+
status.features.precompact.enabled = true;
|
|
144
|
+
status.features.precompact.valid = false;
|
|
145
|
+
status.features.precompact.issues.push('Old format - needs migration');
|
|
146
|
+
}
|
|
147
|
+
} else if (typeof hook === 'string') {
|
|
148
|
+
status.features.precompact.enabled = true;
|
|
149
|
+
status.features.precompact.valid = false;
|
|
150
|
+
status.features.precompact.issues.push('String format - needs migration');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Detect Stop hook configuration (ralphloop, selfimprove)
|
|
156
|
+
*/
|
|
157
|
+
function detectStopHooks(hook, status) {
|
|
158
|
+
if (Array.isArray(hook) && hook.length > 0) {
|
|
159
|
+
const first = hook[0];
|
|
160
|
+
if (first.matcher !== undefined && first.hooks) {
|
|
161
|
+
for (const h of first.hooks) {
|
|
162
|
+
if (h.command?.includes('ralph-loop')) {
|
|
163
|
+
status.features.ralphloop.enabled = true;
|
|
164
|
+
}
|
|
165
|
+
if (h.command?.includes('auto-self-improve')) {
|
|
166
|
+
status.features.selfimprove.enabled = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Detect PreToolUse hooks (damage control)
|
|
175
|
+
*/
|
|
176
|
+
function detectPreToolUseHooks(hooks, status) {
|
|
177
|
+
if (!Array.isArray(hooks) || hooks.length === 0) return;
|
|
178
|
+
|
|
179
|
+
const hasBashHook = hooks.some(
|
|
180
|
+
h => h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
181
|
+
);
|
|
182
|
+
const hasEditHook = hooks.some(
|
|
183
|
+
h => h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
184
|
+
);
|
|
185
|
+
const hasWriteHook = hooks.some(
|
|
186
|
+
h => h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (hasBashHook || hasEditHook || hasWriteHook) {
|
|
190
|
+
status.features.damagecontrol.enabled = true;
|
|
191
|
+
const hookCount = [hasBashHook, hasEditHook, hasWriteHook].filter(Boolean).length;
|
|
192
|
+
if (hookCount < 3) {
|
|
193
|
+
status.features.damagecontrol.valid = false;
|
|
194
|
+
status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Detect statusLine configuration
|
|
201
|
+
*/
|
|
202
|
+
function detectStatusLine(settings, status) {
|
|
203
|
+
if (!settings.statusLine) return;
|
|
204
|
+
|
|
205
|
+
status.features.statusline.enabled = true;
|
|
206
|
+
if (typeof settings.statusLine === 'string') {
|
|
207
|
+
status.features.statusline.valid = false;
|
|
208
|
+
status.features.statusline.issues.push('String format - needs type:command');
|
|
209
|
+
} else if (!settings.statusLine.type) {
|
|
210
|
+
status.features.statusline.valid = false;
|
|
211
|
+
status.features.statusline.issues.push('Missing type:command');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Detect metadata file configuration
|
|
217
|
+
*/
|
|
218
|
+
function detectMetadata(status, version) {
|
|
219
|
+
const metaPath = 'docs/00-meta/agileflow-metadata.json';
|
|
220
|
+
if (!fs.existsSync(metaPath)) return;
|
|
221
|
+
|
|
222
|
+
status.metadata.exists = true;
|
|
223
|
+
const meta = readJSON(metaPath);
|
|
224
|
+
if (!meta) return;
|
|
225
|
+
|
|
226
|
+
status.metadata.version = meta.version;
|
|
227
|
+
|
|
228
|
+
// Archival settings
|
|
229
|
+
if (meta.archival?.enabled) {
|
|
230
|
+
status.features.archival.enabled = true;
|
|
231
|
+
status.features.archival.threshold = meta.archival.threshold_days;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Damage control metadata
|
|
235
|
+
if (meta.features?.damagecontrol?.enabled) {
|
|
236
|
+
status.features.damagecontrol.level = meta.features.damagecontrol.protectionLevel || 'standard';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// AskUserQuestion metadata
|
|
240
|
+
if (meta.features?.askUserQuestion?.enabled) {
|
|
241
|
+
status.features.askuserquestion.enabled = true;
|
|
242
|
+
status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Read feature versions and check if outdated
|
|
246
|
+
if (meta.features) {
|
|
247
|
+
const featureKeyMap = { askUserQuestion: 'askuserquestion' };
|
|
248
|
+
Object.entries(meta.features).forEach(([feature, data]) => {
|
|
249
|
+
const statusKey = featureKeyMap[feature] || feature.toLowerCase();
|
|
250
|
+
if (status.features[statusKey] && data.version) {
|
|
251
|
+
status.features[statusKey].version = data.version;
|
|
252
|
+
if (data.version !== version && status.features[statusKey].enabled) {
|
|
253
|
+
status.features[statusKey].outdated = true;
|
|
254
|
+
status.hasOutdated = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// STATUS PRINTING
|
|
263
|
+
// ============================================================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Print configuration status to console
|
|
267
|
+
* @param {object} status - Status object from detectConfig
|
|
268
|
+
* @returns {{ hasIssues: boolean, hasOutdated: boolean }}
|
|
269
|
+
*/
|
|
270
|
+
function printStatus(status) {
|
|
271
|
+
header('Current Configuration');
|
|
272
|
+
|
|
273
|
+
// Git status
|
|
274
|
+
log(
|
|
275
|
+
`Git: ${status.git.initialized ? '' : ''} ${status.git.initialized ? 'initialized' : 'not initialized'}${status.git.remote ? ` (${status.git.remote})` : ''}`,
|
|
276
|
+
status.git.initialized ? c.green : c.dim
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Settings status
|
|
280
|
+
if (!status.settingsExists) {
|
|
281
|
+
log('Settings: .claude/settings.json not found', c.dim);
|
|
282
|
+
} else if (!status.settingsValid) {
|
|
283
|
+
log('Settings: Invalid JSON', c.red);
|
|
284
|
+
} else {
|
|
285
|
+
log('Settings: .claude/settings.json exists', c.green);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Features status
|
|
289
|
+
header('Features:');
|
|
290
|
+
|
|
291
|
+
const printFeature = (name, label) => {
|
|
292
|
+
const f = status.features[name];
|
|
293
|
+
let statusIcon = f.enabled ? '' : '';
|
|
294
|
+
let statusText = f.enabled ? 'enabled' : 'disabled';
|
|
295
|
+
let color = f.enabled ? c.green : c.dim;
|
|
296
|
+
|
|
297
|
+
if (f.enabled && !f.valid) {
|
|
298
|
+
statusIcon = '';
|
|
299
|
+
statusText = 'INVALID FORMAT';
|
|
300
|
+
color = c.yellow;
|
|
301
|
+
} else if (f.enabled && f.outdated) {
|
|
302
|
+
statusIcon = '';
|
|
303
|
+
statusText = `outdated (v${f.version} -> v${status.currentVersion})`;
|
|
304
|
+
color = c.yellow;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
log(` ${statusIcon} ${label}: ${statusText}`, color);
|
|
308
|
+
|
|
309
|
+
if (f.issues?.length > 0) {
|
|
310
|
+
f.issues.forEach(issue => log(` - ${issue}`, c.yellow));
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
printFeature('sessionstart', 'SessionStart Hook');
|
|
315
|
+
printFeature('precompact', 'PreCompact Hook');
|
|
316
|
+
printFeature('ralphloop', 'RalphLoop (Stop)');
|
|
317
|
+
printFeature('selfimprove', 'SelfImprove (Stop)');
|
|
318
|
+
|
|
319
|
+
// Archival (special display)
|
|
320
|
+
const arch = status.features.archival;
|
|
321
|
+
log(
|
|
322
|
+
` ${arch.enabled ? '' : ''} Archival: ${arch.enabled ? `${arch.threshold} days` : 'disabled'}`,
|
|
323
|
+
arch.enabled ? c.green : c.dim
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
printFeature('statusline', 'Status Line');
|
|
327
|
+
|
|
328
|
+
// Damage Control (special display)
|
|
329
|
+
const dc = status.features.damagecontrol;
|
|
330
|
+
if (dc.enabled) {
|
|
331
|
+
let dcStatusText = 'enabled';
|
|
332
|
+
if (dc.level) dcStatusText += ` (${dc.level})`;
|
|
333
|
+
if (!dc.valid) dcStatusText = 'INCOMPLETE';
|
|
334
|
+
const dcIcon = dc.enabled && dc.valid ? '' : '';
|
|
335
|
+
const dcColor = dc.enabled && dc.valid ? c.green : c.yellow;
|
|
336
|
+
log(` ${dcIcon} Damage Control: ${dcStatusText}`, dcColor);
|
|
337
|
+
if (dc.issues?.length > 0) {
|
|
338
|
+
dc.issues.forEach(issue => log(` - ${issue}`, c.yellow));
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
log(` Damage Control: disabled`, c.dim);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// AskUserQuestion
|
|
345
|
+
const auq = status.features.askuserquestion;
|
|
346
|
+
if (auq.enabled) {
|
|
347
|
+
let auqStatusText = 'enabled';
|
|
348
|
+
if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
|
|
349
|
+
log(` AskUserQuestion: ${auqStatusText}`, c.green);
|
|
350
|
+
} else {
|
|
351
|
+
log(` AskUserQuestion: disabled`, c.dim);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Metadata version
|
|
355
|
+
if (status.metadata.exists) {
|
|
356
|
+
log(`\nMetadata: v${status.metadata.version}`, c.dim);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Issues summary
|
|
360
|
+
const hasIssues = Object.values(status.features).some(f => f.issues?.length > 0);
|
|
361
|
+
if (hasIssues) {
|
|
362
|
+
log('\n Format issues detected! Run with --migrate to fix.', c.yellow);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (status.hasOutdated) {
|
|
366
|
+
log('\n Outdated scripts detected! Run with --upgrade to update.', c.yellow);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return { hasIssues, hasOutdated: status.hasOutdated };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
module.exports = {
|
|
373
|
+
detectConfig,
|
|
374
|
+
printStatus,
|
|
375
|
+
// Export helper functions for testing
|
|
376
|
+
detectHooks,
|
|
377
|
+
detectSessionStartHook,
|
|
378
|
+
detectPreCompactHook,
|
|
379
|
+
detectStopHooks,
|
|
380
|
+
detectPreToolUseHooks,
|
|
381
|
+
detectStatusLine,
|
|
382
|
+
detectMetadata,
|
|
383
|
+
};
|