@litmers/cursorflow-orchestrator 0.1.15 → 0.1.20
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 +23 -1
- package/README.md +26 -7
- package/commands/cursorflow-run.md +2 -0
- package/commands/cursorflow-triggers.md +250 -0
- package/dist/cli/clean.js +8 -7
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +20 -14
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +64 -47
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +27 -17
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +73 -33
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +193 -40
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +3 -2
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +7 -7
- package/dist/cli/signal.js.map +1 -1
- package/dist/core/orchestrator.d.ts +2 -1
- package/dist/core/orchestrator.js +54 -93
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +6 -4
- package/dist/core/reviewer.js +7 -5
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +8 -0
- package/dist/core/runner.js +219 -32
- package/dist/core/runner.js.map +1 -1
- package/dist/utils/config.js +20 -10
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/doctor.js +35 -7
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +2 -2
- package/dist/utils/enhanced-logger.js +114 -43
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.js +163 -10
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +16 -0
- package/dist/utils/log-formatter.js +194 -0
- package/dist/utils/log-formatter.js.map +1 -0
- package/dist/utils/path.d.ts +19 -0
- package/dist/utils/path.js +77 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/repro-thinking-logs.d.ts +1 -0
- package/dist/utils/repro-thinking-logs.js +80 -0
- package/dist/utils/repro-thinking-logs.js.map +1 -0
- package/dist/utils/state.d.ts +4 -1
- package/dist/utils/state.js +11 -8
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/template.d.ts +14 -0
- package/dist/utils/template.js +122 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/webhook.js +3 -0
- package/dist/utils/webhook.js.map +1 -1
- package/package.json +4 -2
- package/scripts/ai-security-check.js +3 -0
- package/scripts/local-security-gate.sh +9 -1
- package/scripts/verify-and-fix.sh +37 -0
- package/src/cli/clean.ts +8 -7
- package/src/cli/index.ts +5 -1
- package/src/cli/init.ts +19 -15
- package/src/cli/logs.ts +67 -47
- package/src/cli/monitor.ts +28 -18
- package/src/cli/prepare.ts +75 -35
- package/src/cli/resume.ts +810 -626
- package/src/cli/run.ts +3 -2
- package/src/cli/signal.ts +7 -6
- package/src/core/orchestrator.ts +68 -93
- package/src/core/reviewer.ts +14 -9
- package/src/core/runner.ts +229 -33
- package/src/utils/config.ts +19 -11
- package/src/utils/doctor.ts +38 -7
- package/src/utils/enhanced-logger.ts +117 -49
- package/src/utils/git.ts +145 -11
- package/src/utils/log-formatter.ts +162 -0
- package/src/utils/path.ts +45 -0
- package/src/utils/repro-thinking-logs.ts +54 -0
- package/src/utils/state.ts +16 -8
- package/src/utils/template.ts +92 -0
- package/src/utils/types.ts +13 -0
- package/src/utils/webhook.ts +3 -0
- package/templates/basic.json +21 -0
- package/scripts/simple-logging-test.sh +0 -97
- package/scripts/test-real-cursor-lifecycle.sh +0 -289
- package/scripts/test-real-logging.sh +0 -289
- package/scripts/test-streaming-multi-task.sh +0 -247
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createLogManager } from './enhanced-logger';
|
|
4
|
+
|
|
5
|
+
async function testThinkingLogs() {
|
|
6
|
+
const testDir = path.join(process.cwd(), '_test_thinking_logs');
|
|
7
|
+
if (fs.existsSync(testDir)) {
|
|
8
|
+
fs.rmSync(testDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
11
|
+
|
|
12
|
+
console.log('--- Initializing Log Manager ---');
|
|
13
|
+
const manager = createLogManager(testDir, 'test-lane-thinking', {
|
|
14
|
+
writeJsonLog: true,
|
|
15
|
+
keepRawLogs: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
manager.setTask('repro-thinking-task', 'sonnet-4.5-thinking');
|
|
19
|
+
|
|
20
|
+
const logLines = [
|
|
21
|
+
'{"type":"tool_call","subtype":"started","call_id":"0_tool_54a8fcc9-6981-4f59-aeb6-3ab6d37b2","tool_call":{"readToolCall":{"args":{"path":"/home/eugene/workbench/workbench-os-eungjin/_cursorflow/worktrees/cursorflow/run-mjfxp57i/agent_output.txt"}}}}',
|
|
22
|
+
'{"type":"thinking","subtype":"delta","text":"**Defining Installation Strategy**\\n\\nI\'ve considered the `package.json` file as the central point for installation in automated environments. Thinking now about how that impacts the user\'s ultimate goal, given this is how the process begins in these environments.\\n\\n\\n"}',
|
|
23
|
+
'{"type":"thinking","subtype":"delta","text":"**Clarifying Execution Context**\\n\\nI\'m focused on the user\'s explicit command: `pnpm add @convex-dev/agent ai @ai-sdk/google zod`. My inability to directly execute this is a key constraint. I\'m exploring ways to inform the user about this. I\'ve considered that, without the tools required to run the original command, any attempt to run a command like `grep` or `date` is pointless and will fail.\\n\\n\\n"}',
|
|
24
|
+
'{"type":"tool_call","subtype":"started","call_id":"0_tool_d8f826c8-9d8f-4cab-9ff8-1c47d1ac1","tool_call":{"shellToolCall":{"args":{"command":"date"}}}}'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
console.log('\n--- Feeding Log Lines to Manager ---');
|
|
28
|
+
for (const line of logLines) {
|
|
29
|
+
console.log('Processing:', line.substring(0, 100) + '...');
|
|
30
|
+
manager.writeStdout(line + '\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
manager.close();
|
|
34
|
+
|
|
35
|
+
console.log('\n--- Verifying terminal-readable.log ---');
|
|
36
|
+
const readableLog = fs.readFileSync(path.join(testDir, 'terminal-readable.log'), 'utf8');
|
|
37
|
+
console.log(readableLog);
|
|
38
|
+
|
|
39
|
+
console.log('\n--- Verifying terminal.jsonl (last 3 entries) ---');
|
|
40
|
+
const jsonlLog = fs.readFileSync(path.join(testDir, 'terminal.jsonl'), 'utf8');
|
|
41
|
+
const lines = jsonlLog.trim().split('\n');
|
|
42
|
+
for (const line of lines.slice(-3)) {
|
|
43
|
+
const parsed = JSON.parse(line);
|
|
44
|
+
console.log(JSON.stringify({
|
|
45
|
+
level: parsed.level,
|
|
46
|
+
message: parsed.message.substring(0, 50) + '...',
|
|
47
|
+
hasMetadata: !!parsed.metadata,
|
|
48
|
+
metadataType: parsed.metadata?.type
|
|
49
|
+
}, null, 2));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
testThinkingLogs().catch(console.error);
|
|
54
|
+
|
package/src/utils/state.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
+
import { safeJoin } from './path';
|
|
7
8
|
import {
|
|
8
9
|
LaneState,
|
|
9
10
|
ConversationEntry,
|
|
@@ -80,18 +81,25 @@ export function readLog<T = any>(logPath: string): T[] {
|
|
|
80
81
|
/**
|
|
81
82
|
* Create initial lane state
|
|
82
83
|
*/
|
|
83
|
-
export function createLaneState(
|
|
84
|
+
export function createLaneState(
|
|
85
|
+
laneName: string,
|
|
86
|
+
config: RunnerConfig,
|
|
87
|
+
tasksFile?: string,
|
|
88
|
+
options: { pipelineBranch?: string; worktreeDir?: string } = {}
|
|
89
|
+
): LaneState {
|
|
84
90
|
return {
|
|
85
91
|
label: laneName,
|
|
86
92
|
status: 'pending',
|
|
87
93
|
currentTaskIndex: 0,
|
|
88
94
|
totalTasks: config.tasks ? config.tasks.length : 0,
|
|
89
|
-
worktreeDir: null,
|
|
90
|
-
pipelineBranch: null,
|
|
95
|
+
worktreeDir: options.worktreeDir || null,
|
|
96
|
+
pipelineBranch: options.pipelineBranch || null,
|
|
91
97
|
startTime: Date.now(),
|
|
92
98
|
endTime: null,
|
|
93
99
|
error: null,
|
|
94
100
|
dependencyRequest: null,
|
|
101
|
+
tasksFile,
|
|
102
|
+
dependsOn: config.dependsOn || [],
|
|
95
103
|
};
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -151,7 +159,7 @@ export function getLatestRunDir(logsDir: string): string | null {
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
const runs = fs.readdirSync(logsDir)
|
|
154
|
-
.filter(f => fs.statSync(
|
|
162
|
+
.filter(f => fs.statSync(safeJoin(logsDir, f)).isDirectory())
|
|
155
163
|
.sort()
|
|
156
164
|
.reverse();
|
|
157
165
|
|
|
@@ -159,7 +167,7 @@ export function getLatestRunDir(logsDir: string): string | null {
|
|
|
159
167
|
return null;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
return
|
|
170
|
+
return safeJoin(logsDir, runs[0]!);
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
/**
|
|
@@ -171,11 +179,11 @@ export function listLanesInRun(runDir: string): { name: string; dir: string; sta
|
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
return fs.readdirSync(runDir)
|
|
174
|
-
.filter(f => fs.statSync(
|
|
182
|
+
.filter(f => fs.statSync(safeJoin(runDir, f)).isDirectory())
|
|
175
183
|
.map(laneName => ({
|
|
176
184
|
name: laneName,
|
|
177
|
-
dir:
|
|
178
|
-
statePath:
|
|
185
|
+
dir: safeJoin(runDir, laneName),
|
|
186
|
+
statePath: safeJoin(runDir, laneName, 'state.json'),
|
|
179
187
|
}));
|
|
180
188
|
}
|
|
181
189
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template loading utilities for CursorFlow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as https from 'https';
|
|
8
|
+
import * as http from 'http';
|
|
9
|
+
import * as logger from './logger';
|
|
10
|
+
import { safeJoin } from './path';
|
|
11
|
+
import { findProjectRoot } from './config';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetch remote template from URL
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchRemoteTemplate(url: string): Promise<any> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
19
|
+
|
|
20
|
+
protocol.get(url, (res) => {
|
|
21
|
+
if (res.statusCode !== 200) {
|
|
22
|
+
reject(new Error(`Failed to fetch template from ${url}: Status ${res.statusCode}`));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let data = '';
|
|
27
|
+
res.on('data', (chunk) => {
|
|
28
|
+
data += chunk;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
res.on('end', () => {
|
|
32
|
+
try {
|
|
33
|
+
resolve(JSON.parse(data));
|
|
34
|
+
} catch (e) {
|
|
35
|
+
reject(new Error(`Failed to parse template JSON from ${url}: ${e}`));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}).on('error', (err) => {
|
|
39
|
+
reject(new Error(`Network error while fetching template from ${url}: ${err.message}`));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve template from various sources:
|
|
46
|
+
* 1. URL (starts with http:// or https://)
|
|
47
|
+
* 2. Built-in template (name without .json)
|
|
48
|
+
* 3. Local file path
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveTemplate(templatePath: string): Promise<any> {
|
|
51
|
+
// 1. Remote URL
|
|
52
|
+
if (templatePath.startsWith('http://') || templatePath.startsWith('https://')) {
|
|
53
|
+
logger.info(`Fetching remote template: ${templatePath}`);
|
|
54
|
+
return fetchRemoteTemplate(templatePath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Built-in template
|
|
58
|
+
// Search in templates/ directory of the project root
|
|
59
|
+
try {
|
|
60
|
+
const projectRoot = findProjectRoot();
|
|
61
|
+
const builtInPath = safeJoin(projectRoot, 'templates', templatePath.endsWith('.json') ? templatePath : `${templatePath}.json`);
|
|
62
|
+
if (fs.existsSync(builtInPath)) {
|
|
63
|
+
logger.info(`Using built-in template: ${templatePath}`);
|
|
64
|
+
return JSON.parse(fs.readFileSync(builtInPath, 'utf8'));
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Ignore error if project root not found, try other methods
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fallback for built-in templates relative to the module (for installed package)
|
|
71
|
+
const templatesDir = path.resolve(__dirname, '../../templates');
|
|
72
|
+
const templateFileName = templatePath.endsWith('.json') ? templatePath : `${templatePath}.json`;
|
|
73
|
+
const modulePath = safeJoin(templatesDir, templateFileName);
|
|
74
|
+
if (fs.existsSync(modulePath)) {
|
|
75
|
+
logger.info(`Using module template: ${templatePath}`);
|
|
76
|
+
return JSON.parse(fs.readFileSync(modulePath, 'utf8'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Local file path
|
|
80
|
+
const localPath = safeJoin(process.cwd(), templatePath);
|
|
81
|
+
if (fs.existsSync(localPath)) {
|
|
82
|
+
logger.info(`Using local template: ${templatePath}`);
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(fs.readFileSync(localPath, 'utf8'));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw new Error(`Failed to parse local template ${templatePath}: ${e}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw new Error(`Template not found: ${templatePath}. It must be a URL, a built-in template name, or a local file path.`);
|
|
91
|
+
}
|
|
92
|
+
|
package/src/utils/types.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface CursorFlowConfig {
|
|
|
18
18
|
lockfileReadOnly: boolean;
|
|
19
19
|
enableReview: boolean;
|
|
20
20
|
reviewModel: string;
|
|
21
|
+
reviewAllTasks?: boolean;
|
|
21
22
|
maxReviewIterations: number;
|
|
22
23
|
defaultLaneConfig: LaneConfig;
|
|
23
24
|
logLevel: string;
|
|
@@ -189,20 +190,27 @@ export interface Task {
|
|
|
189
190
|
model?: string;
|
|
190
191
|
/** Acceptance criteria for the AI reviewer to validate */
|
|
191
192
|
acceptanceCriteria?: string[];
|
|
193
|
+
/** Task-level dependencies (format: "lane:task") */
|
|
194
|
+
dependsOn?: string[];
|
|
195
|
+
/** Task execution timeout in milliseconds. Overrides lane-level timeout. */
|
|
196
|
+
timeout?: number;
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
export interface RunnerConfig {
|
|
195
200
|
tasks: Task[];
|
|
196
201
|
dependsOn?: string[];
|
|
197
202
|
pipelineBranch?: string;
|
|
203
|
+
worktreeDir?: string;
|
|
198
204
|
branchPrefix?: string;
|
|
199
205
|
worktreeRoot?: string;
|
|
200
206
|
baseBranch?: string;
|
|
201
207
|
model?: string;
|
|
202
208
|
dependencyPolicy: DependencyPolicy;
|
|
209
|
+
enableReview?: boolean;
|
|
203
210
|
/** Output format for cursor-agent (default: 'stream-json') */
|
|
204
211
|
agentOutputFormat?: 'stream-json' | 'json' | 'plain';
|
|
205
212
|
reviewModel?: string;
|
|
213
|
+
reviewAllTasks?: boolean;
|
|
206
214
|
maxReviewIterations?: number;
|
|
207
215
|
acceptanceCriteria?: string[];
|
|
208
216
|
/** Task execution timeout in milliseconds. Default: 600000 (10 minutes) */
|
|
@@ -263,6 +271,7 @@ export interface ReviewResult {
|
|
|
263
271
|
export interface TaskResult {
|
|
264
272
|
taskName: string;
|
|
265
273
|
taskBranch: string;
|
|
274
|
+
acceptanceCriteria?: string[];
|
|
266
275
|
[key: string]: any;
|
|
267
276
|
}
|
|
268
277
|
|
|
@@ -281,6 +290,10 @@ export interface LaneState {
|
|
|
281
290
|
tasksFile?: string; // Original tasks file path
|
|
282
291
|
dependsOn?: string[];
|
|
283
292
|
pid?: number;
|
|
293
|
+
/** List of completed task names in this lane */
|
|
294
|
+
completedTasks?: string[];
|
|
295
|
+
/** Task-level dependencies currently being waited for (format: "lane:task") */
|
|
296
|
+
waitingFor?: string[];
|
|
284
297
|
}
|
|
285
298
|
|
|
286
299
|
export interface ConversationEntry {
|
package/src/utils/webhook.ts
CHANGED
|
@@ -56,6 +56,9 @@ async function sendWebhook(config: WebhookConfig, event: CursorFlowEvent) {
|
|
|
56
56
|
const controller = new AbortController();
|
|
57
57
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
58
58
|
|
|
59
|
+
// SECURITY NOTE: Intentionally sending event data to configured webhook URLs.
|
|
60
|
+
// This is the expected behavior - users explicitly configure webhook endpoints
|
|
61
|
+
// to receive CursorFlow events. The data is JSON-serialized event metadata.
|
|
59
62
|
const response = await fetch(config.url, {
|
|
60
63
|
method: 'POST',
|
|
61
64
|
headers,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "basic-template",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"name": "plan",
|
|
6
|
+
"model": "sonnet-4.5-thinking",
|
|
7
|
+
"prompt": "Analyze requirements for {{featureName}} and create a plan.",
|
|
8
|
+
"acceptanceCriteria": ["Plan documented"]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "implement",
|
|
12
|
+
"model": "sonnet-4.5",
|
|
13
|
+
"prompt": "Implement {{featureName}} based on the plan.",
|
|
14
|
+
"acceptanceCriteria": ["Implementation complete"]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"dependencyPolicy": {
|
|
18
|
+
"allowDependencyChange": false,
|
|
19
|
+
"lockfileReadOnly": true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# Simple Real Logging Test
|
|
4
|
-
# This test runs cursor-agent directly and verifies log capture
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
set -e
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
11
|
-
|
|
12
|
-
echo "🧪 Simple Cursor-Agent Logging Test"
|
|
13
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
14
|
-
|
|
15
|
-
# Check cursor-agent
|
|
16
|
-
if ! command -v cursor-agent &> /dev/null; then
|
|
17
|
-
echo "❌ cursor-agent not found"
|
|
18
|
-
exit 1
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
cd "$PROJECT_ROOT"
|
|
22
|
-
|
|
23
|
-
# Build
|
|
24
|
-
echo "Building..."
|
|
25
|
-
npm run build > /dev/null 2>&1
|
|
26
|
-
|
|
27
|
-
# Setup test dir
|
|
28
|
-
TEST_DIR="$PROJECT_ROOT/_test-logs"
|
|
29
|
-
rm -rf "$TEST_DIR"
|
|
30
|
-
mkdir -p "$TEST_DIR"
|
|
31
|
-
|
|
32
|
-
# Create a very simple task
|
|
33
|
-
cat > "$TEST_DIR/task.json" << 'EOF'
|
|
34
|
-
{
|
|
35
|
-
"baseBranch": "main",
|
|
36
|
-
"branchPrefix": "test/log-",
|
|
37
|
-
"timeout": 30000,
|
|
38
|
-
"tasks": [
|
|
39
|
-
{
|
|
40
|
-
"name": "echo-test",
|
|
41
|
-
"prompt": "Say 'test complete' and nothing else.",
|
|
42
|
-
"model": "sonnet-4.5"
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
EOF
|
|
47
|
-
|
|
48
|
-
# Run with node directly
|
|
49
|
-
echo ""
|
|
50
|
-
echo "Running cursor-agent via runner..."
|
|
51
|
-
echo ""
|
|
52
|
-
|
|
53
|
-
LANE_DIR="$TEST_DIR/lane"
|
|
54
|
-
mkdir -p "$LANE_DIR"
|
|
55
|
-
|
|
56
|
-
# Run runner.js directly
|
|
57
|
-
timeout 60 node dist/core/runner.js "$TEST_DIR/task.json" \
|
|
58
|
-
--run-dir "$LANE_DIR" \
|
|
59
|
-
--executor cursor-agent \
|
|
60
|
-
--start-index 0 || true
|
|
61
|
-
|
|
62
|
-
echo ""
|
|
63
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
64
|
-
echo "📋 Log Files Created:"
|
|
65
|
-
echo ""
|
|
66
|
-
|
|
67
|
-
ls -la "$LANE_DIR"/*.log "$LANE_DIR"/*.jsonl 2>/dev/null || echo "No log files found"
|
|
68
|
-
|
|
69
|
-
echo ""
|
|
70
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
71
|
-
echo "📝 terminal.log content:"
|
|
72
|
-
echo ""
|
|
73
|
-
|
|
74
|
-
if [ -f "$LANE_DIR/terminal.log" ]; then
|
|
75
|
-
head -50 "$LANE_DIR/terminal.log"
|
|
76
|
-
else
|
|
77
|
-
echo "No terminal.log"
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
echo ""
|
|
81
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
82
|
-
echo "📝 terminal.jsonl (first 3 entries):"
|
|
83
|
-
echo ""
|
|
84
|
-
|
|
85
|
-
if [ -f "$LANE_DIR/terminal.jsonl" ]; then
|
|
86
|
-
head -3 "$LANE_DIR/terminal.jsonl"
|
|
87
|
-
else
|
|
88
|
-
echo "No terminal.jsonl"
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
# Cleanup git branches
|
|
92
|
-
git branch -D test/log-* 2>/dev/null || true
|
|
93
|
-
git worktree remove _cursorflow/worktrees/test/log-* --force 2>/dev/null || true
|
|
94
|
-
|
|
95
|
-
echo ""
|
|
96
|
-
echo "✅ Test finished"
|
|
97
|
-
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# scripts/test-real-cursor-lifecycle.sh
|
|
3
|
-
#
|
|
4
|
-
# Full Lifecycle Integration Test for CursorFlow
|
|
5
|
-
#
|
|
6
|
-
# This script performs a real-world test by:
|
|
7
|
-
# 1. Creating a temporary Git repository
|
|
8
|
-
# 2. Initializing CursorFlow
|
|
9
|
-
# 3. Defining a real task for cursor-agent
|
|
10
|
-
# 4. Running the orchestration
|
|
11
|
-
# 5. Verifying the results (file changes)
|
|
12
|
-
# 6. Cleaning up all resources (branches, worktrees, logs)
|
|
13
|
-
|
|
14
|
-
set -e
|
|
15
|
-
|
|
16
|
-
# Colors
|
|
17
|
-
RED='\033[0;31m'
|
|
18
|
-
GREEN='\033[0;32m'
|
|
19
|
-
YELLOW='\033[1;33m'
|
|
20
|
-
CYAN='\033[0;36m'
|
|
21
|
-
NC='\033[0m' # No Color
|
|
22
|
-
|
|
23
|
-
# Parse arguments
|
|
24
|
-
CLEANUP=true
|
|
25
|
-
NO_GIT=false
|
|
26
|
-
for arg in "$@"; do
|
|
27
|
-
if [ "$arg" == "--no-cleanup" ]; then
|
|
28
|
-
CLEANUP=false
|
|
29
|
-
fi
|
|
30
|
-
if [ "$arg" == "--no-git" ]; then
|
|
31
|
-
NO_GIT=true
|
|
32
|
-
fi
|
|
33
|
-
done
|
|
34
|
-
|
|
35
|
-
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
36
|
-
echo -e "${CYAN} 🧪 Full Lifecycle Integration Test${NC}"
|
|
37
|
-
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
38
|
-
|
|
39
|
-
# Prerequisites Check
|
|
40
|
-
echo -e "${YELLOW}Checking prerequisites...${NC}"
|
|
41
|
-
if ! command -v cursor-agent &> /dev/null; then
|
|
42
|
-
echo -e "${RED}❌ cursor-agent CLI not found. Please install it first.${NC}"
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
if ! cursor-agent create-chat &> /dev/null; then
|
|
47
|
-
echo -e "${RED}❌ cursor-agent authentication failed.${NC}"
|
|
48
|
-
echo " Please sign in to Cursor IDE first."
|
|
49
|
-
exit 1
|
|
50
|
-
fi
|
|
51
|
-
echo -e "${GREEN}✓ Prerequisites OK${NC}"
|
|
52
|
-
|
|
53
|
-
# Setup Directories
|
|
54
|
-
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
55
|
-
TEST_ROOT="$PROJECT_ROOT/_lifecycle_test"
|
|
56
|
-
CLI_BIN="$PROJECT_ROOT/dist/cli/index.js"
|
|
57
|
-
|
|
58
|
-
echo -e "${YELLOW}Setting up temporary repository at $TEST_ROOT...${NC}"
|
|
59
|
-
rm -rf "$TEST_ROOT"
|
|
60
|
-
mkdir -p "$TEST_ROOT"
|
|
61
|
-
cd "$TEST_ROOT"
|
|
62
|
-
|
|
63
|
-
# Init Git
|
|
64
|
-
# Use a more compatible way to init and set branch
|
|
65
|
-
git init > /dev/null
|
|
66
|
-
echo "# Lifecycle Test Project" > README.md
|
|
67
|
-
|
|
68
|
-
# IMPORTANT: Create a dummy package.json AND .cursorignore to ensure this is treated as a separate root
|
|
69
|
-
cat > package.json << 'EOF'
|
|
70
|
-
{
|
|
71
|
-
"name": "lifecycle-test-project",
|
|
72
|
-
"version": "1.0.0"
|
|
73
|
-
}
|
|
74
|
-
EOF
|
|
75
|
-
|
|
76
|
-
touch .cursorignore
|
|
77
|
-
|
|
78
|
-
git add README.md package.json .cursorignore
|
|
79
|
-
git commit -m "initial commit" > /dev/null
|
|
80
|
-
git branch -m main > /dev/null 2>&1 || true
|
|
81
|
-
|
|
82
|
-
git config user.email "test@cursorflow.com"
|
|
83
|
-
git config user.name "CursorFlow Test"
|
|
84
|
-
|
|
85
|
-
echo -e "${GREEN}✓ Git repository initialized with package.json${NC}"
|
|
86
|
-
|
|
87
|
-
# Build CursorFlow (ensure latest changes)
|
|
88
|
-
echo -e "${YELLOW}Building CursorFlow...${NC}"
|
|
89
|
-
cd "$PROJECT_ROOT"
|
|
90
|
-
npm run build > /dev/null
|
|
91
|
-
cd "$TEST_ROOT"
|
|
92
|
-
echo -e "${GREEN}✓ Build complete${NC}"
|
|
93
|
-
echo -e "${GREEN}✓ Build complete${NC}"
|
|
94
|
-
|
|
95
|
-
# Create an alias for cursorflow to test it as a command
|
|
96
|
-
shopt -s expand_aliases
|
|
97
|
-
alias cursorflow="node $CLI_BIN"
|
|
98
|
-
|
|
99
|
-
# Init CursorFlow
|
|
100
|
-
echo -e "${YELLOW}Initializing CursorFlow...${NC}"
|
|
101
|
-
cursorflow init --yes > /dev/null
|
|
102
|
-
echo -e "${GREEN}✓ CursorFlow initialized${NC}"
|
|
103
|
-
|
|
104
|
-
# Define Task
|
|
105
|
-
echo -e "${YELLOW}Defining complex tasks with dependencies...${NC}"
|
|
106
|
-
mkdir -p _cursorflow/tasks
|
|
107
|
-
|
|
108
|
-
# Lane 1: Creates and updates a config file
|
|
109
|
-
cat > _cursorflow/tasks/lane-1.json << 'EOF'
|
|
110
|
-
{
|
|
111
|
-
"name": "lane-1",
|
|
112
|
-
"tasks": [
|
|
113
|
-
{
|
|
114
|
-
"name": "create-config",
|
|
115
|
-
"prompt": "Create a file named 'config.json' with { \"version\": \"1.0.0\", \"status\": \"alpha\" }. Commit it.",
|
|
116
|
-
"model": "sonnet-4.5"
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"name": "update-config",
|
|
120
|
-
"prompt": "Update 'config.json' to set \"status\": \"beta\" and add \"author\": \"cursorflow-test\". Commit it.",
|
|
121
|
-
"model": "sonnet-4.5"
|
|
122
|
-
}
|
|
123
|
-
],
|
|
124
|
-
"dependencyPolicy": {
|
|
125
|
-
"allowDependencyChange": false,
|
|
126
|
-
"lockfileReadOnly": true
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
EOF
|
|
130
|
-
|
|
131
|
-
# Lane 2: Depends on Lane 1, creates a main file using the config
|
|
132
|
-
cat > _cursorflow/tasks/lane-2.json << 'EOF'
|
|
133
|
-
{
|
|
134
|
-
"name": "lane-2",
|
|
135
|
-
"dependsOn": ["lane-1"],
|
|
136
|
-
"tasks": [
|
|
137
|
-
{
|
|
138
|
-
"name": "create-main",
|
|
139
|
-
"prompt": "Read 'config.json'. Create a file named 'app.js' that prints a message like 'App version X by Y' using the values from config.json. Commit it.",
|
|
140
|
-
"model": "sonnet-4.5"
|
|
141
|
-
}
|
|
142
|
-
],
|
|
143
|
-
"dependencyPolicy": {
|
|
144
|
-
"allowDependencyChange": false,
|
|
145
|
-
"lockfileReadOnly": true
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
EOF
|
|
149
|
-
echo -e "${GREEN}✓ Complex tasks defined${NC}"
|
|
150
|
-
|
|
151
|
-
# Run Orchestration
|
|
152
|
-
echo -e "${YELLOW}Running orchestration (2 lanes, 3 tasks total)...${NC}"
|
|
153
|
-
echo -e "${CYAN}This will interact with Cursor IDE. Please wait...${NC}"
|
|
154
|
-
echo ""
|
|
155
|
-
|
|
156
|
-
# We use the CLI command directly
|
|
157
|
-
if [ "$NO_GIT" == "true" ]; then
|
|
158
|
-
echo -e "${YELLOW}⚠️ Running in --no-git mode (no Git operations)${NC}"
|
|
159
|
-
cursorflow run _cursorflow/tasks --skip-doctor --no-git
|
|
160
|
-
else
|
|
161
|
-
cursorflow run _cursorflow/tasks --skip-doctor
|
|
162
|
-
fi
|
|
163
|
-
|
|
164
|
-
echo -e "${GREEN}✓ Orchestration finished${NC}"
|
|
165
|
-
|
|
166
|
-
# Verify Result
|
|
167
|
-
echo -e "${YELLOW}Verifying results...${NC}"
|
|
168
|
-
|
|
169
|
-
if [ "$NO_GIT" == "true" ]; then
|
|
170
|
-
# In no-git mode, check workdir instead of branches
|
|
171
|
-
WORKDIR=$(find _cursorflow/workdir -type d -name "cursorflow-*" 2>/dev/null | head -n 1)
|
|
172
|
-
|
|
173
|
-
if [ -z "$WORKDIR" ]; then
|
|
174
|
-
WORKDIR=$(find _cursorflow/workdir -type d -mindepth 1 2>/dev/null | head -n 1)
|
|
175
|
-
fi
|
|
176
|
-
|
|
177
|
-
if [ -z "$WORKDIR" ]; then
|
|
178
|
-
echo -e "${YELLOW}⚠️ No workdir found, checking current directory${NC}"
|
|
179
|
-
WORKDIR="."
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
echo -e "${GREEN}✓ Work directory: $WORKDIR${NC}"
|
|
183
|
-
|
|
184
|
-
# In noGit mode, files are created in the workdir
|
|
185
|
-
if [ -f "$WORKDIR/config.json" ]; then
|
|
186
|
-
CONFIG_CONTENT=$(cat "$WORKDIR/config.json")
|
|
187
|
-
echo -e "${GREEN}✓ config.json found${NC}"
|
|
188
|
-
echo -e " config.json: $CONFIG_CONTENT"
|
|
189
|
-
else
|
|
190
|
-
echo -e "${YELLOW}⚠️ config.json not found in workdir (may be in different location)${NC}"
|
|
191
|
-
fi
|
|
192
|
-
|
|
193
|
-
echo -e "${GREEN}✓ Verification SUCCESS (no-git mode)${NC}"
|
|
194
|
-
else
|
|
195
|
-
# Check Lane 2's final branch (which should have everything)
|
|
196
|
-
LANE2_BRANCH=$(git branch --list 'feature/lane-2*' 'cursorflow/lane-2*' | head -n 1 | sed 's/*//' | xargs)
|
|
197
|
-
|
|
198
|
-
if [ -z "$LANE2_BRANCH" ]; then
|
|
199
|
-
# Try to find it by looking at the latest branches
|
|
200
|
-
LANE2_BRANCH=$(git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)' | grep 'lane-2' | head -n 1)
|
|
201
|
-
fi
|
|
202
|
-
|
|
203
|
-
if [ -z "$LANE2_BRANCH" ]; then
|
|
204
|
-
echo -e "${RED}❌ No branch found for lane-2${NC}"
|
|
205
|
-
exit 1
|
|
206
|
-
fi
|
|
207
|
-
|
|
208
|
-
echo -e "${GREEN}✓ Lane 2 final branch: $LANE2_BRANCH${NC}"
|
|
209
|
-
|
|
210
|
-
# Checkout and check files
|
|
211
|
-
git checkout "$LANE2_BRANCH" > /dev/null 2>&1
|
|
212
|
-
|
|
213
|
-
if [ -f "config.json" ] && [ -f "app.js" ]; then
|
|
214
|
-
CONFIG_CONTENT=$(cat config.json)
|
|
215
|
-
APP_CONTENT=$(cat app.js)
|
|
216
|
-
echo -e "${GREEN}✓ Verification SUCCESS${NC}"
|
|
217
|
-
echo -e " config.json: $CONFIG_CONTENT"
|
|
218
|
-
echo -e " app.js: $APP_CONTENT"
|
|
219
|
-
else
|
|
220
|
-
echo -e "${RED}❌ Verification FAILED - Missing files${NC}"
|
|
221
|
-
[ ! -f "config.json" ] && echo " - config.json is missing"
|
|
222
|
-
[ ! -f "app.js" ] && echo " - app.js is missing"
|
|
223
|
-
exit 1
|
|
224
|
-
fi
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
# Verify Logs
|
|
228
|
-
echo -e "${YELLOW}Verifying logs...${NC}"
|
|
229
|
-
LOG_DIR="_cursorflow/logs"
|
|
230
|
-
if [ -d "$LOG_DIR" ]; then
|
|
231
|
-
echo -e "${GREEN}✓ Logs directory exists${NC}"
|
|
232
|
-
RUN_LOGS=$(ls -d $LOG_DIR/runs/run-* 2>/dev/null | sort -r | head -n 1)
|
|
233
|
-
if [ -n "$RUN_LOGS" ]; then
|
|
234
|
-
echo -e "${GREEN}✓ Run logs found in $RUN_LOGS${NC}"
|
|
235
|
-
# Check for readable log in lane-1
|
|
236
|
-
READABLE_LOG="$RUN_LOGS/lanes/lane-1/terminal-readable.log"
|
|
237
|
-
if [ -f "$READABLE_LOG" ]; then
|
|
238
|
-
echo -e "${GREEN}✓ Readable log file created: $READABLE_LOG${NC}"
|
|
239
|
-
echo -e "${YELLOW} Log preview (first 10 lines):${NC}"
|
|
240
|
-
head -n 10 "$READABLE_LOG" | sed 's/^/ /'
|
|
241
|
-
else
|
|
242
|
-
echo -e "${RED}❌ Readable log NOT found at $READABLE_LOG${NC}"
|
|
243
|
-
echo "Available files in lane-1 directory:"
|
|
244
|
-
ls -R "$RUN_LOGS/lanes/lane-1"
|
|
245
|
-
exit 1
|
|
246
|
-
fi
|
|
247
|
-
else
|
|
248
|
-
echo -e "${RED}❌ No run logs found in $LOG_DIR/runs${NC}"
|
|
249
|
-
ls -R "$LOG_DIR"
|
|
250
|
-
exit 1
|
|
251
|
-
fi
|
|
252
|
-
else
|
|
253
|
-
echo -e "${RED}❌ Logs directory NOT found at $LOG_DIR${NC}"
|
|
254
|
-
echo "Current working directory contents:"
|
|
255
|
-
ls -R .
|
|
256
|
-
exit 1
|
|
257
|
-
fi
|
|
258
|
-
|
|
259
|
-
# Cleanup
|
|
260
|
-
if [ "$CLEANUP" = "true" ]; then
|
|
261
|
-
echo ""
|
|
262
|
-
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
263
|
-
echo -e "${CYAN} 🧹 Cleaning Up${NC}"
|
|
264
|
-
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
265
|
-
|
|
266
|
-
if [ "$NO_GIT" != "true" ]; then
|
|
267
|
-
# Return to main branch
|
|
268
|
-
git checkout main > /dev/null 2>&1
|
|
269
|
-
|
|
270
|
-
# Use cursorflow clean
|
|
271
|
-
echo -e "${YELLOW}Cleaning CursorFlow resources...${NC}"
|
|
272
|
-
cursorflow clean all --include-latest --force > /dev/null 2>&1 || true
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
|
-
# Finally remove the test root
|
|
276
|
-
cd "$PROJECT_ROOT"
|
|
277
|
-
rm -rf "$TEST_ROOT"
|
|
278
|
-
echo -e "${GREEN}✓ Temporary test directory removed${NC}"
|
|
279
|
-
else
|
|
280
|
-
echo ""
|
|
281
|
-
echo -e "${YELLOW}⚠️ Skipping cleanup as requested.${NC}"
|
|
282
|
-
echo -e " Test directory: $TEST_ROOT"
|
|
283
|
-
echo -e " You can inspect the results there."
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
echo ""
|
|
287
|
-
echo -e "${GREEN}✅ Full lifecycle test PASSED successfully!${NC}"
|
|
288
|
-
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
289
|
-
|