Skip to content

Commit 717f259

Browse files
committed
Add glab-discussion CLI for GitLab MR discussions
CLI wrapper around the GitLab Discussions REST API with four subcommands: - read: list/dump discussions with incremental file updates - diff: annotated diff with old/new line numbers for commenting - write: create threads, reply, or add diff notes - resolve: resolve/unresolve discussions Includes AI agent auto-detection (defaults to dump mode), bot author tagging, path sanitization, and a Claude Code skill plugin.
1 parent e1d61cc commit 717f259

33 files changed

Lines changed: 2078 additions & 0 deletions

.claude-plugin/marketplace.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "fprochazka-glab-discussion",
3+
"description": "GitLab MR discussion CLI with Claude Code skill for reading, writing, and managing discussions",
4+
"owner": {
5+
"name": "Filip Procházka"
6+
},
7+
"plugins": [
8+
{
9+
"name": "glab-discussion",
10+
"description": "Skill for working with GitLab MR discussions via the glab-discussion CLI",
11+
"version": "0.1.0",
12+
"author": {
13+
"name": "Filip Procházka",
14+
"url": "https://github.com/fprochazka"
15+
},
16+
"source": "./coding-agent-plugins/claude-code",
17+
"category": "integration",
18+
"homepage": "https://github.com/fprochazka/glab-discussion"
19+
}
20+
]
21+
}

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
jobs:
9+
lint:
10+
name: Lint
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: astral-sh/setup-uv@v7
16+
with:
17+
enable-cache: true
18+
19+
- name: Install dependencies
20+
run: uv sync --locked --dev
21+
22+
- name: Ruff check
23+
run: uv run ruff check --output-format github .
24+
25+
- name: Ruff format check
26+
run: uv run ruff format --check .
27+
28+
test:
29+
name: Test (Python ${{ matrix.python-version }})
30+
runs-on: ubuntu-latest
31+
strategy:
32+
matrix:
33+
python-version: ["3.12", "3.13"]
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- uses: astral-sh/setup-uv@v7
38+
with:
39+
python-version: ${{ matrix.python-version }}
40+
enable-cache: true
41+
42+
- name: Install dependencies
43+
run: uv sync --locked --dev
44+
45+
- name: Run tests
46+
run: uv run pytest

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Python
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
*.egg-info/
6+
dist/
7+
build/
8+
9+
# Virtual environments
10+
.venv/
11+
venv/
12+
13+
# Testing / Coverage
14+
.coverage
15+
.pytest_cache/
16+
htmlcov/
17+
18+
# Linting
19+
.ruff_cache/
20+
21+
# IDEs
22+
.idea/
23+
.vscode/
24+
*.swp
25+
*.swo
26+
*~
27+
28+
# OS
29+
.DS_Store
30+
Thumbs.db

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@README.md
2+

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# glab-discussion
2+
3+
CLI wrapper around GitLab Discussions REST API for listing, creating, and managing merge request discussions.
4+
5+
## Installation
6+
7+
```bash
8+
uv tool install glab-discussion
9+
```
10+
11+
## Usage
12+
13+
All subcommands accept `--mr-url` or `--hostname`/`--project`/`--mr-iid` to identify the merge request.
14+
15+
### read
16+
17+
Read and display MR discussions.
18+
19+
```bash
20+
glab-discussion read --mr-url https://gitlab.com/group/project/-/merge_requests/123
21+
glab-discussion read --dump # structured data output
22+
glab-discussion read --dump --full # force full rewrite
23+
```
24+
25+
### write
26+
27+
Create a new discussion or reply to an existing one.
28+
29+
```bash
30+
glab-discussion write --body "Comment text" --mr-url ...
31+
glab-discussion write --body "Reply" --reply-to DISCUSSION_ID --mr-url ...
32+
glab-discussion write --body "Diff note" --file path/to/file.py --new-line 42 --mr-url ...
33+
echo "From stdin" | glab-discussion write --body - --mr-url ...
34+
```
35+
36+
### diff
37+
38+
Show MR diff information.
39+
40+
```bash
41+
glab-discussion diff --mr-url ...
42+
glab-discussion diff --file path/to/file.py --mr-url ...
43+
glab-discussion diff --version 3 --mr-url ...
44+
```
45+
46+
### resolve
47+
48+
Resolve or unresolve a discussion.
49+
50+
```bash
51+
glab-discussion resolve DISCUSSION_ID --mr-url ...
52+
glab-discussion resolve DISCUSSION_ID --unresolve --mr-url ...
53+
```
54+
55+
## Requirements
56+
57+
- `glab` CLI installed and authenticated
58+
- Python 3.12+
59+
60+
## Development
61+
62+
```bash
63+
git clone https://github.com/fprochazka/glab-discussion.git
64+
cd glab-discussion
65+
uv sync --dev
66+
```
67+
68+
Run tests and linting:
69+
70+
```bash
71+
uv run ruff format .
72+
uv run ruff check .
73+
uv run pytest
74+
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "glab-discussion",
3+
"version": "0.1.0",
4+
"description": "Skill for working with GitLab MR discussions via the glab-discussion CLI",
5+
"author": {
6+
"name": "Filip Prochazka"
7+
},
8+
"repository": "https://github.com/fprochazka/glab-discussion",
9+
"license": "MIT",
10+
"keywords": ["gitlab", "discussions", "merge-request", "code-review"]
11+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
name: glab-discussion
3+
description: >-
4+
This skill should be used when the user asks to "read MR discussions",
5+
"list MR comments", "reply to a discussion", "resolve a discussion",
6+
"add a diff note", "review MR comments", "dump discussions",
7+
"show MR diff for commenting", or needs to interact with GitLab merge request
8+
discussions. Provides CLI reference for the glab-discussion tool.
9+
---
10+
11+
# glab-discussion CLI
12+
13+
CLI for listing, creating, and managing GitLab merge request discussions.
14+
Auto-detects the MR from the current git branch. Override with `--mr-url <url>`
15+
or `--hostname <host> --project <path> --mr-iid <n>`.
16+
17+
## Subcommands
18+
19+
### read — Read discussions
20+
21+
```bash
22+
glab-discussion read # auto-selects dump mode in non-interactive environments
23+
glab-discussion read --dump # write per-thread TXT files, incrementally updated
24+
glab-discussion read --dump --full # force full rewrite of all files
25+
glab-discussion read --no-dump # print to stdout instead
26+
```
27+
28+
Writes one TXT file per discussion thread to `/tmp/glab-discussion/<host>/mr-<iid>/`.
29+
Incremental — only rewrites files when content has changed. Prints a summary of new/updated/deleted files.
30+
31+
Each file contains:
32+
- Header: discussion ID, type (General/DiffNote), resolved status, file/line for diff notes, URL
33+
- Body: chronological notes with timestamps, usernames, `[BOT]` for bot accounts
34+
35+
### diff — Show commentable diff with line numbers
36+
37+
```bash
38+
glab-discussion diff # all changed files
39+
glab-discussion diff --file src/Foo.java # single file
40+
glab-discussion diff --version <version_id> # specific diff version
41+
```
42+
43+
Outputs an annotated diff showing both old and new line numbers for each line.
44+
Use these line numbers with `write --new-line` or `write --old-line`.
45+
46+
Output format:
47+
```
48+
Version: <id>
49+
base_sha: <sha>
50+
head_sha: <sha>
51+
start_sha: <sha>
52+
53+
--- a/src/Foo.java
54+
+++ b/src/Foo.java
55+
old | new |
56+
41 | 41 | var x = 1;
57+
42 | | - var y = old();
58+
| 42 | + var y = newMethod();
59+
43 | 43 | var z = 3;
60+
```
61+
62+
### write — Create discussion, reply, or diff note
63+
64+
```bash
65+
# New general discussion thread
66+
glab-discussion write --body "Starting a thread"
67+
glab-discussion write --body - # read body from stdin
68+
69+
# Reply to existing thread
70+
glab-discussion write --reply-to <discussion_id> --body "My reply"
71+
72+
# Diff note on a specific line
73+
glab-discussion write --file src/Foo.java --new-line 42 --body "Issue here"
74+
glab-discussion write --file src/Foo.java --old-line 10 --body "Was wrong"
75+
glab-discussion write --file src/Foo.java --new-line 42 --commit <sha> --body "On this version"
76+
```
77+
78+
**Modes** (mutually exclusive):
79+
- `--reply-to <discussion_id>` — reply to an existing thread
80+
- `--file <path>` — create a diff note (requires `--new-line` and/or `--old-line`)
81+
- Neither — create a new general discussion thread
82+
83+
For diff notes, `--commit <sha>` optionally pins to a specific diff version (matched against `head_commit_sha`). Without it, uses the latest version.
84+
85+
### resolve — Resolve/unresolve a discussion
86+
87+
```bash
88+
glab-discussion resolve <discussion_id>
89+
glab-discussion resolve <discussion_id> --unresolve
90+
```
91+
92+
## Typical AI Workflow
93+
94+
1. **Read discussions:** `glab-discussion read`
95+
2. **Review dumped files:** Read the TXT files to understand discussion threads
96+
3. **Check diff context:** `glab-discussion diff --file <path>` to see commentable lines
97+
4. **Reply:** `glab-discussion write --reply-to <id> --body "..."`
98+
5. **Add diff note:** `glab-discussion write --file <path> --new-line <n> --body "..."`
99+
6. **Resolve:** `glab-discussion resolve <id>`
100+
7. **Re-read:** `glab-discussion read` to see updated state (incremental, only changed files)
101+

pyproject.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[build-system]
2+
requires = ["hatchling", "hatch-vcs"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "glab-discussion"
7+
dynamic = ["version"]
8+
description = "CLI wrapper around GitLab Discussions REST API for listing, creating, and managing MR discussions"
9+
readme = "README.md"
10+
license = "MIT"
11+
requires-python = ">=3.12"
12+
authors = [
13+
{name = "Filip Procházka", email = "dev@fprochazka.cz"}
14+
]
15+
keywords = ["gitlab", "discussions", "merge-request", "cli", "code-review"]
16+
classifiers = [
17+
"Development Status :: 3 - Alpha",
18+
"Environment :: Console",
19+
"Intended Audience :: Developers",
20+
"License :: OSI Approved :: MIT License",
21+
"Programming Language :: Python :: 3",
22+
"Programming Language :: Python :: 3.12",
23+
"Programming Language :: Python :: 3.13",
24+
"Topic :: Software Development :: Version Control :: Git",
25+
"Typing :: Typed",
26+
]
27+
dependencies = []
28+
29+
[project.scripts]
30+
glab-discussion = "glab_discussion.cli:main"
31+
32+
[project.urls]
33+
Homepage = "https://github.com/fprochazka/glab-discussion"
34+
Repository = "https://github.com/fprochazka/glab-discussion"
35+
Issues = "https://github.com/fprochazka/glab-discussion/issues"
36+
37+
[dependency-groups]
38+
dev = [
39+
"pytest>=8.0",
40+
"pytest-cov>=5.0",
41+
"ruff>=0.8",
42+
]
43+
44+
[tool.hatch.version]
45+
source = "vcs"
46+
47+
[tool.hatch.build.targets.wheel]
48+
packages = ["src/glab_discussion"]

ruff.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
target-version = "py312"
2+
line-length = 120
3+
src = ["src", "tests"]
4+
5+
[lint]
6+
select = ["E", "F", "W", "I", "UP", "B", "SIM"]

src/glab_discussion/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)