Files
Gitea-Tools/docs/llm-workflow-runbooks.md
T

332 lines
14 KiB
Markdown

# LLM-Operated Gitea Workflow Runbooks
## Purpose
Runbooks for the common Gitea workflows an LLM performs through the `gitea-mcp`
package of the MCP Control Plane: creating issues, implementing them, opening
and reviewing pull requests, merging, and closing out — safely and
reproducibly.
These runbooks are **operational guidance only**. They add no tooling; the
behavior they rely on already exists (canonical runtime profiles, the
interactive setup menu, identity/eligibility checks, gated review/merge, and
audit logging). See [Related documents](#related-documents).
For cross-project use, copy the portable workflow skill at
[`../skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md).
It extracts the issue-first, isolated-worktree, no-self-review, profile-safety,
merge-cleanup, fail-closed, and recovery rules into a reusable package that can
be adapted to other repositories.
## Principle: the profile is the role, not the LLM
```text
The LLM is not the role.
The MCP execution profile used for the task is the role.
```
An LLM session is never permanently an "author," "reviewer," or "merger." Any
session may perform any of these roles — but only while operating through a
**task-appropriate profile** whose authenticated Gitea identity and allowed
operations fit the task. A task selects a profile; a profile is not assigned to
a model. See [`gitea-execution-profiles.md`](gitea-execution-profiles.md).
Example role-scoped instructions:
```text
Use an author profile to implement issue #N and open a PR.
Use any eligible reviewer profile to review PR #N.
Use any eligible merger profile to merge PR #N if checks pass.
```
## Prerequisites: canonical config + thin launchers
Runtime profiles live in **one canonical JSON file**, referenced by every LLM
launcher. No client config contains raw credentials.
### Canonical config file
Selected by two environment variables:
- `GITEA_MCP_CONFIG` — path to the canonical file (e.g.
`~/.config/gitea-tools/profiles.json`).
- `GITEA_MCP_PROFILE` — the named profile to activate.
Shape (see [`../gitea-mcp.example.json`](../gitea-mcp.example.json)):
```json
{
"version": 1,
"profiles": {
"prgs-reviewer": {
"base_url": "https://gitea.example.invalid",
"username": "<reviewer-username>",
"auth": { "type": "keychain", "id": "prgs-reviewer-token" },
"default_owner": "<owner>",
"execution_profile": "gitea-reviewer"
},
"prgs-author": {
"base_url": "https://gitea.example.invalid",
"username": "<author-username>",
"auth": { "type": "env", "name": "GITEA_TOKEN_PRGS_AUTHOR" },
"default_owner": "<owner>",
"execution_profile": "gitea-author"
}
}
}
```
- `version` — canonical schema version (currently `1`).
- `profiles` — map of profile name → profile.
- `auth` — a **reference**, never an inline secret:
- **keychain**: `{ "type": "keychain", "id": "<service-id>" }` — the token is
read from the macOS keychain on demand.
- **env**: `{ "type": "env", "name": "<ENV_VAR_NAME>" }` — the token is read
from that environment variable.
Inline `token`/`password` keys are rejected. Token *values* are never stored in,
returned by, or logged from profile metadata. Precedence: explicit process env
vars override JSON profile values; the JSON profile fills only what the
environment leaves unset. With `GITEA_MCP_CONFIG` unset, behavior is exactly the
legacy environment-only mode.
### Thin launcher pattern
An LLM MCP launcher (Claude / Gemini / Codex) contains **only** command, args,
and the two `GITEA_MCP_*` variables — never a token or password:
```json
"gitea-tools": {
"command": "/path/to/Gitea-Tools/venv/bin/python3",
"args": ["/path/to/Gitea-Tools/mcp_server.py"],
"env": {
"GITEA_MCP_CONFIG": "/path/to/.config/gitea-tools/profiles.json",
"GITEA_MCP_PROFILE": "prgs-reviewer"
}
}
```
Run the same server as several launcher entries (e.g. `-author`, `-reviewer`,
`-merger`), each pointing at a different `GITEA_MCP_PROFILE`.
## Setup runbook — interactive menu
Create and manage profiles without hand-editing JSON:
```bash
./scripts/gitea-config-menu
```
Menu options: list / add / edit / remove profiles · validate config · test
profile authentication · show authenticated user · generate launcher snippets
(Claude/Gemini/Codex) · check reviewer eligibility for a PR.
In a real terminal the menu takes a **single keypress** (no Enter), **Enter**
quits the main menu and cancels/back-outs of any submenu, and you pick a profile
from a **numbered list** instead of typing its name. Non-interactive runs
(pipes/tests) fall back to line input and never block.
**Create an author + a reviewer profile:**
1. `add profile` → name `prgs-author`, base URL, username, default owner/repo,
execution profile `gitea-author`, auth type `keychain` or `env`.
- keychain: store the token now (hidden prompt); it goes to the keychain
under an id like `prgs-author-token` — never into the JSON.
- env: record a var name like `GITEA_TOKEN_PRGS_AUTHOR`; set that variable
yourself in the environment.
2. `add profile` again → name `prgs-reviewer`, execution profile
`gitea-reviewer`. Existing profiles are preserved.
3. `validate config` → confirm no problems.
4. `generate launcher snippets` → paste the printed snippet into each LLM
client's MCP config (it contains no secret).
5. `test profile authentication` → prints the resolved Gitea username (the only
time an API call is made, and only on request).
6. `check reviewer eligibility for a PR` → enter a PR number; prints the
authenticated user, the PR author, and `ELIGIBLE` / `INELIGIBLE`. Read-only —
it never approves or merges.
## Migration runbook — away from duplicated credential blocks
Old setups duplicated `GITEA_USER_*`, `GITEA_PASS_*`, and `GITEA_SITE_*` across
every LLM's `mcp_config.json` — duplicating profiles and exposing secrets.
1. For each instance/role, create one canonical profile (menu → `add profile`),
storing the secret in the keychain or an env var and referencing it by
id/name only.
2. `validate config`, then `test profile authentication` for each profile.
3. Replace each LLM's server block with the thin launcher (command + args +
`GITEA_MCP_CONFIG` + `GITEA_MCP_PROFILE`).
4. Delete the `GITEA_USER_*` / `GITEA_PASS_*` / `GITEA_SITE_*` blocks from every
LLM config.
5. Rotate any token that previously sat in a client config.
Legacy environment-only setups keep working unchanged until migrated.
## Workflow runbooks
Each runbook names the **profile role** it runs under, the steps, and a safe
prompt. Confirm the active profile first (`gitea_get_profile` / `gitea_whoami`).
## Branch worktree isolation
All LLM implementation and review work happens in an isolated branch worktree
under `branches/`. The main repository checkout is an orchestration checkout:
use it for status checks, issue creation/claiming, and creating worktrees, but
do not edit tracked repository files there.
Branch folders are ignored by git via `branches/`, so dirty work in one issue
does not block starting an unrelated issue in a separate branch folder. No LLM
may edit another issue's branch folder unless explicitly assigned to that issue.
No LLM may clean another issue's branch folder unless the PR is merged or closed
and cleanup is explicitly part of the task.
Implementation work and review work must use separate branch folders. For
example, an implementation branch might live under
`branches/fix-issue-123-example`, while a review branch for the resulting PR
uses its own folder.
Issue creation and claiming may happen from the orchestration checkout:
1. Create or identify the tracking issue.
2. Claim it with `status:in-progress`.
3. Create the issue branch worktree.
4. `cd` into the branch worktree and perform all file edits there.
Preferred helper:
```bash
scripts/worktree-start fix/issue-123-example
cd branches/fix-issue-123-example
```
Because `venv/` is ignored and not copied into new worktrees, run checks with a
known Python interpreter. Either create a venv inside the branch folder, or use
the orchestration checkout's venv by explicit path.
Equivalent manual commands:
```bash
git fetch prgs --prune
git worktree add -b fix/issue-123-example branches/fix-issue-123-example prgs/master
cd branches/fix-issue-123-example
```
For review work, create a separate **detached** review worktree instead of
reusing the author's implementation folder:
```bash
scripts/worktree-review fix/issue-123-example # → branches/review-fix-issue-123-example
```
Cleanup is explicit and only after merge or close. Use the helper (it fetches/
prunes first, refuses to remove a dirty worktree, and only safe-deletes a merged
branch), or the equivalent manual commands:
```bash
scripts/worktree-clean --delete-branch fix/issue-123-example
# equivalent manual commands:
cd <main-repo>
git fetch prgs --prune
git worktree remove branches/fix-issue-123-example
git branch -d fix/issue-123-example
```
All three helpers accept `--dry-run` to print the exact commands/paths without
touching anything.
### Create an issue / child issues
- **Profile:** issue-manager or author (any profile allowed to create issues).
- **Steps:** create the parent/roadmap issue; create child issues; apply the
minimal label set; link children to the parent.
- **Prompt:** `Using the issue-manager profile, create issue "<title>" with body
<body>, then create child issues for <list> and link them to the parent.`
### Implement an issue and open a PR
- **Profile:** author.
- **Steps:** claim the issue (`status:in-progress`); create an isolated branch
worktree from latest `master` under `branches/` (`feat/issue-<n>-...` /
`fix/...` / `docs/...`); `cd` into that worktree; implement narrowly; add or
update tests if behavior changes; run the full suite; commit with an
issue-linked message; open a PR to `master`. **Do not** review or merge your
own PR.
- **Prompt:** `Use an author profile to implement issue #N and open a PR to
master. Do not self-review or self-merge.`
### Review a PR / request changes / approve
- **Profile:** reviewer (must be allowed to review/approve/request_changes, and
must **not** be the PR author).
- **Steps:** confirm identity + eligibility (menu eligibility check or
`gitea_check_pr_eligibility`); read the diff; confirm scope matches the linked
issue; post the review (`comment` / `request_changes` / `approve`) via the
gated review tool. Pin the reviewed head SHA where supported.
- **Prompt:** `Use any eligible reviewer profile to review PR #N. Approve only
if scope matches issue #M and checks pass; otherwise request changes.`
### Merge a PR
- **Profile:** merger (allowed to merge; must **not** be the PR author).
- **Steps:** confirm eligibility; require explicit confirmation
(`MERGE PR <n>`); optionally pin head SHA / changed-file set; merge only when
Gitea reports the PR mergeable (branch-protection checks satisfied). No force,
no ignore-checks.
- **Prompt:** `Use any eligible merger profile to merge PR #N if checks pass and
it is mergeable. Confirm with "MERGE PR N". Do not force-merge.`
### Close the issue after merge
- **Profile:** issue-manager or merger.
- **Steps:** verify remote `master` actually contains the merge; close the
issue (or rely on a `Closes #N` keyword); release `status:in-progress`;
clean up merged branches.
- **Prompt:** `After confirming master contains the merge of PR #N, close issue
#M and delete the merged branch.`
### Stop on blocker
- **Any profile.** If a required gate cannot be satisfied — identity
unverifiable, ineligible profile, self-authored PR, moved head, unexpected
files, detected secret, or any production/deploy behavior — **stop, report the
blocker, and take no mutating action.** Fail closed; never work around a gate.
## Fail-closed behavior
Before any mutating action the workflow verifies identity, active profile,
requested operation, target repo, target issue/PR, and (for review/merge) the PR
author. If any check cannot be satisfied, it **fails closed** — no mutation:
| Condition | Result |
|-----------|--------|
| Authenticated identity cannot be verified | blocked |
| Unknown / unconfigured profile | blocked |
| Profile not allowed the requested operation | blocked |
| Authenticated user **is** the PR author (approve/merge) | blocked (no self-review/-merge) |
| PR head SHA changed since review | blocked |
| PR's changed-file set differs from expected | blocked |
| PR not open, or Gitea reports it not mergeable | blocked |
| Secret / token detected in content | blocked |
| Production / deploy / Ops behavior requested | blocked (out of scope for gitea-mcp) |
All mutating attempts — allowed, blocked, failed, or succeeded — are audit-logged
with the profile and authenticated user when `GITEA_AUDIT_LOG` is set (see
[`safety-model.md`](safety-model.md)).
## Safety notes
- Never place raw tokens or passwords in any LLM MCP config; reference secrets
by keychain id or env var name only.
- Never self-review or self-merge; never bypass Gitea branch protections.
- No Jenkins / Ops / deploy / production behavior in `gitea-mcp`.
## Related documents
- [`../skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md) — portable cross-project LLM workflow skill.
- [`gitea-execution-profiles.md`](gitea-execution-profiles.md) — the profile model.
- [`safety-model.md`](safety-model.md) — trust boundaries and audit logging.
- [`tool-boundaries.md`](tool-boundaries.md) — per-tool allowed operations.
- [`credential-isolation.md`](credential-isolation.md) — credential handling.
- [`release-workflows.md`](release-workflows.md) — release/merge workflow.
- [`../README.md`](../README.md) — canonical config, thin launchers, the menu.