@litmers/cursorflow-orchestrator 0.1.34 → 0.1.36
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 +27 -0
- package/README.md +38 -7
- package/commands/cursorflow-doctor.md +45 -23
- package/commands/cursorflow-run.md +60 -111
- package/dist/cli/doctor.js +47 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/logs.js +10 -1
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +12 -4
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/resume.js +46 -21
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +33 -8
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stop.js +6 -0
- package/dist/cli/stop.js.map +1 -1
- package/dist/cli/tasks.d.ts +5 -3
- package/dist/cli/tasks.js +180 -27
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/orchestrator.js +6 -5
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/services/logging/console.js +2 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/utils/config.d.ts +5 -1
- package/dist/utils/config.js +8 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/doctor.js +40 -8
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +1 -1
- package/dist/utils/enhanced-logger.js +3 -2
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/flow.d.ts +9 -0
- package/dist/utils/flow.js +73 -0
- package/dist/utils/flow.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/doctor.ts +48 -4
- package/src/cli/logs.ts +13 -2
- package/src/cli/monitor.ts +16 -5
- package/src/cli/resume.ts +48 -20
- package/src/cli/run.ts +31 -9
- package/src/cli/stop.ts +8 -0
- package/src/cli/tasks.ts +199 -19
- package/src/core/orchestrator.ts +6 -5
- package/src/services/logging/console.ts +2 -1
- package/src/utils/config.ts +8 -1
- package/src/utils/doctor.ts +36 -8
- package/src/utils/enhanced-logger.ts +3 -2
- package/src/utils/flow.ts +42 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findFlowDir = findFlowDir;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path_1 = require("./path");
|
|
39
|
+
/**
|
|
40
|
+
* Find flow directory by name in the flows directory.
|
|
41
|
+
* Matches by exact name or by suffix (ignoring ID prefix like '001_').
|
|
42
|
+
*
|
|
43
|
+
* @param flowsDir The base flows directory (e.g., _cursorflow/flows)
|
|
44
|
+
* @param flowName The name of the flow to find
|
|
45
|
+
* @returns The absolute path to the flow directory, or null if not found
|
|
46
|
+
*/
|
|
47
|
+
function findFlowDir(flowsDir, flowName) {
|
|
48
|
+
if (!fs.existsSync(flowsDir)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const dirs = fs.readdirSync(flowsDir)
|
|
52
|
+
.filter(name => {
|
|
53
|
+
const dirPath = (0, path_1.safeJoin)(flowsDir, name);
|
|
54
|
+
try {
|
|
55
|
+
return fs.statSync(dirPath).isDirectory();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter(name => {
|
|
62
|
+
// Match by exact name or by suffix (ignoring ID prefix)
|
|
63
|
+
const match = name.match(/^\d+_(.+)$/);
|
|
64
|
+
return match ? match[1] === flowName : name === flowName;
|
|
65
|
+
});
|
|
66
|
+
if (dirs.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
// Return the most recent one (highest ID / alphabetical)
|
|
70
|
+
dirs.sort((a, b) => b.localeCompare(a));
|
|
71
|
+
return (0, path_1.safeJoin)(flowsDir, dirs[0]);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../../src/utils/flow.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,kCA2BC;AAvCD,uCAAyB;AAEzB,iCAAkC;AAElC;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,QAAgB,EAAE,QAAgB;IAC5D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC;SAClC,MAAM,CAAC,IAAI,CAAC,EAAE;QACb,MAAM,OAAO,GAAG,IAAA,eAAQ,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC;YACH,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;SACD,MAAM,CAAC,IAAI,CAAC,EAAE;QACb,wDAAwD;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEL,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,IAAA,eAAQ,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;AACtC,CAAC"}
|
package/package.json
CHANGED
package/src/cli/doctor.ts
CHANGED
|
@@ -13,12 +13,18 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import * as logger from '../utils/logger';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as fs from 'fs';
|
|
16
18
|
import { runDoctor, saveDoctorStatus } from '../utils/doctor';
|
|
17
19
|
import { runInteractiveAgentTest } from '../utils/cursor-agent';
|
|
20
|
+
import { loadConfig, findProjectRoot } from '../utils/config';
|
|
21
|
+
import { safeJoin } from '../utils/path';
|
|
22
|
+
import { findFlowDir } from '../utils/flow';
|
|
18
23
|
|
|
19
24
|
interface DoctorCliOptions {
|
|
20
25
|
json: boolean;
|
|
21
26
|
tasksDir: string | null;
|
|
27
|
+
flowOrTaskName: string | null;
|
|
22
28
|
executor: string | null;
|
|
23
29
|
includeCursorAgentChecks: boolean;
|
|
24
30
|
testAgent: boolean;
|
|
@@ -26,13 +32,16 @@ interface DoctorCliOptions {
|
|
|
26
32
|
|
|
27
33
|
function printHelp(): void {
|
|
28
34
|
console.log(`
|
|
29
|
-
Usage: cursorflow doctor [options]
|
|
35
|
+
Usage: cursorflow doctor [flow-name] [options]
|
|
30
36
|
|
|
31
37
|
Verify your environment is ready for CursorFlow runs.
|
|
32
38
|
|
|
39
|
+
Arguments:
|
|
40
|
+
[flow-name] Flow or task name to validate (optional)
|
|
41
|
+
|
|
33
42
|
Options:
|
|
34
43
|
--json Output machine-readable JSON
|
|
35
|
-
--tasks-dir <path>
|
|
44
|
+
--tasks-dir <path> Validate specific directory (legacy)
|
|
36
45
|
--executor <type> cursor-agent | cloud
|
|
37
46
|
--no-cursor Skip Cursor Agent install/auth checks
|
|
38
47
|
--test-agent Run interactive agent test (to approve MCP/permissions)
|
|
@@ -40,18 +49,30 @@ Options:
|
|
|
40
49
|
|
|
41
50
|
Examples:
|
|
42
51
|
cursorflow doctor
|
|
52
|
+
cursorflow doctor TestFeature
|
|
43
53
|
cursorflow doctor --test-agent
|
|
44
|
-
cursorflow doctor --tasks-dir _cursorflow/
|
|
54
|
+
cursorflow doctor --tasks-dir _cursorflow/flows/001_TestFeature
|
|
45
55
|
`);
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
function parseArgs(args: string[]): DoctorCliOptions {
|
|
49
59
|
const tasksDirIdx = args.indexOf('--tasks-dir');
|
|
50
60
|
const executorIdx = args.indexOf('--executor');
|
|
61
|
+
|
|
62
|
+
// Find positional argument (flow/task name)
|
|
63
|
+
// Exclude args that are values for options
|
|
64
|
+
const optionValueIndices = new Set<number>();
|
|
65
|
+
if (tasksDirIdx >= 0 && tasksDirIdx + 1 < args.length) optionValueIndices.add(tasksDirIdx + 1);
|
|
66
|
+
if (executorIdx >= 0 && executorIdx + 1 < args.length) optionValueIndices.add(executorIdx + 1);
|
|
67
|
+
|
|
68
|
+
const flowOrTaskName = args.find((arg, idx) =>
|
|
69
|
+
!arg.startsWith('--') && !optionValueIndices.has(idx)
|
|
70
|
+
) || null;
|
|
51
71
|
|
|
52
72
|
const options: DoctorCliOptions = {
|
|
53
73
|
json: args.includes('--json'),
|
|
54
74
|
tasksDir: tasksDirIdx >= 0 ? (args[tasksDirIdx + 1] || null) : null,
|
|
75
|
+
flowOrTaskName,
|
|
55
76
|
executor: executorIdx >= 0 ? (args[executorIdx + 1] || null) : null,
|
|
56
77
|
includeCursorAgentChecks: !args.includes('--no-cursor'),
|
|
57
78
|
testAgent: args.includes('--test-agent'),
|
|
@@ -117,9 +138,32 @@ async function doctor(args: string[]): Promise<void> {
|
|
|
117
138
|
process.exit(success ? 0 : 1);
|
|
118
139
|
}
|
|
119
140
|
|
|
141
|
+
// Resolve tasksDir from flow name if provided
|
|
142
|
+
let tasksDir = options.tasksDir;
|
|
143
|
+
if (!tasksDir && options.flowOrTaskName) {
|
|
144
|
+
try {
|
|
145
|
+
const config = loadConfig();
|
|
146
|
+
const flowsDir = safeJoin(config.projectRoot, config.flowsDir);
|
|
147
|
+
const foundFlow = findFlowDir(flowsDir, options.flowOrTaskName);
|
|
148
|
+
if (foundFlow) {
|
|
149
|
+
tasksDir = foundFlow;
|
|
150
|
+
} else {
|
|
151
|
+
// Try as a direct path
|
|
152
|
+
const directPath = path.resolve(process.cwd(), options.flowOrTaskName);
|
|
153
|
+
if (fs.existsSync(directPath)) {
|
|
154
|
+
tasksDir = directPath;
|
|
155
|
+
} else {
|
|
156
|
+
tasksDir = options.flowOrTaskName;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
tasksDir = options.flowOrTaskName;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
120
164
|
const report = runDoctor({
|
|
121
165
|
cwd: process.cwd(),
|
|
122
|
-
tasksDir:
|
|
166
|
+
tasksDir: tasksDir || undefined,
|
|
123
167
|
executor: options.executor || undefined,
|
|
124
168
|
includeCursorAgentChecks: options.includeCursorAgentChecks,
|
|
125
169
|
});
|
package/src/cli/logs.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as logger from '../utils/logger';
|
|
8
|
-
import { loadConfig } from '../utils/config';
|
|
8
|
+
import { loadConfig, getLogsDir } from '../utils/config';
|
|
9
9
|
import { safeJoin } from '../utils/path';
|
|
10
10
|
import {
|
|
11
11
|
readJsonLog,
|
|
@@ -808,11 +808,22 @@ async function logs(args: string[]): Promise<void> {
|
|
|
808
808
|
}
|
|
809
809
|
|
|
810
810
|
const config = loadConfig();
|
|
811
|
+
const originalCwd = process.cwd();
|
|
812
|
+
|
|
813
|
+
// Change current directory to project root for consistent path handling
|
|
814
|
+
if (config.projectRoot !== originalCwd) {
|
|
815
|
+
logger.debug(`Changing directory to project root: ${config.projectRoot}`);
|
|
816
|
+
process.chdir(config.projectRoot);
|
|
817
|
+
}
|
|
811
818
|
|
|
812
819
|
// Find run directory
|
|
813
820
|
let runDir = options.runDir;
|
|
821
|
+
if (runDir && runDir !== 'latest' && !path.isAbsolute(runDir)) {
|
|
822
|
+
runDir = path.resolve(originalCwd, runDir);
|
|
823
|
+
}
|
|
824
|
+
|
|
814
825
|
if (!runDir || runDir === 'latest') {
|
|
815
|
-
runDir = findLatestRunDir(config
|
|
826
|
+
runDir = findLatestRunDir(getLogsDir(config)) || undefined;
|
|
816
827
|
}
|
|
817
828
|
|
|
818
829
|
if (!runDir || !fs.existsSync(runDir)) {
|
package/src/cli/monitor.ts
CHANGED
|
@@ -14,7 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as readline from 'readline';
|
|
15
15
|
import { loadState, readLog } from '../utils/state';
|
|
16
16
|
import { LaneState, ConversationEntry } from '../utils/types';
|
|
17
|
-
import { loadConfig } from '../utils/config';
|
|
17
|
+
import { loadConfig, getLogsDir } from '../utils/config';
|
|
18
18
|
import { safeJoin } from '../utils/path';
|
|
19
19
|
import { getLaneProcessStatus, getFlowSummary, LaneProcessStatus } from '../services/process';
|
|
20
20
|
import { LogBufferService, BufferedLogEntry } from '../services/logging/buffer';
|
|
@@ -142,6 +142,13 @@ class InteractiveMonitor {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
constructor(runDir: string, interval: number, logsDir?: string, initialView: View = View.LIST) {
|
|
145
|
+
const config = loadConfig();
|
|
146
|
+
|
|
147
|
+
// Change current directory to project root for consistent path handling
|
|
148
|
+
if (config.projectRoot !== process.cwd()) {
|
|
149
|
+
process.chdir(config.projectRoot);
|
|
150
|
+
}
|
|
151
|
+
|
|
145
152
|
this.runDir = runDir;
|
|
146
153
|
this.interval = interval;
|
|
147
154
|
this.view = initialView;
|
|
@@ -150,8 +157,7 @@ class InteractiveMonitor {
|
|
|
150
157
|
if (logsDir) {
|
|
151
158
|
this.logsDir = logsDir;
|
|
152
159
|
} else {
|
|
153
|
-
|
|
154
|
-
this.logsDir = safeJoin(config.logsDir, 'runs');
|
|
160
|
+
this.logsDir = safeJoin(getLogsDir(config), 'runs');
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
// Initialize unified log buffer
|
|
@@ -1775,15 +1781,20 @@ async function monitor(args: string[]): Promise<void> {
|
|
|
1775
1781
|
const interval = intervalIdx >= 0 ? parseInt(args[intervalIdx + 1] || '2') || 2 : 2;
|
|
1776
1782
|
|
|
1777
1783
|
const runDirArg = args.find(arg => !arg.startsWith('--') && args.indexOf(arg) !== intervalIdx + 1);
|
|
1784
|
+
const originalCwd = process.cwd();
|
|
1778
1785
|
const config = loadConfig();
|
|
1779
1786
|
|
|
1780
1787
|
let runDir = runDirArg;
|
|
1788
|
+
if (runDir && runDir !== 'latest' && !path.isAbsolute(runDir)) {
|
|
1789
|
+
runDir = path.resolve(originalCwd, runDir);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1781
1792
|
if (!runDir || runDir === 'latest') {
|
|
1782
|
-
runDir = findLatestRunDir(config
|
|
1793
|
+
runDir = findLatestRunDir(getLogsDir(config)) || undefined;
|
|
1783
1794
|
if (!runDir && !list) throw new Error('No run directories found');
|
|
1784
1795
|
if (!runDir && list) {
|
|
1785
1796
|
// Create a dummy runDir if none exists but we want to see the list (dashboard will handle empty list)
|
|
1786
|
-
runDir = path.join(config
|
|
1797
|
+
runDir = path.join(getLogsDir(config), 'runs', 'empty');
|
|
1787
1798
|
}
|
|
1788
1799
|
}
|
|
1789
1800
|
|
package/src/cli/resume.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { loadState, saveState } from '../utils/state';
|
|
|
11
11
|
import { LaneState } from '../types';
|
|
12
12
|
import { runDoctor } from '../utils/doctor';
|
|
13
13
|
import { safeJoin } from '../utils/path';
|
|
14
|
+
import { findFlowDir } from '../utils/flow';
|
|
14
15
|
import {
|
|
15
16
|
EnhancedLogManager,
|
|
16
17
|
createLogManager,
|
|
@@ -431,9 +432,10 @@ function spawnLaneResume(
|
|
|
431
432
|
runnerArgs.push('--executor', options.executor);
|
|
432
433
|
}
|
|
433
434
|
|
|
435
|
+
const shortLaneName = laneName.substring(0, 10);
|
|
434
436
|
const logManager = createLogManager(laneDir, laneName, options.enhancedLogConfig || {}, (msg) => {
|
|
435
437
|
const formatted = formatMessageForConsole(msg, {
|
|
436
|
-
laneLabel: `[${
|
|
438
|
+
laneLabel: `[${shortLaneName}]`,
|
|
437
439
|
includeTimestamp: true
|
|
438
440
|
});
|
|
439
441
|
process.stdout.write(formatted + '\n');
|
|
@@ -632,8 +634,19 @@ async function resume(args: string[]): Promise<void> {
|
|
|
632
634
|
|
|
633
635
|
const config = loadConfig();
|
|
634
636
|
const logsDir = getLogsDir(config);
|
|
637
|
+
const originalCwd = process.cwd();
|
|
638
|
+
|
|
639
|
+
// Change current directory to project root for consistent path handling
|
|
640
|
+
if (config.projectRoot !== originalCwd) {
|
|
641
|
+
logger.debug(`Changing directory to project root: ${config.projectRoot}`);
|
|
642
|
+
process.chdir(config.projectRoot);
|
|
643
|
+
}
|
|
635
644
|
|
|
636
645
|
let runDir = options.runDir;
|
|
646
|
+
if (runDir && !path.isAbsolute(runDir)) {
|
|
647
|
+
runDir = path.resolve(originalCwd, runDir);
|
|
648
|
+
}
|
|
649
|
+
|
|
637
650
|
if (!runDir) {
|
|
638
651
|
runDir = findLatestRunDir(logsDir);
|
|
639
652
|
}
|
|
@@ -645,30 +658,45 @@ async function resume(args: string[]): Promise<void> {
|
|
|
645
658
|
const allLanes = getAllLaneStatuses(runDir);
|
|
646
659
|
let lanesToResume: LaneInfo[] = [];
|
|
647
660
|
|
|
648
|
-
// Check if the lane argument is actually a tasks directory
|
|
649
|
-
if (options.lane
|
|
650
|
-
|
|
651
|
-
|
|
661
|
+
// Check if the lane argument is actually a tasks directory or a flow name
|
|
662
|
+
if (options.lane) {
|
|
663
|
+
let tasksDir = '';
|
|
664
|
+
const lanePathAbs = path.resolve(originalCwd, options.lane);
|
|
652
665
|
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
logger.info(`Resuming ${lanesToResume.length} lane(s) from this directory.`);
|
|
666
|
+
if (fs.existsSync(lanePathAbs) && fs.statSync(lanePathAbs).isDirectory()) {
|
|
667
|
+
tasksDir = lanePathAbs;
|
|
656
668
|
} else {
|
|
657
|
-
|
|
658
|
-
|
|
669
|
+
// Try finding in flowsDir
|
|
670
|
+
const flowsDir = safeJoin(config.projectRoot, config.flowsDir);
|
|
671
|
+
const foundFlow = findFlowDir(flowsDir, options.lane);
|
|
672
|
+
if (foundFlow) {
|
|
673
|
+
tasksDir = foundFlow;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (tasksDir) {
|
|
678
|
+
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile && path.resolve(l.state.tasksFile).startsWith(tasksDir));
|
|
679
|
+
|
|
680
|
+
if (lanesToResume.length > 0) {
|
|
681
|
+
logger.info(`📂 Flow/Task directory detected: ${options.lane}`);
|
|
682
|
+
logger.info(`Resuming ${lanesToResume.length} lane(s) from this directory.`);
|
|
683
|
+
} else {
|
|
684
|
+
logger.warn(`No incomplete lanes found using tasks from directory: ${options.lane}`);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
const lane = allLanes.find(l => l.name === options.lane);
|
|
689
|
+
if (!lane) {
|
|
690
|
+
throw new Error(`Lane '${options.lane}' not found in run directory.`);
|
|
691
|
+
}
|
|
692
|
+
if (!lane.needsResume) {
|
|
693
|
+
logger.success(`Lane '${options.lane}' is already completed.`);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
lanesToResume = [lane];
|
|
659
697
|
}
|
|
660
698
|
} else if (options.all) {
|
|
661
699
|
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile);
|
|
662
|
-
} else if (options.lane) {
|
|
663
|
-
const lane = allLanes.find(l => l.name === options.lane);
|
|
664
|
-
if (!lane) {
|
|
665
|
-
throw new Error(`Lane '${options.lane}' not found in run directory.`);
|
|
666
|
-
}
|
|
667
|
-
if (!lane.needsResume) {
|
|
668
|
-
logger.success(`Lane '${options.lane}' is already completed.`);
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
lanesToResume = [lane];
|
|
672
700
|
}
|
|
673
701
|
|
|
674
702
|
// Check for zombie lanes
|
package/src/cli/run.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { getLogsDir, loadConfig } from '../utils/config';
|
|
|
10
10
|
import { runDoctor, getDoctorStatus } from '../utils/doctor';
|
|
11
11
|
import { areCommandsInstalled, setupCommands } from './setup-commands';
|
|
12
12
|
import { safeJoin } from '../utils/path';
|
|
13
|
+
import { findFlowDir } from '../utils/flow';
|
|
13
14
|
import { loadState } from '../utils/state';
|
|
14
15
|
import { LaneState } from '../types';
|
|
15
16
|
|
|
@@ -191,19 +192,40 @@ async function run(args: string[]): Promise<void> {
|
|
|
191
192
|
|
|
192
193
|
const config = loadConfig();
|
|
193
194
|
const logsDir = getLogsDir(config);
|
|
195
|
+
const originalCwd = process.cwd();
|
|
196
|
+
|
|
197
|
+
// Change current directory to project root for consistent path handling
|
|
198
|
+
if (config.projectRoot !== originalCwd) {
|
|
199
|
+
logger.debug(`Changing directory to project root: ${config.projectRoot}`);
|
|
200
|
+
process.chdir(config.projectRoot);
|
|
201
|
+
}
|
|
194
202
|
|
|
195
203
|
// Resolve tasks dir:
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
// 1. Prefer the exact path if it exists relative to original cwd
|
|
205
|
+
// 2. Search in flowsDir by name
|
|
206
|
+
// 3. Fall back to projectRoot-relative tasksDir for backward compatibility
|
|
207
|
+
let tasksDir = '';
|
|
208
|
+
if (path.isAbsolute(options.tasksDir)) {
|
|
209
|
+
tasksDir = options.tasksDir;
|
|
210
|
+
} else {
|
|
211
|
+
const relPath = path.resolve(originalCwd, options.tasksDir);
|
|
212
|
+
if (fs.existsSync(relPath)) {
|
|
213
|
+
tasksDir = relPath;
|
|
214
|
+
} else {
|
|
215
|
+
// Try finding in flowsDir
|
|
216
|
+
const flowsDir = safeJoin(config.projectRoot, config.flowsDir);
|
|
217
|
+
const foundFlow = findFlowDir(flowsDir, options.tasksDir);
|
|
218
|
+
if (foundFlow) {
|
|
219
|
+
tasksDir = foundFlow;
|
|
220
|
+
} else {
|
|
221
|
+
// Fallback to legacy tasksDir
|
|
222
|
+
tasksDir = safeJoin(config.projectRoot, options.tasksDir);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
204
226
|
|
|
205
227
|
if (!fs.existsSync(tasksDir)) {
|
|
206
|
-
throw new Error(`Tasks directory not found: ${tasksDir}`);
|
|
228
|
+
throw new Error(`Tasks or Flow directory not found: ${options.tasksDir} (resolved to: ${tasksDir})`);
|
|
207
229
|
}
|
|
208
230
|
|
|
209
231
|
// Check for existing incomplete run and auto-resume
|
package/src/cli/stop.ts
CHANGED
|
@@ -86,7 +86,15 @@ async function stop(args: string[]): Promise<void> {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const originalCwd = process.cwd();
|
|
89
90
|
const config = loadConfig();
|
|
91
|
+
|
|
92
|
+
// Change current directory to project root for consistent path handling
|
|
93
|
+
if (config.projectRoot !== originalCwd) {
|
|
94
|
+
logger.debug(`Changing directory to project root: ${config.projectRoot}`);
|
|
95
|
+
process.chdir(config.projectRoot);
|
|
96
|
+
}
|
|
97
|
+
|
|
90
98
|
const logsDir = getLogsDir(config);
|
|
91
99
|
const runsDir = safeJoin(logsDir, 'runs');
|
|
92
100
|
const runService = new RunService(runsDir);
|