@ryanfw/prompt-orchestration-pipeline 0.0.1 → 0.4.0

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 (76) hide show
  1. package/README.md +415 -24
  2. package/package.json +46 -8
  3. package/src/api/files.js +48 -0
  4. package/src/api/index.js +149 -53
  5. package/src/api/validators/seed.js +141 -0
  6. package/src/cli/index.js +444 -29
  7. package/src/cli/run-orchestrator.js +39 -0
  8. package/src/cli/update-pipeline-json.js +47 -0
  9. package/src/components/DAGGrid.jsx +649 -0
  10. package/src/components/JobCard.jsx +96 -0
  11. package/src/components/JobDetail.jsx +159 -0
  12. package/src/components/JobTable.jsx +202 -0
  13. package/src/components/Layout.jsx +134 -0
  14. package/src/components/TaskFilePane.jsx +570 -0
  15. package/src/components/UploadSeed.jsx +239 -0
  16. package/src/components/ui/badge.jsx +20 -0
  17. package/src/components/ui/button.jsx +43 -0
  18. package/src/components/ui/card.jsx +20 -0
  19. package/src/components/ui/focus-styles.css +60 -0
  20. package/src/components/ui/progress.jsx +26 -0
  21. package/src/components/ui/select.jsx +27 -0
  22. package/src/components/ui/separator.jsx +6 -0
  23. package/src/config/paths.js +99 -0
  24. package/src/core/config.js +270 -9
  25. package/src/core/file-io.js +202 -0
  26. package/src/core/module-loader.js +157 -0
  27. package/src/core/orchestrator.js +275 -294
  28. package/src/core/pipeline-runner.js +95 -41
  29. package/src/core/progress.js +66 -0
  30. package/src/core/status-writer.js +331 -0
  31. package/src/core/task-runner.js +719 -73
  32. package/src/core/validation.js +120 -1
  33. package/src/lib/utils.js +6 -0
  34. package/src/llm/README.md +139 -30
  35. package/src/llm/index.js +222 -72
  36. package/src/pages/PipelineDetail.jsx +111 -0
  37. package/src/pages/PromptPipelineDashboard.jsx +223 -0
  38. package/src/providers/deepseek.js +3 -15
  39. package/src/ui/client/adapters/job-adapter.js +258 -0
  40. package/src/ui/client/bootstrap.js +120 -0
  41. package/src/ui/client/hooks/useJobDetailWithUpdates.js +619 -0
  42. package/src/ui/client/hooks/useJobList.js +50 -0
  43. package/src/ui/client/hooks/useJobListWithUpdates.js +335 -0
  44. package/src/ui/client/hooks/useTicker.js +26 -0
  45. package/src/ui/client/index.css +31 -0
  46. package/src/ui/client/index.html +18 -0
  47. package/src/ui/client/main.jsx +38 -0
  48. package/src/ui/config-bridge.browser.js +149 -0
  49. package/src/ui/config-bridge.js +149 -0
  50. package/src/ui/config-bridge.node.js +310 -0
  51. package/src/ui/dist/assets/index-CxcrauYR.js +22702 -0
  52. package/src/ui/dist/assets/style-D6K_oQ12.css +62 -0
  53. package/src/ui/dist/index.html +19 -0
  54. package/src/ui/endpoints/job-endpoints.js +300 -0
  55. package/src/ui/file-reader.js +216 -0
  56. package/src/ui/job-change-detector.js +83 -0
  57. package/src/ui/job-index.js +231 -0
  58. package/src/ui/job-reader.js +274 -0
  59. package/src/ui/job-scanner.js +188 -0
  60. package/src/ui/public/app.js +3 -1
  61. package/src/ui/server.js +1636 -59
  62. package/src/ui/sse-enhancer.js +149 -0
  63. package/src/ui/sse.js +204 -0
  64. package/src/ui/state-snapshot.js +252 -0
  65. package/src/ui/transformers/list-transformer.js +347 -0
  66. package/src/ui/transformers/status-transformer.js +307 -0
  67. package/src/ui/watcher.js +61 -7
  68. package/src/utils/dag.js +101 -0
  69. package/src/utils/duration.js +126 -0
  70. package/src/utils/id-generator.js +30 -0
  71. package/src/utils/jobs.js +7 -0
  72. package/src/utils/pipelines.js +44 -0
  73. package/src/utils/task-files.js +271 -0
  74. package/src/utils/ui.jsx +76 -0
  75. package/src/ui/public/index.html +0 -53
  76. package/src/ui/public/style.css +0 -341
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Universal configuration bridge for UI helpers.
3
+ * Works in both browser and Node environments.
4
+ */
5
+
6
+ /**
7
+ * Global constants and contracts for project data display system
8
+ * @namespace Constants
9
+ */
10
+ export const Constants = {
11
+ /**
12
+ * Job ID validation regex
13
+ * @type {RegExp}
14
+ */
15
+ JOB_ID_REGEX: /^[A-Za-z0-9-_]+$/,
16
+
17
+ /**
18
+ * Valid task states
19
+ * @type {string[]}
20
+ */
21
+ TASK_STATES: ["pending", "running", "done", "failed"],
22
+
23
+ /**
24
+ * Valid job locations
25
+ * @type {string[]}
26
+ */
27
+ JOB_LOCATIONS: ["current", "complete"],
28
+
29
+ /**
30
+ * Status sort order (descending priority)
31
+ * @type {string[]}
32
+ */
33
+ STATUS_ORDER: ["running", "failed", "pending", "complete"],
34
+
35
+ /**
36
+ * File size limits for reading
37
+ * @type {Object}
38
+ */
39
+ FILE_LIMITS: {
40
+ MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
41
+ },
42
+
43
+ /**
44
+ * Retry configuration for atomic reads
45
+ * @type {Object}
46
+ */
47
+ RETRY_CONFIG: {
48
+ MAX_ATTEMPTS: 3,
49
+ DELAY_MS: 1000,
50
+ },
51
+
52
+ /**
53
+ * SSE debounce configuration
54
+ * @type {Object}
55
+ */
56
+ SSE_CONFIG: {
57
+ DEBOUNCE_MS: 200,
58
+ },
59
+
60
+ /**
61
+ * Error codes for structured error responses
62
+ * @type {Object}
63
+ */
64
+ ERROR_CODES: {
65
+ NOT_FOUND: "not_found",
66
+ INVALID_JSON: "invalid_json",
67
+ FS_ERROR: "fs_error",
68
+ JOB_NOT_FOUND: "job_not_found",
69
+ BAD_REQUEST: "bad_request",
70
+ },
71
+ };
72
+
73
+ /**
74
+ * Validates a job ID against the global contract
75
+ * @param {string} jobId - Job ID to validate
76
+ * @returns {boolean} True if valid
77
+ */
78
+ export function validateJobId(jobId) {
79
+ return Constants.JOB_ID_REGEX.test(jobId);
80
+ }
81
+
82
+ /**
83
+ * Validates a task state against the global contract
84
+ * @param {string} state - Task state to validate
85
+ * @returns {boolean} True if valid
86
+ */
87
+ export function validateTaskState(state) {
88
+ return Constants.TASK_STATES.includes(state);
89
+ }
90
+
91
+ /**
92
+ * Gets the status sort priority for a job status
93
+ * @param {string} status - Job status
94
+ * @returns {number} Sort priority (lower number = higher priority)
95
+ */
96
+ export function getStatusPriority(status) {
97
+ const index = Constants.STATUS_ORDER.indexOf(status);
98
+ return index === -1 ? Constants.STATUS_ORDER.length : index;
99
+ }
100
+
101
+ /**
102
+ * Determines job status based on task states
103
+ * @param {Object} tasks - Tasks object from tasks-status.json
104
+ * @returns {string} Job status
105
+ */
106
+ export function determineJobStatus(tasks = {}) {
107
+ const taskEntries = Object.entries(tasks);
108
+
109
+ if (taskEntries.length === 0) {
110
+ return "pending";
111
+ }
112
+
113
+ const taskStates = taskEntries.map(([_, task]) => task.state);
114
+
115
+ if (taskStates.includes("failed")) {
116
+ return "failed";
117
+ }
118
+
119
+ if (taskStates.includes("running")) {
120
+ return "running";
121
+ }
122
+
123
+ if (taskStates.every((state) => state === "done")) {
124
+ return "complete";
125
+ }
126
+
127
+ return "pending";
128
+ }
129
+
130
+ /**
131
+ * Creates a structured error response
132
+ * @param {string} code - Error code
133
+ * @param {string} message - Error message
134
+ * @param {string} [path] - Optional file path
135
+ * @returns {Object} Structured error object
136
+ */
137
+ export function createErrorResponse(code, message, path = null) {
138
+ const error = {
139
+ ok: false,
140
+ code,
141
+ message,
142
+ };
143
+
144
+ if (path) {
145
+ error.path = path;
146
+ }
147
+
148
+ return error;
149
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Node-specific configuration bridge for server-side UI helpers.
3
+ * This module contains filesystem and path utilities that rely on Node APIs.
4
+ * It should only be imported by server-side modules.
5
+ */
6
+
7
+ import path from "node:path";
8
+ import { promises as fs } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ /**
12
+ * Global constants and contracts for project data display system
13
+ * @namespace Constants
14
+ */
15
+ export const Constants = {
16
+ /**
17
+ * Job ID validation regex
18
+ * @type {RegExp}
19
+ */
20
+ JOB_ID_REGEX: /^[A-Za-z0-9-_]+$/,
21
+
22
+ /**
23
+ * Valid task states
24
+ * @type {string[]}
25
+ */
26
+ TASK_STATES: ["pending", "running", "done", "failed"],
27
+
28
+ /**
29
+ * Valid job locations
30
+ * @type {string[]}
31
+ */
32
+ JOB_LOCATIONS: ["current", "complete"],
33
+
34
+ /**
35
+ * Status sort order (descending priority)
36
+ * @type {string[]}
37
+ */
38
+ STATUS_ORDER: ["running", "failed", "pending", "complete"],
39
+
40
+ /**
41
+ * File size limits for reading
42
+ * @type {Object}
43
+ */
44
+ FILE_LIMITS: {
45
+ MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
46
+ },
47
+
48
+ /**
49
+ * Retry configuration for atomic reads
50
+ * @type {Object}
51
+ */
52
+ RETRY_CONFIG: {
53
+ MAX_ATTEMPTS: 3,
54
+ DELAY_MS: process.env.NODE_ENV === "test" ? 10 : 1000,
55
+ },
56
+
57
+ /**
58
+ * SSE debounce configuration
59
+ * @type {Object}
60
+ */
61
+ SSE_CONFIG: {
62
+ DEBOUNCE_MS: 200,
63
+ },
64
+
65
+ /**
66
+ * Error codes for structured error responses
67
+ * @type {Object}
68
+ */
69
+ ERROR_CODES: {
70
+ NOT_FOUND: "not_found",
71
+ INVALID_JSON: "invalid_json",
72
+ FS_ERROR: "fs_error",
73
+ JOB_NOT_FOUND: "job_not_found",
74
+ BAD_REQUEST: "bad_request",
75
+ },
76
+ };
77
+
78
+ // Get current directory for path resolution
79
+ const __filename = fileURLToPath(import.meta.url);
80
+ const __dirname = path.dirname(__filename);
81
+
82
+ /**
83
+ * Resolves pipeline data directory roots relative to project root
84
+ * @returns {Object} Object containing resolved directory paths (current/complete/pending/rejected)
85
+ */
86
+ export function resolvePipelinePaths(root = path.resolve(__dirname, "../..")) {
87
+ const projectRoot = root;
88
+
89
+ return {
90
+ current: path.join(projectRoot, "pipeline-data", "current"),
91
+ complete: path.join(projectRoot, "pipeline-data", "complete"),
92
+ pending: path.join(projectRoot, "pipeline-data", "pending"),
93
+ rejected: path.join(projectRoot, "pipeline-data", "rejected"),
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Gets absolute path to a job directory
99
+ * @param {string} jobId - Job ID
100
+ * @param {string} [location='current'] - Job location ('current' or 'complete')
101
+ * @returns {string} Absolute path to job directory
102
+ */
103
+ export function getJobPath(jobId, location = "current") {
104
+ if (!Constants.JOB_LOCATIONS.includes(location)) {
105
+ throw new Error(
106
+ `Invalid location: ${location}. Must be one of: ${Constants.JOB_LOCATIONS.join(", ")}`
107
+ );
108
+ }
109
+
110
+ if (!Constants.JOB_ID_REGEX.test(jobId)) {
111
+ throw new Error(
112
+ `Invalid job ID: ${jobId}. Must match ${Constants.JOB_ID_REGEX}`
113
+ );
114
+ }
115
+
116
+ const paths = resolvePipelinePaths();
117
+ return path.join(paths[location], jobId);
118
+ }
119
+
120
+ /**
121
+ * Gets path to tasks-status.json for a job
122
+ * @param {string} jobId - Job ID
123
+ * @param {string} [location='current'] - Job location
124
+ * @returns {string} Path to tasks-status.json
125
+ */
126
+ export function getTasksStatusPath(jobId, location = "current") {
127
+ const jobPath = getJobPath(jobId, location);
128
+ return path.join(jobPath, "tasks-status.json");
129
+ }
130
+
131
+ /**
132
+ * Gets path to seed.json for a job
133
+ * @param {string} jobId - Job ID
134
+ * @param {string} [location='current'] - Job location
135
+ * @returns {string} Path to seed.json
136
+ */
137
+ export function getSeedPath(jobId, location = "current") {
138
+ const jobPath = getJobPath(jobId, location);
139
+ return path.join(jobPath, "seed.json");
140
+ }
141
+
142
+ /**
143
+ * Gets path to a task directory
144
+ * @param {string} jobId - Job ID
145
+ * @param {string} taskName - Task name
146
+ * @param {string} [location='current'] - Job location
147
+ * @returns {string} Path to task directory
148
+ */
149
+ export function getTaskPath(jobId, taskName, location = "current") {
150
+ const jobPath = getJobPath(jobId, location);
151
+ return path.join(jobPath, "tasks", taskName);
152
+ }
153
+
154
+ /**
155
+ * Checks if a job directory is locked for writing
156
+ * @param {string} jobDir - Job directory path
157
+ * @returns {Promise<boolean>} True if locked
158
+ */
159
+ export async function isLocked(jobDir) {
160
+ try {
161
+ const entries = await fs.readdir(jobDir, { withFileTypes: true });
162
+
163
+ for (const entry of entries) {
164
+ if (entry.isFile() && entry.name.endsWith(".lock")) {
165
+ return true;
166
+ }
167
+
168
+ if (entry.isDirectory()) {
169
+ const subDirPath = path.join(jobDir, entry.name);
170
+ const subEntries = await fs.readdir(subDirPath, {
171
+ withFileTypes: true,
172
+ });
173
+
174
+ for (const subEntry of subEntries) {
175
+ if (subEntry.isFile() && subEntry.name.endsWith(".lock")) {
176
+ return true;
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ return false;
183
+ } catch (error) {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Creates a structured error response
190
+ * @param {string} code - Error code
191
+ * @param {string} message - Error message
192
+ * @param {string} [path] - Optional file path
193
+ * @returns {Object} Structured error object
194
+ */
195
+ export function createErrorResponse(code, message, path = null) {
196
+ const error = {
197
+ ok: false,
198
+ code,
199
+ message,
200
+ };
201
+
202
+ if (path) {
203
+ error.path = path;
204
+ }
205
+
206
+ return error;
207
+ }
208
+
209
+ /**
210
+ * Validates a job ID against the global contract
211
+ * @param {string} jobId - Job ID to validate
212
+ * @returns {boolean} True if valid
213
+ */
214
+ export function validateJobId(jobId) {
215
+ return Constants.JOB_ID_REGEX.test(jobId);
216
+ }
217
+
218
+ /**
219
+ * Validates a task state against the global contract
220
+ * @param {string} state - Task state to validate
221
+ * @returns {boolean} True if valid
222
+ */
223
+ export function validateTaskState(state) {
224
+ return Constants.TASK_STATES.includes(state);
225
+ }
226
+
227
+ /**
228
+ * Gets the status sort priority for a job status
229
+ * @param {string} status - Job status
230
+ * @returns {number} Sort priority (lower number = higher priority)
231
+ */
232
+ export function getStatusPriority(status) {
233
+ const index = Constants.STATUS_ORDER.indexOf(status);
234
+ return index === -1 ? Constants.STATUS_ORDER.length : index;
235
+ }
236
+
237
+ /**
238
+ * Determines job status based on task states
239
+ * @param {Object} tasks - Tasks object from tasks-status.json
240
+ * @returns {string} Job status
241
+ */
242
+ export function determineJobStatus(tasks = {}) {
243
+ const taskEntries = Object.entries(tasks);
244
+
245
+ if (taskEntries.length === 0) {
246
+ return "pending";
247
+ }
248
+
249
+ const taskStates = taskEntries.map(([_, task]) => task.state);
250
+
251
+ if (taskStates.includes("failed")) {
252
+ return "failed";
253
+ }
254
+
255
+ if (taskStates.includes("running")) {
256
+ return "running";
257
+ }
258
+
259
+ if (taskStates.every((state) => state === "done")) {
260
+ return "complete";
261
+ }
262
+
263
+ return "pending";
264
+ }
265
+
266
+ // Export helper to resolve paths lazily for server use
267
+ let _PATHS = null;
268
+
269
+ /**
270
+ * Initialize cached PATHS for a given project root.
271
+ * Callers should use this when they need PATHS tied to a specific root.
272
+ * Returns the resolved paths.
273
+ */
274
+ export function initPATHS(root) {
275
+ // If root is falsy, resolvePipelinePaths will use the default project root
276
+ _PATHS = resolvePipelinePaths(root || path.resolve(__dirname, "../.."));
277
+ return _PATHS;
278
+ }
279
+
280
+ /**
281
+ * Reset cached PATHS so future calls will re-resolve.
282
+ * Useful in tests or server code that needs to change the project root at runtime.
283
+ */
284
+ export function resetPATHS() {
285
+ _PATHS = null;
286
+ }
287
+
288
+ /**
289
+ * Get the cached PATHS. If a root argument is provided, re-initialize the cache
290
+ * for backward-compatible callers that pass a root to getPATHS(root).
291
+ *
292
+ * If not initialized, initialize with the PO_ROOT environment variable if available,
293
+ * otherwise use the default project root (two levels up from this file).
294
+ */
295
+ export function getPATHS(root) {
296
+ if (root) {
297
+ _PATHS = resolvePipelinePaths(root);
298
+ return _PATHS;
299
+ }
300
+ if (!_PATHS) {
301
+ // Use PO_ROOT environment variable if available, otherwise use default project root
302
+ const effectiveRoot =
303
+ process.env.PO_ROOT || path.resolve(__dirname, "../..");
304
+ _PATHS = resolvePipelinePaths(effectiveRoot);
305
+ }
306
+ return _PATHS;
307
+ }
308
+
309
+ // Convenience export for existing callsites
310
+ export const PATHS = getPATHS();