@litmers/cursorflow-orchestrator 0.1.18 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -7
  3. package/commands/cursorflow-clean.md +19 -0
  4. package/commands/cursorflow-runs.md +59 -0
  5. package/commands/cursorflow-stop.md +55 -0
  6. package/dist/cli/clean.js +178 -6
  7. package/dist/cli/clean.js.map +1 -1
  8. package/dist/cli/index.js +12 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +8 -7
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/logs.js +126 -77
  13. package/dist/cli/logs.js.map +1 -1
  14. package/dist/cli/monitor.d.ts +7 -0
  15. package/dist/cli/monitor.js +1021 -202
  16. package/dist/cli/monitor.js.map +1 -1
  17. package/dist/cli/prepare.js +39 -21
  18. package/dist/cli/prepare.js.map +1 -1
  19. package/dist/cli/resume.js +268 -163
  20. package/dist/cli/resume.js.map +1 -1
  21. package/dist/cli/run.js +11 -5
  22. package/dist/cli/run.js.map +1 -1
  23. package/dist/cli/runs.d.ts +5 -0
  24. package/dist/cli/runs.js +214 -0
  25. package/dist/cli/runs.js.map +1 -0
  26. package/dist/cli/setup-commands.js +0 -0
  27. package/dist/cli/signal.js +8 -8
  28. package/dist/cli/signal.js.map +1 -1
  29. package/dist/cli/stop.d.ts +5 -0
  30. package/dist/cli/stop.js +215 -0
  31. package/dist/cli/stop.js.map +1 -0
  32. package/dist/cli/tasks.d.ts +10 -0
  33. package/dist/cli/tasks.js +165 -0
  34. package/dist/cli/tasks.js.map +1 -0
  35. package/dist/core/auto-recovery.d.ts +212 -0
  36. package/dist/core/auto-recovery.js +737 -0
  37. package/dist/core/auto-recovery.js.map +1 -0
  38. package/dist/core/failure-policy.d.ts +156 -0
  39. package/dist/core/failure-policy.js +488 -0
  40. package/dist/core/failure-policy.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +16 -2
  42. package/dist/core/orchestrator.js +439 -105
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/reviewer.d.ts +2 -0
  45. package/dist/core/reviewer.js +2 -0
  46. package/dist/core/reviewer.js.map +1 -1
  47. package/dist/core/runner.d.ts +33 -10
  48. package/dist/core/runner.js +374 -164
  49. package/dist/core/runner.js.map +1 -1
  50. package/dist/services/logging/buffer.d.ts +67 -0
  51. package/dist/services/logging/buffer.js +309 -0
  52. package/dist/services/logging/buffer.js.map +1 -0
  53. package/dist/services/logging/console.d.ts +89 -0
  54. package/dist/services/logging/console.js +169 -0
  55. package/dist/services/logging/console.js.map +1 -0
  56. package/dist/services/logging/file-writer.d.ts +71 -0
  57. package/dist/services/logging/file-writer.js +516 -0
  58. package/dist/services/logging/file-writer.js.map +1 -0
  59. package/dist/services/logging/formatter.d.ts +39 -0
  60. package/dist/services/logging/formatter.js +227 -0
  61. package/dist/services/logging/formatter.js.map +1 -0
  62. package/dist/services/logging/index.d.ts +11 -0
  63. package/dist/services/logging/index.js +30 -0
  64. package/dist/services/logging/index.js.map +1 -0
  65. package/dist/services/logging/parser.d.ts +31 -0
  66. package/dist/services/logging/parser.js +222 -0
  67. package/dist/services/logging/parser.js.map +1 -0
  68. package/dist/services/process/index.d.ts +59 -0
  69. package/dist/services/process/index.js +257 -0
  70. package/dist/services/process/index.js.map +1 -0
  71. package/dist/types/agent.d.ts +20 -0
  72. package/dist/types/agent.js +6 -0
  73. package/dist/types/agent.js.map +1 -0
  74. package/dist/types/config.d.ts +65 -0
  75. package/dist/types/config.js +6 -0
  76. package/dist/types/config.js.map +1 -0
  77. package/dist/types/events.d.ts +125 -0
  78. package/dist/types/events.js +6 -0
  79. package/dist/types/events.js.map +1 -0
  80. package/dist/types/index.d.ts +12 -0
  81. package/dist/types/index.js +37 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/dist/types/lane.d.ts +43 -0
  84. package/dist/types/lane.js +6 -0
  85. package/dist/types/lane.js.map +1 -0
  86. package/dist/types/logging.d.ts +71 -0
  87. package/dist/types/logging.js +16 -0
  88. package/dist/types/logging.js.map +1 -0
  89. package/dist/types/review.d.ts +17 -0
  90. package/dist/types/review.js +6 -0
  91. package/dist/types/review.js.map +1 -0
  92. package/dist/types/run.d.ts +32 -0
  93. package/dist/types/run.js +6 -0
  94. package/dist/types/run.js.map +1 -0
  95. package/dist/types/task.d.ts +71 -0
  96. package/dist/types/task.js +6 -0
  97. package/dist/types/task.js.map +1 -0
  98. package/dist/ui/components.d.ts +134 -0
  99. package/dist/ui/components.js +389 -0
  100. package/dist/ui/components.js.map +1 -0
  101. package/dist/ui/log-viewer.d.ts +49 -0
  102. package/dist/ui/log-viewer.js +449 -0
  103. package/dist/ui/log-viewer.js.map +1 -0
  104. package/dist/utils/checkpoint.d.ts +87 -0
  105. package/dist/utils/checkpoint.js +317 -0
  106. package/dist/utils/checkpoint.js.map +1 -0
  107. package/dist/utils/config.d.ts +4 -0
  108. package/dist/utils/config.js +18 -8
  109. package/dist/utils/config.js.map +1 -1
  110. package/dist/utils/cursor-agent.js.map +1 -1
  111. package/dist/utils/dependency.d.ts +74 -0
  112. package/dist/utils/dependency.js +420 -0
  113. package/dist/utils/dependency.js.map +1 -0
  114. package/dist/utils/doctor.js +17 -11
  115. package/dist/utils/doctor.js.map +1 -1
  116. package/dist/utils/enhanced-logger.d.ts +10 -33
  117. package/dist/utils/enhanced-logger.js +108 -20
  118. package/dist/utils/enhanced-logger.js.map +1 -1
  119. package/dist/utils/git.d.ts +121 -0
  120. package/dist/utils/git.js +484 -11
  121. package/dist/utils/git.js.map +1 -1
  122. package/dist/utils/health.d.ts +91 -0
  123. package/dist/utils/health.js +556 -0
  124. package/dist/utils/health.js.map +1 -0
  125. package/dist/utils/lock.d.ts +95 -0
  126. package/dist/utils/lock.js +332 -0
  127. package/dist/utils/lock.js.map +1 -0
  128. package/dist/utils/log-buffer.d.ts +17 -0
  129. package/dist/utils/log-buffer.js +14 -0
  130. package/dist/utils/log-buffer.js.map +1 -0
  131. package/dist/utils/log-constants.d.ts +23 -0
  132. package/dist/utils/log-constants.js +28 -0
  133. package/dist/utils/log-constants.js.map +1 -0
  134. package/dist/utils/log-formatter.d.ts +25 -0
  135. package/dist/utils/log-formatter.js +237 -0
  136. package/dist/utils/log-formatter.js.map +1 -0
  137. package/dist/utils/log-service.d.ts +19 -0
  138. package/dist/utils/log-service.js +47 -0
  139. package/dist/utils/log-service.js.map +1 -0
  140. package/dist/utils/logger.d.ts +46 -27
  141. package/dist/utils/logger.js +82 -60
  142. package/dist/utils/logger.js.map +1 -1
  143. package/dist/utils/path.d.ts +19 -0
  144. package/dist/utils/path.js +77 -0
  145. package/dist/utils/path.js.map +1 -0
  146. package/dist/utils/process-manager.d.ts +21 -0
  147. package/dist/utils/process-manager.js +138 -0
  148. package/dist/utils/process-manager.js.map +1 -0
  149. package/dist/utils/retry.d.ts +121 -0
  150. package/dist/utils/retry.js +374 -0
  151. package/dist/utils/retry.js.map +1 -0
  152. package/dist/utils/run-service.d.ts +88 -0
  153. package/dist/utils/run-service.js +412 -0
  154. package/dist/utils/run-service.js.map +1 -0
  155. package/dist/utils/state.d.ts +62 -3
  156. package/dist/utils/state.js +317 -11
  157. package/dist/utils/state.js.map +1 -1
  158. package/dist/utils/task-service.d.ts +82 -0
  159. package/dist/utils/task-service.js +348 -0
  160. package/dist/utils/task-service.js.map +1 -0
  161. package/dist/utils/template.d.ts +14 -0
  162. package/dist/utils/template.js +122 -0
  163. package/dist/utils/template.js.map +1 -0
  164. package/dist/utils/types.d.ts +2 -271
  165. package/dist/utils/types.js +16 -0
  166. package/dist/utils/types.js.map +1 -1
  167. package/package.json +38 -23
  168. package/scripts/ai-security-check.js +0 -1
  169. package/scripts/local-security-gate.sh +0 -0
  170. package/scripts/monitor-lanes.sh +94 -0
  171. package/scripts/patches/test-cursor-agent.js +0 -1
  172. package/scripts/release.sh +0 -0
  173. package/scripts/setup-security.sh +0 -0
  174. package/scripts/stream-logs.sh +72 -0
  175. package/scripts/verify-and-fix.sh +0 -0
  176. package/src/cli/clean.ts +187 -6
  177. package/src/cli/index.ts +12 -1
  178. package/src/cli/init.ts +8 -7
  179. package/src/cli/logs.ts +124 -77
  180. package/src/cli/monitor.ts +1815 -898
  181. package/src/cli/prepare.ts +41 -21
  182. package/src/cli/resume.ts +753 -626
  183. package/src/cli/run.ts +12 -5
  184. package/src/cli/runs.ts +212 -0
  185. package/src/cli/setup-commands.ts +0 -0
  186. package/src/cli/signal.ts +8 -7
  187. package/src/cli/stop.ts +209 -0
  188. package/src/cli/tasks.ts +154 -0
  189. package/src/core/auto-recovery.ts +909 -0
  190. package/src/core/failure-policy.ts +592 -0
  191. package/src/core/orchestrator.ts +1131 -704
  192. package/src/core/reviewer.ts +4 -0
  193. package/src/core/runner.ts +444 -180
  194. package/src/services/logging/buffer.ts +326 -0
  195. package/src/services/logging/console.ts +193 -0
  196. package/src/services/logging/file-writer.ts +526 -0
  197. package/src/services/logging/formatter.ts +268 -0
  198. package/src/services/logging/index.ts +16 -0
  199. package/src/services/logging/parser.ts +232 -0
  200. package/src/services/process/index.ts +261 -0
  201. package/src/types/agent.ts +24 -0
  202. package/src/types/config.ts +79 -0
  203. package/src/types/events.ts +156 -0
  204. package/src/types/index.ts +29 -0
  205. package/src/types/lane.ts +56 -0
  206. package/src/types/logging.ts +96 -0
  207. package/src/types/review.ts +20 -0
  208. package/src/types/run.ts +37 -0
  209. package/src/types/task.ts +79 -0
  210. package/src/ui/components.ts +430 -0
  211. package/src/ui/log-viewer.ts +485 -0
  212. package/src/utils/checkpoint.ts +374 -0
  213. package/src/utils/config.ts +18 -8
  214. package/src/utils/cursor-agent.ts +1 -1
  215. package/src/utils/dependency.ts +482 -0
  216. package/src/utils/doctor.ts +18 -11
  217. package/src/utils/enhanced-logger.ts +122 -60
  218. package/src/utils/git.ts +517 -11
  219. package/src/utils/health.ts +596 -0
  220. package/src/utils/lock.ts +346 -0
  221. package/src/utils/log-buffer.ts +28 -0
  222. package/src/utils/log-constants.ts +26 -0
  223. package/src/utils/log-formatter.ts +245 -0
  224. package/src/utils/log-service.ts +49 -0
  225. package/src/utils/logger.ts +100 -51
  226. package/src/utils/path.ts +45 -0
  227. package/src/utils/process-manager.ts +100 -0
  228. package/src/utils/retry.ts +413 -0
  229. package/src/utils/run-service.ts +433 -0
  230. package/src/utils/state.ts +385 -11
  231. package/src/utils/task-service.ts +370 -0
  232. package/src/utils/template.ts +92 -0
  233. package/src/utils/types.ts +2 -314
  234. package/templates/basic.json +21 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * TaskService - Manages CursorFlow task directories and validation
3
+ *
4
+ * Provides:
5
+ * - List prepared task directories
6
+ * - Get task details (lanes, dependencies)
7
+ * - Validate tasks (doctor integration)
8
+ * - Check run eligibility
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { TaskDirInfo, LaneFileInfo, ValidationStatus, Task } from './types';
14
+
15
+ // Re-export types for consumers
16
+ export { TaskDirInfo, LaneFileInfo, ValidationStatus } from './types';
17
+ import * as logger from './logger';
18
+
19
+ export interface ValidationResult {
20
+ /** Combined errors + warnings for backwards compatibility */
21
+ issues: string[];
22
+ status: ValidationStatus;
23
+ errors: string[];
24
+ warnings: string[];
25
+ lastValidated: number;
26
+ }
27
+
28
+ // Cache for validation results
29
+ const validationCache = new Map<string, ValidationResult>();
30
+
31
+ export class TaskService {
32
+ private tasksDir: string;
33
+
34
+ constructor(tasksDir: string) {
35
+ this.tasksDir = tasksDir;
36
+ }
37
+
38
+ /**
39
+ * List all prepared task directories
40
+ */
41
+ listTaskDirs(): TaskDirInfo[] {
42
+ if (!fs.existsSync(this.tasksDir)) {
43
+ return [];
44
+ }
45
+
46
+ const dirs = fs.readdirSync(this.tasksDir)
47
+ .filter(name => {
48
+ const dirPath = path.join(this.tasksDir, name);
49
+ return fs.statSync(dirPath).isDirectory() && !name.startsWith('.');
50
+ })
51
+ .sort((a, b) => b.localeCompare(a)); // Most recent first (assuming timestamp prefix)
52
+
53
+ return dirs.map(name => this.getTaskDirInfo(name)).filter((t): t is TaskDirInfo => t !== null);
54
+ }
55
+
56
+ /**
57
+ * Get detailed information about a task directory
58
+ */
59
+ getTaskDirInfo(taskName: string): TaskDirInfo | null {
60
+ const taskPath = path.join(this.tasksDir, taskName);
61
+
62
+ if (!fs.existsSync(taskPath)) {
63
+ return null;
64
+ }
65
+
66
+ try {
67
+ const stat = fs.statSync(taskPath);
68
+ const timestamp = this.parseTimestampFromName(taskName) || stat.mtime;
69
+ const featureName = this.extractFeatureName(taskName);
70
+ const lanes = this.scanLaneFiles(taskPath);
71
+
72
+ // Get cached validation status
73
+ const cached = validationCache.get(taskName);
74
+ const validationStatus = cached?.status || 'unknown';
75
+ const lastValidated = cached?.lastValidated;
76
+
77
+ return {
78
+ name: taskName,
79
+ path: taskPath,
80
+ timestamp,
81
+ featureName,
82
+ lanes,
83
+ validationStatus,
84
+ lastValidated,
85
+ };
86
+ } catch (error) {
87
+ logger.debug(`Failed to read task dir ${taskName}: ${error}`);
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Scan lane files in a task directory
94
+ */
95
+ private scanLaneFiles(taskPath: string): LaneFileInfo[] {
96
+ const files = fs.readdirSync(taskPath)
97
+ .filter(name => name.endsWith('.json'))
98
+ .sort();
99
+
100
+ const lanes: LaneFileInfo[] = [];
101
+
102
+ for (const fileName of files) {
103
+ try {
104
+ const filePath = path.join(taskPath, fileName);
105
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
106
+
107
+ const laneName = this.extractLaneName(fileName);
108
+ const tasks = content.tasks || [];
109
+ const dependsOn = content.dependsOn || [];
110
+ const preset = this.detectPreset(tasks);
111
+ const taskFlow = this.generateTaskFlow(tasks);
112
+
113
+ lanes.push({
114
+ fileName,
115
+ laneName,
116
+ preset,
117
+ taskCount: tasks.length,
118
+ taskFlow,
119
+ dependsOn,
120
+ });
121
+ } catch (error) {
122
+ logger.debug(`Failed to parse lane file ${fileName}: ${error}`);
123
+ }
124
+ }
125
+
126
+ return lanes;
127
+ }
128
+
129
+ /**
130
+ * Extract lane name from filename (e.g., "01-lane-1.json" -> "lane-1")
131
+ */
132
+ private extractLaneName(fileName: string): string {
133
+ const baseName = fileName.replace('.json', '');
134
+ // Remove numeric prefix if present (e.g., "01-lane-1" -> "lane-1")
135
+ const match = baseName.match(/^\d+-(.+)$/);
136
+ return match ? match[1] : baseName;
137
+ }
138
+
139
+ /**
140
+ * Parse timestamp from task name (format: YYMMDDHHMM_FeatureName)
141
+ */
142
+ private parseTimestampFromName(taskName: string): Date | null {
143
+ const match = taskName.match(/^(\d{10})_/);
144
+ if (!match) return null;
145
+
146
+ const ts = match[1];
147
+ const year = 2000 + parseInt(ts.substring(0, 2), 10);
148
+ const month = parseInt(ts.substring(2, 4), 10) - 1;
149
+ const day = parseInt(ts.substring(4, 6), 10);
150
+ const hour = parseInt(ts.substring(6, 8), 10);
151
+ const minute = parseInt(ts.substring(8, 10), 10);
152
+
153
+ return new Date(year, month, day, hour, minute);
154
+ }
155
+
156
+ /**
157
+ * Extract feature name from task directory name
158
+ */
159
+ private extractFeatureName(taskName: string): string {
160
+ const parts = taskName.split('_');
161
+ if (parts.length >= 2) {
162
+ return parts.slice(1).join('_');
163
+ }
164
+ return taskName;
165
+ }
166
+
167
+ /**
168
+ * Detect task preset based on task structure
169
+ */
170
+ private detectPreset(tasks: Task[]): string {
171
+ if (tasks.length === 0) return 'empty';
172
+
173
+ const taskNames = tasks.map(t => t.name.toLowerCase());
174
+
175
+ if (taskNames.includes('plan') && taskNames.includes('implement') && taskNames.includes('test')) {
176
+ return 'complex';
177
+ }
178
+ if (taskNames.includes('implement') && taskNames.includes('test') && !taskNames.includes('plan')) {
179
+ return 'simple';
180
+ }
181
+ if (taskNames.includes('merge')) {
182
+ return 'merge';
183
+ }
184
+
185
+ return 'custom';
186
+ }
187
+
188
+ /**
189
+ * Generate task flow string (e.g., "plan → implement → test")
190
+ */
191
+ private generateTaskFlow(tasks: Task[]): string {
192
+ if (tasks.length === 0) return '';
193
+ return tasks.map(t => t.name).join(' → ');
194
+ }
195
+
196
+ /**
197
+ * Validate a task directory
198
+ */
199
+ validateTaskDir(taskName: string): ValidationResult {
200
+ const taskInfo = this.getTaskDirInfo(taskName);
201
+
202
+ if (!taskInfo) {
203
+ return {
204
+ status: 'errors',
205
+ errors: [`Task directory not found: ${taskName}`],
206
+ warnings: [],
207
+ issues: [`Task directory not found: ${taskName}`],
208
+ lastValidated: Date.now(),
209
+ };
210
+ }
211
+
212
+ const errors: string[] = [];
213
+ const warnings: string[] = [];
214
+
215
+ // Check if there are any lane files
216
+ if (taskInfo.lanes.length === 0) {
217
+ errors.push('No lane files found in task directory');
218
+ }
219
+
220
+ // Validate each lane file
221
+ for (const lane of taskInfo.lanes) {
222
+ // Check task count
223
+ if (lane.taskCount === 0) {
224
+ errors.push(`Lane ${lane.fileName} has no tasks defined`);
225
+ }
226
+
227
+ // Check dependencies exist
228
+ for (const dep of lane.dependsOn) {
229
+ const depExists = taskInfo.lanes.some(l =>
230
+ l.laneName === dep || l.fileName === dep || l.fileName === `${dep}.json`
231
+ );
232
+ if (!depExists) {
233
+ warnings.push(`Lane ${lane.fileName} depends on unknown lane: ${dep}`);
234
+ }
235
+ }
236
+
237
+ // Validate lane file structure
238
+ try {
239
+ const filePath = path.join(taskInfo.path, lane.fileName);
240
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
241
+
242
+ // Check tasks array
243
+ if (!Array.isArray(content.tasks)) {
244
+ errors.push(`Lane ${lane.fileName}: 'tasks' must be an array`);
245
+ } else {
246
+ // Validate each task
247
+ for (let i = 0; i < content.tasks.length; i++) {
248
+ const task = content.tasks[i];
249
+ if (!task.name) {
250
+ errors.push(`Lane ${lane.fileName}: Task ${i + 1} missing 'name'`);
251
+ }
252
+ if (!task.prompt) {
253
+ errors.push(`Lane ${lane.fileName}: Task ${i + 1} missing 'prompt'`);
254
+ }
255
+ }
256
+ }
257
+ } catch (error) {
258
+ errors.push(`Lane ${lane.fileName}: Invalid JSON - ${error}`);
259
+ }
260
+ }
261
+
262
+ // Check for circular dependencies
263
+ const circularDeps = this.detectCircularDependencies(taskInfo.lanes);
264
+ if (circularDeps.length > 0) {
265
+ errors.push(`Circular dependencies detected: ${circularDeps.join(' -> ')}`);
266
+ }
267
+
268
+ // Determine overall status
269
+ let status: ValidationStatus = 'valid';
270
+ if (errors.length > 0) {
271
+ status = 'errors';
272
+ } else if (warnings.length > 0) {
273
+ status = 'warnings';
274
+ }
275
+
276
+ const issues = [...errors, ...warnings];
277
+ const result: ValidationResult = {
278
+ issues,
279
+ status,
280
+ errors,
281
+ warnings,
282
+ lastValidated: Date.now(),
283
+ };
284
+
285
+ // Cache the result
286
+ validationCache.set(taskName, result);
287
+
288
+ return result;
289
+ }
290
+
291
+ /**
292
+ * Detect circular dependencies in lane dependency graph
293
+ */
294
+ private detectCircularDependencies(lanes: LaneFileInfo[]): string[] {
295
+ const visited = new Set<string>();
296
+ const stack = new Set<string>();
297
+ const cycle: string[] = [];
298
+
299
+ const dfs = (laneName: string): boolean => {
300
+ if (stack.has(laneName)) {
301
+ cycle.push(laneName);
302
+ return true; // Cycle found
303
+ }
304
+ if (visited.has(laneName)) {
305
+ return false;
306
+ }
307
+
308
+ visited.add(laneName);
309
+ stack.add(laneName);
310
+
311
+ const lane = lanes.find(l => l.laneName === laneName);
312
+ if (lane) {
313
+ for (const dep of lane.dependsOn) {
314
+ if (dfs(dep)) {
315
+ cycle.unshift(laneName);
316
+ return true;
317
+ }
318
+ }
319
+ }
320
+
321
+ stack.delete(laneName);
322
+ return false;
323
+ };
324
+
325
+ for (const lane of lanes) {
326
+ if (dfs(lane.laneName)) {
327
+ break;
328
+ }
329
+ }
330
+
331
+ return cycle;
332
+ }
333
+
334
+ /**
335
+ * Get cached validation status
336
+ */
337
+ getValidationStatus(taskName: string): ValidationStatus {
338
+ const cached = validationCache.get(taskName);
339
+ return cached?.status || 'unknown';
340
+ }
341
+
342
+ /**
343
+ * Check if a task can be run
344
+ */
345
+ canRun(taskName: string): { ok: boolean; issues: string[] } {
346
+ const validation = this.validateTaskDir(taskName);
347
+
348
+ if (validation.status === 'errors') {
349
+ return { ok: false, issues: validation.errors };
350
+ }
351
+
352
+ return { ok: true, issues: validation.warnings };
353
+ }
354
+
355
+ /**
356
+ * Clear validation cache
357
+ */
358
+ clearCache(): void {
359
+ validationCache.clear();
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Create a TaskService instance with default paths
365
+ */
366
+ export function createTaskService(projectRoot?: string): TaskService {
367
+ const root = projectRoot || process.cwd();
368
+ const tasksDir = path.join(root, '_cursorflow', 'tasks');
369
+ return new TaskService(tasksDir);
370
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Template loading utilities for CursorFlow
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as https from 'https';
8
+ import * as http from 'http';
9
+ import * as logger from './logger';
10
+ import { safeJoin } from './path';
11
+ import { findProjectRoot } from './config';
12
+
13
+ /**
14
+ * Fetch remote template from URL
15
+ */
16
+ export async function fetchRemoteTemplate(url: string): Promise<any> {
17
+ return new Promise((resolve, reject) => {
18
+ const protocol = url.startsWith('https') ? https : http;
19
+
20
+ protocol.get(url, (res) => {
21
+ if (res.statusCode !== 200) {
22
+ reject(new Error(`Failed to fetch template from ${url}: Status ${res.statusCode}`));
23
+ return;
24
+ }
25
+
26
+ let data = '';
27
+ res.on('data', (chunk) => {
28
+ data += chunk;
29
+ });
30
+
31
+ res.on('end', () => {
32
+ try {
33
+ resolve(JSON.parse(data));
34
+ } catch (e) {
35
+ reject(new Error(`Failed to parse template JSON from ${url}: ${e}`));
36
+ }
37
+ });
38
+ }).on('error', (err) => {
39
+ reject(new Error(`Network error while fetching template from ${url}: ${err.message}`));
40
+ });
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Resolve template from various sources:
46
+ * 1. URL (starts with http:// or https://)
47
+ * 2. Built-in template (name without .json)
48
+ * 3. Local file path
49
+ */
50
+ export async function resolveTemplate(templatePath: string): Promise<any> {
51
+ // 1. Remote URL
52
+ if (templatePath.startsWith('http://') || templatePath.startsWith('https://')) {
53
+ logger.info(`Fetching remote template: ${templatePath}`);
54
+ return fetchRemoteTemplate(templatePath);
55
+ }
56
+
57
+ // 2. Built-in template
58
+ // Search in templates/ directory of the project root
59
+ try {
60
+ const projectRoot = findProjectRoot();
61
+ const builtInPath = safeJoin(projectRoot, 'templates', templatePath.endsWith('.json') ? templatePath : `${templatePath}.json`);
62
+ if (fs.existsSync(builtInPath)) {
63
+ logger.info(`Using built-in template: ${templatePath}`);
64
+ return JSON.parse(fs.readFileSync(builtInPath, 'utf8'));
65
+ }
66
+ } catch (e) {
67
+ // Ignore error if project root not found, try other methods
68
+ }
69
+
70
+ // Fallback for built-in templates relative to the module (for installed package)
71
+ const templatesDir = path.resolve(__dirname, '../../templates');
72
+ const templateFileName = templatePath.endsWith('.json') ? templatePath : `${templatePath}.json`;
73
+ const modulePath = safeJoin(templatesDir, templateFileName);
74
+ if (fs.existsSync(modulePath)) {
75
+ logger.info(`Using module template: ${templatePath}`);
76
+ return JSON.parse(fs.readFileSync(modulePath, 'utf8'));
77
+ }
78
+
79
+ // 3. Local file path
80
+ const localPath = safeJoin(process.cwd(), templatePath);
81
+ if (fs.existsSync(localPath)) {
82
+ logger.info(`Using local template: ${templatePath}`);
83
+ try {
84
+ return JSON.parse(fs.readFileSync(localPath, 'utf8'));
85
+ } catch (e) {
86
+ throw new Error(`Failed to parse local template ${templatePath}: ${e}`);
87
+ }
88
+ }
89
+
90
+ throw new Error(`Template not found: ${templatePath}. It must be a URL, a built-in template name, or a local file path.`);
91
+ }
92
+