@matthewfl/pi-gitlab
GitLab merge request and issue workflows for Pi: fetch, stage edits, review, publish, and watch for changes.
Package details
Install @matthewfl/pi-gitlab from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@matthewfl/pi-gitlab- Package
@matthewfl/pi-gitlab- Version
0.1.0- Published
- Nov 1, 2026
- Downloads
- 225/mo · 225/wk
- Author
- matthewfl
- License
- MIT
- Types
- extension
- Size
- 270.3 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-gitlab
A pi extension that lets the agent work with GitLab merge requests and issues — reading and replying to discussion threads, leaving code-review comments, updating descriptions or titles, resolving threads, and watching items for changes — while keeping the human in control with review-gated writes by default.
Why you might want it
- Review before posting by default. The agent stages everything it
wants to say as edits in one local file; when it's done, you get a single
review dialog with the whole batch. Approve once, or cancel and nothing
happens. Explicit bypass mode, disabling
confirmBeforePost, and CLIput --submitskip this dialog (see below). - Clear attribution. Every comment the agent posts carries a signature
banner (
_🤖 Automatically written by pi-gitlab…_), so other humans and your own later self can tell agent output apart from yours. - A trust model that matches reality. The file marks which comments came
from you (
from_operator) — those are instructions to the agent — versus everyone else's, which the agent may only consider (it will not take consequential action solely because a stranger asked). - Watches that don't whine. The agent can watch MRs/issues and gets notified only on meaningful events: new comments, resolve flips, branch head moves (CI progress is silenced until a terminal state).
Setup
Requires Node.js 22.19+, Pi, and git on your PATH (used for
three-way merges and diffs).
- Install the extension:Restart Pi (or run
pi install npm:@matthewfl/pi-gitlab/reloadin an existing session). To try it without saving an installation, usepi -e npm:@matthewfl/pi-gitlab. - Run
/gitlab-auth https://your-gitlab-hostinside pi. It walks you through creating a Personal Access Token with theapiscope (it shows the token page URL for both newer and older GitLab versions), verifies the token against the instance, and stores it with0600permissions in~/.pi/agent/gitlab/config/auth.json. That's all pi needs. - Tell your agent about an MR or issue — paste the URL in conversation. The extension figures out which it is from the URL.
What you'll see day to day
When the agent starts working on an MR or issue, a new file appears:
~/.pi/agent/gitlab/mr/<host>__<project>__<mr-NN>.gitlab.md
~/.pi/agent/gitlab/issue/<host>__<project>__<issue-NN>.gitlab.md
That file is the interface. It contains the item (title, state,
pipeline status, description) and every discussion thread, including where
comments anchor into code with a small fenced context (the commented line is
marked with ← commented line). Comments look like:
<comment author="@alice" id="771" from_operator="true">
ci test please
</comment>
When the agent wants to change something, it edits this file: new comments/
replies are added as visible="unpublished" blocks — think of it as
"staged", like a git index — while description edits change the
<description> body and renames change the root tag's title="…"
attribute. You
can open the file any time; you can even hand-edit it yourself. When the
agent runs the publish step, you get one confirmation dialog summarizing
the batch ("reply in thread …, new code comment url.ts:82 …, resolve thread
…"). Approve → everything posts in one go, with per-item ✓/✗ reporting back.
Anything that failed to post stays in the file with a <!-- put FAIL: … -->
note so it can be fixed and retried; it won't double-post.
If someone (you in the web UI, a colleague, a bot) changes the item on GitLab
while the agent works, the next fetch/watch merges the new state in using a
git three-way merge — staged agent edits are preserved; genuinely conflicting
edits get standard <<<<<<< markers that the agent is told to repair before
it may publish again.
Bot trigger phrases (unsigned triggers whitelist)
Some GitLab bots parse comment threads for trigger phrases — /merge,
/test, your CI's ci test, etc. A signature banner on those messages can
confuse them, so config.json can whitelist patterns: when a staged
comment matches, it posts exactly as written, with no signature header.
{
"bypassSignaturePatterns": ["^/merge$"],
"servers": {
"https://gitlab.example.com": {
"bypassSignaturePatterns": ["^ci test\\b"]
}
}
}
- Patterns are JavaScript regexes matched against the trimmed comment body
(the whole body, not per-line) when deciding to POST unsigned — so anchor
with
^…$if you mean exact commands (/merge|/testunanchored would ALSO match a staged comment that merely mentions those words). - Watch silencing, however, only quiets comments whose ENTIRE body matches a pattern: a colleague's or your own web comment like "check why the /test run failed" is prose, not a verbatim trigger, and will still ping. Pi's own unsigned posts are silenced primarily by their recorded note ids, so this narrowing loses nothing you care about.
bypassSignaturePatternsapplies to every instance; entries underservers.<baseUrl>.bypassSignaturePatternsadd patterns for just that instance (same base-url keys asauth.json). Both are merged.- The review dialog flags these with
[unsigned — whitelist match, no signature header]so you always see when the agent will post without attribution. - Posts made this way are still remembered as agent posts (their note ids are
recorded in sidecar state), so the file still marks them
via_pi="true"and watches stay quiet about them (see the bullet above — the pattern itself is only a fallback for unrecorded/foreign-machine posts).
Watches
Ask the agent to "watch" an MR or issue ("keep an eye on this"). It polls on
a configurable interval (default 120s, floor 30s) and messages you/the agent
only when something meaningful changes — e.g.
[gitlab watch] !1 agents (pi-gitlab extension): 1 new note (1 new thread).
with paths to the actual file and a .diff sidecar showing exactly what
changed. Watches resume automatically when a session reopens, and while any watch is active the pi footer shows a status segment naming them — gitlab: watching !1 agents (up to two labels, then +N more) — so when the agent sits idle it is visibly still on duty, not doing nothing. New pipelines
notify only on terminal states (success/failed/canceled), not progress.
Silence is attributable: gitlab_watch list reports each watch's health
(last tick age, notifications delivered, and every suppression with its
reason — own posts, system notes, whitelisted trigger bodies like ci test,
pipeline progress), and a failing watch (bad token, network) says so — in
the session and, after 3 consecutive failed ticks, via a message. The CLI
prints tick errors to stderr. So "why didn't the watch notify?" is one
list call, not an investigation.
Configuration reference (~/.pi/agent/gitlab/config/config.json)
Example config.json with every key set to its default value:
{
"confirmBeforePost": true,
"signatureHeader": "_🤖 Automatically written by pi-gitlab (AI agent). Review before acting._",
"botUsernames": [],
"watchIntervalSeconds": 120,
"bypassSignaturePatterns": [],
"servers": {}
}
All keys are optional; omitted keys use these defaults. servers is empty
by default; see the bot-trigger example above for per-server overrides.
| key | default | meaning |
|---|---|---|
confirmBeforePost |
true |
master safety switch. When true, submit shows the single review dialog before posting; when false, submit posts with no dialog — you are never asked. (The agent can always use submit_bypass_user_review, but is instructed to do so ONLY when you explicitly tell it to submit without your review.) |
signatureHeader |
_🤖 Automatically written by pi-gitlab (AI agent). Review before acting._ |
banner prepended to every posted comment (except whitelist matches). |
watchIntervalSeconds |
120 |
default poll interval for watches (minimum 30). |
botUsernames |
[] |
extra usernames to treat as bot accounts. |
bypassSignaturePatterns |
[] |
global unsigned-trigger whitelist regexes (see above). |
servers.<baseUrl>.bypassSignaturePatterns |
— | per-instance additions to the whitelist. |
Non-corrupt fields unknown to the extension are ignored, so you can version the file forward safely.
CLI for non-pi agents (bin/pi-gitlab.ts)
Other agent systems (plain shell scripts, another assistant, a cron job) can drive the same machinery without loading pi:
npm install --global --omit=peer @matthewfl/pi-gitlab
pi-gitlab login <base-url> # token via GITLAB_TOKEN env, --stdin, or a TTY prompt
pi-gitlab fetch <item-url>
pi-gitlab put [--dry-run|--submit] <item-url>
pi-gitlab watch <item-url> [--interval s]
pi-gitlab skill # print the agent behavior guide
The standalone CLI has no runtime npm dependencies; --omit=peer avoids
installing the Pi host packages it does not need. The npm package ships a
compiled JavaScript CLI, so no TypeScript runner is required. From a source
checkout, node bin/pi-gitlab.ts ... also works on Node.js 22.19+. Either way:
one behavior guide — the CLI reminders and pi-gitlab skill both
carry the same SKILL.md the pi skill loads, so etiquette/trust/staging
rules are maintained once.
- Same data, no re-auth. It reads and writes the identical
~/.pi/agent/gitlab/tree (auth.json 0600, config.json, item files, sidecars). Setup once with/gitlab-authin pi and the CLI reuses it (and vice versa). fetchprints a JSON{file, diff, serverChanged, conflicted};put --dry-runprints the planned actions and exits,--submitposts with no review dialog (there is no operator to ask — the batch is yours to audit via the preview);watchrefreshes the file and exits 0 on the first meaningful change printing the usual one-line notice.- Every command echoes short agent-reminders to stderr (trust rules,
staging semantics, banner note; silence with
--quiet/PI_GITLAB_QUIET=1) so stdout stays parseable. - One agent per item at a time. A per-item lock
(
~/.pi/agent/gitlab/.store/<item>/pi-gitlab.lock, holderpiorcli) is held by every mutating command. Two systems touching the same MR/ issue fail fast with named-holder detail (exit 3); locks left by dead processes are reclaimed, and one-shot locks older than 30 min are treated as crash residue. Ideally you run ONE agent per MR — the lock just refuses silently-overlapping work. - Exit codes: 0 ok · 1 error/validation · 2 put had per-item failures · 3 lock conflict · 64 usage.
What the agent sees (short version)
The agent gets three tools — gitlab_fetch(url) (fetch/refresh an item into
its file; first successful fetch unlocks the other two), gitlab_put(path, mode) (dry_run preview → submit with your approval or bypass when
confirmBeforePost=false), and gitlab_watch(url, action) — plus one
command (/gitlab-auth) and a bundled skill with workflow etiquette
(gitlab-mr-review). The full file grammar, trust hierarchy, and rules the
agent follows are in
skills/gitlab-mr-review/SKILL.md.
Development
Run these commands from extensions/pi-gitlab in the repository:
npm ci
npm run typecheck
npm test
npm run build
pi -e ./index.ts
npm run buildcompiles the standalone CLI intodist/and copies its behavior guide. Pi loads the TypeScript extension directly.npm run load-checkchecks extension loading and the auth dialog via a local Pi executable, using fake credentials and an isolated data directory.npm run package-checkpacks and installs the tarball in a temporary directory, checks the installed CLI and bundled guide, and runs the Pi load check against the installed extension.- Live drivers under
scripts/(auth setup, fetch/put/watch against a real instance) mirror the tool calls for testing without a model turn.
Publishing
From extensions/pi-gitlab, with npm credentials authorized for the
@matthewfl scope:
npm ci
npm run typecheck
npm test
npm run package-check
npm pack --dry-run # builds the CLI and lists public package contents
npm publish # publishConfig.access is public
The initial version is 0.1.0; increment package.json's version for each
subsequent release. Only runtime TypeScript modules, the compiled CLI, skills,
README, license, and package metadata are included; tests, live drivers, and
local configuration are excluded. prepublishOnly runs typechecking and tests,
and prepack builds the CLI automatically.
License
MIT.