agentweaver 0.1.9 → 0.1.11

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 (101) hide show
  1. package/README.md +226 -200
  2. package/dist/artifacts.js +101 -56
  3. package/dist/errors.js +7 -0
  4. package/dist/executors/{codex-local-executor.js → codex-executor.js} +4 -4
  5. package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
  6. package/dist/executors/configs/jira-fetch-config.js +2 -0
  7. package/dist/executors/configs/telegram-notifier-config.js +3 -0
  8. package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
  9. package/dist/executors/fetch-gitlab-review-executor.js +1 -1
  10. package/dist/executors/git-commit-executor.js +25 -0
  11. package/dist/executors/telegram-notifier-executor.js +54 -0
  12. package/dist/flow-state.js +46 -1
  13. package/dist/gitlab.js +13 -8
  14. package/dist/index.js +507 -520
  15. package/dist/interactive-ui.js +495 -87
  16. package/dist/jira.js +52 -5
  17. package/dist/pipeline/auto-flow.js +6 -6
  18. package/dist/pipeline/context.js +1 -0
  19. package/dist/pipeline/declarative-flows.js +7 -4
  20. package/dist/pipeline/flow-catalog.js +60 -23
  21. package/dist/pipeline/flow-model-settings.js +77 -0
  22. package/dist/pipeline/flow-specs/auto-common.json +446 -0
  23. package/dist/pipeline/flow-specs/auto-golang.json +563 -0
  24. package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
  25. package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
  26. package/dist/pipeline/flow-specs/git-commit.json +196 -0
  27. package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
  28. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +165 -0
  29. package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
  30. package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
  31. package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
  32. package/dist/pipeline/flow-specs/implement.json +5 -4
  33. package/dist/pipeline/flow-specs/plan.json +40 -148
  34. package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +73 -13
  35. package/dist/pipeline/flow-specs/review/review-loop.json +280 -0
  36. package/dist/pipeline/flow-specs/review/review-project.json +87 -0
  37. package/dist/pipeline/flow-specs/review/review.json +126 -0
  38. package/dist/pipeline/flow-specs/task-describe.json +191 -11
  39. package/dist/pipeline/launch-profile-config.js +38 -0
  40. package/dist/pipeline/node-registry.js +75 -45
  41. package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
  42. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
  43. package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
  44. package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
  45. package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
  46. package/dist/pipeline/nodes/git-commit-node.js +28 -0
  47. package/dist/pipeline/nodes/git-status-node.js +221 -0
  48. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
  49. package/dist/pipeline/nodes/jira-context-node.js +10 -0
  50. package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
  51. package/dist/pipeline/nodes/plan-codex-node.js +1 -1
  52. package/dist/pipeline/nodes/read-file-node.js +11 -0
  53. package/dist/pipeline/nodes/review-findings-form-node.js +18 -14
  54. package/dist/pipeline/nodes/select-files-form-node.js +72 -0
  55. package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
  56. package/dist/pipeline/nodes/user-input-node.js +29 -8
  57. package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
  58. package/dist/pipeline/prompt-registry.js +2 -4
  59. package/dist/pipeline/prompt-runtime.js +13 -3
  60. package/dist/pipeline/registry.js +6 -8
  61. package/dist/pipeline/spec-compiler.js +5 -0
  62. package/dist/pipeline/spec-loader.js +18 -7
  63. package/dist/pipeline/spec-types.js +7 -3
  64. package/dist/pipeline/spec-validator.js +4 -0
  65. package/dist/pipeline/types.js +1 -0
  66. package/dist/pipeline/value-resolver.js +40 -38
  67. package/dist/prompts.js +104 -110
  68. package/dist/runtime/agentweaver-home.js +8 -0
  69. package/dist/runtime/command-resolution.js +0 -38
  70. package/dist/runtime/env-loader.js +43 -0
  71. package/dist/runtime/process-runner.js +45 -1
  72. package/dist/structured-artifact-schema-registry.js +53 -0
  73. package/dist/structured-artifact-schemas.json +0 -20
  74. package/dist/structured-artifacts.js +3 -43
  75. package/dist/user-input.js +30 -2
  76. package/package.json +2 -6
  77. package/Dockerfile.codex +0 -56
  78. package/dist/executors/claude-executor.js +0 -46
  79. package/dist/executors/codex-docker-executor.js +0 -27
  80. package/dist/executors/configs/claude-config.js +0 -12
  81. package/dist/executors/configs/codex-docker-config.js +0 -10
  82. package/dist/executors/configs/verify-build-config.js +0 -7
  83. package/dist/executors/verify-build-executor.js +0 -123
  84. package/dist/pipeline/flow-specs/auto.json +0 -979
  85. package/dist/pipeline/flow-specs/gitlab-review.json +0 -317
  86. package/dist/pipeline/flow-specs/plan-opencode.json +0 -603
  87. package/dist/pipeline/flow-specs/preflight.json +0 -206
  88. package/dist/pipeline/flow-specs/review-project.json +0 -243
  89. package/dist/pipeline/flow-specs/review.json +0 -312
  90. package/dist/pipeline/flow-specs/run-linter-loop.json +0 -155
  91. package/dist/pipeline/flow-specs/run-tests-loop.json +0 -155
  92. package/dist/pipeline/flows/preflight-flow.js +0 -19
  93. package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
  94. package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
  95. package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
  96. package/dist/pipeline/nodes/review-claude-node.js +0 -38
  97. package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
  98. package/dist/pipeline/nodes/verify-build-node.js +0 -15
  99. package/dist/runtime/docker-runtime.js +0 -51
  100. package/docker-compose.yml +0 -445
  101. package/verify_build.sh +0 -105
@@ -1,25 +1,29 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { TaskRunnerError } from "../../errors.js";
3
+ const SEVERITY_FIX_THRESHOLD = ["blocker", "critical", "major"];
3
4
  export const reviewFindingsFormNode = {
4
5
  kind: "review-findings-form",
5
6
  version: 1,
6
7
  async run(_context, params) {
7
8
  let parsed;
8
9
  try {
9
- parsed = JSON.parse(readFileSync(params.reviewReplyJsonFile, "utf8"));
10
+ parsed = JSON.parse(readFileSync(params.reviewFindingsJsonFile, "utf8"));
10
11
  }
11
12
  catch (error) {
12
- throw new TaskRunnerError(`Failed to read review reply from ${params.reviewReplyJsonFile}: ${error.message}`);
13
+ throw new TaskRunnerError(`Failed to read review findings from ${params.reviewFindingsJsonFile}: ${error.message}`);
13
14
  }
14
- const reviewReply = parsed;
15
- const responses = Array.isArray(reviewReply.responses) ? reviewReply.responses : [];
16
- const selectableFindings = responses
17
- .map((response) => ({
18
- findingTitle: typeof response.finding_title === "string" ? response.finding_title.trim() : "",
19
- disposition: typeof response.disposition === "string" ? response.disposition.trim() : "",
20
- action: typeof response.action === "string" ? response.action.trim() : "",
15
+ const reviewFindings = parsed;
16
+ const findings = Array.isArray(reviewFindings.findings) ? reviewFindings.findings : [];
17
+ const selectableFindings = findings
18
+ .map((finding) => ({
19
+ severity: typeof finding.severity === "string" ? finding.severity.trim().toLowerCase() : "",
20
+ title: typeof finding.title === "string" ? finding.title.trim() : "",
21
+ description: typeof finding.description === "string" ? finding.description.trim() : "",
22
+ disposition: typeof finding.disposition === "string" ? finding.disposition.trim().toLowerCase() : null,
21
23
  }))
22
- .filter((response) => response.findingTitle.length > 0);
24
+ .filter((finding) => finding.title.length > 0 &&
25
+ SEVERITY_FIX_THRESHOLD.includes(finding.severity) &&
26
+ finding.disposition !== "resolved");
23
27
  const fields = [
24
28
  {
25
29
  id: "apply_all",
@@ -35,10 +39,10 @@ export const reviewFindingsFormNode = {
35
39
  type: "multi-select",
36
40
  label: "Какие findings исправить сейчас",
37
41
  help: "Space переключает пункт. Если apply_all=false, выберите хотя бы один finding.",
38
- options: selectableFindings.map((response) => ({
39
- value: response.findingTitle,
40
- label: `${response.findingTitle} | ${response.disposition || "-"}`,
41
- ...(response.action ? { description: response.action } : {}),
42
+ options: selectableFindings.map((finding) => ({
43
+ value: finding.title,
44
+ label: `${finding.title} | ${finding.severity || "-"}`,
45
+ ...(finding.description ? { description: finding.description } : {}),
42
46
  })),
43
47
  default: [],
44
48
  });
@@ -0,0 +1,72 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ import { validateUserInputValues } from "../../user-input.js";
5
+ export const selectFilesFormNode = {
6
+ kind: "select-files-form",
7
+ version: 1,
8
+ async run(context, params) {
9
+ let gitStatusFiles;
10
+ try {
11
+ const statusContent = readFileSync(params.gitStatusJsonFile, "utf8");
12
+ const statusParsed = JSON.parse(statusContent);
13
+ gitStatusFiles = (statusParsed.files ?? []);
14
+ }
15
+ catch (error) {
16
+ throw new TaskRunnerError(`Failed to read git status from ${params.gitStatusJsonFile}: ${error.message}`);
17
+ }
18
+ const fileOptions = gitStatusFiles.map((file) => ({
19
+ value: file.file,
20
+ label: file.originalFile
21
+ ? `${file.xy} ${file.originalFile} -> ${file.file}`
22
+ : `${file.xy} ${file.file}`,
23
+ }));
24
+ const form = {
25
+ formId: params.formId,
26
+ title: params.title,
27
+ ...(params.description ? { description: params.description } : {}),
28
+ submitLabel: "Next",
29
+ fields: [
30
+ {
31
+ id: "selected_files",
32
+ type: "multi-select",
33
+ label: "Select files to commit",
34
+ help: "Choose the files you want to include in this commit",
35
+ required: true,
36
+ options: fileOptions,
37
+ default: gitStatusFiles.map((f) => f.file),
38
+ },
39
+ ],
40
+ };
41
+ const requester = context.requestUserInput ?? (await import("../../user-input.js")).requestUserInputInTerminal;
42
+ const result = await requester(form);
43
+ const selectedFilesValue = result.values.selected_files;
44
+ if (!Array.isArray(selectedFilesValue) || selectedFilesValue.length === 0) {
45
+ throw new TaskRunnerError("At least one file must be selected for commit.");
46
+ }
47
+ validateUserInputValues(form, result.values);
48
+ const outputDir = path.dirname(params.outputFile);
49
+ mkdirSync(outputDir, { recursive: true });
50
+ const outputContent = {
51
+ form_id: result.formId,
52
+ submitted_at: result.submittedAt,
53
+ values: result.values,
54
+ };
55
+ writeFileSync(params.outputFile, `${JSON.stringify(outputContent, null, 2)}\n`, "utf8");
56
+ return {
57
+ value: {
58
+ formId: result.formId,
59
+ submittedAt: result.submittedAt,
60
+ values: result.values,
61
+ outputFile: params.outputFile,
62
+ },
63
+ outputs: [
64
+ {
65
+ kind: "artifact",
66
+ path: params.outputFile,
67
+ required: true,
68
+ },
69
+ ],
70
+ };
71
+ },
72
+ };
@@ -0,0 +1,28 @@
1
+ import { printInfo } from "../../tui.js";
2
+ import { toExecutorContext } from "../types.js";
3
+ export const telegramNotifierNode = {
4
+ kind: "telegram-notify",
5
+ version: 1,
6
+ async run(context, params) {
7
+ const labelText = params.label ?? "Sending Telegram notification";
8
+ printInfo(labelText);
9
+ const chatId = context.env.TELEGRAM_CHAT_ID;
10
+ if (!chatId) {
11
+ printInfo("Telegram notification skipped: chat_id environment variable is not set");
12
+ return { value: { success: false } };
13
+ }
14
+ const executor = context.executors.get("telegram-notifier");
15
+ const input = {
16
+ chatId,
17
+ text: params.message,
18
+ };
19
+ const value = await executor.execute(toExecutorContext(context), input, executor.defaultConfig);
20
+ if (value.success) {
21
+ printInfo(`Telegram notification sent successfully`);
22
+ }
23
+ else {
24
+ printInfo(`Telegram notification failed`);
25
+ }
26
+ return { value };
27
+ },
28
+ };
@@ -18,24 +18,45 @@ function buildReviewFixPromptSuffix(params, values) {
18
18
  throw new TaskRunnerError("Review-fix requires selecting at least one finding or enabling 'apply all'.");
19
19
  }
20
20
  const selectionSummary = applyAll
21
- ? "Выбраны все findings."
22
- : `Выбраны findings:\n- ${selectedFindings.join("\n- ")}`;
21
+ ? "All findings selected."
22
+ : `Selected findings:\n- ${selectedFindings.join("\n- ")}`;
23
23
  const promptSuffix = [
24
- "Используй пользовательский выбор ниже как source of truth для scope текущего review-fix.",
25
- `Файл выбора: ${params.outputFile}`,
24
+ "Use the user selection below as source of truth for the current review-fix scope.",
25
+ `Selection file: ${params.outputFile}`,
26
26
  `apply_all: ${applyAll ? "true" : "false"}`,
27
- applyAll ? "Исправляй все findings текущей итерации." : `Исправляй только выбранные findings:\n- ${selectedFindings.join("\n- ")}`,
28
- extraNotes ? `Дополнительные указания пользователя:\n${extraNotes}` : "",
27
+ applyAll ? "Fix all findings in the current iteration." : `Fix only selected findings:\n- ${selectedFindings.join("\n- ")}`,
28
+ extraNotes ? `User additional instructions:\n${extraNotes}` : "",
29
29
  ]
30
30
  .filter((item) => item.trim().length > 0)
31
31
  .join("\n\n");
32
- const summaryText = extraNotes ? `${selectionSummary}\n\nЗаметка:\n${extraNotes}` : selectionSummary;
32
+ const summaryText = extraNotes ? `${selectionSummary}\n\nNote:\n${extraNotes}` : selectionSummary;
33
33
  return { promptSuffix, summaryText };
34
34
  }
35
+ function buildTaskDescribePromptSuffix(params, values) {
36
+ const jiraRef = typeof values.jira_ref === "string" ? values.jira_ref.trim() : "";
37
+ const taskDescription = typeof values.task_description === "string" ? values.task_description.trim() : "";
38
+ if (jiraRef) {
39
+ return {
40
+ promptSuffix: "",
41
+ summaryText: `Источник задачи: Jira\nJira: ${jiraRef}`,
42
+ };
43
+ }
44
+ return {
45
+ promptSuffix: [
46
+ "Use the user task description as source of truth.",
47
+ `User input file: ${params.outputFile}`,
48
+ `Task description:\n${taskDescription}`,
49
+ ].join("\n\n"),
50
+ summaryText: `Task source: user-input\n\n${taskDescription}`,
51
+ };
52
+ }
35
53
  function buildPromptSuffix(params, values) {
36
54
  if (params.formId === "review-fix-selection") {
37
55
  return buildReviewFixPromptSuffix(params, values);
38
56
  }
57
+ if (params.formId === "task-describe-source-input") {
58
+ return buildTaskDescribePromptSuffix(params, values);
59
+ }
39
60
  if (params.fields.length === 0) {
40
61
  return {
41
62
  promptSuffix: "",
@@ -58,7 +79,7 @@ function buildPromptSuffix(params, values) {
58
79
  });
59
80
  const summaryText = lines.join("\n");
60
81
  return {
61
- promptSuffix: `Используй пользовательский ввод из файла ${params.outputFile}.\n\n${summaryText}`,
82
+ promptSuffix: `Use user input from file ${params.outputFile}.\n\n${summaryText}`,
62
83
  summaryText,
63
84
  };
64
85
  }
@@ -0,0 +1,46 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ const SEVERITY_AUTO_SELECT = ["blocker", "critical"];
5
+ export const writeSelectionFileNode = {
6
+ kind: "write-selection-file",
7
+ version: 1,
8
+ async run(_context, params) {
9
+ let parsed;
10
+ try {
11
+ parsed = JSON.parse(readFileSync(params.reviewFindingsJsonFile, "utf8"));
12
+ }
13
+ catch (error) {
14
+ throw new TaskRunnerError(`Failed to read review findings from ${params.reviewFindingsJsonFile}: ${error.message}`);
15
+ }
16
+ const reviewFindings = parsed;
17
+ const findings = Array.isArray(reviewFindings.findings) ? reviewFindings.findings : [];
18
+ const selectedFindings = findings
19
+ .filter((finding) => {
20
+ const severity = typeof finding.severity === "string" ? finding.severity.trim().toLowerCase() : "";
21
+ const disposition = typeof finding.disposition === "string" ? finding.disposition.trim().toLowerCase() : null;
22
+ return SEVERITY_AUTO_SELECT.includes(severity) && disposition !== "resolved" && disposition != null;
23
+ })
24
+ .map((finding) => finding.title)
25
+ .filter((title) => typeof title === "string" && title.trim().length > 0);
26
+ const applyAll = selectedFindings.length === 0;
27
+ const artifact = {
28
+ form_id: "review-fix-selection",
29
+ submitted_at: new Date().toISOString(),
30
+ values: {
31
+ apply_all: applyAll,
32
+ selected_findings: selectedFindings,
33
+ extra_notes: "",
34
+ },
35
+ };
36
+ writeFileSync(params.outputFile, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
37
+ return {
38
+ value: {
39
+ outputFile: params.outputFile,
40
+ findingsCount: selectedFindings.length,
41
+ selectedFindings,
42
+ applyAll,
43
+ },
44
+ };
45
+ },
46
+ };
@@ -1,7 +1,8 @@
1
- import { BUG_ANALYZE_PROMPT_TEMPLATE, GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE, BUG_FIX_PROMPT_TEMPLATE, IMPLEMENT_PROMPT_TEMPLATE, JIRA_DESCRIPTION_PROMPT_TEMPLATE, MR_DESCRIPTION_PROMPT_TEMPLATE, PLAN_QUESTIONS_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROJECT_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_REPLY_PROJECT_PROMPT_TEMPLATE, REVIEW_REPLY_PROMPT_TEMPLATE, REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE, RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, } from "../prompts.js";
1
+ import { BUG_ANALYZE_PROMPT_TEMPLATE, COMMIT_MESSAGE_PROMPT_TEMPLATE, GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE, BUG_FIX_PROMPT_TEMPLATE, IMPLEMENT_PROMPT_TEMPLATE, JIRA_DESCRIPTION_PROMPT_TEMPLATE, MR_DESCRIPTION_PROMPT_TEMPLATE, PLAN_QUESTIONS_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROJECT_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE, RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, } from "../prompts.js";
2
2
  const promptTemplates = {
3
3
  "bug-analyze": BUG_ANALYZE_PROMPT_TEMPLATE,
4
4
  "bug-fix": BUG_FIX_PROMPT_TEMPLATE,
5
+ "commit-message": COMMIT_MESSAGE_PROMPT_TEMPLATE,
5
6
  "gitlab-diff-review": GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE,
6
7
  implement: IMPLEMENT_PROMPT_TEMPLATE,
7
8
  "task-describe": JIRA_DESCRIPTION_PROMPT_TEMPLATE,
@@ -11,9 +12,6 @@ const promptTemplates = {
11
12
  review: REVIEW_PROMPT_TEMPLATE,
12
13
  "review-project": REVIEW_PROJECT_PROMPT_TEMPLATE,
13
14
  "review-fix": REVIEW_FIX_PROMPT_TEMPLATE,
14
- "review-reply": REVIEW_REPLY_PROMPT_TEMPLATE,
15
- "review-reply-project": REVIEW_REPLY_PROJECT_PROMPT_TEMPLATE,
16
- "review-reply-summary": REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE,
17
15
  "review-summary": REVIEW_SUMMARY_PROMPT_TEMPLATE,
18
16
  "run-go-linter-loop-fix": RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE,
19
17
  "run-go-tests-loop-fix": RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE,
@@ -1,5 +1,5 @@
1
1
  import { TaskRunnerError } from "../errors.js";
2
- import { formatPrompt, formatTemplate } from "../prompts.js";
2
+ import { STRUCTURED_JSON_LANGUAGE_INSTRUCTION, formatPrompt, formatTemplate } from "../prompts.js";
3
3
  import { getPromptTemplate } from "./prompt-registry.js";
4
4
  import { resolveValue } from "./value-resolver.js";
5
5
  export function renderPrompt(binding, context) {
@@ -11,8 +11,18 @@ export function renderPrompt(binding, context) {
11
11
  const basePrompt = formatTemplate(baseTemplate, vars);
12
12
  const resolvedExtraPrompt = binding.extraPrompt ? resolveValue(binding.extraPrompt, context) : null;
13
13
  const extraPrompt = resolvedExtraPrompt === null || resolvedExtraPrompt === undefined ? null : String(resolvedExtraPrompt);
14
+ const mdLang = context.flowParams.mdLang;
15
+ const jsonInstruction = STRUCTURED_JSON_LANGUAGE_INSTRUCTION.trim();
16
+ let langInstruction = null;
17
+ if (mdLang === "en") {
18
+ langInstruction = "Generate all markdown output files in English language.";
19
+ }
20
+ else if (mdLang === "ru" || mdLang === null || mdLang === undefined) {
21
+ langInstruction = "Generate all markdown output files in Russian language.";
22
+ }
23
+ const finalExtraPrompt = [extraPrompt, jsonInstruction, langInstruction].filter(Boolean).join("\n");
14
24
  if ((binding.format ?? "task-prompt") === "plain") {
15
- return basePrompt;
25
+ return finalExtraPrompt ? `${basePrompt}\n\n${finalExtraPrompt}` : basePrompt;
16
26
  }
17
- return formatPrompt(basePrompt, extraPrompt);
27
+ return formatPrompt(basePrompt, finalExtraPrompt);
18
28
  }
@@ -1,24 +1,22 @@
1
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";
2
+ import { codexExecutor } from "../executors/codex-executor.js";
5
3
  import { fetchGitLabDiffExecutor } from "../executors/fetch-gitlab-diff-executor.js";
6
4
  import { fetchGitLabReviewExecutor } from "../executors/fetch-gitlab-review-executor.js";
5
+ import { gitCommitExecutor } from "../executors/git-commit-executor.js";
7
6
  import { jiraFetchExecutor } from "../executors/jira-fetch-executor.js";
8
7
  import { opencodeExecutor } from "../executors/opencode-executor.js";
9
8
  import { processExecutor } from "../executors/process-executor.js";
10
- import { verifyBuildExecutor } from "../executors/verify-build-executor.js";
9
+ import { telegramNotifierExecutor } from "../executors/telegram-notifier-executor.js";
11
10
  const builtInExecutors = {
12
11
  process: processExecutor,
13
12
  "command-check": commandCheckExecutor,
14
13
  "fetch-gitlab-diff": fetchGitLabDiffExecutor,
15
14
  "fetch-gitlab-review": fetchGitLabReviewExecutor,
15
+ "git-commit": gitCommitExecutor,
16
16
  "jira-fetch": jiraFetchExecutor,
17
- "codex-local": codexLocalExecutor,
18
- "codex-docker": codexDockerExecutor,
17
+ codex: codexExecutor,
19
18
  opencode: opencodeExecutor,
20
- claude: claudeExecutor,
21
- "verify-build": verifyBuildExecutor,
19
+ "telegram-notifier": telegramNotifierExecutor,
22
20
  };
23
21
  export function createExecutorRegistry() {
24
22
  return {
@@ -54,6 +54,11 @@ function interpolateValueSpec(value, repeatVars) {
54
54
  },
55
55
  };
56
56
  }
57
+ if ("add" in value) {
58
+ return {
59
+ add: value.add.map((candidate) => interpolateValueSpec(candidate, repeatVars)),
60
+ };
61
+ }
57
62
  if ("concat" in value) {
58
63
  return {
59
64
  concat: value.concat.map((candidate) => interpolateValueSpec(candidate, repeatVars)),
@@ -22,20 +22,31 @@ export function listBuiltInFlowSpecFiles() {
22
22
  if (!existsSync(BUILT_IN_FLOW_SPECS_DIR)) {
23
23
  return [];
24
24
  }
25
- return readdirSync(BUILT_IN_FLOW_SPECS_DIR, { withFileTypes: true })
26
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
27
- .map((entry) => entry.name)
25
+ return collectJsonFilesRecursively(BUILT_IN_FLOW_SPECS_DIR)
26
+ .map((filePath) => path.relative(BUILT_IN_FLOW_SPECS_DIR, filePath))
28
27
  .sort((left, right) => left.localeCompare(right));
29
28
  }
29
+ function collectJsonFilesRecursively(directory) {
30
+ const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
31
+ const files = [];
32
+ for (const entry of entries) {
33
+ const entryPath = path.join(directory, entry.name);
34
+ if (entry.isDirectory()) {
35
+ files.push(...collectJsonFilesRecursively(entryPath));
36
+ continue;
37
+ }
38
+ if (entry.isFile() && entry.name.endsWith(".json")) {
39
+ files.push(entryPath);
40
+ }
41
+ }
42
+ return files;
43
+ }
30
44
  export function listProjectFlowSpecFiles(cwd) {
31
45
  const directory = projectFlowSpecsDir(cwd);
32
46
  if (!existsSync(directory)) {
33
47
  return [];
34
48
  }
35
- return readdirSync(directory, { withFileTypes: true })
36
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
37
- .map((entry) => path.join(directory, entry.name))
38
- .sort((left, right) => left.localeCompare(right));
49
+ return collectJsonFilesRecursively(directory);
39
50
  }
40
51
  export function loadFlowSpecSync(source) {
41
52
  return parseFlowSpec(source.source === "built-in" ? resolveBuiltInFlowSpecPath(source.fileName) : source.filePath);
@@ -31,13 +31,17 @@ export const ARTIFACT_REF_KINDS = [
31
31
  "review-json-file",
32
32
  "review-fix-file",
33
33
  "review-fix-json-file",
34
- "review-reply-file",
35
- "review-reply-json-file",
36
34
  "run-go-linter-result-json-file",
37
35
  "run-go-tests-result-json-file",
38
- "review-reply-summary-file",
39
36
  "review-summary-file",
40
37
  "task-summary-file",
41
38
  "task-summary-json-file",
39
+ "task-describe-input-json-file",
40
+ "git-status-json-file",
41
+ "git-diff-file",
42
+ "git-commit-message-json-file",
43
+ "git-commit-input-json-file",
44
+ "select-files-output-json-file",
45
+ "commit-message-output-json-file",
42
46
  ];
43
47
  export const ARTIFACT_LIST_REF_KINDS = ["bug-analyze-artifacts", "plan-artifacts"];
@@ -51,6 +51,10 @@ function validateValueSpec(value, path) {
51
51
  validateValueSpec(value.appendPrompt.suffix, `${path}.appendPrompt.suffix`);
52
52
  return;
53
53
  }
54
+ if ("add" in value) {
55
+ value.add.forEach((candidate, index) => validateValueSpec(candidate, `${path}.add[${index}]`));
56
+ return;
57
+ }
54
58
  if ("concat" in value) {
55
59
  value.concat.forEach((candidate, index) => validateValueSpec(candidate, `${path}.concat[${index}]`));
56
60
  return;
@@ -5,6 +5,7 @@ export function toExecutorContext(context) {
5
5
  ui: context.ui,
6
6
  dryRun: context.dryRun,
7
7
  verbose: context.verbose,
8
+ mdLang: context.mdLang ?? null,
8
9
  runtime: context.runtime,
9
10
  };
10
11
  }
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, gitlabDiffReviewInputJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planningAnswersJsonFile, planningQuestionsJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, reviewReplyFile, reviewReplyJsonFile, runGoLinterResultJsonFile, runGoTestsResultJsonFile, taskSummaryFile, taskSummaryJsonFile, } from "../artifacts.js";
2
+ import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, gitlabDiffReviewInputJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planningAnswersJsonFile, planningQuestionsJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, runGoLinterResultJsonFile, runGoTestsResultJsonFile, taskSummaryFile, taskDescribeInputJsonFile, taskSummaryJsonFile, gitStatusJsonFile, gitCommitMessageJsonFile, gitCommitInputJsonFile, selectFilesOutputJsonFile, commitMessageOutputJsonFile, gitDiffFile as gitDiffFileHelper, } from "../artifacts.js";
3
3
  import { TaskRunnerError } from "../errors.js";
4
4
  import { formatTemplate } from "../prompts.js";
5
5
  function readStepRef(segments, context, originalPath) {
@@ -73,59 +73,59 @@ function resolveArtifact(spec, context) {
73
73
  const iteration = spec.iteration === undefined ? undefined : Number(resolveValue(spec.iteration, context));
74
74
  switch (spec.kind) {
75
75
  case "bug-analyze-file":
76
- return bugAnalyzeFile(taskKey);
76
+ return bugAnalyzeFile(taskKey, iteration);
77
77
  case "bug-analyze-json-file":
78
- return bugAnalyzeJsonFile(taskKey);
78
+ return bugAnalyzeJsonFile(taskKey, iteration);
79
79
  case "bug-fix-design-file":
80
- return bugFixDesignFile(taskKey);
80
+ return bugFixDesignFile(taskKey, iteration);
81
81
  case "bug-fix-design-json-file":
82
- return bugFixDesignJsonFile(taskKey);
82
+ return bugFixDesignJsonFile(taskKey, iteration);
83
83
  case "bug-fix-plan-file":
84
- return bugFixPlanFile(taskKey);
84
+ return bugFixPlanFile(taskKey, iteration);
85
85
  case "bug-fix-plan-json-file":
86
- return bugFixPlanJsonFile(taskKey);
86
+ return bugFixPlanJsonFile(taskKey, iteration);
87
87
  case "design-file":
88
- return designFile(taskKey);
88
+ return designFile(taskKey, iteration);
89
89
  case "design-json-file":
90
- return designJsonFile(taskKey);
90
+ return designJsonFile(taskKey, iteration);
91
91
  case "gitlab-diff-file":
92
- return gitlabDiffFile(taskKey);
92
+ return gitlabDiffFile(taskKey, iteration);
93
93
  case "gitlab-diff-json-file":
94
- return gitlabDiffJsonFile(taskKey);
94
+ return gitlabDiffJsonFile(taskKey, iteration);
95
95
  case "gitlab-diff-review-input-json-file":
96
96
  return gitlabDiffReviewInputJsonFile(taskKey);
97
97
  case "gitlab-review-file":
98
- return gitlabReviewFile(taskKey);
98
+ return gitlabReviewFile(taskKey, iteration);
99
99
  case "gitlab-review-input-json-file":
100
100
  return gitlabReviewInputJsonFile(taskKey);
101
101
  case "gitlab-review-json-file":
102
- return gitlabReviewJsonFile(taskKey);
102
+ return gitlabReviewJsonFile(taskKey, iteration);
103
103
  case "jira-attachments-context-file":
104
104
  return jiraAttachmentsContextFile(taskKey);
105
105
  case "jira-attachments-manifest-file":
106
106
  return jiraAttachmentsManifestFile(taskKey);
107
107
  case "jira-description-file":
108
- return jiraDescriptionFile(taskKey);
108
+ return jiraDescriptionFile(taskKey, iteration);
109
109
  case "jira-description-json-file":
110
- return jiraDescriptionJsonFile(taskKey);
110
+ return jiraDescriptionJsonFile(taskKey, iteration);
111
111
  case "jira-task-file":
112
112
  return jiraTaskFile(taskKey);
113
113
  case "mr-description-file":
114
- return mrDescriptionFile(taskKey);
114
+ return mrDescriptionFile(taskKey, iteration);
115
115
  case "mr-description-json-file":
116
- return mrDescriptionJsonFile(taskKey);
116
+ return mrDescriptionJsonFile(taskKey, iteration);
117
117
  case "planning-answers-json-file":
118
118
  return planningAnswersJsonFile(taskKey);
119
119
  case "planning-questions-json-file":
120
120
  return planningQuestionsJsonFile(taskKey);
121
121
  case "plan-file":
122
- return planFile(taskKey);
122
+ return planFile(taskKey, iteration);
123
123
  case "plan-json-file":
124
- return planJsonFile(taskKey);
124
+ return planJsonFile(taskKey, iteration);
125
125
  case "qa-file":
126
- return qaFile(taskKey);
126
+ return qaFile(taskKey, iteration);
127
127
  case "qa-json-file":
128
- return qaJsonFile(taskKey);
128
+ return qaJsonFile(taskKey, iteration);
129
129
  case "ready-to-merge-file":
130
130
  return readyToMergeFile(taskKey);
131
131
  case "review-file":
@@ -148,16 +148,6 @@ function resolveArtifact(spec, context) {
148
148
  throw new TaskRunnerError("review-fix-json-file requires iteration");
149
149
  }
150
150
  return reviewFixJsonFile(taskKey, iteration);
151
- case "review-reply-file":
152
- if (iteration === undefined) {
153
- throw new TaskRunnerError("review-reply-file requires iteration");
154
- }
155
- return reviewReplyFile(taskKey, iteration);
156
- case "review-reply-json-file":
157
- if (iteration === undefined) {
158
- throw new TaskRunnerError("review-reply-json-file requires iteration");
159
- }
160
- return reviewReplyJsonFile(taskKey, iteration);
161
151
  case "run-go-linter-result-json-file":
162
152
  if (iteration === undefined) {
163
153
  throw new TaskRunnerError("run-go-linter-result-json-file requires iteration");
@@ -168,20 +158,29 @@ function resolveArtifact(spec, context) {
168
158
  throw new TaskRunnerError("run-go-tests-result-json-file requires iteration");
169
159
  }
170
160
  return runGoTestsResultJsonFile(taskKey, iteration);
171
- case "review-reply-summary-file":
172
- if (iteration === undefined) {
173
- throw new TaskRunnerError("review-reply-summary-file requires iteration");
174
- }
175
- return artifactFile("review-reply-summary", taskKey, iteration);
176
161
  case "review-summary-file":
177
162
  if (iteration === undefined) {
178
163
  throw new TaskRunnerError("review-summary-file requires iteration");
179
164
  }
180
165
  return artifactFile("review-summary", taskKey, iteration);
181
166
  case "task-summary-file":
182
- return taskSummaryFile(taskKey);
167
+ return taskSummaryFile(taskKey, iteration);
183
168
  case "task-summary-json-file":
184
- return taskSummaryJsonFile(taskKey);
169
+ return taskSummaryJsonFile(taskKey, iteration);
170
+ case "task-describe-input-json-file":
171
+ return taskDescribeInputJsonFile(taskKey);
172
+ case "git-status-json-file":
173
+ return gitStatusJsonFile(taskKey);
174
+ case "git-diff-file":
175
+ return gitDiffFileHelper(taskKey);
176
+ case "git-commit-message-json-file":
177
+ return gitCommitMessageJsonFile(taskKey);
178
+ case "git-commit-input-json-file":
179
+ return gitCommitInputJsonFile(taskKey);
180
+ case "select-files-output-json-file":
181
+ return selectFilesOutputJsonFile(taskKey);
182
+ case "commit-message-output-json-file":
183
+ return commitMessageOutputJsonFile(taskKey);
185
184
  }
186
185
  }
187
186
  function resolveArtifactList(spec, context) {
@@ -223,6 +222,9 @@ export function resolveValue(value, context) {
223
222
  }
224
223
  return `${baseText}\n${suffixText}`;
225
224
  }
225
+ if ("add" in value) {
226
+ return value.add.reduce((sum, candidate) => sum + Number(resolveValue(candidate, context)), 0);
227
+ }
226
228
  if ("concat" in value) {
227
229
  return value.concat
228
230
  .map((candidate) => resolveValue(candidate, context))