Check Change
ASecurityReview an open change thoroughly and against something outside the change itself: assess what work categories the diff contains, resolve each category''s review treatment from the check-knowledge memory shelf, review each through its layers (objective linters, then design-grounding from committed sources for design-bearing categories), consolidate findings that each cite their basis, and stop at one decision gate resolved per gate-config — the reviewer owns the approve/reject verdict when the...
- 3 stars
- 0 votes
- 0 copies
- 0 views
- Added November 7, 2026
Works with
Security analysis
100/100Pro scans all 11 files and shows the line behind each finding
npx -y skills add intent-driven-ai/garura --skill check-change --agent claude-codeAre you the author of Check Change?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/intent-driven-ai-check-change)---
name: check-change
position: end
description: 'Review an open change thoroughly and against something outside the change itself: assess what work categories the diff contains, resolve each category''s review treatment from the check-knowledge memory shelf, review each through its layers (objective linters, then design-grounding from committed sources for design-bearing categories), consolidate findings that each cite their basis, and stop at one decision gate resolved per gate-config — the reviewer owns the approve/reject verdict when the gate is on; when off the computed recommendation is recorded and posted as a clearly marked harness verdict. The gate (third step) of the end sequence in the ProductOS command model. Use when a PR is ready to be reviewed and a verdict is needed.'
user-invocable: true
---
# check-change
Review an open change so the merge decision rests on something **outside** the change. Assess
the work categories the diff contains, resolve each category's treatment from the
check-knowledge memory shelf, review each through up to two layers (objective linters, then
design-grounding for design-bearing categories), consolidate findings that each cite a basis,
and stop at one decision gate resolved per gate-config: the reviewer owns the verdict when
the gate is on; when off, the computed recommendation becomes the decision, marked as a
harness verdict — either way it is posted to the PR.
**Pipeline position: end** (third step of the end sequence: commit-change →
propose-change → check-change → merge-change). The D2 pipeline-position rule injects this
sequence into any play declared `position: end`. Independent, invokable play; ordering is
enforced by pre-flight (an open PR must exist).
## Compiled From
This play was compiled from the check-change ICE (`reference/ice.md`) by edit-play
(#443 redesign; #467 Batch C gate ruling; #484 scripted chain; #496 grounding fan-out; #860 installed tools). Intent defines constraints (C1–C12) and failure
conditions (F1–F13); the expectation defines success scenarios (S1–S6), a Done means
(D1–D3, baked to `stop-condition.yaml`), and one recovery entry per failure condition.
To modify this play, update `reference/ice.md` and recompile with edit-play.
Do NOT edit this file manually — it is a compiled artifact.
## Role
You are the orchestrator. You own the workflow, the step order, and the one decision gate.
You delegate the agentic assessment and the design-grounding to `change-reviewer` and the
standards linter to `quality-auditor` — genuine judgment, via JSON contracts over files on
disk. The mechanical PR/host I/O (context bind, verdict post) runs in bundled scripts, not
an agent (C10, #484). You presume **no** work categories and **no** review
treatment: the categories are assessed every run, and `change-reviewer` resolves each
category's treatment from the `knowledge/review/` shelf, which it copies into short-term
memory through the memory tool, and writes it into `categories.yaml`. You read the treatment
only from `categories.yaml` (C2). Layer 1 lints the changed parts where they sit in the
repository under review, with the installed tools in `.garura/core/tools/` (C12). You run only
the layers and steps that apply to the categories present. You never invent the verdict — you compute a *recommendation*; the gate
resolution (C8) decides who owns the decision: the human when the gate is on, the
recommendation itself (recorded as a harness verdict) when it is off.
**The guiding rule: the change under review is never its own standard.**
**Forbidden:** presuming or hardcoding a category list or a review treatment; grounding a
design principle in the branch's own new content; running a full-repo scan (reads outside the
diff are allowed *only* to reconstruct committed grounding, and are read-only);
auto-deciding the verdict while the gate resolves on; recording an off-gate decision that
differs from the computed recommendation, or posting any decision that does not name its
decider (harness vs human); leaving a verdict silent or unposted; reporting a finding with
no cited basis.
**Agent boundaries:**
| Agent | Domain | Skill/script it invokes | Phases | Role type |
|-------|--------|-------------------------|--------|-----------|
| `template-manager` | Hand out the Next-line map (`standards/rules/pipeline-next.md`) before Step 2, and the close's templates | the memory tool | Assessment, Close | Utility |
| `change-reviewer` | (Assessment) assess work categories from the diff, write each category's treatment into `categories.yaml`, and copy the memory the review shares (the review shelf, the PR severity taxonomy); (Review) design-ground **one** design-bearing category from committed sources — dispatched once per design-bearing category as a concurrent read-only fan-out (C11) | the memory tool; `git show <base>:<path>` | Assessment, Review | Domain |
| `quality-auditor` | Run the standards linter (`check-pr-quality`) against the diff for the standards-driven categories, with the PR severity taxonomy copy that Step 2 made | `check-pr-quality` | Review | Domain |
The mechanical PR/host I/O has **no agent** (C10): `fetch_pr_context.py` binds the PR context
(Step 1) and `post_verdict.py` posts the decision + commits the run artifacts (Step 6), both
via `platform_adapter.py`.
Domain agents: 2 (`change-reviewer`, `quality-auditor`) — `change-reviewer` is dispatched
once for assessment and then **once per design-bearing category** for grounding, in one
concurrent batch (C11, `standards/rules/concurrent-fanout.md`). The verdict **recommendation**
is a deterministic script; the verdict **decision** follows the gate resolution — the human's
when the gate is on, the recommendation recorded as a harness verdict when it is off (C8).
## Pre-flight
| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve working base + config (`.garura/core/config.yaml`) | — | Hard halt |
| An open PR exists for the branch | — | Hard halt |
| Platform CLI available (`gh`) | C8 | Hard halt |
| Resolve the standards set via `standards_order` | C2 | Hard halt |
| Fetch PR diff + `changed_paths` + base ref (the only files in scope) | C5 | Hard halt |
Resolve the deterministic pre-flight facts with the bundled resolver — config tokens,
`branch`, `issue`, `evidence_record`:
```
python3 scripts/preflight.py --play check-change \
--config .garura/core/config.yaml --branch "$(git branch --show-current)"
```
Right after the resolver, record the session identity stamp's start marker (#463 — soft-fail, never a halt):
```
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}{issue}/status/session-stamp-review-change.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
```
The open-PR check and `gh` availability are live host reads; the standards set and the PR
diff/`changed_paths`/base ref are resolved by the bundled `fetch_pr_context.py` (Step 1, via
`platform_adapter.py`) — no agent (C10). It names no memory folder: the check-knowledge shelf
comes through the memory tool in Step 2 (C2), and the Next-line map from `template-manager`
at the end of Step 1 (C12). Record the standards set, the PR's
`changed_paths`, and the base ref; everything downstream is bounded to those paths.
## Task DAG
Create ALL tasks immediately after resolving the working base — before any domain work.
The play owns this DAG; agents must not edit its top-level tasks.
```
[T1] Bind PR Context blockedBy: []
[T2] Assess Categories blockedBy: [T1]
[T3] Review Per Category (layers) blockedBy: [T2]
[T4] Consolidate + Recommend Verdict blockedBy: [T3]
[T5] Decision Gate (config-resolved) blockedBy: [T4]
[T6] Post Decision to PR blockedBy: [T5]
[T7] Scenario Validation blockedBy: [T6]
[T8] Close blockedBy: [T7]
```
Mark each task in-progress before its step and completed right after its eval passes. No
runtime reordering. On resume, skip completed and reset in-progress to pending.
## Workflow
### Phase: Assessment
**Step 1 — Bind PR Context** · Owner: play (script) · Depends on: pre-flight
Run the bundled `fetch_pr_context.py` — it fetches the PR diff/changed-paths/base-ref (via
`platform_adapter.py`) and binds `standards_order` from config into `context.yaml`. No agent
(C10). Standards *resolution per finding* is owned downstream by `check-pr-quality`; this step
binds the diff, the changed paths, the base ref and the standards_order:
```
python3 scripts/fetch_pr_context.py --config .garura/core/config.yaml \
--out-context "<working>/context.yaml" --out-diff "<working>/pr.diff"
```
`context.yaml` records `standards_order`, `changed_paths`, `changed_files_reported`, and
`base`. It names no memory folder. `changed_paths` comes from the diff headers, so a PR of
any size gives every path. When the host's file count and the count of paths in the diff do
not agree, the script writes no context file and exits 3: stop the review and show its message.
Then, before Step 2, dispatch `template-manager` for the Next-line map (C12). Its copy is the
one `change-reviewer` gets as an input and every play-linter run gets as `--next-map`;
`change-reviewer` does not fetch it.
{ "task": "load these templates into the issue's short-term memory",
"inputs": { "issue": <issue>, "templates": ["standards/rules/pipeline-next.md"] },
"outputs": { "out_dir": "{stm_base}{issue}/context/templates/",
"manifest": "{stm_base}{issue}/context/templates/manifest-check-change.json" },
"task_id": "load-next-map" }
`next_map` is `{stm_base}{issue}/context/templates/standards/rules/pipeline-next.md`. When
`template-manager` returns `template_missing`, the play-linter runs record
`did not run: no Next-line map` and the review goes on.
**Step 2 — Assess Categories** · Owner: `change-reviewer` · Depends on: Step 1
This one dispatch copies all the memory the review shares, so no later dispatch writes a
shared file (C11): the `knowledge/review/` shelf (`**/knowledge/review/*.md`) into
`review_shelf`, and the PR severity taxonomy (`**/standards/rules/pr.md`) into
`severity_taxonomy`, each through the memory tool into the issue's short-term memory, with
each source in the manifest beside them (C2). The agent then reads the diff and derives the
work categories actually present — judging each changed file by its content, consulting the
playbook copies to recognise seeded categories but never limited to them. It writes
`categories.yaml`: each present category, the changed paths that evidence it, the governing
playbook id (or `null` + "playbook missing" for a new category), and the category's
treatment from that playbook — the layers that apply, the linters to run, and the runnable
checks.
{
"task": "assess the work categories present in the diff; cite changed-path evidence and the governing playbook; write each category's treatment",
"inputs": { "context": "<working>/context.yaml", "diff": "<working>/pr.diff",
"next_map": "{stm_base}{issue}/context/templates/standards/rules/pipeline-next.md" },
"outputs": { "categories": "<working>/categories.yaml",
"review_shelf": "{stm_base}{issue}/context/memory/knowledge/review/",
"severity_taxonomy": "{stm_base}{issue}/context/memory/standards/rules/pr.md" }
}
**SE-1 (F1/C1):** every category in `categories.yaml` cites the changed paths that put it
there, and the set was derived from the diff — no presumed/hardcoded category list was used
(an absent category is simply not listed).
### Phase: Review
**Step 3 — Review Per Category** · Owner: play (dispatch) · Depends on: Step 2
For each category in `categories.yaml`, read its treatment from its entry there (never from
a playbook copy), then run **only the layers that apply**:
- **Layer 1 — linters.** For the standards-driven categories (code, config, tests, docs-prose)
the play dispatches `quality-auditor` to run `check-pr-quality` against the diff →
`quality-findings.yaml`. For harness/memory the play runs the installed structural linters
the treatment names, from the project root, on the changed parts where they sit in the
repository under review (C12). When `changed_paths` holds a path under `core/components`:
```
node .garura/core/tools/lint-components/index.js --layout source --target core/components
python3 .garura/core/tools/lint-play/lint_play.py <the changed play's SKILL.md> --next-map "<next_map>"
```
with the play linter run once for each changed play, on its `plays/<play>/SKILL.md` under
`core/components`. Else:
```
node .garura/core/tools/lint-components/index.js --layout installed --target .claude
python3 .garura/core/tools/lint-play/lint_play.py .claude/skills/<play>/SKILL.md --next-map "<next_map>"
```
with the play linter run once for each changed installed play. `<next_map>` is the copy
`template-manager` handed out at the end of Step 1, so the Next-line check runs. Only a
violation whose file is in `changed_paths` becomes a finding; count the others, and record
the count as "there before". When an installed tool is not there, record `not installed`
for that check; when a tool exits with a crash, record `did not run: <first error line>`.
Neither is a pass, and neither stops the review. Every tool runs from its installed place;
reading the repository under review is not running Garura's source. For
categories whose linter is a known gap (ADRs, config-map path-existence), the play performs
the playbook's objective checks and records the basis. Where a category has a **runnable**
check (a test suite, a language linter), the play executes it — it is not merely read.
- **Layer 2 — design-grounding (concurrent read-only fan-out, C11).** Take the design-bearing
categories from `categories.yaml` (harness, memory, any new design-bearing category) and
dispatch **one `change-reviewer` per design-bearing category in a single concurrent batch**
(one message, N dispatches — concurrent read-only fan-out: each grounds only its own category
from committed sources, writes only its own `design-findings-<category>.yaml`, no grounding
agent depends on another; the safety conditions hold and the batch is **joined** before Step 4
reads any output — see `standards/rules/concurrent-fanout.md`). Each agent reconstructs its
category's principles from committed sources (base ref, `docs/philosophy/**`, `docs/adr/**`,
`standards/rules/**`, read through the memory tool) and checks conformance; grounding reads are read-only and never
from diff-added content. Each agent writes the extra memory it copies only under
`{stm_base}{issue}/context/memory/grounding-<category>/`, with its own `manifest.json` there,
and reads the shared copies Step 2 made, so no two dispatches write one file. The batch is bounded (the design-bearing set fixed by Step 2) and its
outputs are collected in a stable category order, so the result is identical to a serial run.
A single design-bearing category is a fan-out of width one.
A category whose categories.yaml entry is absent has its layers skipped — that is the expected
path, not a failure.
One grounding dispatch per design-bearing category `<cat>`, all sent as one concurrent batch:
{
"task": "design-ground ONLY the '<cat>' category from committed sources; emit conformance findings",
"inputs": { "context": "<working>/context.yaml", "categories": "<working>/categories.yaml",
"category": "<cat>", "diff": "<working>/pr.diff" },
"outputs": { "design_findings": "<working>/design-findings-<cat>.yaml",
"memory_dir": "{stm_base}{issue}/context/memory/grounding-<cat>/" }
}
The standards linter (a single dispatch across all standards-driven categories):
{
"task": "run the standards linter against the diff for the standards-driven categories, with the PR severity taxonomy copy Step 2 made as severity_taxonomy_path",
"inputs": { "context": "<working>/context.yaml", "categories": "<working>/categories.yaml",
"severity_taxonomy_path": "{stm_base}{issue}/context/memory/standards/rules/pr.md" },
"outputs": { "quality_findings": "<working>/quality-findings.yaml" }
}
**SE-2 (F2/C2):** each reviewed category's treatment is sourced from its `knowledge/review/`
playbook and written by `change-reviewer` into its `categories.yaml` entry (the entry cites the
playbook id, and the manifest names its memory source); the play read the treatment only from
`categories.yaml` — no treatment is carried in the play body, the play read no playbook copy,
and no memory folder was read by its path.
**SE-3 (F3/C3):** the layers executed equal the union of the applicable layers for the
categories present — no layer ran for an absent category, and no applicable layer was skipped.
**SE-4 (F4/C4):** every grounded principle across the `design-findings-<category>.yaml` set
cites a committed/external source; none cites a diff-added file (the branch is not its own standard).
**SE-5 (F5/C5):** every path read for review is within `changed_paths`, except grounding reads
which are read-only and committed — no full-repo scan ran.
**SE-7 (F7/C7):** for each in-scope category with a runnable check, the check was executed and
its result recorded; none was merely read.
**SE-12 (F12/C11):** design-grounding ran as a concurrent read-only fan-out — the count of
grounding dispatches equals the count of design-bearing categories in `categories.yaml`; each
wrote only its own `design-findings-<category>.yaml` and only its own
`grounding-<category>/` memory folder and manifest (distinct paths, no overlap); the batch was
joined before Step 4 read any of them; and the collected result is order-stable (a serial re-run
would produce the same consolidated findings).
**SE-13 (F13/C12):** every Layer 1 linter that ran was an installed tool under
`.garura/core/tools/` (the command lines recorded in the findings show it), run on the layout
where the changed parts sit (source when `changed_paths` holds a path under `core/components`,
else installed); every Layer 1 finding names a file in `changed_paths`, and the other
violations are recorded as a "there before" count; each play-linter run carried `--next-map`
with the copy `template-manager` handed out, and its output shows the
`next-command (pipeline-next)` check; each tool that was not installed has a `not installed`
entry, each tool that crashed has a `did not run: <first error line>` entry, and the review
went on.
### Phase: Decision
**Step 4 — Consolidate + Recommend Verdict** · Owner: play (script) · Depends on: Step 3
The play assembles its agents' outputs — `quality-findings.yaml`, the joined
`design-findings-<category>.yaml` set (read in stable category order after the fan-out barrier),
and any bundled-linter findings — into one `findings.yaml` grouped by category and severity — every
finding already classified P1–P4 (by `check-pr-quality` for standards findings, by the
`pr.md` taxonomy for design findings) and carrying a cited basis. It then computes the
**recommended** verdict — a fixed rule, no agent:
```
python3 scripts/compute_verdict.py --findings <working>/findings.yaml --threshold <conf>
```
It emits `recommended_verdict.yaml` (recommendation + reason + cited blocking findings +
counts): **reject** if any P1 finding (or a runnable suite is red), otherwise **approve**.
This is an input to the human gate, not the decision.
**SE-6 (F6/C6):** every finding in `findings.yaml` carries a P1–P4 class **and** a cited basis
— a linter rule id or a grounded principle with its committed source.
**Step 5 — Decision Gate (class: standard)** · Owner: play · Depends on: Step 4
This checkpoint is a config switch per `standards/rules/gate-config.md` (C8, #466/#467):
resolve `gates.plays.check-change` → `gates.classes.standard` → `gates.default`
(absent ⇒ on). Per the #467 ruling `gates.plays.check-change` is `off`; flipping the
config back to `on` restores the human wait unchanged. `review-pr.bypass` remains a
SEPARATE, pre-existing config (whether a reject blocks the downstream merge), untouched
by this switch.
- **On — the human decides.** Present, in plain language: the categories assessed; for
each design-bearing category, the principles grounded and their committed sources; the
consolidated findings grouped by category and severity, each with its basis; and the
recommended verdict with its reason. Then output the summary and **wait for the
reviewer's typed approve or reject** (do NOT use AskUserQuestion; do NOT proceed on the
recommendation alone). Record the reviewer's decision verbatim to `decision.yaml`:
`decision: approve | reject` and `decided_by: human` (C9).
- **Off — the recommendation becomes the decision.** Do not wait. Record
`gate skipped by config (gates.plays.check-change)` as a Checkpoint Decisions row in
the evidence. Write `decision.yaml` directly from `recommended_verdict.yaml`:
`decision:` its `verdict` **verbatim** (approve | reject), `decided_by: harness`, and the
citing (blocking) findings (C9). A `reject` outcome stands exactly as a human reject would — fix the cited
findings and re-raise. The verdict is still never silent and never unposted (C9).
**Step 6 — Post Decision to PR** · Owner: play (script) · Depends on: Step 5
Assemble the decision comment (decision + the cited findings + routing) into a file — the
comment ALWAYS names its decider: a human decision is posted as the reviewer's verdict; an
off-gate decision leads with `Harness verdict (gate off per gates.plays.check-change):
<decision>` so no reader mistakes it for a human review (C8, F10). Then run the bundled
`post_verdict.py` — it reads `decision.yaml` first and posts nothing (exit 2) unless the
record holds a decision (approve | reject) and its decider (harness | human), and the
comment's first line names that same decision and decider (C9, #536);
then it posts the comment (via `platform_adapter.py`), writes `review/posted.json`
`{posted: true, comment_url, decision, decided_by, decision_recorded: true}`, and
commits+pushes the review run artifacts, so the PR updates with its own review record. No
agent (C10):
```
python3 scripts/post_verdict.py --config .garura/core/config.yaml \
--pr-number "<pr number>" --comment-file "<working>/decision-comment.md" \
--decision "{stm_base}{issue}/review/decision.yaml" \
--issue <issue> --artifacts-dir "{stm_base}{issue}/review/" \
--out "<working>/posted.json"
```
Whether a `reject` hard-blocks the downstream merge is governed by config
(`review-pr.bypass`), unchanged from prior behavior. A decision recorded with its decider
AND posted counts as done regardless of approve/reject (C9).
**SE-8 (F8/C8):** the recommended verdict was computed; when the gate resolved on, the
recorded decision equals the reviewer's typed input; a verdict comment authored by this
run exists on the PR after the step.
**SE-9 (F9/C9):** `posted.json` reads `posted: true` with a comment URL, and
`decision_recorded: true` with the `decision` and `decided_by` of `decision.yaml`; the run
artifacts are on the remote branch; the stop-condition verdict reads held before the close may read
COMPLETED.
**SE-10 (F10/C8):** when the gate resolved off, `decision.yaml` reads `decided_by: harness`
and equals the computed recommendation verbatim with its citing findings, and the posted
comment identifies its decider (harness vs human).
**SE-11 (F11/C10):** the PR context bind (Step 1) and the verdict post + artifact
commit/push (Step 6) ran as the bundled `fetch_pr_context.py` / `post_verdict.py` (via
`platform_adapter.py`), not an agent dispatch — no `repo-orchestrator` was invoked for the
mechanical PR/host work.
### Phase: Scenario Validation
**Step 7 — Scenario Evals** · Owner: play · Depends on: Step 6
- **SCE-1 (S1 — mixed PR):** the assessed category set reflects the diff's content; every
present category ran its applicable layers; no absent-category layer ran; posted.json
records the comment URL; the run artifacts are committed and pushed to the branch; the
stop-condition verdict reads held.
- **SCE-2 (S2 — single-kind PR):** only the applicable layers ran; design-grounding and test
execution were skipped — not failed — when their categories were absent; posted.json
records the comment URL; the run artifacts are committed and pushed to the branch; the
stop-condition verdict reads held.
- **SCE-3 (S3 — external grounding):** every grounding source recorded is committed/external;
none is a diff-added file.
- **SCE-4 (S4 — blocking finding):** with a P1 finding, the recommended verdict is reject
citing it; the decision followed the gate resolution (human's input when on; the harness
reject, marked as such, when off); the posted decision equals the recorded decision and
names its decider.
- **SCE-5 (S5 — cited basis):** every finding in the consolidated report names a linter rule
or a grounded principle as its basis.
- **SCE-6 (S6 — multi-category grounding):** when the diff holds more than one design-bearing
category, the number of grounding dispatches equals the number of design-bearing categories,
each wrote only its own `design-findings-<category>.yaml`, the batch was joined before
consolidation read any of them, and the consolidated result is order-stable.
### Phase: Evidence & Close
**Step 8 — Close** · Owner: play · Depends on: Step 7
Run the Standard Play Close. Evidence recording is play-only and config-gated per the D1
evidence rule (`standards/rules/evidence-recording.md`).
```bash
# --- Standard Play Close (shared; see ../_shared/play-close.md) ---
# Run the close in ../_shared/play-close.md. Its Step L loads the templates through
# template-manager and sets ts and evidence_dest; then run this play's own lines below;
# then Steps C0-C2.
# evidence_base, slug (project-scoped play):
# evidence_base="${stm_base}${issue}/evidence/check-change/" ; slug="#${issue}"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}${issue}/status/session-stamp-review-change.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the run's STM as the close's
# authoritative input.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}${issue}/" \
--out "${stm_base}${issue}/status/stop-condition-review-change.yaml"
sc_exit=$? # 0 held · 1 unmet · 2 error
```
**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED`.
Anything else closes `HALTED` with `exit_reason: stop_condition_unmet` and the evidence's
Stop Condition section names every unmet clause. An unevaluable verdict is never a pass.
**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag (global +
per-play `evidence.plays.check-change`; first match wins, absent ⇒ record). When false,
skip the write and record `evidence skipped (record=false)` in the report's pointer line.
Otherwise fill the `evidence-file.md` slots (play `check-change`, run_id
`review-change-${ts}`, issue, started_at/completed_at, status per C0, exit_reason; artifacts
produced: `context.yaml`, `categories.yaml`, `quality-findings.yaml`, `design-findings.yaml`,
`findings.yaml`, `recommended_verdict.yaml`, `decision.yaml`, `posted.json` (with the PR
comment URL), the stop-condition verdict; step/scenario eval results; the recorded decision
(with its `decided_by`) as the terminal status; checkpoint decisions (incl. any
`gate skipped by config` row);
commit reference = the run-artifacts commit SHA; the session identity stamp fields from
$session_stamp (#463): session_id, ledger_file, ledger_start_offset, ledger_end_offset (null
when unresolved — never blocks the close); and stop_condition per C0 with the Stop Condition
section filled) and write to `$evidence_dest`. Do NOT hand-author the body.
**Step C2 — Render delivery report.** Also render the **Next** line: resolve this play in the Next-line map (`$next_map`, from `template-manager`) and emit `**Next:** /<command> — <why>.` (omitted when the mapped command is null), per `play-close.md`. Fill the `delivery-report.md` slots and output the
report: `## check-change Delivered — #${issue}` (lead with the human's decision), the Run
Summary table, the Pipeline Steps table from the task DAG, the Artifacts Produced table, Next
Steps (on approve: run merge-change; on reject: fix the cited findings and re-raise), and a
pointer to `$evidence_dest`. Always emitted; never gated.
```bash
# --- end Standard Play Close ---
```
## Scenario Validation
| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — mixed PR | reviewer | SCE-1 |
| S2 — single-kind PR | reviewer | SCE-2 |
| S3 — external grounding | reviewer | SCE-3 |
| S4 — blocking finding | reviewer | SCE-4 |
| S5 — cited basis | reviewer | SCE-5 |
| S6 — multi-category grounding | reviewer | SCE-6 |
## Recovery
| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | categories presumed or hardcoded | re-run the category assessment from the diff and discard any presumed list | autonomous |
| F2 | treatment taken from the play body or from a playbook copy | have `change-reviewer` re-resolve the category's treatment from the `knowledge/review/` shelf into `categories.yaml`, and read it from there | autonomous |
| F3 | a layer ran for an absent category, or an applicable layer was skipped | recompute applicability from the assessed categories — skip absent-category layers, run every applicable one | autonomous |
| F4 | grounding drawn from the branch's own new content | re-ground the design from committed/external sources only and re-check conformance | autonomous |
| F5 | the review expanded beyond the diff | re-scope to the diff; keep any outside reads read-only and grounding-only | autonomous |
| F6 | a finding has no cited basis | attach the finding's basis (linter rule or grounded principle) or drop the finding | autonomous |
| F7 | a runnable check was read but not executed | execute the check and fold its result into the findings | autonomous |
| F8 | the verdict was auto-decided while the gate was on, or the decision was not posted | hold at the human gate, record the reviewer's decision verbatim, and post it to the PR | human |
| F9 | the run is about to close COMPLETED with the Done means unmet (verdict never posted, posted.json or run artifacts missing) | post the verdict / fix the run state (write posted.json, commit+push the run artifacts), then re-evaluate the stop condition; the close stays HALTED until the verdict reads held | autonomous |
| F10 | with the gate off, `decision.yaml` differs from the computed recommendation, or the posted comment does not identify its decider | rewrite `decision.yaml` from `recommended_verdict.yaml` (`decided_by: harness`, recommendation verbatim, citing findings) and re-post the comment with the decider named | autonomous |
| F11 | a mechanical PR/host op (context bind, or verdict post + artifact commit/push) ran through an agent instead of its bundled script | route it through `fetch_pr_context.py` / `post_verdict.py` (via `platform_adapter.py`) and remove the agent dispatch | autonomous |
| F12 | design-grounding ran as a serial monolithic pass, or a grounding agent crossed category scope / wrote a shared output, or the batch wasn't joined before consolidation | re-dispatch grounding as a concurrent read-only fan-out — one read-only `change-reviewer` per design-bearing category, each scoped to its category and its own `design-findings-<category>.yaml`, joined before consolidation | autonomous |
| F13 | a Layer 1 linter ran from Garura's source or on the wrong layout, a violation outside `changed_paths` became a finding, memory was read by a path, the play linter ran without the Next-line map, or a missing or crashed tool was passed over or halted the run | re-run Layer 1 with the installed tools in `.garura/core/tools/` on the layout where the changed parts sit, keep as findings only the violations in `changed_paths` (count the rest as "there before"), get the Next-line map from `template-manager`, and record `not installed` or `did not run: <first error line>` for a tool that is missing or crashed | autonomous |
## Pause and Resume
Steps run top to bottom. On entry, resolve the PR from the branch, check the status
marker, skip completed steps, reset any in-progress step to pending, and continue. When the
gate resolves on, the decision gate (Step 5) is never auto-resolved on resume — if it has
not been answered, re-present and wait; when it resolves off, resume re-derives the
decision from the recommendation exactly as a first pass would. A re-run re-posts the
current decision rather than adding a conflicting second one.
## Compilation Metadata
| Field | Value |
|-------|-------|
| fingerprint | sha256:0205d7b77d832711125bf40ce6160b962a15749106c914a8a5ef68eecb8807f7 (of `reference/ice.md`) |
| compiled_by | edit-play (#466 Batch A; #467 Batch C; #484 scripted chain; #496 grounding fan-out; #536 Done means; #860 installed tools) |
| pipeline_position | end |
| workflow_structure | A (multi-agent; agentic assessment → concurrent read-only design-grounding fan-out (one agent per design-bearing category) → consolidation; config-resolved verdict gate; gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 2 (change-reviewer — assessment ×1, grounding ×N design-bearing categories in a concurrent batch; quality-auditor) |
| utility_agents | 1 (template-manager — the Next-line map before Step 2 and the close's templates; mechanical PR/host I/O runs in bundled scripts) |
| skills_reused | check-pr-quality; installed tools .garura/core/tools/lint-components and .garura/core/tools/lint-play |
| knowledge_consumed | through the memory tool: knowledge/review/ (category playbooks) and standards/rules/pr.md (severity taxonomy), copied once in Step 2; standards/rules/pipeline-next.md (the Next-line map, from template-manager, for the play linter); standards/rules/concurrent-fanout.md (Layer-2 fan-out); standards_order set |
| scripts | 7 (preflight.py — pre-flight facts; compute_verdict.py — recommended verdict over classified findings; check_stop_condition.py — Done-means gate; session_stamp.py — #463 identity stamp; platform_adapter.py — code-host adapter; fetch_pr_context.py — bind PR context; post_verdict.py — post decision + commit artifacts) |
| step_evals | 13 (SE-1…SE-13) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 13 (one per failure condition; F8 hands to the human gate, the rest autonomous) |
Files in this skill
- SKILL.md
- reference/ice.md
- scripts/check_stop_condition.py
- scripts/compute_verdict.py
- scripts/fetch_pr_context.py
- scripts/platform_adapter.py
- scripts/post_verdict.py
- scripts/preflight.py
- scripts/session_stamp.py
- scripts/test_fetch_pr_context.py
- stop-condition.yaml
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.