@litmers/cursorflow-orchestrator 0.1.15 → 0.1.20
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 +23 -1
- package/README.md +26 -7
- package/commands/cursorflow-run.md +2 -0
- package/commands/cursorflow-triggers.md +250 -0
- package/dist/cli/clean.js +8 -7
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +20 -14
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +64 -47
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +27 -17
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +73 -33
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +193 -40
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +3 -2
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +7 -7
- package/dist/cli/signal.js.map +1 -1
- package/dist/core/orchestrator.d.ts +2 -1
- package/dist/core/orchestrator.js +54 -93
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +6 -4
- package/dist/core/reviewer.js +7 -5
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +8 -0
- package/dist/core/runner.js +219 -32
- package/dist/core/runner.js.map +1 -1
- package/dist/utils/config.js +20 -10
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/doctor.js +35 -7
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +2 -2
- package/dist/utils/enhanced-logger.js +114 -43
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.js +163 -10
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +16 -0
- package/dist/utils/log-formatter.js +194 -0
- package/dist/utils/log-formatter.js.map +1 -0
- 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/repro-thinking-logs.d.ts +1 -0
- package/dist/utils/repro-thinking-logs.js +80 -0
- package/dist/utils/repro-thinking-logs.js.map +1 -0
- package/dist/utils/state.d.ts +4 -1
- package/dist/utils/state.js +11 -8
- package/dist/utils/state.js.map +1 -1
- 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 +13 -0
- package/dist/utils/webhook.js +3 -0
- package/dist/utils/webhook.js.map +1 -1
- package/package.json +4 -2
- package/scripts/ai-security-check.js +3 -0
- package/scripts/local-security-gate.sh +9 -1
- package/scripts/verify-and-fix.sh +37 -0
- package/src/cli/clean.ts +8 -7
- package/src/cli/index.ts +5 -1
- package/src/cli/init.ts +19 -15
- package/src/cli/logs.ts +67 -47
- package/src/cli/monitor.ts +28 -18
- package/src/cli/prepare.ts +75 -35
- package/src/cli/resume.ts +810 -626
- package/src/cli/run.ts +3 -2
- package/src/cli/signal.ts +7 -6
- package/src/core/orchestrator.ts +68 -93
- package/src/core/reviewer.ts +14 -9
- package/src/core/runner.ts +229 -33
- package/src/utils/config.ts +19 -11
- package/src/utils/doctor.ts +38 -7
- package/src/utils/enhanced-logger.ts +117 -49
- package/src/utils/git.ts +145 -11
- package/src/utils/log-formatter.ts +162 -0
- package/src/utils/path.ts +45 -0
- package/src/utils/repro-thinking-logs.ts +54 -0
- package/src/utils/state.ts +16 -8
- package/src/utils/template.ts +92 -0
- package/src/utils/types.ts +13 -0
- package/src/utils/webhook.ts +3 -0
- package/templates/basic.json +21 -0
- package/scripts/simple-logging-test.sh +0 -97
- package/scripts/test-real-cursor-lifecycle.sh +0 -289
- package/scripts/test-real-logging.sh +0 -289
- package/scripts/test-streaming-multi-task.sh +0 -247
package/src/cli/prepare.ts
CHANGED
|
@@ -9,6 +9,8 @@ import * as path from 'path';
|
|
|
9
9
|
import * as logger from '../utils/logger';
|
|
10
10
|
import { loadConfig, getTasksDir } from '../utils/config';
|
|
11
11
|
import { Task, RunnerConfig } from '../utils/types';
|
|
12
|
+
import { safeJoin } from '../utils/path';
|
|
13
|
+
import { resolveTemplate } from '../utils/template';
|
|
12
14
|
|
|
13
15
|
// Preset template types
|
|
14
16
|
type PresetType = 'complex' | 'simple' | 'merge';
|
|
@@ -97,20 +99,24 @@ Prepare task files for a new feature - Terminal-first workflow.
|
|
|
97
99
|
--prompt <text> Task prompt (uses preset or single task)
|
|
98
100
|
--criteria <list> Comma-separated acceptance criteria
|
|
99
101
|
--model <model> Model to use (default: sonnet-4.5)
|
|
100
|
-
--task <spec> Full task spec: "name|model|prompt|criteria" (repeatable)
|
|
102
|
+
--task <spec> Full task spec: "name|model|prompt|criteria|dependsOn|timeout" (repeatable)
|
|
101
103
|
|
|
102
104
|
Dependencies:
|
|
103
105
|
--sequential Chain lanes: 1 → 2 → 3
|
|
104
106
|
--deps <spec> Custom dependencies: "2:1;3:1,2"
|
|
105
107
|
--depends-on <lanes> Dependencies for --add-lane: "01-lane-1,02-lane-2"
|
|
108
|
+
Task-level deps: In --task, add "lane:task" at the end.
|
|
109
|
+
Example: "test|sonnet-4.5|Run tests|All pass|01-lane-1:setup"
|
|
110
|
+
Task-level timeout: In --task, add milliseconds at the end.
|
|
111
|
+
Example: "heavy|sonnet-4.5|Big task|Done||1200000"
|
|
106
112
|
|
|
107
113
|
Incremental (add to existing):
|
|
108
114
|
--add-lane <dir> Add a new lane to existing task directory
|
|
109
115
|
--add-task <file> Append task(s) to existing lane JSON file
|
|
110
116
|
|
|
111
117
|
Advanced:
|
|
112
|
-
--template <path>
|
|
113
|
-
--force
|
|
118
|
+
--template <path|url|name> External template JSON file, URL, or built-in name
|
|
119
|
+
--force Overwrite existing files
|
|
114
120
|
|
|
115
121
|
═══════════════════════════════════════════════════════════════════════════════
|
|
116
122
|
|
|
@@ -203,23 +209,31 @@ function parseArgs(args: string[]): PrepareOptions {
|
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
function parseTaskSpec(spec: string): Task {
|
|
206
|
-
// Format: "name|model|prompt|criteria1,criteria2"
|
|
212
|
+
// Format: "name|model|prompt|criteria1,criteria2|lane:task1,lane:task2|timeoutMs"
|
|
207
213
|
const parts = spec.split('|');
|
|
208
214
|
|
|
209
215
|
if (parts.length < 3) {
|
|
210
|
-
throw new Error(`Invalid task spec: "${spec}". Expected format: "name|model|prompt[|
|
|
216
|
+
throw new Error(`Invalid task spec: "${spec}". Expected format: "name|model|prompt[|criteria[|dependsOn[|timeout]]]"`);
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
const [name, model, prompt, criteriaStr] = parts;
|
|
219
|
+
const [name, model, prompt, criteriaStr, depsStr, timeoutStr] = parts;
|
|
214
220
|
const acceptanceCriteria = criteriaStr
|
|
215
221
|
? criteriaStr.split(',').map(c => c.trim()).filter(c => c)
|
|
216
222
|
: undefined;
|
|
217
223
|
|
|
224
|
+
const dependsOn = depsStr
|
|
225
|
+
? depsStr.split(',').map(d => d.trim()).filter(d => d)
|
|
226
|
+
: undefined;
|
|
227
|
+
|
|
228
|
+
const timeout = timeoutStr ? parseInt(timeoutStr) : undefined;
|
|
229
|
+
|
|
218
230
|
return {
|
|
219
231
|
name: name.trim(),
|
|
220
232
|
model: model.trim() || 'sonnet-4.5',
|
|
221
233
|
prompt: prompt.trim(),
|
|
222
234
|
...(acceptanceCriteria && acceptanceCriteria.length > 0 ? { acceptanceCriteria } : {}),
|
|
235
|
+
...(dependsOn && dependsOn.length > 0 ? { dependsOn } : {}),
|
|
236
|
+
...(timeout ? { timeout } : {}),
|
|
223
237
|
};
|
|
224
238
|
}
|
|
225
239
|
|
|
@@ -600,7 +614,7 @@ function getFeatureNameFromDir(taskDir: string): string {
|
|
|
600
614
|
}
|
|
601
615
|
|
|
602
616
|
async function addLaneToDir(options: PrepareOptions): Promise<void> {
|
|
603
|
-
const taskDir = path.resolve(process.cwd(), options.addLane!);
|
|
617
|
+
const taskDir = path.resolve(process.cwd(), options.addLane!); // nosemgrep
|
|
604
618
|
|
|
605
619
|
if (!fs.existsSync(taskDir)) {
|
|
606
620
|
throw new Error(`Task directory not found: ${taskDir}`);
|
|
@@ -610,29 +624,56 @@ async function addLaneToDir(options: PrepareOptions): Promise<void> {
|
|
|
610
624
|
const laneNumber = getNextLaneNumber(taskDir);
|
|
611
625
|
const laneName = `lane-${laneNumber}`;
|
|
612
626
|
const fileName = `${laneNumber.toString().padStart(2, '0')}-${laneName}.json`;
|
|
613
|
-
const filePath =
|
|
614
|
-
|
|
615
|
-
if (fs.existsSync(filePath) && !options.force) {
|
|
616
|
-
throw new Error(`Lane file already exists: ${filePath}. Use --force to overwrite.`);
|
|
617
|
-
}
|
|
627
|
+
const filePath = safeJoin(taskDir, fileName);
|
|
618
628
|
|
|
619
629
|
const hasDependencies = options.dependsOnLanes.length > 0;
|
|
620
630
|
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
|
|
631
|
+
// Load template if provided
|
|
632
|
+
let template = null;
|
|
633
|
+
if (options.template) {
|
|
634
|
+
template = await resolveTemplate(options.template);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
let taskConfig;
|
|
638
|
+
let effectivePreset: EffectivePresetType = options.preset || (hasDependencies ? 'merge' : 'complex');
|
|
639
|
+
|
|
640
|
+
if (template) {
|
|
641
|
+
taskConfig = { ...template, laneNumber, devPort: 3000 + laneNumber };
|
|
642
|
+
effectivePreset = 'custom';
|
|
643
|
+
} else {
|
|
644
|
+
// Build tasks from options (auto-detects merge preset if has dependencies)
|
|
645
|
+
const tasks = buildTasksFromOptions(options, laneNumber, featureName, hasDependencies);
|
|
646
|
+
taskConfig = getDefaultConfig(laneNumber, featureName, tasks);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Replace placeholders
|
|
650
|
+
const processedConfig = replacePlaceholders(taskConfig, {
|
|
651
|
+
featureName,
|
|
652
|
+
laneNumber,
|
|
653
|
+
devPort: 3000 + laneNumber,
|
|
654
|
+
});
|
|
624
655
|
|
|
625
656
|
// Add dependencies if specified
|
|
626
657
|
const finalConfig = {
|
|
627
|
-
...
|
|
658
|
+
...processedConfig,
|
|
628
659
|
...(hasDependencies ? { dependsOn: options.dependsOnLanes } : {}),
|
|
629
660
|
};
|
|
630
661
|
|
|
631
|
-
|
|
662
|
+
// Use atomic write with wx flag to avoid TOCTOU race condition (unless force is set)
|
|
663
|
+
try {
|
|
664
|
+
const writeFlag = options.force ? 'w' : 'wx';
|
|
665
|
+
fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2) + '\n', { encoding: 'utf8', flag: writeFlag });
|
|
666
|
+
} catch (err: any) {
|
|
667
|
+
if (err.code === 'EEXIST') {
|
|
668
|
+
throw new Error(`Lane file already exists: ${filePath}. Use --force to overwrite.`);
|
|
669
|
+
}
|
|
670
|
+
throw err;
|
|
671
|
+
}
|
|
632
672
|
|
|
633
|
-
const
|
|
673
|
+
const tasksList = finalConfig.tasks || [];
|
|
674
|
+
const taskSummary = tasksList.map((t: any) => t.name).join(' → ');
|
|
634
675
|
const depsInfo = hasDependencies ? ` (depends: ${options.dependsOnLanes.join(', ')})` : '';
|
|
635
|
-
const presetInfo = options.preset ? ` [${options.preset}]` : (hasDependencies ? ' [merge]' : '');
|
|
676
|
+
const presetInfo = options.preset ? ` [${options.preset}]` : (hasDependencies ? ' [merge]' : (template ? ' [template]' : ''));
|
|
636
677
|
|
|
637
678
|
logger.success(`Added lane: ${fileName} [${taskSummary}]${presetInfo}${depsInfo}`);
|
|
638
679
|
logger.info(`Directory: ${taskDir}`);
|
|
@@ -643,18 +684,22 @@ async function addLaneToDir(options: PrepareOptions): Promise<void> {
|
|
|
643
684
|
}
|
|
644
685
|
|
|
645
686
|
async function addTaskToLane(options: PrepareOptions): Promise<void> {
|
|
646
|
-
const laneFile = path.resolve(process.cwd(), options.addTask!);
|
|
647
|
-
|
|
648
|
-
if (!fs.existsSync(laneFile)) {
|
|
649
|
-
throw new Error(`Lane file not found: ${laneFile}`);
|
|
650
|
-
}
|
|
687
|
+
const laneFile = path.resolve(process.cwd(), options.addTask!); // nosemgrep
|
|
651
688
|
|
|
652
689
|
if (options.taskSpecs.length === 0) {
|
|
653
690
|
throw new Error('No task specified. Use --task "name|model|prompt|criteria" to define a task.');
|
|
654
691
|
}
|
|
655
692
|
|
|
656
|
-
// Read existing config
|
|
657
|
-
|
|
693
|
+
// Read existing config - let the error propagate if file doesn't exist (avoids TOCTOU)
|
|
694
|
+
let existingConfig: any;
|
|
695
|
+
try {
|
|
696
|
+
existingConfig = JSON.parse(fs.readFileSync(laneFile, 'utf8'));
|
|
697
|
+
} catch (err: any) {
|
|
698
|
+
if (err.code === 'ENOENT') {
|
|
699
|
+
throw new Error(`Lane file not found: ${laneFile}`);
|
|
700
|
+
}
|
|
701
|
+
throw err;
|
|
702
|
+
}
|
|
658
703
|
|
|
659
704
|
if (!existingConfig.tasks || !Array.isArray(existingConfig.tasks)) {
|
|
660
705
|
existingConfig.tasks = [];
|
|
@@ -687,7 +732,7 @@ async function createNewFeature(options: PrepareOptions): Promise<void> {
|
|
|
687
732
|
const now = new Date();
|
|
688
733
|
const timestamp = now.toISOString().replace(/[-T:]/g, '').substring(2, 12);
|
|
689
734
|
const taskDirName = `${timestamp}_${options.featureName}`;
|
|
690
|
-
const taskDir =
|
|
735
|
+
const taskDir = safeJoin(tasksBaseDir, taskDirName);
|
|
691
736
|
|
|
692
737
|
if (fs.existsSync(taskDir) && !options.force) {
|
|
693
738
|
throw new Error(`Task directory already exists: ${taskDir}. Use --force to overwrite.`);
|
|
@@ -702,12 +747,7 @@ async function createNewFeature(options: PrepareOptions): Promise<void> {
|
|
|
702
747
|
// Load template if provided (overrides --prompt/--task/--preset)
|
|
703
748
|
let template = null;
|
|
704
749
|
if (options.template) {
|
|
705
|
-
|
|
706
|
-
if (!fs.existsSync(templatePath)) {
|
|
707
|
-
throw new Error(`Template file not found: ${templatePath}`);
|
|
708
|
-
}
|
|
709
|
-
template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
710
|
-
logger.info(`Using template: ${options.template}`);
|
|
750
|
+
template = await resolveTemplate(options.template);
|
|
711
751
|
}
|
|
712
752
|
|
|
713
753
|
// Calculate dependencies
|
|
@@ -720,7 +760,7 @@ async function createNewFeature(options: PrepareOptions): Promise<void> {
|
|
|
720
760
|
for (let i = 1; i <= options.lanes; i++) {
|
|
721
761
|
const laneName = `lane-${i}`;
|
|
722
762
|
const fileName = `${i.toString().padStart(2, '0')}-${laneName}.json`;
|
|
723
|
-
const filePath =
|
|
763
|
+
const filePath = safeJoin(taskDir, fileName);
|
|
724
764
|
|
|
725
765
|
const depNums = dependencyMap.get(i) || [];
|
|
726
766
|
const dependsOn = depNums.map(n => {
|
|
@@ -767,7 +807,7 @@ async function createNewFeature(options: PrepareOptions): Promise<void> {
|
|
|
767
807
|
}
|
|
768
808
|
|
|
769
809
|
// Create README
|
|
770
|
-
const readmePath =
|
|
810
|
+
const readmePath = safeJoin(taskDir, 'README.md');
|
|
771
811
|
const readme = `# Task: ${options.featureName}
|
|
772
812
|
|
|
773
813
|
Prepared at: ${now.toISOString()}
|