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.
- package/README.md +218 -224
- package/dist/artifacts.js +100 -55
- package/dist/executors/{codex-local-executor.js → codex-executor.js} +4 -4
- package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
- package/dist/executors/configs/jira-fetch-config.js +2 -0
- package/dist/executors/configs/telegram-notifier-config.js +3 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
- package/dist/executors/fetch-gitlab-review-executor.js +1 -1
- package/dist/executors/git-commit-executor.js +25 -0
- package/dist/executors/telegram-notifier-executor.js +54 -0
- package/dist/flow-state.js +46 -1
- package/dist/gitlab.js +13 -8
- package/dist/index.js +469 -514
- package/dist/interactive-ui.js +144 -43
- package/dist/jira.js +52 -5
- package/dist/pipeline/auto-flow.js +6 -6
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/flow-catalog.js +34 -4
- package/dist/pipeline/flow-model-settings.js +77 -0
- package/dist/pipeline/flow-specs/auto-common.json +446 -0
- package/dist/pipeline/flow-specs/auto-golang.json +563 -0
- package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
- package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
- package/dist/pipeline/flow-specs/git-commit.json +196 -0
- package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +165 -0
- package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
- package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
- package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
- package/dist/pipeline/flow-specs/implement.json +5 -4
- package/dist/pipeline/flow-specs/plan.json +40 -148
- package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +73 -13
- package/dist/pipeline/flow-specs/review/review-loop.json +280 -0
- package/dist/pipeline/flow-specs/review/review-project.json +87 -0
- package/dist/pipeline/flow-specs/review/review.json +126 -0
- package/dist/pipeline/flow-specs/task-describe.json +191 -11
- package/dist/pipeline/launch-profile-config.js +38 -0
- package/dist/pipeline/node-registry.js +75 -45
- package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
- package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
- package/dist/pipeline/nodes/git-commit-node.js +28 -0
- package/dist/pipeline/nodes/git-status-node.js +221 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
- package/dist/pipeline/nodes/jira-context-node.js +10 -0
- package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
- package/dist/pipeline/nodes/plan-codex-node.js +1 -1
- package/dist/pipeline/nodes/read-file-node.js +11 -0
- package/dist/pipeline/nodes/review-findings-form-node.js +18 -14
- package/dist/pipeline/nodes/select-files-form-node.js +72 -0
- package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
- package/dist/pipeline/nodes/user-input-node.js +29 -8
- package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
- package/dist/pipeline/prompt-registry.js +2 -4
- package/dist/pipeline/prompt-runtime.js +13 -3
- package/dist/pipeline/registry.js +6 -8
- package/dist/pipeline/spec-compiler.js +5 -0
- package/dist/pipeline/spec-types.js +7 -3
- package/dist/pipeline/spec-validator.js +4 -0
- package/dist/pipeline/types.js +1 -0
- package/dist/pipeline/value-resolver.js +40 -38
- package/dist/prompts.js +104 -110
- package/dist/runtime/agentweaver-home.js +8 -0
- package/dist/runtime/command-resolution.js +0 -38
- package/dist/runtime/env-loader.js +43 -0
- package/dist/structured-artifact-schema-registry.js +53 -0
- package/dist/structured-artifact-schemas.json +0 -20
- package/dist/structured-artifacts.js +3 -43
- package/dist/user-input.js +30 -2
- package/package.json +2 -6
- package/Dockerfile.codex +0 -56
- package/dist/executors/claude-executor.js +0 -46
- package/dist/executors/codex-docker-executor.js +0 -27
- package/dist/executors/configs/claude-config.js +0 -12
- package/dist/executors/configs/codex-docker-config.js +0 -10
- package/dist/executors/configs/verify-build-config.js +0 -7
- package/dist/executors/verify-build-executor.js +0 -123
- package/dist/pipeline/flow-specs/auto.json +0 -979
- package/dist/pipeline/flow-specs/gitlab-review.json +0 -317
- package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
- package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
- package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
- package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
- package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
- package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
- package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
- package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
- package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
- package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
- package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
- package/dist/pipeline/flow-specs/review-project.json +0 -243
- package/dist/pipeline/flow-specs/review.json +0 -312
- package/dist/pipeline/flows/preflight-flow.js +0 -19
- package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
- package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
- package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
- package/dist/pipeline/nodes/review-claude-node.js +0 -38
- package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
- package/dist/pipeline/nodes/verify-build-node.js +0 -15
- package/dist/runtime/docker-runtime.js +0 -51
- package/docker-compose.yml +0 -445
- 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(
|
|
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
|
-
?
|
|
88
|
-
: "
|
|
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,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
|
|
9
|
+
const executor = context.executors.get("codex");
|
|
10
10
|
const input = {
|
|
11
11
|
prompt: params.prompt,
|
|
12
12
|
env: { ...context.env },
|