agentweaver 0.1.2 → 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.
Files changed (50) hide show
  1. package/README.md +11 -10
  2. package/dist/artifacts.js +24 -2
  3. package/dist/executors/claude-executor.js +12 -2
  4. package/dist/executors/claude-summary-executor.js +1 -1
  5. package/dist/executors/codex-docker-executor.js +1 -1
  6. package/dist/executors/codex-local-executor.js +1 -1
  7. package/dist/executors/configs/claude-config.js +2 -1
  8. package/dist/index.js +388 -451
  9. package/dist/interactive-ui.js +451 -194
  10. package/dist/jira.js +3 -1
  11. package/dist/pipeline/auto-flow.js +9 -0
  12. package/dist/pipeline/context.js +2 -0
  13. package/dist/pipeline/declarative-flow-runner.js +246 -0
  14. package/dist/pipeline/declarative-flows.js +24 -0
  15. package/dist/pipeline/flow-specs/auto.json +471 -0
  16. package/dist/pipeline/flow-specs/implement.json +47 -0
  17. package/dist/pipeline/flow-specs/plan.json +88 -0
  18. package/dist/pipeline/flow-specs/preflight.json +174 -0
  19. package/dist/pipeline/flow-specs/review-fix.json +76 -0
  20. package/dist/pipeline/flow-specs/review.json +233 -0
  21. package/dist/pipeline/flow-specs/test-fix.json +24 -0
  22. package/dist/pipeline/flow-specs/test-linter-fix.json +24 -0
  23. package/dist/pipeline/flow-specs/test.json +19 -0
  24. package/dist/pipeline/flows/implement-flow.js +3 -4
  25. package/dist/pipeline/flows/preflight-flow.js +17 -57
  26. package/dist/pipeline/flows/review-fix-flow.js +3 -4
  27. package/dist/pipeline/flows/review-flow.js +8 -4
  28. package/dist/pipeline/flows/test-fix-flow.js +3 -4
  29. package/dist/pipeline/node-registry.js +71 -0
  30. package/dist/pipeline/node-runner.js +9 -3
  31. package/dist/pipeline/nodes/build-failure-summary-node.js +4 -4
  32. package/dist/pipeline/nodes/claude-prompt-node.js +54 -0
  33. package/dist/pipeline/nodes/claude-summary-node.js +12 -6
  34. package/dist/pipeline/nodes/codex-docker-prompt-node.js +1 -0
  35. package/dist/pipeline/nodes/codex-local-prompt-node.js +32 -0
  36. package/dist/pipeline/nodes/file-check-node.js +15 -0
  37. package/dist/pipeline/nodes/summary-file-load-node.js +16 -0
  38. package/dist/pipeline/nodes/task-summary-node.js +12 -6
  39. package/dist/pipeline/prompt-registry.js +22 -0
  40. package/dist/pipeline/prompt-runtime.js +18 -0
  41. package/dist/pipeline/registry.js +0 -2
  42. package/dist/pipeline/spec-compiler.js +200 -0
  43. package/dist/pipeline/spec-loader.js +14 -0
  44. package/dist/pipeline/spec-types.js +1 -0
  45. package/dist/pipeline/spec-validator.js +290 -0
  46. package/dist/pipeline/value-resolver.js +199 -0
  47. package/dist/prompts.js +1 -3
  48. package/dist/runtime/process-runner.js +24 -23
  49. package/dist/tui.js +39 -0
  50. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
- import { READY_TO_MERGE_FILE, REVIEW_REPLY_FILE_RE, artifactFile, planArtifacts, requireArtifacts, } from "../../artifacts.js";
2
+ import { REVIEW_REPLY_FILE_RE, artifactFile, planArtifacts, readyToMergeFile, requireArtifacts, taskWorkspaceDir, } from "../../artifacts.js";
3
3
  import { REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, formatTemplate } from "../../prompts.js";
4
4
  import { printPanel } from "../../tui.js";
5
5
  import { runFlow } from "../flow-runner.js";
@@ -9,7 +9,11 @@ import { reviewClaudeNode } from "../nodes/review-claude-node.js";
9
9
  import { reviewReplyCodexNode } from "../nodes/review-reply-codex-node.js";
10
10
  function nextReviewIterationForTask(taskKey) {
11
11
  let maxIndex = 0;
12
- for (const entry of readdirSync(process.cwd(), { withFileTypes: true })) {
12
+ const workspaceDir = taskWorkspaceDir(taskKey);
13
+ if (!existsSync(workspaceDir)) {
14
+ return 1;
15
+ }
16
+ for (const entry of readdirSync(workspaceDir, { withFileTypes: true })) {
13
17
  if (!entry.isFile()) {
14
18
  continue;
15
19
  }
@@ -96,8 +100,8 @@ export function createReviewFlowDefinition(iteration) {
96
100
  },
97
101
  {
98
102
  id: "check_ready_to_merge",
99
- async run(stepContext) {
100
- const readyToMerge = !stepContext.dryRun && existsSync(READY_TO_MERGE_FILE);
103
+ async run(stepContext, stepParams) {
104
+ const readyToMerge = !stepContext.dryRun && existsSync(readyToMergeFile(stepParams.taskKey));
101
105
  if (readyToMerge) {
102
106
  printPanel("Ready To Merge", "Изменения готовы к merge\nФайл ready-to-merge.md создан.", "green");
103
107
  }
@@ -1,13 +1,12 @@
1
1
  import { planArtifacts, requireArtifacts } from "../../artifacts.js";
2
2
  import { TEST_FIX_PROMPT_TEMPLATE, TEST_LINTER_FIX_PROMPT_TEMPLATE, formatPrompt } from "../../prompts.js";
3
3
  import { runNode } from "../node-runner.js";
4
- import { codexDockerPromptNode } from "../nodes/codex-docker-prompt-node.js";
4
+ import { codexLocalPromptNode } from "../nodes/codex-local-prompt-node.js";
5
5
  export async function runTestFixFlow(context, params) {
6
6
  requireArtifacts(planArtifacts(params.taskKey), `${params.command} mode requires plan artifacts from the planning phase.`);
7
7
  const prompt = formatPrompt(params.command === "test-fix" ? TEST_FIX_PROMPT_TEMPLATE : TEST_LINTER_FIX_PROMPT_TEMPLATE, params.extraPrompt);
8
- await runNode(codexDockerPromptNode, context, {
9
- dockerComposeFile: params.dockerComposeFile,
8
+ await runNode(codexLocalPromptNode, context, {
10
9
  prompt,
11
- labelText: `Running Codex ${params.command} mode in isolated Docker`,
10
+ labelText: `Running Codex ${params.command} mode locally`,
12
11
  });
13
12
  }
@@ -0,0 +1,71 @@
1
+ import { buildFailureSummaryNode } from "./nodes/build-failure-summary-node.js";
2
+ import { claudePromptNode } from "./nodes/claude-prompt-node.js";
3
+ import { codexDockerPromptNode } from "./nodes/codex-docker-prompt-node.js";
4
+ import { codexLocalPromptNode } from "./nodes/codex-local-prompt-node.js";
5
+ import { commandCheckNode } from "./nodes/command-check-node.js";
6
+ import { fileCheckNode } from "./nodes/file-check-node.js";
7
+ import { jiraFetchNode } from "./nodes/jira-fetch-node.js";
8
+ import { planCodexNode } from "./nodes/plan-codex-node.js";
9
+ import { reviewClaudeNode } from "./nodes/review-claude-node.js";
10
+ import { reviewReplyCodexNode } from "./nodes/review-reply-codex-node.js";
11
+ import { summaryFileLoadNode } from "./nodes/summary-file-load-node.js";
12
+ import { verifyBuildNode } from "./nodes/verify-build-node.js";
13
+ const builtInNodes = {
14
+ "build-failure-summary": buildFailureSummaryNode,
15
+ "claude-prompt": claudePromptNode,
16
+ "codex-docker-prompt": codexDockerPromptNode,
17
+ "codex-local-prompt": codexLocalPromptNode,
18
+ "command-check": commandCheckNode,
19
+ "file-check": fileCheckNode,
20
+ "jira-fetch": jiraFetchNode,
21
+ "plan-codex": planCodexNode,
22
+ "review-claude": reviewClaudeNode,
23
+ "review-reply-codex": reviewReplyCodexNode,
24
+ "summary-file-load": summaryFileLoadNode,
25
+ "verify-build": verifyBuildNode,
26
+ };
27
+ const builtInNodeMetadata = {
28
+ "build-failure-summary": { kind: "build-failure-summary", version: 1, prompt: "forbidden", requiredParams: ["output"] },
29
+ "claude-prompt": { kind: "claude-prompt", version: 1, prompt: "required", requiredParams: ["labelText"] },
30
+ "codex-docker-prompt": {
31
+ kind: "codex-docker-prompt",
32
+ version: 1,
33
+ prompt: "required",
34
+ requiredParams: ["dockerComposeFile", "labelText"],
35
+ },
36
+ "codex-local-prompt": { kind: "codex-local-prompt", version: 1, prompt: "required", requiredParams: ["labelText"] },
37
+ "command-check": { kind: "command-check", version: 1, prompt: "forbidden", requiredParams: ["commands"] },
38
+ "file-check": { kind: "file-check", version: 1, prompt: "forbidden", requiredParams: ["path"] },
39
+ "jira-fetch": { kind: "jira-fetch", version: 1, prompt: "forbidden", requiredParams: ["jiraApiUrl", "outputFile"] },
40
+ "plan-codex": { kind: "plan-codex", version: 1, prompt: "forbidden", requiredParams: ["prompt", "requiredArtifacts"] },
41
+ "review-claude": {
42
+ kind: "review-claude",
43
+ version: 1,
44
+ prompt: "forbidden",
45
+ requiredParams: ["jiraTaskFile", "taskKey", "iteration", "claudeCmd"],
46
+ },
47
+ "review-reply-codex": {
48
+ kind: "review-reply-codex",
49
+ version: 1,
50
+ prompt: "forbidden",
51
+ requiredParams: ["jiraTaskFile", "taskKey", "iteration", "codexCmd"],
52
+ },
53
+ "summary-file-load": { kind: "summary-file-load", version: 1, prompt: "forbidden", requiredParams: ["path"] },
54
+ "verify-build": { kind: "verify-build", version: 1, prompt: "forbidden", requiredParams: ["dockerComposeFile", "labelText"] },
55
+ };
56
+ export function createNodeRegistry() {
57
+ return {
58
+ get(kind) {
59
+ return builtInNodes[kind];
60
+ },
61
+ getMeta(kind) {
62
+ return builtInNodeMetadata[kind];
63
+ },
64
+ has(kind) {
65
+ return kind in builtInNodes;
66
+ },
67
+ kinds() {
68
+ return Object.keys(builtInNodes);
69
+ },
70
+ };
71
+ }
@@ -1,14 +1,20 @@
1
1
  import { setCurrentNode } from "../tui.js";
2
2
  import { runNodeChecks } from "./checks.js";
3
- export async function runNode(node, context, params) {
3
+ export async function runNode(node, context, params, options = {}) {
4
4
  setCurrentNode(node.kind);
5
5
  try {
6
6
  const result = await node.run(context, params);
7
- const checks = node.checks?.(context, params, result) ?? [];
8
- runNodeChecks(checks);
7
+ if (!options.skipChecks) {
8
+ const checks = node.checks?.(context, params, result) ?? [];
9
+ runNodeChecks(checks);
10
+ }
9
11
  return result;
10
12
  }
11
13
  finally {
12
14
  setCurrentNode(null);
13
15
  }
14
16
  }
17
+ export async function runNodeByKind(kind, context, params, options = {}) {
18
+ const node = context.nodes.get(kind);
19
+ return runNode(node, context, params, options);
20
+ }
@@ -1,5 +1,5 @@
1
1
  import { toExecutorContext } from "../types.js";
2
- const DEFAULT_CLAUDE_SUMMARY_MODEL = "haiku";
2
+ const DEFAULT_CLAUDE_MODEL = "haiku";
3
3
  function truncateText(text, maxChars = 12000) {
4
4
  return text.length <= maxChars ? text.trim() : text.trim().slice(-maxChars);
5
5
  }
@@ -11,8 +11,8 @@ function fallbackBuildFailureSummary(output) {
11
11
  const tail = lines.length > 0 ? lines.slice(-8) : ["No build output captured."];
12
12
  return `Не удалось получить summary через Claude.\n\nПоследние строки лога:\n${tail.join("\n")}`;
13
13
  }
14
- function claudeSummaryModel(env) {
15
- return env.CLAUDE_SUMMARY_MODEL?.trim() || DEFAULT_CLAUDE_SUMMARY_MODEL;
14
+ function claudeModel(env) {
15
+ return env.CLAUDE_MODEL?.trim() || env.CLAUDE_SUMMARY_MODEL?.trim() || env.CLAUDE_REVIEW_MODEL?.trim() || DEFAULT_CLAUDE_MODEL;
16
16
  }
17
17
  export const buildFailureSummaryNode = {
18
18
  kind: "build-failure-summary",
@@ -36,7 +36,7 @@ export const buildFailureSummaryNode = {
36
36
  },
37
37
  };
38
38
  }
39
- const model = claudeSummaryModel(context.env);
39
+ const model = claudeModel(context.env);
40
40
  const prompt = "Ниже лог упавшей build verification.\n" +
41
41
  "Сделай краткое резюме на русском языке, без воды.\n" +
42
42
  "Нужно обязательно выделить:\n" +
@@ -0,0 +1,54 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { requireArtifacts } from "../../artifacts.js";
3
+ import { printInfo, printPrompt, printSummary } from "../../tui.js";
4
+ import { toExecutorContext } from "../types.js";
5
+ export const claudePromptNode = {
6
+ kind: "claude-prompt",
7
+ version: 1,
8
+ async run(context, params) {
9
+ printInfo(params.labelText);
10
+ printPrompt("Claude", params.prompt);
11
+ const executor = context.executors.get("claude");
12
+ const value = await executor.execute(toExecutorContext(context), {
13
+ prompt: params.prompt,
14
+ ...(params.command ? { command: params.command } : {}),
15
+ ...(params.model ? { model: params.model } : {}),
16
+ env: { ...context.env },
17
+ }, executor.defaultConfig);
18
+ const outputs = [
19
+ ...(params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
20
+ ...(params.outputFile ? [{ kind: "artifact", path: params.outputFile, required: true }] : []),
21
+ ];
22
+ if (!params.outputFile) {
23
+ return { value, outputs };
24
+ }
25
+ requireArtifacts([params.outputFile], params.missingArtifactsMessage ?? `Claude prompt did not produce ${params.outputFile}.`);
26
+ const artifactText = readFileSync(params.outputFile, "utf8").trim();
27
+ if (params.summaryTitle) {
28
+ printSummary(params.summaryTitle, artifactText);
29
+ }
30
+ return {
31
+ value: {
32
+ ...value,
33
+ artifactText,
34
+ },
35
+ outputs,
36
+ };
37
+ },
38
+ checks(_context, params) {
39
+ const requiredArtifacts = [
40
+ ...(params.requiredArtifacts ?? []),
41
+ ...(params.outputFile ? [params.outputFile] : []),
42
+ ];
43
+ if (requiredArtifacts.length === 0) {
44
+ return [];
45
+ }
46
+ return [
47
+ {
48
+ kind: "require-artifacts",
49
+ paths: requiredArtifacts,
50
+ message: params.missingArtifactsMessage ?? "Claude prompt node did not produce required artifacts.",
51
+ },
52
+ ];
53
+ },
54
+ };
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { requireArtifacts } from "../../artifacts.js";
1
3
  import { printInfo, printPrompt, printSummary } from "../../tui.js";
2
4
  import { toExecutorContext } from "../types.js";
3
5
  export const claudeSummaryNode = {
@@ -6,17 +8,21 @@ export const claudeSummaryNode = {
6
8
  async run(context, params) {
7
9
  printInfo(`Preparing summary in ${params.outputFile}`);
8
10
  printPrompt("Claude", params.prompt);
9
- const executor = context.executors.get("claude-summary");
11
+ const executor = context.executors.get("claude");
10
12
  const value = await executor.execute(toExecutorContext(context), {
11
13
  prompt: params.prompt,
12
- outputFile: params.outputFile,
13
- command: params.claudeCmd,
14
+ ...(params.model ? { model: params.model } : {}),
15
+ ...(params.claudeCmd ? { command: params.claudeCmd } : {}),
14
16
  env: { ...context.env },
15
- verbose: params.verbose,
16
17
  }, executor.defaultConfig);
17
- printSummary(params.summaryTitle, value.artifactText);
18
+ requireArtifacts([params.outputFile], `Claude summary did not produce ${params.outputFile}.`);
19
+ const artifactText = readFileSync(params.outputFile, "utf8").trim();
20
+ printSummary(params.summaryTitle, artifactText);
18
21
  return {
19
- value,
22
+ value: {
23
+ ...value,
24
+ artifactText,
25
+ },
20
26
  outputs: [{ kind: "artifact", path: params.outputFile, required: true }],
21
27
  };
22
28
  },
@@ -10,6 +10,7 @@ export const codexDockerPromptNode = {
10
10
  const value = await executor.execute(toExecutorContext(context), {
11
11
  dockerComposeFile: params.dockerComposeFile,
12
12
  prompt: params.prompt,
13
+ ...(params.model ? { model: params.model } : {}),
13
14
  }, executor.defaultConfig);
14
15
  return {
15
16
  value,
@@ -0,0 +1,32 @@
1
+ import { printInfo, printPrompt } from "../../tui.js";
2
+ import { toExecutorContext } from "../types.js";
3
+ export const codexLocalPromptNode = {
4
+ kind: "codex-local-prompt",
5
+ version: 1,
6
+ async run(context, params) {
7
+ printInfo(params.labelText);
8
+ printPrompt("Codex", params.prompt);
9
+ const executor = context.executors.get("codex-local");
10
+ const value = await executor.execute(toExecutorContext(context), {
11
+ prompt: params.prompt,
12
+ ...(params.model ? { model: params.model } : {}),
13
+ env: { ...context.env },
14
+ }, executor.defaultConfig);
15
+ return {
16
+ value,
17
+ outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
18
+ };
19
+ },
20
+ checks(_context, params) {
21
+ if (!params.requiredArtifacts || params.requiredArtifacts.length === 0) {
22
+ return [];
23
+ }
24
+ return [
25
+ {
26
+ kind: "require-artifacts",
27
+ paths: params.requiredArtifacts,
28
+ message: params.missingArtifactsMessage ?? "Codex local node did not produce required artifacts.",
29
+ },
30
+ ];
31
+ },
32
+ };
@@ -0,0 +1,15 @@
1
+ import { existsSync } from "node:fs";
2
+ import { printPanel } from "../../tui.js";
3
+ export const fileCheckNode = {
4
+ kind: "file-check",
5
+ version: 1,
6
+ async run(_context, params) {
7
+ const exists = existsSync(params.path);
8
+ if (exists && params.panelTitle && params.foundMessage) {
9
+ printPanel(params.panelTitle, params.foundMessage, params.tone ?? "green");
10
+ }
11
+ return {
12
+ value: { exists },
13
+ };
14
+ },
15
+ };
@@ -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
+ };
@@ -1,4 +1,6 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { taskSummaryFile } from "../../artifacts.js";
3
+ import { requireArtifacts } from "../../artifacts.js";
2
4
  import { TASK_SUMMARY_PROMPT_TEMPLATE, formatTemplate } from "../../prompts.js";
3
5
  import { toExecutorContext } from "../types.js";
4
6
  export const taskSummaryNode = {
@@ -10,17 +12,21 @@ export const taskSummaryNode = {
10
12
  jira_task_file: params.jiraTaskFile,
11
13
  task_summary_file: outputFile,
12
14
  });
13
- const executor = context.executors.get("claude-summary");
15
+ const executor = context.executors.get("claude");
14
16
  const value = await executor.execute(toExecutorContext(context), {
15
17
  prompt,
16
- outputFile,
17
- command: params.claudeCmd,
18
+ ...(params.model ? { model: params.model } : {}),
19
+ ...(params.claudeCmd ? { command: params.claudeCmd } : {}),
18
20
  env: { ...context.env },
19
- verbose: params.verbose,
20
21
  }, executor.defaultConfig);
21
- context.setSummary?.(value.artifactText);
22
+ requireArtifacts([outputFile], `Claude summary did not produce ${outputFile}.`);
23
+ const artifactText = readFileSync(outputFile, "utf8").trim();
24
+ context.setSummary?.(artifactText);
22
25
  return {
23
- value,
26
+ value: {
27
+ ...value,
28
+ artifactText,
29
+ },
24
30
  outputs: [{ kind: "artifact", path: outputFile, required: true }],
25
31
  };
26
32
  },
@@ -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
+ }
@@ -1,6 +1,5 @@
1
1
  import { commandCheckExecutor } from "../executors/command-check-executor.js";
2
2
  import { claudeExecutor } from "../executors/claude-executor.js";
3
- import { claudeSummaryExecutor } from "../executors/claude-summary-executor.js";
4
3
  import { codexDockerExecutor } from "../executors/codex-docker-executor.js";
5
4
  import { codexLocalExecutor } from "../executors/codex-local-executor.js";
6
5
  import { jiraFetchExecutor } from "../executors/jira-fetch-executor.js";
@@ -13,7 +12,6 @@ const builtInExecutors = {
13
12
  "codex-local": codexLocalExecutor,
14
13
  "codex-docker": codexDockerExecutor,
15
14
  claude: claudeExecutor,
16
- "claude-summary": claudeSummaryExecutor,
17
15
  "verify-build": verifyBuildExecutor,
18
16
  };
19
17
  export function createExecutorRegistry() {
@@ -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 {};