@ryanfw/prompt-orchestration-pipeline 0.15.0 → 0.16.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.
- package/package.json +4 -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/orchestrator.js +0 -18
- package/src/core/pipeline-runner.js +0 -37
- package/src/core/status-writer.js +0 -18
- package/src/core/symlink-utils.js +0 -12
- package/src/core/task-runner.js +0 -23
- 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/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
|
@@ -105,21 +105,36 @@ export default function PromptPipelineDashboard() {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
108
|
+
const progressBanner =
|
|
109
|
+
runningJobs.length > 0 ? (
|
|
110
|
+
<Box
|
|
111
|
+
role="status"
|
|
112
|
+
aria-live="polite"
|
|
113
|
+
className="bg-blue-50 border-b border-blue-200"
|
|
114
|
+
>
|
|
115
|
+
<Flex
|
|
116
|
+
align="center"
|
|
117
|
+
gap="4"
|
|
118
|
+
className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-3"
|
|
119
|
+
>
|
|
120
|
+
<Text size="2">
|
|
121
|
+
{runningJobs.length} job{runningJobs.length !== 1 ? "s" : ""}{" "}
|
|
122
|
+
running
|
|
123
|
+
</Text>
|
|
124
|
+
<Progress
|
|
125
|
+
value={aggregateProgress}
|
|
126
|
+
variant="running"
|
|
127
|
+
className="flex-1"
|
|
128
|
+
/>
|
|
129
|
+
<Text size="2" weight="medium">
|
|
130
|
+
{aggregateProgress}%
|
|
131
|
+
</Text>
|
|
132
|
+
</Flex>
|
|
133
|
+
</Box>
|
|
134
|
+
) : null;
|
|
120
135
|
|
|
121
136
|
return (
|
|
122
|
-
<Layout title="Prompt Pipeline"
|
|
137
|
+
<Layout title="Prompt Pipeline" subheader={progressBanner}>
|
|
123
138
|
{error && (
|
|
124
139
|
<Box className="mb-4 rounded-md bg-yellow-50 p-3 border border-yellow-200">
|
|
125
140
|
<Text size="2" className="text-yellow-800">
|
|
@@ -41,6 +41,38 @@ export async function writeAnalysisFile(pipelinePath, taskName, analysisData) {
|
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Validate artifacts.reads and artifacts.writes are arrays
|
|
45
|
+
if (!Array.isArray(analysisData.artifacts.reads)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Invalid analysisData.artifacts.reads: expected an array but got ${typeof analysisData.artifacts.reads}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!Array.isArray(analysisData.artifacts.writes)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid analysisData.artifacts.writes: expected an array but got ${typeof analysisData.artifacts.writes}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate unresolvedReads and unresolvedWrites if present (must be arrays)
|
|
58
|
+
if (
|
|
59
|
+
analysisData.artifacts.unresolvedReads !== undefined &&
|
|
60
|
+
!Array.isArray(analysisData.artifacts.unresolvedReads)
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid analysisData.artifacts.unresolvedReads: expected an array but got ${typeof analysisData.artifacts.unresolvedReads}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
analysisData.artifacts.unresolvedWrites !== undefined &&
|
|
69
|
+
!Array.isArray(analysisData.artifacts.unresolvedWrites)
|
|
70
|
+
) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Invalid analysisData.artifacts.unresolvedWrites: expected an array but got ${typeof analysisData.artifacts.unresolvedWrites}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
44
76
|
if (!Array.isArray(analysisData.models)) {
|
|
45
77
|
throw new Error(
|
|
46
78
|
`Invalid analysisData.models: expected an array but got ${typeof analysisData.models}`
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { chat } from "../../llm/index.js";
|
|
2
|
+
|
|
3
|
+
const SYSTEM_PROMPT = `You are an expert code analyzer. Your task is to match a dynamic artifact reference in JavaScript code to one of the known artifact filenames.
|
|
4
|
+
|
|
5
|
+
Given:
|
|
6
|
+
1. The full task source code
|
|
7
|
+
2. A dynamic expression used as an argument to io.readArtifact() or io.writeArtifact()
|
|
8
|
+
3. The surrounding code context
|
|
9
|
+
4. A list of available artifact filenames
|
|
10
|
+
|
|
11
|
+
Analyze the code to determine what the dynamic expression likely evaluates to, then match it against the available artifacts.
|
|
12
|
+
|
|
13
|
+
Return your response as JSON with this structure:
|
|
14
|
+
{
|
|
15
|
+
"resolvedFileName": "matched-artifact.json" or null if no match,
|
|
16
|
+
"confidence": 0.0 to 1.0,
|
|
17
|
+
"reasoning": "Brief explanation of your analysis"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Guidelines:
|
|
21
|
+
- Look at variable assignments, function return values, and naming patterns
|
|
22
|
+
- Consider the stage name and surrounding context for clues
|
|
23
|
+
- Return confidence 0 if no reasonable match exists
|
|
24
|
+
- Only return high confidence (>=0.7) when there's strong evidence
|
|
25
|
+
- If multiple artifacts could match, choose the most likely one but reduce confidence`;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a dynamic artifact reference using LLM analysis.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} taskCode - The full task source code
|
|
31
|
+
* @param {object} unresolvedArtifact - The unresolved artifact reference
|
|
32
|
+
* @param {string} unresolvedArtifact.expression - The dynamic expression code
|
|
33
|
+
* @param {string} unresolvedArtifact.codeContext - Surrounding code context
|
|
34
|
+
* @param {string} unresolvedArtifact.stage - Stage name where the call occurs
|
|
35
|
+
* @param {string[]} availableArtifacts - List of known artifact filenames
|
|
36
|
+
* @returns {Promise<{resolvedFileName: string|null, confidence: number, reasoning: string}>}
|
|
37
|
+
*/
|
|
38
|
+
export async function resolveArtifactReference(
|
|
39
|
+
taskCode,
|
|
40
|
+
unresolvedArtifact,
|
|
41
|
+
availableArtifacts
|
|
42
|
+
) {
|
|
43
|
+
const { expression, codeContext, stage } = unresolvedArtifact;
|
|
44
|
+
|
|
45
|
+
const userPrompt = `Task source code:
|
|
46
|
+
\`\`\`javascript
|
|
47
|
+
${taskCode}
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
Dynamic expression: ${expression}
|
|
51
|
+
Stage: ${stage}
|
|
52
|
+
Code context:
|
|
53
|
+
\`\`\`javascript
|
|
54
|
+
${codeContext}
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
Available artifact filenames:
|
|
58
|
+
${availableArtifacts.map((a) => `- ${a}`).join("\n")}
|
|
59
|
+
|
|
60
|
+
Analyze the code and determine which artifact this expression likely refers to.`;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const response = await chat({
|
|
64
|
+
provider: "deepseek",
|
|
65
|
+
messages: [
|
|
66
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
67
|
+
{ role: "user", content: userPrompt },
|
|
68
|
+
],
|
|
69
|
+
temperature: 0,
|
|
70
|
+
responseFormat: "json_object",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const parsed = JSON.parse(response.content);
|
|
74
|
+
|
|
75
|
+
const rawResolvedFileName =
|
|
76
|
+
typeof parsed.resolvedFileName === "string" ? parsed.resolvedFileName : null;
|
|
77
|
+
const resolvedFileName =
|
|
78
|
+
rawResolvedFileName && availableArtifacts.includes(rawResolvedFileName)
|
|
79
|
+
? rawResolvedFileName
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
const rawConfidence =
|
|
83
|
+
typeof parsed.confidence === "number" ? parsed.confidence : 0;
|
|
84
|
+
const confidence = resolvedFileName === null ? 0 : rawConfidence;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
resolvedFileName,
|
|
88
|
+
confidence,
|
|
89
|
+
reasoning: parsed.reasoning ?? "",
|
|
90
|
+
};
|
|
91
|
+
} catch {
|
|
92
|
+
return {
|
|
93
|
+
resolvedFileName: null,
|
|
94
|
+
confidence: 0,
|
|
95
|
+
reasoning: "Failed to analyze artifact reference",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -7,15 +7,39 @@ const generate =
|
|
|
7
7
|
import * as t from "@babel/types";
|
|
8
8
|
import { isInsideTryCatch, getStageName } from "../utils/ast.js";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extract surrounding code context for a node.
|
|
12
|
+
*
|
|
13
|
+
* @param {import("@babel/traverse").NodePath} path - The Babel path
|
|
14
|
+
* @param {string} sourceCode - The original source code
|
|
15
|
+
* @returns {string} Up to 5 lines of surrounding context (2 lines before and 2 lines after the target line)
|
|
16
|
+
*/
|
|
17
|
+
export function extractCodeContext(path, sourceCode) {
|
|
18
|
+
if (!sourceCode || !path.node.loc) {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const lines = sourceCode.split("\n");
|
|
23
|
+
const nodeLine = path.node.loc.start.line;
|
|
24
|
+
|
|
25
|
+
// Get 2 lines before and 2 lines after (5 lines total, 1-indexed)
|
|
26
|
+
const startLine = Math.max(1, nodeLine - 2);
|
|
27
|
+
const endLine = Math.min(lines.length, nodeLine + 2);
|
|
28
|
+
|
|
29
|
+
return lines.slice(startLine - 1, endLine).join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
/**
|
|
11
33
|
* Extract io.readArtifact calls from the AST.
|
|
12
34
|
*
|
|
13
35
|
* @param {import("@babel/types").File} ast - The AST to analyze
|
|
14
|
-
* @
|
|
36
|
+
* @param {string} [sourceCode] - The original source code (for extracting context)
|
|
37
|
+
* @returns {{reads: Array<{fileName: string, stage: string, required: boolean}>, unresolvedReads: Array<{expression: string, codeContext: string, stage: string, required: boolean, location: {line: number, column: number}}>}} Artifact read references
|
|
15
38
|
* @throws {Error} If io.readArtifact call is found outside an exported function
|
|
16
39
|
*/
|
|
17
|
-
export function extractArtifactReads(ast) {
|
|
40
|
+
export function extractArtifactReads(ast, sourceCode) {
|
|
18
41
|
const reads = [];
|
|
42
|
+
const unresolvedReads = [];
|
|
19
43
|
|
|
20
44
|
traverse(ast, {
|
|
21
45
|
CallExpression(path) {
|
|
@@ -27,15 +51,6 @@ export function extractArtifactReads(ast) {
|
|
|
27
51
|
t.isIdentifier(callee.object, { name: "io" }) &&
|
|
28
52
|
t.isIdentifier(callee.property, { name: "readArtifact" })
|
|
29
53
|
) {
|
|
30
|
-
// Extract fileName from first argument
|
|
31
|
-
const fileName = extractFileName(path.node.arguments[0]);
|
|
32
|
-
|
|
33
|
-
if (!fileName) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`io.readArtifact requires a string literal or template literal argument at ${path.node.loc?.start?.line}:${path.node.loc?.start?.column}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
54
|
// Get the stage name (must be in an exported function)
|
|
40
55
|
const stage = getStageName(path);
|
|
41
56
|
|
|
@@ -48,23 +63,46 @@ export function extractArtifactReads(ast) {
|
|
|
48
63
|
// Check if inside try/catch to determine if required
|
|
49
64
|
const required = !isInsideTryCatch(path);
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
// Extract fileName from first argument
|
|
67
|
+
const fileName = extractFileName(path.node.arguments[0]);
|
|
68
|
+
|
|
69
|
+
if (fileName) {
|
|
70
|
+
reads.push({ fileName, stage, required });
|
|
71
|
+
} else if (path.node.arguments[0]) {
|
|
72
|
+
// Capture unresolved reference
|
|
73
|
+
const argNode = path.node.arguments[0];
|
|
74
|
+
const expression = generate(argNode, { concise: true }).code;
|
|
75
|
+
const codeContext = extractCodeContext(path, sourceCode);
|
|
76
|
+
const location = {
|
|
77
|
+
line: argNode.loc?.start?.line ?? 0,
|
|
78
|
+
column: argNode.loc?.start?.column ?? 0,
|
|
79
|
+
};
|
|
80
|
+
unresolvedReads.push({
|
|
81
|
+
expression,
|
|
82
|
+
codeContext,
|
|
83
|
+
stage,
|
|
84
|
+
required,
|
|
85
|
+
location,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
52
88
|
}
|
|
53
89
|
},
|
|
54
90
|
});
|
|
55
91
|
|
|
56
|
-
return reads;
|
|
92
|
+
return { reads, unresolvedReads };
|
|
57
93
|
}
|
|
58
94
|
|
|
59
95
|
/**
|
|
60
96
|
* Extract io.writeArtifact calls from the AST.
|
|
61
97
|
*
|
|
62
98
|
* @param {import("@babel/types").File} ast - The AST to analyze
|
|
63
|
-
* @
|
|
99
|
+
* @param {string} [sourceCode] - The original source code (for extracting context)
|
|
100
|
+
* @returns {{writes: Array<{fileName: string, stage: string}>, unresolvedWrites: Array<{expression: string, codeContext: string, stage: string, location: {line: number, column: number}}>}} Artifact write references
|
|
64
101
|
* @throws {Error} If io.writeArtifact call is found outside an exported function
|
|
65
102
|
*/
|
|
66
|
-
export function extractArtifactWrites(ast) {
|
|
103
|
+
export function extractArtifactWrites(ast, sourceCode) {
|
|
67
104
|
const writes = [];
|
|
105
|
+
const unresolvedWrites = [];
|
|
68
106
|
|
|
69
107
|
traverse(ast, {
|
|
70
108
|
CallExpression(path) {
|
|
@@ -76,15 +114,6 @@ export function extractArtifactWrites(ast) {
|
|
|
76
114
|
t.isIdentifier(callee.object, { name: "io" }) &&
|
|
77
115
|
t.isIdentifier(callee.property, { name: "writeArtifact" })
|
|
78
116
|
) {
|
|
79
|
-
// Extract fileName from first argument
|
|
80
|
-
const fileName = extractFileName(path.node.arguments[0]);
|
|
81
|
-
|
|
82
|
-
if (!fileName) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`io.writeArtifact requires a string literal or template literal argument at ${path.node.loc?.start?.line}:${path.node.loc?.start?.column}`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
117
|
// Get the stage name (must be in an exported function)
|
|
89
118
|
const stage = getStageName(path);
|
|
90
119
|
|
|
@@ -94,12 +123,27 @@ export function extractArtifactWrites(ast) {
|
|
|
94
123
|
);
|
|
95
124
|
}
|
|
96
125
|
|
|
97
|
-
|
|
126
|
+
// Extract fileName from first argument
|
|
127
|
+
const fileName = extractFileName(path.node.arguments[0]);
|
|
128
|
+
|
|
129
|
+
if (fileName) {
|
|
130
|
+
writes.push({ fileName, stage });
|
|
131
|
+
} else if (path.node.arguments[0]) {
|
|
132
|
+
// Capture unresolved reference
|
|
133
|
+
const argNode = path.node.arguments[0];
|
|
134
|
+
const expression = generate(argNode, { concise: true }).code;
|
|
135
|
+
const codeContext = extractCodeContext(path, sourceCode);
|
|
136
|
+
const location = {
|
|
137
|
+
line: argNode.loc?.start?.line ?? 0,
|
|
138
|
+
column: argNode.loc?.start?.column ?? 0,
|
|
139
|
+
};
|
|
140
|
+
unresolvedWrites.push({ expression, codeContext, stage, location });
|
|
141
|
+
}
|
|
98
142
|
}
|
|
99
143
|
},
|
|
100
144
|
});
|
|
101
145
|
|
|
102
|
-
return writes;
|
|
146
|
+
return { writes, unresolvedWrites };
|
|
103
147
|
}
|
|
104
148
|
|
|
105
149
|
/**
|
|
@@ -49,8 +49,8 @@ export function analyzeTask(code, taskFilePath = null) {
|
|
|
49
49
|
|
|
50
50
|
// Extract all metadata from the AST
|
|
51
51
|
const stages = extractStages(ast);
|
|
52
|
-
const reads = extractArtifactReads(ast);
|
|
53
|
-
const writes = extractArtifactWrites(ast);
|
|
52
|
+
const { reads, unresolvedReads } = extractArtifactReads(ast, code);
|
|
53
|
+
const { writes, unresolvedWrites } = extractArtifactWrites(ast, code);
|
|
54
54
|
const models = extractLLMCalls(ast);
|
|
55
55
|
|
|
56
56
|
// Compose into the TaskAnalysis object
|
|
@@ -60,6 +60,8 @@ export function analyzeTask(code, taskFilePath = null) {
|
|
|
60
60
|
artifacts: {
|
|
61
61
|
reads,
|
|
62
62
|
writes,
|
|
63
|
+
unresolvedReads,
|
|
64
|
+
unresolvedWrites,
|
|
63
65
|
},
|
|
64
66
|
models,
|
|
65
67
|
};
|