agentweaver 0.1.10 → 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 (106) hide show
  1. package/README.md +218 -224
  2. package/dist/artifacts.js +100 -55
  3. package/dist/executors/{codex-local-executor.js → codex-executor.js} +4 -4
  4. package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
  5. package/dist/executors/configs/jira-fetch-config.js +2 -0
  6. package/dist/executors/configs/telegram-notifier-config.js +3 -0
  7. package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
  8. package/dist/executors/fetch-gitlab-review-executor.js +1 -1
  9. package/dist/executors/git-commit-executor.js +25 -0
  10. package/dist/executors/telegram-notifier-executor.js +54 -0
  11. package/dist/flow-state.js +46 -1
  12. package/dist/gitlab.js +13 -8
  13. package/dist/index.js +469 -514
  14. package/dist/interactive-ui.js +144 -43
  15. package/dist/jira.js +52 -5
  16. package/dist/pipeline/auto-flow.js +6 -6
  17. package/dist/pipeline/context.js +1 -0
  18. package/dist/pipeline/flow-catalog.js +34 -4
  19. package/dist/pipeline/flow-model-settings.js +77 -0
  20. package/dist/pipeline/flow-specs/auto-common.json +446 -0
  21. package/dist/pipeline/flow-specs/auto-golang.json +563 -0
  22. package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
  23. package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
  24. package/dist/pipeline/flow-specs/git-commit.json +196 -0
  25. package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
  26. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +165 -0
  27. package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
  28. package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
  29. package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
  30. package/dist/pipeline/flow-specs/implement.json +5 -4
  31. package/dist/pipeline/flow-specs/plan.json +40 -148
  32. package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +73 -13
  33. package/dist/pipeline/flow-specs/review/review-loop.json +280 -0
  34. package/dist/pipeline/flow-specs/review/review-project.json +87 -0
  35. package/dist/pipeline/flow-specs/review/review.json +126 -0
  36. package/dist/pipeline/flow-specs/task-describe.json +191 -11
  37. package/dist/pipeline/launch-profile-config.js +38 -0
  38. package/dist/pipeline/node-registry.js +75 -45
  39. package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
  40. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
  41. package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
  42. package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
  43. package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
  44. package/dist/pipeline/nodes/git-commit-node.js +28 -0
  45. package/dist/pipeline/nodes/git-status-node.js +221 -0
  46. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
  47. package/dist/pipeline/nodes/jira-context-node.js +10 -0
  48. package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
  49. package/dist/pipeline/nodes/plan-codex-node.js +1 -1
  50. package/dist/pipeline/nodes/read-file-node.js +11 -0
  51. package/dist/pipeline/nodes/review-findings-form-node.js +18 -14
  52. package/dist/pipeline/nodes/select-files-form-node.js +72 -0
  53. package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
  54. package/dist/pipeline/nodes/user-input-node.js +29 -8
  55. package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
  56. package/dist/pipeline/prompt-registry.js +2 -4
  57. package/dist/pipeline/prompt-runtime.js +13 -3
  58. package/dist/pipeline/registry.js +6 -8
  59. package/dist/pipeline/spec-compiler.js +5 -0
  60. package/dist/pipeline/spec-types.js +7 -3
  61. package/dist/pipeline/spec-validator.js +4 -0
  62. package/dist/pipeline/types.js +1 -0
  63. package/dist/pipeline/value-resolver.js +40 -38
  64. package/dist/prompts.js +104 -110
  65. package/dist/runtime/agentweaver-home.js +8 -0
  66. package/dist/runtime/command-resolution.js +0 -38
  67. package/dist/runtime/env-loader.js +43 -0
  68. package/dist/structured-artifact-schema-registry.js +53 -0
  69. package/dist/structured-artifact-schemas.json +0 -20
  70. package/dist/structured-artifacts.js +3 -43
  71. package/dist/user-input.js +30 -2
  72. package/package.json +2 -6
  73. package/Dockerfile.codex +0 -56
  74. package/dist/executors/claude-executor.js +0 -46
  75. package/dist/executors/codex-docker-executor.js +0 -27
  76. package/dist/executors/configs/claude-config.js +0 -12
  77. package/dist/executors/configs/codex-docker-config.js +0 -10
  78. package/dist/executors/configs/verify-build-config.js +0 -7
  79. package/dist/executors/verify-build-executor.js +0 -123
  80. package/dist/pipeline/flow-specs/auto.json +0 -979
  81. package/dist/pipeline/flow-specs/gitlab-review.json +0 -317
  82. package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
  83. package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
  84. package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
  85. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
  86. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
  87. package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
  88. package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
  89. package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
  90. package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
  91. package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
  92. package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
  93. package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
  94. package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
  95. package/dist/pipeline/flow-specs/review-project.json +0 -243
  96. package/dist/pipeline/flow-specs/review.json +0 -312
  97. package/dist/pipeline/flows/preflight-flow.js +0 -19
  98. package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
  99. package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
  100. package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
  101. package/dist/pipeline/nodes/review-claude-node.js +0 -38
  102. package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
  103. package/dist/pipeline/nodes/verify-build-node.js +0 -15
  104. package/dist/runtime/docker-runtime.js +0 -51
  105. package/docker-compose.yml +0 -445
  106. package/verify_build.sh +0 -105
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { printInfo, printPrompt, printSummary } from "../../tui.js";
3
+ import { toExecutorContext } from "../types.js";
4
+ export const codexPromptNode = {
5
+ kind: "codex-prompt",
6
+ version: 1,
7
+ async run(context, params) {
8
+ printInfo(params.labelText);
9
+ printPrompt("Codex", params.prompt);
10
+ const executor = context.executors.get("codex");
11
+ const value = await executor.execute(toExecutorContext(context), {
12
+ prompt: params.prompt,
13
+ ...(params.model ? { model: params.model } : {}),
14
+ env: { ...context.env },
15
+ }, executor.defaultConfig);
16
+ const outputPaths = Array.from(new Set([...(params.requiredArtifacts ?? []), ...(params.outputFile ? [params.outputFile] : [])]));
17
+ if (params.outputFile && params.summaryTitle) {
18
+ const summaryText = readFileSync(params.outputFile, "utf8").trim();
19
+ context.setSummary?.(summaryText);
20
+ printSummary(params.summaryTitle, summaryText);
21
+ }
22
+ return {
23
+ value,
24
+ outputs: outputPaths.map((path) => ({ kind: "artifact", path, required: true })),
25
+ };
26
+ },
27
+ checks(_context, params) {
28
+ const requiredPaths = Array.from(new Set([...(params.requiredArtifacts ?? []), ...(params.outputFile ? [params.outputFile] : [])]));
29
+ if (requiredPaths.length === 0) {
30
+ return [];
31
+ }
32
+ return [
33
+ {
34
+ kind: "require-artifacts",
35
+ paths: requiredPaths,
36
+ message: params.missingArtifactsMessage
37
+ ?? (params.outputFile ? `Codex node did not produce ${params.outputFile}.` : "Codex node did not produce required artifacts."),
38
+ },
39
+ ];
40
+ },
41
+ };
@@ -0,0 +1,79 @@
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
+ function parseCommitMessage(content) {
6
+ try {
7
+ const parsed = JSON.parse(content);
8
+ if (typeof parsed === "object" && parsed !== null) {
9
+ return {
10
+ subject: typeof parsed.subject === "string" ? parsed.subject : "",
11
+ };
12
+ }
13
+ }
14
+ catch {
15
+ // Fallback to plain text
16
+ }
17
+ return { subject: content.trim() };
18
+ }
19
+ export const commitMessageFormNode = {
20
+ kind: "commit-message-form",
21
+ version: 1,
22
+ async run(context, params) {
23
+ let commitMessage;
24
+ try {
25
+ const messageContent = readFileSync(params.commitMessageFile, "utf8");
26
+ commitMessage = parseCommitMessage(messageContent);
27
+ }
28
+ catch (error) {
29
+ throw new TaskRunnerError(`Failed to read commit message from ${params.commitMessageFile}: ${error.message}`);
30
+ }
31
+ const form = {
32
+ formId: params.formId,
33
+ title: params.title,
34
+ ...(params.description ? { description: params.description } : {}),
35
+ submitLabel: "Commit",
36
+ fields: [
37
+ {
38
+ id: "commit_message",
39
+ type: "text",
40
+ label: "Commit message",
41
+ help: "Format: {taskKey}: {taskDescription}. Subject ≤72 chars.",
42
+ required: true,
43
+ multiline: false,
44
+ default: commitMessage.subject,
45
+ },
46
+ ],
47
+ };
48
+ const requester = context.requestUserInput ?? (await import("../../user-input.js")).requestUserInputInTerminal;
49
+ const result = await requester(form);
50
+ const commitMessageValue = result.values.commit_message;
51
+ if (typeof commitMessageValue !== "string" || commitMessageValue.trim().length === 0) {
52
+ throw new TaskRunnerError("Commit message is required.");
53
+ }
54
+ validateUserInputValues(form, result.values);
55
+ const outputDir = path.dirname(params.outputFile);
56
+ mkdirSync(outputDir, { recursive: true });
57
+ const outputContent = {
58
+ form_id: result.formId,
59
+ submitted_at: result.submittedAt,
60
+ values: result.values,
61
+ };
62
+ writeFileSync(params.outputFile, `${JSON.stringify(outputContent, null, 2)}\n`, "utf8");
63
+ return {
64
+ value: {
65
+ formId: result.formId,
66
+ submittedAt: result.submittedAt,
67
+ values: result.values,
68
+ outputFile: params.outputFile,
69
+ },
70
+ outputs: [
71
+ {
72
+ kind: "artifact",
73
+ path: params.outputFile,
74
+ required: true,
75
+ },
76
+ ],
77
+ };
78
+ },
79
+ };
@@ -0,0 +1,138 @@
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
+ function parseCommitMessage(content) {
6
+ try {
7
+ const parsed = JSON.parse(content);
8
+ if (typeof parsed === "object" && parsed !== null) {
9
+ const result = {
10
+ subject: typeof parsed.subject === "string" ? parsed.subject : "",
11
+ };
12
+ if (typeof parsed.body === "string" && parsed.body.trim().length > 0) {
13
+ result.body = parsed.body.trim();
14
+ }
15
+ return result;
16
+ }
17
+ }
18
+ catch {
19
+ // Fallback to plain text
20
+ }
21
+ return { subject: content.trim(), body: "" };
22
+ }
23
+ export const gitCommitFormNode = {
24
+ kind: "git-commit-form",
25
+ version: 1,
26
+ async run(context, params) {
27
+ let gitStatusFiles;
28
+ try {
29
+ const statusContent = readFileSync(params.gitStatusJsonFile, "utf8");
30
+ const statusParsed = JSON.parse(statusContent);
31
+ gitStatusFiles = (statusParsed.files ?? []);
32
+ }
33
+ catch (error) {
34
+ throw new TaskRunnerError(`Failed to read git status from ${params.gitStatusJsonFile}: ${error.message}`);
35
+ }
36
+ let commitMessage;
37
+ try {
38
+ const messageContent = readFileSync(params.commitMessageFile, "utf8");
39
+ commitMessage = parseCommitMessage(messageContent);
40
+ }
41
+ catch (error) {
42
+ throw new TaskRunnerError(`Failed to read commit message from ${params.commitMessageFile}: ${error.message}`);
43
+ }
44
+ const fileOptions = gitStatusFiles.map((file) => ({
45
+ value: file.file,
46
+ label: file.originalFile
47
+ ? `${file.xy} ${file.originalFile} -> ${file.file}`
48
+ : `${file.xy} ${file.file}`,
49
+ }));
50
+ const defaultCommitMessage = commitMessage.body
51
+ ? `${commitMessage.subject}\n\n${commitMessage.body}`
52
+ : commitMessage.subject;
53
+ const form = {
54
+ formId: params.formId,
55
+ title: params.title,
56
+ ...(params.description ? { description: params.description } : {}),
57
+ submitLabel: "Commit",
58
+ fields: [
59
+ {
60
+ id: "selected_files",
61
+ type: "multi-select",
62
+ label: "Select files to commit",
63
+ help: "Choose the files you want to include in this commit",
64
+ required: true,
65
+ options: fileOptions,
66
+ default: gitStatusFiles.map((f) => f.file),
67
+ },
68
+ {
69
+ id: "commit_message",
70
+ type: "text",
71
+ label: "Commit message",
72
+ help: "Edit the commit message if needed. Subject ≤72 chars, conventional commits format.",
73
+ required: true,
74
+ multiline: true,
75
+ rows: 8,
76
+ default: defaultCommitMessage,
77
+ },
78
+ {
79
+ id: "edit_in_editor",
80
+ type: "boolean",
81
+ label: "Edit message in editor",
82
+ help: "Open your EDITOR to edit the commit message before committing",
83
+ required: false,
84
+ default: false,
85
+ },
86
+ {
87
+ id: "confirm",
88
+ type: "boolean",
89
+ label: "Confirm commit",
90
+ help: "Set to true to proceed with the commit",
91
+ required: true,
92
+ default: true,
93
+ },
94
+ ],
95
+ };
96
+ const requester = context.requestUserInput ?? (await import("../../user-input.js")).requestUserInputInTerminal;
97
+ const result = await requester(form);
98
+ const commitMessageValue = result.values.commit_message;
99
+ const selectedFilesValue = result.values.selected_files;
100
+ const confirmValue = result.values.confirm;
101
+ if (!Array.isArray(selectedFilesValue) || selectedFilesValue.length === 0) {
102
+ throw new TaskRunnerError("At least one file must be selected for commit.");
103
+ }
104
+ if (typeof commitMessageValue !== "string" || commitMessageValue.trim().length === 0) {
105
+ throw new TaskRunnerError("Commit message is required.");
106
+ }
107
+ if (typeof confirmValue !== "boolean" || !confirmValue) {
108
+ throw new TaskRunnerError("Commit must be confirmed.");
109
+ }
110
+ validateUserInputValues(form, result.values);
111
+ const outputDir = path.dirname(params.outputFile);
112
+ mkdirSync(outputDir, { recursive: true });
113
+ const editInEditorValue = result.values.edit_in_editor === true;
114
+ const outputContent = {
115
+ form_id: result.formId,
116
+ submitted_at: result.submittedAt,
117
+ values: result.values,
118
+ edit_in_editor: editInEditorValue,
119
+ };
120
+ writeFileSync(params.outputFile, `${JSON.stringify(outputContent, null, 2)}\n`, "utf8");
121
+ return {
122
+ value: {
123
+ formId: result.formId,
124
+ submittedAt: result.submittedAt,
125
+ values: result.values,
126
+ outputFile: params.outputFile,
127
+ editInEditor: editInEditorValue,
128
+ },
129
+ outputs: [
130
+ {
131
+ kind: "artifact",
132
+ path: params.outputFile,
133
+ required: true,
134
+ },
135
+ ],
136
+ };
137
+ },
138
+ };
@@ -0,0 +1,28 @@
1
+ import { printInfo } from "../../tui.js";
2
+ import { toExecutorContext } from "../types.js";
3
+ export const gitCommitNode = {
4
+ kind: "git-commit",
5
+ version: 1,
6
+ async run(context, params) {
7
+ printInfo(params.labelText ?? "Committing changes");
8
+ if (context.dryRun) {
9
+ printInfo("DRY RUN: git commit не выполнен");
10
+ return {
11
+ value: {
12
+ output: "",
13
+ commitHash: null,
14
+ },
15
+ };
16
+ }
17
+ const executor = context.executors.get("git-commit");
18
+ const executorInput = {
19
+ message: params.message,
20
+ files: params.files,
21
+ };
22
+ if (params.editInEditor) {
23
+ executorInput.editEnabled = true;
24
+ }
25
+ const value = await executor.execute(toExecutorContext(context), executorInput, executor.defaultConfig);
26
+ return { value };
27
+ },
28
+ };
@@ -0,0 +1,221 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ import { printInfo } from "../../tui.js";
5
+ function unquoteGitPath(s) {
6
+ if (s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') {
7
+ return s;
8
+ }
9
+ const inner = s.slice(1, -1);
10
+ const decoder = new TextDecoder();
11
+ let result = "";
12
+ const byteBuf = [];
13
+ const flushBytes = () => {
14
+ if (byteBuf.length > 0) {
15
+ result += decoder.decode(new Uint8Array(byteBuf));
16
+ byteBuf.length = 0;
17
+ }
18
+ };
19
+ for (let i = 0; i < inner.length; i++) {
20
+ if (inner[i] === "\\" && i + 1 < inner.length) {
21
+ const next = inner[i + 1];
22
+ switch (next) {
23
+ case "\\":
24
+ flushBytes();
25
+ result += "\\";
26
+ i++;
27
+ break;
28
+ case '"':
29
+ flushBytes();
30
+ result += '"';
31
+ i++;
32
+ break;
33
+ case "a":
34
+ flushBytes();
35
+ result += "\x07";
36
+ i++;
37
+ break;
38
+ case "b":
39
+ flushBytes();
40
+ result += "\b";
41
+ i++;
42
+ break;
43
+ case "f":
44
+ flushBytes();
45
+ result += "\f";
46
+ i++;
47
+ break;
48
+ case "n":
49
+ flushBytes();
50
+ result += "\n";
51
+ i++;
52
+ break;
53
+ case "r":
54
+ flushBytes();
55
+ result += "\r";
56
+ i++;
57
+ break;
58
+ case "t":
59
+ flushBytes();
60
+ result += "\t";
61
+ i++;
62
+ break;
63
+ case "v":
64
+ flushBytes();
65
+ result += "\v";
66
+ i++;
67
+ break;
68
+ default: {
69
+ if (next >= "0" && next <= "7") {
70
+ let octal = next;
71
+ let consumed = 0;
72
+ for (let j = 1; j <= 2 && i + 1 + j < inner.length; j++) {
73
+ const ch = inner[i + 1 + j];
74
+ if (ch !== undefined && ch >= "0" && ch <= "7") {
75
+ octal += ch;
76
+ consumed = j;
77
+ }
78
+ else {
79
+ break;
80
+ }
81
+ }
82
+ byteBuf.push(parseInt(octal, 8) & 0xff);
83
+ i += 1 + consumed;
84
+ }
85
+ else {
86
+ flushBytes();
87
+ result += inner[i];
88
+ }
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ else {
94
+ flushBytes();
95
+ result += inner[i];
96
+ }
97
+ }
98
+ flushBytes();
99
+ return result;
100
+ }
101
+ function splitRename(raw) {
102
+ let inQuote = false;
103
+ for (let i = 0; i <= raw.length - 4; i++) {
104
+ if (raw[i] === "\\" && inQuote) {
105
+ i++;
106
+ continue;
107
+ }
108
+ if (raw[i] === '"') {
109
+ inQuote = !inQuote;
110
+ continue;
111
+ }
112
+ if (!inQuote && raw.slice(i, i + 4) === " -> ") {
113
+ return { original: raw.slice(0, i), file: raw.slice(i + 4) };
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ export function parsePorcelain(output) {
119
+ const lines = output.split(/\r?\n/);
120
+ const files = [];
121
+ for (const line of lines) {
122
+ if (!line.trim()) {
123
+ continue;
124
+ }
125
+ const xy = line.slice(0, 2);
126
+ const rawFile = line.slice(3);
127
+ const indexStatus = xy[0] ?? " ";
128
+ const workTreeStatus = xy[1] ?? " ";
129
+ const staged = indexStatus !== " " && indexStatus !== "?";
130
+ let type;
131
+ if (indexStatus === "A" || workTreeStatus === "A") {
132
+ type = "added";
133
+ }
134
+ else if (indexStatus === "D" || workTreeStatus === "D") {
135
+ type = "deleted";
136
+ }
137
+ else if (indexStatus === "R") {
138
+ type = "renamed";
139
+ }
140
+ else if (indexStatus === "?" && workTreeStatus === "?") {
141
+ type = "untracked";
142
+ }
143
+ else {
144
+ type = "modified";
145
+ }
146
+ let file;
147
+ let originalFile;
148
+ if (indexStatus === "R" || indexStatus === "C") {
149
+ const parts = splitRename(rawFile);
150
+ if (parts) {
151
+ originalFile = unquoteGitPath(parts.original);
152
+ file = unquoteGitPath(parts.file);
153
+ }
154
+ else {
155
+ file = unquoteGitPath(rawFile);
156
+ }
157
+ }
158
+ else {
159
+ file = unquoteGitPath(rawFile);
160
+ }
161
+ files.push({
162
+ xy,
163
+ indexStatus,
164
+ workTreeStatus,
165
+ file,
166
+ ...(originalFile !== undefined ? { originalFile } : {}),
167
+ staged,
168
+ type,
169
+ });
170
+ }
171
+ return files;
172
+ }
173
+ function persistGitStatus(filePath, result) {
174
+ const dir = path.dirname(filePath);
175
+ mkdirSync(dir, { recursive: true });
176
+ writeFileSync(filePath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
177
+ }
178
+ export const gitStatusNode = {
179
+ kind: "git-status",
180
+ version: 1,
181
+ async run(context, params) {
182
+ printInfo(params.labelText ?? "Collecting git status");
183
+ const porcelainOutput = await context.runtime.runCommand(["git", "status", "--porcelain"], {
184
+ dryRun: context.dryRun,
185
+ verbose: context.verbose,
186
+ label: "git status",
187
+ });
188
+ const files = parsePorcelain(porcelainOutput);
189
+ if (files.length === 0) {
190
+ throw new TaskRunnerError("Нет изменённых файлов для коммита.");
191
+ }
192
+ const diff = await context.runtime.runCommand(["git", "diff"], {
193
+ dryRun: context.dryRun,
194
+ verbose: context.verbose,
195
+ label: "git diff",
196
+ });
197
+ const stagedDiff = await context.runtime.runCommand(["git", "diff", "--cached"], {
198
+ dryRun: context.dryRun,
199
+ verbose: context.verbose,
200
+ label: "git diff --cached",
201
+ });
202
+ const fullDiff = stagedDiff + diff;
203
+ const diffStat = await context.runtime.runCommand(["git", "diff", "--stat"], {
204
+ dryRun: context.dryRun,
205
+ verbose: context.verbose,
206
+ label: "git diff --stat",
207
+ });
208
+ const result = {
209
+ files,
210
+ diff: fullDiff,
211
+ diffStat,
212
+ };
213
+ writeFileSync(params.outputFile, `${JSON.stringify(result, null, 2)}\n`, "utf8");
214
+ if (params.diffOutputFile) {
215
+ writeFileSync(params.diffOutputFile, fullDiff, "utf8");
216
+ }
217
+ return {
218
+ value: result,
219
+ };
220
+ },
221
+ };
@@ -1,6 +1,9 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { TaskRunnerError } from "../../errors.js";
4
+ function normalizeMarkdownLanguage(mdLang) {
5
+ return mdLang === "en" ? "en" : "ru";
6
+ }
4
7
  function commentLine(comment) {
5
8
  if (typeof comment.new_line === "number") {
6
9
  return comment.new_line;
@@ -44,7 +47,7 @@ function toReviewFinding(comment, index) {
44
47
  .join("\n"),
45
48
  };
46
49
  }
47
- function renderReviewMarkdown(artifact) {
50
+ function renderReviewMarkdown(artifact, mdLang) {
48
51
  const lines = [
49
52
  "# Review",
50
53
  "",
@@ -54,7 +57,7 @@ function renderReviewMarkdown(artifact) {
54
57
  "",
55
58
  ];
56
59
  if (artifact.findings.length === 0) {
57
- lines.push("Замечаний не найдено.");
60
+ lines.push(mdLang === "en" ? "No findings found." : "Замечаний не найдено.");
58
61
  return lines.join("\n");
59
62
  }
60
63
  artifact.findings.forEach((finding, index) => {
@@ -70,7 +73,7 @@ function renderReviewMarkdown(artifact) {
70
73
  export const gitlabReviewArtifactsNode = {
71
74
  kind: "gitlab-review-artifacts",
72
75
  version: 1,
73
- async run(_context, params) {
76
+ async run(context, params) {
74
77
  let parsed;
75
78
  try {
76
79
  parsed = JSON.parse(readFileSync(params.gitlabReviewJsonFile, "utf8"));
@@ -84,13 +87,14 @@ export const gitlabReviewArtifactsNode = {
84
87
  .filter((finding) => finding !== null);
85
88
  const artifact = {
86
89
  summary: findings.length > 0
87
- ? `Импортировано ${findings.length} комментариев код-ревью из GitLab.`
88
- : "Комментарии код-ревью в GitLab не найдены.",
90
+ ? `Imported ${findings.length} GitLab code review comments.`
91
+ : "No GitLab code review comments found.",
89
92
  ready_to_merge: findings.length === 0,
90
93
  findings,
91
94
  };
95
+ const mdLang = normalizeMarkdownLanguage(context.mdLang);
92
96
  writeFileSync(params.reviewJsonFile, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
93
- writeFileSync(params.reviewFile, `${renderReviewMarkdown(artifact)}\n`, "utf8");
97
+ writeFileSync(params.reviewFile, `${renderReviewMarkdown(artifact, mdLang)}\n`, "utf8");
94
98
  return {
95
99
  value: {
96
100
  findingsCount: findings.length,
@@ -0,0 +1,10 @@
1
+ import { parseJiraContext } from "../../scope.js";
2
+ export const jiraContextNode = {
3
+ kind: "jira-context",
4
+ version: 1,
5
+ async run(_context, params) {
6
+ return {
7
+ value: parseJiraContext(params.jiraRef),
8
+ };
9
+ },
10
+ };
@@ -0,0 +1,62 @@
1
+ import { TaskRunnerError } from "../../errors.js";
2
+ import { printInfo, printPrompt } from "../../tui.js";
3
+ import { isAllowedModelForExecutor, isLlmExecutorId, } from "../launch-profile-config.js";
4
+ import { toExecutorContext } from "../types.js";
5
+ export const llmPromptNode = {
6
+ kind: "llm-prompt",
7
+ version: 1,
8
+ async run(context, params) {
9
+ if (!isLlmExecutorId(params.executor)) {
10
+ throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
11
+ }
12
+ if (params.model && !isAllowedModelForExecutor(params.executor, params.model)) {
13
+ throw new TaskRunnerError(`Model '${params.model}' is not allowed for executor '${params.executor}'.`);
14
+ }
15
+ printInfo(params.labelText);
16
+ printPrompt(`LLM:${params.executor}`, params.prompt);
17
+ const executorContext = toExecutorContext(context);
18
+ if (params.executor === "codex") {
19
+ const executor = context.executors.get("codex");
20
+ const value = await executor.execute(executorContext, {
21
+ prompt: params.prompt,
22
+ ...(params.model ? { model: params.model } : {}),
23
+ env: { ...context.env },
24
+ }, executor.defaultConfig);
25
+ return {
26
+ value: {
27
+ ...value,
28
+ executor: "codex",
29
+ },
30
+ outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
31
+ };
32
+ }
33
+ if (params.executor === "opencode") {
34
+ const executor = context.executors.get("opencode");
35
+ const value = await executor.execute(executorContext, {
36
+ prompt: params.prompt,
37
+ ...(params.model ? { model: params.model } : {}),
38
+ env: { ...context.env },
39
+ }, executor.defaultConfig);
40
+ return {
41
+ value: {
42
+ ...value,
43
+ executor: "opencode",
44
+ },
45
+ outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
46
+ };
47
+ }
48
+ throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
49
+ },
50
+ checks(_context, params) {
51
+ if (!params.requiredArtifacts || params.requiredArtifacts.length === 0) {
52
+ return [];
53
+ }
54
+ return [
55
+ {
56
+ kind: "require-artifacts",
57
+ paths: params.requiredArtifacts,
58
+ message: params.missingArtifactsMessage ?? "LLM prompt node did not produce required artifacts.",
59
+ },
60
+ ];
61
+ },
62
+ };
@@ -6,7 +6,7 @@ export const planCodexNode = {
6
6
  async run(context, params) {
7
7
  printInfo("Running Codex planning mode");
8
8
  printPrompt("Codex", params.prompt);
9
- const executor = context.executors.get("codex-local");
9
+ const executor = context.executors.get("codex");
10
10
  const input = {
11
11
  prompt: params.prompt,
12
12
  env: { ...context.env },
@@ -0,0 +1,11 @@
1
+ import { readFileSync } from "node:fs";
2
+ export const readFileNode = {
3
+ kind: "read-file",
4
+ version: 1,
5
+ async run(_context, params) {
6
+ const text = readFileSync(params.path, "utf8").trim();
7
+ return {
8
+ value: { text },
9
+ };
10
+ },
11
+ };