@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +26 -7
  3. package/commands/cursorflow-run.md +2 -0
  4. package/commands/cursorflow-triggers.md +250 -0
  5. package/dist/cli/clean.js +8 -7
  6. package/dist/cli/clean.js.map +1 -1
  7. package/dist/cli/index.js +5 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/init.js +20 -14
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/cli/logs.js +64 -47
  12. package/dist/cli/logs.js.map +1 -1
  13. package/dist/cli/monitor.js +27 -17
  14. package/dist/cli/monitor.js.map +1 -1
  15. package/dist/cli/prepare.js +73 -33
  16. package/dist/cli/prepare.js.map +1 -1
  17. package/dist/cli/resume.js +193 -40
  18. package/dist/cli/resume.js.map +1 -1
  19. package/dist/cli/run.js +3 -2
  20. package/dist/cli/run.js.map +1 -1
  21. package/dist/cli/signal.js +7 -7
  22. package/dist/cli/signal.js.map +1 -1
  23. package/dist/core/orchestrator.d.ts +2 -1
  24. package/dist/core/orchestrator.js +54 -93
  25. package/dist/core/orchestrator.js.map +1 -1
  26. package/dist/core/reviewer.d.ts +6 -4
  27. package/dist/core/reviewer.js +7 -5
  28. package/dist/core/reviewer.js.map +1 -1
  29. package/dist/core/runner.d.ts +8 -0
  30. package/dist/core/runner.js +219 -32
  31. package/dist/core/runner.js.map +1 -1
  32. package/dist/utils/config.js +20 -10
  33. package/dist/utils/config.js.map +1 -1
  34. package/dist/utils/doctor.js +35 -7
  35. package/dist/utils/doctor.js.map +1 -1
  36. package/dist/utils/enhanced-logger.d.ts +2 -2
  37. package/dist/utils/enhanced-logger.js +114 -43
  38. package/dist/utils/enhanced-logger.js.map +1 -1
  39. package/dist/utils/git.js +163 -10
  40. package/dist/utils/git.js.map +1 -1
  41. package/dist/utils/log-formatter.d.ts +16 -0
  42. package/dist/utils/log-formatter.js +194 -0
  43. package/dist/utils/log-formatter.js.map +1 -0
  44. package/dist/utils/path.d.ts +19 -0
  45. package/dist/utils/path.js +77 -0
  46. package/dist/utils/path.js.map +1 -0
  47. package/dist/utils/repro-thinking-logs.d.ts +1 -0
  48. package/dist/utils/repro-thinking-logs.js +80 -0
  49. package/dist/utils/repro-thinking-logs.js.map +1 -0
  50. package/dist/utils/state.d.ts +4 -1
  51. package/dist/utils/state.js +11 -8
  52. package/dist/utils/state.js.map +1 -1
  53. package/dist/utils/template.d.ts +14 -0
  54. package/dist/utils/template.js +122 -0
  55. package/dist/utils/template.js.map +1 -0
  56. package/dist/utils/types.d.ts +13 -0
  57. package/dist/utils/webhook.js +3 -0
  58. package/dist/utils/webhook.js.map +1 -1
  59. package/package.json +4 -2
  60. package/scripts/ai-security-check.js +3 -0
  61. package/scripts/local-security-gate.sh +9 -1
  62. package/scripts/verify-and-fix.sh +37 -0
  63. package/src/cli/clean.ts +8 -7
  64. package/src/cli/index.ts +5 -1
  65. package/src/cli/init.ts +19 -15
  66. package/src/cli/logs.ts +67 -47
  67. package/src/cli/monitor.ts +28 -18
  68. package/src/cli/prepare.ts +75 -35
  69. package/src/cli/resume.ts +810 -626
  70. package/src/cli/run.ts +3 -2
  71. package/src/cli/signal.ts +7 -6
  72. package/src/core/orchestrator.ts +68 -93
  73. package/src/core/reviewer.ts +14 -9
  74. package/src/core/runner.ts +229 -33
  75. package/src/utils/config.ts +19 -11
  76. package/src/utils/doctor.ts +38 -7
  77. package/src/utils/enhanced-logger.ts +117 -49
  78. package/src/utils/git.ts +145 -11
  79. package/src/utils/log-formatter.ts +162 -0
  80. package/src/utils/path.ts +45 -0
  81. package/src/utils/repro-thinking-logs.ts +54 -0
  82. package/src/utils/state.ts +16 -8
  83. package/src/utils/template.ts +92 -0
  84. package/src/utils/types.ts +13 -0
  85. package/src/utils/webhook.ts +3 -0
  86. package/templates/basic.json +21 -0
  87. package/scripts/simple-logging-test.sh +0 -97
  88. package/scripts/test-real-cursor-lifecycle.sh +0 -289
  89. package/scripts/test-real-logging.sh +0 -289
  90. 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
+
@@ -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(laneName: string, config: RunnerConfig): LaneState {
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(path.join(logsDir, f)).isDirectory())
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 path.join(logsDir, runs[0]!);
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(path.join(runDir, f)).isDirectory())
182
+ .filter(f => fs.statSync(safeJoin(runDir, f)).isDirectory())
175
183
  .map(laneName => ({
176
184
  name: laneName,
177
- dir: path.join(runDir, laneName),
178
- statePath: path.join(runDir, laneName, 'state.json'),
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
+
@@ -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 {
@@ -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
-