Conversation
2753b66 to
6f4b548
Compare
|
Hi @potiuk, I've implemented the GitLab bridge (
Whenever you have time, I'd really appreciate your initial thoughts or guidance on whether this direction looks good or if anything needs adjusting. Thank you! |
Resolves apache#305 Phase 1: Python CLI package skeleton, workspace integration, and read-only API client.
Resolves apache#305 Component 2: 100% offline deterministic test coverage for client, issues, MRs, pipelines, and CLI using pytest and mock.
Resolves apache#305 Component 3: Detailed operations catalogue, prerequisites, usage guide, and issue template schemas for GitLab adapter.
Resolves apache#305 Component 4: Finalize the adapter integration by adding it to the official capability taxonomy map and replacing its tracked extension point references with its shipped status in the registries.
Also fixes gitlab README prerequisites formatting to satisfy validator strict mode
- Add GitLab API client, CLI, issues, merge requests, and pipeline inspection - Add 100% offline unit tests with deterministic HTTP mock responses - Add Prerequisites, Configuration, Kind, and Vendor declarations in README.md - Register GitLab capabilities in docs/vendor-neutrality.md
- Use path.as_posix() for cross-platform forward-slash skip path matching - Add UTF-8 encoding and replace error handling in _git_show subprocess calls - Support Git-on-Windows pointer files in skill discovery and capability resolution - Gracefully skip directory symlink creation test when lacking OS privileges (WinError 1314)
- Reduce combined description and when_to_use length to 1489 characters to comply with the 1536 character truncation limit
ddc6f85 to
9339331
Compare
…tion, and diff overflow
|
Done with the GitLab integration. |
onlyarnav
left a comment
There was a problem hiding this comment.
Thank you for putting this together! Adding first-class GitLab support to Magpie (#305) is a great step forward for forge neutrality. The stdlib-only implementation (urllib.request), zero runtime dependencies, offline unit test suite with mock responses, and safe-redirect credential protection (_SafeRedirectHandler) are very well designed.
However, there are several blocking architectural and functional issues that need to be resolved before this can be merged:
1. Overstated Capabilities & Artificial Vendor-Neutrality Inflation [Blocking]
-
Files:
-
Problem:
tools/gitlab/README.mddeclares:**Capability:** contract:tracker + contract:source-control + contract:change-request **Kind:** implementation **Vendor:** GitLab
This causes
vendor-neutrality-scoreto count GitLab as a complete, selectable backend vendor for all three contracts indocs/vendor-neutrality.md.
However,magpie-gitlabcurrently implements only 5 read-only GET operations:contract:tracker: Onlyissue listandissue get(no issue creation, commenting, labeling, assignment, or closing).contract:source-control: Onlyrepo get/ project metadata (no branch listing, commit fetching, diffs, or VCS operations).contract:change-request: Onlymr list,mr get,mr diff,mr commits, and pipeline status (no MR creation, review, commenting, approval, or merging).
Furthermore, the PR description states:
"HITL — All state-mutating actions (commenting, labelling, approving, merging) are gated on explicit user confirmation."
In reality, none of these state-mutating actions exist in
tools/gitlab/src/magpie_gitlab(there are zeroPOST,PUT,PATCH, orDELETErequests in the package). -
Required Fix:
Follow the standard established bytools/bitbucket:- Add
**Coverage:** partialtotools/gitlab/README.md. - In
docs/labels-and-capabilities.md, mark it asCoverage: partialfoundation that must not be counted as a complete/selectable backend until write operations (review, comments, merge, issue mutation) are implemented. - Revert the complete-backend vendor additions in
docs/vendor-neutrality.md(or list as partial foundation).
- Add
2. Mandatory Token Requirement Breaks Unauthenticated Public Project Reads [Blocking]
- File:
tools/gitlab/src/magpie_gitlab/client.py - Problem:
Line 118 unconditionally enforces:Becausetoken = require(config.token, "GITLAB_TOKEN or CI_JOB_TOKEN")
get_json()andget_paged_json()always call_auth_headers(), any attempt to query public projects, public issues, or public MRs ongitlab.comor self-hosted instances (e.g.salsa.debian.org, GNOME GitLab) without an environment token immediately fails with:
GitLabError: GITLAB_TOKEN or CI_JOB_TOKEN is required. - Required Fix:
GitLab REST API v4 allows unauthenticated reads on public projects. Allowconfig.tokento be optional for read operations:def _auth_headers(config: GitLabConfig) -> dict[str, str]: headers: dict[str, str] = {"Accept": "application/json"} if not config.token: return headers if config.token_type == "job_token": headers["JOB-TOKEN"] = config.token else: headers["Authorization"] = f"Bearer {config.token}" return headers
3. URL Scheme Validation Logic Flaw on Localhost [Security / Bug]
- File:
tools/gitlab/src/magpie_gitlab/client.py - Problem:
Because the condition is
parsed = urllib.parse.urlparse(url) if parsed.scheme != "https" and parsed.hostname not in ( "localhost", "127.0.0.1", "::1", ): raise GitLabError(f"Insecure instance URL scheme '{parsed.scheme}': HTTPS is required")
parsed.scheme != "https" and parsed.hostname not in (...), wheneverparsed.hostnameis"localhost", any scheme (e.g.ftp://localhost,file://localhost) evaluates toTrue and False == Falseand bypasses validation without error. - Required Fix:
Explicitly allow only HTTPS generally, and HTTP strictly for localhost:if parsed.scheme == "https": return if parsed.scheme == "http" and parsed.hostname in ("localhost", "127.0.0.1", "::1"): return raise GitLabError(f"Insecure instance URL scheme '{parsed.scheme}': HTTPS is required")
4. list_mr_pipelines Implemented but Unexposed in CLI
- Files:
- Problem:
pipelines.pyimplementslist_mr_pipelines(project, mr_iid, config), butcli.pyhas no subcommand for it (onlypipeline statusis registered). - Required Fix:
Add a command incli.pyundermr(e.g.,magpie-gitlab mr pipelines <project> <mr_iid>) to exposelist_mr_pipelines.
5. Unbounded Pagination in get_paged_json
- File:
tools/gitlab/src/magpie_gitlab/client.py - Problem:
get_paged_json()followsX-Next-Pageindefinitely. On repositories with thousands of issues or merge requests,magpie-gitlab issue list <project>will execute dozens or hundreds of sequential HTTP calls and buffer all records in memory. - Suggestion:
Add an optional--limitormax_pagesparameter to avoid unbounded fetches in automated agent loops.
6. Support Canonical GitLab PRIVATE-TOKEN Header
- File:
tools/gitlab/src/magpie_gitlab/client.py - Problem:
Personal, Project, and Group Access Tokens (glpat-...) are canonically authenticated in GitLab REST API v4 using thePRIVATE-TOKEN: <token>header. WhileAuthorization: Beareris supported on gitlab.com, older self-hosted instances (e.g. Debian Salsa) or enterprise reverse proxies may reject Bearer headers for PATs. - Suggestion:
UsePRIVATE-TOKEN: <token>whentoken.startswith("glpat-")or support it as the standard header for non-job tokens.
7. Bundled Unrelated Changes
- Files:
plugins/magpie-release-management/skills/prepare/SKILL.md(unrelated description edits)docs/mode-economics.md(generated token count churn fromprepare/SKILL.md)tools/gitlab/uv.lock(redundant nested lockfile; Magpie uses a single workspace rootuv.lock)tools/skill-and-tool-validator/src/skill_and_tool_validator/__init__.py(Windows symlink pointer resolution)
- Suggestion:
- Delete
tools/gitlab/uv.lock(the workspace rootuv.lockalready includesmagpie-gitlab). - Revert the unrelated changes to
prepare/SKILL.mdanddocs/mode-economics.md. - Isolate the cross-platform Windows validator fixes into a separate, dedicated PR.
- Delete
…direct, and pagination
|
Thanks for the detailed review @onlyarnav! I have addressed all requested changes across the 7 points:
Ready for another look! |
potiuk
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround. Points 2, 3, 6 and 7 from @onlyarnav's review are fixed and I checked them against the diff. Point 1, the blocking one, is not: the reply says Coverage: partial was added and the docs switched to the partial-foundation form, but no file in the current head (5a00631) carries that marker. GitLab is still presented as a complete backend in four places. There are also a few code-level issues, detailed below and inline.
Blocking — GitLab is still counted as a complete backend
The
partialqualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend.
—docs/labels-and-capabilities.md,tools/bitbucketrow
The adapter has seven read-only GETs and no writes. Still:
tools/gitlab/README.mdhas no**Coverage:** partialline (comparetools/bitbucket/README.md).- The
docs/labels-and-capabilities.mdrow describes a full forge bridge with noCoverage: partialqualifier. docs/adapters/registry.mdlistsgitlabwithout thepartial-read-onlymarker Bitbucket carries, and drops GitLab #305 from the open-extension column.docs/vendor-neutrality.mdputs GitLab in "Backends today" as a complete backend, removes it from the open extension points, and the regenerated contract table counts it as a fifth / sixth / fourth vendor.
Please mirror the Bitbucket precedent: add the Coverage line to the README, add the partial qualifier to the capabilities row and the registry, keep GitLab #305 listed as open coverage work, and list GitLab in vendor-neutrality.md as a partial foundation excluded from complete-backend counts (the same wording used for Bitbucket). For the same reason, the PR should say Part of #305 rather than Closes #305.
Unvalidated path and query parameters
issue_iid, mr_iid and pipeline_id are interpolated into the URL path as-is, and --state goes into the query string without encoding. An ID taken from issue or MR text (which is external content an agent reads) can walk to another API endpoint with the caller's token, e.g. mr get grp/proj '1/../../../../user'. A state like opened&per_page=1 injects parameters. The fix is small: type=int on the ID positionals, choices= on --state (opened/closed/all, plus merged/locked for MRs), and urllib.parse.urlencode to build the query.
mr diff uses the deprecated /changes endpoint
GET /projects/:id/merge_requests/:iid/changes has been deprecated since GitLab 15.7 and is slated for removal in API v5. Its replacement, GET .../merge_requests/:iid/diffs, is paginated. Moving to it also fixes the current behaviour of failing outright on large MRs (overflow: true): the paginated endpoint returns the whole diff instead.
Silent truncation in get_paged_json
DEFAULT_MAX_PAGES = 10 caps every list at 1,000 items, but the result gives no sign that anything was cut. An agent that runs issue list on a large project gets back a partial list and treats it as complete. get_mr_diff already raises on overflow; pagination should be just as explicit (raise, or return a truncation marker the CLI prints to stderr). Also, --limit slices the result after all pages have been fetched, so it doesn't reduce the number of requests. Pass it through as max_pages (ceil(limit / 100)). Separately, if a later page returns a non-list, return [data] throws away every item collected so far.
Smaller observations
client.py,_SafeRedirectHandler: the origin check compareshostnameonly, so a same-host redirect to a different port (gitlab.example.com:8443) keeps the token. Compare(scheme, hostname, port).client.py,_auth_headers: theglpat-prefix check wins overGITLAB_AUTH_SCHEME, soGITLAB_AUTH_SCHEME=bearercan't force Bearer for a PAT, and unknown values are silently ignored.token_type="bearer"is also recorded for tokens that end up sent asPRIVATE-TOKEN. Suggest letting an explicit scheme win and rejecting unknown values.client.py:require()is no longer used outside the tests since the token became optional; drop it.hasattr(response, "headers")is always true.cli.py: the subparsers aren'trequired=True, somagpie-gitlab issueprints the top-level help instead of theissuehelp.- Tests:
conftest.pypatchesurllib.request.build_openerglobally, so nothing checks that_build_opener()actually installs_SafeRedirectHandler. A regression to plainurlopenwould still pass. Add one un-patched assertion on the opener's handlers.test_get_paged_json_multi_pageuses a URL with no query string, so rebuilding the page-2 URL withstate=…&per_page=100&page=2is never checked.- Several tests set
GITLAB_TOKENwithout clearingCI_JOB_TOKEN/GITLAB_AUTH_SCHEME, so they depend on the developer's environment.
tool.md: the reply says it was updated formr pipelines, but the catalogue still lists only five operations.repo get,mr commitsandmr pipelinesare missing.README.md: the prerequisites still say a token is required. It is now optional for public projects.GITLAB_AUTH_SCHEMEis not documented.- The PR description is out of date. It still describes HITL-gated "commenting, labelling, approving, merging" (the adapter has no writes) and Windows validator fixes (reverted in this push).
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. After you've
addressed the points above and pushed an update, an Apache Magpie
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md.
|
|
||
| # GitLab bridge | ||
|
|
||
| **Capability:** contract:tracker + contract:source-control + contract:change-request |
There was a problem hiding this comment.
Blocking. No **Coverage:** partial line here. The adapter implements seven read-only GETs and no writes, so it is a partial foundation, not a complete tracker / source-control / change-request backend. Mirror tools/bitbucket/README.md, which carries **Coverage:** partial between Capability and Kind.
| | [`tools/bitbucket`](../tools/bitbucket/) | `contract:change-request` + `contract:tracker` | Coverage: `partial`. Bitbucket Cloud and Bitbucket Data Center bridge foundation for repository metadata context, branch restriction context for PR-management decisions, pull-request discovery/fetching, read-only commit fetching, read-only diff fetching, comments-only discussion fetching, read-only review-state fetching, Cloud-only pull-request task listing/fetching, read-only merge-check context fetching, and read-only status fetching, plus narrowly scoped Cloud pull-request comment creation and approve/unapprove actions. Tracker coverage includes Cloud-only issue listing/fetching, issue comment fetching, issue attachment metadata fetching, and confirmed issue-comment creation. The `partial` qualifier means this tool implements named contract operations but does not satisfy the complete contract and must not be counted as a complete/selectable backend. Broader pull-request review/mutation, broader issue writes, and linked Jira handoff coverage remain incomplete. | | ||
| | [`tools/fossil`](../tools/fossil/) | `contract:tracker` + `contract:source-control` | Fossil SCM forge bridge: integrates local SQLite-backed ticket tracking, wiki, and forum reads with the version-control shim | | ||
| | [`tools/github`](../tools/github/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | GitHub REST / GraphQL tracker substrate (called by every lifecycle phase) plus the Git source-control binding documented in [`source-control.md`](../tools/github/source-control.md) (runnable backend in [`tools/vcs`](../tools/vcs/)) and the pull-request review/merge gate (`change-request`; the ASF default backend, alongside `tools/jira-patch/` and `tools/mail-patch/` for SVN-first projects) | | ||
| | [`tools/gitlab`](../tools/gitlab/) | `contract:tracker` + `contract:source-control` + `contract:change-request` | GitLab REST API v4 forge bridge: project issues, merge requests, diffs, and pipelines | |
There was a problem hiding this comment.
Blocking. This row needs the Coverage: partial qualifier and a list of what is actually implemented (issue list/get, MR list/get/diff/commits/pipelines, pipeline status, project get), plus the sentence that it "must not be counted as a complete/selectable backend", as in the Bitbucket row just above.
| | [`tools/scan-format`](../../tools/scan-format/) | ASVS | other scanner formats | | ||
| | [`tools/vcs`](../../tools/vcs/) | Git, Mercurial, Fossil | Subversion [\#602](https://github.com/apache/magpie/issues/602), Jujutsu [\#603](https://github.com/apache/magpie/issues/603), Perforce [\#605](https://github.com/apache/magpie/issues/605) | | ||
| | Forge / tracker | [`github`](../../tools/github/), [`jira`](../../tools/jira/), [`bitbucket`](../../tools/bitbucket/) `partial-read-only` foundation, [`sourcehut`](../../tools/sourcehut/), [`fossil`](../../tools/fossil/) | GitLab [\#305](https://github.com/apache/magpie/issues/305), Forgejo/Gitea [\#310](https://github.com/apache/magpie/issues/310), Pagure [\#312](https://github.com/apache/magpie/issues/312), deeper Bitbucket/Jira coverage [\#606](https://github.com/apache/magpie/issues/606), Bugzilla [\#302](https://github.com/apache/magpie/issues/302) | | ||
| | Forge / tracker | [`github`](../../tools/github/), [`jira`](../../tools/jira/), [`bitbucket`](../../tools/bitbucket/) `partial-read-only` foundation, [`sourcehut`](../../tools/sourcehut/), [`fossil`](../../tools/fossil/), [`gitlab`](../../tools/gitlab/) | Forgejo/Gitea [\#310](https://github.com/apache/magpie/issues/310), Pagure [\#312](https://github.com/apache/magpie/issues/312), deeper Bitbucket/Jira coverage [\#606](https://github.com/apache/magpie/issues/606), Bugzilla [\#302](https://github.com/apache/magpie/issues/302) | |
There was a problem hiding this comment.
Blocking. Please add the partial-read-only marker next to gitlab (as bitbucket has) and keep GitLab #305 in the open-extension column. Full GitLab coverage is still open work.
| The forge/tracker extension points are open, labelled `good first | ||
| issue`, not hypothetical: | ||
| [GitLab](https://github.com/apache/magpie/issues/305), | ||
|
|
There was a problem hiding this comment.
Blocking. GitLab should stay listed as an open extension point until parity lands. As it stands, removing the line also leaves a blank line that splits the paragraph in two.
| | LLM backend | ✅ by construction | Claude Code, Ollama, vLLM, Apache-hosted, Bedrock, direct Anthropic | Any endpoint meeting the capability floor + privacy gate | | ||
| | Agentic harness | ✅ by construction (`AGENTS.md` standard) | Claude Code; OpenCode; [Codex adapter](adapters/codex.md) (experimental); [Gemini adapter](adapters/gemini.md) (experimental); community use under Cursor, Copilot, Kiro | Remaining runtime adapters [#314–#322](https://github.com/apache/magpie/issues?q=is%3Aissue+state%3Aopen+adapter+in%3Atitle) | | ||
| | Forge / tracker | ✅ by construction | GitHub, Jira, SourceHut; Bitbucket `partial-read-only` foundation excluded from complete-backend counts; CVE/scan/relay via adapter contracts | GitLab [#305](https://github.com/apache/magpie/issues/305), Forgejo/Gitea [#310](https://github.com/apache/magpie/issues/310), Pagure [#312](https://github.com/apache/magpie/issues/312), full Bitbucket tracker/change-request/Jira coverage [#606](https://github.com/apache/magpie/issues/606), Bugzilla [#302](https://github.com/apache/magpie/issues/302) | | ||
| | Forge / tracker | ✅ by construction | GitHub, Jira, SourceHut, GitLab; Bitbucket `partial-read-only` foundation excluded from complete-backend counts; CVE/scan/relay via adapter contracts | Forgejo/Gitea [#310](https://github.com/apache/magpie/issues/310), Pagure [#312](https://github.com/apache/magpie/issues/312), full Bitbucket tracker/change-request/Jira coverage [#606](https://github.com/apache/magpie/issues/606), Bugzilla [#302](https://github.com/apache/magpie/issues/302) | |
There was a problem hiding this comment.
Blocking. This lists GitLab as a complete backend. Please use the Bitbucket wording instead ("GitLab partial-read-only foundation excluded from complete-backend counts") and keep GitLab #305 in the right-hand column.
| scheme = config.auth_scheme.lower() | ||
| if config.token_type == "job_token" or scheme in ("job-token", "job_token"): | ||
| headers["JOB-TOKEN"] = config.token | ||
| elif config.token.startswith("glpat-") or scheme in ("private-token", "privatetoken"): |
There was a problem hiding this comment.
The glpat- prefix check wins over an explicit GITLAB_AUTH_SCHEME, so GITLAB_AUTH_SCHEME=bearer can't force Bearer for a PAT, and unknown scheme values are silently ignored. Suggest: explicit scheme first, reject unknown values, and set token_type to what is actually sent (load_config records "bearer" for tokens that go out as PRIVATE-TOKEN).
| monkeypatch.setattr(urllib.request, "urlopen", mock_open) | ||
| monkeypatch.setattr( | ||
| urllib.request, | ||
| "build_opener", |
There was a problem hiding this comment.
Patching urllib.request.build_opener globally means no test ever checks that _build_opener() installs _SafeRedirectHandler. A regression to plain urlopen would stay green. Please add one un-patched test asserting a _SafeRedirectHandler instance is in _build_opener().handlers.
|
|
||
| cfg = load_config() | ||
| items = get_paged_json("https://gitlab.example.com/api/v4/projects/test/issues", cfg) | ||
| assert items == [{"id": 1}, {"id": 2}] |
There was a problem hiding this comment.
Nit: the URL here has no query string, so rebuilding the page-2 URL (state=opened&per_page=100&page=2) is never checked. Worth asserting mock_urlopen.call_args_list[1][0][0].full_url with a ?state=opened input.
| | List issues | `magpie-gitlab issue list <project>` | | ||
| | Read MR | `magpie-gitlab mr get <project> <mr_iid>` | | ||
| | MR Diff | `magpie-gitlab mr diff <project> <mr_iid>` | | ||
| | CI Status | `magpie-gitlab pipeline status <project> <pipeline_id>` | |
There was a problem hiding this comment.
The catalogue is missing repo get, mr commits and mr pipelines (the reply said this was updated for mr pipelines). The --limit flag is also undocumented.
|
|
||
| - **Runtime:** Python 3.11+ via `uv`. | ||
| - **CLIs:** `uv`. | ||
| - **Credentials / auth:** `GITLAB_TOKEN` or `CI_JOB_TOKEN` with API access. |
There was a problem hiding this comment.
Nit: since the unauthenticated-read change a token is optional for public projects. Please say so here, and document GITLAB_AUTH_SCHEME, which load_config() reads.
# Conflicts: # docs/vendor-neutrality.md
…n, auth, and input validation
|
Thanks @potiuk for the detailed review and guidance. All points from your review have been addressed in the latest push (1efbfea):
|
potiuk
left a comment
There was a problem hiding this comment.
Most of the last round is fixed, including the blocking point: GitLab is now presented as a partial-read-only foundation on every surface. Three things still need to land before merge: a documented auth-scheme value the code rejects, one remaining silent-truncation path, and a rebase to regenerate .github/labeler.yml.
I checked the previous points against 1efbfea rather than the reply:
- Fixed: the Coverage / capabilities / registry / vendor-neutrality wording;
type=intandchoices=withurlencode;/diffs; non-list later pages; the(scheme, host, port)redirect check; explicit-scheme precedence;required=True; the un-patched opener test; the page-2 query-string test; and thetool.md/ README additions. All seven ofonlyarnav's points are fixed as well. - Still open, although the reply lists them as done:
require()and thehasattrcheck,token_type, and the shared test fixture (inline).
GITLAB_AUTH_SCHEME=JobToken is rejected (tools/gitlab/README.md:45)
The README lists PrivateToken, Bearer, JobToken. _auth_headers lowercases the value and accepts private-token/privatetoken, bearer, and job-token/job_token, so "JobToken".lower() → jobtoken raises Unsupported GITLAB_AUTH_SCHEME: 'JobToken'. The tests use Job-Token, so nothing catches it.
elif scheme in ("job-token", "job_token"): # ← "jobtoken" (README spelling) falls through to the raise
headers["JOB-TOKEN"] = config.token
else:
raise GitLabError(f"Unsupported GITLAB_AUTH_SCHEME: '{config.auth_scheme}'")Please accept jobtoken (client.py:166) and add a test for the README spelling, or change the README to the spellings the code accepts.
--limit above 1000 is still silently truncated (client.py:249)
With --limit 2000, line 220 clamps target_pages to min(20, DEFAULT_MAX_PAGES) = 10, so the loop stops at 1000 items. The notice only prints when limit is None, so the caller gets half of what they asked for and nothing on stderr. The default-cap notice also says "use --limit to fetch more", but --limit can never exceed that cap.
if target_pages is not None and pages_fetched >= target_pages:
if has_more and limit is None: # ← an explicit --limit that hit max_pages stays silentEither derive target_pages from limit alone when it is given, or print the notice whenever has_more is true and fewer than limit items came back. Please also reword the default notice so it names a remedy that works, and add a limit > 1000 test.
Rebase needed: .github/labeler.yml is generated from tool READMEs
.github/labeler.yml landed on main after this branch was cut; it is generated from every tools/<name>/README.md **Capability:** line. It is missing from the branch, which is why PR CI is green. Merging this branch into main and running tools/dev/generate-labeler-config.py --check fails, so main's whole-tree prek would go red after merge.
Before opening or updating a PR, run
prek run --all-files… the PR's CI run is scoped to the PR's diff, so a file the branch did not touch but broke anyway … goes green on the PR and fails on themainbuild after merge.
—AGENTS.md
Please rebase onto current main, run tools/dev/generate-labeler-config.py (or prek run --all-files), and commit the regenerated file.
Smaller observations
Inline: require() / hasattr (client.py:144), token_type (client.py:121), the mock_env fixture (conftest.py:63), the user.md aside (README.md:52), mr list missing from tool.md, and the README intro wording.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. After you've
addressed the points above and pushed an update, an Apache Magpie
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md.
| - **Credentials / auth:** `GITLAB_TOKEN` (Personal Access Token, OAuth Bearer token) | ||
| or `CI_JOB_TOKEN` with API access. Tokens are optional for unauthenticated reads | ||
| on public projects. | ||
| - **Auth scheme override:** `GITLAB_AUTH_SCHEME` (`PrivateToken`, `Bearer`, `JobToken`) |
There was a problem hiding this comment.
JobToken is rejected: _auth_headers lowercases to jobtoken, which isn't in ("job-token", "job_token") (client.py:166), so it raises Unsupported GITLAB_AUTH_SCHEME. Accept jobtoken and add a test for this spelling, or list the spellings the code accepts.
| break | ||
|
|
||
| if target_pages is not None and pages_fetched >= target_pages: | ||
| if has_more and limit is None: |
There was a problem hiding this comment.
An explicit --limit above 1000 is capped at 10 pages by line 220 and stays silent here because of limit is None. Also, the default notice's "use --limit to fetch more" can't work, since --limit can't exceed the cap. Please derive target_pages from limit when given (or warn whenever fewer than limit items came back) and add a limit > 1000 test.
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| def require(value: str | None, name: str) -> str: |
There was a problem hiding this comment.
The reply says this was removed, but require() is still here (and exercised only by test_require), and hasattr(response, "headers") is still at line 236. Please drop both.
|
|
||
| if gitlab_token: | ||
| token = gitlab_token | ||
| token_type = "bearer" |
There was a problem hiding this comment.
glpat- tokens go out as PRIVATE-TOKEN, but token_type still records "bearer". Record what is actually sent, or drop the field.
|
|
||
| @pytest.fixture | ||
| def mock_env(monkeypatch): | ||
| monkeypatch.setenv("GITLAB_TOKEN", "glpat-test123") |
There was a problem hiding this comment.
The shared mock_env fixture still leaves GITLAB_AUTH_SCHEME and CI_JOB_TOKEN untouched, so every get_json / get_paged_json test depends on the developer's environment. Add monkeypatch.delenv("GITLAB_AUTH_SCHEME", raising=False) and the same for CI_JOB_TOKEN.
|
|
||
| ## Configuration | ||
|
|
||
| Set `GITLAB_TOKEN` in your environment (or `user.md`): |
There was a problem hiding this comment.
load_config() never reads user.md, and user.md can resolve inside the adopter's tree — AGENTS.md: "Tool credentials live under $HOME, never in the project tree." Please drop "(or user.md)".
| | Read repository metadata | `magpie-gitlab repo get <project>` | | ||
| | Read issue body | `magpie-gitlab issue get <project> <issue_iid>` | | ||
| | List issues | `magpie-gitlab issue list <project>` | | ||
| | Read MR | `magpie-gitlab mr get <project> <mr_iid>` | |
There was a problem hiding this comment.
Nit: the catalogue lists 8 operations but the CLI has 9 — magpie-gitlab mr list <project> is missing (its --state option is documented below).
| **Vendor:** GitLab | ||
|
|
||
| GitLab forge, issue tracker, and merge request bridge for Apache Magpie. | ||
| Provides 100% offline-tested, deterministic API access to GitLab instances, |
There was a problem hiding this comment.
Nit: "100% offline-tested … strict vendor-neutrality rules" describes the test suite, and a vendor adapter isn't itself vendor-neutral. A factual one-liner ("Read-only client for the GitLab REST API v4.") reads better; please also reflow to one sentence per line.
Summary
tools/gitlab/) as apartial-read-onlyfoundation for Apache Magpie (Part of feat(tools/gitlab): add tracker + forge bridge for GitLab-hosted projects #305).magpie-gitlab) supporting read-only operations acrosscontract:tracker(issue list/get),contract:source-control(repo metadata context), andcontract:change-request(MR get/list/diffs/commits/pipelines, pipeline status).type=intfor issue/MR/pipeline IDs) and state query parameters (urllib.parse.urlencodewith strict choices) to prevent injection and path traversal./diffsendpoint for full diff retrieval without overflow limits, safe origin-preserving redirect handling (_SafeRedirectHandler), and bounded pagination with advisory output on truncation.GITLAB_AUTH_SCHEMEconfiguration (PrivateToken,Bearer,JobToken), and self-hosted instances viaGITLAB_INSTANCE_URL.tools/gitlab/README.md(**Coverage:** partial),docs/adapters/registry.md,docs/labels-and-capabilities.md, anddocs/vendor-neutrality.md(excluded from complete backend counts per Bitbucket parity and PR fix(vendor-neutrality-score): never count partial-coverage tools as backends #1381).Type of change
tools/<system>/*.md)tools/*/withpyproject.toml)docs/,README.md)Test plan
uv run --directory tools/gitlab pytest -v(56/56 unit tests passing in ~0.35s covering client auth, redirects, pagination, diffs, issues, pipelines, and CLI validation)uv run --directory tools/gitlab mypy(Clean pass: 0 issues in 13 source files)uv run --directory tools/gitlab ruff check(Clean pass: 0 lint errors)uv run --directory tools/gitlab ruff format --check(Clean pass: 16 files formatted)uv run --project tools/vendor-neutrality-score vendor-neutrality-score(docs/vendor-neutrality.mdtable verified in sync with partial foundation status)RFC-AI-0004 compliance
urllibonly); network access explicitly declared forgitlab.comand configured self-hosted instances.**Coverage:** partial; full issue/MR mutations and write operations remain open under feat(tools/gitlab): add tracker + forge bridge for GitLab-hosted projects #305.GITLAB_INSTANCE_URL.(scheme, hostname, port)origin equivalence before forwarding auth headers.Linked issues
Part of #305