Skip to content

Commit 7bbee3e

Browse files
authored
feat(gitlab): gl-mrs MR triage board + watch-mine supervisor (#275)
* feat(gitlab): gl-mrs MR triage board + watch-mine supervisor gl-mrs lists MRs (default author=@me), sorted failing-first, each enriched in parallel with what you'd otherwise round-trip for: pipeline status (a failure shows the failed job name = the failure class), approval state, age, diff size, watch-state cross-reference, and conflict/draft/threads flags. Actionable footer points at the first failing-and-unwatched MR. "Mine" is just the default filter, not a special op — enumeration is a platform concern, kept out of the generic watch preset. watch-mine.sh: glue between a list-mine op and the watch preset — runs the feed op, extracts ids, spawns one watcher each. Idempotent, safe on /loop. gl-mrs:failed,iids | watch-mine turns "ping me when any of my MRs goes red" into one looped command. Enrich knobs (enrich_workers/enrich_cap/per_page) passed through as SUPERTOOL_* env. 44 unit tests; full suite 3135 passed. Refs #274 (gh-prs twin) Co-Authored-By: Max <noreply> * fix(gl-mrs): review fixes — iids None-guard, fd leak, _age skew, main() tests Addresses PR #275 review: - iids output guards against a missing iid (no stray "None" into the watch-mine feed) - _watched_iids closes the PID file via context manager (no fd leak) - _age guards future timestamps (clock skew) → "now" instead of "-1d"; datetime import hoisted to module top - +9 tests: _age skew/bad-iso, _flags True-suppression, and main() integration (iids output, missing-iid skip, failed filter, glab error, bad JSON) — covers the watch-mine.sh contract 53 gl-mrs tests pass. Co-Authored-By: Max <noreply>
1 parent cf54766 commit 7bbee3e

9 files changed

Lines changed: 981 additions & 0 deletions

File tree

.supertool.example.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@
274274
"lines": 80,
275275
"error_patterns": "ERROR,FAIL,Fatal"
276276
},
277+
"gl-mrs": {
278+
"cmd": "python3 {path}gitlab/mrs.py {args}",
279+
"timeout": 60,
280+
"description": "MR triage board. Override the gitlab preset's defaults to tune pipeline enrichment — extra keys pass through as SUPERTOOL_ENRICH_WORKERS / SUPERTOOL_ENRICH_CAP / SUPERTOOL_PER_PAGE.",
281+
"example": "gl-mrs:author=@me,failed",
282+
"enrich_workers": 8,
283+
"enrich_cap": 40,
284+
"per_page": 50
285+
},
277286
"_hidden_internal": {
278287
"cmd": "echo internal-helper",
279288
"description": "Hidden from `./supertool ops` output via status:0 — useful for ops that exist for aliases or scripts but shouldn't clutter the public listing.",

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **`gl-mrs` — MR triage board.** Lists MRs (default `author=@me`), sorted failing-first then stalest, each enriched in parallel with everything you'd otherwise round-trip for: pipeline status (a failure shows the failed **job name**`phpstan2`, `test_unit_dpt` — i.e. the failure class, pre-empting a `gl-job` call), approval state, age, diff size, watch-state cross-reference (which MRs already have a live `watch` poller), and `conflict`/`draft`/`threads` flags. Footer points at the first failing-and-unwatched MR. Filters: `author`/`reviewer`/`assignee`/`label`/`milestone`/`state`/`per`. Flags: `nopipe`, `iids` (bare id list), `failed` (only failing). Tunable via `enrich_workers`/`enrich_cap`/`per_page`. "Mine" is just the default filter, not a special op — enumeration is a platform concern, kept out of the generic `watch` preset. See [docs/presets/gitlab.md](docs/presets/gitlab.md).
13+
- **`presets/watch/watch-mine.sh` — watch-a-whole-query supervisor.** Glue between a "list mine" op (`gl-mrs`/`gh-prs`) and the `watch` preset: runs the feed op, extracts ids, spawns one watcher each. Idempotent, so it's safe on `/loop`. `gl-mrs:failed,iids | watch` turns "ping me when any of my MRs goes red" into one looped command. See [docs/presets/watch.md](docs/presets/watch.md).
1214
- **`watch` preset — background event pollers with async wake into Claude Code.** New ops `watch:SOURCE:ID[:only=...]`, `unwatch:SOURCE:ID`, `watches`. Each invocation forks a detached poller that emits state-change events to a UDS socket, a status file, and macOS Notification Center. Source-plugin contract makes new sources ~50 lines. Bundled sources: `github-pr` (PR state, checks, reviews, comments, merge/close, conflicts) and `gitlab-mr` (MR state, pipeline, merge/close, conflicts). Closes [#165](https://github.com/Digital-Process-Tools/claude-supertool/issues/165). See [docs/presets/watch.md](docs/presets/watch.md).
1315
- **`notifiers/claude-channel/` — MCP channel server for async wake.** TypeScript / Bun server that binds the watch UDS socket and pushes events into a running Claude Code session via the Channels feature (research preview, requires Claude Code v2.1.80+). Events arrive as `<channel source="claude-channel" watcher_source="<source>" id="<id>" event="<event>" ...>` tags. UDS file mode 0600 + localhost-only auth model. Launch with `claude --dangerously-load-development-channels server:claude-channel`. See [notifiers/claude-channel/README.md](notifiers/claude-channel/README.md).
1416
- **`glob` brace expansion.** `glob:src/**/*.{json,xml}` now fans out into `*.json` + `*.xml` (shell/fd/ripgrep semantics) and dedupes results, instead of silently returning 0 files. Supports multiple groups (`{a,b}.{x,y}` → 4) and nesting (`{a,b{1,2}}` → 3). Patterns without braces are unchanged. Closes [#161](https://github.com/Digital-Process-Tools/claude-supertool/issues/161).

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ supertool 'read:src/Module.py' 'read:src/Auth.py' 'grep:TODO:src/:20' 'map:src/'
3232

3333
- **`git-status`** — branch + tracking + ahead/behind + dirty files + open MR/PR + suggested next step. One call, decision ready.
3434
- **`gl-mr:NUMBER`** / **`gh-pr:NUMBER`** — full MR/PR dashboard: branch, pipeline, reviewer, approval, diff stat, comments. Replaces 4-5 `glab`/`gh` calls.
35+
- **`gl-mrs`** — MR triage board: your open MRs + per-MR pipeline status + which already have a `watch` poller running + an actionable footer. Pairs with `watch` to auto-watch every failing MR.
3536
- **`claude-log-summary:UUID`** — model, duration, tool calls, tokens, cache hit %, errors-by-tool. Audit your own runs.
3637

3738
That's a sample. supertool ships ~40 ops out of the box (built-ins + `gitlab` / `github` / `git` / `claude-log` presets) — add your own and you're past 60 fast.

docs/presets/gitlab.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ GitLab ops via the `glab` CLI. Replaces the 3-5 separate `glab` calls needed to
1212
|----|--------|-----------------|
1313
| `gl-issue` | `gl-issue:NUMBER[:full]` | Issue metadata, description, comments (truncated by default), related MRs. `:full` disables truncation |
1414
| `gl-mr` | `gl-mr:NUMBER_OR_BRANCH[:status]` | MR dashboard: branch, pipeline, reviewer/approval state, linked issue, diff stat, comments. `:status` returns slim merge-state only |
15+
| `gl-mrs` | `gl-mrs[:filters,flags]` | MR triage board, sorted failing-first then stalest. Per MR (enriched in parallel): pipeline status (a failure shows the failed **job name** = the failure class), approval state, age, diff size, watch-state cross-reference, and `conflict`/`draft`/`threads` flags — plus an actionable footer. Filters (comma-sep): `author`/`reviewer`/`assignee`/`label`/`milestone`/`state`/`per`. Flags: `nopipe` (skip enrichment), `iids` (bare id list), `failed` (only failing) |
1516
| `gl-pipeline` | `gl-pipeline:NUMBER` | Pipeline job list grouped by stage with pass/fail status and failed job IDs |
1617
| `gl-job` | `gl-job:NUMBER[:raw[:START[:END]]]` | Job failure detail: MR context + error pattern search + log tail. `:raw` dumps the full trace; `:raw:START:END` slices lines (1-indexed, inclusive) |
1718

@@ -38,8 +39,33 @@ Returns approval state, pipeline status, diff stat, and all comments in one call
3839
```
3940
Gets the full issue context and checks whether an MR already exists for the branch.
4041

42+
**Triage your open MRs:**
43+
```bash
44+
./supertool 'gl-mrs' # my MRs: pipeline, approval, age, watch-state
45+
./supertool 'gl-mrs:reviewer=@me' # MRs I'm reviewing
46+
./supertool 'gl-mrs:author=@me,failed,iids' # bare ids of my failing MRs
47+
```
48+
The last form feeds the [`watch`](watch.md) supervisor: pipe failing-MR ids straight into background pollers.
49+
4150
## Configuration
4251

52+
`gl-mrs` enrichment is tunable (parallelism, how many MRs to enrich, page size):
53+
54+
```json
55+
{
56+
"ops": {
57+
"gl-mrs": {
58+
"cmd": "python3 {path}gitlab/mrs.py {args}",
59+
"enrich_workers": 8,
60+
"enrich_cap": 40,
61+
"per_page": 50
62+
}
63+
}
64+
}
65+
```
66+
67+
The list endpoint omits pipeline, approval, and diff data, so each MR costs a couple of `glab api` calls (MR detail + approvals, plus failed-job names for failing MRs). `enrich_workers` threads those calls; `enrich_cap` bounds how many MRs get enriched; `per_page` bounds how many are listed.
68+
4369
`gl-job` error pattern search and log tail length are configurable:
4470

4571
```json

docs/presets/watch.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ Composable in one batched call:
2626
./supertool 'watch:github-pr:179' 'watch:github-pr:180' 'watches'
2727
```
2828

29+
## Watch many from a query — `watch-mine.sh`
30+
31+
`watch:SOURCE:ID` watches one id. To watch *every id a query returns* — e.g. all your failing MRs — pair it with a list op via the bundled supervisor `presets/watch/watch-mine.sh`. It runs a "list mine" op (`gl-mrs`/`gh-prs`), extracts the ids, and spawns one watcher each. Idempotent (the `watch` op skips ids already watched), so it's safe on a loop:
32+
33+
```bash
34+
# default: my failing GitLab MRs → gitlab-mr watchers
35+
bash presets/watch/watch-mine.sh
36+
37+
# re-sync every 5 min from inside Claude Code
38+
/loop 5m bash presets/watch/watch-mine.sh
39+
40+
# any feed op + source — e.g. my failing GitHub PRs (once a gh-prs op exists)
41+
bash presets/watch/watch-mine.sh 'gh-prs:author=@me,failed,iids' github-pr
42+
```
43+
44+
Args: `$1` feed op (default `gl-mrs:author=@me,failed,iids`), `$2` watch source (default `gitlab-mr`), `$3` notify events (default `pipeline_failed,merged`). The separation is deliberate — the list op owns *what's mine* (a platform concern), the watch preset stays generic. The feed op just has to emit bare ids (the `iids` flow); only `gl-mrs` ships today, a `gh-prs` twin is the obvious next one.
45+
2946
## Bundled sources
3047

3148
| Source | Polls | Events |

presets/gitlab.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
"description": "MR review: branch, pipeline, approval, linked issue, diff stat, comments. :status = slim merge-state only",
1515
"syntax": "gl-mr:NUMBER_OR_BRANCH[:status]"
1616
},
17+
"gl-mrs": {
18+
"cmd": "{python} {path}gitlab/mrs.py {args}",
19+
"timeout": 60,
20+
"description": "MR triage board: list MRs (default author=@me), sorted failing-first. Per MR: pipeline status (failed→job name), approval state, age, diff size, watch-state, conflict/draft/threads flags + actionable footer. Filters (comma-sep): author/reviewer/assignee/label/milestone/state. Flags: nopipe, iids, failed. Enrichment calls run in parallel.",
21+
"syntax": "gl-mrs[:author=@me,reviewer=@me,state=opened,failed,nopipe,iids]",
22+
"enrich_workers": 8,
23+
"enrich_cap": 40,
24+
"per_page": 50
25+
},
1726
"gl-issue-create": {
1827
"cmd": "{python} {path}gitlab/issue_create.py {arg}",
1928
"timeout": 30,

0 commit comments

Comments
 (0)