@litmers/cursorflow-orchestrator 0.1.18 → 0.1.26

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 (234) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -7
  3. package/commands/cursorflow-clean.md +19 -0
  4. package/commands/cursorflow-runs.md +59 -0
  5. package/commands/cursorflow-stop.md +55 -0
  6. package/dist/cli/clean.js +178 -6
  7. package/dist/cli/clean.js.map +1 -1
  8. package/dist/cli/index.js +12 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +8 -7
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/logs.js +126 -77
  13. package/dist/cli/logs.js.map +1 -1
  14. package/dist/cli/monitor.d.ts +7 -0
  15. package/dist/cli/monitor.js +1021 -202
  16. package/dist/cli/monitor.js.map +1 -1
  17. package/dist/cli/prepare.js +39 -21
  18. package/dist/cli/prepare.js.map +1 -1
  19. package/dist/cli/resume.js +268 -163
  20. package/dist/cli/resume.js.map +1 -1
  21. package/dist/cli/run.js +11 -5
  22. package/dist/cli/run.js.map +1 -1
  23. package/dist/cli/runs.d.ts +5 -0
  24. package/dist/cli/runs.js +214 -0
  25. package/dist/cli/runs.js.map +1 -0
  26. package/dist/cli/setup-commands.js +0 -0
  27. package/dist/cli/signal.js +8 -8
  28. package/dist/cli/signal.js.map +1 -1
  29. package/dist/cli/stop.d.ts +5 -0
  30. package/dist/cli/stop.js +215 -0
  31. package/dist/cli/stop.js.map +1 -0
  32. package/dist/cli/tasks.d.ts +10 -0
  33. package/dist/cli/tasks.js +165 -0
  34. package/dist/cli/tasks.js.map +1 -0
  35. package/dist/core/auto-recovery.d.ts +212 -0
  36. package/dist/core/auto-recovery.js +737 -0
  37. package/dist/core/auto-recovery.js.map +1 -0
  38. package/dist/core/failure-policy.d.ts +156 -0
  39. package/dist/core/failure-policy.js +488 -0
  40. package/dist/core/failure-policy.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +16 -2
  42. package/dist/core/orchestrator.js +439 -105
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/reviewer.d.ts +2 -0
  45. package/dist/core/reviewer.js +2 -0
  46. package/dist/core/reviewer.js.map +1 -1
  47. package/dist/core/runner.d.ts +33 -10
  48. package/dist/core/runner.js +374 -164
  49. package/dist/core/runner.js.map +1 -1
  50. package/dist/services/logging/buffer.d.ts +67 -0
  51. package/dist/services/logging/buffer.js +309 -0
  52. package/dist/services/logging/buffer.js.map +1 -0
  53. package/dist/services/logging/console.d.ts +89 -0
  54. package/dist/services/logging/console.js +169 -0
  55. package/dist/services/logging/console.js.map +1 -0
  56. package/dist/services/logging/file-writer.d.ts +71 -0
  57. package/dist/services/logging/file-writer.js +516 -0
  58. package/dist/services/logging/file-writer.js.map +1 -0
  59. package/dist/services/logging/formatter.d.ts +39 -0
  60. package/dist/services/logging/formatter.js +227 -0
  61. package/dist/services/logging/formatter.js.map +1 -0
  62. package/dist/services/logging/index.d.ts +11 -0
  63. package/dist/services/logging/index.js +30 -0
  64. package/dist/services/logging/index.js.map +1 -0
  65. package/dist/services/logging/parser.d.ts +31 -0
  66. package/dist/services/logging/parser.js +222 -0
  67. package/dist/services/logging/parser.js.map +1 -0
  68. package/dist/services/process/index.d.ts +59 -0
  69. package/dist/services/process/index.js +257 -0
  70. package/dist/services/process/index.js.map +1 -0
  71. package/dist/types/agent.d.ts +20 -0
  72. package/dist/types/agent.js +6 -0
  73. package/dist/types/agent.js.map +1 -0
  74. package/dist/types/config.d.ts +65 -0
  75. package/dist/types/config.js +6 -0
  76. package/dist/types/config.js.map +1 -0
  77. package/dist/types/events.d.ts +125 -0
  78. package/dist/types/events.js +6 -0
  79. package/dist/types/events.js.map +1 -0
  80. package/dist/types/index.d.ts +12 -0
  81. package/dist/types/index.js +37 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/dist/types/lane.d.ts +43 -0
  84. package/dist/types/lane.js +6 -0
  85. package/dist/types/lane.js.map +1 -0
  86. package/dist/types/logging.d.ts +71 -0
  87. package/dist/types/logging.js +16 -0
  88. package/dist/types/logging.js.map +1 -0
  89. package/dist/types/review.d.ts +17 -0
  90. package/dist/types/review.js +6 -0
  91. package/dist/types/review.js.map +1 -0
  92. package/dist/types/run.d.ts +32 -0
  93. package/dist/types/run.js +6 -0
  94. package/dist/types/run.js.map +1 -0
  95. package/dist/types/task.d.ts +71 -0
  96. package/dist/types/task.js +6 -0
  97. package/dist/types/task.js.map +1 -0
  98. package/dist/ui/components.d.ts +134 -0
  99. package/dist/ui/components.js +389 -0
  100. package/dist/ui/components.js.map +1 -0
  101. package/dist/ui/log-viewer.d.ts +49 -0
  102. package/dist/ui/log-viewer.js +449 -0
  103. package/dist/ui/log-viewer.js.map +1 -0
  104. package/dist/utils/checkpoint.d.ts +87 -0
  105. package/dist/utils/checkpoint.js +317 -0
  106. package/dist/utils/checkpoint.js.map +1 -0
  107. package/dist/utils/config.d.ts +4 -0
  108. package/dist/utils/config.js +18 -8
  109. package/dist/utils/config.js.map +1 -1
  110. package/dist/utils/cursor-agent.js.map +1 -1
  111. package/dist/utils/dependency.d.ts +74 -0
  112. package/dist/utils/dependency.js +420 -0
  113. package/dist/utils/dependency.js.map +1 -0
  114. package/dist/utils/doctor.js +17 -11
  115. package/dist/utils/doctor.js.map +1 -1
  116. package/dist/utils/enhanced-logger.d.ts +10 -33
  117. package/dist/utils/enhanced-logger.js +108 -20
  118. package/dist/utils/enhanced-logger.js.map +1 -1
  119. package/dist/utils/git.d.ts +121 -0
  120. package/dist/utils/git.js +484 -11
  121. package/dist/utils/git.js.map +1 -1
  122. package/dist/utils/health.d.ts +91 -0
  123. package/dist/utils/health.js +556 -0
  124. package/dist/utils/health.js.map +1 -0
  125. package/dist/utils/lock.d.ts +95 -0
  126. package/dist/utils/lock.js +332 -0
  127. package/dist/utils/lock.js.map +1 -0
  128. package/dist/utils/log-buffer.d.ts +17 -0
  129. package/dist/utils/log-buffer.js +14 -0
  130. package/dist/utils/log-buffer.js.map +1 -0
  131. package/dist/utils/log-constants.d.ts +23 -0
  132. package/dist/utils/log-constants.js +28 -0
  133. package/dist/utils/log-constants.js.map +1 -0
  134. package/dist/utils/log-formatter.d.ts +25 -0
  135. package/dist/utils/log-formatter.js +237 -0
  136. package/dist/utils/log-formatter.js.map +1 -0
  137. package/dist/utils/log-service.d.ts +19 -0
  138. package/dist/utils/log-service.js +47 -0
  139. package/dist/utils/log-service.js.map +1 -0
  140. package/dist/utils/logger.d.ts +46 -27
  141. package/dist/utils/logger.js +82 -60
  142. package/dist/utils/logger.js.map +1 -1
  143. package/dist/utils/path.d.ts +19 -0
  144. package/dist/utils/path.js +77 -0
  145. package/dist/utils/path.js.map +1 -0
  146. package/dist/utils/process-manager.d.ts +21 -0
  147. package/dist/utils/process-manager.js +138 -0
  148. package/dist/utils/process-manager.js.map +1 -0
  149. package/dist/utils/retry.d.ts +121 -0
  150. package/dist/utils/retry.js +374 -0
  151. package/dist/utils/retry.js.map +1 -0
  152. package/dist/utils/run-service.d.ts +88 -0
  153. package/dist/utils/run-service.js +412 -0
  154. package/dist/utils/run-service.js.map +1 -0
  155. package/dist/utils/state.d.ts +62 -3
  156. package/dist/utils/state.js +317 -11
  157. package/dist/utils/state.js.map +1 -1
  158. package/dist/utils/task-service.d.ts +82 -0
  159. package/dist/utils/task-service.js +348 -0
  160. package/dist/utils/task-service.js.map +1 -0
  161. package/dist/utils/template.d.ts +14 -0
  162. package/dist/utils/template.js +122 -0
  163. package/dist/utils/template.js.map +1 -0
  164. package/dist/utils/types.d.ts +2 -271
  165. package/dist/utils/types.js +16 -0
  166. package/dist/utils/types.js.map +1 -1
  167. package/package.json +38 -23
  168. package/scripts/ai-security-check.js +0 -1
  169. package/scripts/local-security-gate.sh +0 -0
  170. package/scripts/monitor-lanes.sh +94 -0
  171. package/scripts/patches/test-cursor-agent.js +0 -1
  172. package/scripts/release.sh +0 -0
  173. package/scripts/setup-security.sh +0 -0
  174. package/scripts/stream-logs.sh +72 -0
  175. package/scripts/verify-and-fix.sh +0 -0
  176. package/src/cli/clean.ts +187 -6
  177. package/src/cli/index.ts +12 -1
  178. package/src/cli/init.ts +8 -7
  179. package/src/cli/logs.ts +124 -77
  180. package/src/cli/monitor.ts +1815 -898
  181. package/src/cli/prepare.ts +41 -21
  182. package/src/cli/resume.ts +753 -626
  183. package/src/cli/run.ts +12 -5
  184. package/src/cli/runs.ts +212 -0
  185. package/src/cli/setup-commands.ts +0 -0
  186. package/src/cli/signal.ts +8 -7
  187. package/src/cli/stop.ts +209 -0
  188. package/src/cli/tasks.ts +154 -0
  189. package/src/core/auto-recovery.ts +909 -0
  190. package/src/core/failure-policy.ts +592 -0
  191. package/src/core/orchestrator.ts +1131 -704
  192. package/src/core/reviewer.ts +4 -0
  193. package/src/core/runner.ts +444 -180
  194. package/src/services/logging/buffer.ts +326 -0
  195. package/src/services/logging/console.ts +193 -0
  196. package/src/services/logging/file-writer.ts +526 -0
  197. package/src/services/logging/formatter.ts +268 -0
  198. package/src/services/logging/index.ts +16 -0
  199. package/src/services/logging/parser.ts +232 -0
  200. package/src/services/process/index.ts +261 -0
  201. package/src/types/agent.ts +24 -0
  202. package/src/types/config.ts +79 -0
  203. package/src/types/events.ts +156 -0
  204. package/src/types/index.ts +29 -0
  205. package/src/types/lane.ts +56 -0
  206. package/src/types/logging.ts +96 -0
  207. package/src/types/review.ts +20 -0
  208. package/src/types/run.ts +37 -0
  209. package/src/types/task.ts +79 -0
  210. package/src/ui/components.ts +430 -0
  211. package/src/ui/log-viewer.ts +485 -0
  212. package/src/utils/checkpoint.ts +374 -0
  213. package/src/utils/config.ts +18 -8
  214. package/src/utils/cursor-agent.ts +1 -1
  215. package/src/utils/dependency.ts +482 -0
  216. package/src/utils/doctor.ts +18 -11
  217. package/src/utils/enhanced-logger.ts +122 -60
  218. package/src/utils/git.ts +517 -11
  219. package/src/utils/health.ts +596 -0
  220. package/src/utils/lock.ts +346 -0
  221. package/src/utils/log-buffer.ts +28 -0
  222. package/src/utils/log-constants.ts +26 -0
  223. package/src/utils/log-formatter.ts +245 -0
  224. package/src/utils/log-service.ts +49 -0
  225. package/src/utils/logger.ts +100 -51
  226. package/src/utils/path.ts +45 -0
  227. package/src/utils/process-manager.ts +100 -0
  228. package/src/utils/retry.ts +413 -0
  229. package/src/utils/run-service.ts +433 -0
  230. package/src/utils/state.ts +385 -11
  231. package/src/utils/task-service.ts +370 -0
  232. package/src/utils/template.ts +92 -0
  233. package/src/utils/types.ts +2 -314
  234. package/templates/basic.json +21 -0
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Checkpoint and recovery system for CursorFlow
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { safeJoin } from './path';
8
+ import * as git from './git';
9
+ import { LaneState } from './types';
10
+ import { loadState, saveState } from './state';
11
+ import * as logger from './logger';
12
+
13
+ export interface GitState {
14
+ branch: string | null;
15
+ commit: string | null;
16
+ uncommittedChanges: boolean;
17
+ changedFiles: string[];
18
+ }
19
+
20
+ export interface Checkpoint {
21
+ id: string;
22
+ timestamp: number;
23
+ laneName: string;
24
+ laneState: LaneState;
25
+ gitState: GitState | null;
26
+ taskIndex: number;
27
+ description?: string;
28
+ }
29
+
30
+ export interface CheckpointOptions {
31
+ /** Directory to store checkpoints */
32
+ checkpointDir?: string;
33
+ /** Maximum number of checkpoints to keep per lane */
34
+ maxCheckpoints?: number;
35
+ /** Description of the checkpoint */
36
+ description?: string;
37
+ }
38
+
39
+ const DEFAULT_MAX_CHECKPOINTS = 10;
40
+
41
+ /**
42
+ * Get checkpoint directory for a lane
43
+ */
44
+ export function getCheckpointDir(laneDir: string): string {
45
+ return safeJoin(laneDir, 'checkpoints');
46
+ }
47
+
48
+ /**
49
+ * Generate checkpoint ID
50
+ */
51
+ function generateCheckpointId(): string {
52
+ return `cp-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
53
+ }
54
+
55
+ /**
56
+ * Create a checkpoint for a lane
57
+ */
58
+ export async function createCheckpoint(
59
+ laneName: string,
60
+ laneDir: string,
61
+ worktreeDir: string | null,
62
+ options: CheckpointOptions = {}
63
+ ): Promise<Checkpoint> {
64
+ const checkpointDir = options.checkpointDir || getCheckpointDir(laneDir);
65
+ const maxCheckpoints = options.maxCheckpoints || DEFAULT_MAX_CHECKPOINTS;
66
+
67
+ // Ensure checkpoint directory exists
68
+ if (!fs.existsSync(checkpointDir)) {
69
+ fs.mkdirSync(checkpointDir, { recursive: true });
70
+ }
71
+
72
+ // Load current lane state
73
+ const statePath = safeJoin(laneDir, 'state.json');
74
+ const laneState = loadState<LaneState>(statePath);
75
+
76
+ if (!laneState) {
77
+ throw new Error(`Cannot create checkpoint: Lane state not found at ${statePath}`);
78
+ }
79
+
80
+ // Get Git state if worktree exists
81
+ let gitState: GitState | null = null;
82
+ if (worktreeDir && fs.existsSync(worktreeDir)) {
83
+ try {
84
+ const branch = git.getCurrentBranch(worktreeDir);
85
+ const commit = git.runGit(['rev-parse', 'HEAD'], { cwd: worktreeDir, silent: true });
86
+ const changedFiles = git.getChangedFiles(worktreeDir);
87
+
88
+ gitState = {
89
+ branch,
90
+ commit,
91
+ uncommittedChanges: changedFiles.length > 0,
92
+ changedFiles: changedFiles.map(f => f.file),
93
+ };
94
+ } catch (e) {
95
+ logger.warn(`Failed to capture Git state for checkpoint: ${e}`);
96
+ }
97
+ }
98
+
99
+ // Create checkpoint
100
+ const checkpoint: Checkpoint = {
101
+ id: generateCheckpointId(),
102
+ timestamp: Date.now(),
103
+ laneName,
104
+ laneState: { ...laneState },
105
+ gitState,
106
+ taskIndex: laneState.currentTaskIndex,
107
+ description: options.description,
108
+ };
109
+
110
+ // Save checkpoint
111
+ const checkpointPath = safeJoin(checkpointDir, `${checkpoint.id}.json`);
112
+ fs.writeFileSync(checkpointPath, JSON.stringify(checkpoint, null, 2), 'utf8');
113
+
114
+ // Cleanup old checkpoints
115
+ await cleanupOldCheckpoints(checkpointDir, maxCheckpoints);
116
+
117
+ logger.info(`Created checkpoint: ${checkpoint.id} (task ${checkpoint.taskIndex})`);
118
+
119
+ return checkpoint;
120
+ }
121
+
122
+ /**
123
+ * List all checkpoints for a lane
124
+ */
125
+ export function listCheckpoints(laneDir: string): Checkpoint[] {
126
+ const checkpointDir = getCheckpointDir(laneDir);
127
+
128
+ if (!fs.existsSync(checkpointDir)) {
129
+ return [];
130
+ }
131
+
132
+ const files = fs.readdirSync(checkpointDir)
133
+ .filter(f => f.startsWith('cp-') && f.endsWith('.json'))
134
+ .sort()
135
+ .reverse(); // Most recent first
136
+
137
+ return files.map(f => {
138
+ try {
139
+ const content = fs.readFileSync(safeJoin(checkpointDir, f), 'utf8');
140
+ return JSON.parse(content) as Checkpoint;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }).filter((cp): cp is Checkpoint => cp !== null);
145
+ }
146
+
147
+ /**
148
+ * Get a specific checkpoint
149
+ */
150
+ export function getCheckpoint(laneDir: string, checkpointId: string): Checkpoint | null {
151
+ const checkpointDir = getCheckpointDir(laneDir);
152
+ const checkpointPath = safeJoin(checkpointDir, `${checkpointId}.json`);
153
+
154
+ if (!fs.existsSync(checkpointPath)) {
155
+ return null;
156
+ }
157
+
158
+ try {
159
+ const content = fs.readFileSync(checkpointPath, 'utf8');
160
+ return JSON.parse(content) as Checkpoint;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get the latest checkpoint for a lane
168
+ */
169
+ export function getLatestCheckpoint(laneDir: string): Checkpoint | null {
170
+ const checkpoints = listCheckpoints(laneDir);
171
+ return checkpoints.length > 0 ? checkpoints[0]! : null;
172
+ }
173
+
174
+ /**
175
+ * Restore lane state from a checkpoint
176
+ */
177
+ export async function restoreFromCheckpoint(
178
+ checkpoint: Checkpoint,
179
+ laneDir: string,
180
+ options: {
181
+ restoreGitState?: boolean;
182
+ worktreeDir?: string;
183
+ } = {}
184
+ ): Promise<{ success: boolean; warnings: string[] }> {
185
+ const warnings: string[] = [];
186
+
187
+ // Restore lane state
188
+ const statePath = safeJoin(laneDir, 'state.json');
189
+ const restoredState: LaneState = {
190
+ ...checkpoint.laneState,
191
+ status: 'pending', // Reset status for resume
192
+ error: null,
193
+ updatedAt: Date.now(),
194
+ };
195
+ saveState(statePath, restoredState);
196
+
197
+ logger.info(`Restored lane state from checkpoint ${checkpoint.id}`);
198
+
199
+ // Restore Git state if requested
200
+ if (options.restoreGitState && checkpoint.gitState && options.worktreeDir) {
201
+ const worktreeDir = options.worktreeDir;
202
+
203
+ if (!fs.existsSync(worktreeDir)) {
204
+ warnings.push(`Worktree not found: ${worktreeDir}`);
205
+ } else {
206
+ try {
207
+ // Check for uncommitted changes
208
+ if (git.hasUncommittedChanges(worktreeDir)) {
209
+ warnings.push('Worktree has uncommitted changes. Stashing...');
210
+ git.runGit(['stash', 'push', '-m', `Pre-restore checkpoint ${checkpoint.id}`], { cwd: worktreeDir });
211
+ }
212
+
213
+ // Checkout the checkpoint commit if available
214
+ if (checkpoint.gitState.commit) {
215
+ git.runGit(['checkout', checkpoint.gitState.commit], { cwd: worktreeDir, silent: true });
216
+ logger.info(`Restored Git state to commit ${checkpoint.gitState.commit.substring(0, 7)}`);
217
+ }
218
+ } catch (e: any) {
219
+ warnings.push(`Failed to restore Git state: ${e.message}`);
220
+ }
221
+ }
222
+ }
223
+
224
+ return { success: true, warnings };
225
+ }
226
+
227
+ /**
228
+ * Delete a checkpoint
229
+ */
230
+ export function deleteCheckpoint(laneDir: string, checkpointId: string): boolean {
231
+ const checkpointDir = getCheckpointDir(laneDir);
232
+ const checkpointPath = safeJoin(checkpointDir, `${checkpointId}.json`);
233
+
234
+ if (!fs.existsSync(checkpointPath)) {
235
+ return false;
236
+ }
237
+
238
+ try {
239
+ fs.unlinkSync(checkpointPath);
240
+ return true;
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Clean up old checkpoints, keeping only the most recent ones
248
+ */
249
+ async function cleanupOldCheckpoints(checkpointDir: string, maxCheckpoints: number): Promise<number> {
250
+ if (!fs.existsSync(checkpointDir)) {
251
+ return 0;
252
+ }
253
+
254
+ const files = fs.readdirSync(checkpointDir)
255
+ .filter(f => f.startsWith('cp-') && f.endsWith('.json'))
256
+ .sort()
257
+ .reverse();
258
+
259
+ let deleted = 0;
260
+ for (let i = maxCheckpoints; i < files.length; i++) {
261
+ try {
262
+ fs.unlinkSync(safeJoin(checkpointDir, files[i]!));
263
+ deleted++;
264
+ } catch {
265
+ // Ignore
266
+ }
267
+ }
268
+
269
+ return deleted;
270
+ }
271
+
272
+ /**
273
+ * Auto-checkpoint decorator - creates checkpoints before critical operations
274
+ */
275
+ export function withAutoCheckpoint<T extends (...args: any[]) => Promise<any>>(
276
+ fn: T,
277
+ options: {
278
+ getLaneDir: (...args: Parameters<T>) => string;
279
+ getLaneName: (...args: Parameters<T>) => string;
280
+ getWorktreeDir?: (...args: Parameters<T>) => string | null;
281
+ description?: string;
282
+ }
283
+ ): T {
284
+ return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
285
+ const laneDir = options.getLaneDir(...args);
286
+ const laneName = options.getLaneName(...args);
287
+ const worktreeDir = options.getWorktreeDir?.(...args) || null;
288
+
289
+ try {
290
+ await createCheckpoint(laneName, laneDir, worktreeDir, {
291
+ description: options.description || `Before ${fn.name}`,
292
+ });
293
+ } catch (e) {
294
+ logger.warn(`Auto-checkpoint failed: ${e}`);
295
+ }
296
+
297
+ return fn(...args);
298
+ }) as T;
299
+ }
300
+
301
+ /**
302
+ * Find the best checkpoint to recover from after a failure
303
+ */
304
+ export function findRecoveryCheckpoint(
305
+ laneDir: string,
306
+ targetTaskIndex?: number
307
+ ): Checkpoint | null {
308
+ const checkpoints = listCheckpoints(laneDir);
309
+
310
+ if (checkpoints.length === 0) {
311
+ return null;
312
+ }
313
+
314
+ // If target task index is specified, find the checkpoint just before it
315
+ if (targetTaskIndex !== undefined) {
316
+ for (const cp of checkpoints) {
317
+ if (cp.taskIndex <= targetTaskIndex) {
318
+ return cp;
319
+ }
320
+ }
321
+ }
322
+
323
+ // Return the latest checkpoint
324
+ return checkpoints[0]!;
325
+ }
326
+
327
+ /**
328
+ * Checkpoint statistics for monitoring
329
+ */
330
+ export interface CheckpointStats {
331
+ totalCheckpoints: number;
332
+ oldestTimestamp: number | null;
333
+ newestTimestamp: number | null;
334
+ totalSizeBytes: number;
335
+ }
336
+
337
+ /**
338
+ * Get checkpoint statistics for a lane
339
+ */
340
+ export function getCheckpointStats(laneDir: string): CheckpointStats {
341
+ const checkpointDir = getCheckpointDir(laneDir);
342
+
343
+ if (!fs.existsSync(checkpointDir)) {
344
+ return {
345
+ totalCheckpoints: 0,
346
+ oldestTimestamp: null,
347
+ newestTimestamp: null,
348
+ totalSizeBytes: 0,
349
+ };
350
+ }
351
+
352
+ const checkpoints = listCheckpoints(laneDir);
353
+ let totalSize = 0;
354
+
355
+ const files = fs.readdirSync(checkpointDir)
356
+ .filter(f => f.startsWith('cp-') && f.endsWith('.json'));
357
+
358
+ for (const f of files) {
359
+ try {
360
+ const stat = fs.statSync(safeJoin(checkpointDir, f));
361
+ totalSize += stat.size;
362
+ } catch {
363
+ // Ignore
364
+ }
365
+ }
366
+
367
+ return {
368
+ totalCheckpoints: checkpoints.length,
369
+ oldestTimestamp: checkpoints.length > 0 ? checkpoints[checkpoints.length - 1]!.timestamp : null,
370
+ newestTimestamp: checkpoints.length > 0 ? checkpoints[0]!.timestamp : null,
371
+ totalSizeBytes: totalSize,
372
+ };
373
+ }
374
+
@@ -7,6 +7,7 @@
7
7
  import * as path from 'path';
8
8
  import * as fs from 'fs';
9
9
  import { CursorFlowConfig } from './types';
10
+ import { safeJoin } from './path';
10
11
  export { CursorFlowConfig };
11
12
 
12
13
  /**
@@ -16,8 +17,8 @@ export function findProjectRoot(cwd = process.cwd()): string {
16
17
  let current = cwd;
17
18
 
18
19
  while (current !== path.parse(current).root) {
19
- const packagePath = path.join(current, 'package.json');
20
- const configPath = path.join(current, 'cursorflow.config.js');
20
+ const packagePath = safeJoin(current, 'package.json');
21
+ const configPath = safeJoin(current, 'cursorflow.config.js');
21
22
 
22
23
  if (fs.existsSync(packagePath) || fs.existsSync(configPath)) {
23
24
  return current;
@@ -36,16 +37,17 @@ export function loadConfig(projectRoot: string | null = null): CursorFlowConfig
36
37
  projectRoot = findProjectRoot();
37
38
  }
38
39
 
39
- const configPath = path.join(projectRoot, 'cursorflow.config.js');
40
+ const configPath = safeJoin(projectRoot, 'cursorflow.config.js');
40
41
 
41
42
  // Default configuration
42
43
  const defaults: CursorFlowConfig = {
43
44
  // Directories
44
45
  tasksDir: '_cursorflow/tasks',
45
46
  logsDir: '_cursorflow/logs',
47
+ pofDir: '_cursorflow/pof',
46
48
 
47
49
  // Git
48
- baseBranch: 'main',
50
+ // baseBranch is auto-detected from current branch at runtime
49
51
  branchPrefix: 'feature/',
50
52
 
51
53
  // Execution
@@ -114,14 +116,21 @@ export function loadConfig(projectRoot: string | null = null): CursorFlowConfig
114
116
  * Get absolute path for tasks directory
115
117
  */
116
118
  export function getTasksDir(config: CursorFlowConfig): string {
117
- return path.join(config.projectRoot, config.tasksDir);
119
+ return safeJoin(config.projectRoot, config.tasksDir);
118
120
  }
119
121
 
120
122
  /**
121
123
  * Get absolute path for logs directory
122
124
  */
123
125
  export function getLogsDir(config: CursorFlowConfig): string {
124
- return path.join(config.projectRoot, config.logsDir);
126
+ return safeJoin(config.projectRoot, config.logsDir);
127
+ }
128
+
129
+ /**
130
+ * Get absolute path for POF directory
131
+ */
132
+ export function getPofDir(config: CursorFlowConfig): string {
133
+ return safeJoin(config.projectRoot, config.pofDir);
125
134
  }
126
135
 
127
136
  /**
@@ -157,15 +166,16 @@ export function validateConfig(config: CursorFlowConfig): boolean {
157
166
  * Create default config file
158
167
  */
159
168
  export function createDefaultConfig(projectRoot: string, force = false): string {
160
- const configPath = path.join(projectRoot, 'cursorflow.config.js');
169
+ const configPath = safeJoin(projectRoot, 'cursorflow.config.js');
161
170
 
162
171
  const template = `module.exports = {
163
172
  // Directory configuration
164
173
  tasksDir: '_cursorflow/tasks',
165
174
  logsDir: '_cursorflow/logs',
175
+ pofDir: '_cursorflow/pof',
166
176
 
167
177
  // Git configuration
168
- baseBranch: 'main',
178
+ baseBranch: git.getCurrentBranch() || 'main',
169
179
  branchPrefix: 'feature/',
170
180
 
171
181
  // Execution configuration
@@ -2,7 +2,7 @@
2
2
  * Cursor Agent CLI wrapper and utilities
3
3
  */
4
4
 
5
- import { execSync, spawnSync } from 'child_process';
5
+ import { spawnSync } from 'child_process';
6
6
 
7
7
  /**
8
8
  * Check if cursor-agent CLI is installed