agentweaver 0.1.5 → 0.1.6
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/Dockerfile.codex +55 -0
- package/README.md +10 -6
- package/dist/artifacts.js +9 -0
- package/dist/executors/configs/fetch-gitlab-review-config.js +3 -0
- package/dist/executors/fetch-gitlab-review-executor.js +25 -0
- package/dist/gitlab.js +153 -0
- package/dist/index.js +41 -91
- package/dist/pipeline/flow-specs/auto.json +2 -2
- package/dist/pipeline/flow-specs/gitlab-review.json +347 -0
- package/dist/pipeline/flow-specs/implement.json +0 -9
- package/dist/pipeline/flow-specs/review-fix.json +2 -11
- package/dist/pipeline/flow-specs/run-linter-loop.json +17 -11
- package/dist/pipeline/flow-specs/run-tests-loop.json +17 -11
- package/dist/pipeline/node-registry.js +20 -1
- package/dist/pipeline/nodes/fetch-gitlab-review-node.js +34 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +105 -0
- package/dist/pipeline/nodes/local-script-check-node.js +81 -0
- package/dist/pipeline/nodes/review-findings-form-node.js +14 -14
- package/dist/pipeline/prompt-registry.js +1 -3
- package/dist/pipeline/registry.js +2 -0
- package/dist/pipeline/value-resolver.js +7 -1
- package/dist/prompts.js +0 -2
- package/dist/structured-artifacts.js +33 -0
- package/docker-compose.yml +384 -0
- package/package.json +7 -3
- package/run_linter.sh +89 -0
- package/run_tests.sh +113 -0
- package/verify_build.sh +104 -0
- package/dist/executors/claude-summary-executor.js +0 -31
- package/dist/executors/configs/claude-summary-config.js +0 -8
- package/dist/pipeline/flow-runner.js +0 -13
- package/dist/pipeline/flow-specs/test-fix.json +0 -24
- package/dist/pipeline/flow-specs/test-linter-fix.json +0 -24
- package/dist/pipeline/flow-specs/test.json +0 -19
- package/dist/pipeline/flow-types.js +0 -1
- package/dist/pipeline/flows/implement-flow.js +0 -47
- package/dist/pipeline/flows/plan-flow.js +0 -42
- package/dist/pipeline/flows/review-fix-flow.js +0 -62
- package/dist/pipeline/flows/review-flow.js +0 -124
- package/dist/pipeline/flows/test-fix-flow.js +0 -12
- package/dist/pipeline/flows/test-flow.js +0 -32
- package/dist/pipeline/nodes/claude-summary-node.js +0 -38
- package/dist/pipeline/nodes/implement-codex-node.js +0 -16
- package/dist/pipeline/nodes/task-summary-node.js +0 -42
package/Dockerfile.codex
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
FROM golang:1.25.5-bookworm
|
|
2
|
+
|
|
3
|
+
ARG GOLANGCI_LINT_VERSION=v2.7.2
|
|
4
|
+
ARG MOCKGEN_VERSION=v1.6.0
|
|
5
|
+
ARG SWAG_VERSION=latest
|
|
6
|
+
ARG PROTOC_GEN_GO_VERSION=latest
|
|
7
|
+
ARG PROTOC_GEN_GO_GRPC_VERSION=latest
|
|
8
|
+
|
|
9
|
+
RUN apt-get update \
|
|
10
|
+
&& apt-get install -y --no-install-recommends \
|
|
11
|
+
ca-certificates \
|
|
12
|
+
nodejs \
|
|
13
|
+
npm \
|
|
14
|
+
curl \
|
|
15
|
+
jq \
|
|
16
|
+
less \
|
|
17
|
+
file \
|
|
18
|
+
make \
|
|
19
|
+
procps \
|
|
20
|
+
ripgrep \
|
|
21
|
+
git \
|
|
22
|
+
openssh-client \
|
|
23
|
+
docker.io \
|
|
24
|
+
protobuf-compiler \
|
|
25
|
+
unzip \
|
|
26
|
+
zip \
|
|
27
|
+
findutils \
|
|
28
|
+
&& update-ca-certificates \
|
|
29
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
30
|
+
|
|
31
|
+
RUN if ! getent group 1000 >/dev/null; then groupadd -g 1000 codex; fi \
|
|
32
|
+
&& if ! getent passwd 1000 >/dev/null; then useradd -u 1000 -g 1000 -d /codex-home/home -M -s /bin/bash codex; fi
|
|
33
|
+
|
|
34
|
+
RUN npm install -g @openai/codex@latest \
|
|
35
|
+
&& npm cache clean --force
|
|
36
|
+
|
|
37
|
+
RUN GOBIN=/usr/local/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION} \
|
|
38
|
+
&& GOBIN=/usr/local/bin go install github.com/golang/mock/mockgen@${MOCKGEN_VERSION} \
|
|
39
|
+
&& GOBIN=/usr/local/bin go install github.com/swaggo/swag/cmd/swag@${SWAG_VERSION} \
|
|
40
|
+
&& GOBIN=/usr/local/bin go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION} \
|
|
41
|
+
&& GOBIN=/usr/local/bin go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \
|
|
42
|
+
&& ln -sf /usr/local/go/bin/go /usr/bin/go \
|
|
43
|
+
&& ln -sf /usr/local/go/bin/gofmt /usr/bin/gofmt
|
|
44
|
+
|
|
45
|
+
ENV PATH="/usr/local/go/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
46
|
+
|
|
47
|
+
COPY verify_build.sh /usr/local/bin/verify_build.sh
|
|
48
|
+
COPY run_tests.sh /usr/local/bin/run_tests.sh
|
|
49
|
+
COPY run_linter.sh /usr/local/bin/run_linter.sh
|
|
50
|
+
RUN chmod +x /usr/local/bin/verify_build.sh /usr/local/bin/run_tests.sh /usr/local/bin/run_linter.sh
|
|
51
|
+
|
|
52
|
+
WORKDIR /workspace
|
|
53
|
+
|
|
54
|
+
ENTRYPOINT ["codex"]
|
|
55
|
+
CMD ["--dangerously-bypass-approvals-and-sandbox"]
|
package/README.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
# AgentWeaver
|
|
2
2
|
|
|
3
|
-
`AgentWeaver` is a TypeScript/Node.js CLI for engineering workflows around Jira, Codex, and Claude.
|
|
3
|
+
`AgentWeaver` is a TypeScript/Node.js CLI for engineering workflows around Jira, GitLab review artifacts, Codex, and Claude.
|
|
4
4
|
|
|
5
5
|
It orchestrates a flow like:
|
|
6
6
|
|
|
7
|
-
`plan -> implement ->
|
|
7
|
+
`plan -> implement -> run-linter-loop -> run-tests-loop -> review -> review-fix`
|
|
8
8
|
|
|
9
9
|
The package is designed to run as an npm CLI and includes an interactive terminal UI built on `neo-blessed`.
|
|
10
10
|
|
|
11
11
|
## What It Does
|
|
12
12
|
|
|
13
13
|
- Fetches a Jira issue by key or browse URL
|
|
14
|
+
- Fetches GitLab merge request review comments into reusable markdown and JSON artifacts
|
|
14
15
|
- Generates workflow artifacts such as design, implementation plan, QA plan, bug analysis, reviews, and summaries
|
|
15
16
|
- Machine-readable JSON artifacts are stored under `.agentweaver-<TASK>/.artifacts/` and act as the source of truth between workflow steps; Markdown artifacts remain for human inspection
|
|
16
|
-
- Runs workflow stages like `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `
|
|
17
|
+
- Runs workflow stages like `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `run-tests-loop`, `run-linter-loop`, and `auto`
|
|
17
18
|
- Persists compact `auto` pipeline state on disk so runs can resume without storing large agent outputs
|
|
18
19
|
- Uses Docker runtime services for isolated Codex execution and build verification
|
|
19
20
|
|
|
@@ -22,9 +23,9 @@ The package is designed to run as an npm CLI and includes an interactive termina
|
|
|
22
23
|
The CLI now uses an executor + node + declarative flow architecture.
|
|
23
24
|
|
|
24
25
|
- `src/index.ts` remains the CLI entrypoint and high-level orchestration layer
|
|
25
|
-
- `src/executors/` contains first-class executors for external actions such as Jira fetch, local Codex, Docker-based build verification, Claude, Claude summaries, and process execution
|
|
26
|
+
- `src/executors/` contains first-class executors for external actions such as Jira fetch, GitLab review fetch, local Codex, Docker-based build verification, Claude, Claude summaries, and process execution
|
|
26
27
|
- `src/pipeline/nodes/` contains reusable runtime nodes built on top of executors
|
|
27
|
-
- `src/pipeline/flow-specs/` contains declarative JSON flow specs for `preflight`, `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `
|
|
28
|
+
- `src/pipeline/flow-specs/` contains declarative JSON flow specs for `preflight`, `bug-analyze`, `bug-fix`, `gitlab-review`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `run-tests-loop`, `run-linter-loop`, and `auto`
|
|
28
29
|
- `src/runtime/` contains shared runtime services such as command resolution, Docker runtime environment setup, and subprocess execution
|
|
29
30
|
|
|
30
31
|
This keeps command handlers focused on choosing a flow and providing parameters instead of assembling prompts and subprocess wiring inline.
|
|
@@ -86,6 +87,7 @@ Required:
|
|
|
86
87
|
Common optional variables:
|
|
87
88
|
|
|
88
89
|
- `JIRA_BASE_URL` — required when you pass only an issue key like `DEMO-123`
|
|
90
|
+
- `GITLAB_TOKEN` — personal access token for `gitlab-review`
|
|
89
91
|
- `AGENTWEAVER_HOME` — path to the AgentWeaver installation directory
|
|
90
92
|
- `DOCKER_COMPOSE_BIN` — override compose command, for example `docker compose`
|
|
91
93
|
- `CODEX_BIN` — override `codex` executable path
|
|
@@ -117,6 +119,7 @@ Direct CLI usage:
|
|
|
117
119
|
agentweaver plan DEMO-3288
|
|
118
120
|
agentweaver bug-analyze DEMO-3288
|
|
119
121
|
agentweaver bug-fix DEMO-3288
|
|
122
|
+
agentweaver gitlab-review DEMO-3288
|
|
120
123
|
agentweaver mr-description DEMO-3288
|
|
121
124
|
agentweaver task-describe DEMO-3288
|
|
122
125
|
agentweaver implement DEMO-3288
|
|
@@ -132,6 +135,7 @@ From source checkout:
|
|
|
132
135
|
node dist/index.js plan DEMO-3288
|
|
133
136
|
node dist/index.js bug-analyze DEMO-3288
|
|
134
137
|
node dist/index.js bug-fix DEMO-3288
|
|
138
|
+
node dist/index.js gitlab-review DEMO-3288
|
|
135
139
|
node dist/index.js mr-description DEMO-3288
|
|
136
140
|
node dist/index.js task-describe DEMO-3288
|
|
137
141
|
node dist/index.js auto DEMO-3288
|
|
@@ -189,7 +193,7 @@ Activity pane behavior:
|
|
|
189
193
|
|
|
190
194
|
## Docker Runtime
|
|
191
195
|
|
|
192
|
-
Docker is used as an isolated execution environment for Codex
|
|
196
|
+
Docker is used as an isolated execution environment for Codex-related runtime scenarios that still require container orchestration.
|
|
193
197
|
|
|
194
198
|
Main services:
|
|
195
199
|
|
package/dist/artifacts.js
CHANGED
|
@@ -89,6 +89,15 @@ export function mrDescriptionFile(taskKey) {
|
|
|
89
89
|
export function mrDescriptionJsonFile(taskKey) {
|
|
90
90
|
return taskArtifactsFile(taskKey, `mr-description-${taskKey}.json`);
|
|
91
91
|
}
|
|
92
|
+
export function gitlabReviewFile(taskKey) {
|
|
93
|
+
return taskWorkspaceFile(taskKey, `gitlab-review-${taskKey}.md`);
|
|
94
|
+
}
|
|
95
|
+
export function gitlabReviewJsonFile(taskKey) {
|
|
96
|
+
return taskArtifactsFile(taskKey, `gitlab-review-${taskKey}.json`);
|
|
97
|
+
}
|
|
98
|
+
export function gitlabReviewInputJsonFile(taskKey) {
|
|
99
|
+
return taskArtifactsFile(taskKey, `gitlab-review-input-${taskKey}.json`);
|
|
100
|
+
}
|
|
92
101
|
export function autoStateFile(taskKey) {
|
|
93
102
|
return taskArtifactsFile(taskKey, `.agentweaver-state-${taskKey}.json`);
|
|
94
103
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fetchGitLabReviewExecutorDefaultConfig } from "./configs/fetch-gitlab-review-config.js";
|
|
2
|
+
import { buildGitLabReviewFetchTarget, fetchGitLabReview } from "../gitlab.js";
|
|
3
|
+
export const fetchGitLabReviewExecutor = {
|
|
4
|
+
kind: "fetch-gitlab-review",
|
|
5
|
+
version: 1,
|
|
6
|
+
defaultConfig: fetchGitLabReviewExecutorDefaultConfig,
|
|
7
|
+
async execute(context, input) {
|
|
8
|
+
const target = buildGitLabReviewFetchTarget(input.mergeRequestUrl);
|
|
9
|
+
if (context.verbose) {
|
|
10
|
+
context.ui.writeStdout(`GitLab MR URL: ${target.mergeRequestUrl}\n`);
|
|
11
|
+
context.ui.writeStdout(`GitLab project path: ${target.projectPath}\n`);
|
|
12
|
+
context.ui.writeStdout(`GitLab merge request IID: ${target.mergeRequestIid}\n`);
|
|
13
|
+
context.ui.writeStdout(`GitLab discussions API URL: ${target.discussionsApiUrl}\n`);
|
|
14
|
+
context.ui.writeStdout(`Saving GitLab review markdown to: ${input.outputFile}\n`);
|
|
15
|
+
context.ui.writeStdout(`Saving GitLab review JSON to: ${input.outputJsonFile}\n`);
|
|
16
|
+
}
|
|
17
|
+
const artifact = await fetchGitLabReview(input.mergeRequestUrl, input.outputFile, input.outputJsonFile);
|
|
18
|
+
return {
|
|
19
|
+
outputFile: input.outputFile,
|
|
20
|
+
outputJsonFile: input.outputJsonFile,
|
|
21
|
+
mergeRequestUrl: artifact.merge_request_url,
|
|
22
|
+
commentsCount: artifact.comments.length,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
package/dist/gitlab.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { TaskRunnerError } from "./errors.js";
|
|
5
|
+
const MERGE_REQUEST_PATH_RE = /^(?<projectPath>.+?)\/-\/merge_requests\/(?<iid>\d+)(?:\/.*)?$/;
|
|
6
|
+
function normalizeUrl(value) {
|
|
7
|
+
return value.trim().replace(/\/+$/, "");
|
|
8
|
+
}
|
|
9
|
+
function normalizeProjectPath(value) {
|
|
10
|
+
return value.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
11
|
+
}
|
|
12
|
+
export function parseGitLabMergeRequestUrl(mergeRequestUrl) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = new URL(normalizeUrl(mergeRequestUrl));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new TaskRunnerError("Expected GitLab merge request URL like https://gitlab.example.com/group/project/-/merge_requests/123");
|
|
19
|
+
}
|
|
20
|
+
const match = MERGE_REQUEST_PATH_RE.exec(parsed.pathname);
|
|
21
|
+
const projectPath = normalizeProjectPath(match?.groups?.projectPath ?? "");
|
|
22
|
+
const iidRaw = match?.groups?.iid;
|
|
23
|
+
if (!projectPath || !iidRaw) {
|
|
24
|
+
throw new TaskRunnerError("Expected GitLab merge request URL like https://gitlab.example.com/group/project/-/merge_requests/123");
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
apiBaseUrl: `${parsed.protocol}//${parsed.host}/api/v4`,
|
|
28
|
+
mergeRequestUrl: `${parsed.protocol}//${parsed.host}${parsed.pathname}`,
|
|
29
|
+
projectPath,
|
|
30
|
+
mergeRequestIid: Number.parseInt(iidRaw, 10),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function buildGitLabReviewFetchTarget(mergeRequestUrl) {
|
|
34
|
+
const mergeRequestRef = parseGitLabMergeRequestUrl(mergeRequestUrl);
|
|
35
|
+
return {
|
|
36
|
+
...mergeRequestRef,
|
|
37
|
+
discussionsApiUrl: `${mergeRequestRef.apiBaseUrl}/projects/${encodeURIComponent(mergeRequestRef.projectPath)}/merge_requests/${mergeRequestRef.mergeRequestIid}/discussions`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function fetchDiscussionPage(target, page, token) {
|
|
41
|
+
const apiUrl = `${target.discussionsApiUrl}?per_page=100&page=${page}`;
|
|
42
|
+
const response = await fetch(apiUrl, {
|
|
43
|
+
headers: {
|
|
44
|
+
"PRIVATE-TOKEN": token,
|
|
45
|
+
Accept: "application/json",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new TaskRunnerError([
|
|
50
|
+
`Failed to fetch GitLab merge request discussions: HTTP ${response.status}`,
|
|
51
|
+
`MR URL: ${target.mergeRequestUrl}`,
|
|
52
|
+
`GitLab project path: ${target.projectPath}`,
|
|
53
|
+
`GitLab merge request IID: ${target.mergeRequestIid}`,
|
|
54
|
+
`GitLab discussions API URL: ${apiUrl}`,
|
|
55
|
+
].join("\n"));
|
|
56
|
+
}
|
|
57
|
+
const nextPageHeader = response.headers.get("x-next-page");
|
|
58
|
+
const nextPage = nextPageHeader && nextPageHeader.trim().length > 0 ? Number.parseInt(nextPageHeader, 10) : null;
|
|
59
|
+
const discussions = (await response.json());
|
|
60
|
+
return { discussions, nextPage: Number.isNaN(nextPage ?? Number.NaN) ? null : nextPage };
|
|
61
|
+
}
|
|
62
|
+
async function fetchMergeRequestDiscussions(target, token) {
|
|
63
|
+
const discussions = [];
|
|
64
|
+
let page = 1;
|
|
65
|
+
while (true) {
|
|
66
|
+
const chunk = await fetchDiscussionPage(target, page, token);
|
|
67
|
+
discussions.push(...chunk.discussions);
|
|
68
|
+
if (!chunk.nextPage) {
|
|
69
|
+
return discussions;
|
|
70
|
+
}
|
|
71
|
+
page = chunk.nextPage;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function normalizeDiscussionNotes(discussions) {
|
|
75
|
+
return discussions.flatMap((discussion) => {
|
|
76
|
+
const discussionId = String(discussion.id ?? "");
|
|
77
|
+
if (!discussionId) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
return (discussion.notes ?? [])
|
|
81
|
+
.filter((note) => typeof note.body === "string" && note.body.trim().length > 0)
|
|
82
|
+
.filter((note) => note.system !== true)
|
|
83
|
+
.map((note) => ({
|
|
84
|
+
id: String(note.id ?? `${discussionId}-${note.created_at ?? "unknown"}`),
|
|
85
|
+
discussion_id: discussionId,
|
|
86
|
+
body: note.body?.trim() ?? "",
|
|
87
|
+
author: note.author?.username?.trim() || note.author?.name?.trim() || "unknown",
|
|
88
|
+
created_at: note.created_at ?? new Date(0).toISOString(),
|
|
89
|
+
system: Boolean(note.system),
|
|
90
|
+
resolvable: Boolean(note.resolvable),
|
|
91
|
+
resolved: Boolean(note.resolved),
|
|
92
|
+
file_path: note.position?.new_path ?? note.position?.old_path ?? null,
|
|
93
|
+
new_line: typeof note.position?.new_line === "number" ? note.position.new_line : null,
|
|
94
|
+
old_line: typeof note.position?.old_line === "number" ? note.position.old_line : null,
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function buildGitLabReviewMarkdown(artifact) {
|
|
99
|
+
const lines = [
|
|
100
|
+
"# GitLab Review",
|
|
101
|
+
"",
|
|
102
|
+
`- MR: ${artifact.merge_request_url}`,
|
|
103
|
+
`- Project: ${artifact.project_path}`,
|
|
104
|
+
`- IID: ${artifact.merge_request_iid}`,
|
|
105
|
+
`- Fetched at: ${artifact.fetched_at}`,
|
|
106
|
+
`- Comments: ${artifact.comments.length}`,
|
|
107
|
+
"",
|
|
108
|
+
];
|
|
109
|
+
if (artifact.comments.length === 0) {
|
|
110
|
+
lines.push("Код-ревью комментариев не найдено.");
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
}
|
|
113
|
+
artifact.comments.forEach((comment, index) => {
|
|
114
|
+
lines.push(`## Comment ${index + 1}`);
|
|
115
|
+
lines.push(`- Author: ${comment.author}`);
|
|
116
|
+
lines.push(`- Created at: ${comment.created_at}`);
|
|
117
|
+
lines.push(`- Discussion: ${comment.discussion_id}`);
|
|
118
|
+
if (comment.file_path) {
|
|
119
|
+
const location = [comment.file_path, comment.new_line ?? comment.old_line].filter((item) => item !== null).join(":");
|
|
120
|
+
lines.push(`- Location: ${location}`);
|
|
121
|
+
}
|
|
122
|
+
if (comment.resolvable) {
|
|
123
|
+
lines.push(`- Resolved: ${comment.resolved ? "yes" : "no"}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push(comment.body);
|
|
127
|
+
lines.push("");
|
|
128
|
+
});
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
export async function fetchGitLabReview(mergeRequestUrl, outputFile, outputJsonFile) {
|
|
132
|
+
const token = process.env.GITLAB_TOKEN?.trim();
|
|
133
|
+
if (!token) {
|
|
134
|
+
throw new TaskRunnerError("GITLAB_TOKEN is required for gitlab-review flow.");
|
|
135
|
+
}
|
|
136
|
+
const target = buildGitLabReviewFetchTarget(mergeRequestUrl);
|
|
137
|
+
const discussions = await fetchMergeRequestDiscussions(target, token);
|
|
138
|
+
const comments = normalizeDiscussionNotes(discussions);
|
|
139
|
+
const fetchedAt = new Date().toISOString();
|
|
140
|
+
const artifact = {
|
|
141
|
+
summary: comments.length > 0 ? `Fetched ${comments.length} GitLab review comments.` : "No GitLab review comments found.",
|
|
142
|
+
merge_request_url: target.mergeRequestUrl,
|
|
143
|
+
project_path: target.projectPath,
|
|
144
|
+
merge_request_iid: target.mergeRequestIid,
|
|
145
|
+
fetched_at: fetchedAt,
|
|
146
|
+
comments,
|
|
147
|
+
};
|
|
148
|
+
mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
149
|
+
mkdirSync(path.dirname(outputJsonFile), { recursive: true });
|
|
150
|
+
await writeFile(outputJsonFile, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
151
|
+
await writeFile(outputFile, `${buildGitLabReviewMarkdown(artifact)}\n`, "utf8");
|
|
152
|
+
return artifact;
|
|
153
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "no
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, ensureTaskWorkspaceDir, jiraTaskFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, taskWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
|
|
6
|
+
import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, ensureTaskWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, jiraTaskFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, taskWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
|
|
7
7
|
import { TaskRunnerError } from "./errors.js";
|
|
8
8
|
import { buildJiraApiUrl, buildJiraBrowseUrl, extractIssueKey, requireJiraTaskFile } from "./jira.js";
|
|
9
9
|
import { validateStructuredArtifacts } from "./structured-artifacts.js";
|
|
@@ -14,7 +14,7 @@ import { loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
|
|
|
14
14
|
import { findPhaseById, runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
|
|
15
15
|
import { runPreflightFlow } from "./pipeline/flows/preflight-flow.js";
|
|
16
16
|
import { resolveCmd, resolveDockerComposeCmd } from "./runtime/command-resolution.js";
|
|
17
|
-
import { defaultDockerComposeFile, dockerRuntimeEnv } from "./runtime/docker-runtime.js";
|
|
17
|
+
import { agentweaverHome, defaultDockerComposeFile, dockerRuntimeEnv } from "./runtime/docker-runtime.js";
|
|
18
18
|
import { runCommand } from "./runtime/process-runner.js";
|
|
19
19
|
import { InteractiveUi } from "./interactive-ui.js";
|
|
20
20
|
import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
|
|
@@ -22,15 +22,13 @@ import { requestUserInputInTerminal } from "./user-input.js";
|
|
|
22
22
|
const COMMANDS = [
|
|
23
23
|
"bug-analyze",
|
|
24
24
|
"bug-fix",
|
|
25
|
+
"gitlab-review",
|
|
25
26
|
"mr-description",
|
|
26
27
|
"plan",
|
|
27
28
|
"task-describe",
|
|
28
29
|
"implement",
|
|
29
30
|
"review",
|
|
30
31
|
"review-fix",
|
|
31
|
-
"test",
|
|
32
|
-
"test-fix",
|
|
33
|
-
"test-linter-fix",
|
|
34
32
|
"run-tests-loop",
|
|
35
33
|
"run-linter-loop",
|
|
36
34
|
"auto",
|
|
@@ -81,6 +79,7 @@ function usage() {
|
|
|
81
79
|
return `Usage:
|
|
82
80
|
agentweaver <jira-browse-url|jira-issue-key>
|
|
83
81
|
agentweaver --force <jira-browse-url|jira-issue-key>
|
|
82
|
+
agentweaver gitlab-review <jira-browse-url|jira-issue-key>
|
|
84
83
|
agentweaver bug-analyze [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
85
84
|
agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
86
85
|
agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
@@ -89,9 +88,6 @@ function usage() {
|
|
|
89
88
|
agentweaver implement [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
90
89
|
agentweaver review [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
91
90
|
agentweaver review-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
92
|
-
agentweaver test [--dry] [--verbose] <jira-browse-url|jira-issue-key>
|
|
93
|
-
agentweaver test-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
94
|
-
agentweaver test-linter-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
95
91
|
agentweaver run-tests-loop [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
96
92
|
agentweaver run-linter-loop [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
97
93
|
agentweaver auto [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
@@ -116,6 +112,7 @@ Required environment variables:
|
|
|
116
112
|
|
|
117
113
|
Optional environment variables:
|
|
118
114
|
JIRA_BASE_URL
|
|
115
|
+
GITLAB_TOKEN
|
|
119
116
|
AGENTWEAVER_HOME
|
|
120
117
|
DOCKER_COMPOSE_BIN
|
|
121
118
|
CODEX_BIN
|
|
@@ -398,6 +395,7 @@ function latestReviewReplyIteration(taskKey) {
|
|
|
398
395
|
function buildConfig(command, jiraRef, options = {}) {
|
|
399
396
|
const jiraIssueKey = extractIssueKey(jiraRef);
|
|
400
397
|
ensureTaskWorkspaceDir(jiraIssueKey);
|
|
398
|
+
const homeDir = agentweaverHome(PACKAGE_ROOT);
|
|
401
399
|
return {
|
|
402
400
|
command,
|
|
403
401
|
jiraRef,
|
|
@@ -412,6 +410,8 @@ function buildConfig(command, jiraRef, options = {}) {
|
|
|
412
410
|
jiraBrowseUrl: buildJiraBrowseUrl(jiraRef),
|
|
413
411
|
jiraApiUrl: buildJiraApiUrl(jiraRef),
|
|
414
412
|
jiraTaskFile: jiraTaskFile(jiraIssueKey),
|
|
413
|
+
runTestsScript: path.join(homeDir, "run_tests.sh"),
|
|
414
|
+
runLinterScript: path.join(homeDir, "run_linter.sh"),
|
|
415
415
|
};
|
|
416
416
|
}
|
|
417
417
|
function checkPrerequisites(config) {
|
|
@@ -428,26 +428,18 @@ function checkPrerequisites(config) {
|
|
|
428
428
|
if (config.command === "review") {
|
|
429
429
|
resolveCmd("claude", "CLAUDE_BIN");
|
|
430
430
|
}
|
|
431
|
-
if (["implement", "review-fix", "test", "run-tests-loop", "run-linter-loop"].includes(config.command)) {
|
|
432
|
-
resolveDockerComposeCmd();
|
|
433
|
-
if (!existsSync(config.dockerComposeFile)) {
|
|
434
|
-
throw new TaskRunnerError(`docker-compose file not found: ${config.dockerComposeFile}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
431
|
}
|
|
438
432
|
function checkAutoPrerequisites(config) {
|
|
439
433
|
resolveCmd("codex", "CODEX_BIN");
|
|
440
434
|
resolveCmd("claude", "CLAUDE_BIN");
|
|
441
|
-
resolveDockerComposeCmd();
|
|
442
|
-
if (!existsSync(config.dockerComposeFile)) {
|
|
443
|
-
throw new TaskRunnerError(`docker-compose file not found: ${config.dockerComposeFile}`);
|
|
444
|
-
}
|
|
445
435
|
}
|
|
446
436
|
function autoFlowParams(config) {
|
|
447
437
|
return {
|
|
448
438
|
jiraApiUrl: config.jiraApiUrl,
|
|
449
439
|
taskKey: config.taskKey,
|
|
450
440
|
dockerComposeFile: config.dockerComposeFile,
|
|
441
|
+
runTestsScript: config.runTestsScript,
|
|
442
|
+
runLinterScript: config.runLinterScript,
|
|
451
443
|
extraPrompt: config.extraPrompt,
|
|
452
444
|
reviewFixPoints: config.reviewFixPoints,
|
|
453
445
|
};
|
|
@@ -455,6 +447,7 @@ function autoFlowParams(config) {
|
|
|
455
447
|
const FLOW_DESCRIPTIONS = {
|
|
456
448
|
auto: "Полный пайплайн задачи: планирование, реализация, проверки, ревью, ответы на ревью и повторные итерации до готовности к merge.",
|
|
457
449
|
"bug-analyze": "Анализирует баг по Jira и создаёт структурированные артефакты: гипотезу причины, дизайн исправления и план работ.",
|
|
450
|
+
"gitlab-review": "Запрашивает GitLab MR URL через user-input, загружает комментарии код-ревью по API и сохраняет markdown плюс structured JSON artifact.",
|
|
458
451
|
"bug-fix": "Берёт результаты bug-analyze как source of truth и реализует исправление бага в коде.",
|
|
459
452
|
"mr-description": "Готовит краткое intent-описание для merge request на основе задачи и текущих изменений.",
|
|
460
453
|
plan: "Загружает задачу из Jira и создаёт дизайн, план реализации и QA-план в structured JSON и markdown.",
|
|
@@ -462,11 +455,8 @@ const FLOW_DESCRIPTIONS = {
|
|
|
462
455
|
implement: "Реализует задачу по утверждённым design/plan артефактам и при необходимости запускает post-verify сборки.",
|
|
463
456
|
review: "Запускает Claude-код-ревью текущих изменений, валидирует structured findings, затем готовит ответ на замечания через Codex.",
|
|
464
457
|
"review-fix": "Исправляет замечания после review-reply, обновляет код и прогоняет обязательные проверки после правок.",
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
"test-linter-fix": "Прогоняет линтер и генерацию, затем исправляет замечания для чистого прогона.",
|
|
468
|
-
"run-tests-loop": "Циклически запускает `./run_tests.sh`, анализирует последнюю ошибку и правит код до успешного прохождения или исчерпания попыток.",
|
|
469
|
-
"run-linter-loop": "Циклически запускает `./run_linter.sh`, исправляет проблемы линтера или генерации и повторяет попытки до успеха.",
|
|
458
|
+
"run-tests-loop": "Циклически запускает `./run_tests.sh` локально, анализирует последнюю ошибку и правит код до успешного прохождения или исчерпания попыток.",
|
|
459
|
+
"run-linter-loop": "Циклически запускает `./run_linter.sh` локально, исправляет проблемы линтера или генерации и повторяет попытки до успеха.",
|
|
470
460
|
};
|
|
471
461
|
function flowDescription(id) {
|
|
472
462
|
return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
|
|
@@ -506,15 +496,13 @@ function interactiveFlowDefinitions() {
|
|
|
506
496
|
autoFlowDefinition(),
|
|
507
497
|
declarativeFlowDefinition("bug-analyze", "bug-analyze", "bug-analyze.json"),
|
|
508
498
|
declarativeFlowDefinition("bug-fix", "bug-fix", "bug-fix.json"),
|
|
499
|
+
declarativeFlowDefinition("gitlab-review", "gitlab-review", "gitlab-review.json"),
|
|
509
500
|
declarativeFlowDefinition("mr-description", "mr-description", "mr-description.json"),
|
|
510
501
|
declarativeFlowDefinition("plan", "plan", "plan.json"),
|
|
511
502
|
declarativeFlowDefinition("task-describe", "task-describe", "task-describe.json"),
|
|
512
503
|
declarativeFlowDefinition("implement", "implement", "implement.json"),
|
|
513
504
|
declarativeFlowDefinition("review", "review", "review.json"),
|
|
514
505
|
declarativeFlowDefinition("review-fix", "review-fix", "review-fix.json"),
|
|
515
|
-
declarativeFlowDefinition("test", "test", "test.json"),
|
|
516
|
-
declarativeFlowDefinition("test-fix", "test-fix", "test-fix.json"),
|
|
517
|
-
declarativeFlowDefinition("test-linter-fix", "test-linter-fix", "test-linter-fix.json"),
|
|
518
506
|
declarativeFlowDefinition("run-tests-loop", "run-tests-loop", "run-tests-loop.json"),
|
|
519
507
|
declarativeFlowDefinition("run-linter-loop", "run-linter-loop", "run-linter-loop.json"),
|
|
520
508
|
];
|
|
@@ -684,6 +672,23 @@ async function executeCommand(config, runFollowupVerify = true, requestUserInput
|
|
|
684
672
|
}, requestUserInput);
|
|
685
673
|
return false;
|
|
686
674
|
}
|
|
675
|
+
if (config.command === "gitlab-review") {
|
|
676
|
+
requireJiraTaskFile(config.jiraTaskFile);
|
|
677
|
+
validateStructuredArtifacts([
|
|
678
|
+
{ path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
|
|
679
|
+
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
680
|
+
], "GitLab-review mode requires valid structured plan artifacts from the planning phase.");
|
|
681
|
+
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
682
|
+
await runDeclarativeFlowBySpecFile("gitlab-review.json", config, {
|
|
683
|
+
taskKey: config.taskKey,
|
|
684
|
+
iteration,
|
|
685
|
+
extraPrompt: config.extraPrompt,
|
|
686
|
+
}, requestUserInput);
|
|
687
|
+
if (!config.dryRun) {
|
|
688
|
+
printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
|
|
689
|
+
}
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
687
692
|
if (config.command === "bug-fix") {
|
|
688
693
|
requireJiraTaskFile(config.jiraTaskFile);
|
|
689
694
|
requireArtifacts(bugAnalyzeArtifacts(config.taskKey), "Bug-fix mode requires bug-analyze artifacts from the bug analysis phase.");
|
|
@@ -722,24 +727,10 @@ async function executeCommand(config, runFollowupVerify = true, requestUserInput
|
|
|
722
727
|
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
723
728
|
{ path: qaJsonFile(config.taskKey), schemaId: "qa-plan/v1" },
|
|
724
729
|
], "Implement mode requires valid structured plan artifacts from the planning phase.");
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
extraPrompt: config.extraPrompt,
|
|
730
|
-
runFollowupVerify,
|
|
731
|
-
}, requestUserInput);
|
|
732
|
-
}
|
|
733
|
-
catch (error) {
|
|
734
|
-
if (!config.dryRun) {
|
|
735
|
-
const output = String(error.output ?? "");
|
|
736
|
-
if (output.trim()) {
|
|
737
|
-
printError("Build verification failed");
|
|
738
|
-
printSummary("Build Failure Summary", await summarizeBuildFailure(output));
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
throw error;
|
|
742
|
-
}
|
|
730
|
+
await runDeclarativeFlowBySpecFile("implement.json", config, {
|
|
731
|
+
taskKey: config.taskKey,
|
|
732
|
+
extraPrompt: config.extraPrompt,
|
|
733
|
+
}, requestUserInput);
|
|
743
734
|
return false;
|
|
744
735
|
}
|
|
745
736
|
if (config.command === "review") {
|
|
@@ -766,61 +757,20 @@ async function executeCommand(config, runFollowupVerify = true, requestUserInput
|
|
|
766
757
|
{ path: reviewJsonFile(config.taskKey, latestIteration), schemaId: "review-findings/v1" },
|
|
767
758
|
{ path: reviewReplyJsonFile(config.taskKey, latestIteration), schemaId: "review-reply/v1" },
|
|
768
759
|
], "Review-fix mode requires valid structured review artifacts.");
|
|
769
|
-
|
|
770
|
-
await runDeclarativeFlowBySpecFile("review-fix.json", config, {
|
|
771
|
-
taskKey: config.taskKey,
|
|
772
|
-
dockerComposeFile: config.dockerComposeFile,
|
|
773
|
-
latestIteration,
|
|
774
|
-
reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
|
|
775
|
-
runFollowupVerify,
|
|
776
|
-
extraPrompt: config.extraPrompt,
|
|
777
|
-
reviewFixPoints: config.reviewFixPoints,
|
|
778
|
-
}, requestUserInput);
|
|
779
|
-
}
|
|
780
|
-
catch (error) {
|
|
781
|
-
if (!config.dryRun) {
|
|
782
|
-
const output = String(error.output ?? "");
|
|
783
|
-
if (output.trim()) {
|
|
784
|
-
printError("Build verification failed");
|
|
785
|
-
printSummary("Build Failure Summary", await summarizeBuildFailure(output));
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
throw error;
|
|
789
|
-
}
|
|
790
|
-
return false;
|
|
791
|
-
}
|
|
792
|
-
if (config.command === "test") {
|
|
793
|
-
requireJiraTaskFile(config.jiraTaskFile);
|
|
794
|
-
try {
|
|
795
|
-
await runDeclarativeFlowBySpecFile("test.json", config, {
|
|
796
|
-
taskKey: config.taskKey,
|
|
797
|
-
dockerComposeFile: config.dockerComposeFile,
|
|
798
|
-
}, requestUserInput);
|
|
799
|
-
}
|
|
800
|
-
catch (error) {
|
|
801
|
-
if (!config.dryRun) {
|
|
802
|
-
const output = String(error.output ?? "");
|
|
803
|
-
if (output.trim()) {
|
|
804
|
-
printError("Build verification failed");
|
|
805
|
-
printSummary("Build Failure Summary", await summarizeBuildFailure(output));
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
throw error;
|
|
809
|
-
}
|
|
810
|
-
return false;
|
|
811
|
-
}
|
|
812
|
-
if (config.command === "test-fix" || config.command === "test-linter-fix") {
|
|
813
|
-
requireJiraTaskFile(config.jiraTaskFile);
|
|
814
|
-
await runDeclarativeFlowBySpecFile(config.command === "test-fix" ? "test-fix.json" : "test-linter-fix.json", config, {
|
|
760
|
+
await runDeclarativeFlowBySpecFile("review-fix.json", config, {
|
|
815
761
|
taskKey: config.taskKey,
|
|
762
|
+
latestIteration,
|
|
763
|
+
reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
|
|
816
764
|
extraPrompt: config.extraPrompt,
|
|
765
|
+
reviewFixPoints: config.reviewFixPoints,
|
|
817
766
|
}, requestUserInput);
|
|
818
767
|
return false;
|
|
819
768
|
}
|
|
820
769
|
if (config.command === "run-tests-loop" || config.command === "run-linter-loop") {
|
|
821
770
|
await runDeclarativeFlowBySpecFile(config.command === "run-tests-loop" ? "run-tests-loop.json" : "run-linter-loop.json", config, {
|
|
822
771
|
taskKey: config.taskKey,
|
|
823
|
-
|
|
772
|
+
runTestsScript: config.runTestsScript,
|
|
773
|
+
runLinterScript: config.runLinterScript,
|
|
824
774
|
extraPrompt: config.extraPrompt,
|
|
825
775
|
}, requestUserInput);
|
|
826
776
|
return false;
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
"fileName": { "const": "run-linter-loop.json" },
|
|
194
194
|
"labelText": { "const": "Running run-linter-loop after implement" },
|
|
195
195
|
"taskKey": { "ref": "params.taskKey" },
|
|
196
|
-
"
|
|
196
|
+
"runLinterScript": { "ref": "params.runLinterScript" },
|
|
197
197
|
"extraPrompt": { "ref": "params.extraPrompt" }
|
|
198
198
|
}
|
|
199
199
|
},
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"fileName": { "const": "run-tests-loop.json" },
|
|
205
205
|
"labelText": { "const": "Running run-tests-loop after implement" },
|
|
206
206
|
"taskKey": { "ref": "params.taskKey" },
|
|
207
|
-
"
|
|
207
|
+
"runTestsScript": { "ref": "params.runTestsScript" },
|
|
208
208
|
"extraPrompt": { "ref": "params.extraPrompt" }
|
|
209
209
|
}
|
|
210
210
|
}
|