Keep reviewer-vs-merger role boundary wording and master's #533 pre-merge baseline proof section in review-pr template.
998 lines
49 KiB
Markdown
998 lines
49 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.
|
||
|
||
> For the **project-agnostic** version of these operating rules (issue-first,
|
||
> isolated worktrees, no self-review/merge, profile safety, cleanup, fail-closed)
|
||
> that can be copied into any repository, see the reusable skill
|
||
> [`skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md)
|
||
> and its `templates/`. This runbook is the Gitea-specific application of it.
|
||
|
||
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).
|
||
|
||
> **New session? Call the guide tools first (#128 / #129).** Before using any other
|
||
> Gitea MCP tool in a fresh session, call `mcp_get_control_plane_guide`
|
||
> (read-only): it reports the active profile, authenticated identity,
|
||
> allowed/forbidden operations, profile-aware do/don't guidance, and the
|
||
> non-negotiable rules (hard stops, fail-closed behavior, head-SHA pinning,
|
||
> merge confirmation, redaction, author/reviewer separation, profile
|
||
> switching). Also call `gitea_get_runtime_context` and `mcp_list_project_skills`
|
||
> to discover the available project workflows and `mcp_get_skill_guide(<name>)`
|
||
> for step-by-step instructions. This replaces long pasted operator prompts for
|
||
> the standard rules; operator prompts still control task-specific scope.
|
||
> See issue #129 for the skill registry design.
|
||
|
||
Jenkins and GlitchTip workflows use separate MCP servers, not this Gitea MCP
|
||
runtime. Register them as `jenkins-mcp` and `glitchtip-mcp`, reconnect or
|
||
reload the client, and verify visible tools before claiming either integration
|
||
is usable. See [`mcp-client-registration.md`](mcp-client-registration.md).
|
||
|
||
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.
|
||
```
|
||
|
||
### Attribution: `LLM-Agent-SHA` (metadata only)
|
||
|
||
Sessions may attribute their work with an opaque `LLM-Agent-SHA`
|
||
(`llm-<12 lowercase hex>`, e.g. `llm-8f3a9c2d6b41`) in PR-body and
|
||
review-handoff metadata blocks — see
|
||
[`llm-agent-sha.md`](llm-agent-sha.md) for the full convention. It is
|
||
**attribution only**: eligibility is decided solely by the authenticated
|
||
Gitea user and the profile's allowed operations. Two sessions with different
|
||
SHAs under the same Gitea user are the same actor — a different SHA never
|
||
permits self-review or self-merge. Keep the SHA out of branch and worktree
|
||
names.
|
||
|
||
## 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"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Dual-profile MCP launcher pattern (Recommended)
|
||
|
||
To avoid the bottleneck of relaunching/restarting the MCP server to switch between author and reviewer roles, the client should register **both** profiles concurrently as separate server instances in the client's MCP configuration:
|
||
|
||
```json
|
||
"gitea-author": {
|
||
"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-author"
|
||
}
|
||
},
|
||
"gitea-reviewer": {
|
||
"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"
|
||
}
|
||
}
|
||
```
|
||
|
||
* **Tool Namespaces:** Tool calls become distinct and identity-scoped in the client UI:
|
||
* `mcp__gitea-author__*` (for creating issues, pushing branches, creating PRs)
|
||
* `mcp__gitea-reviewer__*` (for reviewing PRs, approving, requesting changes, merging)
|
||
* **Trust Model:** Separate tokens remain separate in the keychain/environment. Each instance operates under its own `GITEA_MCP_PROFILE` and enforces its own `allowed_operations`. A runtime `whoami` identity check is still performed independently, and self-review/self-merge checks remain strictly mandatory. The dual-server pattern is a operational convenience and never a security bypass.
|
||
* **Reviewer-Identity PR Creation Deadlock:** Reviewer/merge identities must not create PRs or push branches. Doing so makes the reviewer identity the PR author in Gitea, blocking subsequent independent review and causing a review deadlock. Normally, PRs must be created by the author/work identity (`gitea-author`), leaving the reviewer identity (`gitea-reviewer`) clean and available for independent review and merge.
|
||
* **Reconciler namespace (#310):** Register a third static instance for
|
||
already-landed PR cleanup when review queues block on open PRs whose heads
|
||
already landed on `master`:
|
||
|
||
```json
|
||
"gitea-reconciler": {
|
||
"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-reconciler"
|
||
}
|
||
}
|
||
```
|
||
|
||
The reconciler profile grants `gitea.pr.close` only for
|
||
`gitea_reconcile_already_landed_pr` after ancestry proof — not for normal
|
||
review or author workflows.
|
||
|
||
* **Fallback:** If the dual-profile MCP launcher pattern is not supported or configured in the client, the LLM must relaunch or restart the client/MCP with the correct profile environment variable before claiming or working on any tasks.
|
||
|
||
## 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`).
|
||
|
||
## Work Selection Rule for LLMs
|
||
|
||
Before starting any issue or PR work, acquire or verify a work lease. Do not
|
||
begin coding, reviewing, fixing, branching, committing, pushing, commenting,
|
||
or creating a PR until you prove the target is not already being worked.
|
||
|
||
Required checks:
|
||
|
||
1. List open PRs.
|
||
2. Search for PRs linked to the target issue.
|
||
3. Search local and remote branches for the issue number.
|
||
4. Search registered worktrees for the issue branch.
|
||
5. Check dirty worktrees.
|
||
6. Check active leases or recent handoffs.
|
||
7. Check whether the issue was already completed by a merged PR.
|
||
|
||
If another active LLM/session owns the lease, stop. Allowed responses:
|
||
continue as the lease owner; review the existing PR if reviewer capability
|
||
allows; produce a handoff; request takeover after lease expiry; stop with
|
||
"work already claimed."
|
||
|
||
Never create a parallel branch or PR for the same issue unless the old branch
|
||
is proven abandoned and the takeover is recorded.
|
||
|
||
Gitea-Tools lease gates: `gitea_lock_issue` (fail-closed before author
|
||
mutations), `status:in-progress`, and claim comments. `gitea_lock_issue`
|
||
records an `author_issue_work` lease in a keyed lock file under
|
||
`GITEA_ISSUE_LOCK_DIR` (default `~/.cache/gitea-tools/issue-locks`), one file
|
||
per `remote` + `org` + `repo` + `issue_number`. The current MCP session binds
|
||
its active lock through a per-process pointer so concurrent repos/issues never
|
||
share one overwrite-prone slot (#443).
|
||
|
||
Each lock payload includes issue number, optional PR number, branch, worktree
|
||
path, claimant identity/profile, created timestamp, expiry timestamp, and last
|
||
heartbeat timestamp. An active same-issue/same-operation lease blocks duplicate
|
||
work. An expired lease still blocks takeover until a recovery review records why
|
||
the prior work is abandoned, completed, or unsafe to continue.
|
||
|
||
**Stacked PRs (#484).** By default the lock worktree must be base-equivalent to
|
||
`master`/`main`/`dev` — ordinary work is unchanged. A *stacked* PR (deliberately
|
||
based on another unmerged PR's branch) is an explicit, opt-in path: pass
|
||
`stacked_base_branch` **and** `stacked_base_pr` to `gitea_lock_issue`. The lock
|
||
fails closed unless that branch is owned by a live **open** PR whose number
|
||
matches `stacked_base_pr`, so arbitrary or stale branches cannot be used as
|
||
bases. When approved, the lock payload records
|
||
`approved_stacked_base = {branch, pr_number, verified_open}` and the worktree may
|
||
be base-equivalent to that branch instead of master. `gitea_create_pr` then
|
||
allows `base = <that branch>` only when it matches the recorded approval, the
|
||
dependency PR is **still open**, and the PR body documents the stack:
|
||
|
||
- `Stacked on PR #<X> / issue #<Y>`
|
||
- `Base branch: <feature-branch>`
|
||
- `Head branch: <this-issue-branch>`
|
||
- `Do not merge before PR #<X>` (merge ordering)
|
||
- retarget/rebase to `master` after the dependency lands, if required
|
||
|
||
Stacked support never bypasses the issue lock — the base is recorded *on* the
|
||
lock and re-verified at PR time. A merged/closed dependency base fails closed;
|
||
retarget onto `master` or re-lock against a live base.
|
||
|
||
**Do not manually seed `/tmp/gitea_issue_lock.json` or any lock file as a normal
|
||
recovery path.** That global slot is deprecated and can clobber unrelated live
|
||
leases (#438). After an MCP restart, call `gitea_lock_issue` again — own-branch
|
||
adoption rebinds the session when the issue's exact branch already exists (#442).
|
||
`gitea_create_pr` resolves the durable keyed lock by session pointer or by
|
||
matching `head` branch without unsafe manual seeding.
|
||
|
||
**Issue-lock recovery (#447):** Do not manually seed, restore, or delete
|
||
`/tmp/gitea_issue_lock.json` as a normal recovery path. That file is global
|
||
shared state and manual writes can clobber another session's live lease. Use
|
||
`sanctioned recovery` instead:
|
||
|
||
1. `gitea_lock_issue` on a clean `branches/` worktree (normal path).
|
||
2. Own-branch adoption via #442 when the issue's exact branch is already pushed.
|
||
3. Operator override only when explicitly authorized — record
|
||
`External-state mutations` and `operator override proof` in the final report.
|
||
|
||
**Adoption proof in the live lock response (#477):** when `gitea_lock_issue`
|
||
adopts an existing own branch, the response carries an `adoption` block with
|
||
citable fields — `adoption_decision` (`ADOPT`), `adopted` (`true`),
|
||
`adopted_branch`, `adopted_branch_head`, `matcher_summary` (boundary-safe reason
|
||
the branch qualified), `competing_branch_check`, and `safe_next_action`. A normal
|
||
lock instead returns an `adoption_check` block with `adoption_decision`
|
||
(`NO_MATCH`) and `adopted: false`, so a non-adoption response can never be misread
|
||
as claiming adoption. Recovery reports should quote the live lock response
|
||
`adoption`/`adoption_check` block directly instead of inferring adoption from
|
||
separate offline checks.
|
||
|
||
`gitea_create_pr` rejects lock files that lack sanctioned `lock_provenance`
|
||
metadata. Final-report validation blocks handoffs that hide lock read/write/delete
|
||
under `External-state mutations: none` or mix author PR creation with reviewer
|
||
approval in one run. See also #438 (global lock redesign).
|
||
|
||
Remote branches matching the issue number are also treated as active work unless
|
||
the recovery review proves the branch is abandoned or superseded. Never delete
|
||
or clean up a branch when it has an active lease, dirty worktree, open PR, or is
|
||
the only copy of unmerged work.
|
||
|
||
Full portable wording:
|
||
[`skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md).
|
||
|
||
## Global LLM Worktree Rule
|
||
|
||
The main project checkout is a stable control checkout. It must stay on the
|
||
configured stable branch: `master`, `main`, or `dev`.
|
||
|
||
All LLM task work must happen inside the project's `branches/` directory.
|
||
|
||
Before any mutation, prove:
|
||
|
||
1. current project root
|
||
2. current working directory
|
||
3. current branch
|
||
4. stable branch for the main checkout
|
||
5. session-owned worktree path under `branches/`
|
||
|
||
If `cwd` is not inside `branches/`, stop. Do not edit, create, delete, format,
|
||
test-write, commit, merge, rebase, checkout task branches, resolve conflicts,
|
||
or run cleanup.
|
||
|
||
There are no exceptions for small fixes, docs, tests, cleanup, PR review fixes,
|
||
conflict resolution, or emergencies.
|
||
|
||
The main checkout may only be used for read-only inspection, fetching,
|
||
stable-branch update after merged PRs, creating `branches/` worktrees, or
|
||
explicit control-checkout repair.
|
||
|
||
Portable wording: [`skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md).
|
||
|
||
### Root checkout guard (#475)
|
||
|
||
The MCP server enforces a fail-closed **root checkout guard** before author,
|
||
reviewer, and merger mutations when the active workspace is not an isolated
|
||
`branches/...` worktree (reconciler close paths remain exempt per #468).
|
||
|
||
The guard blocks when the **control checkout** (repository root) is:
|
||
|
||
- on a non-stable branch (`master` / `main` / `dev` expected),
|
||
- detached HEAD,
|
||
- dirty (tracked edits),
|
||
- or its `HEAD` does not match `prgs/master` when that ref is available.
|
||
|
||
**Remediation (never auto-reset or stash):**
|
||
|
||
> Root checkout is not on master. Preserve state, switch root back to master,
|
||
> and use `scripts/worktree-review` or the sanctioned issue worktree flow.
|
||
|
||
**Recovery after root hijack:**
|
||
|
||
1. Preserve any in-progress edits (copy paths, note branch name, or commit on a
|
||
rescue branch from a `branches/...` worktree).
|
||
2. From the repository root: `git checkout master` (or `main` / `dev` per repo
|
||
policy) and `git fetch prgs && git merge --ff-only prgs/master` when safe.
|
||
3. Confirm `git status` is clean and `git branch --show-current` is `master`.
|
||
4. Resume work only inside `branches/issue-<n>-<slug>` via `gitea_lock_issue` /
|
||
`git worktree add`.
|
||
|
||
`branches/...` directories are disposable role worktrees; the root checkout is
|
||
the stable orchestration surface only.
|
||
|
||
## Bootstrap Review Path for self-hosted MCP fixes (#557)
|
||
|
||
When a PR fixes Gitea-Tools MCP workflow code that the **live daemon** still
|
||
runs from broken `master`, canonical review can deadlock on itself.
|
||
|
||
Do **not** improvise raw API, direct imports, root edits, or gate bypasses.
|
||
|
||
Use the narrow controller-authorized path documented in:
|
||
|
||
- [`docs/bootstrap-review-path.md`](bootstrap-review-path.md)
|
||
|
||
Hard rules:
|
||
|
||
- No merge without a durable `BOOTSTRAP REVIEW AUTHORIZATION (#557)` record on
|
||
the PR or linked issue.
|
||
- Validation, duplicate-PR, mutation-ledger, and contamination audits remain
|
||
mandatory.
|
||
- Root checkout stays read-only orchestration only; verification runs in
|
||
`branches/` worktrees.
|
||
- After land: restart MCP daemons and re-verify with canonical tools only.
|
||
- This never weakens normal reviewer/merger gates for ordinary PRs.
|
||
|
||
## Shell Spawn Hard-Stop Rule
|
||
|
||
Symptom: a shell tool call returns `exit_code: -1` with empty stdout/stderr.
|
||
That is an executor spawn failure, not a command failure — the command never
|
||
ran, and retrying the identical call cannot succeed.
|
||
|
||
Required behavior (fail closed, issue #258):
|
||
|
||
1. **Probe once.** On the first spawn failure, run one trivial probe
|
||
(`echo ok` or `pwd`). If the probe also returns `exit_code: -1`, mark
|
||
shell unavailable for the session.
|
||
2. **Hard-stop at two.** After two consecutive spawn failures, stop all
|
||
further shell tool use for the session; never retry the same failing
|
||
spawn. A hundred retries produce a hundred identical failures (session
|
||
`019f382e`: 100+ tool calls stalled on a trivial encode-and-commit task).
|
||
3. **Emit a recovery report.** The report must direct the operator to:
|
||
- restart the session,
|
||
- kill hung background terminals (a hung test runner holding the
|
||
executor is a known contributor),
|
||
- prefer MCP-native paths for remaining mutations (for example
|
||
`gitea_commit_files` under `gitea.repo.commit`) instead of shell.
|
||
4. **No improvised fallbacks.** Shell unavailability never authorizes
|
||
WebFetch/browser/manual-encoding workarounds (see #260). No shell means
|
||
stop-and-report.
|
||
|
||
Doc-contract tests: `tests/test_shell_spawn_hard_stop_docs.py`.
|
||
|
||
## Subagent Tool-Budget Guardrails
|
||
|
||
General-purpose subagents tasked with **deterministic MCP work** (for example a
|
||
single `gitea_commit_files` call) have expanded to 100–122 tool calls,
|
||
WebFetch/Playwright fallbacks, and throwaway helper-script generation instead of
|
||
calling the native MCP tool once (observed during #152 closure, issue #259).
|
||
|
||
**Default budgets** (fail closed when exceeded):
|
||
|
||
| Task class | Max tool calls | Max wall time |
|
||
|------------|----------------|---------------|
|
||
| Single-step MCP mutation (`commit_files`, `create_pr`, `lock_issue`) | 15 | 5 minutes |
|
||
| Review / merge queue inspection | 40 | 15 minutes |
|
||
| Exploration / codebase search (non-mutating) | 60 | 20 minutes |
|
||
|
||
**Required behavior:**
|
||
|
||
1. **Main session first.** When the active author profile allows
|
||
`gitea.repo.commit` and `gitea_commit_files` is visible, the main session
|
||
must call it directly — do not delegate commit authority to a subagent
|
||
(see #260).
|
||
2. **Native MCP before fallback.** After a shell spawn failure (#258), attempt
|
||
the native MCP tool once before any alternate path. Shell unavailability
|
||
never authorizes WebFetch, Playwright, or manual base64 encoding.
|
||
3. **No retry spirals.** Never resume a failed subagent into a larger retry
|
||
loop or spawn a second subagent for the same deterministic step. Stop and
|
||
emit a recovery report instead.
|
||
4. **Forbidden detours** when `gitea_commit_files` is available: WebFetch,
|
||
Playwright/browser automation, manual LLM-generated base64, and ad-hoc
|
||
`_encode_*` / `_emit_*` helper scripts left in the repo.
|
||
|
||
Doc-contract tests: `tests/test_subagent_tool_budget_docs.py`.
|
||
|
||
## 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.
|
||
|
||
**Issue → branch → worktree → PR → cleanup.** Every implementation branch is
|
||
tied to an issue number so the work is traceable end to end:
|
||
|
||
| Stage | Form |
|
||
|-------|------|
|
||
| Issue | `#123` (claimed with `status:in-progress`) |
|
||
| Branch | `(fix\|feat\|docs\|chore)/issue-123-<slug>` (review: `review/pr-456-<slug>`) |
|
||
| Worktree | `branches/fix-issue-123-<slug>` (slashes → hyphens) |
|
||
| PR | body says `Closes #123` or `Fixes #123` (closes issue); `Implements #123` or `Refs #123` (does NOT close) |
|
||
| Cleanup | remove remote+local branch + worktree folder; drop `status:in-progress` |
|
||
|
||
`scripts/worktree-start` **rejects** implementation branches that are not
|
||
issue-linked (use `--allow-unlinked` only for genuine exceptions). When claiming,
|
||
post a comment like
|
||
`Claimed. Branch: fix/issue-123-<slug>. Worktree: branches/fix-issue-123-<slug>.`
|
||
Gitea has no native issue→branch API field (only a PR's head branch), so this
|
||
linkage is enforced by branch name + claim comment + PR body + cleanup.
|
||
|
||
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.
|
||
|
||
## Agent temp artifact cleanup (#261)
|
||
|
||
Failed or aborted MCP commit attempts sometimes leave throwaway helper scripts in
|
||
the **repository root**. These are not part of any issue scope and pollute
|
||
`git status`, which can break `gitea_lock_issue` and preflight checks.
|
||
|
||
**Patterns (repo root only, untracked):**
|
||
|
||
- `_encode_*.py` — base64 payload encoders
|
||
- `_emit_*.py` — commit payload emitters
|
||
- `_inline_*.py` — inline encoding helpers
|
||
|
||
**Required cleanup (after MCP commit completes or aborts):**
|
||
|
||
1. Delete any matching files at the repo root (`rm ./_encode_*.py` etc.).
|
||
2. Confirm `git status` is clean on the orchestration checkout before
|
||
`gitea_lock_issue`.
|
||
3. Prefer native `gitea_commit_files` / gated commit paths — do not leave shell
|
||
encoding fallbacks behind.
|
||
|
||
Root-level matches are listed in `.gitignore` so they never get committed.
|
||
`gitea_get_runtime_context` and `gitea_lock_issue` surface **warnings** (not
|
||
hard blocks) when these artifacts are still present.
|
||
|
||
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.
|
||
|
||
### MCP-native commit path (#260)
|
||
|
||
When the active author profile allows **`gitea.repo.commit`** and
|
||
**`gitea_commit_files`** is visible in the client, that is the **only** approved
|
||
path for committing files to the tracked repository. Do not improvise alternate
|
||
encoding or transport when MCP commit is available.
|
||
|
||
**Required before commit:**
|
||
|
||
1. Call `gitea_resolve_task_capability` for `commit_files` or
|
||
`gitea_commit_files` and confirm `allowed_in_current_session` is true.
|
||
2. Use `gitea_commit_files` with file payloads prepared in the author worktree.
|
||
3. Stage only issue-scoped paths; never commit throwaway `_encode_*` /
|
||
`_emit_*` / `_inline_*` helpers.
|
||
|
||
**Explicitly forbidden workarounds** when MCP commit is reachable:
|
||
|
||
- `WebFetch` / HTTP calls to external decode sites (for example httpbin base64
|
||
endpoints)
|
||
- Playwright or other browser automation to bypass MCP
|
||
- Manual LLM-generated base64 pasted into ad-hoc scripts
|
||
- Delegating commit authority to a subagent while the main session has
|
||
`gitea.repo.commit` on an author profile
|
||
|
||
**If shell encoding is unavailable** (spawn failure, hung terminal) **and** MCP
|
||
commit cannot run: **stop** with a recovery report. Mention restarting the
|
||
session, clearing hung background terminals, switching to MCP-native commit, and
|
||
the agent temp artifact cleanup checklist. Do **not** retry shell encoding in a
|
||
loop and do **not** substitute WebFetch/Playwright/manual base64.
|
||
|
||
### 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. Include an `LLM Handoff Metadata` block (with `LLM-Agent-SHA`) in
|
||
the PR body — see [`llm-agent-sha.md`](llm-agent-sha.md).
|
||
- **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. Include a
|
||
`Review Metadata` block (with your own `LLM-Agent-SHA`) in the review —
|
||
and remember: a different `LLM-Agent-SHA` does **not** make you a different
|
||
actor; only a different authenticated Gitea user does
|
||
([`llm-agent-sha.md`](llm-agent-sha.md)).
|
||
- **Prompt:** `Use any eligible reviewer profile to review PR #N. Approve only
|
||
if scope matches issue #M and checks pass; otherwise request changes.`
|
||
|
||
**Live queue reconciliation (mandatory before any review/merge decision):**
|
||
|
||
- Reconcile live state first. Do **not** assume prior handoffs, cached tool
|
||
output, or chat summaries are current.
|
||
- Steps (in order):
|
||
1. Call `gitea_list_prs` (open state) with explicit remote/org/repo.
|
||
2. Immediately `gitea_view_pr <number>` for the candidate; capture head SHA,
|
||
state, mergeable, updated_at, merged_at/merge_commit_sha if present.
|
||
3. Verify against any prior report: state, head SHA, updated timestamp,
|
||
linked issue state (use `gitea_view_issue` + `gitea_list_issues`).
|
||
4. `git fetch <remote> --prune && git checkout master && git pull <remote> master --ff-only`
|
||
5. If conflict/staleness detected (prior said "merged" but live open; head or
|
||
updated_at differs from claimed; merge commit missing on master), report
|
||
the inconsistency explicitly and **STOP** before review or merge.
|
||
- After a successful merge: re-run list_prs + view_pr on the PR, confirm
|
||
master advanced, and include the live post-merge verification in the handoff.
|
||
- Treat any ambiguous queue state as a blocker until a fresh, consistent live
|
||
picture is obtained.
|
||
|
||
### Merge a PR
|
||
|
||
- **Profile:** merger (allowed to merge; must **not** be the PR author).
|
||
- **Steps:**
|
||
- Merger workflow starts only after formal approval at current head. Reviewer workflow ends with review decision and separate merger handoff.
|
||
- 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. Verify that remote master contains the merge commit or the expected squashed changes (do not assume a "closed" PR succeeded without verifying the actual landed changes).
|
||
- Review and merge are separate workflow roles. A reviewer approval is not merge authorization.
|
||
- **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.`
|
||
|
||
#### Merger lease adoption (#536)
|
||
|
||
When review and merge run in **separate sessions**, the merger must **not**
|
||
manually seed `reviewer_pr_lease._SESSION_LEASE` or equivalent in-process state.
|
||
That ad hoc pattern was used incidentally for PR #493 and PR #421; it is not
|
||
canonical proof and is rejected by mutation gates.
|
||
|
||
**Canonical merger handoff:**
|
||
|
||
1. Confirm a reviewer session holds an active PR lease and posted **APPROVED**
|
||
at the current live head (`gitea_get_pr_review_feedback`).
|
||
2. In a clean merger worktree under `branches/`, call
|
||
`gitea_adopt_merger_pr_lease` with `worktree`, `expected_head_sha`, and
|
||
optional `issue_number`.
|
||
3. The tool posts durable adoption proof on the PR thread (`<!-- mcp-review-lease-adoption:v1 -->`)
|
||
recording actor, profiles, adopted-from session/comment, adoption reason, and
|
||
timestamp, then records sanctioned in-session provenance.
|
||
4. Call `gitea_merge_pr` with the same pinned `expected_head_sha`.
|
||
|
||
**Forbidden:** Python one-liners or scripts that call `record_session_lease()`
|
||
without provenance from `gitea_acquire_reviewer_pr_lease`,
|
||
`gitea_adopt_merger_pr_lease`, or `gitea_heartbeat_reviewer_pr_lease`.
|
||
|
||
**Same-session review+merge:** the reviewer session may use
|
||
`gitea_acquire_reviewer_pr_lease` directly; adoption is only for cross-session
|
||
merger handoff.
|
||
|
||
### Close the issue after merge / Reconciliation
|
||
|
||
- **Profile:** issue-manager or merger.
|
||
- **Steps:** Verify remote `master` actually contains the merge (post-merge file-presence verification):
|
||
- Run: `git fetch <remote> --prune; git checkout master; git pull <remote> master --ff-only`
|
||
- Verify that expected files added/modified in the PR are present on `master` (or absent if deleted).
|
||
- Alternatively, verify with: `git log --oneline -- <expected-file>` or `git merge-base --is-ancestor <pr-head-sha> master`
|
||
- Close the issue; release `status:in-progress` (if it cannot be removed, report why).
|
||
- **If closed but not merged (`merged=false`):** Stop normal flow. Do not delete worktrees. Compare PR content to remote `master`.
|
||
- **fully landed:** comment it landed, remove `status:in-progress`, clean up.
|
||
- **partially landed:** reopen issue, create corrective PR for missing pieces.
|
||
- **not landed:** reopen issue/PR, do not clean up.
|
||
- **Direct push to master:** is forbidden except as a documented recovery exception. Final reports must include why, commits, PR metadata, and repaired labels.
|
||
- **Final reports:** must include both PR metadata (state, merged flag, merge commit) and Git content (remote master hash, expected content present, verification method used & results).
|
||
- **Prompt (normal):** `After verifying master contains the merge of PR #N using post-merge file-presence verification, close issue #M and delete the merged branch. Include verification details in the report.`
|
||
- **Prompt (reconcile):** `Reconcile closed-not-merged PR #N by verifying if its content landed on master.`
|
||
|
||
### 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.
|
||
|
||
## Task/role alignment (#167)
|
||
|
||
The **requested task** decides what a session may do — not the credential it
|
||
happens to hold. Resolve the task first with
|
||
`gitea_resolve_task_capability(task=...)`: it returns `stop_required` and
|
||
`task_role_guidance` alongside the permission decision. An LLM asked to
|
||
review must never degrade into author work just because it is connected as
|
||
an author.
|
||
|
||
| Requested task | Required identity/profile | Allowed | Forbidden | Stop when |
|
||
|---|---|---|---|---|
|
||
| Review PR (`review_pr`) | reviewer (e.g. `sysadmin` / `prgs-reviewer`) | read, gated review verdicts | commits, pushes, file edits, author comments, merge without eligibility | active profile is an author profile — stop immediately; do **not** switch to author-side fixes unless the operator explicitly re-tasks |
|
||
| Address PR change requests (`address_pr_change_requests`) | author (e.g. `jcwalker3` / `prgs-author`) | commit/push fixes to the PR branch, PR comment summarizing fixes | review verdicts, approve, request-changes, merge | active profile lacks branch push |
|
||
| Merge PR (`merge_pr`) | merger (e.g. `sysadmin` / `prgs-merger`) | gated merge after eligibility + approval | merging own PR, merging without pinned head match, reviewing PRs | active profile is an author or reviewer-only profile, or any merge gate fails |
|
||
| Comment on issue discussion (`comment_issue`) | any profile with `gitea.issue.comment` | issue thread comments | review verdicts, closing via comment | permission missing (`gitea.pr.comment` does **not** imply it) |
|
||
| Comment on PR (`comment_pr`) | any profile with `gitea.pr.comment` | PR thread comments | review verdicts | permission missing |
|
||
| Author implementation (`create_branch`/`push_branch`/`create_pr`) | author | branch, commit, push, open PR | self-review, self-merge | profile lacks the author permissions |
|
||
|
||
If the task is review/merge and the session is an author profile, the only
|
||
correct outputs are: the read-only PR queue inventory (#164), the structured
|
||
permission report (#142), and a stop. Ask the operator to reconnect to the
|
||
reviewer namespace; a credential or profile swap in the same session never
|
||
cures same-session authorship.
|
||
|
||
## Review feedback discovery (MCP-native)
|
||
|
||
Formal review verdicts (APPROVED / REQUEST_CHANGES / COMMENT) live on the
|
||
review endpoints, **not** in the issue-comment thread. Never infer review
|
||
state from issue comments — use `gitea_get_pr_review_feedback(pr_number=...)`
|
||
(read-only, requires `gitea.read`). It reports:
|
||
|
||
- every submitted review: reviewer, verdict, redacted body, timestamp, and
|
||
the head SHA it reviewed;
|
||
- `latest_review_state_by_reviewer` (PENDING drafts never count);
|
||
- `has_blocking_change_requests` / `approval_visible` (dismissed reviews do
|
||
not block);
|
||
- `current_head_sha` vs `latest_reviewed_head_sha`, `review_feedback_stale`,
|
||
and `author_pushed_after_request_changes` — so a reviewer can see whether
|
||
feedback predates new commits, and an author can see whether fixes have
|
||
been pushed since the REQUEST_CHANGES.
|
||
|
||
A permission block returns `feedback_not_attempted: true` with a structured
|
||
permission report — distinct from a successful "no reviews yet" result, so a
|
||
blocked lookup is never misread as "no feedback exists".
|
||
|
||
## Validation reporting discipline (#167)
|
||
|
||
Validation results in handoffs and PR bodies must state exactly what ran and
|
||
what happened. Build the validation section with
|
||
`build_validation_report(...)` (in `mcp_server.py`) or follow its contract by
|
||
hand — every command is one of:
|
||
|
||
- `passed` — the exact command ran and succeeded;
|
||
- `failed` — must include the exact command **and its output**; never
|
||
paraphrase ("shell-invocation quirks" is not a status);
|
||
- `skipped` — deliberately not run; name the reason and any targeted check
|
||
that replaced it;
|
||
- `not-run` — was required but never executed; say so plainly.
|
||
|
||
Never imply full-suite success unless the full-suite command itself passed
|
||
(`full_suite_passed: true`). A report that hides a failed or skipped check
|
||
is worse than a failing report.
|
||
|
||
## Controller Handoff (required, every task)
|
||
|
||
Every task — implementation, review, merge, triage, documentation,
|
||
discussion-only, or blocked planning — **must end with a
|
||
`Controller Handoff`** so a controller LLM can pick up the state
|
||
without rereading the conversation. The canonical formats and rules live in
|
||
the portable skill:
|
||
[`../skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md) §K.
|
||
|
||
**Compact format is the default** — nine lines (`Task / Repo/state /
|
||
Issues/PRs / Changed / Validation / Blockers / Review / Next / Safety`),
|
||
written for controller-LLM readability, not a full human status report. The
|
||
`Safety:` line is never omitted (usually
|
||
`no self-review; no self-merge; no tags; no secrets; no prod`). PR bodies
|
||
still carry the full review detail — the handoff never replaces PR
|
||
documentation.
|
||
|
||
**The long form** (Work performed · Current state · Files changed ·
|
||
Validation · Issues encountered · Review needed? · Next recommended action ·
|
||
Safety confirmations) **is reserved for high-risk or complex tasks**: a
|
||
merge/tag/release happened, validation failed, permissions/profile gates
|
||
blocked work, secrets or production access were involved, an owner decision
|
||
is complicated, the task spanned multiple repos or cross-issue state, or the
|
||
owner explicitly asks for it.
|
||
|
||
Hard rules: never omit it; never bury blockers earlier only; an opened PR
|
||
means "Review needed — PR is open"; a blocked merge names the exact gate;
|
||
discussion-only comments need owner/design feedback, not code review; any
|
||
touched release state names the exact tag/commit and why. Design debates
|
||
belong in **discussion/RFC issues** (e.g. #100 `profiles.json v2`) — comment
|
||
on the issue, create no branches/PRs, and end the comment with this handoff.
|
||
|
||
## 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)).
|
||
|
||
## Releases and version tags
|
||
|
||
All release tagging, version bumps, and validation must comply with the [Release / Version Process SOP](release-version-sop.md).
|
||
|
||
|
||
Versions follow SemVer — **`vMAJOR.MINOR.PATCH`**, using **`v0.x.y`** while
|
||
unstable. Pick the bump by the largest change since the last tag:
|
||
|
||
- **PATCH** — bug fixes, docs, tests, wrappers, non-breaking workflow polish.
|
||
- **MINOR** — new MCP tools, new workflow helpers, new config features;
|
||
backward-compatible behavior.
|
||
- **MAJOR** — breaking config/schema/API behavior or a changed MCP contract.
|
||
|
||
Tags are **annotated** (`git tag -a`), created **only from the exact commit on
|
||
remote `master`**, **only after the full suite passes**, and carry release notes
|
||
referencing the merged PRs/issues. **Never tag** feature branches, dirty
|
||
worktrees, unreviewed or self-authored work, or commits not on remote `master`.
|
||
|
||
Release runbook (see [`../skills/llm-project-workflow/templates/release-tag.md`](../skills/llm-project-workflow/templates/release-tag.md)):
|
||
|
||
1. `git fetch prgs --prune`.
|
||
2. Confirm local `master` equals `prgs/master` (`0 0`) and the tree is clean.
|
||
3. Run the full test suite; stop on failure.
|
||
4. Review merged issues/PRs since the last tag
|
||
(`git log --oneline <last-tag>..prgs/master`).
|
||
5. Choose the version bump.
|
||
6. `git tag -a <vX.Y.Z> prgs/master -m "<notes referencing #issues / PRs>"`.
|
||
7. `git push prgs <vX.Y.Z>`; add release notes if the forge supports it.
|
||
|
||
`scripts/release-tag` automates steps 1–7 with these gates built in (SemVer
|
||
check, fetch/prune, on-master, clean tree, local==remote master, HEAD on remote
|
||
master, no duplicate tag, tests run unless `--skip-tests`, annotated tag only).
|
||
It is **safe by default** — no push unless `--push`, and `--dry-run` changes
|
||
nothing:
|
||
|
||
```bash
|
||
scripts/release-tag --dry-run v0.4.0
|
||
scripts/release-tag v0.4.0 --notes-file /tmp/release-notes.md
|
||
scripts/release-tag v0.4.0 --notes-file /tmp/release-notes.md --push
|
||
```
|
||
|
||
## Namespace workspace binding (#510)
|
||
|
||
Each MCP namespace resolves its **own** active task workspace. Foreign role
|
||
worktree environment variables must not poison another namespace's purity
|
||
checks.
|
||
|
||
| Namespace | Workspace env vars (in priority under `GITEA_ACTIVE_WORKTREE`) | Allowed roots |
|
||
|-----------|------------------------------------------------------------------|---------------|
|
||
| author | `GITEA_AUTHOR_WORKTREE` | `branches/<task>` worktree only (#274) |
|
||
| reviewer | `GITEA_REVIEWER_WORKTREE` | clean `branches/<review>` worktree |
|
||
| merger | `GITEA_MERGER_WORKTREE` | clean `branches/<merge>` worktree **or** clean control checkout |
|
||
| reconciler | `GITEA_RECONCILER_WORKTREE` | clean `branches/<reconcile>` worktree **or** clean control checkout |
|
||
|
||
`GITEA_AUTHOR_WORKTREE` is **author-only**. Reviewer, merger, and reconciler
|
||
MCP processes ignore it even when it points at a dirty author WIP tree.
|
||
|
||
### Safe reconnect / rebind procedure
|
||
|
||
When a mutation blocks on workspace binding:
|
||
|
||
1. Read the error — it names the **resolved workspace path**, **role
|
||
namespace**, and **binding source** (tool arg, env var, or process root).
|
||
2. Reconnect or relaunch the correct namespace MCP server from the intended
|
||
workspace (or set the role-specific env var before launch).
|
||
3. Pass `worktree_path` on reviewer/merger mutation tools when the active
|
||
branches/ worktree differs from the MCP process root.
|
||
4. **Do not** clean, reset, or discard foreign role worktrees to unblock your
|
||
own namespace — that destroys another agent's WIP.
|
||
|
||
### CTH guidance for workspace binding blockers
|
||
|
||
When posting a Canonical Thread Handoff after a binding blocker:
|
||
|
||
- State which namespace was active (author / reviewer / merger / reconciler).
|
||
- Quote the resolved workspace path and binding source from the error.
|
||
- Name the safe reconnect action (relaunch MCP from `branches/...`, set
|
||
`GITEA_*_WORKTREE`, or pass `worktree_path`).
|
||
- Explicitly note that foreign worktrees must not be cleaned to unblock.
|
||
|
||
## 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
|
||
|
||
- [`issue-acceptance-gate.md`](issue-acceptance-gate.md) — controller issue-acceptance audit after PR merge (#500).
|
||
- [`../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.
|
||
- [`gitea-dual-namespace-deployment.md`](gitea-dual-namespace-deployment.md) — static author/reviewer namespace deployment (#139 decision).
|
||
- [`llm-agent-sha.md`](llm-agent-sha.md) — opaque agent attribution metadata (never an eligibility input).
|
||
- [`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.
|
||
- [`state-handoff-ledger.md`](state-handoff-ledger.md) — canonical state comments and next-action handoff (#494).
|
||
|
||
## Canonical state handoff ledger (#494)
|
||
|
||
Gitea comments and final reports must make continuation obvious without chat
|
||
history. See [`state-handoff-ledger.md`](state-handoff-ledger.md) for:
|
||
|
||
- discussion → issue → PR → review → merge → reconcile lifecycle
|
||
- templates for discussion, issue, PR, and queue-controller state comments
|
||
- final-report requirements: Current status, Next actor, Next action, Next prompt
|
||
- queue-controller priority order and discussion ≥5-comment rule (urgent/trivial
|
||
exceptions)
|
||
|
||
Helpers live in `state_handoff_ledger.py`; final-report enforcement is wired
|
||
through `assess_final_report_validator`.
|
||
|
||
## PR-only queue cleanup mode (#390)
|
||
|
||
Use `pr-queue-cleanup` mode when the queue holds many open PRs and the goal
|
||
is to drain reviews deterministically. One run = one PR = one canonical
|
||
review (`skills/llm-project-workflow/workflows/pr-queue-cleanup.md`).
|
||
|
||
* Cleanup runs are reviewer-role only (`pr_queue_cleanup` resolver task);
|
||
author sessions get `wrong_role_stop`.
|
||
* Forbidden in cleanup mode: issue claiming, branch creation, implementation
|
||
edits, new issue filing, and any second-PR review after a terminal
|
||
mutation.
|
||
* Terminal chain: stop after `REQUEST_CHANGES`; after `APPROVED` continue
|
||
only to same-PR merge with explicit per-PR operator authorization and
|
||
passing gates; stop after merge or merge blocker.
|
||
* Every run reports the next suggested PR without continuing to it; the next
|
||
PR requires a fresh run with fresh identity/capability/inventory proof.
|
||
* Use full `review-merge-pr` mode instead when the operator wants a single
|
||
targeted review, and `work-issue` mode for any authoring.
|