agileflow 2.99.0 → 2.99.2
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/dashboard-protocol.js +38 -0
- package/lib/dashboard-server.js +197 -7
- package/lib/feedback.js +36 -9
- package/lib/git-operations.js +4 -1
- package/lib/merge-operations.js +25 -0
- package/lib/progress.js +7 -6
- package/lib/session-operations.js +611 -0
- package/lib/session-switching.js +191 -0
- package/lib/template-loader.js +4 -2
- package/lib/worktree-operations.js +5 -25
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +13 -0
- package/scripts/agileflow-welcome.js +11 -6
- package/scripts/batch-pmap-loop.js +11 -4
- package/scripts/claude-tmux.sh +186 -103
- package/scripts/damage-control-bash.js +33 -3
- package/scripts/damage-control-edit.js +33 -3
- package/scripts/damage-control-write.js +33 -3
- package/scripts/lib/configure-features.js +10 -7
- package/scripts/lib/configure-repair.js +12 -2
- package/scripts/lib/process-cleanup.js +197 -15
- package/scripts/obtain-context.js +5 -0
- package/scripts/session-manager.js +156 -932
- package/scripts/spawn-parallel.js +15 -11
- package/src/core/agents/configuration/archival.md +2 -1
- package/src/core/agents/configuration/attribution.md +2 -1
- package/src/core/agents/configuration/ci.md +2 -1
- package/src/core/agents/configuration/damage-control.md +2 -1
- package/src/core/agents/configuration/git-config.md +2 -1
- package/src/core/agents/configuration/hooks.md +2 -1
- package/src/core/agents/configuration/precompact.md +2 -1
- package/src/core/agents/configuration/status-line.md +2 -1
- package/src/core/agents/configuration/verify.md +2 -1
- package/src/core/commands/adr/list.md +1 -1
- package/src/core/commands/adr/update.md +1 -1
- package/src/core/commands/adr/view.md +1 -1
- package/src/core/commands/adr.md +1 -1
- package/src/core/commands/agent.md +1 -1
- package/src/core/commands/api.md +1 -1
- package/src/core/commands/assign.md +1 -1
- package/src/core/commands/audit.md +1 -1
- package/src/core/commands/auto.md +1 -1
- package/src/core/commands/automate.md +1 -1
- package/src/core/commands/babysit.md +1 -1
- package/src/core/commands/baseline.md +1 -1
- package/src/core/commands/batch.md +1 -1
- package/src/core/commands/blockers.md +1 -1
- package/src/core/commands/board.md +1 -1
- package/src/core/commands/changelog.md +1 -1
- package/src/core/commands/choose.md +1 -1
- package/src/core/commands/ci.md +1 -1
- package/src/core/commands/compress.md +1 -1
- package/src/core/commands/configure.md +56 -1
- package/src/core/commands/context/export.md +1 -1
- package/src/core/commands/context/full.md +1 -1
- package/src/core/commands/context/note.md +1 -1
- package/src/core/commands/council.md +1 -1
- package/src/core/commands/debt.md +1 -1
- package/src/core/commands/deploy.md +1 -1
- package/src/core/commands/deps.md +1 -1
- package/src/core/commands/diagnose.md +1 -1
- package/src/core/commands/docs.md +1 -1
- package/src/core/commands/epic/list.md +1 -1
- package/src/core/commands/epic/view.md +1 -1
- package/src/core/commands/epic.md +1 -1
- package/src/core/commands/feedback.md +1 -1
- package/src/core/commands/handoff.md +1 -1
- package/src/core/commands/help.md +4 -190
- package/src/core/commands/ideate/history.md +1 -1
- package/src/core/commands/ideate/new.md +1 -1
- package/src/core/commands/impact.md +1 -1
- package/src/core/commands/install.md +1 -1
- package/src/core/commands/logic/audit.md +1 -1
- package/src/core/commands/maintain.md +1 -1
- package/src/core/commands/metrics.md +1 -1
- package/src/core/commands/multi-expert.md +1 -1
- package/src/core/commands/packages.md +1 -1
- package/src/core/commands/pr.md +1 -1
- package/src/core/commands/readme-sync.md +1 -1
- package/src/core/commands/research/analyze.md +1 -1
- package/src/core/commands/research/ask.md +1 -1
- package/src/core/commands/research/import.md +1 -1
- package/src/core/commands/research/list.md +1 -1
- package/src/core/commands/research/synthesize.md +1 -1
- package/src/core/commands/research/view.md +1 -1
- package/src/core/commands/retro.md +1 -1
- package/src/core/commands/review.md +1 -1
- package/src/core/commands/rlm.md +1 -1
- package/src/core/commands/roadmap/analyze.md +1 -1
- package/src/core/commands/rpi.md +1 -1
- package/src/core/commands/serve.md +127 -0
- package/src/core/commands/session/cleanup.md +1 -1
- package/src/core/commands/session/end.md +84 -23
- package/src/core/commands/session/history.md +1 -1
- package/src/core/commands/session/init.md +1 -1
- package/src/core/commands/session/new.md +198 -84
- package/src/core/commands/session/resume.md +1 -1
- package/src/core/commands/session/spawn.md +1 -1
- package/src/core/commands/session/status.md +1 -1
- package/src/core/commands/skill/create.md +1 -1
- package/src/core/commands/skill/delete.md +1 -1
- package/src/core/commands/skill/edit.md +1 -1
- package/src/core/commands/skill/list.md +1 -1
- package/src/core/commands/skill/test.md +1 -1
- package/src/core/commands/skill/upgrade.md +1 -1
- package/src/core/commands/sprint.md +1 -1
- package/src/core/commands/status.md +1 -1
- package/src/core/commands/story/list.md +1 -1
- package/src/core/commands/story/view.md +1 -1
- package/src/core/commands/story-validate.md +1 -1
- package/src/core/commands/story.md +1 -1
- package/src/core/commands/team/list.md +1 -1
- package/src/core/commands/team/start.md +1 -1
- package/src/core/commands/team/status.md +1 -1
- package/src/core/commands/team/stop.md +1 -1
- package/src/core/commands/template.md +1 -1
- package/src/core/commands/tests.md +1 -1
- package/src/core/commands/update.md +1 -1
- package/src/core/commands/validate-expertise.md +1 -1
- package/src/core/commands/velocity.md +1 -1
- package/src/core/commands/verify.md +1 -1
- package/src/core/commands/whats-new.md +1 -1
- package/src/core/commands/workflow.md +1 -1
- package/tools/cli/installers/ide/codex.js +12 -4
- package/tools/cli/lib/content-injector.js +23 -4
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-switching.js - Session switching and thread type management
|
|
3
|
+
*
|
|
4
|
+
* Extracted from session-manager.js to reduce file size.
|
|
5
|
+
* Uses factory pattern for dependency injection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const { getSessionStatePath } = require('./paths');
|
|
12
|
+
const { sessionThreadMachine } = require('./state-machine');
|
|
13
|
+
const { THREAD_TYPES } = require('./worktree-operations');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create session switching operations bound to the given dependencies.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} deps
|
|
19
|
+
* @param {string} deps.ROOT - Project root path
|
|
20
|
+
* @param {Function} deps.loadRegistry - Load registry data
|
|
21
|
+
* @param {Function} deps.saveRegistry - Save registry data
|
|
22
|
+
*/
|
|
23
|
+
function createSessionSwitching(deps) {
|
|
24
|
+
const { ROOT, loadRegistry, saveRegistry } = deps;
|
|
25
|
+
|
|
26
|
+
const SESSION_STATE_PATH = getSessionStatePath(ROOT);
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Session Switching
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
function switchSession(sessionIdOrNickname) {
|
|
33
|
+
const registry = loadRegistry();
|
|
34
|
+
let targetSession = null,
|
|
35
|
+
targetId = null;
|
|
36
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
37
|
+
if (id === sessionIdOrNickname || session.nickname === sessionIdOrNickname) {
|
|
38
|
+
targetSession = session;
|
|
39
|
+
targetId = id;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!targetSession)
|
|
44
|
+
return { success: false, error: `Session "${sessionIdOrNickname}" not found` };
|
|
45
|
+
if (!fs.existsSync(targetSession.path))
|
|
46
|
+
return { success: false, error: `Session directory does not exist: ${targetSession.path}` };
|
|
47
|
+
|
|
48
|
+
let sessionState = {};
|
|
49
|
+
if (fs.existsSync(SESSION_STATE_PATH)) {
|
|
50
|
+
try {
|
|
51
|
+
sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
/* start fresh */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sessionState.active_session = {
|
|
58
|
+
id: targetId,
|
|
59
|
+
nickname: targetSession.nickname,
|
|
60
|
+
path: targetSession.path,
|
|
61
|
+
branch: targetSession.branch,
|
|
62
|
+
switched_at: new Date().toISOString(),
|
|
63
|
+
original_cwd: ROOT,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const stateDir = path.dirname(SESSION_STATE_PATH);
|
|
67
|
+
if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
|
|
68
|
+
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2) + '\n');
|
|
69
|
+
|
|
70
|
+
registry.sessions[targetId].last_active = new Date().toISOString();
|
|
71
|
+
saveRegistry(registry);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
session: {
|
|
76
|
+
id: targetId,
|
|
77
|
+
nickname: targetSession.nickname,
|
|
78
|
+
path: targetSession.path,
|
|
79
|
+
branch: targetSession.branch,
|
|
80
|
+
},
|
|
81
|
+
path: targetSession.path,
|
|
82
|
+
addDirCommand: `/add-dir ${targetSession.path}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function clearActiveSession() {
|
|
87
|
+
if (!fs.existsSync(SESSION_STATE_PATH)) return { success: true };
|
|
88
|
+
try {
|
|
89
|
+
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
90
|
+
delete sessionState.active_session;
|
|
91
|
+
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2) + '\n');
|
|
92
|
+
return { success: true };
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return { success: false, error: e.message };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getActiveSession() {
|
|
99
|
+
if (!fs.existsSync(SESSION_STATE_PATH)) return { active: false };
|
|
100
|
+
try {
|
|
101
|
+
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
102
|
+
return sessionState.active_session
|
|
103
|
+
? { active: true, session: sessionState.active_session }
|
|
104
|
+
: { active: false };
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return { active: false };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Thread Type Management
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
function getSessionThreadType(sessionId = null) {
|
|
115
|
+
const registry = loadRegistry();
|
|
116
|
+
const cwd = process.cwd();
|
|
117
|
+
let targetId = sessionId;
|
|
118
|
+
if (!targetId) {
|
|
119
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
120
|
+
if (session.path === cwd) {
|
|
121
|
+
targetId = id;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!targetId || !registry.sessions[targetId])
|
|
127
|
+
return { success: false, error: 'Session not found' };
|
|
128
|
+
const session = registry.sessions[targetId];
|
|
129
|
+
const threadType = session.thread_type || (session.is_main ? 'base' : 'parallel');
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
thread_type: threadType,
|
|
133
|
+
session_id: targetId,
|
|
134
|
+
is_main: session.is_main,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function setSessionThreadType(sessionId, threadType) {
|
|
139
|
+
if (!THREAD_TYPES.includes(threadType)) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: `Invalid thread type: ${threadType}. Valid: ${THREAD_TYPES.join(', ')}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const registry = loadRegistry();
|
|
146
|
+
if (!registry.sessions[sessionId])
|
|
147
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
148
|
+
registry.sessions[sessionId].thread_type = threadType;
|
|
149
|
+
saveRegistry(registry);
|
|
150
|
+
return { success: true, thread_type: threadType };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function transitionThread(sessionId, targetType, options = {}) {
|
|
154
|
+
const { force = false } = options;
|
|
155
|
+
const registry = loadRegistry();
|
|
156
|
+
const session = registry.sessions[sessionId];
|
|
157
|
+
if (!session) return { success: false, error: `Session ${sessionId} not found` };
|
|
158
|
+
|
|
159
|
+
const currentType = session.thread_type || (session.is_main ? 'base' : 'parallel');
|
|
160
|
+
const result = sessionThreadMachine.transition(currentType, targetType, { force });
|
|
161
|
+
if (!result.success)
|
|
162
|
+
return { success: false, from: currentType, to: targetType, error: result.error };
|
|
163
|
+
if (result.noop) return { success: true, from: currentType, to: targetType, noop: true };
|
|
164
|
+
|
|
165
|
+
registry.sessions[sessionId].thread_type = targetType;
|
|
166
|
+
registry.sessions[sessionId].thread_transitioned_at = new Date().toISOString();
|
|
167
|
+
saveRegistry(registry);
|
|
168
|
+
return { success: true, from: currentType, to: targetType, forced: result.forced || false };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getValidThreadTransitions(sessionId) {
|
|
172
|
+
const registry = loadRegistry();
|
|
173
|
+
const session = registry.sessions[sessionId];
|
|
174
|
+
if (!session) return { success: false, error: `Session ${sessionId} not found` };
|
|
175
|
+
const currentType = session.thread_type || (session.is_main ? 'base' : 'parallel');
|
|
176
|
+
const validTransitions = sessionThreadMachine.getValidTransitions(currentType);
|
|
177
|
+
return { success: true, current: currentType, validTransitions };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
switchSession,
|
|
182
|
+
clearActiveSession,
|
|
183
|
+
getActiveSession,
|
|
184
|
+
getSessionThreadType,
|
|
185
|
+
setSessionThreadType,
|
|
186
|
+
transitionThread,
|
|
187
|
+
getValidThreadTransitions,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { createSessionSwitching };
|
package/lib/template-loader.js
CHANGED
|
@@ -90,8 +90,10 @@ function extractSimpleFrontmatter(raw) {
|
|
|
90
90
|
const key = line.substring(0, colonIdx).trim();
|
|
91
91
|
let value = line.substring(colonIdx + 1).trim();
|
|
92
92
|
// Remove quotes if present
|
|
93
|
-
if (
|
|
94
|
-
|
|
93
|
+
if (
|
|
94
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
95
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
96
|
+
) {
|
|
95
97
|
value = value.slice(1, -1);
|
|
96
98
|
}
|
|
97
99
|
frontmatter[key] = value;
|
|
@@ -57,36 +57,16 @@ function detectThreadType(session, isWorktree = false) {
|
|
|
57
57
|
/**
|
|
58
58
|
* Display progress feedback during long operations.
|
|
59
59
|
* Returns a function to stop the progress indicator.
|
|
60
|
+
* Uses stderr to avoid corrupting stdout JSON output from callers.
|
|
60
61
|
*
|
|
61
62
|
* @param {string} message - Progress message
|
|
62
63
|
* @returns {function} Stop function
|
|
63
64
|
*/
|
|
64
65
|
function progressIndicator(message) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// For TTY (interactive terminal), show spinner
|
|
70
|
-
if (process.stderr.isTTY) {
|
|
71
|
-
const interval = setInterval(() => {
|
|
72
|
-
process.stderr.write(`\r${frames[frameIndex++ % frames.length]} ${message}`);
|
|
73
|
-
}, 80);
|
|
74
|
-
return () => {
|
|
75
|
-
clearInterval(interval);
|
|
76
|
-
process.stderr.write(`\r${' '.repeat(message.length + 2)}\r`);
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// For non-TTY (Claude Code, piped output), emit periodic updates to stderr
|
|
81
|
-
process.stderr.write(`⏳ ${message}...\n`);
|
|
82
|
-
const interval = setInterval(() => {
|
|
83
|
-
elapsed += 10;
|
|
84
|
-
process.stderr.write(`⏳ Still working... (${elapsed}s elapsed)\n`);
|
|
85
|
-
}, 10000); // Update every 10 seconds
|
|
86
|
-
|
|
87
|
-
return () => {
|
|
88
|
-
clearInterval(interval);
|
|
89
|
-
};
|
|
66
|
+
const { FeedbackSpinner } = require('./feedback');
|
|
67
|
+
const spinner = new FeedbackSpinner(message, { stream: process.stderr });
|
|
68
|
+
spinner.start();
|
|
69
|
+
return () => spinner.succeed();
|
|
90
70
|
}
|
|
91
71
|
|
|
92
72
|
/**
|
package/package.json
CHANGED
|
@@ -44,6 +44,7 @@ const {
|
|
|
44
44
|
upgradeFeatures,
|
|
45
45
|
} = require('./lib/configure-features');
|
|
46
46
|
const { listScripts, showVersionInfo, repairScripts } = require('./lib/configure-repair');
|
|
47
|
+
const { feedback } = require('../lib/feedback');
|
|
47
48
|
|
|
48
49
|
// ============================================================================
|
|
49
50
|
// VERSION
|
|
@@ -281,7 +282,10 @@ function main() {
|
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
// Always detect first
|
|
285
|
+
const spinner = feedback.spinner('Detecting configuration...');
|
|
286
|
+
spinner.start();
|
|
284
287
|
const status = detectConfig(VERSION);
|
|
288
|
+
spinner.succeed('Configuration detected');
|
|
285
289
|
const { hasIssues, hasOutdated } = printStatus(status);
|
|
286
290
|
|
|
287
291
|
// Detect only mode
|
|
@@ -317,11 +321,17 @@ function main() {
|
|
|
317
321
|
actions.disabled = p.disable || [];
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
// Enable/disable specific features with progress tracking
|
|
325
|
+
const totalChanges = enable.length + disable.length;
|
|
326
|
+
const featureTask =
|
|
327
|
+
totalChanges > 1 ? feedback.task('Applying feature changes', totalChanges) : null;
|
|
328
|
+
|
|
320
329
|
// Enable specific features
|
|
321
330
|
enable.forEach(f => {
|
|
322
331
|
if (enableFeature(f, { archivalDays }, VERSION)) {
|
|
323
332
|
actions.enabled.push(f);
|
|
324
333
|
}
|
|
334
|
+
if (featureTask) featureTask.step(`Enabled ${f}`);
|
|
325
335
|
});
|
|
326
336
|
|
|
327
337
|
// Disable specific features
|
|
@@ -329,8 +339,11 @@ function main() {
|
|
|
329
339
|
if (disableFeature(f, VERSION)) {
|
|
330
340
|
actions.disabled.push(f);
|
|
331
341
|
}
|
|
342
|
+
if (featureTask) featureTask.step(`Disabled ${f}`);
|
|
332
343
|
});
|
|
333
344
|
|
|
345
|
+
if (featureTask) featureTask.complete('Feature changes applied');
|
|
346
|
+
|
|
334
347
|
// Print summary if anything changed
|
|
335
348
|
if (actions.enabled.length > 0 || actions.disabled.length > 0) {
|
|
336
349
|
printSummary(actions);
|
|
@@ -1965,9 +1965,12 @@ async function main() {
|
|
|
1965
1965
|
// Check for multiple Claude processes in the same working directory
|
|
1966
1966
|
if (processCleanup) {
|
|
1967
1967
|
try {
|
|
1968
|
-
//
|
|
1968
|
+
// Auto-kill is explicitly opt-in at runtime.
|
|
1969
|
+
// Even if metadata has autoKill=true from older configs, we require
|
|
1970
|
+
// AGILEFLOW_PROCESS_CLEANUP_AUTOKILL=1 to prevent accidental session kills.
|
|
1969
1971
|
const metadata = cache?.metadata;
|
|
1970
|
-
const
|
|
1972
|
+
const autoKillConfigured = metadata?.features?.processCleanup?.autoKill === true;
|
|
1973
|
+
const autoKill = autoKillConfigured && process.env.AGILEFLOW_PROCESS_CLEANUP_AUTOKILL === '1';
|
|
1971
1974
|
|
|
1972
1975
|
const cleanupResult = processCleanup.cleanupDuplicateProcesses({
|
|
1973
1976
|
rootDir,
|
|
@@ -1987,15 +1990,17 @@ async function main() {
|
|
|
1987
1990
|
console.log(`${c.dim} └─ PID ${proc.pid} (${proc.method})${c.reset}`);
|
|
1988
1991
|
});
|
|
1989
1992
|
} else {
|
|
1990
|
-
// Warn only (auto-kill
|
|
1993
|
+
// Warn only (auto-kill disabled or skipped by safety guards)
|
|
1991
1994
|
console.log(
|
|
1992
1995
|
`${c.amber}⚠️ ${cleanupResult.duplicates} other Claude process(es) in same directory${c.reset}`
|
|
1993
1996
|
);
|
|
1994
1997
|
console.log(`${c.slate} This may cause slowdowns and freezing. Options:${c.reset}`);
|
|
1995
1998
|
console.log(`${c.slate} • Close duplicate Claude windows/tabs${c.reset}`);
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
+
if (autoKillConfigured) {
|
|
2000
|
+
console.log(
|
|
2001
|
+
`${c.slate} • Auto-kill configured but runtime opt-in is off (safer default)${c.reset}`
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
1999
2004
|
}
|
|
2000
2005
|
|
|
2001
2006
|
if (cleanupResult.errors.length > 0) {
|
|
@@ -27,6 +27,7 @@ const { c } = require('../lib/colors');
|
|
|
27
27
|
const { getProjectRoot } = require('../lib/paths');
|
|
28
28
|
const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
|
|
29
29
|
const { parseIntBounded } = require('../lib/validate');
|
|
30
|
+
const { feedback } = require('../lib/feedback');
|
|
30
31
|
|
|
31
32
|
// ===== SESSION STATE HELPERS =====
|
|
32
33
|
|
|
@@ -261,10 +262,14 @@ function handleBatchLoop(rootDir) {
|
|
|
261
262
|
items[currentItem].iterations = (items[currentItem].iterations || 0) + 1;
|
|
262
263
|
|
|
263
264
|
// Run tests for this file
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
const testSpinner = feedback.spinner(`Running tests for: ${currentItem}`);
|
|
266
|
+
testSpinner.start();
|
|
267
267
|
const testResult = runTestsForFile(rootDir, currentItem);
|
|
268
|
+
if (testResult.passed) {
|
|
269
|
+
testSpinner.succeed(`Tests passed for: ${currentItem}`);
|
|
270
|
+
} else {
|
|
271
|
+
testSpinner.fail(`Tests failed for: ${currentItem}`);
|
|
272
|
+
}
|
|
268
273
|
|
|
269
274
|
if (testResult.passed) {
|
|
270
275
|
console.log(`${c.green}✓ Tests passed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
@@ -373,8 +378,10 @@ async function handleInit(args, rootDir) {
|
|
|
373
378
|
const maxIterations = parseIntBounded(maxArg ? maxArg.split('=')[1] : null, 50, 1, 200);
|
|
374
379
|
|
|
375
380
|
// Resolve glob pattern
|
|
376
|
-
|
|
381
|
+
const globSpinner = feedback.spinner(`Resolving pattern: ${pattern}`);
|
|
382
|
+
globSpinner.start();
|
|
377
383
|
const files = await resolveGlob(pattern, rootDir);
|
|
384
|
+
globSpinner.succeed(`Resolved ${files.length} files`);
|
|
378
385
|
|
|
379
386
|
if (files.length === 0) {
|
|
380
387
|
console.log(`${c.yellow}No files found matching: ${pattern}${c.reset}`);
|