@ryanfw/prompt-orchestration-pipeline 0.15.1 → 0.16.1
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/package.json +5 -1
- package/src/api/validators/json.js +3 -1
- package/src/components/Layout.jsx +4 -0
- package/src/components/TaskCreationSidebar.jsx +198 -47
- package/src/components/ui/CopyableCode.jsx +110 -0
- package/src/core/batch-runner.js +277 -0
- package/src/core/file-io.js +37 -1
- package/src/core/validation.js +3 -1
- package/src/pages/Code.jsx +538 -272
- package/src/pages/PromptPipelineDashboard.jsx +28 -13
- package/src/task-analysis/enrichers/analysis-writer.js +32 -0
- package/src/task-analysis/enrichers/artifact-resolver.js +98 -0
- package/src/task-analysis/enrichers/schema-deducer.js +3 -1
- package/src/task-analysis/extractors/artifacts.js +70 -26
- package/src/task-analysis/index.js +4 -2
- package/src/ui/dist/assets/{index-B5HMRkR9.js → index-DI_nRqVI.js} +3271 -397
- package/src/ui/dist/assets/index-DI_nRqVI.js.map +1 -0
- package/src/ui/dist/assets/style-CVd3RRU2.css +180 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/pipeline-analysis-endpoint.js +59 -0
- package/src/ui/endpoints/pipeline-artifacts-endpoint.js +109 -0
- package/src/ui/express-app.js +4 -0
- package/src/ui/watcher.js +20 -10
- package/src/ui/dist/assets/index-B5HMRkR9.js.map +0 -1
- package/src/ui/dist/assets/style-CoM9SoQF.css +0 -180
package/src/ui/dist/index.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
/>
|
|
12
12
|
<title>Prompt Pipeline Dashboard</title>
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/style-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-DI_nRqVI.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/style-CVd3RRU2.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
|
@@ -7,6 +7,7 @@ import { analyzeTask } from "../../task-analysis/index.js";
|
|
|
7
7
|
import { writeAnalysisFile } from "../../task-analysis/enrichers/analysis-writer.js";
|
|
8
8
|
import { deduceArtifactSchema } from "../../task-analysis/enrichers/schema-deducer.js";
|
|
9
9
|
import { writeSchemaFiles } from "../../task-analysis/enrichers/schema-writer.js";
|
|
10
|
+
import { resolveArtifactReference } from "../../task-analysis/enrichers/artifact-resolver.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Handle pipeline analysis endpoint.
|
|
@@ -134,6 +135,11 @@ export async function handlePipelineAnalysis(req, res) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// Collect all known artifact filenames from writes for LLM resolution
|
|
139
|
+
const allKnownArtifacts = taskAnalyses.flatMap((t) =>
|
|
140
|
+
t.analysis.artifacts.writes.map((w) => w.fileName)
|
|
141
|
+
);
|
|
142
|
+
|
|
137
143
|
// Send started event
|
|
138
144
|
stream.send("started", {
|
|
139
145
|
pipelineSlug: slug,
|
|
@@ -167,6 +173,59 @@ export async function handlePipelineAnalysis(req, res) {
|
|
|
167
173
|
return;
|
|
168
174
|
}
|
|
169
175
|
|
|
176
|
+
// Resolve unresolved artifact references using LLM
|
|
177
|
+
const unresolvedReads = analysis.artifacts.unresolvedReads || [];
|
|
178
|
+
const unresolvedWrites = analysis.artifacts.unresolvedWrites || [];
|
|
179
|
+
|
|
180
|
+
for (const unresolved of unresolvedReads) {
|
|
181
|
+
try {
|
|
182
|
+
const resolution = await resolveArtifactReference(
|
|
183
|
+
taskCode,
|
|
184
|
+
unresolved,
|
|
185
|
+
allKnownArtifacts
|
|
186
|
+
);
|
|
187
|
+
if (resolution.confidence >= 0.7 && resolution.resolvedFileName) {
|
|
188
|
+
// Check if this fileName already exists in reads array
|
|
189
|
+
const alreadyExists = analysis.artifacts.reads.some(
|
|
190
|
+
(artifact) => artifact.fileName === resolution.resolvedFileName
|
|
191
|
+
);
|
|
192
|
+
if (!alreadyExists) {
|
|
193
|
+
analysis.artifacts.reads.push({
|
|
194
|
+
fileName: resolution.resolvedFileName,
|
|
195
|
+
stage: unresolved.stage,
|
|
196
|
+
required: unresolved.required,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Silently skip failed resolutions
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const unresolved of unresolvedWrites) {
|
|
206
|
+
try {
|
|
207
|
+
const resolution = await resolveArtifactReference(
|
|
208
|
+
taskCode,
|
|
209
|
+
unresolved,
|
|
210
|
+
allKnownArtifacts
|
|
211
|
+
);
|
|
212
|
+
if (resolution.confidence >= 0.7 && resolution.resolvedFileName) {
|
|
213
|
+
// Check if this fileName already exists in writes array
|
|
214
|
+
const alreadyExists = analysis.artifacts.writes.some(
|
|
215
|
+
(artifact) => artifact.fileName === resolution.resolvedFileName
|
|
216
|
+
);
|
|
217
|
+
if (!alreadyExists) {
|
|
218
|
+
analysis.artifacts.writes.push({
|
|
219
|
+
fileName: resolution.resolvedFileName,
|
|
220
|
+
stage: unresolved.stage,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// Silently skip failed resolutions
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
170
229
|
// Process each artifact write
|
|
171
230
|
const artifacts = analysis.artifacts.writes;
|
|
172
231
|
let jsonArtifactIndex = 0;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline artifacts endpoint
|
|
3
|
+
*
|
|
4
|
+
* Reads all *.analysis.json files, aggregates artifacts.writes,
|
|
5
|
+
* returns de-duplicated list.
|
|
6
|
+
*
|
|
7
|
+
* Response format: { ok: true, artifacts: [{ fileName, sources: [{ taskName, stage }] }] }
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getPipelineConfig } from "../../core/config.js";
|
|
11
|
+
import { createErrorResponse, Constants } from "../config-bridge.js";
|
|
12
|
+
import { promises as fs } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* HTTP handler for GET /api/pipelines/:slug/artifacts
|
|
17
|
+
*
|
|
18
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
19
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
20
|
+
*/
|
|
21
|
+
export async function handlePipelineArtifacts(req, res) {
|
|
22
|
+
const { slug } = req.params;
|
|
23
|
+
|
|
24
|
+
// Validate slug parameter
|
|
25
|
+
if (!slug || typeof slug !== "string") {
|
|
26
|
+
res
|
|
27
|
+
.status(400)
|
|
28
|
+
.json(
|
|
29
|
+
createErrorResponse(
|
|
30
|
+
Constants.ERROR_CODES.BAD_REQUEST,
|
|
31
|
+
"Invalid slug parameter"
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Enforce safe characters in slug
|
|
38
|
+
const slugIsValid = /^[A-Za-z0-9_-]+$/.test(slug);
|
|
39
|
+
if (!slugIsValid) {
|
|
40
|
+
res
|
|
41
|
+
.status(400)
|
|
42
|
+
.json(
|
|
43
|
+
createErrorResponse(
|
|
44
|
+
Constants.ERROR_CODES.BAD_REQUEST,
|
|
45
|
+
"Invalid slug: only letters, numbers, hyphens, and underscores allowed"
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get pipeline config
|
|
52
|
+
let pipelineConfig;
|
|
53
|
+
try {
|
|
54
|
+
pipelineConfig = getPipelineConfig(slug);
|
|
55
|
+
} catch {
|
|
56
|
+
res
|
|
57
|
+
.status(404)
|
|
58
|
+
.json(
|
|
59
|
+
createErrorResponse(
|
|
60
|
+
Constants.ERROR_CODES.NOT_FOUND,
|
|
61
|
+
`Pipeline '${slug}' not found`
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Determine analysis directory path
|
|
68
|
+
const pipelineDir = path.dirname(pipelineConfig.pipelineJsonPath);
|
|
69
|
+
const analysisDir = path.join(pipelineDir, "analysis");
|
|
70
|
+
|
|
71
|
+
// Check if analysis directory exists
|
|
72
|
+
try {
|
|
73
|
+
await fs.access(analysisDir);
|
|
74
|
+
} catch {
|
|
75
|
+
// No analysis directory - return empty artifacts
|
|
76
|
+
res.status(200).json({ ok: true, artifacts: [] });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Read all *.analysis.json files
|
|
81
|
+
const files = await fs.readdir(analysisDir);
|
|
82
|
+
const analysisFiles = files.filter((f) => f.endsWith(".analysis.json"));
|
|
83
|
+
|
|
84
|
+
// Aggregate artifacts with de-duplication
|
|
85
|
+
const artifactMap = new Map();
|
|
86
|
+
|
|
87
|
+
for (const file of analysisFiles) {
|
|
88
|
+
try {
|
|
89
|
+
const content = await fs.readFile(path.join(analysisDir, file), "utf8");
|
|
90
|
+
const analysis = JSON.parse(content);
|
|
91
|
+
const taskId = analysis.taskId || file.replace(".analysis.json", "");
|
|
92
|
+
const writes = analysis.artifacts?.writes || [];
|
|
93
|
+
|
|
94
|
+
for (const write of writes) {
|
|
95
|
+
const { fileName, stage } = write;
|
|
96
|
+
if (!artifactMap.has(fileName)) {
|
|
97
|
+
artifactMap.set(fileName, { fileName, sources: [] });
|
|
98
|
+
}
|
|
99
|
+
artifactMap.get(fileName).sources.push({ taskName: taskId, stage });
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Skip malformed files
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const artifacts = Array.from(artifactMap.values());
|
|
108
|
+
res.status(200).json({ ok: true, artifacts });
|
|
109
|
+
}
|
package/src/ui/express-app.js
CHANGED
|
@@ -24,6 +24,7 @@ import { handlePipelineTypeDetailRequest } from "./endpoints/pipeline-type-detai
|
|
|
24
24
|
import { handlePipelineAnalysis } from "./endpoints/pipeline-analysis-endpoint.js";
|
|
25
25
|
import { handleTaskAnalysisRequest } from "./endpoints/task-analysis-endpoint.js";
|
|
26
26
|
import { handleSchemaFileRequest } from "./endpoints/schema-file-endpoint.js";
|
|
27
|
+
import { handlePipelineArtifacts } from "./endpoints/pipeline-artifacts-endpoint.js";
|
|
27
28
|
import { handleTaskPlan } from "./endpoints/task-creation-endpoint.js";
|
|
28
29
|
import { handleTaskSave } from "./endpoints/task-save-endpoint.js";
|
|
29
30
|
import { sendJson } from "./utils/http-utils.js";
|
|
@@ -130,6 +131,9 @@ export function buildExpressApp({ dataDir, viteServer }) {
|
|
|
130
131
|
await handlePipelineTypeDetailRequest(req, res);
|
|
131
132
|
});
|
|
132
133
|
|
|
134
|
+
// GET /api/pipelines/:slug/artifacts
|
|
135
|
+
app.get("/api/pipelines/:slug/artifacts", handlePipelineArtifacts);
|
|
136
|
+
|
|
133
137
|
// POST /api/pipelines/:slug/analyze
|
|
134
138
|
app.post("/api/pipelines/:slug/analyze", handlePipelineAnalysis);
|
|
135
139
|
|
package/src/ui/watcher.js
CHANGED
|
@@ -7,6 +7,9 @@ import chokidar from "chokidar";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { detectJobChange } from "./job-change-detector.js";
|
|
9
9
|
import { sseEnhancer } from "./sse-enhancer.js";
|
|
10
|
+
import { createLogger } from "../core/logger.js";
|
|
11
|
+
|
|
12
|
+
const logger = createLogger("Watcher");
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Normalize path separators to forward slash and trim
|
|
@@ -69,17 +72,17 @@ export function start(paths, onChange, options = {}) {
|
|
|
69
72
|
// Always use relative path for consistency with tests
|
|
70
73
|
const normalizedPath = rel;
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
logger.debug("File added:", normalizedPath);
|
|
73
76
|
|
|
74
77
|
// Detect registry.json changes and reload config
|
|
75
78
|
if (normalizedPath === "pipeline-config/registry.json") {
|
|
76
|
-
|
|
79
|
+
logger.log("registry.json added, reloading config...");
|
|
77
80
|
try {
|
|
78
81
|
const { resetConfig } = await import("../core/config.js");
|
|
79
82
|
resetConfig();
|
|
80
|
-
|
|
83
|
+
logger.log("Config cache invalidated successfully");
|
|
81
84
|
} catch (error) {
|
|
82
|
-
|
|
85
|
+
logger.error("Failed to reload config:", error);
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -89,7 +92,7 @@ export function start(paths, onChange, options = {}) {
|
|
|
89
92
|
// Check for job-specific changes with normalized path
|
|
90
93
|
const jobChange = detectJobChange(normalizedPath);
|
|
91
94
|
if (jobChange) {
|
|
92
|
-
|
|
95
|
+
logger.debug("Job change detected:", jobChange);
|
|
93
96
|
sseEnhancer.handleJobChange(jobChange);
|
|
94
97
|
}
|
|
95
98
|
});
|
|
@@ -100,17 +103,24 @@ export function start(paths, onChange, options = {}) {
|
|
|
100
103
|
// Always use relative path for consistency with tests
|
|
101
104
|
const normalizedPath = rel;
|
|
102
105
|
|
|
103
|
-
|
|
106
|
+
// Skip "modified" events for files under pipeline-data/.../files/
|
|
107
|
+
// (logs etc. are frequently updated but frontend only cares about creation)
|
|
108
|
+
if (/pipeline-data\/[^/]+\/[^/]+\/files\//.test(normalizedPath)) {
|
|
109
|
+
logger.debug("Skipping files/ modification:", normalizedPath);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.debug("File changed:", normalizedPath);
|
|
104
114
|
|
|
105
115
|
// Detect registry.json changes and reload config
|
|
106
116
|
if (normalizedPath === "pipeline-config/registry.json") {
|
|
107
|
-
|
|
117
|
+
logger.log("registry.json modified, reloading config...");
|
|
108
118
|
try {
|
|
109
119
|
const { resetConfig } = await import("../core/config.js");
|
|
110
120
|
resetConfig();
|
|
111
|
-
|
|
121
|
+
logger.log("Config cache invalidated successfully");
|
|
112
122
|
} catch (error) {
|
|
113
|
-
|
|
123
|
+
logger.error("Failed to reload config:", error);
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -120,7 +130,7 @@ export function start(paths, onChange, options = {}) {
|
|
|
120
130
|
// Check for job-specific changes with normalized path
|
|
121
131
|
const jobChange = detectJobChange(normalizedPath);
|
|
122
132
|
if (jobChange) {
|
|
123
|
-
|
|
133
|
+
logger.debug("Job change detected:", jobChange);
|
|
124
134
|
sseEnhancer.handleJobChange(jobChange);
|
|
125
135
|
}
|
|
126
136
|
});
|