@litmers/cursorflow-orchestrator 0.1.3 → 0.1.6

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 (90) hide show
  1. package/CHANGELOG.md +17 -7
  2. package/README.md +33 -2
  3. package/commands/cursorflow-doctor.md +24 -0
  4. package/commands/cursorflow-signal.md +19 -0
  5. package/dist/cli/clean.d.ts +5 -0
  6. package/dist/cli/clean.js +57 -0
  7. package/dist/cli/clean.js.map +1 -0
  8. package/dist/cli/doctor.d.ts +15 -0
  9. package/dist/cli/doctor.js +139 -0
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/index.d.ts +6 -0
  12. package/dist/cli/index.js +125 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/init.d.ts +7 -0
  15. package/dist/cli/init.js +302 -0
  16. package/dist/cli/init.js.map +1 -0
  17. package/dist/cli/monitor.d.ts +8 -0
  18. package/dist/cli/monitor.js +210 -0
  19. package/dist/cli/monitor.js.map +1 -0
  20. package/dist/cli/resume.d.ts +5 -0
  21. package/dist/cli/resume.js +128 -0
  22. package/dist/cli/resume.js.map +1 -0
  23. package/dist/cli/run.d.ts +5 -0
  24. package/dist/cli/run.js +128 -0
  25. package/dist/cli/run.js.map +1 -0
  26. package/dist/cli/setup-commands.d.ts +23 -0
  27. package/dist/cli/setup-commands.js +234 -0
  28. package/dist/cli/setup-commands.js.map +1 -0
  29. package/dist/cli/signal.d.ts +7 -0
  30. package/dist/cli/signal.js +99 -0
  31. package/dist/cli/signal.js.map +1 -0
  32. package/dist/core/orchestrator.d.ts +47 -0
  33. package/dist/core/orchestrator.js +192 -0
  34. package/dist/core/orchestrator.js.map +1 -0
  35. package/dist/core/reviewer.d.ts +60 -0
  36. package/dist/core/reviewer.js +239 -0
  37. package/dist/core/reviewer.js.map +1 -0
  38. package/dist/core/runner.d.ts +51 -0
  39. package/dist/core/runner.js +499 -0
  40. package/dist/core/runner.js.map +1 -0
  41. package/dist/utils/config.d.ts +31 -0
  42. package/dist/utils/config.js +198 -0
  43. package/dist/utils/config.js.map +1 -0
  44. package/dist/utils/cursor-agent.d.ts +61 -0
  45. package/dist/utils/cursor-agent.js +263 -0
  46. package/dist/utils/cursor-agent.js.map +1 -0
  47. package/dist/utils/doctor.d.ts +63 -0
  48. package/dist/utils/doctor.js +280 -0
  49. package/dist/utils/doctor.js.map +1 -0
  50. package/dist/utils/git.d.ts +131 -0
  51. package/dist/utils/git.js +272 -0
  52. package/dist/utils/git.js.map +1 -0
  53. package/dist/utils/logger.d.ts +68 -0
  54. package/dist/utils/logger.js +158 -0
  55. package/dist/utils/logger.js.map +1 -0
  56. package/dist/utils/state.d.ts +65 -0
  57. package/dist/utils/state.js +216 -0
  58. package/dist/utils/state.js.map +1 -0
  59. package/dist/utils/types.d.ts +118 -0
  60. package/dist/utils/types.js +6 -0
  61. package/dist/utils/types.js.map +1 -0
  62. package/examples/README.md +155 -0
  63. package/examples/demo-project/README.md +262 -0
  64. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +18 -0
  65. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +18 -0
  66. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +109 -0
  67. package/package.json +71 -61
  68. package/scripts/ai-security-check.js +11 -4
  69. package/scripts/local-security-gate.sh +76 -0
  70. package/src/cli/{clean.js → clean.ts} +11 -5
  71. package/src/cli/doctor.ts +127 -0
  72. package/src/cli/{index.js → index.ts} +27 -16
  73. package/src/cli/{init.js → init.ts} +26 -18
  74. package/src/cli/{monitor.js → monitor.ts} +57 -44
  75. package/src/cli/resume.ts +119 -0
  76. package/src/cli/run.ts +109 -0
  77. package/src/cli/{setup-commands.js → setup-commands.ts} +38 -18
  78. package/src/cli/signal.ts +89 -0
  79. package/src/core/{orchestrator.js → orchestrator.ts} +44 -26
  80. package/src/core/{reviewer.js → reviewer.ts} +36 -29
  81. package/src/core/{runner.js → runner.ts} +125 -76
  82. package/src/utils/{config.js → config.ts} +17 -25
  83. package/src/utils/{cursor-agent.js → cursor-agent.ts} +38 -47
  84. package/src/utils/doctor.ts +312 -0
  85. package/src/utils/{git.js → git.ts} +70 -56
  86. package/src/utils/{logger.js → logger.ts} +170 -178
  87. package/src/utils/{state.js → state.ts} +30 -38
  88. package/src/utils/types.ts +134 -0
  89. package/src/cli/resume.js +0 -31
  90. package/src/cli/run.js +0 -51
@@ -1,31 +1,43 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * Core Runner - Execute tasks sequentially in a lane
4
3
  *
5
4
  * Adapted from sequential-agent-runner.js
6
5
  */
7
6
 
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { spawn } = require('child_process');
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { execSync, spawnSync } from 'child_process';
11
10
 
12
- const git = require('../utils/git');
13
- const logger = require('../utils/logger');
14
- const { ensureCursorAgent, checkCursorApiKey } = require('../utils/cursor-agent');
15
- const { saveState, loadState, appendLog, createConversationEntry, createGitLogEntry } = require('../utils/state');
11
+ import * as git from '../utils/git';
12
+ import * as logger from '../utils/logger';
13
+ import { ensureCursorAgent, checkCursorAuth, printAuthHelp } from '../utils/cursor-agent';
14
+ import { saveState, appendLog, createConversationEntry } from '../utils/state';
15
+ import {
16
+ RunnerConfig,
17
+ Task,
18
+ TaskExecutionResult,
19
+ AgentSendResult,
20
+ DependencyPolicy,
21
+ DependencyRequestPlan,
22
+ LaneState
23
+ } from '../utils/types';
16
24
 
17
25
  /**
18
26
  * Execute cursor-agent command with timeout and better error handling
19
27
  */
20
- function cursorAgentCreateChat() {
21
- const { execSync } = require('child_process');
22
-
28
+ export function cursorAgentCreateChat(): string {
23
29
  try {
24
- const out = execSync('cursor-agent create-chat', {
30
+ const res = spawnSync('cursor-agent', ['create-chat'], {
25
31
  encoding: 'utf8',
26
32
  stdio: 'pipe',
27
33
  timeout: 30000, // 30 second timeout
28
34
  });
35
+
36
+ if (res.error || res.status !== 0) {
37
+ throw res.error || new Error(res.stderr || 'Failed to create chat');
38
+ }
39
+
40
+ const out = res.stdout;
29
41
  const lines = out.split('\n').filter(Boolean);
30
42
  const chatId = lines[lines.length - 1] || null;
31
43
 
@@ -35,7 +47,7 @@ function cursorAgentCreateChat() {
35
47
 
36
48
  logger.info(`Created chat session: ${chatId}`);
37
49
  return chatId;
38
- } catch (error) {
50
+ } catch (error: any) {
39
51
  // Check for common errors
40
52
  if (error.message.includes('ENOENT')) {
41
53
  throw new Error('cursor-agent CLI not found. Install with: npm install -g @cursor/agent');
@@ -77,14 +89,14 @@ function cursorAgentCreateChat() {
77
89
  }
78
90
  }
79
91
 
80
- function parseJsonFromStdout(stdout) {
92
+ function parseJsonFromStdout(stdout: string): any {
81
93
  const text = String(stdout || '').trim();
82
94
  if (!text) return null;
83
95
  const lines = text.split('\n').filter(Boolean);
84
96
 
85
97
  for (let i = lines.length - 1; i >= 0; i--) {
86
- const line = lines[i].trim();
87
- if (line.startsWith('{') && line.endsWith('}')) {
98
+ const line = lines[i]?.trim();
99
+ if (line?.startsWith('{') && line?.endsWith('}')) {
88
100
  try {
89
101
  return JSON.parse(line);
90
102
  } catch {
@@ -95,9 +107,12 @@ function parseJsonFromStdout(stdout) {
95
107
  return null;
96
108
  }
97
109
 
98
- function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
99
- const { spawnSync } = require('child_process');
100
-
110
+ export function cursorAgentSend({ workspaceDir, chatId, prompt, model }: {
111
+ workspaceDir: string;
112
+ chatId: string;
113
+ prompt: string;
114
+ model?: string;
115
+ }): AgentSendResult {
101
116
  const args = [
102
117
  '--print',
103
118
  '--output-format', 'json',
@@ -117,7 +132,7 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
117
132
 
118
133
  // Check for timeout
119
134
  if (res.error) {
120
- if (res.error.code === 'ETIMEDOUT') {
135
+ if ((res.error as any).code === 'ETIMEDOUT') {
121
136
  return {
122
137
  ok: false,
123
138
  exitCode: -1,
@@ -166,14 +181,14 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
166
181
 
167
182
  return {
168
183
  ok: false,
169
- exitCode: res.status,
184
+ exitCode: res.status ?? -1,
170
185
  error: errorMsg,
171
186
  };
172
187
  }
173
188
 
174
189
  return {
175
190
  ok: !json.is_error,
176
- exitCode: res.status,
191
+ exitCode: res.status ?? 0,
177
192
  sessionId: json.session_id || chatId,
178
193
  resultText: json.result || '',
179
194
  };
@@ -182,12 +197,12 @@ function cursorAgentSend({ workspaceDir, chatId, prompt, model }) {
182
197
  /**
183
198
  * Extract dependency change request from agent response
184
199
  */
185
- function extractDependencyRequest(text) {
200
+ export function extractDependencyRequest(text: string): { required: boolean; plan?: DependencyRequestPlan; raw: string } {
186
201
  const t = String(text || '');
187
202
  const marker = 'DEPENDENCY_CHANGE_REQUIRED';
188
203
 
189
204
  if (!t.includes(marker)) {
190
- return { required: false };
205
+ return { required: false, raw: t };
191
206
  }
192
207
 
193
208
  const after = t.split(marker).slice(1).join(marker);
@@ -197,7 +212,7 @@ function extractDependencyRequest(text) {
197
212
  try {
198
213
  return {
199
214
  required: true,
200
- plan: JSON.parse(match[0]),
215
+ plan: JSON.parse(match[0]!) as DependencyRequestPlan,
201
216
  raw: t,
202
217
  };
203
218
  } catch {
@@ -211,7 +226,7 @@ function extractDependencyRequest(text) {
211
226
  /**
212
227
  * Wrap prompt with dependency policy
213
228
  */
214
- function wrapPromptForDependencyPolicy(prompt, policy) {
229
+ export function wrapPromptForDependencyPolicy(prompt: string, policy: DependencyPolicy): string {
215
230
  if (policy.allowDependencyChange && !policy.lockfileReadOnly) {
216
231
  return prompt;
217
232
  }
@@ -243,8 +258,8 @@ ${prompt}`;
243
258
  /**
244
259
  * Apply file permissions based on dependency policy
245
260
  */
246
- function applyDependencyFilePermissions(worktreeDir, policy) {
247
- const targets = [];
261
+ export function applyDependencyFilePermissions(worktreeDir: string, policy: DependencyPolicy): void {
262
+ const targets: string[] = [];
248
263
 
249
264
  if (!policy.allowDependencyChange) {
250
265
  targets.push('package.json');
@@ -271,16 +286,24 @@ function applyDependencyFilePermissions(worktreeDir, policy) {
271
286
  /**
272
287
  * Run a single task
273
288
  */
274
- async function runTask({
289
+ export async function runTask({
275
290
  task,
276
291
  config,
277
292
  index,
278
293
  worktreeDir,
279
- pipelineBranch,
280
294
  taskBranch,
281
295
  chatId,
282
296
  runDir,
283
- }) {
297
+ }: {
298
+ task: Task;
299
+ config: RunnerConfig;
300
+ index: number;
301
+ worktreeDir: string;
302
+ pipelineBranch: string;
303
+ taskBranch: string;
304
+ chatId: string;
305
+ runDir: string;
306
+ }): Promise<TaskExecutionResult> {
284
307
  const model = task.model || config.model || 'sonnet-4.5';
285
308
  const convoPath = path.join(runDir, 'conversation.jsonl');
286
309
 
@@ -310,7 +333,7 @@ async function runTask({
310
333
  model,
311
334
  });
312
335
 
313
- appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error, {
336
+ appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error || 'No response', {
314
337
  task: task.name,
315
338
  model,
316
339
  }));
@@ -325,7 +348,7 @@ async function runTask({
325
348
  }
326
349
 
327
350
  // Check for dependency request
328
- const depReq = extractDependencyRequest(r1.resultText);
351
+ const depReq = extractDependencyRequest(r1.resultText || '');
329
352
  if (depReq.required && !config.dependencyPolicy.allowDependencyChange) {
330
353
  return {
331
354
  taskName: task.name,
@@ -348,8 +371,8 @@ async function runTask({
348
371
  /**
349
372
  * Run all tasks in sequence
350
373
  */
351
- async function runTasks(config, runDir) {
352
- const { checkCursorAuth, printAuthHelp } = require('../utils/cursor-agent');
374
+ export async function runTasks(tasksFile: string, config: RunnerConfig, runDir: string, options: { startIndex?: number } = {}): Promise<TaskExecutionResult[]> {
375
+ const startIndex = options.startIndex || 0;
353
376
 
354
377
  // Ensure cursor-agent is installed
355
378
  ensureCursorAgent();
@@ -379,41 +402,68 @@ async function runTasks(config, runDir) {
379
402
  logger.success('✓ Cursor authentication OK');
380
403
 
381
404
  const repoRoot = git.getRepoRoot();
382
- const pipelineBranch = config.pipelineBranch || `${config.branchPrefix}${Date.now().toString(36)}`;
383
- const worktreeDir = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees', pipelineBranch);
384
405
 
385
- logger.section('🚀 Starting Pipeline');
406
+ // Load existing state if resuming
407
+ const statePath = path.join(runDir, 'state.json');
408
+ let state: LaneState | null = null;
409
+
410
+ if (startIndex > 0 && fs.existsSync(statePath)) {
411
+ state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
412
+ }
413
+
414
+ const pipelineBranch = state?.pipelineBranch || config.pipelineBranch || `${config.branchPrefix || 'cursorflow/'}${Date.now().toString(36)}`;
415
+ const worktreeDir = state?.worktreeDir || path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees', pipelineBranch);
416
+
417
+ if (startIndex === 0) {
418
+ logger.section('🚀 Starting Pipeline');
419
+ } else {
420
+ logger.section(`🔁 Resuming Pipeline from task ${startIndex + 1}`);
421
+ }
422
+
386
423
  logger.info(`Pipeline Branch: ${pipelineBranch}`);
387
424
  logger.info(`Worktree: ${worktreeDir}`);
388
425
  logger.info(`Tasks: ${config.tasks.length}`);
389
426
 
390
- // Create worktree
391
- git.createWorktree(worktreeDir, pipelineBranch, {
392
- baseBranch: config.baseBranch || 'main',
393
- cwd: repoRoot,
394
- });
427
+ // Create worktree only if starting fresh
428
+ if (startIndex === 0 || !fs.existsSync(worktreeDir)) {
429
+ git.createWorktree(worktreeDir, pipelineBranch, {
430
+ baseBranch: config.baseBranch || 'main',
431
+ cwd: repoRoot,
432
+ });
433
+ }
395
434
 
396
435
  // Create chat
397
436
  logger.info('Creating chat session...');
398
437
  const chatId = cursorAgentCreateChat();
399
438
 
400
- // Save initial state
401
- const state = {
402
- status: 'running',
403
- pipelineBranch,
404
- worktreeDir,
405
- chatId,
406
- totalTasks: config.tasks.length,
407
- currentTaskIndex: 0,
408
- };
439
+ // Initialize state if not loaded
440
+ if (!state) {
441
+ state = {
442
+ status: 'running',
443
+ pipelineBranch,
444
+ worktreeDir,
445
+ totalTasks: config.tasks.length,
446
+ currentTaskIndex: 0,
447
+ label: pipelineBranch,
448
+ startTime: Date.now(),
449
+ endTime: null,
450
+ error: null,
451
+ dependencyRequest: null,
452
+ tasksFile, // Store tasks file for resume
453
+ };
454
+ } else {
455
+ state.status = 'running';
456
+ state.error = null;
457
+ state.dependencyRequest = null;
458
+ }
409
459
 
410
- saveState(path.join(runDir, 'state.json'), state);
460
+ saveState(statePath, state);
411
461
 
412
462
  // Run tasks
413
- const results = [];
463
+ const results: TaskExecutionResult[] = [];
414
464
 
415
- for (let i = 0; i < config.tasks.length; i++) {
416
- const task = config.tasks[i];
465
+ for (let i = startIndex; i < config.tasks.length; i++) {
466
+ const task = config.tasks[i]!;
417
467
  const taskBranch = `${pipelineBranch}--${String(i + 1).padStart(2, '0')}-${task.name}`;
418
468
 
419
469
  const result = await runTask({
@@ -431,20 +481,21 @@ async function runTasks(config, runDir) {
431
481
 
432
482
  // Update state
433
483
  state.currentTaskIndex = i + 1;
434
- saveState(path.join(runDir, 'state.json'), state);
484
+ saveState(statePath, state);
435
485
 
436
486
  // Handle blocked or error
437
487
  if (result.status === 'BLOCKED_DEPENDENCY') {
438
- state.status = 'blocked_dependency';
439
- state.dependencyRequest = result.dependencyRequest;
440
- saveState(path.join(runDir, 'state.json'), state);
488
+ state.status = 'failed';
489
+ state.dependencyRequest = result.dependencyRequest || null;
490
+ saveState(statePath, state);
441
491
  logger.warn('Task blocked on dependency change');
442
492
  process.exit(2);
443
493
  }
444
494
 
445
495
  if (result.status !== 'FINISHED') {
446
496
  state.status = 'failed';
447
- saveState(path.join(runDir, 'state.json'), state);
497
+ state.error = result.error || 'Unknown error';
498
+ saveState(statePath, state);
448
499
  logger.error(`Task failed: ${result.error}`);
449
500
  process.exit(1);
450
501
  }
@@ -457,17 +508,13 @@ async function runTasks(config, runDir) {
457
508
 
458
509
  // Complete
459
510
  state.status = 'completed';
460
- saveState(path.join(runDir, 'state.json'), state);
511
+ state.endTime = Date.now();
512
+ saveState(statePath, state);
461
513
 
462
514
  logger.success('All tasks completed!');
463
515
  return results;
464
516
  }
465
517
 
466
- module.exports = {
467
- runTasks,
468
- runTask,
469
- };
470
-
471
518
  /**
472
519
  * CLI entry point
473
520
  */
@@ -479,12 +526,14 @@ if (require.main === module) {
479
526
  process.exit(1);
480
527
  }
481
528
 
482
- const tasksFile = args[0];
529
+ const tasksFile = args[0]!;
483
530
  const runDirIdx = args.indexOf('--run-dir');
484
- const executorIdx = args.indexOf('--executor');
531
+ const startIdxIdx = args.indexOf('--start-index');
532
+ // const executorIdx = args.indexOf('--executor');
485
533
 
486
- const runDir = runDirIdx >= 0 ? args[runDirIdx + 1] : '.';
487
- const executor = executorIdx >= 0 ? args[executorIdx + 1] : 'cursor-agent';
534
+ const runDir = runDirIdx >= 0 ? args[runDirIdx + 1]! : '.';
535
+ const startIndex = startIdxIdx >= 0 ? parseInt(args[startIdxIdx + 1] || '0') : 0;
536
+ // const executor = executorIdx >= 0 ? args[executorIdx + 1] : 'cursor-agent';
488
537
 
489
538
  if (!fs.existsSync(tasksFile)) {
490
539
  console.error(`Tasks file not found: ${tasksFile}`);
@@ -492,10 +541,10 @@ if (require.main === module) {
492
541
  }
493
542
 
494
543
  // Load tasks configuration
495
- let config;
544
+ let config: RunnerConfig;
496
545
  try {
497
- config = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
498
- } catch (error) {
546
+ config = JSON.parse(fs.readFileSync(tasksFile, 'utf8')) as RunnerConfig;
547
+ } catch (error: any) {
499
548
  console.error(`Failed to load tasks file: ${error.message}`);
500
549
  process.exit(1);
501
550
  }
@@ -507,13 +556,13 @@ if (require.main === module) {
507
556
  };
508
557
 
509
558
  // Run tasks
510
- runTasks(config, runDir)
559
+ runTasks(tasksFile, config, runDir, { startIndex })
511
560
  .then(() => {
512
561
  process.exit(0);
513
562
  })
514
563
  .catch(error => {
515
564
  console.error(`Runner failed: ${error.message}`);
516
- if (process.env.DEBUG) {
565
+ if (process.env['DEBUG']) {
517
566
  console.error(error.stack);
518
567
  }
519
568
  process.exit(1);
@@ -1,17 +1,18 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * Configuration loader for CursorFlow
4
3
  *
5
4
  * Finds project root and loads user configuration with defaults
6
5
  */
7
6
 
8
- const path = require('path');
9
- const fs = require('fs');
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ import { CursorFlowConfig } from './types';
10
+ export { CursorFlowConfig };
10
11
 
11
12
  /**
12
13
  * Find project root by looking for package.json
13
14
  */
14
- function findProjectRoot(cwd = process.cwd()) {
15
+ export function findProjectRoot(cwd = process.cwd()): string {
15
16
  let current = cwd;
16
17
 
17
18
  while (current !== path.parse(current).root) {
@@ -28,7 +29,7 @@ function findProjectRoot(cwd = process.cwd()) {
28
29
  /**
29
30
  * Load configuration with defaults
30
31
  */
31
- function loadConfig(projectRoot = null) {
32
+ export function loadConfig(projectRoot: string | null = null): CursorFlowConfig {
32
33
  if (!projectRoot) {
33
34
  projectRoot = findProjectRoot();
34
35
  }
@@ -36,7 +37,7 @@ function loadConfig(projectRoot = null) {
36
37
  const configPath = path.join(projectRoot, 'cursorflow.config.js');
37
38
 
38
39
  // Default configuration
39
- const defaults = {
40
+ const defaults: CursorFlowConfig = {
40
41
  // Directories
41
42
  tasksDir: '_cursorflow/tasks',
42
43
  logsDir: '_cursorflow/logs',
@@ -46,8 +47,8 @@ function loadConfig(projectRoot = null) {
46
47
  branchPrefix: 'feature/',
47
48
 
48
49
  // Execution
49
- executor: 'cursor-agent', // 'cursor-agent' | 'cloud'
50
- pollInterval: 60, // seconds
50
+ executor: 'cursor-agent',
51
+ pollInterval: 60,
51
52
 
52
53
  // Dependencies
53
54
  allowDependencyChange: false,
@@ -60,12 +61,12 @@ function loadConfig(projectRoot = null) {
60
61
 
61
62
  // Lane defaults
62
63
  defaultLaneConfig: {
63
- devPort: 3001, // 3000 + laneNumber
64
+ devPort: 3001,
64
65
  autoCreatePr: false,
65
66
  },
66
67
 
67
68
  // Logging
68
- logLevel: 'info', // 'error' | 'warn' | 'info' | 'debug'
69
+ logLevel: 'info',
69
70
  verboseGit: false,
70
71
 
71
72
  // Advanced
@@ -81,7 +82,7 @@ function loadConfig(projectRoot = null) {
81
82
  try {
82
83
  const userConfig = require(configPath);
83
84
  return { ...defaults, ...userConfig, projectRoot };
84
- } catch (error) {
85
+ } catch (error: any) {
85
86
  console.warn(`Warning: Failed to load config from ${configPath}: ${error.message}`);
86
87
  console.warn('Using default configuration...');
87
88
  }
@@ -93,22 +94,22 @@ function loadConfig(projectRoot = null) {
93
94
  /**
94
95
  * Get absolute path for tasks directory
95
96
  */
96
- function getTasksDir(config) {
97
+ export function getTasksDir(config: CursorFlowConfig): string {
97
98
  return path.join(config.projectRoot, config.tasksDir);
98
99
  }
99
100
 
100
101
  /**
101
102
  * Get absolute path for logs directory
102
103
  */
103
- function getLogsDir(config) {
104
+ export function getLogsDir(config: CursorFlowConfig): string {
104
105
  return path.join(config.projectRoot, config.logsDir);
105
106
  }
106
107
 
107
108
  /**
108
109
  * Validate configuration
109
110
  */
110
- function validateConfig(config) {
111
- const errors = [];
111
+ export function validateConfig(config: CursorFlowConfig): boolean {
112
+ const errors: string[] = [];
112
113
 
113
114
  if (!config.tasksDir) {
114
115
  errors.push('tasksDir is required');
@@ -136,7 +137,7 @@ function validateConfig(config) {
136
137
  /**
137
138
  * Create default config file
138
139
  */
139
- function createDefaultConfig(projectRoot, force = false) {
140
+ export function createDefaultConfig(projectRoot: string, force = false): string {
140
141
  const configPath = path.join(projectRoot, 'cursorflow.config.js');
141
142
 
142
143
  if (fs.existsSync(configPath) && !force) {
@@ -184,12 +185,3 @@ function createDefaultConfig(projectRoot, force = false) {
184
185
  fs.writeFileSync(configPath, template, 'utf8');
185
186
  return configPath;
186
187
  }
187
-
188
- module.exports = {
189
- findProjectRoot,
190
- loadConfig,
191
- getTasksDir,
192
- getLogsDir,
193
- validateConfig,
194
- createDefaultConfig,
195
- };