@terreno/agent-plugin-loop 0.0.0-stage → 58.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flourish Health, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,60 @@
1
- # Temporary Holding Version
1
+ # Terreno Loop plugin for Claude Code
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Goal-driven loops for Claude Code. Give it an outcome an agent can measure on its own,
4
+ such as CI wall time, bundle size, or lint count, and it charters the goal, finds the work,
5
+ slices it into pull requests, and re-measures after each one lands until the metric hits
6
+ target or the budget runs out. Each slice goes through the `terreno` lifecycle plugin, so
7
+ install that plugin too.
8
+
9
+ Reference: https://github.com/TerrenoLabs/terreno/blob/master/docs/reference/scan-plugin.md
10
+
11
+ ## Install
12
+
13
+ Pick one channel and register only that one. Each channel provides a plugin named
14
+ `terreno-loop`, so two channels give duplicate commands.
15
+
16
+ Latest stable release, installed from npm as `@terreno/agent-plugin-loop`:
17
+
18
+ ```text
19
+ /plugin marketplace add https://raw.githubusercontent.com/TerrenoLabs/terreno/master/plugins/claude-release/.claude-plugin/marketplace.json
20
+ /plugin install terreno-loop@terreno-releases
21
+ ```
22
+
23
+ Every merge to `master`:
24
+
25
+ ```text
26
+ /plugin marketplace add TerrenoLabs/terreno
27
+ /plugin install terreno-loop@terreno-plugins
28
+ ```
29
+
30
+ One release, pinned: add `TerrenoLabs/terreno#<version>` as the marketplace instead.
31
+
32
+ Then run `/terreno-loop:run <goal>`. Claude Code does not auto-update third-party marketplaces by
33
+ default; run `/plugin update terreno-loop@<marketplace>` to pick up a new release.
34
+
35
+ ## What you get
36
+
37
+ | Command | Does |
38
+ | --- | --- |
39
+ | `/terreno-loop:1-aim` | Charter the goal: metric, baseline, rules, budget |
40
+ | `/terreno-loop:2-sweep` | Run the rules and map parallel workers into candidate findings |
41
+ | `/terreno-loop:3-sift` | Dedupe, verify, and rank findings |
42
+ | `/terreno-loop:4-plot` | Slice findings into PR-sized plans |
43
+ | `/terreno-loop:5-track` | Re-measure the metric and choose the next move |
44
+ | `/terreno-loop:campaign` | Run bounded rounds, stopping at each gate |
45
+ | `/terreno-loop:run` | Stay resident: keep the goal moving and tend every open PR |
46
+
47
+ Loops can run for hours and use many tokens. Set a budget or stop condition in the charter.
48
+
49
+ ## Maintainers
50
+
51
+ This directory is generated. Do not hand-edit it; run `bun run skills:sync`.
52
+
53
+ Claude Code resolves a plugin skill's command from the frontmatter `name`, so the
54
+ shortened names live here instead of in the shared skill files. Cursor, Codex, and
55
+ `npx skills` keep the canonical names from `plugins/terreno-loop/`.
56
+
57
+ | Source | Owns |
58
+ | --- | --- |
59
+ | `plugins/terreno-loop/skills/` | Aim, Sweep, Sift, Plot, Track, and the outer loops |
60
+ | `plugins/terreno-loop/references/` | Scan contract, map-reduce, goal tracking, schemas |
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
+ "description": "Goal-driven code scans for Terreno: charter a measurable long-term goal, map-reduce the repository into verified findings, slice them into IPs and tasks, and drive each slice through the Terreno lifecycle until the metric hits target.",
3
+ "files": [
4
+ ".claude-plugin/",
5
+ "references/",
6
+ "skills/",
7
+ "CHANGELOG.md",
8
+ "LICENSE",
9
+ "README.md"
10
+ ],
11
+ "homepage": "https://github.com/TerrenoLabs/terreno/blob/master/plugins/README.md",
12
+ "keywords": [
13
+ "claude-code",
14
+ "claude-plugin",
15
+ "terreno",
16
+ "scan",
17
+ "codebase-health",
18
+ "map-reduce",
19
+ "refactoring",
20
+ "sdlc"
21
+ ],
22
+ "license": "MIT",
2
23
  "name": "@terreno/agent-plugin-loop",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "directory": "plugins/terreno-loop-claude",
29
+ "type": "git",
30
+ "url": "git+https://github.com/TerrenoLabs/terreno.git"
31
+ },
32
+ "version": "58.0.0-beta.2"
33
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://terreno.dev/schemas/scan-finding.schema.json",
4
+ "title": "Terreno scan finding",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["id", "rule", "title", "severity", "confidence", "anchors", "why", "ev"],
8
+ "properties": {
9
+ "id": {"type": "string"},
10
+ "rule": {"type": "string"},
11
+ "title": {"type": "string"},
12
+ "severity": {"enum": ["high", "medium", "low"]},
13
+ "confidence": {"enum": ["high", "medium", "low"]},
14
+ "effort": {"enum": ["S", "M", "L"]},
15
+ "anchors": {
16
+ "type": "array",
17
+ "minItems": 1,
18
+ "items": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": ["path"],
22
+ "properties": {
23
+ "path": {"type": "string"},
24
+ "line": {"type": "integer", "minimum": 1},
25
+ "symbol": {"type": "string"}
26
+ }
27
+ }
28
+ },
29
+ "why": {"type": "string"},
30
+ "fix": {"type": "string"},
31
+ "ev": {"type": "string"},
32
+ "dedupe": {"type": "string"},
33
+ "round": {"type": "integer", "minimum": 1},
34
+ "shard": {"type": "string"},
35
+ "slice": {"type": "string"},
36
+ "status": {"enum": ["open", "planned", "fixed", "dismissed", "reopened"]},
37
+ "dismissed": {"type": "string"}
38
+ }
39
+ }
@@ -0,0 +1,71 @@
1
+ # Goal tracking
2
+
3
+ A campaign is a long-term goal plus the evidence that the repository is moving toward
4
+ it. Without a measured metric a scan degrades into an opinion backlog.
5
+
6
+ ## Goal shape
7
+
8
+ | Field | Rule |
9
+ | --- | --- |
10
+ | `statement` | One outcome sentence in the repository's language: "cold `bun run compile` finishes in under 30s". |
11
+ | `metric.name` | What is counted or timed. |
12
+ | `metric.command` | The exact command that prints the value. It must run in this repository, unattended, and print a number. |
13
+ | `metric.unit` | `s`, `ms`, `count`, `percent`, `bytes`. |
14
+ | `metric.direction` | `down` or `up` — which way is better. |
15
+ | `baseline` | The value captured by running the command during Aim, with its output quoted. |
16
+ | `target` | The value that ends the campaign. |
17
+ | `horizon` | Rounds or calendar bound after which the campaign reports instead of continuing. |
18
+ | `budget` | Maximum rounds, maximum slices per round, and maximum open PRs at once (`wipLimit`). |
19
+ | `review` | Who reviews campaign PRs and how they are routed; see [`pr-routing.md`](pr-routing.md). |
20
+
21
+ If no command can produce the metric, the goal is not yet measurable. Either pick a
22
+ proxy that is measurable and say so in the charter, or return `BLOCKED` with the exact
23
+ instrumentation the repository is missing. Do not proceed with a metric that is scored by
24
+ judgment alone.
25
+
26
+ Sanity rules for a metric command:
27
+
28
+ - It must be reproducible on a clean checkout of the current branch.
29
+ - It must not depend on the campaign's own artifacts.
30
+ - Time-based metrics are measured the same way every round (same warm/cold state, same
31
+ machine class); record which in the charter.
32
+ - If a metric is noisy, record the measurement protocol (for example, best of three) in
33
+ the charter and use it every round.
34
+
35
+ ## Round record
36
+
37
+ Track appends one record per round:
38
+
39
+ - round number and the shard/rule set used
40
+ - metric at round start and round end, each with the command output
41
+ - findings found, kept, planned, fixed, dismissed, and reopened
42
+ - slices landed, with their IP slug, PR, reviewer, and merge state
43
+ - time each PR spent waiting on review, when the host reports it
44
+ - residual risk and anything deliberately deferred
45
+
46
+ The round record is the campaign's memory. A fresh Track invocation must be able to state
47
+ the whole trajectory from state alone.
48
+
49
+ ## Deciding the next move
50
+
51
+ After re-measuring, Track picks exactly one:
52
+
53
+ | Condition | Next |
54
+ | --- | --- |
55
+ | Backlog still has planned findings within budget | `plot` |
56
+ | Backlog empty, rounds remaining, metric short of target | `sweep` |
57
+ | Metric reached target and held after the last slice landed | `null` — campaign complete |
58
+ | Two consecutive rounds moved the metric less than the charter's minimum step | `null` — report diminishing returns and ask |
59
+ | Metric moved the wrong way after a landed slice | `null` — report the regression with evidence and ask |
60
+ | Budget or horizon exhausted | `null` — report progress and ask |
61
+
62
+ Diminishing returns, regressions, and exhausted budgets are **reports with a question**,
63
+ not silent stops. Each includes the trajectory, what was fixed, what remains, options,
64
+ and a recommended default.
65
+
66
+ ## Re-baselining
67
+
68
+ Do not move the baseline to make progress look better. Re-baseline only when the metric
69
+ command or measurement protocol changes, and then record both the old and new values, the
70
+ reason, and the round it changed in. A campaign whose baseline was rewritten without that
71
+ record is not trustworthy and Track must say so.
@@ -0,0 +1,91 @@
1
+ # Heartbeat
2
+
3
+ A campaign spends most of its wall-clock time waiting on humans and CI. The heartbeat is
4
+ how one resident session stays useful during that wait without spamming anyone.
5
+
6
+ One tick = one bounded pass over the campaign's open PRs and its backlog. A tick that
7
+ finds nothing to do produces **no output** and schedules the next one.
8
+
9
+ ## Tick procedure
10
+
11
+ 1. **Snapshot.** One listing of the campaign's PRs, by the charter's label when it has
12
+ one, otherwise by the recorded PR numbers in campaign state:
13
+
14
+ ```bash
15
+ gh pr list --author @me --label <goal-slug> --state open \
16
+ --json number,title,headRefOid,isDraft,mergeable,reviewDecision,statusCheckRollup,reviewRequests,comments,updatedAt
17
+ ```
18
+
19
+ Reconcile the result against campaign state: a PR that merged, closed, or gained a new
20
+ head since the last tick has changed status regardless of what state remembers.
21
+
22
+ 2. **Classify each PR** into exactly one bucket:
23
+
24
+ | Bucket | Signal |
25
+ | --- | --- |
26
+ | `unrouted` | PR exists with no recorded reviewer/assignee |
27
+ | `answered` | A human comment, review, or requested change arrived after the last tick |
28
+ | `broken` | CI failed on the current head, the PR conflicts with its base, or a review-bot thread is unanswered |
29
+ | `bot-pending` | Review bots or CI still running on the current head |
30
+ | `merged` | Merged since the last tick |
31
+ | `closed` | Closed without merging |
32
+ | `waiting` | Green, routed, no new human input — waiting on review |
33
+
34
+ 3. **Act in priority order**, at most one PR per bucket per tick unless the tick budget
35
+ allows more:
36
+
37
+ 1. `unrouted` → apply [`pr-routing.md`](pr-routing.md).
38
+ 2. `answered` → invoke the lifecycle's Taste for that PR in a fresh subagent. Taste
39
+ owns reading the threads, making the fix, running the pre-push gate, pushing, and
40
+ watching CI. The loop does not answer review comments itself.
41
+ 3. `broken` → same: one Taste invocation for that PR. Taste counts fix rounds across
42
+ ticks; when it returns `BLOCKED` after three, mark the PR `blocked` and surface it
43
+ at the top of the next report. A blocked PR is never left as a quiet `waiting`.
44
+ 4. `merged` → invoke the scan Track stage for that slice: re-measure, close findings
45
+ with evidence, record the round.
46
+ 5. `closed` → mark the slice `abandoned` with the closing reason, return its findings
47
+ to `open`, and surface it in the next report.
48
+ 6. `bot-pending` and `waiting` → nothing. These are why the next tick exists.
49
+
50
+ 4. **Refill capacity.** After acting, if open campaign PRs are below `wipLimit` and the
51
+ backlog has planned findings, advance the campaign one step (Plot the next slice, or
52
+ Sweep when the backlog is empty).
53
+
54
+ 5. **Persist and schedule.** Update campaign state — including `lastTick`, each PR's
55
+ bucket, and the comment ids already answered — then choose the next interval and exit
56
+ the tick.
57
+
58
+ ## Answering exactly once
59
+
60
+ Record the id of every comment, review, and thread the campaign has already acted on. A
61
+ tick answers a human only when that id is new. Re-reading an old thread and replying again
62
+ is the single most common way a resident agent becomes noise.
63
+
64
+ Never post a status comment on a PR just because a tick ran. The only comments the
65
+ campaign writes are the ones the lifecycle's own contracts call for.
66
+
67
+ ## Choosing the interval
68
+
69
+ Match the wait, not the clock:
70
+
71
+ | State | Interval |
72
+ | --- | --- |
73
+ | CI running on a head the campaign just pushed | Prefer the provider's watch hook (`gh pr checks --watch`, `circleci run watch`); fall back to 2–5 minutes |
74
+ | Review bots queued on a new head | 2–5 minutes |
75
+ | Everything green, waiting on a human reviewer | 15–30 minutes |
76
+ | Nothing open and backlog work remains | No wait; continue the campaign immediately |
77
+ | Nothing open, nothing planned, goal not met | 30–60 minutes, or stop and report if the charter's horizon passed |
78
+
79
+ Prefer the harness's native scheduling or completion hooks over sleeping. Use a timer
80
+ only when no hook applies. Never busy-poll a PR that is waiting on a person.
81
+
82
+ ## Tick safety
83
+
84
+ - One tick never spawns more than one lifecycle stage per PR.
85
+ - A tick that fails to reach the host (network, auth, rate limit) records the failure,
86
+ backs off to double the current interval up to the maximum, and tries again. Three
87
+ consecutive host failures is `BLOCKED` with `kind: access`.
88
+ - A tick never rewrites history on a PR a human is reviewing: no force-push beyond what
89
+ the lifecycle's own push gate does, no branch deletion, no closing a human's PR.
90
+ - Ticks are idempotent. A tick that dies mid-way leaves state consistent enough that the
91
+ next tick re-derives everything from the host snapshot.
@@ -0,0 +1,90 @@
1
+ # Agentic map-reduce
2
+
3
+ One agent reading a whole repository finds shallow problems and forgets the first file by
4
+ the last. A scan instead **plans** detection rules, **shards** their hits into small
5
+ batches, **maps** a fresh worker over each batch, and **reduces** the returned candidates
6
+ into one ranked findings list.
7
+
8
+ ```text
9
+ Aim → rules, exclusions, validity rule, severity rubric (plan)
10
+ Sweep → rule hits → deterministic shards → parallel workers (shard + map)
11
+ Sift → dedupe → verify → drop → rank → report (reduce)
12
+ ```
13
+
14
+ ## Plan (Aim)
15
+
16
+ Each detection rule records:
17
+
18
+ | Field | Meaning |
19
+ | --- | --- |
20
+ | `name` | stable slug used by every later stage |
21
+ | `query` | the exact command: `rg` pattern, AST query, dependency query, build probe |
22
+ | `matches` | what a hit means in one sentence |
23
+ | `why` | how a fix moves the goal metric |
24
+ | `exclude` | globs this rule must not report on |
25
+
26
+ A rule is not approved until it has been **dry-run** and its hit count recorded. A rule
27
+ with zero hits is removed or rewritten in Aim, never carried into Sweep as an empty
28
+ promise. A rule with hits in the tens of thousands is narrowed before it ships.
29
+
30
+ Default exclusions unless the charter says otherwise: dependency directories, build
31
+ output, generated trees, lockfiles, snapshots, vendored code, and fixtures.
32
+
33
+ ## Shard (Sweep)
34
+
35
+ Sharding is deterministic so a re-run of the same round produces the same shards:
36
+
37
+ 1. Run every approved rule and collect hits as `path:line:rule`.
38
+ 2. Group hits by their nearest meaningful unit — package, then directory, then file.
39
+ 3. Pack groups into shards up to the charter's shard budget (default: at most 25 files
40
+ or 80 hits per shard, whichever is reached first). Never split one file across shards.
41
+ 4. Sort shards by path so the manifest is stable, and write the manifest to the round's
42
+ artifact directory.
43
+ 5. Cap the number of shards at the charter's limit. When the cap truncates coverage,
44
+ record exactly which groups were dropped and why in the result — silent truncation
45
+ reads as "we looked everywhere" when the scan did not.
46
+
47
+ ## Map (Sweep)
48
+
49
+ Each shard gets one fresh worker with no parent conversation. The briefing contains only:
50
+
51
+ - the goal statement and metric, in one sentence each
52
+ - the rules in play for this shard, with their `matches` and `why`
53
+ - the shard's file list and the hit lines
54
+ - the validity rule and severity rubric, quoted
55
+ - the named commands the worker may run
56
+ - the findings schema and the instruction to return that JSON and nothing else
57
+
58
+ Worker rules:
59
+
60
+ 1. Treat the briefing as the working set. Do not scan outside the shard's files except
61
+ to read a definition or caller the shard's hits point at.
62
+ 2. Do not run a full-repository search, a full-branch diff, or the skill catalog.
63
+ 3. Do not spawn nested workers.
64
+ 4. Do not edit files, stage changes, or run anything destructive.
65
+ 5. Every candidate carries a `path:line` anchor plus the evidence that proves it.
66
+ 6. Report "no valid candidates" plainly; an empty shard is a normal outcome, and
67
+ inventing a marginal finding to look productive is a defect.
68
+
69
+ Run workers concurrently up to the harness's practical limit. A worker that fails is
70
+ retried once with the same briefing; a second failure records the shard as failed in the
71
+ result and does not fail the whole sweep.
72
+
73
+ ## Reduce (Sift)
74
+
75
+ 1. **Normalize.** Give every candidate a dedupe key of `rule + path + symbol`. Merge
76
+ candidates that describe the same defect at different anchors into one finding with
77
+ all anchors listed.
78
+ 2. **Verify.** Re-check each surviving candidate against the charter's validity rule.
79
+ For `high` severity, verify adversarially: try to refute the finding, and keep it only
80
+ when refutation fails. Re-run the candidate's evidence command where one exists.
81
+ 3. **Drop.** Remove candidates whose evidence does not reproduce, whose anchor no longer
82
+ exists, that the validity rule excludes, or that cannot be tied to the goal metric.
83
+ Record the drop count and the top drop reasons.
84
+ 4. **Rank.** Order by goal impact × confidence ÷ effort. Severity is the charter's
85
+ rubric, not the worker's opinion.
86
+ 5. **Report.** Write the findings report: goal and metric header, one row per finding
87
+ with severity, effort, anchors, and evidence, then the dropped-candidate summary.
88
+
89
+ A round that keeps zero findings is a valid result. Report it as a dry round and let
90
+ Track decide whether to re-aim, widen the rules, or declare the goal met.
@@ -0,0 +1,77 @@
1
+ # PR routing
2
+
3
+ Every slice PR reaches a named human. A campaign that opens unassigned PRs is producing
4
+ work nobody owns.
5
+
6
+ Routing is decided once in Aim, recorded in the charter's `review` block, and applied by
7
+ the campaign or the loop immediately after Brew creates the PR. It is never re-decided
8
+ per PR by guesswork.
9
+
10
+ ## The review block
11
+
12
+ | Field | Meaning |
13
+ | --- | --- |
14
+ | `mode` | `fixed`, `codeowners`, `blame`, `round-robin`, or `none` |
15
+ | `reviewers` | Login handles requested for review |
16
+ | `assignees` | Login handles set as assignee (who owns landing it); defaults to `reviewers` |
17
+ | `teams` | Org team slugs requested for review, when the host supports them |
18
+ | `draft` | Open PRs as drafts and mark ready only when CI on the head is green |
19
+ | `labels` | Labels applied to every campaign PR, for example the goal slug |
20
+ | `wipLimit` | Maximum campaign PRs open at once |
21
+ | `mergePolicy` | `human` (default), or `agent-after-approval` with its exact conditions |
22
+ | `notify` | How to nudge: PR body mention, no mention, or a named external channel |
23
+
24
+ ## Modes
25
+
26
+ | Mode | Resolve reviewers by |
27
+ | --- | --- |
28
+ | `fixed` | The charter's explicit handles. Simplest, and the default when one person owns the goal. |
29
+ | `codeowners` | The repository's `CODEOWNERS` entries for the slice's touched paths. Let the host apply them; do not duplicate the request. |
30
+ | `blame` | The most frequent recent authors of the slice's touched files, excluding bots and the campaign's own commits. Cap at two. |
31
+ | `round-robin` | Rotate through the charter's handle list, one per slice, recorded in state so the rotation survives a fresh invocation. |
32
+ | `none` | Open the PR unassigned because the repository routes review elsewhere. Valid only when the charter says so explicitly. |
33
+
34
+ When a mode resolves to nobody — no `CODEOWNERS` match, blame returns only bots, an empty
35
+ handle list — fall back to the charter's `reviewers`, and if that is also empty, stop and
36
+ ask. Do not invent a handle, and do not guess one from a commit email.
37
+
38
+ ## Applying routing
39
+
40
+ After Brew reports the PR, and before the loop counts the slice as in flight:
41
+
42
+ ```bash
43
+ gh pr edit <pr> --add-reviewer <handle> --add-assignee <handle> --add-label <label>
44
+ gh pr ready <pr> # only when draft is true and CI on the head is green
45
+ ```
46
+
47
+ Record on the slice in campaign state: `pr`, `reviewer`, `assignee`, and the routing mode
48
+ that chose them. A slice whose PR exists with no recorded reviewer is not routed yet; the
49
+ next heartbeat tick routes it before doing anything else with that PR.
50
+
51
+ When a push addresses changes that were requested, re-request review from the same
52
+ handles rather than waiting silently:
53
+
54
+ ```bash
55
+ gh pr edit <pr> --add-reviewer <handle>
56
+ ```
57
+
58
+ ## Verifying a handle
59
+
60
+ Before using a handle the campaign has not used before, verify it can be requested:
61
+
62
+ ```bash
63
+ gh api "repos/{owner}/{repo}/collaborators/<handle>" --silent
64
+ ```
65
+
66
+ A handle that fails verification is `BLOCKED` with `kind: human`: name the handle, say it
67
+ could not be requested, and ask who should review instead. Never quietly open the PR
68
+ unassigned when the charter asked for a reviewer.
69
+
70
+ ## What the campaign must not do
71
+
72
+ - Do not approve its own PRs, dismiss reviews, or resolve another person's review thread.
73
+ - Do not merge when `mergePolicy` is `human`, even with approval and green CI.
74
+ - Do not `@`-mention on every push. One routing action per PR, plus one re-request when
75
+ requested changes have been addressed.
76
+ - Do not exceed `wipLimit`. Waiting on review is correct behavior, not a reason to start
77
+ more work.