@litmers/cursorflow-orchestrator 0.1.15 โ†’ 0.1.18

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 (59) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +1 -0
  3. package/commands/cursorflow-run.md +2 -0
  4. package/commands/cursorflow-triggers.md +250 -0
  5. package/dist/cli/clean.js +1 -1
  6. package/dist/cli/clean.js.map +1 -1
  7. package/dist/cli/init.js +13 -8
  8. package/dist/cli/init.js.map +1 -1
  9. package/dist/cli/logs.js +24 -15
  10. package/dist/cli/logs.js.map +1 -1
  11. package/dist/cli/monitor.js +12 -3
  12. package/dist/cli/monitor.js.map +1 -1
  13. package/dist/cli/prepare.js +36 -13
  14. package/dist/cli/prepare.js.map +1 -1
  15. package/dist/cli/resume.js.map +1 -1
  16. package/dist/core/orchestrator.js +10 -6
  17. package/dist/core/orchestrator.js.map +1 -1
  18. package/dist/core/reviewer.d.ts +6 -4
  19. package/dist/core/reviewer.js +7 -5
  20. package/dist/core/reviewer.js.map +1 -1
  21. package/dist/core/runner.d.ts +8 -0
  22. package/dist/core/runner.js +166 -14
  23. package/dist/core/runner.js.map +1 -1
  24. package/dist/utils/config.js +13 -4
  25. package/dist/utils/config.js.map +1 -1
  26. package/dist/utils/doctor.js +28 -1
  27. package/dist/utils/doctor.js.map +1 -1
  28. package/dist/utils/enhanced-logger.d.ts +2 -2
  29. package/dist/utils/enhanced-logger.js +102 -34
  30. package/dist/utils/enhanced-logger.js.map +1 -1
  31. package/dist/utils/repro-thinking-logs.d.ts +1 -0
  32. package/dist/utils/repro-thinking-logs.js +80 -0
  33. package/dist/utils/repro-thinking-logs.js.map +1 -0
  34. package/dist/utils/types.d.ts +12 -0
  35. package/dist/utils/webhook.js +3 -0
  36. package/dist/utils/webhook.js.map +1 -1
  37. package/package.json +4 -2
  38. package/scripts/ai-security-check.js +3 -0
  39. package/scripts/local-security-gate.sh +9 -1
  40. package/scripts/verify-and-fix.sh +37 -0
  41. package/src/cli/clean.ts +1 -1
  42. package/src/cli/init.ts +12 -9
  43. package/src/cli/logs.ts +25 -15
  44. package/src/cli/monitor.ts +13 -4
  45. package/src/cli/prepare.ts +36 -15
  46. package/src/cli/resume.ts +1 -1
  47. package/src/core/orchestrator.ts +10 -6
  48. package/src/core/reviewer.ts +14 -9
  49. package/src/core/runner.ts +173 -15
  50. package/src/utils/config.ts +12 -5
  51. package/src/utils/doctor.ts +31 -1
  52. package/src/utils/enhanced-logger.ts +105 -40
  53. package/src/utils/repro-thinking-logs.ts +54 -0
  54. package/src/utils/types.ts +12 -0
  55. package/src/utils/webhook.ts +3 -0
  56. package/scripts/simple-logging-test.sh +0 -97
  57. package/scripts/test-real-cursor-lifecycle.sh +0 -289
  58. package/scripts/test-real-logging.sh +0 -289
  59. package/scripts/test-streaming-multi-task.sh +0 -247
@@ -13,7 +13,7 @@
13
13
 
14
14
  import * as fs from 'fs';
15
15
  import * as path from 'path';
16
- import { PassThrough, Transform, TransformCallback } from 'stream';
16
+ import { Transform, TransformCallback } from 'stream';
17
17
  import { EnhancedLogConfig } from './types';
18
18
 
19
19
  // Re-export for backwards compatibility
@@ -163,6 +163,23 @@ export class StreamingMessageParser {
163
163
  },
164
164
  });
165
165
  break;
166
+
167
+ case 'thinking':
168
+ // Thinking message (Claude 3.7+ etc.)
169
+ if (json.subtype === 'delta' && json.text) {
170
+ // Check if this is a new message or continuation
171
+ if (this.currentRole !== 'thinking') {
172
+ // Flush previous message if any
173
+ this.flush();
174
+ this.currentRole = 'thinking';
175
+ this.messageStartTime = json.timestamp_ms || Date.now();
176
+ }
177
+ this.currentMessage += json.text;
178
+ } else if (json.subtype === 'completed') {
179
+ // Thinking completed - flush immediately
180
+ this.flush();
181
+ }
182
+ break;
166
183
  }
167
184
  }
168
185
 
@@ -191,7 +208,7 @@ export class StreamingMessageParser {
191
208
  }
192
209
 
193
210
  export interface ParsedMessage {
194
- type: 'system' | 'user' | 'assistant' | 'tool' | 'tool_result' | 'result';
211
+ type: 'system' | 'user' | 'assistant' | 'tool' | 'tool_result' | 'result' | 'thinking';
195
212
  role: string;
196
213
  content: string;
197
214
  timestamp: number;
@@ -505,6 +522,21 @@ export class EnhancedLogManager {
505
522
  formatted = `[${ts}] ๐Ÿ“„ RESL: ${msg.metadata?.toolName || 'Tool'}${toolResultLines}\n`;
506
523
  break;
507
524
 
525
+ case 'thinking':
526
+ // Format thinking block
527
+ const thinkLabel = `[ ๐Ÿค” THINKING ] `;
528
+ const thinkWidth = 80;
529
+ const thinkTop = `โ”Œโ”€${thinkLabel}${'โ”€'.repeat(Math.max(0, thinkWidth - thinkLabel.length - 2))}`;
530
+ const thinkBottom = `โ””โ”€${'โ”€'.repeat(thinkWidth - 2)}`;
531
+
532
+ const thinkLines = msg.content.trim().split('\n');
533
+ formatted = `[${ts}] ${thinkTop}\n`;
534
+ for (const line of thinkLines) {
535
+ formatted += `[${ts}] โ”‚ ${line}\n`;
536
+ }
537
+ formatted += `[${ts}] ${thinkBottom}\n`;
538
+ break;
539
+
508
540
  default:
509
541
  formatted = `[${ts}] ${msg.content}\n`;
510
542
  }
@@ -669,20 +701,71 @@ export class EnhancedLogManager {
669
701
  this.cleanTransform.write(data);
670
702
  }
671
703
 
672
- // Parse streaming JSON for readable log (handles boxes, messages, tool calls)
673
- this.parseStreamingData(text);
704
+ // Process lines for readable log and JSON entries
705
+ this.lineBuffer += text;
706
+ const lines = this.lineBuffer.split('\n');
707
+ this.lineBuffer = lines.pop() || '';
674
708
 
675
- // Also include significant info/status lines in readable log (compact)
676
- if (this.readableLogFd !== null) {
677
- const lines = text.split('\n');
678
- for (const line of lines) {
679
- const cleanLine = stripAnsi(line).trim();
709
+ for (const line of lines) {
710
+ const cleanLine = stripAnsi(line).trim();
711
+ if (!cleanLine) continue;
712
+
713
+ // Handle streaming JSON messages (for boxes, etc. in readable log)
714
+ if (cleanLine.startsWith('{')) {
715
+ if (this.streamingParser) {
716
+ this.streamingParser.parseLine(cleanLine);
717
+ }
718
+
719
+ // Special handling for terminal.jsonl entries for AI messages
720
+ if (this.config.writeJsonLog) {
721
+ try {
722
+ const json = JSON.parse(cleanLine);
723
+ let displayMsg = cleanLine;
724
+ let metadata = { ...json };
725
+
726
+ // Extract cleaner text for significant AI message types
727
+ if (json.type === 'thinking' && json.text) {
728
+ displayMsg = json.text;
729
+ } else if (json.type === 'assistant' && json.message?.content) {
730
+ displayMsg = json.message.content
731
+ .filter((c: any) => c.type === 'text')
732
+ .map((c: any) => c.text)
733
+ .join('');
734
+ } else if (json.type === 'user' && json.message?.content) {
735
+ displayMsg = json.message.content
736
+ .filter((c: any) => c.type === 'text')
737
+ .map((c: any) => c.text)
738
+ .join('');
739
+ } else if (json.type === 'tool_call' && json.subtype === 'started') {
740
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
741
+ const args = json.tool_call[toolName]?.args || {};
742
+ displayMsg = `๐Ÿ”ง CALL: ${toolName}(${JSON.stringify(args)})`;
743
+ } else if (json.type === 'tool_call' && json.subtype === 'completed') {
744
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
745
+ displayMsg = `๐Ÿ“„ RESL: ${toolName}`;
746
+ } else if (json.type === 'result') {
747
+ displayMsg = json.result || 'Task completed';
748
+ }
749
+
750
+ this.writeJsonEntry({
751
+ timestamp: new Date().toISOString(),
752
+ level: 'stdout',
753
+ lane: this.session.laneName,
754
+ task: this.session.taskName,
755
+ message: displayMsg.substring(0, 2000), // Larger limit for AI text
756
+ metadata,
757
+ });
758
+ continue; // Already logged this JSON line
759
+ } catch {
760
+ // Not valid JSON or error, fall through to regular logging
761
+ }
762
+ }
763
+ }
764
+
765
+ // Also include significant info/status lines in readable log (compact)
766
+ if (this.readableLogFd !== null) {
680
767
  // Look for log lines: [ISO_DATE] [LEVEL] ...
681
- if (cleanLine &&
682
- !cleanLine.startsWith('{') &&
683
- !this.isNoiseLog(cleanLine) &&
684
- /\[\d{4}-\d{2}-\d{2}T/.test(cleanLine)) {
685
-
768
+ if (!this.isNoiseLog(cleanLine) && /\[\d{4}-\d{2}-\d{2}T/.test(cleanLine)) {
686
769
  try {
687
770
  // Check if it has a level marker
688
771
  if (/\[(INFO|WARN|ERROR|SUCCESS|DEBUG)\]/.test(cleanLine)) {
@@ -698,44 +781,26 @@ export class EnhancedLogManager {
698
781
  } catch {}
699
782
  }
700
783
  }
701
- }
702
-
703
- // Write JSON entry (for significant lines only)
704
- if (this.config.writeJsonLog) {
705
- const cleanText = stripAnsi(text).trim();
706
- if (cleanText && !this.isNoiseLog(cleanText)) {
784
+
785
+ // Write regular non-JSON lines to terminal.jsonl
786
+ if (this.config.writeJsonLog && !this.isNoiseLog(cleanLine)) {
707
787
  this.writeJsonEntry({
708
788
  timestamp: new Date().toISOString(),
709
789
  level: 'stdout',
710
790
  lane: this.session.laneName,
711
791
  task: this.session.taskName,
712
- message: cleanText.substring(0, 1000), // Truncate very long lines
713
- raw: this.config.keepRawLogs ? undefined : text.substring(0, 1000),
792
+ message: cleanLine.substring(0, 1000),
793
+ raw: this.config.keepRawLogs ? undefined : line.substring(0, 1000),
714
794
  });
715
795
  }
716
796
  }
717
797
  }
718
798
 
719
799
  /**
720
- * Parse streaming JSON data for readable log
800
+ * Parse streaming JSON data for readable log - legacy, integrated into writeStdout
721
801
  */
722
802
  private parseStreamingData(text: string): void {
723
- if (!this.streamingParser) return;
724
-
725
- // Buffer incomplete lines
726
- this.lineBuffer += text;
727
- const lines = this.lineBuffer.split('\n');
728
-
729
- // Keep the last incomplete line in buffer
730
- this.lineBuffer = lines.pop() || '';
731
-
732
- // Parse complete lines
733
- for (const line of lines) {
734
- const trimmed = line.trim();
735
- if (trimmed.startsWith('{')) {
736
- this.streamingParser.parseLine(trimmed);
737
- }
738
- }
803
+ // Legacy method, no longer used but kept for internal references if any
739
804
  }
740
805
 
741
806
  /**
@@ -873,7 +938,7 @@ export class EnhancedLogManager {
873
938
 
874
939
  // Skip common progress/spinner patterns
875
940
  const noisePatterns = [
876
- /^[\sโ”‚โ”œโ””โ”€โ”Œโ”โ”˜โ”ดโ”ฌโ”คโ”œ]+$/, // Box drawing only
941
+ /^[\sโ”‚โ”œโ””โ”€โ”Œโ”โ”˜โ”ดโ”ฌโ”ค]+$/, // Box drawing only (removed duplicate โ”œ)
877
942
  /^[.\s]+$/, // Dots only
878
943
  /^[=>\s-]+$/, // Progress bar characters
879
944
  /^\d+%$/, // Percentage only
@@ -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
+
@@ -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,6 +190,10 @@ 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 {
@@ -200,9 +205,11 @@ export interface RunnerConfig {
200
205
  baseBranch?: string;
201
206
  model?: string;
202
207
  dependencyPolicy: DependencyPolicy;
208
+ enableReview?: boolean;
203
209
  /** Output format for cursor-agent (default: 'stream-json') */
204
210
  agentOutputFormat?: 'stream-json' | 'json' | 'plain';
205
211
  reviewModel?: string;
212
+ reviewAllTasks?: boolean;
206
213
  maxReviewIterations?: number;
207
214
  acceptanceCriteria?: string[];
208
215
  /** Task execution timeout in milliseconds. Default: 600000 (10 minutes) */
@@ -263,6 +270,7 @@ export interface ReviewResult {
263
270
  export interface TaskResult {
264
271
  taskName: string;
265
272
  taskBranch: string;
273
+ acceptanceCriteria?: string[];
266
274
  [key: string]: any;
267
275
  }
268
276
 
@@ -281,6 +289,10 @@ export interface LaneState {
281
289
  tasksFile?: string; // Original tasks file path
282
290
  dependsOn?: string[];
283
291
  pid?: number;
292
+ /** List of completed task names in this lane */
293
+ completedTasks?: string[];
294
+ /** Task-level dependencies currently being waited for (format: "lane:task") */
295
+ waitingFor?: string[];
284
296
  }
285
297
 
286
298
  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,
@@ -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
-