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.
Files changed (44) hide show
  1. package/Dockerfile.codex +55 -0
  2. package/README.md +10 -6
  3. package/dist/artifacts.js +9 -0
  4. package/dist/executors/configs/fetch-gitlab-review-config.js +3 -0
  5. package/dist/executors/fetch-gitlab-review-executor.js +25 -0
  6. package/dist/gitlab.js +153 -0
  7. package/dist/index.js +41 -91
  8. package/dist/pipeline/flow-specs/auto.json +2 -2
  9. package/dist/pipeline/flow-specs/gitlab-review.json +347 -0
  10. package/dist/pipeline/flow-specs/implement.json +0 -9
  11. package/dist/pipeline/flow-specs/review-fix.json +2 -11
  12. package/dist/pipeline/flow-specs/run-linter-loop.json +17 -11
  13. package/dist/pipeline/flow-specs/run-tests-loop.json +17 -11
  14. package/dist/pipeline/node-registry.js +20 -1
  15. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +34 -0
  16. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +105 -0
  17. package/dist/pipeline/nodes/local-script-check-node.js +81 -0
  18. package/dist/pipeline/nodes/review-findings-form-node.js +14 -14
  19. package/dist/pipeline/prompt-registry.js +1 -3
  20. package/dist/pipeline/registry.js +2 -0
  21. package/dist/pipeline/value-resolver.js +7 -1
  22. package/dist/prompts.js +0 -2
  23. package/dist/structured-artifacts.js +33 -0
  24. package/docker-compose.yml +384 -0
  25. package/package.json +7 -3
  26. package/run_linter.sh +89 -0
  27. package/run_tests.sh +113 -0
  28. package/verify_build.sh +104 -0
  29. package/dist/executors/claude-summary-executor.js +0 -31
  30. package/dist/executors/configs/claude-summary-config.js +0 -8
  31. package/dist/pipeline/flow-runner.js +0 -13
  32. package/dist/pipeline/flow-specs/test-fix.json +0 -24
  33. package/dist/pipeline/flow-specs/test-linter-fix.json +0 -24
  34. package/dist/pipeline/flow-specs/test.json +0 -19
  35. package/dist/pipeline/flow-types.js +0 -1
  36. package/dist/pipeline/flows/implement-flow.js +0 -47
  37. package/dist/pipeline/flows/plan-flow.js +0 -42
  38. package/dist/pipeline/flows/review-fix-flow.js +0 -62
  39. package/dist/pipeline/flows/review-flow.js +0 -124
  40. package/dist/pipeline/flows/test-fix-flow.js +0 -12
  41. package/dist/pipeline/flows/test-flow.js +0 -32
  42. package/dist/pipeline/nodes/claude-summary-node.js +0 -38
  43. package/dist/pipeline/nodes/implement-codex-node.js +0 -16
  44. package/dist/pipeline/nodes/task-summary-node.js +0 -42
@@ -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 -> test -> review -> review-fix -> test`
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`, `test`, and `auto`
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`, `test`, `test-fix`, `test-linter-fix`, `run-tests-loop`, `run-linter-loop`, and `auto`
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 and build/test verification.
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,3 @@
1
+ export const fetchGitLabReviewExecutorDefaultConfig = {
2
+ authEnvVar: "GITLAB_TOKEN",
3
+ };
@@ -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
- test: "Запускает verify/build-проверку в контейнере и показывает результат с краткой сводкой при падении.",
466
- "test-fix": "Прогоняет тесты, исправляет найденные проблемы и готовит код к следующей успешной проверке.",
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
- try {
726
- await runDeclarativeFlowBySpecFile("implement.json", config, {
727
- taskKey: config.taskKey,
728
- dockerComposeFile: config.dockerComposeFile,
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
- try {
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
- dockerComposeFile: config.dockerComposeFile,
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
- "dockerComposeFile": { "ref": "params.dockerComposeFile" },
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
- "dockerComposeFile": { "ref": "params.dockerComposeFile" },
207
+ "runTestsScript": { "ref": "params.runTestsScript" },
208
208
  "extraPrompt": { "ref": "params.extraPrompt" }
209
209
  }
210
210
  }