Skip to main content

ops-issue-loop

Turns a repo's `ops/ready-for-ai` backlog into CI-green PRs by commanding `ops-change` (implement → verify) per issue. It owns the loop only — the queue, the `/goal`, rolling cap-3 dispatch, model choice per issue, the CI-green cap and the backstops — and knows nothing about how to build any product: that is `ops-change`, which the repo provides and which wraps its own workspace. It stops at a green PR and never merges; landing is `ops-merge-loop`, rework is `ops-rework-loop`. Two modes: LOCAL (orchestrated, cap 3) and CLOUD (one session per issue, CI as the test gate). Trigger via loop-dispatch on Issue: Labeled `ops/ready-for-ai`, or "work the ready issues".

Datos de origen

Repositorio
umbraco/umbraco-ai-ops
Última actividad en el origen
24 de septiembre de 2026 a las 14:50
Idioma detectado de SKILL.md
inglés
Estrellas
2
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
ops-issue-loop
description
Turns a repo's `ops/ready-for-ai` backlog into CI-green PRs by commanding `ops-change` (implement → verify) per issue. It owns the loop only — the queue, the `/goal`, rolling cap-3 dispatch, model choice per issue, the CI-green cap and the backstops — and knows nothing about how to build any product: that is `ops-change`, which the repo provides and which wraps its own workspace. It stops at a green PR and never merges; landing is `ops-merge-loop`, rework is `ops-rework-loop`. Two modes: LOCAL (orchestrated, cap 3) and CLOUD (one session per issue, CI as the test gate). Trigger via loop-dispatch on Issue: Labeled `ops/ready-for-ai`, or "work the ready issues".
# ops-issue-loop A durable, product-**agnostic** loop that turns the ready backlog into CI-green PRs. It owns *how to run the loop*. It does not own *how to build anything* — that is **`ops-change`**, which the repo always provides. **Where it stops matters.** A green PR, and no further. It does not merge, and it does not handle review feedback. There is exactly **one merge path** in this engine (`ops-merge-loop → ops-integrate`) behind exactly **one human gate** (the landing label), and a second one here would defeat both. ## What it calls | Capability · action | Why | Visibility | |---|---|---| | **`ops-change · implement`** | do the work for one issue | service | | **`ops-change · verify`** | prove it, this repo's way | service | | `ops-ci · status` / `log` | drive CI green on the PR | cross-cutting (read) | | `ops-repo-meta · identity` | the labels, **by purpose** — `labels.ready`, `labels.in_progress`, `labels.done`, `labels.blocked`, `labels.authored`. Never a hard-coded name | cross-cutting (read) | | `ops-repo-meta · topology` / `lines` | `issues` = where the backlog is, `code` = where the PR opens, and which line is primary | cross-cutting (read) | | `ops-notify · send` | only when the loop gives up on an issue | cross-cutting (infra) | **Never `ops-workspace`.** `ops-change` wraps it, because whether this repo needs a worktree, a container or nothing at all is a product fact. **Never `ops-branching`** — `ops-change` opens its own PR. **Never `ops-integrate`** — this loop does not land. If the repo ships no `ops-change`, **stop and say so.** There is nothing to build, and no default could be right. ## Modes - **Local (orchestrated)** — default. A long-lived loop over the whole backlog: cap-3 parallel dispatch, each issue driven to a green PR. Everything down to *Rules*. - **Cloud (one-shot per issue)** — set by the caller ("run in cloud mode"). One routine fire per labelled issue; build that **one** issue to a green PR with **CI as the test gate**, then stop. See [Cloud mode](#cloud-mode). ## Step 1 — gather the backlog Resolve `labels.ready` and the `issues` repo. **List** open issues carrying that label (`github-ops` → *List issues by label / state*), reading number, title and body. - None → report "nothing is ready" and stop. If the **label does not exist**, say that explicitly: someone has to create it before this loop has anything to do. - Otherwise build a queue of `{number, title, body}` and **announce it** (numbers + titles) before dispatching anything. ## Step 2 — set the goal ``` /goal every open <labels.ready> issue in <issues repo> is terminal — a CI-green PR awaiting the landing label, or blocked-with-a-comment — and no actionable work is left in the queue ``` Make it **satisfiable**: terminal, not merged. This loop cannot merge, so a goal that said "merged" would never clear. Clear it with `/goal clear` when met or aborted. ## Step 3 — build (rolling, cap 3) Dispatch **one subagent per issue**, at most **3 at once** — the first three in a single message, then one per freed slot. Each subagent, for its issue: 0. **Claim the issue, before doing any work.** Remove `labels.ready`, add `labels.in_progress`. Resolve both names from `ops-repo-meta · identity` — never invent them. **This is the first action, not a closing formality.** Removing the ready label is the only thing stopping a second event re-dispatching the same issue, and a build can run for many minutes: leave the label on during it and a re-fire produces a duplicate branch and a duplicate PR. > This step used to sit at the end, after CI went green, while still claiming to be what > prevents a re-fire. It could not be both. A live run ended with CI still building, so the > issue kept `ops/ready-for-ai` and never got its comment — the loop had followed the skill > exactly (29-07-2026). 1. **`ops-change · implement`** with `{ issue, line, port }`. `line` is the **primary line** from `ops-repo-meta · lines`; `port` is `null` here. This loop works the primary line only — porting to another line is its own change, with its own PR, verify and CI. 2. **`ops-change · verify`** on the branch it returns. Red → fix and re-verify, inside `ops-change`. 3. **The PR** — `ops-change · implement` already opened it, through `ops-branching` privately, and returned `pr_number` and `url` alongside the branch. This loop never picks a base and never calls `ops-branching` itself. **An `implement` that returns a branch and no `pr_number` has not finished**: report it as a failure and block the issue. Do not open the PR yourself to get past it — that is a loop reaching into a `supporting` capability, and the result is a pushed branch nobody is reviewing. **Apply `labels.authored` to the PR** once `implement` returns its number. That is provenance, not a gate: it says a loop wrote this, and it stays on. On a repo where humans and loops both open PRs, nothing else tells the two apart without opening each one. It is the PR-side twin of `labels.done` on the issue, and it is the **only** label this loop ever puts on a PR — `land`, `rework` and `port` are the human's, and a loop that applies its own gate has removed the gate. 4. **Drive CI green** — `ops-ci · status`; on red, `ops-ci · log` then back to `implement` / `verify`. **Cap: 8 attempts.** 5. **Mark it done.** Remove `labels.in_progress`, add `labels.done`, and — if step 3 did not already — comment on the issue. Four labels, four different jobs, and they are not interchangeable. Three sit on the issue; `authored` is the one that sits on the PR: | Purpose | Default | Where | When | |---|---|---|---| | `labels.ready` | `ops/ready-for-ai` | issue | the human's gate. Off the moment work starts | | `labels.in_progress` | `ops/in-progress` | issue | **state.** On only while work is in flight | | `labels.done` | `ops/generated-by-ai` | issue | **provenance.** On at the end, and stays on | | `labels.authored` | `ops/generated-by-ai` | **PR** | **provenance.** On at step 3, and stays on | `done` and `authored` share a default *name* and are not one purpose: they are applied to different things, on different repos where the topology is split, at different moments. A repo renaming one must be able to leave the other alone, which is why there are two keys. `done` is not the opposite of `in_progress` — it records that a loop built this, which stays true forever. `in_progress` records that one is building it *now*, which stops being true. Conflating them is what left a live run's issue looking untouched (29-07-2026). **Comment as soon as the PR exists** (step 3), not here. A run that dies while CI is still building must still leave the issue saying a fix is in review. **Write the comment for whoever reads the issue, not for the loop.** Say what was done and which line it is on, in words. **Whether it may link the PR is a topology fact**, and `github-ops` holds the rule: on a split topology (`issues` ≠ `code`) the comment carries no link or reference into the code repo at all. The PR already links the issue, so people who can see the code can still find the PR. On a single repo, put the PR link after the sentence. The same boundary is why `Closes #N` cannot be relied on either (`ops-change · close-issue`). **This comment never says the issue is fixed or released.** A merged PR is not a shipped fix. Closing the issue belongs to `ops-change · close-issue`, and on most repos it waits for a release. Track `{issue, branch, pr_number, model, attempts}`. A subagent is done at a green PR. **Never pass `isolation: worktree` on the Agent call.** `ops-change · implement` prepares its own workspace via `ops-workspace`, and a second isolation layer around it bypasses the repo's own setup — the seeded database, the claimed port, the restored dependencies. A subagent that cannot finish records the issue as **blocked**: `labels.blocked` on, `labels.in_progress` **off**, a comment saying why — **in words, not as a pasted build log** (see step 3). (The ready label came off back in step 0.) Clearing `in_progress` matters — an issue left carrying it reads as work still running, and nothing will ever come back to it. Confirm that happened and **move on** — one bad issue must not stall the queue. ## Step 4 — hand off and stop Every buildable issue now has a green PR. **That is the end of this loop's job.** - **Landing** is `ops-merge-loop`: a human reviews and applies `labels.land`. This loop must **not** apply that label — it is the human gate, and a loop that applies its own approval has removed it. - **Review feedback** is `ops-rework-loop`, fired by the rework label. Report the tally — what is awaiting review, what is blocked and why — and `/goal clear`. **Notify** only for issues the loop **gave up on**, with a stable `key` per issue so a re-run does not re-notify. A green PR is not notification-worthy; the comment is the record. ## Model selection The orchestrator has read each issue, so it triages scope and picks the tier — rather than paying top-tier for a copy tweak or under-powering a real change. The orchestrator itself **inherits the session model**; pinning it would fight `/model` and the routine's own setting. | Scope | Model | |---|---| | **Complex** — new subsystem, cross-cutting, subtle correctness, wide blast radius | `opus` | | **Standard** — a focused feature or fix in existing code, with tests | `sonnet` | | **Trivial but code-touching** — a one-line fix, a config tweak | `sonnet` | | **Docs / non-code only** — no build or test impact | `haiku` (optional) | **Floor: never dispatch code-touching work below `sonnet`.** When unsure, round **up** — an over-powered build is cheaper than a blocked one. **Never `fable`**, for any subagent, any issue, any tier. ## Stop conditions The loop ends when **no actionable work remains**: no queued issue, no running subagent. Every remaining issue is then terminal — a green PR awaiting the landing label, or blocked. - **Local / interactive** → stop, `/goal clear`, hand back a summary. Don't sit polling when the human is right there. - **Cloud / unattended** → the routine ends. There is nothing to wait for: this loop no longer watches for reviews, because `ops-rework-loop` is event-driven and `ops-merge-loop` owns landing. That is a real simplification, and it exists because the loop no longer merges — the old long-lived review phase was there only to serve its own merge. **Backstops — stop touching the issue, label it `labels.blocked`, clear `labels.in_progress`, comment why:** - **CI-green cap: 8** attempts on one PR. Then blocked, with the last failure **named** in the issue comment and its **output on the PR** — same boundary as step 3. A build log is written for whoever can read the code, and pasting one onto a public issue publishes internal paths, internal package names and snippets of a private source tree. The loop cannot notice, because it can read both repos. - **No-progress guard** — never retry the same failing action verbatim. A pass that produces no new state means blocked, not another lap. - **Global backstop (unattended)** — bound total dispatches or wall-clock. When it trips, `log` what was left undone; never silently drop issues. - **Label or issue changed mid-flight** — ready label pulled, or the issue closed → drop it immediately. ## Capturing learnings **Not implemented here, and not this loop's job.** The `ops-learnings` plugin's read-only `SubagentStop` / `SessionEnd` hooks analyse each transcript **off the critical path** and file `ops/proto-learning` issues to the `learnings` repo. `ops-triage-loop` sweeps them weekly. **Neither this loop nor any subagent files a learning by hand, and nothing here edits a skill or `CLAUDE.md` inline.** The only duty is *not* to fix a learning inline: do the work well and let the hooks capture it. ## Rules - **Never touch an issue without the ready label.** It is the only gate. The one exception is the outcome swap in Step 3 — that is the loop finishing an issue, not a human pulling the gate. - **Never merge, and never apply the landing label.** One merge path, one human gate. - **Never resolve a base branch, a merge strategy, or a workspace.** You cannot, and must not want to. - **Ask for labels by purpose**, never by literal name — a repo may have renamed them. - **Reviews are non-negotiable**: `ops-change · verify` owns running this repo's security and code review over the changes, and fixing what they surface before pushing. This loop requires that it happened; it does not define what it is. **Requiring it means reading `reviews` in what `verify` returns** — the catalog makes that part of the action, so a `verify` that only builds and tests comes back with an empty list. Treat empty as a **missing gate**: say so in the issue comment rather than reporting a clean pass. A requirement nobody checks is a requirement the first scaffolded `ops-change` will quietly drop. - **Recap as you go** — one line per dispatch, completion and block. - **Never use `fable`.** ## Cloud mode Everything above is local mode. Cloud mode is **event-triggered, one session per issue**, so there is **no cap-3 queue**: cross-issue parallelism comes from separate sessions firing. The session is a thin orchestrator on a cheap base model that triages one issue and dispatches **one** build subagent on the best-fit tier. For the triggering issue (from the event; if unclear, the **oldest** open ready issue; none → a quiet no-op): 1. **Triage and dispatch** — pick the tier, spawn one subagent. *If the environment cannot spawn a subagent with a model override, do the work inline on the routine's own model and say so.* 2. **Build** in the session's own checkout. `ops-change` will recognise it already has an isolated workspace and reuse it rather than nesting a worktree. **CI is the test gate**: run whatever fast sanity pass `verify` defines and let the full suite run in CI. 3. **Open the PR, drive CI green** (8-attempt cap). 4. **Swap the labels and stop.** Do not enter a review phase; do not merge.
Ver en GitHub