agentweaver 0.1.0 → 0.1.3
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/README.md +30 -5
- package/dist/artifacts.js +24 -2
- package/dist/executors/claude-executor.js +46 -0
- package/dist/executors/claude-summary-executor.js +31 -0
- package/dist/executors/codex-docker-executor.js +27 -0
- package/dist/executors/codex-local-executor.js +25 -0
- package/dist/executors/command-check-executor.js +14 -0
- package/dist/executors/configs/claude-config.js +12 -0
- package/dist/executors/configs/claude-summary-config.js +8 -0
- package/dist/executors/configs/codex-docker-config.js +10 -0
- package/dist/executors/configs/codex-local-config.js +8 -0
- package/dist/executors/configs/jira-fetch-config.js +4 -0
- package/dist/executors/configs/process-config.js +3 -0
- package/dist/executors/configs/verify-build-config.js +7 -0
- package/dist/executors/jira-fetch-executor.js +11 -0
- package/dist/executors/process-executor.js +21 -0
- package/dist/executors/types.js +1 -0
- package/dist/executors/verify-build-executor.js +22 -0
- package/dist/index.js +456 -699
- package/dist/interactive-ui.js +536 -182
- package/dist/jira.js +3 -1
- package/dist/pipeline/auto-flow.js +9 -0
- package/dist/pipeline/build-failure-summary.js +6 -0
- package/dist/pipeline/checks.js +15 -0
- package/dist/pipeline/context.js +19 -0
- package/dist/pipeline/declarative-flow-runner.js +246 -0
- package/dist/pipeline/declarative-flows.js +24 -0
- package/dist/pipeline/flow-runner.js +13 -0
- package/dist/pipeline/flow-specs/auto.json +471 -0
- package/dist/pipeline/flow-specs/implement.json +47 -0
- package/dist/pipeline/flow-specs/plan.json +88 -0
- package/dist/pipeline/flow-specs/preflight.json +174 -0
- package/dist/pipeline/flow-specs/review-fix.json +76 -0
- package/dist/pipeline/flow-specs/review.json +233 -0
- package/dist/pipeline/flow-specs/test-fix.json +24 -0
- package/dist/pipeline/flow-specs/test-linter-fix.json +24 -0
- package/dist/pipeline/flow-specs/test.json +19 -0
- package/dist/pipeline/flow-types.js +1 -0
- package/dist/pipeline/flows/implement-flow.js +47 -0
- package/dist/pipeline/flows/plan-flow.js +42 -0
- package/dist/pipeline/flows/preflight-flow.js +19 -0
- package/dist/pipeline/flows/review-fix-flow.js +62 -0
- package/dist/pipeline/flows/review-flow.js +124 -0
- package/dist/pipeline/flows/test-fix-flow.js +12 -0
- package/dist/pipeline/flows/test-flow.js +32 -0
- package/dist/pipeline/node-registry.js +71 -0
- package/dist/pipeline/node-runner.js +20 -0
- package/dist/pipeline/nodes/build-failure-summary-node.js +71 -0
- package/dist/pipeline/nodes/claude-prompt-node.js +54 -0
- package/dist/pipeline/nodes/claude-summary-node.js +38 -0
- package/dist/pipeline/nodes/codex-docker-prompt-node.js +32 -0
- package/dist/pipeline/nodes/codex-local-prompt-node.js +32 -0
- package/dist/pipeline/nodes/command-check-node.js +10 -0
- package/dist/pipeline/nodes/file-check-node.js +15 -0
- package/dist/pipeline/nodes/implement-codex-node.js +16 -0
- package/dist/pipeline/nodes/jira-fetch-node.js +25 -0
- package/dist/pipeline/nodes/plan-codex-node.js +32 -0
- package/dist/pipeline/nodes/review-claude-node.js +38 -0
- package/dist/pipeline/nodes/review-reply-codex-node.js +40 -0
- package/dist/pipeline/nodes/summary-file-load-node.js +16 -0
- package/dist/pipeline/nodes/task-summary-node.js +42 -0
- package/dist/pipeline/nodes/verify-build-node.js +14 -0
- package/dist/pipeline/prompt-registry.js +22 -0
- package/dist/pipeline/prompt-runtime.js +18 -0
- package/dist/pipeline/registry.js +23 -0
- package/dist/pipeline/spec-compiler.js +200 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +1 -0
- package/dist/pipeline/spec-validator.js +290 -0
- package/dist/pipeline/types.js +10 -0
- package/dist/pipeline/value-resolver.js +199 -0
- package/dist/prompts.js +1 -3
- package/dist/runtime/command-resolution.js +139 -0
- package/dist/runtime/docker-runtime.js +51 -0
- package/dist/runtime/process-runner.js +112 -0
- package/dist/tui.js +73 -0
- package/package.json +3 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { artifactFile, designFile, planFile } from "../../artifacts.js";
|
|
2
|
+
import { REVIEW_PROMPT_TEMPLATE, formatPrompt, formatTemplate } from "../../prompts.js";
|
|
3
|
+
import { printInfo, printPrompt } from "../../tui.js";
|
|
4
|
+
import { toExecutorContext } from "../types.js";
|
|
5
|
+
export const reviewClaudeNode = {
|
|
6
|
+
kind: "review-claude",
|
|
7
|
+
version: 1,
|
|
8
|
+
async run(context, params) {
|
|
9
|
+
const reviewFile = artifactFile("review", params.taskKey, params.iteration);
|
|
10
|
+
const prompt = formatPrompt(formatTemplate(REVIEW_PROMPT_TEMPLATE, {
|
|
11
|
+
jira_task_file: params.jiraTaskFile,
|
|
12
|
+
design_file: designFile(params.taskKey),
|
|
13
|
+
plan_file: planFile(params.taskKey),
|
|
14
|
+
review_file: reviewFile,
|
|
15
|
+
}), params.extraPrompt);
|
|
16
|
+
printInfo(`Running Claude review mode (iteration ${params.iteration})`);
|
|
17
|
+
printPrompt("Claude", prompt);
|
|
18
|
+
const executor = context.executors.get("claude");
|
|
19
|
+
const value = await executor.execute(toExecutorContext(context), {
|
|
20
|
+
prompt,
|
|
21
|
+
command: params.claudeCmd,
|
|
22
|
+
env: { ...context.env },
|
|
23
|
+
}, executor.defaultConfig);
|
|
24
|
+
return {
|
|
25
|
+
value,
|
|
26
|
+
outputs: [{ kind: "artifact", path: reviewFile, required: true }],
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
checks(_context, params) {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
kind: "require-artifacts",
|
|
33
|
+
paths: [artifactFile("review", params.taskKey, params.iteration)],
|
|
34
|
+
message: "Claude review did not produce the required review artifact.",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { artifactFile, designFile, planFile } from "../../artifacts.js";
|
|
2
|
+
import { REVIEW_REPLY_PROMPT_TEMPLATE, formatPrompt, formatTemplate } from "../../prompts.js";
|
|
3
|
+
import { printInfo, printPrompt } from "../../tui.js";
|
|
4
|
+
import { toExecutorContext } from "../types.js";
|
|
5
|
+
export const reviewReplyCodexNode = {
|
|
6
|
+
kind: "review-reply-codex",
|
|
7
|
+
version: 1,
|
|
8
|
+
async run(context, params) {
|
|
9
|
+
const reviewFile = artifactFile("review", params.taskKey, params.iteration);
|
|
10
|
+
const reviewReplyFile = artifactFile("review-reply", params.taskKey, params.iteration);
|
|
11
|
+
const prompt = formatPrompt(formatTemplate(REVIEW_REPLY_PROMPT_TEMPLATE, {
|
|
12
|
+
review_file: reviewFile,
|
|
13
|
+
jira_task_file: params.jiraTaskFile,
|
|
14
|
+
design_file: designFile(params.taskKey),
|
|
15
|
+
plan_file: planFile(params.taskKey),
|
|
16
|
+
review_reply_file: reviewReplyFile,
|
|
17
|
+
}), params.extraPrompt);
|
|
18
|
+
printInfo(`Running Codex review reply mode (iteration ${params.iteration})`);
|
|
19
|
+
printPrompt("Codex", prompt);
|
|
20
|
+
const executor = context.executors.get("codex-local");
|
|
21
|
+
const value = await executor.execute(toExecutorContext(context), {
|
|
22
|
+
prompt,
|
|
23
|
+
command: params.codexCmd,
|
|
24
|
+
env: { ...context.env },
|
|
25
|
+
}, executor.defaultConfig);
|
|
26
|
+
return {
|
|
27
|
+
value,
|
|
28
|
+
outputs: [{ kind: "artifact", path: reviewReplyFile, required: true }],
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
checks(_context, params) {
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
kind: "require-artifacts",
|
|
35
|
+
paths: [artifactFile("review-reply", params.taskKey, params.iteration)],
|
|
36
|
+
message: "Codex review reply did not produce the required review-reply artifact.",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { printSummary } from "../../tui.js";
|
|
3
|
+
export const summaryFileLoadNode = {
|
|
4
|
+
kind: "summary-file-load",
|
|
5
|
+
version: 1,
|
|
6
|
+
async run(context, params) {
|
|
7
|
+
const text = readFileSync(params.path, "utf8").trim();
|
|
8
|
+
context.setSummary?.(text);
|
|
9
|
+
if (params.title) {
|
|
10
|
+
printSummary(params.title, text);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
value: { text },
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { taskSummaryFile } from "../../artifacts.js";
|
|
3
|
+
import { requireArtifacts } from "../../artifacts.js";
|
|
4
|
+
import { TASK_SUMMARY_PROMPT_TEMPLATE, formatTemplate } from "../../prompts.js";
|
|
5
|
+
import { toExecutorContext } from "../types.js";
|
|
6
|
+
export const taskSummaryNode = {
|
|
7
|
+
kind: "task-summary",
|
|
8
|
+
version: 1,
|
|
9
|
+
async run(context, params) {
|
|
10
|
+
const outputFile = taskSummaryFile(params.taskKey);
|
|
11
|
+
const prompt = formatTemplate(TASK_SUMMARY_PROMPT_TEMPLATE, {
|
|
12
|
+
jira_task_file: params.jiraTaskFile,
|
|
13
|
+
task_summary_file: outputFile,
|
|
14
|
+
});
|
|
15
|
+
const executor = context.executors.get("claude");
|
|
16
|
+
const value = await executor.execute(toExecutorContext(context), {
|
|
17
|
+
prompt,
|
|
18
|
+
...(params.model ? { model: params.model } : {}),
|
|
19
|
+
...(params.claudeCmd ? { command: params.claudeCmd } : {}),
|
|
20
|
+
env: { ...context.env },
|
|
21
|
+
}, executor.defaultConfig);
|
|
22
|
+
requireArtifacts([outputFile], `Claude summary did not produce ${outputFile}.`);
|
|
23
|
+
const artifactText = readFileSync(outputFile, "utf8").trim();
|
|
24
|
+
context.setSummary?.(artifactText);
|
|
25
|
+
return {
|
|
26
|
+
value: {
|
|
27
|
+
...value,
|
|
28
|
+
artifactText,
|
|
29
|
+
},
|
|
30
|
+
outputs: [{ kind: "artifact", path: outputFile, required: true }],
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
checks(_context, params) {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
kind: "require-artifacts",
|
|
37
|
+
paths: [taskSummaryFile(params.taskKey)],
|
|
38
|
+
message: `Claude summary did not produce ${taskSummaryFile(params.taskKey)}.`,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { printInfo } from "../../tui.js";
|
|
2
|
+
import { toExecutorContext } from "../types.js";
|
|
3
|
+
export const verifyBuildNode = {
|
|
4
|
+
kind: "verify-build",
|
|
5
|
+
version: 1,
|
|
6
|
+
async run(context, params) {
|
|
7
|
+
printInfo(params.labelText);
|
|
8
|
+
const executor = context.executors.get("verify-build");
|
|
9
|
+
const value = await executor.execute(toExecutorContext(context), {
|
|
10
|
+
dockerComposeFile: params.dockerComposeFile,
|
|
11
|
+
}, executor.defaultConfig);
|
|
12
|
+
return { value };
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IMPLEMENT_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_REPLY_PROMPT_TEMPLATE, REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, TEST_FIX_PROMPT_TEMPLATE, TEST_LINTER_FIX_PROMPT_TEMPLATE, } from "../prompts.js";
|
|
2
|
+
const promptTemplates = {
|
|
3
|
+
implement: IMPLEMENT_PROMPT_TEMPLATE,
|
|
4
|
+
plan: PLAN_PROMPT_TEMPLATE,
|
|
5
|
+
review: REVIEW_PROMPT_TEMPLATE,
|
|
6
|
+
"review-fix": REVIEW_FIX_PROMPT_TEMPLATE,
|
|
7
|
+
"review-reply": REVIEW_REPLY_PROMPT_TEMPLATE,
|
|
8
|
+
"review-reply-summary": REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE,
|
|
9
|
+
"review-summary": REVIEW_SUMMARY_PROMPT_TEMPLATE,
|
|
10
|
+
"task-summary": TASK_SUMMARY_PROMPT_TEMPLATE,
|
|
11
|
+
"test-fix": TEST_FIX_PROMPT_TEMPLATE,
|
|
12
|
+
"test-linter-fix": TEST_LINTER_FIX_PROMPT_TEMPLATE,
|
|
13
|
+
};
|
|
14
|
+
export function isPromptTemplateRef(value) {
|
|
15
|
+
return value in promptTemplates;
|
|
16
|
+
}
|
|
17
|
+
export function getPromptTemplate(ref) {
|
|
18
|
+
return promptTemplates[ref];
|
|
19
|
+
}
|
|
20
|
+
export function promptTemplateRefs() {
|
|
21
|
+
return Object.keys(promptTemplates);
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TaskRunnerError } from "../errors.js";
|
|
2
|
+
import { formatPrompt, formatTemplate } from "../prompts.js";
|
|
3
|
+
import { getPromptTemplate } from "./prompt-registry.js";
|
|
4
|
+
import { resolveValue } from "./value-resolver.js";
|
|
5
|
+
export function renderPrompt(binding, context) {
|
|
6
|
+
const baseTemplate = binding.inlineTemplate ?? (binding.templateRef ? getPromptTemplate(binding.templateRef) : null);
|
|
7
|
+
if (!baseTemplate) {
|
|
8
|
+
throw new TaskRunnerError("Prompt binding must define templateRef or inlineTemplate");
|
|
9
|
+
}
|
|
10
|
+
const vars = Object.fromEntries(Object.entries(binding.vars ?? {}).map(([key, value]) => [key, String(resolveValue(value, context))]));
|
|
11
|
+
const basePrompt = formatTemplate(baseTemplate, vars);
|
|
12
|
+
const resolvedExtraPrompt = binding.extraPrompt ? resolveValue(binding.extraPrompt, context) : null;
|
|
13
|
+
const extraPrompt = resolvedExtraPrompt === null || resolvedExtraPrompt === undefined ? null : String(resolvedExtraPrompt);
|
|
14
|
+
if ((binding.format ?? "task-prompt") === "plain") {
|
|
15
|
+
return basePrompt;
|
|
16
|
+
}
|
|
17
|
+
return formatPrompt(basePrompt, extraPrompt);
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { commandCheckExecutor } from "../executors/command-check-executor.js";
|
|
2
|
+
import { claudeExecutor } from "../executors/claude-executor.js";
|
|
3
|
+
import { codexDockerExecutor } from "../executors/codex-docker-executor.js";
|
|
4
|
+
import { codexLocalExecutor } from "../executors/codex-local-executor.js";
|
|
5
|
+
import { jiraFetchExecutor } from "../executors/jira-fetch-executor.js";
|
|
6
|
+
import { processExecutor } from "../executors/process-executor.js";
|
|
7
|
+
import { verifyBuildExecutor } from "../executors/verify-build-executor.js";
|
|
8
|
+
const builtInExecutors = {
|
|
9
|
+
process: processExecutor,
|
|
10
|
+
"command-check": commandCheckExecutor,
|
|
11
|
+
"jira-fetch": jiraFetchExecutor,
|
|
12
|
+
"codex-local": codexLocalExecutor,
|
|
13
|
+
"codex-docker": codexDockerExecutor,
|
|
14
|
+
claude: claudeExecutor,
|
|
15
|
+
"verify-build": verifyBuildExecutor,
|
|
16
|
+
};
|
|
17
|
+
export function createExecutorRegistry() {
|
|
18
|
+
return {
|
|
19
|
+
get(id) {
|
|
20
|
+
return builtInExecutors[id];
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
function interpolateText(template, repeatVars) {
|
|
2
|
+
let result = template;
|
|
3
|
+
for (const [key, value] of Object.entries(repeatVars)) {
|
|
4
|
+
result = result.replaceAll(`\${${key}}`, String(value));
|
|
5
|
+
}
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
function interpolateValueSpec(value, repeatVars) {
|
|
9
|
+
if ("const" in value || "artifact" in value || "artifactList" in value || "list" in value) {
|
|
10
|
+
if ("artifact" in value) {
|
|
11
|
+
return {
|
|
12
|
+
artifact: {
|
|
13
|
+
...value.artifact,
|
|
14
|
+
taskKey: interpolateValueSpec(value.artifact.taskKey, repeatVars),
|
|
15
|
+
...(value.artifact.iteration ? { iteration: interpolateValueSpec(value.artifact.iteration, repeatVars) } : {}),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if ("artifactList" in value) {
|
|
20
|
+
return {
|
|
21
|
+
artifactList: {
|
|
22
|
+
...value.artifactList,
|
|
23
|
+
taskKey: interpolateValueSpec(value.artifactList.taskKey, repeatVars),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if ("list" in value) {
|
|
28
|
+
return {
|
|
29
|
+
list: value.list.map((candidate) => interpolateValueSpec(candidate, repeatVars)),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
if ("ref" in value) {
|
|
35
|
+
return {
|
|
36
|
+
ref: interpolateText(value.ref, repeatVars),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if ("template" in value) {
|
|
40
|
+
return {
|
|
41
|
+
template: interpolateText(value.template, repeatVars),
|
|
42
|
+
...(value.vars
|
|
43
|
+
? {
|
|
44
|
+
vars: Object.fromEntries(Object.entries(value.vars).map(([key, candidate]) => [key, interpolateValueSpec(candidate, repeatVars)])),
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if ("appendPrompt" in value) {
|
|
50
|
+
return {
|
|
51
|
+
appendPrompt: {
|
|
52
|
+
...(value.appendPrompt.base ? { base: interpolateValueSpec(value.appendPrompt.base, repeatVars) } : {}),
|
|
53
|
+
suffix: interpolateValueSpec(value.appendPrompt.suffix, repeatVars),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if ("concat" in value) {
|
|
58
|
+
return {
|
|
59
|
+
concat: value.concat.map((candidate) => interpolateValueSpec(candidate, repeatVars)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function interpolateCondition(condition, repeatVars) {
|
|
65
|
+
if (!condition) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
if ("ref" in condition) {
|
|
69
|
+
return {
|
|
70
|
+
ref: interpolateText(condition.ref, repeatVars),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if ("not" in condition) {
|
|
74
|
+
return {
|
|
75
|
+
not: interpolateCondition(condition.not, repeatVars),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if ("all" in condition) {
|
|
79
|
+
return {
|
|
80
|
+
all: condition.all.map((candidate) => interpolateCondition(candidate, repeatVars)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if ("any" in condition) {
|
|
84
|
+
return {
|
|
85
|
+
any: condition.any.map((candidate) => interpolateCondition(candidate, repeatVars)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if ("equals" in condition) {
|
|
89
|
+
return {
|
|
90
|
+
equals: [
|
|
91
|
+
interpolateValueSpec(condition.equals[0], repeatVars),
|
|
92
|
+
interpolateValueSpec(condition.equals[1], repeatVars),
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if ("exists" in condition) {
|
|
97
|
+
return {
|
|
98
|
+
exists: interpolateValueSpec(condition.exists, repeatVars),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return condition;
|
|
102
|
+
}
|
|
103
|
+
function interpolatePrompt(prompt, repeatVars) {
|
|
104
|
+
return {
|
|
105
|
+
...(prompt.templateRef ? { templateRef: prompt.templateRef } : {}),
|
|
106
|
+
...(prompt.inlineTemplate ? { inlineTemplate: interpolateText(prompt.inlineTemplate, repeatVars) } : {}),
|
|
107
|
+
...(prompt.vars
|
|
108
|
+
? {
|
|
109
|
+
vars: Object.fromEntries(Object.entries(prompt.vars).map(([key, candidate]) => [key, interpolateValueSpec(candidate, repeatVars)])),
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
...(prompt.extraPrompt ? { extraPrompt: interpolateValueSpec(prompt.extraPrompt, repeatVars) } : {}),
|
|
113
|
+
...(prompt.format ? { format: prompt.format } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function interpolateExpectation(expectation, repeatVars) {
|
|
117
|
+
if (expectation.kind === "require-artifacts") {
|
|
118
|
+
const when = expectation.when ? interpolateCondition(expectation.when, repeatVars) : undefined;
|
|
119
|
+
return {
|
|
120
|
+
kind: expectation.kind,
|
|
121
|
+
...(when ? { when } : {}),
|
|
122
|
+
paths: interpolateValueSpec(expectation.paths, repeatVars),
|
|
123
|
+
message: interpolateText(expectation.message, repeatVars),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const when = expectation.when ? interpolateCondition(expectation.when, repeatVars) : undefined;
|
|
127
|
+
if (expectation.kind === "require-file") {
|
|
128
|
+
return {
|
|
129
|
+
kind: expectation.kind,
|
|
130
|
+
...(when ? { when } : {}),
|
|
131
|
+
path: interpolateValueSpec(expectation.path, repeatVars),
|
|
132
|
+
message: interpolateText(expectation.message, repeatVars),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
kind: expectation.kind,
|
|
137
|
+
...(when ? { when } : {}),
|
|
138
|
+
value: interpolateValueSpec(expectation.value, repeatVars),
|
|
139
|
+
...(expectation.equals ? { equals: interpolateValueSpec(expectation.equals, repeatVars) } : {}),
|
|
140
|
+
message: interpolateText(expectation.message, repeatVars),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function interpolateAfterAction(action, repeatVars) {
|
|
144
|
+
const when = action.when ? interpolateCondition(action.when, repeatVars) : undefined;
|
|
145
|
+
return {
|
|
146
|
+
kind: action.kind,
|
|
147
|
+
...(when ? { when } : {}),
|
|
148
|
+
path: interpolateValueSpec(action.path, repeatVars),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function expandPhase(phase, repeatVars) {
|
|
152
|
+
const phaseWhen = phase.when ? interpolateCondition(phase.when, repeatVars) : undefined;
|
|
153
|
+
return {
|
|
154
|
+
id: interpolateText(phase.id, repeatVars),
|
|
155
|
+
repeatVars,
|
|
156
|
+
...(phaseWhen ? { when: phaseWhen } : {}),
|
|
157
|
+
steps: phase.steps.map((step) => {
|
|
158
|
+
const stepWhen = step.when ? interpolateCondition(step.when, repeatVars) : undefined;
|
|
159
|
+
const stopFlowIf = step.stopFlowIf ? interpolateCondition(step.stopFlowIf, repeatVars) : undefined;
|
|
160
|
+
return {
|
|
161
|
+
id: interpolateText(step.id, repeatVars),
|
|
162
|
+
node: step.node,
|
|
163
|
+
...(stepWhen ? { when: stepWhen } : {}),
|
|
164
|
+
...(step.prompt ? { prompt: interpolatePrompt(step.prompt, repeatVars) } : {}),
|
|
165
|
+
...(step.params
|
|
166
|
+
? {
|
|
167
|
+
params: Object.fromEntries(Object.entries(step.params).map(([key, value]) => [key, interpolateValueSpec(value, repeatVars)])),
|
|
168
|
+
}
|
|
169
|
+
: {}),
|
|
170
|
+
...(step.expect ? { expect: step.expect.map((item) => interpolateExpectation(item, repeatVars)) } : {}),
|
|
171
|
+
...(stopFlowIf ? { stopFlowIf } : {}),
|
|
172
|
+
...(step.after ? { after: step.after.map((item) => interpolateAfterAction(item, repeatVars)) } : {}),
|
|
173
|
+
repeatVars,
|
|
174
|
+
};
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function expandRepeat(block) {
|
|
179
|
+
const phases = [];
|
|
180
|
+
for (let index = block.repeat.from; index <= block.repeat.to; index += 1) {
|
|
181
|
+
const repeatVars = {
|
|
182
|
+
[block.repeat.var]: index,
|
|
183
|
+
};
|
|
184
|
+
for (const phase of block.phases) {
|
|
185
|
+
phases.push(expandPhase(phase, repeatVars));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return phases;
|
|
189
|
+
}
|
|
190
|
+
export function compileFlowSpec(spec) {
|
|
191
|
+
const phases = [];
|
|
192
|
+
for (const item of spec.phases) {
|
|
193
|
+
if ("repeat" in item) {
|
|
194
|
+
phases.push(...expandRepeat(item));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
phases.push(expandPhase(item, {}));
|
|
198
|
+
}
|
|
199
|
+
return phases;
|
|
200
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { TaskRunnerError } from "../errors.js";
|
|
5
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export function loadFlowSpecSync(fileName) {
|
|
7
|
+
const filePath = path.join(MODULE_DIR, "flow-specs", fileName);
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new TaskRunnerError(`Failed to load flow spec ${filePath}: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|