# 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). ## 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": "", "auth": { "type": "keychain", "id": "prgs-reviewer-token" }, "default_owner": "", "execution_profile": "gitea-reviewer" }, "prgs-author": { "base_url": "https://gitea.example.invalid", "username": "", "auth": { "type": "env", "name": "GITEA_TOKEN_PRGS_AUTHOR" }, "default_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": "" }` — the token is read from the macOS keychain on demand. - **env**: `{ "type": "env", "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`). ### 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 "" 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`); branch from latest `master` (`feat/issue-<n>-...` / `fix/...` / `docs/...`); implement narrowly; add/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 - [`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.