@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.
- package/CHANGELOG.md +25 -0
- package/README.md +25 -7
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +178 -6
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +12 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +8 -7
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +126 -77
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1021 -202
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +39 -21
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +268 -163
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +11 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +8 -8
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +16 -2
- package/dist/core/orchestrator.js +439 -105
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +374 -164
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +18 -8
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +17 -11
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +108 -20
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +484 -11
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +25 -0
- package/dist/utils/log-formatter.js +237 -0
- package/dist/utils/log-formatter.js.map +1 -0
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path.d.ts +19 -0
- package/dist/utils/path.js +77 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +62 -3
- package/dist/utils/state.js +317 -11
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/template.d.ts +14 -0
- package/dist/utils/template.js +122 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/types.d.ts +2 -271
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +187 -6
- package/src/cli/index.ts +12 -1
- package/src/cli/init.ts +8 -7
- package/src/cli/logs.ts +124 -77
- package/src/cli/monitor.ts +1815 -898
- package/src/cli/prepare.ts +41 -21
- package/src/cli/resume.ts +753 -626
- package/src/cli/run.ts +12 -5
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +8 -7
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1131 -704
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +444 -180
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +18 -8
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +18 -11
- package/src/utils/enhanced-logger.ts +122 -60
- package/src/utils/git.ts +517 -11
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +245 -0
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/path.ts +45 -0
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +385 -11
- package/src/utils/task-service.ts +370 -0
- package/src/utils/template.ts +92 -0
- package/src/utils/types.ts +2 -314
- 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
|
+
|
package/src/utils/config.ts
CHANGED
|
@@ -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 =
|
|
20
|
-
const configPath =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|