@litmers/cursorflow-orchestrator 0.1.13 → 0.1.14
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 +34 -0
- package/README.md +83 -2
- package/commands/cursorflow-clean.md +20 -6
- package/commands/cursorflow-prepare.md +1 -1
- package/commands/cursorflow-resume.md +127 -6
- package/commands/cursorflow-run.md +2 -2
- package/commands/cursorflow-signal.md +11 -4
- package/dist/cli/clean.js +164 -12
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +6 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.d.ts +8 -0
- package/dist/cli/logs.js +746 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/monitor.js +113 -30
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +1 -1
- package/dist/cli/resume.js +367 -18
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +2 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +34 -20
- package/dist/cli/signal.js.map +1 -1
- package/dist/core/orchestrator.d.ts +11 -1
- package/dist/core/orchestrator.js +257 -35
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.js +20 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.js +113 -13
- package/dist/core/runner.js.map +1 -1
- package/dist/utils/config.js +34 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +209 -0
- package/dist/utils/enhanced-logger.js +963 -0
- package/dist/utils/enhanced-logger.js.map +1 -0
- package/dist/utils/events.d.ts +59 -0
- package/dist/utils/events.js +37 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/git.d.ts +5 -0
- package/dist/utils/git.js +25 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/types.d.ts +122 -1
- package/dist/utils/webhook.d.ts +5 -0
- package/dist/utils/webhook.js +109 -0
- package/dist/utils/webhook.js.map +1 -0
- package/examples/README.md +1 -1
- package/package.json +1 -1
- package/scripts/simple-logging-test.sh +97 -0
- package/scripts/test-real-logging.sh +289 -0
- package/scripts/test-streaming-multi-task.sh +247 -0
- package/src/cli/clean.ts +170 -13
- package/src/cli/index.ts +4 -1
- package/src/cli/logs.ts +848 -0
- package/src/cli/monitor.ts +123 -30
- package/src/cli/prepare.ts +1 -1
- package/src/cli/resume.ts +463 -22
- package/src/cli/run.ts +2 -0
- package/src/cli/signal.ts +43 -27
- package/src/core/orchestrator.ts +303 -37
- package/src/core/reviewer.ts +22 -0
- package/src/core/runner.ts +128 -12
- package/src/utils/config.ts +36 -0
- package/src/utils/enhanced-logger.ts +1097 -0
- package/src/utils/events.ts +117 -0
- package/src/utils/git.ts +25 -0
- package/src/utils/types.ts +150 -1
- package/src/utils/webhook.ts +85 -0
package/src/cli/clean.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as logger from '../utils/logger';
|
|
10
10
|
import * as git from '../utils/git';
|
|
11
|
-
import { loadConfig, getLogsDir } from '../utils/config';
|
|
11
|
+
import { loadConfig, getLogsDir, getTasksDir } from '../utils/config';
|
|
12
12
|
|
|
13
13
|
interface CleanOptions {
|
|
14
14
|
type?: string;
|
|
@@ -17,6 +17,8 @@ interface CleanOptions {
|
|
|
17
17
|
force: boolean;
|
|
18
18
|
all: boolean;
|
|
19
19
|
help: boolean;
|
|
20
|
+
keepLatest: boolean;
|
|
21
|
+
includeLatest: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function printHelp(): void {
|
|
@@ -29,26 +31,43 @@ Types:
|
|
|
29
31
|
branches Remove local feature branches
|
|
30
32
|
worktrees Remove temporary Git worktrees
|
|
31
33
|
logs Clear log directories
|
|
34
|
+
tasks Clear task directories
|
|
32
35
|
all Remove all of the above (default)
|
|
33
36
|
|
|
34
37
|
Options:
|
|
35
38
|
--dry-run Show what would be removed without deleting
|
|
36
39
|
--force Force removal (ignore uncommitted changes)
|
|
40
|
+
--include-latest Also remove the most recent item (by default, latest is kept)
|
|
37
41
|
--help, -h Show help
|
|
38
42
|
`);
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
function parseArgs(args: string[]): CleanOptions {
|
|
46
|
+
const includeLatest = args.includes('--include-latest');
|
|
42
47
|
return {
|
|
43
|
-
type: args.find(a => ['branches', 'worktrees', 'logs', 'all'].includes(a)),
|
|
48
|
+
type: args.find(a => ['branches', 'worktrees', 'logs', 'tasks', 'all'].includes(a)),
|
|
44
49
|
pattern: null,
|
|
45
50
|
dryRun: args.includes('--dry-run'),
|
|
46
51
|
force: args.includes('--force'),
|
|
47
52
|
all: args.includes('--all'),
|
|
48
53
|
help: args.includes('--help') || args.includes('-h'),
|
|
54
|
+
keepLatest: !includeLatest, // Default: keep latest, unless --include-latest is specified
|
|
55
|
+
includeLatest,
|
|
49
56
|
};
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Get the modification time of a path (directory or file)
|
|
61
|
+
*/
|
|
62
|
+
function getModTime(targetPath: string): number {
|
|
63
|
+
try {
|
|
64
|
+
const stat = fs.statSync(targetPath);
|
|
65
|
+
return stat.mtimeMs;
|
|
66
|
+
} catch {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
async function clean(args: string[]): Promise<void> {
|
|
53
72
|
const options = parseArgs(args);
|
|
54
73
|
|
|
@@ -68,12 +87,15 @@ async function clean(args: string[]): Promise<void> {
|
|
|
68
87
|
await cleanWorktrees(config, repoRoot, options);
|
|
69
88
|
await cleanBranches(config, repoRoot, options);
|
|
70
89
|
await cleanLogs(config, options);
|
|
90
|
+
await cleanTasks(config, options);
|
|
71
91
|
} else if (type === 'worktrees') {
|
|
72
92
|
await cleanWorktrees(config, repoRoot, options);
|
|
73
93
|
} else if (type === 'branches') {
|
|
74
94
|
await cleanBranches(config, repoRoot, options);
|
|
75
95
|
} else if (type === 'logs') {
|
|
76
96
|
await cleanLogs(config, options);
|
|
97
|
+
} else if (type === 'tasks') {
|
|
98
|
+
await cleanTasks(config, options);
|
|
77
99
|
}
|
|
78
100
|
|
|
79
101
|
logger.success('\n✨ Cleaning complete!');
|
|
@@ -84,7 +106,7 @@ async function cleanWorktrees(config: any, repoRoot: string, options: CleanOptio
|
|
|
84
106
|
const worktrees = git.listWorktrees(repoRoot);
|
|
85
107
|
|
|
86
108
|
const worktreeRoot = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees');
|
|
87
|
-
|
|
109
|
+
let toRemove = worktrees.filter(wt => {
|
|
88
110
|
// Skip main worktree
|
|
89
111
|
if (wt.path === repoRoot) return false;
|
|
90
112
|
|
|
@@ -99,6 +121,15 @@ async function cleanWorktrees(config: any, repoRoot: string, options: CleanOptio
|
|
|
99
121
|
return;
|
|
100
122
|
}
|
|
101
123
|
|
|
124
|
+
// If keepLatest is set, keep the most recent worktree
|
|
125
|
+
if (options.keepLatest && toRemove.length > 1) {
|
|
126
|
+
// Sort by modification time (newest first)
|
|
127
|
+
toRemove.sort((a, b) => getModTime(b.path) - getModTime(a.path));
|
|
128
|
+
const kept = toRemove[0];
|
|
129
|
+
toRemove = toRemove.slice(1);
|
|
130
|
+
logger.info(` Keeping latest worktree: ${kept.path} (${kept.branch || 'no branch'})`);
|
|
131
|
+
}
|
|
132
|
+
|
|
102
133
|
for (const wt of toRemove) {
|
|
103
134
|
if (options.dryRun) {
|
|
104
135
|
logger.info(` [DRY RUN] Would remove worktree: ${wt.path} (${wt.branch || 'no branch'})`);
|
|
@@ -123,6 +154,21 @@ async function cleanWorktrees(config: any, repoRoot: string, options: CleanOptio
|
|
|
123
154
|
}
|
|
124
155
|
}
|
|
125
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Get the commit timestamp of a branch
|
|
159
|
+
*/
|
|
160
|
+
function getBranchCommitTime(branch: string, repoRoot: string): number {
|
|
161
|
+
try {
|
|
162
|
+
const result = git.runGitResult(['log', '-1', '--format=%ct', branch], { cwd: repoRoot });
|
|
163
|
+
if (result.success && result.stdout.trim()) {
|
|
164
|
+
return parseInt(result.stdout.trim(), 10) * 1000; // Convert to milliseconds
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// Ignore errors
|
|
168
|
+
}
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
126
172
|
async function cleanBranches(config: any, repoRoot: string, options: CleanOptions) {
|
|
127
173
|
logger.info('\nChecking branches...');
|
|
128
174
|
|
|
@@ -136,13 +182,22 @@ async function cleanBranches(config: any, repoRoot: string, options: CleanOption
|
|
|
136
182
|
.filter(b => b && b !== 'main' && b !== 'master');
|
|
137
183
|
|
|
138
184
|
const prefix = config.branchPrefix || 'feature/';
|
|
139
|
-
|
|
185
|
+
let toDelete = branches.filter(b => b.startsWith(prefix));
|
|
140
186
|
|
|
141
187
|
if (toDelete.length === 0) {
|
|
142
188
|
logger.info(' No branches found to clean.');
|
|
143
189
|
return;
|
|
144
190
|
}
|
|
145
191
|
|
|
192
|
+
// If keepLatest is set, keep the most recent branch
|
|
193
|
+
if (options.keepLatest && toDelete.length > 1) {
|
|
194
|
+
// Sort by commit time (newest first)
|
|
195
|
+
toDelete.sort((a, b) => getBranchCommitTime(b, repoRoot) - getBranchCommitTime(a, repoRoot));
|
|
196
|
+
const kept = toDelete[0];
|
|
197
|
+
toDelete = toDelete.slice(1);
|
|
198
|
+
logger.info(` Keeping latest branch: ${kept}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
146
201
|
for (const branch of toDelete) {
|
|
147
202
|
if (options.dryRun) {
|
|
148
203
|
logger.info(` [DRY RUN] Would delete branch: ${branch}`);
|
|
@@ -166,16 +221,118 @@ async function cleanLogs(config: any, options: CleanOptions) {
|
|
|
166
221
|
return;
|
|
167
222
|
}
|
|
168
223
|
|
|
169
|
-
|
|
170
|
-
|
|
224
|
+
// If keepLatest is set, keep the most recent log directory/file
|
|
225
|
+
if (options.keepLatest) {
|
|
226
|
+
const entries = fs.readdirSync(logsDir, { withFileTypes: true });
|
|
227
|
+
let items = entries.map(entry => ({
|
|
228
|
+
name: entry.name,
|
|
229
|
+
path: path.join(logsDir, entry.name),
|
|
230
|
+
isDir: entry.isDirectory(),
|
|
231
|
+
mtime: getModTime(path.join(logsDir, entry.name))
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
if (items.length <= 1) {
|
|
235
|
+
logger.info(' Only one or no log entries found, nothing to clean.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Sort by modification time (newest first)
|
|
240
|
+
items.sort((a, b) => b.mtime - a.mtime);
|
|
241
|
+
const kept = items[0];
|
|
242
|
+
const toRemove = items.slice(1);
|
|
243
|
+
|
|
244
|
+
logger.info(` Keeping latest log: ${kept.name}`);
|
|
245
|
+
|
|
246
|
+
for (const item of toRemove) {
|
|
247
|
+
if (options.dryRun) {
|
|
248
|
+
logger.info(` [DRY RUN] Would remove log: ${item.name}`);
|
|
249
|
+
} else {
|
|
250
|
+
try {
|
|
251
|
+
logger.info(` Removing log: ${item.name}...`);
|
|
252
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
253
|
+
} catch (e: any) {
|
|
254
|
+
logger.error(` Failed to remove log ${item.name}: ${e.message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
171
258
|
} else {
|
|
172
|
-
|
|
173
|
-
logger.info(`
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
259
|
+
if (options.dryRun) {
|
|
260
|
+
logger.info(` [DRY RUN] Would remove logs directory: ${logsDir}`);
|
|
261
|
+
} else {
|
|
262
|
+
try {
|
|
263
|
+
logger.info(` Removing logs...`);
|
|
264
|
+
fs.rmSync(logsDir, { recursive: true, force: true });
|
|
265
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
266
|
+
logger.info(` Logs cleared.`);
|
|
267
|
+
} catch (e: any) {
|
|
268
|
+
logger.error(` Failed to clean logs: ${e.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function cleanTasks(config: any, options: CleanOptions) {
|
|
275
|
+
const tasksDir = getTasksDir(config);
|
|
276
|
+
logger.info(`\nChecking tasks in ${tasksDir}...`);
|
|
277
|
+
|
|
278
|
+
if (!fs.existsSync(tasksDir)) {
|
|
279
|
+
logger.info(' Tasks directory does not exist.');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// If keepLatest is set, keep the most recent task directory/file
|
|
284
|
+
if (options.keepLatest) {
|
|
285
|
+
const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
|
|
286
|
+
// Skip example task if it exists and there are other tasks
|
|
287
|
+
let items = entries
|
|
288
|
+
.filter(entry => entry.name !== 'example')
|
|
289
|
+
.map(entry => ({
|
|
290
|
+
name: entry.name,
|
|
291
|
+
path: path.join(tasksDir, entry.name),
|
|
292
|
+
isDir: entry.isDirectory(),
|
|
293
|
+
mtime: getModTime(path.join(tasksDir, entry.name))
|
|
294
|
+
}));
|
|
295
|
+
|
|
296
|
+
if (items.length <= 1) {
|
|
297
|
+
logger.info(' Only one or no user task entries found, nothing to clean.');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Sort by modification time (newest first)
|
|
302
|
+
items.sort((a, b) => b.mtime - a.mtime);
|
|
303
|
+
const kept = items[0];
|
|
304
|
+
const toRemove = items.slice(1);
|
|
305
|
+
|
|
306
|
+
logger.info(` Keeping latest task: ${kept.name}`);
|
|
307
|
+
|
|
308
|
+
for (const item of toRemove) {
|
|
309
|
+
if (options.dryRun) {
|
|
310
|
+
logger.info(` [DRY RUN] Would remove task: ${item.name}`);
|
|
311
|
+
} else {
|
|
312
|
+
try {
|
|
313
|
+
logger.info(` Removing task: ${item.name}...`);
|
|
314
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
315
|
+
} catch (e: any) {
|
|
316
|
+
logger.error(` Failed to remove task ${item.name}: ${e.message}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
if (options.dryRun) {
|
|
322
|
+
logger.info(` [DRY RUN] Would remove tasks in directory: ${tasksDir} (except example)`);
|
|
323
|
+
} else {
|
|
324
|
+
try {
|
|
325
|
+
const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
|
|
326
|
+
for (const entry of entries) {
|
|
327
|
+
if (entry.name === 'example') continue;
|
|
328
|
+
const itemPath = path.join(tasksDir, entry.name);
|
|
329
|
+
logger.info(` Removing task: ${entry.name}...`);
|
|
330
|
+
fs.rmSync(itemPath, { recursive: true, force: true });
|
|
331
|
+
}
|
|
332
|
+
logger.info(` Tasks cleared.`);
|
|
333
|
+
} catch (e: any) {
|
|
334
|
+
logger.error(` Failed to clean tasks: ${e.message}`);
|
|
335
|
+
}
|
|
179
336
|
}
|
|
180
337
|
}
|
|
181
338
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ const COMMANDS: Record<string, CommandFn> = {
|
|
|
20
20
|
doctor: require('./doctor'),
|
|
21
21
|
signal: require('./signal'),
|
|
22
22
|
models: require('./models'),
|
|
23
|
+
logs: require('./logs'),
|
|
23
24
|
setup: require('./setup-commands').main,
|
|
24
25
|
'setup-commands': require('./setup-commands').main,
|
|
25
26
|
};
|
|
@@ -37,10 +38,11 @@ function printHelp(): void {
|
|
|
37
38
|
\x1b[33mrun\x1b[0m <tasks-dir> [options] Run orchestration (DAG-based)
|
|
38
39
|
\x1b[33mmonitor\x1b[0m [run-dir] [options] \x1b[36mInteractive\x1b[0m lane dashboard
|
|
39
40
|
\x1b[33mclean\x1b[0m <type> [options] Clean branches/worktrees/logs
|
|
40
|
-
\x1b[33mresume\x1b[0m
|
|
41
|
+
\x1b[33mresume\x1b[0m [lane] [options] Resume lane(s) - use --all for batch resume
|
|
41
42
|
\x1b[33mdoctor\x1b[0m [options] Check environment and preflight
|
|
42
43
|
\x1b[33msignal\x1b[0m <lane> <msg> Directly intervene in a running lane
|
|
43
44
|
\x1b[33mmodels\x1b[0m [options] List available AI models
|
|
45
|
+
\x1b[33mlogs\x1b[0m [run-dir] [options] View, export, and follow logs
|
|
44
46
|
|
|
45
47
|
\x1b[1mGLOBAL OPTIONS\x1b[0m
|
|
46
48
|
--config <path> Config file path
|
|
@@ -114,3 +116,4 @@ if (require.main === module) {
|
|
|
114
116
|
|
|
115
117
|
export default main;
|
|
116
118
|
export { main };
|
|
119
|
+
export { events } from '../utils/events';
|