Skip to content

Commit 7ad303f

Browse files
fix(gitlab): address bot review nits on #93
- templates/droid-review.yml: remove top-level `stages:` block so the Component doesn't leak its stage list into the consuming project. Job-level `stage: $[[ inputs.stage ]]` is unchanged; consumers add the stage to their own `stages:` list (or omit it for the default). - templates/droid-review.yml: tighten droid_action_ref docstring to "tag/branch" since the clone uses `git clone --branch` (SHAs would silently fail). - src/gitlab/token.ts: drop CI_JOB_TOKEN fallback. It was always set in CI so the MissingGitlabTokenError never fired, producing confusing 401s on first API call instead of a clear "set GITLAB_TOKEN" message. CI_JOB_TOKEN also lacks scopes for notes/discussions. - src/mcp/gitlab-mr-server.ts: require a line anchor on ReviewComment (line for RIGHT, old_line for LEFT) via .refine(); GitLab rejects unanchored diff discussions. - src/mcp/gitlab-mr-server.ts: remove `.max(30)` cap on comments so large MRs don't fail the entire submit_review call. Tests: 445/445 pass; tsc clean. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 7561989 commit 7ad303f

4 files changed

Lines changed: 56 additions & 45 deletions

File tree

src/gitlab/token.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ export class MissingGitlabTokenError extends Error {
1010
}
1111

1212
export function setupGitlabToken(): string {
13-
const token =
14-
process.env.GITLAB_TOKEN ||
15-
process.env.OVERRIDE_GITLAB_TOKEN ||
16-
process.env.CI_JOB_TOKEN;
13+
const token = process.env.GITLAB_TOKEN || process.env.OVERRIDE_GITLAB_TOKEN;
1714

1815
if (!token) {
1916
throw new MissingGitlabTokenError();

src/mcp/gitlab-mr-server.ts

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,51 @@ function errorResult(error: unknown) {
2424
};
2525
}
2626

27-
const ReviewCommentSchema = z.object({
28-
path: z
29-
.string()
30-
.describe(
31-
"Path of the file to comment on (use the new_path from the diff)",
32-
),
33-
body: z.string().min(1).describe("Comment text (supports markdown)"),
34-
line: z
35-
.number()
36-
.int()
37-
.optional()
38-
.describe(
39-
"Line number in the new file for single-line comments, or end line for multi-line",
40-
),
41-
side: z
42-
.enum(["LEFT", "RIGHT"])
43-
.optional()
44-
.default("RIGHT")
45-
.describe(
46-
"Side of the diff: RIGHT for new/modified code, LEFT for removed code",
47-
),
48-
old_path: z
49-
.string()
50-
.optional()
51-
.describe("Path in the old file (defaults to path if unset)"),
52-
old_line: z
53-
.number()
54-
.int()
55-
.optional()
56-
.describe("Line number in the old file (required when side=LEFT)"),
57-
});
27+
const ReviewCommentSchema = z
28+
.object({
29+
path: z
30+
.string()
31+
.describe(
32+
"Path of the file to comment on (use the new_path from the diff)",
33+
),
34+
body: z.string().min(1).describe("Comment text (supports markdown)"),
35+
line: z
36+
.number()
37+
.int()
38+
.optional()
39+
.describe(
40+
"Line number in the new file. Required for side=RIGHT (the default).",
41+
),
42+
side: z
43+
.enum(["LEFT", "RIGHT"])
44+
.optional()
45+
.default("RIGHT")
46+
.describe(
47+
"Side of the diff: RIGHT for new/modified code, LEFT for removed code",
48+
),
49+
old_path: z
50+
.string()
51+
.optional()
52+
.describe("Path in the old file (defaults to path if unset)"),
53+
old_line: z
54+
.number()
55+
.int()
56+
.optional()
57+
.describe(
58+
"Line number in the old file. Required for side=LEFT comments.",
59+
),
60+
})
61+
.refine(
62+
(c) => {
63+
const side = c.side ?? "RIGHT";
64+
if (side === "LEFT") return typeof c.old_line === "number";
65+
return typeof c.line === "number";
66+
},
67+
{
68+
message:
69+
"Inline diff discussions require a line anchor: provide `line` for side=RIGHT comments, or `old_line` for side=LEFT comments.",
70+
},
71+
);
5872

5973
type ReviewComment = z.infer<typeof ReviewCommentSchema>;
6074

@@ -247,9 +261,8 @@ export function createGitlabMrServer({
247261
.describe("Optional summary note body in markdown"),
248262
comments: z
249263
.array(ReviewCommentSchema)
250-
.max(30)
251264
.optional()
252-
.describe("Inline review comments (max 30 per call)"),
265+
.describe("Inline review comments"),
253266
},
254267
async ({ mr_iid, body, comments }) => {
255268
try {

templates/droid-review.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,18 @@ spec:
7373
default: "https://gitlab.com/factory-components/droid-action.git"
7474
droid_action_ref:
7575
description: |
76-
Git ref (tag/branch/sha) of droid-action to clone at runtime.
77-
The same ref SHOULD match the `include: remote:` URL above.
76+
Git tag or branch of droid-action to clone at runtime (SHAs are
77+
not supported because the clone uses `--branch`). The same ref
78+
SHOULD match the `include: remote:` URL above.
7879
Internal: most users leave this at the default.
7980
default: "main"
8081
stage:
81-
description: "GitLab CI stage to assign the droid-review job to."
82+
description: |
83+
GitLab CI stage to assign the droid-review job to. The consuming
84+
project's `.gitlab-ci.yml` must include this stage in its
85+
`stages:` list (or omit the list to use the default ordering).
8286
default: "test"
8387
---
84-
stages:
85-
- $[[ inputs.stage ]]
86-
8788
droid-review:
8889
stage: $[[ inputs.stage ]]
8990
image: oven/bun:1.2.11

test/gitlab/token.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ describe("setupGitlabToken", () => {
3939
expect(setupGitlabToken()).toBe("glpat-override");
4040
});
4141

42-
it("falls back to CI_JOB_TOKEN", () => {
42+
it("does NOT fall back to CI_JOB_TOKEN (its scopes are insufficient for notes/discussions)", () => {
4343
process.env.CI_JOB_TOKEN = "ci-job-token";
44-
expect(setupGitlabToken()).toBe("ci-job-token");
44+
expect(() => setupGitlabToken()).toThrow(MissingGitlabTokenError);
4545
});
4646

4747
it("throws MissingGitlabTokenError when nothing is set", () => {

0 commit comments

Comments
 (0)