Metadata-Version: 2.5
Name: glab-discussion
Version: 0.4.0
Summary: CLI wrapper around GitLab Discussions REST API for listing, creating, and managing MR discussions
Project-URL: Homepage, https://github.com/fprochazka/glab-discussion
Project-URL: Repository, https://github.com/fprochazka/glab-discussion
Project-URL: Issues, https://github.com/fprochazka/glab-discussion/issues
Author-email: Filip Procházka <dev@fprochazka.cz>
License-Expression: MIT
License-File: LICENSE
Keywords: cli,code-review,discussions,gitlab,merge-request
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Version Control :: Git
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# glab-discussion

CLI wrapper around GitLab Discussions REST API for listing, creating, and managing merge request discussions. Built on top of [`glab`](https://docs.gitlab.com/cli/) for authentication.

## Why

The `glab` CLI has no native support for merge request discussions. Reviewing comments, replying to threads, adding inline code review notes, and resolving discussions all require manual API calls with complex nested JSON payloads. This tool wraps those APIs into simple commands, with an incremental dump mode designed for AI agent workflows — each discussion thread gets its own file, only changed threads are rewritten, and bot authors are automatically tagged.

## Installation

```bash
uv tool install glab-discussion
```

### Claude Code plugin

The repo includes a Claude Code plugin with:

- a **skill** that teaches AI agents how to use `glab-discussion`
- a **PreToolUse hook** that blocks three command shapes and redirects the agent to `glab-discussion` instead. This keeps thread IDs, resolve state, and inline diff positions in scope rather than letting the agent wrangle raw API JSON.

```bash
claude plugin marketplace add fprochazka/glab-discussion
claude plugin install glab-discussion@fprochazka-glab-discussion
```

To upgrade after a new release:

```bash
uv tool install --force glab-discussion
uv tool install --force bash-classify
claude plugin marketplace update fprochazka-glab-discussion
claude plugin update glab-discussion@fprochazka-glab-discussion
```

The hook blocks:

| Rule | Shape |
|---|---|
| `mr-discussions-api` | `glab api` against a merge request's `discussions` or `notes` sub-resource |
| `mr-view-comments` | `glab mr view --comments`, its `-c` short form, and `--resolved` / `--unresolved`, which imply it |
| `mr-note` | `glab mr note`, except the read-only `glab mr note list` |

The verdict comes from [`bash-classify`](https://github.com/fprochazka/bash-classify), which parses the command and reports which of those shapes it actually *invokes*. Text that merely names one — a heredoc body, an `echo` argument, a commit message, a `grep` pattern — is not an invocation and is allowed, while a wrapper (`sudo`, `timeout`, `bash -c`, `xargs`) does not hide one.

Without bash-classify the hook still works, but in a degraded mode: it falls back to matching the raw command text. That is wrong in both directions — it denies anything that so much as mentions a blocked command, and it lets `glab mr view 42 -c` through, because the old pattern only ever knew the long `--comments` spelling. Every deny issued that way says so in its reason, and a SessionStart hook says the same thing once at the start of a session, so the agent can tell you to install or upgrade the tool (plugin SessionStart hooks need Claude Code 2.1.257 or newer; versions 2.1.216 to 2.1.252 silently skipped them).

## Usage

By default, the MR is auto-detected from the current git branch (via `glab mr view`). Override with `--mr-url` or `--hostname`/`--project`/`--mr-iid`.

### read

Read MR discussions. Prints to stdout by default, or writes per-thread files with `--dump`. In non-interactive environments (AI agents, piped output), `--dump` is the default.

```bash
glab-discussion read                 # auto-detect MR from git branch
glab-discussion read --dump          # one file per thread, incrementally updated
glab-discussion read --dump --full   # clear and rewrite all files
glab-discussion read --no-dump       # force stdout even in non-interactive mode
```

### write

Create a new discussion, reply to a thread, or add an inline diff note.

```bash
glab-discussion write --body "Comment text"
glab-discussion write --reply-to DISCUSSION_ID --body "Reply"
glab-discussion write --file path/to/file.py --new-line 42 --body "Issue here"
glab-discussion write --file path/to/file.py --old-line 10 --body "Was wrong"
echo "From stdin" | glab-discussion write --body -
```

`--new-line` corresponds to the file on the MR source branch — if the branch is checked out locally, local file line numbers match directly. `--old-line` refers to the target branch version.

### diff

Show the MR diff annotated with old/new line numbers, so you know which line numbers to use with `write --new-line` or `--old-line`.

```bash
glab-discussion diff
glab-discussion diff --file path/to/file.py
glab-discussion diff --version 3
```

### resolve

Resolve or unresolve a discussion.

```bash
glab-discussion resolve DISCUSSION_ID
glab-discussion resolve DISCUSSION_ID --unresolve
```

### edit

Edit an existing note's body.

```bash
glab-discussion edit NOTE_ID --body "Updated text"
echo "From stdin" | glab-discussion edit NOTE_ID --body -
```

### delete

Delete a note.

```bash
glab-discussion delete NOTE_ID
```

## Requirements

- [`glab` CLI](https://docs.gitlab.com/cli/) installed and authenticated
- Python 3.12+

The Claude Code plugin's hook additionally needs:

- `jq`
- [`bash-classify`](https://github.com/fprochazka/bash-classify) 0.10.0 or newer. Without it the hook still runs, but falls back to text patterns that can misfire: they deny commands that only mention a blocked command in a heredoc, a commit message or a `grep` pattern.

```bash
uv tool install bash-classify
```

## Development

```bash
git clone https://github.com/fprochazka/glab-discussion.git
cd glab-discussion
uv sync --dev
```

Run tests and linting:

```bash
uv run ruff format .
uv run ruff check .
uv run pytest
```

## Releasing

Version is derived automatically from git tags via `hatch-vcs` — no manual version bumping needed.

Before tagging, bump the version in both plugin manifest files:

- `coding-agent-plugins/claude-code/.claude-plugin/plugin.json`
- `.claude-plugin/marketplace.json`

Wait for CI to pass on master, then tag, push, and create a GitHub release:

```bash
# Review changes since last release
git log $(git describe --tags --abbrev=0)..HEAD --oneline

git tag v<version>
git push origin v<version>
gh release create v<version> --title "v<version>" --notes "..."
```

The `publish.yml` GitHub Action builds and publishes to PyPI automatically via trusted publishing.
