@litmers/cursorflow-orchestrator 0.1.31 → 0.1.34

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 (129) hide show
  1. package/README.md +144 -52
  2. package/commands/cursorflow-add.md +159 -0
  3. package/commands/cursorflow-monitor.md +23 -2
  4. package/commands/cursorflow-new.md +87 -0
  5. package/dist/cli/add.d.ts +7 -0
  6. package/dist/cli/add.js +377 -0
  7. package/dist/cli/add.js.map +1 -0
  8. package/dist/cli/clean.js +1 -0
  9. package/dist/cli/clean.js.map +1 -1
  10. package/dist/cli/config.d.ts +7 -0
  11. package/dist/cli/config.js +181 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/index.js +34 -30
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/logs.js +7 -33
  16. package/dist/cli/logs.js.map +1 -1
  17. package/dist/cli/monitor.js +51 -62
  18. package/dist/cli/monitor.js.map +1 -1
  19. package/dist/cli/new.d.ts +7 -0
  20. package/dist/cli/new.js +232 -0
  21. package/dist/cli/new.js.map +1 -0
  22. package/dist/cli/prepare.js +95 -193
  23. package/dist/cli/prepare.js.map +1 -1
  24. package/dist/cli/resume.js +11 -47
  25. package/dist/cli/resume.js.map +1 -1
  26. package/dist/cli/run.js +27 -22
  27. package/dist/cli/run.js.map +1 -1
  28. package/dist/cli/tasks.js +1 -2
  29. package/dist/cli/tasks.js.map +1 -1
  30. package/dist/core/failure-policy.d.ts +9 -0
  31. package/dist/core/failure-policy.js +9 -0
  32. package/dist/core/failure-policy.js.map +1 -1
  33. package/dist/core/orchestrator.d.ts +20 -6
  34. package/dist/core/orchestrator.js +213 -333
  35. package/dist/core/orchestrator.js.map +1 -1
  36. package/dist/core/runner/agent.d.ts +27 -0
  37. package/dist/core/runner/agent.js +294 -0
  38. package/dist/core/runner/agent.js.map +1 -0
  39. package/dist/core/runner/index.d.ts +5 -0
  40. package/dist/core/runner/index.js +22 -0
  41. package/dist/core/runner/index.js.map +1 -0
  42. package/dist/core/runner/pipeline.d.ts +9 -0
  43. package/dist/core/runner/pipeline.js +539 -0
  44. package/dist/core/runner/pipeline.js.map +1 -0
  45. package/dist/core/runner/prompt.d.ts +25 -0
  46. package/dist/core/runner/prompt.js +175 -0
  47. package/dist/core/runner/prompt.js.map +1 -0
  48. package/dist/core/runner/task.d.ts +26 -0
  49. package/dist/core/runner/task.js +283 -0
  50. package/dist/core/runner/task.js.map +1 -0
  51. package/dist/core/runner/utils.d.ts +37 -0
  52. package/dist/core/runner/utils.js +161 -0
  53. package/dist/core/runner/utils.js.map +1 -0
  54. package/dist/core/runner.d.ts +2 -96
  55. package/dist/core/runner.js +11 -1136
  56. package/dist/core/runner.js.map +1 -1
  57. package/dist/core/stall-detection.d.ts +326 -0
  58. package/dist/core/stall-detection.js +781 -0
  59. package/dist/core/stall-detection.js.map +1 -0
  60. package/dist/types/config.d.ts +6 -6
  61. package/dist/types/flow.d.ts +84 -0
  62. package/dist/types/flow.js +10 -0
  63. package/dist/types/flow.js.map +1 -0
  64. package/dist/types/index.d.ts +1 -0
  65. package/dist/types/index.js +3 -3
  66. package/dist/types/index.js.map +1 -1
  67. package/dist/types/lane.d.ts +0 -2
  68. package/dist/types/logging.d.ts +5 -1
  69. package/dist/types/task.d.ts +7 -11
  70. package/dist/utils/config.js +7 -15
  71. package/dist/utils/config.js.map +1 -1
  72. package/dist/utils/dependency.d.ts +36 -1
  73. package/dist/utils/dependency.js +256 -1
  74. package/dist/utils/dependency.js.map +1 -1
  75. package/dist/utils/enhanced-logger.d.ts +45 -82
  76. package/dist/utils/enhanced-logger.js +238 -844
  77. package/dist/utils/enhanced-logger.js.map +1 -1
  78. package/dist/utils/git.d.ts +29 -0
  79. package/dist/utils/git.js +115 -5
  80. package/dist/utils/git.js.map +1 -1
  81. package/dist/utils/state.js +0 -2
  82. package/dist/utils/state.js.map +1 -1
  83. package/dist/utils/task-service.d.ts +2 -2
  84. package/dist/utils/task-service.js +40 -31
  85. package/dist/utils/task-service.js.map +1 -1
  86. package/package.json +4 -3
  87. package/src/cli/add.ts +397 -0
  88. package/src/cli/clean.ts +1 -0
  89. package/src/cli/config.ts +177 -0
  90. package/src/cli/index.ts +36 -32
  91. package/src/cli/logs.ts +7 -31
  92. package/src/cli/monitor.ts +55 -71
  93. package/src/cli/new.ts +235 -0
  94. package/src/cli/prepare.ts +98 -205
  95. package/src/cli/resume.ts +13 -56
  96. package/src/cli/run.ts +311 -306
  97. package/src/cli/tasks.ts +1 -2
  98. package/src/core/failure-policy.ts +9 -0
  99. package/src/core/orchestrator.ts +277 -378
  100. package/src/core/runner/agent.ts +314 -0
  101. package/src/core/runner/index.ts +6 -0
  102. package/src/core/runner/pipeline.ts +567 -0
  103. package/src/core/runner/prompt.ts +174 -0
  104. package/src/core/runner/task.ts +320 -0
  105. package/src/core/runner/utils.ts +142 -0
  106. package/src/core/runner.ts +8 -1347
  107. package/src/core/stall-detection.ts +936 -0
  108. package/src/types/config.ts +6 -6
  109. package/src/types/flow.ts +91 -0
  110. package/src/types/index.ts +15 -3
  111. package/src/types/lane.ts +0 -2
  112. package/src/types/logging.ts +5 -1
  113. package/src/types/task.ts +7 -11
  114. package/src/utils/config.ts +8 -16
  115. package/src/utils/dependency.ts +311 -2
  116. package/src/utils/enhanced-logger.ts +263 -927
  117. package/src/utils/git.ts +145 -5
  118. package/src/utils/state.ts +0 -2
  119. package/src/utils/task-service.ts +48 -40
  120. package/commands/cursorflow-review.md +0 -56
  121. package/commands/cursorflow-runs.md +0 -59
  122. package/dist/cli/runs.d.ts +0 -5
  123. package/dist/cli/runs.js +0 -214
  124. package/dist/cli/runs.js.map +0 -1
  125. package/dist/core/reviewer.d.ts +0 -66
  126. package/dist/core/reviewer.js +0 -265
  127. package/dist/core/reviewer.js.map +0 -1
  128. package/src/cli/runs.ts +0 -212
  129. package/src/core/reviewer.ts +0 -285
@@ -0,0 +1,177 @@
1
+ /**
2
+ * CursorFlow 'config' command
3
+ *
4
+ * View and set configuration values
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as logger from '../utils/logger';
10
+ import { loadConfig, findProjectRoot } from '../utils/config';
11
+ import { safeJoin } from '../utils/path';
12
+
13
+ interface ConfigOptions {
14
+ key: string | null;
15
+ value: string | null;
16
+ list: boolean;
17
+ help: boolean;
18
+ }
19
+
20
+ function printHelp(): void {
21
+ console.log(`
22
+ \x1b[1mcursorflow config\x1b[0m - 설정 조회 및 변경
23
+
24
+ \x1b[1m사용법:\x1b[0m
25
+ cursorflow config # 현재 설정 보기
26
+ cursorflow config <key> # 특정 설정 값 보기
27
+ cursorflow config <key> <value> # 설정 값 변경
28
+
29
+ \x1b[1m주요 설정 키:\x1b[0m
30
+ defaultModel 기본 AI 모델 (예: gemini-3-flash)
31
+ branchPrefix 브랜치 접두사 (예: feature/)
32
+ executor 실행기 (cursor-agent | cloud)
33
+
34
+ \x1b[1m예시:\x1b[0m
35
+ # 현재 설정 보기
36
+ cursorflow config
37
+
38
+ # 기본 모델 확인
39
+ cursorflow config defaultModel
40
+
41
+ # 기본 모델 변경
42
+ cursorflow config defaultModel opus-4.5-thinking
43
+
44
+ \x1b[1m참고:\x1b[0m
45
+ 설정 파일 위치: cursorflow.config.js
46
+ 설정을 영구 저장하려면 cursorflow.config.js 파일을 직접 수정하세요.
47
+ `);
48
+ }
49
+
50
+ function parseArgs(args: string[]): ConfigOptions {
51
+ const result: ConfigOptions = {
52
+ key: null,
53
+ value: null,
54
+ list: false,
55
+ help: false,
56
+ };
57
+
58
+ let positionalCount = 0;
59
+
60
+ for (let i = 0; i < args.length; i++) {
61
+ const arg = args[i];
62
+
63
+ if (arg === '--help' || arg === '-h') {
64
+ result.help = true;
65
+ } else if (arg === '--list' || arg === '-l') {
66
+ result.list = true;
67
+ } else if (!arg.startsWith('--')) {
68
+ if (positionalCount === 0) {
69
+ result.key = arg;
70
+ } else if (positionalCount === 1) {
71
+ result.value = arg;
72
+ }
73
+ positionalCount++;
74
+ }
75
+ }
76
+
77
+ return result;
78
+ }
79
+
80
+ async function config(args: string[]): Promise<void> {
81
+ const options = parseArgs(args);
82
+
83
+ if (options.help) {
84
+ printHelp();
85
+ return;
86
+ }
87
+
88
+ const projectRoot = findProjectRoot();
89
+ const currentConfig = loadConfig(projectRoot);
90
+ const configPath = safeJoin(projectRoot, 'cursorflow.config.js');
91
+
92
+ // If setting a value
93
+ if (options.key && options.value) {
94
+ const key = options.key;
95
+ const value = options.value;
96
+
97
+ // Validate key exists
98
+ if (!(key in currentConfig)) {
99
+ logger.error(`알 수 없는 설정 키: ${key}`);
100
+ console.log('\n사용 가능한 키: defaultModel, branchPrefix, executor, ...');
101
+ process.exit(1);
102
+ }
103
+
104
+ // Update or create config file
105
+ let configContent: string;
106
+
107
+ if (fs.existsSync(configPath)) {
108
+ // Read existing config
109
+ const existingContent = fs.readFileSync(configPath, 'utf-8');
110
+
111
+ // Simple regex replace for the key
112
+ const keyRegex = new RegExp(`(${key}\\s*:\\s*)['"]?[^'",\\n]+['"]?`, 'g');
113
+ if (keyRegex.test(existingContent)) {
114
+ configContent = existingContent.replace(keyRegex, `$1'${value}'`);
115
+ } else {
116
+ // Add the key to the config
117
+ configContent = existingContent.replace(
118
+ /module\.exports\s*=\s*\{/,
119
+ `module.exports = {\n ${key}: '${value}',`
120
+ );
121
+ }
122
+ } else {
123
+ // Create new config file
124
+ configContent = `module.exports = {
125
+ ${key}: '${value}',
126
+ };
127
+ `;
128
+ }
129
+
130
+ fs.writeFileSync(configPath, configContent);
131
+ logger.success(`✅ ${key} = '${value}' 설정됨`);
132
+ console.log(`\n설정 파일: ${configPath}`);
133
+ return;
134
+ }
135
+
136
+ // If viewing a specific key
137
+ if (options.key) {
138
+ const key = options.key;
139
+
140
+ if (!(key in currentConfig)) {
141
+ logger.error(`알 수 없는 설정 키: ${key}`);
142
+ process.exit(1);
143
+ }
144
+
145
+ const value = (currentConfig as any)[key];
146
+ console.log(`${key} = ${JSON.stringify(value)}`);
147
+ return;
148
+ }
149
+
150
+ // List all config
151
+ logger.section('CursorFlow 설정');
152
+ console.log('');
153
+
154
+ const importantKeys = [
155
+ 'defaultModel',
156
+ 'branchPrefix',
157
+ 'executor',
158
+ 'flowsDir',
159
+ 'tasksDir',
160
+ 'logsDir',
161
+ ];
162
+
163
+ console.log('\x1b[1m주요 설정:\x1b[0m');
164
+ for (const key of importantKeys) {
165
+ const value = (currentConfig as any)[key];
166
+ console.log(` ${key.padEnd(20)} ${JSON.stringify(value)}`);
167
+ }
168
+
169
+ console.log('');
170
+ console.log(`설정 파일: ${configPath}`);
171
+ console.log('');
172
+ console.log('설정 변경: cursorflow config <key> <value>');
173
+ console.log('예: cursorflow config defaultModel opus-4.5-thinking');
174
+ }
175
+
176
+ export = config;
177
+
package/src/cli/index.ts CHANGED
@@ -12,6 +12,11 @@ type CommandFn = (args: string[]) => Promise<void>;
12
12
  */
13
13
  const COMMANDS: Record<string, CommandFn> = {
14
14
  init: require('./init'),
15
+ // New Flow architecture commands
16
+ new: require('./new'),
17
+ add: require('./add'),
18
+ config: require('./config'),
19
+ // Legacy prepare command (deprecated)
15
20
  prepare: require('./prepare'),
16
21
  run: require('./run'),
17
22
  monitor: require('./monitor'),
@@ -21,7 +26,6 @@ const COMMANDS: Record<string, CommandFn> = {
21
26
  signal: require('./signal'),
22
27
  models: require('./models'),
23
28
  logs: require('./logs'),
24
- runs: require('./runs'),
25
29
  tasks: require('./tasks'),
26
30
  stop: require('./stop'),
27
31
  setup: require('./setup-commands').main,
@@ -35,37 +39,37 @@ function printHelp(): void {
35
39
  \x1b[1mUSAGE\x1b[0m
36
40
  $ \x1b[32mcursorflow\x1b[0m <command> [options]
37
41
 
38
- \x1b[1mCOMMANDS\x1b[0m
39
- \x1b[33minit\x1b[0m [options] Initialize CursorFlow in project
40
- \x1b[33msetup\x1b[0m [options] Install Cursor IDE commands
41
- \x1b[33mprepare\x1b[0m <feature> [opts] Prepare task directory and JSON files
42
- \x1b[33mrun\x1b[0m <tasks-dir> [options] Run orchestration (DAG-based)
43
- \x1b[33mmonitor\x1b[0m [run-dir] [options] \x1b[36mInteractive\x1b[0m lane dashboard
44
- \x1b[33mtasks\x1b[0m [name] [options] Browse and validate prepared tasks
45
- \x1b[33mruns\x1b[0m [run-id] [options] List and view run details
46
- \x1b[33mstop\x1b[0m [run-id] [options] Stop running workflows
47
- \x1b[33mclean\x1b[0m <type> [options] Clean branches/worktrees/logs/tasks
48
- \x1b[33mresume\x1b[0m [lane] [options] Resume lane(s) - use --all for batch resume
49
- \x1b[33mdoctor\x1b[0m [options] Check environment and preflight
50
- \x1b[33msignal\x1b[0m <lane> <msg> Directly intervene in a running lane
51
- \x1b[33mmodels\x1b[0m [options] List available AI models
52
- \x1b[33mlogs\x1b[0m [run-dir] [options] View, export, and follow logs
53
-
54
- \x1b[1mGLOBAL OPTIONS\x1b[0m
55
- --config <path> Config file path
56
- --help, -h Show help
57
- --version, -v Show version
58
-
59
- \x1b[1mEXAMPLES\x1b[0m
60
- $ \x1b[32mcursorflow init --example\x1b[0m
61
- $ \x1b[32mcursorflow prepare NewFeature --lanes 3\x1b[0m
62
- $ \x1b[32mcursorflow run _cursorflow/tasks/MyFeature/\x1b[0m
63
- $ \x1b[32mcursorflow monitor latest\x1b[0m
64
- $ \x1b[32mcursorflow logs --all --follow\x1b[0m
65
- $ \x1b[32mcursorflow runs --running\x1b[0m
66
- $ \x1b[32mcursorflow resume --all\x1b[0m
67
- $ \x1b[32mcursorflow doctor\x1b[0m
68
- $ \x1b[32mcursorflow models\x1b[0m
42
+ \x1b[1mFLOW COMMANDS (New)\x1b[0m
43
+ \x1b[33mnew\x1b[0m <flow> --lanes "..." Create a new Flow with Lanes
44
+ \x1b[33madd\x1b[0m <flow> <lane> --task Add Tasks to a Lane
45
+ \x1b[33mconfig\x1b[0m [key] [value] View or set config (e.g., defaultModel)
46
+
47
+ \x1b[1mEXECUTION\x1b[0m
48
+ \x1b[33mrun\x1b[0m <flow> [options] Run orchestration (DAG-based)
49
+ \x1b[33mmonitor\x1b[0m [run-dir] [options] \x1b[36mInteractive\x1b[0m lane dashboard
50
+ \x1b[33mstop\x1b[0m [run-id] [options] Stop running workflows
51
+ \x1b[33mresume\x1b[0m [lane] [options] Resume lane(s)
52
+
53
+ \x1b[1mINSPECTION\x1b[0m
54
+ \x1b[33mtasks\x1b[0m [name] [options] Browse and validate prepared tasks
55
+ \x1b[33mlogs\x1b[0m [run-dir] [options] View, export, and follow logs
56
+ \x1b[33mdoctor\x1b[0m [options] Check environment and preflight
57
+
58
+ \x1b[1mUTILITY\x1b[0m
59
+ \x1b[33minit\x1b[0m [options] Initialize CursorFlow in project
60
+ \x1b[33msetup\x1b[0m [options] Install Cursor IDE commands
61
+ \x1b[33mclean\x1b[0m <type> [options] Clean branches/worktrees/logs/tasks
62
+ \x1b[33msignal\x1b[0m <lane> <msg> Directly intervene in a running lane
63
+ \x1b[33mmodels\x1b[0m [options] List available AI models
64
+
65
+ \x1b[1mLEGACY\x1b[0m
66
+ \x1b[33mprepare\x1b[0m <feature> [opts] (deprecated) Use 'new' + 'add' instead
67
+
68
+ \x1b[1mQUICK START\x1b[0m
69
+ $ \x1b[32mcursorflow new MyFeature --lanes "backend,frontend"\x1b[0m
70
+ $ \x1b[32mcursorflow add MyFeature backend --task "name=impl|model=sonnet-4.5|prompt=API 구현"\x1b[0m
71
+ $ \x1b[32mcursorflow add MyFeature frontend --task "name=ui|model=sonnet-4.5|prompt=UI 구현" --after "backend"\x1b[0m
72
+ $ \x1b[32mcursorflow run MyFeature\x1b[0m
69
73
 
70
74
  \x1b[1mDOCUMENTATION\x1b[0m
71
75
  https://github.com/eungjin-cigro/cursorflow#readme
package/src/cli/logs.ts CHANGED
@@ -160,17 +160,12 @@ function displayTextLogs(
160
160
  let logFile: string;
161
161
  const readableLog = safeJoin(laneDir, 'terminal-readable.log');
162
162
  const rawLog = safeJoin(laneDir, 'terminal-raw.log');
163
- const cleanLog = safeJoin(laneDir, 'terminal.log');
164
163
 
165
164
  if (options.raw) {
166
165
  logFile = rawLog;
167
- } else if (options.clean) {
168
- logFile = cleanLog;
169
- } else if (options.readable && fs.existsSync(readableLog)) {
170
- logFile = readableLog;
171
166
  } else {
172
- // Default or fallback to clean log
173
- logFile = cleanLog;
167
+ // Default to readable log (clean option also uses readable now)
168
+ logFile = readableLog;
174
169
  }
175
170
 
176
171
  if (!fs.existsSync(logFile)) {
@@ -686,17 +681,12 @@ function followLogs(laneDir: string, options: LogsOptions): void {
686
681
  let logFile: string;
687
682
  const readableLog = safeJoin(laneDir, 'terminal-readable.log');
688
683
  const rawLog = safeJoin(laneDir, 'terminal-raw.log');
689
- const cleanLog = safeJoin(laneDir, 'terminal.log');
690
684
 
691
685
  if (options.raw) {
692
686
  logFile = rawLog;
693
- } else if (options.clean) {
694
- logFile = cleanLog;
695
- } else if (options.readable && fs.existsSync(readableLog)) {
696
- logFile = readableLog;
697
687
  } else {
698
- // Default or fallback to clean log
699
- logFile = cleanLog;
688
+ // Default to readable log
689
+ logFile = readableLog;
700
690
  }
701
691
 
702
692
  if (!fs.existsSync(logFile)) {
@@ -778,16 +768,14 @@ function displaySummary(runDir: string): void {
778
768
 
779
769
  for (const lane of lanes) {
780
770
  const laneDir = safeJoin(runDir, 'lanes', lane);
781
- const cleanLog = safeJoin(laneDir, 'terminal.log');
782
771
  const rawLog = safeJoin(laneDir, 'terminal-raw.log');
783
- const jsonLog = safeJoin(laneDir, 'terminal.jsonl');
784
772
  const readableLog = safeJoin(laneDir, 'terminal-readable.log');
785
773
 
786
774
  console.log(` ${logger.COLORS.green}📁 ${lane}${logger.COLORS.reset}`);
787
775
 
788
- if (fs.existsSync(cleanLog)) {
789
- const stats = fs.statSync(cleanLog);
790
- console.log(` └─ terminal.log ${formatSize(stats.size)}`);
776
+ if (fs.existsSync(readableLog)) {
777
+ const stats = fs.statSync(readableLog);
778
+ console.log(` └─ terminal-readable.log ${formatSize(stats.size)} ${logger.COLORS.yellow}(default)${logger.COLORS.reset}`);
791
779
  }
792
780
 
793
781
  if (fs.existsSync(rawLog)) {
@@ -795,18 +783,6 @@ function displaySummary(runDir: string): void {
795
783
  console.log(` └─ terminal-raw.log ${formatSize(stats.size)}`);
796
784
  }
797
785
 
798
- if (fs.existsSync(readableLog)) {
799
- const stats = fs.statSync(readableLog);
800
- console.log(` └─ terminal-readable.log ${formatSize(stats.size)} ${logger.COLORS.yellow}(parsed AI output)${logger.COLORS.reset}`);
801
- }
802
-
803
- if (fs.existsSync(jsonLog)) {
804
- const stats = fs.statSync(jsonLog);
805
- const entries = readJsonLog(jsonLog);
806
- const errors = entries.filter(e => e.level === 'error' || e.level === 'stderr').length;
807
- console.log(` └─ terminal.jsonl ${formatSize(stats.size)} (${entries.length} entries, ${errors} errors)`);
808
- }
809
-
810
786
  console.log('');
811
787
  }
812
788
  }
@@ -56,13 +56,13 @@ const UI = {
56
56
  interface LaneWithDeps {
57
57
  name: string;
58
58
  path: string;
59
- dependsOn: string[];
60
59
  }
61
60
 
62
61
  interface MonitorOptions {
63
62
  runDir?: string;
64
63
  interval: number;
65
64
  help: boolean;
65
+ list: boolean;
66
66
  }
67
67
 
68
68
  function printHelp(): void {
@@ -73,8 +73,14 @@ Interactive lane dashboard to track progress and dependencies.
73
73
 
74
74
  Options:
75
75
  [run-dir] Run directory to monitor (default: latest)
76
+ --list, -l List all runs (multiple flows dashboard)
76
77
  --interval <seconds> Refresh interval (default: 2)
77
78
  --help, -h Show help
79
+
80
+ Examples:
81
+ cursorflow monitor # Monitor latest run
82
+ cursorflow monitor --list # Show all runs dashboard
83
+ cursorflow monitor run-123 # Monitor specific run
78
84
  `);
79
85
  }
80
86
 
@@ -135,9 +141,10 @@ class InteractiveMonitor {
135
141
  return process.stdout.rows || 24;
136
142
  }
137
143
 
138
- constructor(runDir: string, interval: number, logsDir?: string) {
144
+ constructor(runDir: string, interval: number, logsDir?: string, initialView: View = View.LIST) {
139
145
  this.runDir = runDir;
140
146
  this.interval = interval;
147
+ this.view = initialView;
141
148
 
142
149
  // Set logs directory for multiple flows discovery
143
150
  if (logsDir) {
@@ -152,6 +159,21 @@ class InteractiveMonitor {
152
159
  }
153
160
 
154
161
  public async start() {
162
+ // Non-interactive mode for CI/pipes
163
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
164
+ this.discoverFlows();
165
+ this.refresh();
166
+ // Print summary and exit
167
+ if (this.view === View.FLOWS_DASHBOARD) {
168
+ console.log(`\nFound ${this.allFlows.length} flows`);
169
+ } else {
170
+ console.log(`\nMonitoring run: ${path.basename(this.runDir)}`);
171
+ const flowSummary = getFlowSummary(this.runDir);
172
+ console.log(`Status: ${flowSummary.running} running, ${flowSummary.completed} completed, ${flowSummary.failed} failed`);
173
+ }
174
+ return;
175
+ }
176
+
155
177
  this.setupTerminal();
156
178
 
157
179
  // Start unified log streaming
@@ -999,14 +1021,12 @@ class InteractiveMonitor {
999
1021
  // Next action
1000
1022
  let nextAction = '-';
1001
1023
  if (status.status === 'completed') {
1002
- const dependents = this.lanes.filter(l => laneStatuses[l.name]?.dependsOn?.includes(lane.name));
1003
- nextAction = dependents.length > 0 ? `→ ${dependents.map(d => d.name).join(', ')}` : '✓ Done';
1024
+ nextAction = '✓ Done';
1004
1025
  } else if (status.status === 'waiting') {
1005
1026
  if (status.waitingFor?.length > 0) {
1006
1027
  nextAction = `⏳ ${status.waitingFor.join(', ')}`;
1007
1028
  } else {
1008
- const missingDeps = status.dependsOn.filter((d: string) => laneStatuses[d]?.status !== 'completed');
1009
- nextAction = missingDeps.length > 0 ? `⏳ ${missingDeps.join(', ')}` : '⏳ waiting';
1029
+ nextAction = '⏳ waiting';
1010
1030
  }
1011
1031
  } else if (processStatus?.actualStatus === 'running') {
1012
1032
  nextAction = '🚀 working...';
@@ -1063,9 +1083,6 @@ class InteractiveMonitor {
1063
1083
  process.stdout.write(` ${UI.COLORS.dim}Duration${UI.COLORS.reset} ${this.formatDuration(processStatus?.duration || status.duration)}\n`);
1064
1084
  process.stdout.write(` ${UI.COLORS.dim}Branch${UI.COLORS.reset} ${status.pipelineBranch}\n`);
1065
1085
 
1066
- if (status.dependsOn && status.dependsOn.length > 0) {
1067
- process.stdout.write(` ${UI.COLORS.dim}Depends${UI.COLORS.reset} ${status.dependsOn.join(', ')}\n`);
1068
- }
1069
1086
  if (status.waitingFor && status.waitingFor.length > 0) {
1070
1087
  process.stdout.write(` ${UI.COLORS.yellow}Waiting${UI.COLORS.reset} ${status.waitingFor.join(', ')}\n`);
1071
1088
  }
@@ -1075,7 +1092,7 @@ class InteractiveMonitor {
1075
1092
 
1076
1093
  // Live terminal preview
1077
1094
  this.renderSectionTitle('Live Terminal', 'last 10 lines');
1078
- const logPath = safeJoin(lane.path, 'terminal.log');
1095
+ const logPath = safeJoin(lane.path, 'terminal-readable.log');
1079
1096
  if (fs.existsSync(logPath)) {
1080
1097
  const content = fs.readFileSync(logPath, 'utf8');
1081
1098
  const lines = content.split('\n').slice(-10);
@@ -1132,7 +1149,6 @@ class InteractiveMonitor {
1132
1149
  const colors: Record<string, string> = {
1133
1150
  user: UI.COLORS.yellow,
1134
1151
  assistant: UI.COLORS.green,
1135
- reviewer: UI.COLORS.magenta,
1136
1152
  intervention: UI.COLORS.red,
1137
1153
  system: UI.COLORS.cyan,
1138
1154
  };
@@ -1243,9 +1259,9 @@ class InteractiveMonitor {
1243
1259
  const nodeText = `${statusIcon} ${laneName}`;
1244
1260
  process.stdout.write(` ${statusColor}${nodeText.padEnd(20)}${UI.COLORS.reset}`);
1245
1261
 
1246
- // Render dependencies
1247
- if (status?.dependsOn?.length > 0) {
1248
- process.stdout.write(` ${UI.COLORS.dim}←${UI.COLORS.reset} ${UI.COLORS.yellow}${status.dependsOn.join(', ')}${UI.COLORS.reset}`);
1262
+ // Show task-level dependencies if waiting
1263
+ if (status?.waitingFor?.length > 0) {
1264
+ process.stdout.write(` ${UI.COLORS.dim}←${UI.COLORS.reset} ${UI.COLORS.yellow}${status.waitingFor.join(', ')}${UI.COLORS.reset}`);
1249
1265
  }
1250
1266
  process.stdout.write('\n');
1251
1267
  }
@@ -1256,54 +1272,21 @@ class InteractiveMonitor {
1256
1272
  }
1257
1273
  }
1258
1274
 
1259
- process.stdout.write(`\n ${UI.COLORS.dim}Lanes wait for dependencies to complete before starting${UI.COLORS.reset}\n`);
1275
+ process.stdout.write(`\n ${UI.COLORS.dim}Tasks can wait for other tasks using task-level dependencies${UI.COLORS.reset}\n`);
1260
1276
 
1261
1277
  this.renderFooter(['[←/Esc] Back']);
1262
1278
  }
1263
1279
 
1264
1280
  /**
1265
- * Calculate dependency levels for visualization
1281
+ * Calculate levels for visualization (all lanes run in parallel now)
1266
1282
  */
1267
1283
  private calculateDependencyLevels(): string[][] {
1268
- const levels: string[][] = [];
1269
- const assigned = new Set<string>();
1270
-
1271
- // First, find lanes with no dependencies
1272
- const noDeps = this.lanes.filter(l => !l.dependsOn || l.dependsOn.length === 0);
1273
- if (noDeps.length > 0) {
1274
- levels.push(noDeps.map(l => l.name));
1275
- noDeps.forEach(l => assigned.add(l.name));
1276
- }
1277
-
1278
- // Then assign remaining lanes by dependency completion
1279
- let maxIterations = 10;
1280
- while (assigned.size < this.lanes.length && maxIterations-- > 0) {
1281
- const nextLevel: string[] = [];
1282
-
1283
- for (const lane of this.lanes) {
1284
- if (assigned.has(lane.name)) continue;
1285
-
1286
- // Check if all dependencies are assigned
1287
- const allDepsAssigned = lane.dependsOn.every(d => assigned.has(d));
1288
- if (allDepsAssigned) {
1289
- nextLevel.push(lane.name);
1290
- }
1291
- }
1292
-
1293
- if (nextLevel.length === 0) {
1294
- // Remaining lanes have circular deps or missing deps
1295
- const remaining = this.lanes.filter(l => !assigned.has(l.name)).map(l => l.name);
1296
- if (remaining.length > 0) {
1297
- levels.push(remaining);
1298
- }
1299
- break;
1300
- }
1301
-
1302
- levels.push(nextLevel);
1303
- nextLevel.forEach(n => assigned.add(n));
1284
+ // Since lane-level dependencies are removed, all lanes can run in parallel
1285
+ // Group them into a single level
1286
+ if (this.lanes.length === 0) {
1287
+ return [];
1304
1288
  }
1305
-
1306
- return levels;
1289
+ return [this.lanes.map(l => l.name)];
1307
1290
  }
1308
1291
 
1309
1292
  private renderTerminal() {
@@ -1326,8 +1309,8 @@ class InteractiveMonitor {
1326
1309
  logLines = this.getReadableLogLines(jsonlPath, lane.name);
1327
1310
  totalLines = logLines.length;
1328
1311
  } else {
1329
- // Use raw log
1330
- const logPath = safeJoin(lane.path, 'terminal.log');
1312
+ // Use readable log
1313
+ const logPath = safeJoin(lane.path, 'terminal-readable.log');
1331
1314
  if (fs.existsSync(logPath)) {
1332
1315
  const content = fs.readFileSync(logPath, 'utf8');
1333
1316
  logLines = content.split('\n');
@@ -1689,39 +1672,33 @@ class InteractiveMonitor {
1689
1672
  return fs.readdirSync(lanesDir)
1690
1673
  .filter(d => fs.statSync(safeJoin(lanesDir, d)).isDirectory())
1691
1674
  .map(name => {
1692
- const config = laneConfigs.find(c => c.name === name);
1693
1675
  return {
1694
1676
  name,
1695
1677
  path: safeJoin(lanesDir, name),
1696
- dependsOn: config?.dependsOn || [],
1697
1678
  };
1698
1679
  });
1699
1680
  }
1700
1681
 
1701
- private listLaneFilesFromDir(tasksDir: string): { name: string; dependsOn: string[] }[] {
1682
+ private listLaneFilesFromDir(tasksDir: string): { name: string }[] {
1702
1683
  if (!fs.existsSync(tasksDir)) return [];
1703
1684
  return fs.readdirSync(tasksDir)
1704
1685
  .filter(f => f.endsWith('.json'))
1705
1686
  .map(f => {
1706
1687
  const filePath = safeJoin(tasksDir, f);
1707
1688
  try {
1708
- const config = JSON.parse(fs.readFileSync(filePath, 'utf8'));
1709
- return { name: path.basename(f, '.json'), dependsOn: config.dependsOn || [] };
1689
+ return { name: path.basename(f, '.json') };
1710
1690
  } catch {
1711
- return { name: path.basename(f, '.json'), dependsOn: [] };
1691
+ return { name: path.basename(f, '.json') };
1712
1692
  }
1713
1693
  });
1714
1694
  }
1715
1695
 
1716
- private getLaneStatus(lanePath: string, laneName: string) {
1696
+ private getLaneStatus(lanePath: string, _laneName: string) {
1717
1697
  const statePath = safeJoin(lanePath, 'state.json');
1718
1698
  const state = loadState<LaneState & { chatId?: string }>(statePath);
1719
1699
 
1720
- const laneInfo = this.lanes.find(l => l.name === laneName);
1721
- const dependsOn = state?.dependsOn || laneInfo?.dependsOn || [];
1722
-
1723
1700
  if (!state) {
1724
- return { status: 'pending', currentTask: 0, totalTasks: '?', progress: '0%', dependsOn, duration: 0, pipelineBranch: '-', chatId: '-' };
1701
+ return { status: 'pending', currentTask: 0, totalTasks: '?', progress: '0%', duration: 0, pipelineBranch: '-', chatId: '-' };
1725
1702
  }
1726
1703
 
1727
1704
  const progress = state.totalTasks > 0 ? Math.round((state.currentTaskIndex / state.totalTasks) * 100) : 0;
@@ -1737,7 +1714,6 @@ class InteractiveMonitor {
1737
1714
  progress: `${progress}%`,
1738
1715
  pipelineBranch: state.pipelineBranch || '-',
1739
1716
  chatId: state.chatId || '-',
1740
- dependsOn,
1741
1717
  duration,
1742
1718
  error: state.error,
1743
1719
  pid: state.pid,
@@ -1788,6 +1764,8 @@ function findLatestRunDir(logsDir: string): string | null {
1788
1764
  */
1789
1765
  async function monitor(args: string[]): Promise<void> {
1790
1766
  const help = args.includes('--help') || args.includes('-h');
1767
+ const list = args.includes('--list') || args.includes('-l');
1768
+
1791
1769
  if (help) {
1792
1770
  printHelp();
1793
1771
  return;
@@ -1802,12 +1780,18 @@ async function monitor(args: string[]): Promise<void> {
1802
1780
  let runDir = runDirArg;
1803
1781
  if (!runDir || runDir === 'latest') {
1804
1782
  runDir = findLatestRunDir(config.logsDir) || undefined;
1805
- if (!runDir) throw new Error('No run directories found');
1783
+ if (!runDir && !list) throw new Error('No run directories found');
1784
+ if (!runDir && list) {
1785
+ // Create a dummy runDir if none exists but we want to see the list (dashboard will handle empty list)
1786
+ runDir = path.join(config.logsDir, 'runs', 'empty');
1787
+ }
1806
1788
  }
1807
1789
 
1808
- if (!fs.existsSync(runDir)) throw new Error(`Run directory not found: ${runDir}`);
1790
+ if (runDir && !fs.existsSync(runDir) && !list) {
1791
+ throw new Error(`Run directory not found: ${runDir}`);
1792
+ }
1809
1793
 
1810
- const monitor = new InteractiveMonitor(runDir, interval);
1794
+ const monitor = new InteractiveMonitor(runDir!, interval, undefined, list ? View.FLOWS_DASHBOARD : View.LIST);
1811
1795
  await monitor.start();
1812
1796
  }
1813
1797