@ryanfw/prompt-orchestration-pipeline 1.3.2 → 1.3.4

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 (206) hide show
  1. package/docs/http-api.md +80 -4
  2. package/package.json +1 -4
  3. package/src/api/__tests__/index.test.ts +443 -32
  4. package/src/api/index.ts +108 -127
  5. package/src/cli/__tests__/analyze-task.test.ts +40 -7
  6. package/src/cli/__tests__/index.test.ts +337 -17
  7. package/src/cli/__tests__/types.test.ts +0 -18
  8. package/src/cli/__tests__/update-pipeline-json.test.ts +19 -9
  9. package/src/cli/analyze-task.ts +3 -14
  10. package/src/cli/index.ts +73 -61
  11. package/src/cli/types.ts +0 -13
  12. package/src/cli/update-pipeline-json.ts +3 -14
  13. package/src/config/__tests__/paths.test.ts +46 -1
  14. package/src/config/__tests__/pipeline-registry.test.ts +767 -0
  15. package/src/config/__tests__/sse-events.test.ts +470 -0
  16. package/src/config/__tests__/statuses.test.ts +41 -0
  17. package/src/config/paths.ts +11 -2
  18. package/src/config/pipeline-registry.ts +258 -0
  19. package/src/config/sse-events.ts +122 -0
  20. package/src/config/statuses.ts +20 -1
  21. package/src/core/__tests__/agent-step.test.ts +115 -0
  22. package/src/core/__tests__/config.test.ts +545 -105
  23. package/src/core/__tests__/core-boot-resolution.test.ts +181 -0
  24. package/src/core/__tests__/job-concurrency.test.ts +260 -0
  25. package/src/core/__tests__/job-submission.test.ts +396 -0
  26. package/src/core/__tests__/job-view.test.ts +1341 -0
  27. package/src/core/__tests__/json-file.test.ts +111 -0
  28. package/src/core/__tests__/lifecycle-policy.test.ts +81 -7
  29. package/src/core/__tests__/logger.test.ts +22 -0
  30. package/src/core/__tests__/orchestrator-no-deprecated-exports.test.ts +41 -0
  31. package/src/core/__tests__/orchestrator.test.ts +615 -2
  32. package/src/core/__tests__/pipeline-runner.test.ts +946 -9
  33. package/src/core/__tests__/redact.test.ts +153 -0
  34. package/src/core/__tests__/runner-liveness.test.ts +910 -0
  35. package/src/core/__tests__/seed-naming.test.ts +57 -0
  36. package/src/core/__tests__/single-derivation.test.ts +159 -0
  37. package/src/core/__tests__/task-runner.test.ts +594 -3
  38. package/src/core/__tests__/task-telemetry.test.ts +241 -0
  39. package/src/core/__tests__/workspace-root-resolution-guard.test.ts +62 -0
  40. package/src/core/agent-step.ts +4 -1
  41. package/src/core/agent-types.ts +5 -4
  42. package/src/core/config.ts +134 -222
  43. package/src/core/control.ts +3 -0
  44. package/src/core/job-concurrency.ts +56 -20
  45. package/src/core/job-submission.ts +133 -0
  46. package/src/core/job-view.ts +473 -0
  47. package/src/core/json-file.ts +45 -0
  48. package/src/core/lifecycle-policy.ts +23 -8
  49. package/src/core/logger.ts +0 -29
  50. package/src/core/orchestrator.ts +200 -74
  51. package/src/core/pipeline-runner.ts +85 -53
  52. package/src/core/redact.ts +40 -0
  53. package/src/core/runner-liveness.ts +280 -0
  54. package/src/core/seed-naming.ts +9 -0
  55. package/src/core/status-writer.ts +27 -33
  56. package/src/core/task-runner.ts +356 -319
  57. package/src/core/task-telemetry.ts +107 -0
  58. package/src/harness/__tests__/subprocess.test.ts +112 -1
  59. package/src/harness/subprocess.ts +55 -16
  60. package/src/llm/__tests__/index.test.ts +684 -33
  61. package/src/llm/index.ts +310 -67
  62. package/src/providers/__tests__/alibaba.test.ts +67 -6
  63. package/src/providers/__tests__/anthropic.test.ts +35 -14
  64. package/src/providers/__tests__/base.test.ts +62 -0
  65. package/src/providers/__tests__/claude-code.test.ts +99 -14
  66. package/src/providers/__tests__/deepseek.test.ts +16 -6
  67. package/src/providers/__tests__/gemini.test.ts +47 -25
  68. package/src/providers/__tests__/moonshot.test.ts +27 -0
  69. package/src/providers/__tests__/openai.test.ts +262 -74
  70. package/src/providers/__tests__/opencode.test.ts +77 -0
  71. package/src/providers/__tests__/request-timeout-contract.test.ts +226 -0
  72. package/src/providers/__tests__/stream-accumulator.test.ts +52 -0
  73. package/src/providers/__tests__/types.test.ts +85 -0
  74. package/src/providers/__tests__/zero-fill-guard.test.ts +62 -0
  75. package/src/providers/__tests__/zhipu.test.ts +27 -14
  76. package/src/providers/alibaba.ts +20 -39
  77. package/src/providers/anthropic.ts +23 -11
  78. package/src/providers/base.ts +19 -0
  79. package/src/providers/claude-code.ts +27 -18
  80. package/src/providers/deepseek.ts +9 -28
  81. package/src/providers/gemini.ts +20 -58
  82. package/src/providers/moonshot.ts +15 -11
  83. package/src/providers/openai.ts +79 -61
  84. package/src/providers/opencode.ts +16 -33
  85. package/src/providers/stream-accumulator.ts +27 -21
  86. package/src/providers/types.ts +29 -4
  87. package/src/providers/zhipu.ts +15 -44
  88. package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
  89. package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +34 -2
  90. package/src/task-analysis/__tests__/no-ast-imports.test.ts +52 -0
  91. package/src/task-analysis/__tests__/repository.test.ts +65 -0
  92. package/src/task-analysis/__tests__/types.test.ts +63 -130
  93. package/src/task-analysis/analyzer.ts +91 -0
  94. package/src/task-analysis/enrichers/schema-deducer.ts +13 -2
  95. package/src/task-analysis/index.ts +2 -36
  96. package/src/task-analysis/repository.ts +45 -0
  97. package/src/task-analysis/types.ts +42 -58
  98. package/src/ui/client/__tests__/api.test.ts +143 -1
  99. package/src/ui/client/__tests__/bootstrap.test.ts +178 -62
  100. package/src/ui/client/__tests__/job-adapter.test.ts +203 -2
  101. package/src/ui/client/__tests__/load-state.test.ts +70 -0
  102. package/src/ui/client/__tests__/types.test.ts +66 -3
  103. package/src/ui/client/__tests__/useJobDetailWithUpdates.test.ts +390 -77
  104. package/src/ui/client/__tests__/useJobList.test.ts +198 -23
  105. package/src/ui/client/__tests__/useJobListWithUpdates.test.ts +218 -7
  106. package/src/ui/client/adapters/__tests__/job-adapter.test.ts +186 -0
  107. package/src/ui/client/adapters/job-adapter.ts +41 -16
  108. package/src/ui/client/api.ts +38 -15
  109. package/src/ui/client/bootstrap.ts +19 -14
  110. package/src/ui/client/hooks/useJobDetailWithUpdates.ts +73 -97
  111. package/src/ui/client/hooks/useJobList.ts +26 -31
  112. package/src/ui/client/hooks/useJobListWithUpdates.ts +42 -76
  113. package/src/ui/client/load-state.ts +20 -0
  114. package/src/ui/client/reducers/__tests__/job-events.test.ts +568 -0
  115. package/src/ui/client/reducers/job-events.ts +137 -0
  116. package/src/ui/client/types.ts +16 -20
  117. package/src/ui/components/DAGGrid.tsx +6 -1
  118. package/src/ui/components/JobDetail.tsx +12 -4
  119. package/src/ui/components/JobTable.tsx +41 -13
  120. package/src/ui/components/StageTimeline.tsx +8 -20
  121. package/src/ui/components/TaskAnalysisDisplay.tsx +48 -6
  122. package/src/ui/components/__tests__/DAGGrid.test.tsx +36 -0
  123. package/src/ui/components/__tests__/JobDetail.test.tsx +112 -0
  124. package/src/ui/components/__tests__/JobTable.test.tsx +137 -1
  125. package/src/ui/components/__tests__/StageTimeline.test.tsx +5 -6
  126. package/src/ui/components/__tests__/TaskAnalysisDisplay.test.tsx +64 -0
  127. package/src/ui/components/types.ts +35 -15
  128. package/src/ui/dist/assets/{index--RH3sAt3.js → index-DN3-zvtP.js} +4324 -263
  129. package/src/ui/dist/assets/index-DN3-zvtP.js.map +1 -0
  130. package/src/ui/dist/assets/style-CtZBnjlR.css +2 -0
  131. package/src/ui/dist/index.html +2 -2
  132. package/src/ui/pages/PipelineDetail.tsx +60 -4
  133. package/src/ui/pages/PromptPipelineDashboard.tsx +59 -7
  134. package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +114 -32
  135. package/src/ui/pages/__tests__/pages.test.tsx +236 -42
  136. package/src/ui/server/__tests__/concurrency-endpoint.test.ts +54 -0
  137. package/src/ui/server/__tests__/config-bridge-node.test.ts +34 -1
  138. package/src/ui/server/__tests__/config-bridge.test.ts +9 -1
  139. package/src/ui/server/__tests__/embedded-assets.test.ts +66 -0
  140. package/src/ui/server/__tests__/file-endpoints.test.ts +183 -5
  141. package/src/ui/server/__tests__/gate-endpoints.test.ts +55 -0
  142. package/src/ui/server/__tests__/index.test.ts +63 -0
  143. package/src/ui/server/__tests__/job-control-endpoints.test.ts +512 -2
  144. package/src/ui/server/__tests__/job-endpoints.test.ts +158 -2
  145. package/src/ui/server/__tests__/read-static-path-guard.test.ts +94 -0
  146. package/src/ui/server/__tests__/router-root-isolation.test.ts +204 -0
  147. package/src/ui/server/__tests__/router-threading.test.ts +148 -0
  148. package/src/ui/server/__tests__/router.test.ts +104 -0
  149. package/src/ui/server/__tests__/sse-broadcast.test.ts +44 -0
  150. package/src/ui/server/__tests__/sse-enhancer.test.ts +70 -0
  151. package/src/ui/server/config-bridge-node.ts +8 -26
  152. package/src/ui/server/config-bridge.ts +3 -4
  153. package/src/ui/server/embedded-assets-imports.d.ts +44 -0
  154. package/src/ui/server/embedded-assets.ts +13 -2
  155. package/src/ui/server/endpoints/__tests__/meta-endpoint.test.ts +1 -0
  156. package/src/ui/server/endpoints/__tests__/pipeline-analysis-endpoint.test.ts +167 -0
  157. package/src/ui/server/endpoints/__tests__/schema-file-endpoint.test.ts +104 -0
  158. package/src/ui/server/endpoints/__tests__/task-analysis-endpoint.test.ts +103 -0
  159. package/src/ui/server/endpoints/__tests__/upload-endpoints.test.ts +162 -96
  160. package/src/ui/server/endpoints/concurrency-endpoint.ts +2 -1
  161. package/src/ui/server/endpoints/create-pipeline-endpoint.ts +14 -29
  162. package/src/ui/server/endpoints/file-endpoints.ts +15 -10
  163. package/src/ui/server/endpoints/gate-endpoints.ts +2 -6
  164. package/src/ui/server/endpoints/job-control-endpoints.ts +129 -141
  165. package/src/ui/server/endpoints/job-endpoints.ts +7 -0
  166. package/src/ui/server/endpoints/meta-endpoint.ts +1 -1
  167. package/src/ui/server/endpoints/pipeline-analysis-endpoint.ts +99 -11
  168. package/src/ui/server/endpoints/schema-file-endpoint.ts +11 -3
  169. package/src/ui/server/endpoints/task-analysis-endpoint.ts +21 -5
  170. package/src/ui/server/endpoints/task-save-endpoint.ts +1 -2
  171. package/src/ui/server/endpoints/upload-endpoints.ts +5 -40
  172. package/src/ui/server/index.ts +19 -14
  173. package/src/ui/server/job-reader.ts +14 -2
  174. package/src/ui/server/router.ts +33 -10
  175. package/src/ui/server/sse-broadcast.ts +14 -3
  176. package/src/ui/server/sse-enhancer.ts +12 -2
  177. package/src/ui/state/__tests__/schema-loader.test.ts +11 -1
  178. package/src/ui/state/__tests__/snapshot.test.ts +120 -14
  179. package/src/ui/state/__tests__/types.test.ts +104 -5
  180. package/src/ui/state/snapshot.ts +2 -2
  181. package/src/ui/state/transformers/__tests__/list-transformer.test.ts +85 -2
  182. package/src/ui/state/transformers/__tests__/status-transformer.test.ts +250 -22
  183. package/src/ui/state/transformers/list-transformer.ts +13 -3
  184. package/src/ui/state/transformers/status-transformer.ts +36 -170
  185. package/src/ui/state/types.ts +15 -48
  186. package/src/utils/__tests__/path-containment.test.ts +160 -0
  187. package/src/{ui/server/utils → utils}/path-containment.ts +21 -0
  188. package/src/task-analysis/__tests__/enrichers-analysis-writer.test.ts +0 -86
  189. package/src/task-analysis/__tests__/enrichers-artifact-resolver.test.ts +0 -124
  190. package/src/task-analysis/__tests__/extractors-artifacts.test.ts +0 -133
  191. package/src/task-analysis/__tests__/extractors-llm-calls.test.ts +0 -46
  192. package/src/task-analysis/__tests__/extractors-stages.test.ts +0 -52
  193. package/src/task-analysis/__tests__/index.test.ts +0 -143
  194. package/src/task-analysis/__tests__/parser.test.ts +0 -41
  195. package/src/task-analysis/__tests__/utils-ast.test.ts +0 -82
  196. package/src/task-analysis/enrichers/analysis-writer.ts +0 -75
  197. package/src/task-analysis/enrichers/artifact-resolver.ts +0 -89
  198. package/src/task-analysis/extractors/artifacts.ts +0 -143
  199. package/src/task-analysis/extractors/llm-calls.ts +0 -117
  200. package/src/task-analysis/extractors/stages.ts +0 -45
  201. package/src/task-analysis/parser.ts +0 -20
  202. package/src/task-analysis/utils/ast.ts +0 -45
  203. package/src/ui/dist/assets/index--RH3sAt3.js.map +0 -1
  204. package/src/ui/dist/assets/style-CSSKuMOe.css +0 -2
  205. package/src/ui/embedded-assets.js +0 -12
  206. package/src/ui/server/__tests__/path-containment.test.ts +0 -54
@@ -1,75 +0,0 @@
1
- // ── src/task-analysis/enrichers/analysis-writer.ts ──
2
- // Persists TaskAnalysis to disk as JSON.
3
-
4
- import { mkdir } from "node:fs/promises";
5
- import path from "node:path";
6
- import type { TaskAnalysis } from "../types.ts";
7
-
8
- function validate(analysisData: TaskAnalysis): void {
9
- const { taskFilePath, stages, models, artifacts } = analysisData;
10
-
11
- if (taskFilePath === null || taskFilePath === undefined || taskFilePath === "") {
12
- throw new Error(
13
- `Invalid taskFilePath: expected a non-null, non-empty string, got ${taskFilePath === "" ? "empty string" : String(taskFilePath)}`,
14
- );
15
- }
16
-
17
- if (!Array.isArray(stages)) {
18
- throw new Error(
19
- `Invalid stages: expected an array, got ${typeof stages}`,
20
- );
21
- }
22
-
23
- if (!Array.isArray(models)) {
24
- throw new Error(
25
- `Invalid models: expected an array, got ${typeof models}`,
26
- );
27
- }
28
-
29
- if (
30
- typeof artifacts !== "object" ||
31
- artifacts === null ||
32
- !Array.isArray(artifacts.reads) ||
33
- !Array.isArray(artifacts.writes)
34
- ) {
35
- throw new Error(
36
- `Invalid artifacts: expected an object with reads and writes arrays`,
37
- );
38
- }
39
-
40
- if (
41
- "unresolvedReads" in artifacts &&
42
- !Array.isArray(artifacts.unresolvedReads)
43
- ) {
44
- throw new Error(
45
- `Invalid artifacts.unresolvedReads: expected an array, got ${typeof artifacts.unresolvedReads}`,
46
- );
47
- }
48
-
49
- if (
50
- "unresolvedWrites" in artifacts &&
51
- !Array.isArray(artifacts.unresolvedWrites)
52
- ) {
53
- throw new Error(
54
- `Invalid artifacts.unresolvedWrites: expected an array, got ${typeof artifacts.unresolvedWrites}`,
55
- );
56
- }
57
- }
58
-
59
- export async function writeAnalysisFile(
60
- pipelinePath: string,
61
- taskName: string,
62
- analysisData: TaskAnalysis,
63
- ): Promise<void> {
64
- validate(analysisData);
65
-
66
- const analysisDir = path.join(pipelinePath, "analysis");
67
- await mkdir(analysisDir, { recursive: true });
68
-
69
- const data = { ...analysisData, analyzedAt: new Date().toISOString() };
70
-
71
- await Bun.write(
72
- path.join(analysisDir, `${taskName}.analysis.json`),
73
- JSON.stringify(data, null, 2),
74
- );
75
- }
@@ -1,89 +0,0 @@
1
- // ── src/task-analysis/enrichers/artifact-resolver.ts ──
2
- // LLM-powered resolution of dynamic artifact references.
3
-
4
- import { chat } from "../../llm/index.ts";
5
- import type { UnresolvedArtifactDescriptor, ArtifactResolution } from "../types.ts";
6
-
7
- const FALLBACK: ArtifactResolution = {
8
- resolvedFileName: null,
9
- confidence: 0,
10
- reasoning: "Failed to analyze artifact reference",
11
- };
12
-
13
- function clampConfidence(value: unknown): number {
14
- if (typeof value !== "number" || !isFinite(value)) return 0;
15
- if (value < 0) return 0;
16
- if (value > 1) return 1;
17
- return value;
18
- }
19
-
20
- export async function resolveArtifactReference(
21
- taskCode: string,
22
- unresolvedArtifact: UnresolvedArtifactDescriptor,
23
- availableArtifacts: string[],
24
- ): Promise<ArtifactResolution> {
25
- try {
26
- const messages = [
27
- {
28
- role: "system" as const,
29
- content:
30
- "You are a code analysis expert. Given pipeline task source code, a dynamic artifact expression, and a list of available artifact filenames, determine which artifact filename the expression resolves to. Respond with a JSON object containing: resolvedFileName (string or null), confidence (number between 0 and 1), and reasoning (string).",
31
- },
32
- {
33
- role: "user" as const,
34
- content: `Analyze the following pipeline task source code and determine which artifact filename the dynamic expression resolves to.
35
-
36
- Task source code:
37
- \`\`\`
38
- ${taskCode}
39
- \`\`\`
40
-
41
- Dynamic expression: ${unresolvedArtifact.expression}
42
- Code context: ${unresolvedArtifact.codeContext}
43
- Stage: ${unresolvedArtifact.stage}
44
-
45
- Available artifact filenames:
46
- ${availableArtifacts.map((f) => `- ${f}`).join("\n")}
47
-
48
- Respond with a JSON object with these fields:
49
- - resolvedFileName: the matching filename from the available list, or null if you cannot determine it
50
- - confidence: a number between 0 and 1 indicating your confidence
51
- - reasoning: a string explaining your reasoning`,
52
- },
53
- ];
54
-
55
- const response = await chat({
56
- provider: "deepseek",
57
- model: "deepseek-chat",
58
- messages,
59
- temperature: 0,
60
- responseFormat: { type: "json_object" },
61
- });
62
-
63
- const { content } = response;
64
-
65
- if (typeof content !== "object" || content === null) {
66
- throw new Error(
67
- `Unexpected gateway response: content must be a non-null object, got ${typeof content}`,
68
- );
69
- }
70
-
71
- const { resolvedFileName, confidence, reasoning } = content as Record<string, unknown>;
72
-
73
- let sanitizedFileName = typeof resolvedFileName === "string" ? resolvedFileName : null;
74
- let sanitizedConfidence = clampConfidence(confidence);
75
-
76
- if (sanitizedFileName !== null && !availableArtifacts.includes(sanitizedFileName)) {
77
- sanitizedFileName = null;
78
- sanitizedConfidence = 0;
79
- }
80
-
81
- return {
82
- resolvedFileName: sanitizedFileName,
83
- confidence: sanitizedConfidence,
84
- reasoning: typeof reasoning === "string" ? reasoning : "",
85
- };
86
- } catch {
87
- return FALLBACK;
88
- }
89
- }
@@ -1,143 +0,0 @@
1
- import traverse from "@babel/traverse";
2
- import generate from "@babel/generator";
3
- import type { File as BabelFile } from "@babel/types";
4
- import type { NodePath } from "@babel/traverse";
5
- import type {
6
- ArtifactRead,
7
- ArtifactWrite,
8
- UnresolvedRead,
9
- UnresolvedWrite,
10
- } from "../types.ts";
11
- import { getStageName, isInsideTryCatch } from "../utils/ast.ts";
12
-
13
- export function extractCodeContext(
14
- path: NodePath,
15
- sourceCode: string
16
- ): string {
17
- if (!sourceCode || !path.node.loc) return "";
18
- const lines = sourceCode.split("\n");
19
- const line = path.node.loc.start.line - 1; // convert to 0-based
20
- const start = Math.max(0, line - 2);
21
- const end = Math.min(lines.length, line + 3);
22
- return lines.slice(start, end).join("\n");
23
- }
24
-
25
- function resolveFileName(
26
- argNode: import("@babel/types").Expression | import("@babel/types").SpreadElement
27
- ): string | null {
28
- if (argNode.type === "StringLiteral") {
29
- return argNode.value;
30
- }
31
- if (argNode.type === "TemplateLiteral") {
32
- if (argNode.expressions.length === 0) {
33
- return argNode.quasis.map((q) => q.value.cooked ?? "").join("");
34
- }
35
- // Template with expressions: generate source text, strip backticks
36
- const src = generate(argNode).code;
37
- return src.replace(/^`|`$/g, "");
38
- }
39
- return null;
40
- }
41
-
42
- export function extractArtifactReads(
43
- ast: BabelFile,
44
- sourceCode?: string
45
- ): { reads: ArtifactRead[]; unresolvedReads: UnresolvedRead[] } {
46
- const reads: ArtifactRead[] = [];
47
- const unresolvedReads: UnresolvedRead[] = [];
48
-
49
- traverse(ast, {
50
- CallExpression(path) {
51
- const { callee } = path.node;
52
- if (
53
- callee.type !== "MemberExpression" ||
54
- callee.object.type !== "Identifier" ||
55
- callee.object.name !== "io" ||
56
- callee.property.type !== "Identifier" ||
57
- callee.property.name !== "readArtifact"
58
- )
59
- return;
60
-
61
- const stage = getStageName(path);
62
- if (stage === null) {
63
- const loc = path.node.loc;
64
- throw new Error(
65
- `io.readArtifact call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
66
- );
67
- }
68
-
69
- const arg = path.node.arguments[0];
70
- if (!arg || arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") return;
71
-
72
- const required = !isInsideTryCatch(path);
73
- const fileName = resolveFileName(arg);
74
-
75
- if (fileName !== null) {
76
- reads.push({ fileName, stage, required });
77
- } else {
78
- const expression = generate(arg).code;
79
- const codeContext = sourceCode
80
- ? extractCodeContext(path, sourceCode)
81
- : "";
82
- const location = {
83
- line: arg.loc?.start.line ?? 0,
84
- column: arg.loc?.start.column ?? 0,
85
- };
86
- unresolvedReads.push({ expression, codeContext, stage, required, location });
87
- }
88
- },
89
- });
90
-
91
- return { reads, unresolvedReads };
92
- }
93
-
94
- export function extractArtifactWrites(
95
- ast: BabelFile,
96
- sourceCode?: string
97
- ): { writes: ArtifactWrite[]; unresolvedWrites: UnresolvedWrite[] } {
98
- const writes: ArtifactWrite[] = [];
99
- const unresolvedWrites: UnresolvedWrite[] = [];
100
-
101
- traverse(ast, {
102
- CallExpression(path) {
103
- const { callee } = path.node;
104
- if (
105
- callee.type !== "MemberExpression" ||
106
- callee.object.type !== "Identifier" ||
107
- callee.object.name !== "io" ||
108
- callee.property.type !== "Identifier" ||
109
- callee.property.name !== "writeArtifact"
110
- )
111
- return;
112
-
113
- const stage = getStageName(path);
114
- if (stage === null) {
115
- const loc = path.node.loc;
116
- throw new Error(
117
- `io.writeArtifact call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
118
- );
119
- }
120
-
121
- const arg = path.node.arguments[0];
122
- if (!arg || arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") return;
123
-
124
- const fileName = resolveFileName(arg);
125
-
126
- if (fileName !== null) {
127
- writes.push({ fileName, stage });
128
- } else {
129
- const expression = generate(arg).code;
130
- const codeContext = sourceCode
131
- ? extractCodeContext(path, sourceCode)
132
- : "";
133
- const location = {
134
- line: arg.loc?.start.line ?? 0,
135
- column: arg.loc?.start.column ?? 0,
136
- };
137
- unresolvedWrites.push({ expression, codeContext, stage, location });
138
- }
139
- },
140
- });
141
-
142
- return { writes, unresolvedWrites };
143
- }
@@ -1,117 +0,0 @@
1
- import traverse from "@babel/traverse";
2
- import type { NodePath } from "@babel/traverse";
3
- import type { File as BabelFile } from "@babel/types";
4
- import type { ModelCall } from "../types.ts";
5
- import { getStageName } from "../utils/ast.ts";
6
-
7
- function isLLMDirectAccess(
8
- path: NodePath
9
- ): { provider: string; method: string } | null {
10
- const { node } = path;
11
- if (node.type !== "CallExpression") return null;
12
- const { callee } = node;
13
- if (callee.type !== "MemberExpression") return null;
14
- if (callee.property.type !== "Identifier") return null;
15
-
16
- const method = callee.property.name;
17
- const obj = callee.object;
18
- if (obj.type !== "MemberExpression") return null;
19
- if (obj.object.type !== "Identifier") return null;
20
- if (obj.object.name !== "llm") return null;
21
- if (obj.property.type !== "Identifier") return null;
22
-
23
- return { provider: obj.property.name, method };
24
- }
25
-
26
- function isLLMDestructuredAccess(
27
- path: NodePath
28
- ): { provider: string; method: string } | null {
29
- const { node } = path;
30
- if (node.type !== "CallExpression") return null;
31
- const { callee } = node;
32
- if (callee.type !== "MemberExpression") return null;
33
- if (callee.object.type !== "Identifier") return null;
34
- if (callee.property.type !== "Identifier") return null;
35
-
36
- const identName = callee.object.name;
37
- const method = callee.property.name;
38
-
39
- const binding = path.scope.getBinding(identName);
40
- if (!binding) return null;
41
-
42
- const bindingPath = binding.path;
43
-
44
- // Pattern 2: const { provider } = llm;
45
- if (bindingPath.node.type === "VariableDeclarator") {
46
- const { id, init } = bindingPath.node;
47
- if (
48
- init?.type === "Identifier" &&
49
- init.name === "llm" &&
50
- id.type === "ObjectPattern"
51
- ) {
52
- return { provider: identName, method };
53
- }
54
- return null;
55
- }
56
-
57
- // Pattern 3: ({ llm: { provider } }) => ...
58
- // Babel registers the binding for the destructured identifier at the outer
59
- // ObjectPattern level (the whole param pattern). We check that the outer
60
- // ObjectPattern has a property keyed "llm" whose value is an ObjectPattern
61
- // containing the identifier, and that the parent is a function node.
62
- if (bindingPath.node.type === "ObjectPattern") {
63
- const parentNode = bindingPath.parentPath?.node;
64
- const isParam =
65
- parentNode?.type === "FunctionDeclaration" ||
66
- parentNode?.type === "FunctionExpression" ||
67
- parentNode?.type === "ArrowFunctionExpression";
68
- if (!isParam) return null;
69
-
70
- const outerPattern = bindingPath.node;
71
- for (const prop of outerPattern.properties) {
72
- if (
73
- prop.type === "ObjectProperty" &&
74
- prop.key.type === "Identifier" &&
75
- prop.key.name === "llm" &&
76
- prop.value.type === "ObjectPattern"
77
- ) {
78
- const innerPattern = prop.value;
79
- const hasIdent = innerPattern.properties.some(
80
- (p) =>
81
- p.type === "ObjectProperty" &&
82
- p.key.type === "Identifier" &&
83
- p.key.name === identName
84
- );
85
- if (hasIdent) return { provider: identName, method };
86
- }
87
- }
88
- return null;
89
- }
90
-
91
- return null;
92
- }
93
-
94
- export function extractLLMCalls(ast: BabelFile): ModelCall[] {
95
- const calls: ModelCall[] = [];
96
-
97
- traverse(ast, {
98
- CallExpression(path) {
99
- const direct = isLLMDirectAccess(path);
100
- const destructured = direct === null ? isLLMDestructuredAccess(path) : null;
101
- const match = direct ?? destructured;
102
- if (!match) return;
103
-
104
- const stage = getStageName(path);
105
- if (stage === null) {
106
- const loc = path.node.loc;
107
- throw new Error(
108
- `LLM call at line ${loc?.start.line ?? "unknown"}, column ${loc?.start.column ?? "unknown"} is not inside an exported function`
109
- );
110
- }
111
-
112
- calls.push({ provider: match.provider, method: match.method, stage });
113
- },
114
- });
115
-
116
- return calls;
117
- }
@@ -1,45 +0,0 @@
1
- import traverse from "@babel/traverse";
2
- import type { File as BabelFile } from "@babel/types";
3
- import type { Stage } from "../types.ts";
4
-
5
- export function extractStages(ast: BabelFile): Stage[] {
6
- const stages: Stage[] = [];
7
-
8
- traverse(ast, {
9
- ExportNamedDeclaration(path) {
10
- const { declaration } = path.node;
11
- if (declaration == null) return;
12
-
13
- if (declaration.type === "FunctionDeclaration") {
14
- const name = declaration.id?.name;
15
- if (name == null) return;
16
- stages.push({
17
- name,
18
- order: path.node.loc?.start.line ?? 0,
19
- isAsync: declaration.async ?? false,
20
- });
21
- return;
22
- }
23
-
24
- if (declaration.type === "VariableDeclaration") {
25
- const declarator = declaration.declarations[0];
26
- if (declarator == null) return;
27
- if (declarator.id.type !== "Identifier") return;
28
- const init = declarator.init;
29
- if (
30
- init == null ||
31
- (init.type !== "ArrowFunctionExpression" &&
32
- init.type !== "FunctionExpression")
33
- )
34
- return;
35
- stages.push({
36
- name: declarator.id.name,
37
- order: path.node.loc?.start.line ?? 0,
38
- isAsync: init.async ?? false,
39
- });
40
- }
41
- },
42
- });
43
-
44
- return stages.sort((a, b) => a.order - b.order);
45
- }
@@ -1,20 +0,0 @@
1
- import { parse } from "@babel/parser";
2
- import type { File as BabelFile } from "@babel/types";
3
-
4
- export function parseTaskSource(code: string): BabelFile {
5
- try {
6
- return parse(code, { sourceType: "module", plugins: ["jsx"] });
7
- } catch (err) {
8
- const loc =
9
- err != null &&
10
- typeof err === "object" &&
11
- "loc" in err &&
12
- err.loc != null &&
13
- typeof err.loc === "object" &&
14
- "line" in err.loc &&
15
- "column" in err.loc
16
- ? `line ${err.loc.line}, column ${err.loc.column}`
17
- : "unknown location";
18
- throw new Error(`Syntax error at ${loc}`, { cause: err });
19
- }
20
- }
@@ -1,45 +0,0 @@
1
- import type { NodePath } from "@babel/traverse";
2
- import type {
3
- TryStatement,
4
- ExportNamedDeclaration,
5
- FunctionDeclaration,
6
- VariableDeclaration,
7
- } from "@babel/types";
8
-
9
- export function isInsideTryCatch(path: NodePath): boolean {
10
- let current: NodePath | null = path.parentPath;
11
- while (current !== null) {
12
- if (current.isTryStatement()) {
13
- const tryNode = current.node as TryStatement;
14
- const blockPath = current.get("block") as NodePath;
15
- if (blockPath.node === tryNode.block) {
16
- return true;
17
- }
18
- }
19
- current = current.parentPath;
20
- }
21
- return false;
22
- }
23
-
24
- export function getStageName(path: NodePath): string | null {
25
- let current: NodePath | null = path.parentPath;
26
- while (current !== null) {
27
- if (current.isExportNamedDeclaration()) {
28
- const node = current.node as ExportNamedDeclaration;
29
- const decl = node.declaration;
30
- if (decl === null || decl === undefined) return null;
31
- if (decl.type === "FunctionDeclaration") {
32
- return (decl as FunctionDeclaration).id?.name ?? null;
33
- }
34
- if (decl.type === "VariableDeclaration") {
35
- const declarator = (decl as VariableDeclaration).declarations[0];
36
- if (declarator?.id.type === "Identifier") {
37
- return declarator.id.name;
38
- }
39
- }
40
- return null;
41
- }
42
- current = current.parentPath;
43
- }
44
- return null;
45
- }