@terreno/agent-plugin-loop 0.0.0-stage → 57.21.0
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/.claude-plugin/plugin.json +28 -0
- package/CHANGELOG.md +2124 -0
- package/LICENSE +21 -0
- package/README.md +59 -2
- package/package.json +31 -4
- package/references/finding.schema.json +39 -0
- package/references/goal-tracking.md +71 -0
- package/references/heartbeat.md +91 -0
- package/references/mapreduce.md +90 -0
- package/references/pr-routing.md +77 -0
- package/references/scan-contract.md +195 -0
- package/references/scan-result.schema.json +121 -0
- package/references/scan-state.schema.json +199 -0
- package/skills/1-aim/SKILL.md +114 -0
- package/skills/1-aim/references/charter.md +96 -0
- package/skills/2-sweep/SKILL.md +92 -0
- package/skills/2-sweep/references/worker-briefing.md +70 -0
- package/skills/3-sift/SKILL.md +93 -0
- package/skills/4-plot/SKILL.md +124 -0
- package/skills/5-track/SKILL.md +112 -0
- package/skills/campaign/SKILL.md +185 -0
- package/skills/run/SKILL.md +165 -0
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
|
-
#
|
|
1
|
+
# Terreno Loop plugin for Claude Code
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
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": "57.21.0"
|
|
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.
|