@litmers/cursorflow-orchestrator 0.1.37 → 0.1.40
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/README.md +13 -13
- package/commands/cursorflow-init.md +113 -32
- package/commands/cursorflow-prepare.md +146 -339
- package/commands/cursorflow-run.md +148 -131
- package/dist/cli/add.js +8 -4
- package/dist/cli/add.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/new.js +3 -5
- package/dist/cli/new.js.map +1 -1
- package/dist/cli/prepare.js +0 -1
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +24 -15
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +1 -6
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/setup-commands.d.ts +1 -0
- package/dist/cli/setup-commands.js +1 -0
- package/dist/cli/setup-commands.js.map +1 -1
- package/dist/core/orchestrator.js +13 -5
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +5 -1
- package/dist/core/runner/agent.js +31 -1
- package/dist/core/runner/agent.js.map +1 -1
- package/dist/core/runner/pipeline.d.ts +0 -1
- package/dist/core/runner/pipeline.js +136 -173
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/core/runner/prompt.d.ts +0 -1
- package/dist/core/runner/prompt.js +11 -16
- package/dist/core/runner/prompt.js.map +1 -1
- package/dist/core/runner/task.d.ts +1 -2
- package/dist/core/runner/task.js +31 -40
- package/dist/core/runner/task.js.map +1 -1
- package/dist/core/runner.js +15 -2
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +32 -4
- package/dist/core/stall-detection.js +151 -149
- package/dist/core/stall-detection.js.map +1 -1
- package/dist/services/logging/console.d.ts +7 -1
- package/dist/services/logging/console.js +13 -3
- package/dist/services/logging/console.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +1 -0
- package/dist/services/logging/formatter.js +6 -3
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/types/config.d.ts +3 -1
- package/dist/types/logging.d.ts +1 -1
- package/dist/types/task.d.ts +3 -8
- package/dist/utils/config.js +5 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/doctor.js +4 -4
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +1 -1
- package/dist/utils/enhanced-logger.js +3 -3
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +12 -1
- package/dist/utils/git.js +56 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.js +13 -13
- package/dist/utils/health.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +1 -1
- package/dist/utils/log-formatter.js +45 -8
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/add.ts +9 -4
- package/src/cli/index.ts +3 -0
- package/src/cli/new.ts +3 -5
- package/src/cli/prepare.ts +0 -1
- package/src/cli/resume.ts +28 -19
- package/src/cli/run.ts +1 -6
- package/src/cli/setup-commands.ts +1 -1
- package/src/core/orchestrator.ts +14 -5
- package/src/core/runner/agent.ts +36 -4
- package/src/core/runner/pipeline.ts +149 -182
- package/src/core/runner/prompt.ts +11 -18
- package/src/core/runner/task.ts +32 -41
- package/src/core/runner.ts +17 -2
- package/src/core/stall-detection.ts +263 -147
- package/src/services/logging/console.ts +13 -3
- package/src/services/logging/formatter.ts +6 -3
- package/src/types/config.ts +3 -1
- package/src/types/logging.ts +4 -2
- package/src/types/task.ts +3 -8
- package/src/utils/config.ts +6 -0
- package/src/utils/doctor.ts +5 -5
- package/src/utils/enhanced-logger.ts +3 -3
- package/src/utils/flow.ts +1 -0
- package/src/utils/git.ts +61 -1
- package/src/utils/health.ts +15 -15
- package/src/utils/log-formatter.ts +51 -8
- package/src/utils/logger.ts +2 -2
- package/commands/cursorflow-add.md +0 -159
- package/commands/cursorflow-clean.md +0 -84
- package/commands/cursorflow-doctor.md +0 -102
- package/commands/cursorflow-models.md +0 -51
- package/commands/cursorflow-monitor.md +0 -90
- package/commands/cursorflow-new.md +0 -87
- package/commands/cursorflow-resume.md +0 -205
- package/commands/cursorflow-signal.md +0 -52
- package/commands/cursorflow-stop.md +0 -55
- package/commands/cursorflow-triggers.md +0 -250
package/src/types/task.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface Task {
|
|
|
17
17
|
|
|
18
18
|
export interface RunnerConfig {
|
|
19
19
|
tasks: Task[];
|
|
20
|
+
dependsOn?: string[];
|
|
20
21
|
pipelineBranch?: string;
|
|
21
22
|
worktreeDir?: string;
|
|
22
23
|
branchPrefix?: string;
|
|
@@ -25,21 +26,15 @@ export interface RunnerConfig {
|
|
|
25
26
|
model?: string;
|
|
26
27
|
dependencyPolicy: DependencyPolicy;
|
|
27
28
|
/** Output format for cursor-agent (default: 'json') */
|
|
28
|
-
agentOutputFormat?: 'json' | 'plain';
|
|
29
|
+
agentOutputFormat?: 'json' | 'plain' | 'stream-json';
|
|
29
30
|
/** Task execution timeout in milliseconds. Default: 600000 (10 minutes) */
|
|
30
31
|
timeout?: number;
|
|
31
32
|
/**
|
|
32
33
|
* Enable intervention feature (stdin piping for message injection).
|
|
33
34
|
* Warning: May cause stdout buffering issues on some systems.
|
|
34
|
-
* Default:
|
|
35
|
+
* Default: true
|
|
35
36
|
*/
|
|
36
37
|
enableIntervention?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Disable Git operations (worktree, branch, push, commit).
|
|
39
|
-
* Useful for testing or environments without Git remote.
|
|
40
|
-
* Default: false
|
|
41
|
-
*/
|
|
42
|
-
noGit?: boolean;
|
|
43
38
|
/**
|
|
44
39
|
* Enable verbose Git logging.
|
|
45
40
|
* Default: false
|
package/src/utils/config.ts
CHANGED
|
@@ -60,6 +60,9 @@ export function loadConfig(projectRoot: string | null = null): CursorFlowConfig
|
|
|
60
60
|
allowDependencyChange: false,
|
|
61
61
|
lockfileReadOnly: true,
|
|
62
62
|
|
|
63
|
+
// Intervention
|
|
64
|
+
enableIntervention: true,
|
|
65
|
+
|
|
63
66
|
// Lane defaults
|
|
64
67
|
defaultLaneConfig: {
|
|
65
68
|
devPort: 3001,
|
|
@@ -193,6 +196,9 @@ export function createDefaultConfig(projectRoot: string, force = false): string
|
|
|
193
196
|
allowDependencyChange: false,
|
|
194
197
|
lockfileReadOnly: true,
|
|
195
198
|
|
|
199
|
+
// Intervention - allows sending messages to running agents
|
|
200
|
+
enableIntervention: true,
|
|
201
|
+
|
|
196
202
|
// Lane configuration
|
|
197
203
|
defaultLaneConfig: {
|
|
198
204
|
devPort: 3001, // 3000 + laneNumber
|
package/src/utils/doctor.ts
CHANGED
|
@@ -618,7 +618,7 @@ function validateBranchNames(
|
|
|
618
618
|
/**
|
|
619
619
|
* Status file to track when doctor was last run successfully.
|
|
620
620
|
*/
|
|
621
|
-
const DOCTOR_STATUS_FILE = '
|
|
621
|
+
const DOCTOR_STATUS_FILE = '_cursorflow/doctor-status.json';
|
|
622
622
|
|
|
623
623
|
export function saveDoctorStatus(repoRoot: string, report: DoctorReport): void {
|
|
624
624
|
const statusPath = safeJoin(repoRoot, DOCTOR_STATUS_FILE);
|
|
@@ -720,6 +720,10 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
|
|
|
720
720
|
}
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
+
const repoRoot = resolveRepoRoot(cwd) || undefined;
|
|
724
|
+
context.repoRoot = repoRoot;
|
|
725
|
+
const gitCwd = repoRoot || cwd;
|
|
726
|
+
|
|
723
727
|
// 1) Git repository checks
|
|
724
728
|
if (!isInsideGitWorktree(cwd)) {
|
|
725
729
|
addIssue(issues, {
|
|
@@ -736,10 +740,6 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
|
|
|
736
740
|
return { ok: false, issues, context };
|
|
737
741
|
}
|
|
738
742
|
|
|
739
|
-
const repoRoot = resolveRepoRoot(cwd) || undefined;
|
|
740
|
-
context.repoRoot = repoRoot;
|
|
741
|
-
const gitCwd = repoRoot || cwd;
|
|
742
|
-
|
|
743
743
|
if (!hasAtLeastOneCommit(gitCwd)) {
|
|
744
744
|
addIssue(issues, {
|
|
745
745
|
id: 'git.no_commits',
|
|
@@ -131,13 +131,13 @@ export class EnhancedLogManager {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* Get lane-task label like [
|
|
134
|
+
* Get lane-task label like [1-1-lanename10]
|
|
135
135
|
*/
|
|
136
136
|
private getLaneTaskLabel(): string {
|
|
137
137
|
const laneNum = (this.session.laneIndex ?? 0) + 1;
|
|
138
138
|
const taskNum = (this.session.taskIndex ?? 0) + 1;
|
|
139
139
|
const shortLaneName = this.session.laneName.substring(0, 10);
|
|
140
|
-
return
|
|
140
|
+
return `${laneNum}-${taskNum}-${shortLaneName}`.substring(0, 18).padEnd(18);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
@@ -267,7 +267,7 @@ export class EnhancedLogManager {
|
|
|
267
267
|
*/
|
|
268
268
|
public writeReadableMessage(msg: ParsedMessage): void {
|
|
269
269
|
// Use formatMessageForConsole for consistent formatting
|
|
270
|
-
// Use short lane-task label like [
|
|
270
|
+
// Use short lane-task label like [1-1-lanename10]
|
|
271
271
|
const formatted = formatMessageForConsole(msg, {
|
|
272
272
|
laneLabel: `[${this.getLaneTaskLabel()}]`,
|
|
273
273
|
includeTimestamp: false, // We'll add our own short timestamp
|
package/src/utils/flow.ts
CHANGED
package/src/utils/git.ts
CHANGED
|
@@ -156,6 +156,7 @@ export function runGit(args: string[], options: GitRunOptions = {}): string {
|
|
|
156
156
|
cwd: cwd || process.cwd(),
|
|
157
157
|
encoding: 'utf8',
|
|
158
158
|
stdio: stdioMode as any,
|
|
159
|
+
timeout: 30000, // 30 second timeout for all git operations
|
|
159
160
|
});
|
|
160
161
|
|
|
161
162
|
if (result.error) {
|
|
@@ -206,6 +207,7 @@ export function runGitResult(args: string[], options: GitRunOptions = {}): GitRe
|
|
|
206
207
|
cwd: cwd || process.cwd(),
|
|
207
208
|
encoding: 'utf8',
|
|
208
209
|
stdio: 'pipe',
|
|
210
|
+
timeout: 30000, // 30 second timeout
|
|
209
211
|
});
|
|
210
212
|
|
|
211
213
|
const gitResult = {
|
|
@@ -263,7 +265,7 @@ export function isGitRepo(cwd?: string): boolean {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
/**
|
|
266
|
-
* Check if worktree exists
|
|
268
|
+
* Check if worktree exists in Git's worktree list
|
|
267
269
|
*/
|
|
268
270
|
export function worktreeExists(worktreePath: string, cwd?: string): boolean {
|
|
269
271
|
const result = runGitResult(['worktree', 'list'], { cwd });
|
|
@@ -272,6 +274,64 @@ export function worktreeExists(worktreePath: string, cwd?: string): boolean {
|
|
|
272
274
|
return result.stdout.includes(worktreePath);
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Check if a directory is a valid Git worktree (not just a regular directory)
|
|
279
|
+
* This prevents accidental checkout in the main repository when the worktree
|
|
280
|
+
* directory exists but is not properly registered as a worktree.
|
|
281
|
+
*/
|
|
282
|
+
export function isValidWorktree(dirPath: string): boolean {
|
|
283
|
+
// 1. Directory must exist
|
|
284
|
+
if (!fs.existsSync(dirPath)) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 2. Check if Git recognizes this as a worktree by checking .git file
|
|
289
|
+
// In a worktree, .git is a file (not a directory) pointing to the main repo's .git/worktrees/
|
|
290
|
+
const dotGitPath = path.join(dirPath, '.git');
|
|
291
|
+
if (!fs.existsSync(dotGitPath)) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const stat = fs.statSync(dotGitPath);
|
|
297
|
+
|
|
298
|
+
// In worktrees, .git is a FILE containing "gitdir: /path/to/.git/worktrees/..."
|
|
299
|
+
// In the main repo, .git is a DIRECTORY
|
|
300
|
+
if (stat.isDirectory()) {
|
|
301
|
+
// This is the main repository, not a worktree
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Read the .git file to verify it points to a valid worktree
|
|
306
|
+
const content = fs.readFileSync(dotGitPath, 'utf8').trim();
|
|
307
|
+
if (!content.startsWith('gitdir:')) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Verify the gitdir path exists
|
|
312
|
+
const gitdirPath = content.replace('gitdir:', '').trim();
|
|
313
|
+
return fs.existsSync(gitdirPath);
|
|
314
|
+
} catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Remove a directory that was supposed to be a worktree but isn't valid
|
|
321
|
+
* Used to clean up orphaned/corrupted worktree directories before recreation
|
|
322
|
+
*/
|
|
323
|
+
export function cleanupInvalidWorktreeDir(dirPath: string): void {
|
|
324
|
+
if (!fs.existsSync(dirPath)) return;
|
|
325
|
+
|
|
326
|
+
// Only remove if it's NOT a valid worktree (safety check)
|
|
327
|
+
if (isValidWorktree(dirPath)) {
|
|
328
|
+
throw new Error(`Cannot cleanup: ${dirPath} is a valid worktree`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Remove the directory recursively
|
|
332
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
|
|
275
335
|
/**
|
|
276
336
|
* Create worktree
|
|
277
337
|
*/
|
package/src/utils/health.ts
CHANGED
|
@@ -455,14 +455,6 @@ export async function preflightCheck(options: {
|
|
|
455
455
|
blockers.push(`Git: ${gitHealth.message}`);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
// Check authentication
|
|
459
|
-
if (options.requireAuth !== false) {
|
|
460
|
-
const authHealth = await checkAuthHealth();
|
|
461
|
-
if (!authHealth.ok) {
|
|
462
|
-
blockers.push(`Authentication: ${authHealth.message}`);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
458
|
// Check Git remote (warning only unless required)
|
|
467
459
|
const remoteHealth = await checkGitRemoteHealth(options.cwd);
|
|
468
460
|
if (!remoteHealth.ok) {
|
|
@@ -473,6 +465,21 @@ export async function preflightCheck(options: {
|
|
|
473
465
|
}
|
|
474
466
|
}
|
|
475
467
|
|
|
468
|
+
// Check worktrees
|
|
469
|
+
const worktreeHealth = await checkWorktrees(options.cwd);
|
|
470
|
+
if (!worktreeHealth.ok) {
|
|
471
|
+
warnings.push(`Worktrees: ${worktreeHealth.message}`);
|
|
472
|
+
recommendations.push('Run `cursorflow clean worktrees` to clean up orphaned worktrees');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check authentication
|
|
476
|
+
if (options.requireAuth !== false) {
|
|
477
|
+
const authHealth = await checkAuthHealth();
|
|
478
|
+
if (!authHealth.ok) {
|
|
479
|
+
blockers.push(`Authentication: ${authHealth.message}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
476
483
|
// Check disk space
|
|
477
484
|
const diskHealth = await checkDiskSpace();
|
|
478
485
|
if (!diskHealth.ok) {
|
|
@@ -486,13 +493,6 @@ export async function preflightCheck(options: {
|
|
|
486
493
|
recommendations.push('Run `cursorflow clean locks` to remove stale locks');
|
|
487
494
|
}
|
|
488
495
|
|
|
489
|
-
// Check worktrees
|
|
490
|
-
const worktreeHealth = await checkWorktrees(options.cwd);
|
|
491
|
-
if (!worktreeHealth.ok) {
|
|
492
|
-
warnings.push(`Worktrees: ${worktreeHealth.message}`);
|
|
493
|
-
recommendations.push('Run `cursorflow clean worktrees` to clean up orphaned worktrees');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
496
|
// Check system resources
|
|
497
497
|
const resourceHealth = await checkSystemResources();
|
|
498
498
|
if (!resourceHealth.ok) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Box format only for: user, assistant, system, result
|
|
8
8
|
* - Compact format for: tool, tool_result, thinking (gray/dim)
|
|
9
9
|
* - Tool names simplified: ShellToolCall → Shell
|
|
10
|
-
* - Lane labels
|
|
10
|
+
* - Lane labels fixed 20 chars: [1-1-backend ]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { COLORS } from './log-constants';
|
|
@@ -44,12 +44,17 @@ export function formatMessageForConsole(
|
|
|
44
44
|
const ts = includeTimestamp ? new Date(msg.timestamp).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
45
45
|
const tsPrefix = ts ? `${COLORS.gray}[${ts}]${COLORS.reset} ` : '';
|
|
46
46
|
|
|
47
|
-
// Handle context (e.g. from logger.info) - max
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// Handle context (e.g. from logger.info) - max 18 chars inside brackets
|
|
48
|
+
// Format: [1-1-lanename1234] padded to fixed width 20 (including brackets)
|
|
49
|
+
let effectiveLaneLabel = laneLabel || (context ? `[${context.substring(0, 18).padEnd(18)}]` : '');
|
|
50
|
+
|
|
51
|
+
// Smart truncation: ensure it always ends with ]
|
|
52
|
+
if (effectiveLaneLabel.length > 20) {
|
|
53
|
+
effectiveLaneLabel = effectiveLaneLabel.substring(0, 19) + ']';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fixed width 20 chars for consistent alignment
|
|
57
|
+
const labelPrefix = effectiveLaneLabel ? `${COLORS.magenta}${effectiveLaneLabel.padEnd(20)}${COLORS.reset} ` : '';
|
|
53
58
|
|
|
54
59
|
let typePrefix = '';
|
|
55
60
|
let content = msg.content;
|
|
@@ -155,6 +160,44 @@ export function formatMessageForConsole(
|
|
|
155
160
|
|
|
156
161
|
if (!typePrefix) return `${tsPrefix}${labelPrefix}${content}`;
|
|
157
162
|
|
|
163
|
+
// Avoid double prefixes (e.g. INFO INFO)
|
|
164
|
+
const plainTypePrefix = stripAnsi(typePrefix).replace(/[^\x00-\x7F]/g, '').trim(); // "INFO", "DONE", etc.
|
|
165
|
+
const plainContent = stripAnsi(content);
|
|
166
|
+
if (plainContent.includes(` ${plainTypePrefix} `) || plainContent.startsWith(`${plainTypePrefix} `)) {
|
|
167
|
+
// If content already has the prefix, try to strip it from content or just use content as is
|
|
168
|
+
// For simplicity, if it's already there, we can just skip adding our typePrefix
|
|
169
|
+
// but we still want the colors. This is tricky.
|
|
170
|
+
// Usually it's better to just return the content if it looks already formatted.
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// A better way: if content starts with an emoji that matches our type, skip typePrefix
|
|
174
|
+
const emojiMap: Record<string, string> = {
|
|
175
|
+
'info': 'ℹ️',
|
|
176
|
+
'success': '✅',
|
|
177
|
+
'result': '✅',
|
|
178
|
+
'warn': '⚠️',
|
|
179
|
+
'error': '❌',
|
|
180
|
+
'tool': '🔧',
|
|
181
|
+
'thinking': '🤔',
|
|
182
|
+
'user': '🧑',
|
|
183
|
+
'assistant': '🤖'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const targetEmoji = emojiMap[msg.type];
|
|
187
|
+
if (targetEmoji && plainContent.trim().startsWith(targetEmoji)) {
|
|
188
|
+
return `${tsPrefix}${labelPrefix}${content}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle separator lines - if content is just a repeat of ━ or ─, extend it
|
|
192
|
+
const separatorMatch = content.match(/^([━─=]+)$/);
|
|
193
|
+
if (separatorMatch) {
|
|
194
|
+
const char = separatorMatch[1]![0]!;
|
|
195
|
+
// Use a fixed width for now (80 is a good standard)
|
|
196
|
+
// In a real terminal we could use process.stdout.columns
|
|
197
|
+
const targetWidth = 80;
|
|
198
|
+
content = char.repeat(targetWidth);
|
|
199
|
+
}
|
|
200
|
+
|
|
158
201
|
// Compact format (single line)
|
|
159
202
|
if (!useBox) {
|
|
160
203
|
return `${tsPrefix}${labelPrefix}${typePrefix.padEnd(12)} ${content}`;
|
|
@@ -169,7 +212,7 @@ export function formatMessageForConsole(
|
|
|
169
212
|
const emojiCount = (strippedPrefix.match(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2300}-\u{23FF}]|[\u{2B50}-\u{2B55}]|[\u{231A}-\u{231B}]|[\u{23E9}-\u{23F3}]|[\u{23F8}-\u{23FA}]|✅|❌|⚙️|ℹ️|⚠️|🔧|📄|🤔|🧑|🤖/gu) || []).length;
|
|
170
213
|
const visualWidth = strippedPrefix.length + emojiCount; // emoji adds 1 extra width
|
|
171
214
|
|
|
172
|
-
const boxWidth =
|
|
215
|
+
const boxWidth = 80;
|
|
173
216
|
const header = `${typePrefix}┌${'─'.repeat(boxWidth)}`;
|
|
174
217
|
let result = `${fullPrefix}${header}\n`;
|
|
175
218
|
|
package/src/utils/logger.ts
CHANGED
|
@@ -144,9 +144,9 @@ export function withContext(context: string) {
|
|
|
144
144
|
*/
|
|
145
145
|
export function section(message: string): void {
|
|
146
146
|
console.log('');
|
|
147
|
-
console.log(`${COLORS.cyan}
|
|
147
|
+
console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
148
148
|
console.log(`${COLORS.cyan} ${message}${COLORS.reset}`);
|
|
149
|
-
console.log(`${COLORS.cyan}
|
|
149
|
+
console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
150
150
|
console.log('');
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# cursorflow add
|
|
2
|
-
|
|
3
|
-
Lane에 Task를 추가합니다.
|
|
4
|
-
|
|
5
|
-
## 사용법
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
cursorflow add <FlowName> <LaneName> --task "name=...|model=...|prompt=..." [--after ...]
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## 설명
|
|
12
|
-
|
|
13
|
-
지정된 Flow의 Lane에 Task를 추가합니다.
|
|
14
|
-
`--task` 옵션은 여러 번 사용하여 여러 태스크를 순차적으로 추가할 수 있습니다.
|
|
15
|
-
|
|
16
|
-
## --task 형식
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
"name=<이름>|model=<모델>|prompt=<프롬프트>"
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### 필수 필드
|
|
23
|
-
|
|
24
|
-
| 필드 | 설명 | 예시 |
|
|
25
|
-
|------|------|------|
|
|
26
|
-
| `name` | 태스크 이름 (영문, 숫자, -, _) | `name=implement` |
|
|
27
|
-
| `prompt` | 태스크 프롬프트/지시사항 | `prompt=API 구현` |
|
|
28
|
-
|
|
29
|
-
### 선택 필드
|
|
30
|
-
|
|
31
|
-
| 필드 | 설명 | 예시 |
|
|
32
|
-
|------|------|------|
|
|
33
|
-
| `model` | AI 모델 (생략 시 기본 모델 사용) | `model=<your-model>` |
|
|
34
|
-
|
|
35
|
-
기본 모델 설정: `cursorflow config defaultModel <model-name>`
|
|
36
|
-
|
|
37
|
-
## --after 형식 (의존성 설정)
|
|
38
|
-
|
|
39
|
-
첫 번째 태스크가 시작되기 전에 완료되어야 할 태스크를 지정합니다.
|
|
40
|
-
|
|
41
|
-
| 형식 | 설명 |
|
|
42
|
-
|------|------|
|
|
43
|
-
| `"lane"` | 해당 레인의 **마지막 태스크** 완료 후 시작 |
|
|
44
|
-
| `"lane:task"` | 특정 태스크 완료 후 시작 |
|
|
45
|
-
| `"a:t1, b:t2"` | **여러 태스크가 모두 완료**된 후 시작 |
|
|
46
|
-
|
|
47
|
-
## 예시
|
|
48
|
-
|
|
49
|
-
### 기본 사용: 단일 태스크 추가 (기본 모델 사용)
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
cursorflow add SearchFeature api \
|
|
53
|
-
--task "name=implement|prompt=검색 API 구현"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 여러 태스크 추가
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
cursorflow add SearchFeature api \
|
|
60
|
-
--task "name=plan|prompt=API 설계" \
|
|
61
|
-
--task "name=implement|prompt=검색 API 구현" \
|
|
62
|
-
--task "name=test|prompt=테스트 코드 작성"
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### 의존성 설정: 특정 태스크 완료 후 시작
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
# api 레인의 implement 태스크 완료 후 시작
|
|
69
|
-
cursorflow add SearchFeature web \
|
|
70
|
-
--task "name=ui|prompt=검색 UI 구현" \
|
|
71
|
-
--after "api:implement"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 의존성 설정: 레인 전체 완료 후 시작
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# api 레인의 마지막 태스크 완료 후 시작
|
|
78
|
-
cursorflow add SearchFeature web \
|
|
79
|
-
--task "name=ui|prompt=검색 UI 구현" \
|
|
80
|
-
--after "api"
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 다중 의존성: 여러 태스크 완료 후 시작
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# web과 mobile 모두 완료된 후 시작
|
|
87
|
-
cursorflow add SearchFeature e2e \
|
|
88
|
-
--task "name=verify|prompt=E2E 테스트" \
|
|
89
|
-
--after "web:ui, mobile:app"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## 출력 예시
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
✅ 3개 태스크 추가 완료
|
|
96
|
-
|
|
97
|
-
📄 01-api.json
|
|
98
|
-
|
|
99
|
-
├── plan (<default-model>)
|
|
100
|
-
├── implement (<default-model>)
|
|
101
|
-
└── test (<default-model>)
|
|
102
|
-
|
|
103
|
-
전체 태스크 목록:
|
|
104
|
-
1. plan (new)
|
|
105
|
-
2. implement (new)
|
|
106
|
-
3. test (new)
|
|
107
|
-
|
|
108
|
-
다음 단계:
|
|
109
|
-
cursorflow run SearchFeature # Flow 실행
|
|
110
|
-
cursorflow doctor SearchFeature # 설정 검증
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## 생성되는 Lane 파일 구조
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"laneName": "api",
|
|
118
|
-
"tasks": [
|
|
119
|
-
{
|
|
120
|
-
"name": "plan",
|
|
121
|
-
"model": "<your-model>",
|
|
122
|
-
"prompt": "API 설계"
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"name": "implement",
|
|
126
|
-
"model": "<your-model>",
|
|
127
|
-
"prompt": "검색 API 구현"
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
"name": "test",
|
|
131
|
-
"model": "<your-model>",
|
|
132
|
-
"prompt": "테스트 코드 작성"
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 의존성이 있는 경우
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{
|
|
142
|
-
"laneName": "web",
|
|
143
|
-
"tasks": [
|
|
144
|
-
{
|
|
145
|
-
"name": "ui",
|
|
146
|
-
"model": "<your-model>",
|
|
147
|
-
"prompt": "검색 UI 구현",
|
|
148
|
-
"dependsOn": ["01-api:implement"]
|
|
149
|
-
}
|
|
150
|
-
]
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## 관련 명령어
|
|
155
|
-
|
|
156
|
-
- [cursorflow new](cursorflow-new.md) - Flow와 Lane 생성
|
|
157
|
-
- [cursorflow run](cursorflow-run.md) - Flow 실행
|
|
158
|
-
- [cursorflow doctor](cursorflow-doctor.md) - 설정 검증
|
|
159
|
-
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# CursorFlow Clean
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
Clean up temporary resources created by CursorFlow, including Git worktrees, feature branches, log files, and task definitions.
|
|
5
|
-
|
|
6
|
-
## Usage
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
cursorflow clean <type> [options]
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Clean Types
|
|
13
|
-
|
|
14
|
-
| Type | Description |
|
|
15
|
-
|------|------|
|
|
16
|
-
| `branches` | Remove local feature branches created by CursorFlow |
|
|
17
|
-
| `worktrees` | Remove temporary Git worktrees |
|
|
18
|
-
| `logs` | Clear all run and terminal logs |
|
|
19
|
-
| `tasks` | Remove task definition directories (keeps `example/`) |
|
|
20
|
-
| `all` | Clean everything (branches, worktrees, logs, and tasks) |
|
|
21
|
-
|
|
22
|
-
## Options
|
|
23
|
-
|
|
24
|
-
| Option | Description |
|
|
25
|
-
|------|------|
|
|
26
|
-
| `--run <id>` | Clean resources linked to a specific run |
|
|
27
|
-
| `--older-than <time>` | Clean resources older than a specific time (e.g., `24h`, `7d`) |
|
|
28
|
-
| `--orphaned` | Clean orphaned resources (worktrees without runs, etc.) |
|
|
29
|
-
| `--dry-run` | Show what would be removed without actually deleting anything |
|
|
30
|
-
| `--force` | Force removal (ignore uncommitted changes in worktrees) |
|
|
31
|
-
| `--include-latest` | Also remove the most recent item (by default, latest is kept) |
|
|
32
|
-
| `--help`, `-h` | Show help |
|
|
33
|
-
|
|
34
|
-
## Examples
|
|
35
|
-
|
|
36
|
-
### Review before deleting (latest is kept by default)
|
|
37
|
-
```bash
|
|
38
|
-
cursorflow clean all --dry-run
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Clean only worktrees (keeps the latest worktree)
|
|
42
|
-
```bash
|
|
43
|
-
cursorflow clean worktrees
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Force clean everything including the latest
|
|
47
|
-
```bash
|
|
48
|
-
cursorflow clean all --force --include-latest
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Remove all worktrees including the latest
|
|
52
|
-
```bash
|
|
53
|
-
cursorflow clean worktrees --include-latest
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Clean resources for a specific run
|
|
57
|
-
```bash
|
|
58
|
-
cursorflow clean all --run run-1734873132
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Clean logs and tasks older than 7 days
|
|
62
|
-
```bash
|
|
63
|
-
cursorflow clean logs --older-than 7d
|
|
64
|
-
cursorflow clean tasks --older-than 7d
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Clean orphaned worktrees and branches
|
|
68
|
-
```bash
|
|
69
|
-
cursorflow clean all --orphaned
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Notes
|
|
73
|
-
|
|
74
|
-
1. **Safety**: It is highly recommended to run with `--dry-run` first to see exactly what will be deleted.
|
|
75
|
-
2. **Worktrees**: The command identifies CursorFlow worktrees by their location (usually in `_cursorflow/worktrees/`) or their prefix.
|
|
76
|
-
3. **Branches**: Only branches starting with the configured `branchPrefix` (default: `cursorflow/`) are targeted.
|
|
77
|
-
4. **Irreversible**: Once logs are deleted, they cannot be recovered.
|
|
78
|
-
5. **Default Behavior**: By default, the most recent item is preserved. The "most recent" is determined by:
|
|
79
|
-
- **Worktrees**: Directory modification time
|
|
80
|
-
- **Branches**: Latest commit timestamp
|
|
81
|
-
- **Logs**: File/directory modification time
|
|
82
|
-
- **Tasks**: Directory modification time
|
|
83
|
-
|
|
84
|
-
Use `--include-latest` to remove everything including the most recent item.
|