From c7fb27a384f3396a927158ad23ba46d8a1721271 Mon Sep 17 00:00:00 2001 From: Ruslan Korneev Date: Mon, 27 Jul 2026 17:08:12 +0200 Subject: [PATCH] feat(adw): the profile is the phase list, not a label on it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit workflow-triage picked a profile and the scripts honoured it — review.sh scaled the fan-out, gate.sh scaled the required lenses — but adw-run did not. Its phases were written unconditionally, so a `light` run still paid for the scout agent, the full artifact set, the groom agent passes and a stop at G2. The two skills also disagreed in writing: triage said `light` → G7 only, adw-run said "stop for the operator" with no mention of the profile. The orchestrator won, so `light` bought a cheaper groom and a cheaper review and nothing else. adw-run now carries a phase × profile table and runs one column. Below `standard`: no scout agent (read the files — the agent protects a context this task will not fill), no groom agent (`light` runs the one lens inline), no test-author agent (write the failing test yourself and do not touch it afterwards), and no G2 — a twelve-line plan is one the operator approves without reading, and a gate that gets rubber-stamped devalues the one that must not. Adds `superlight` for work below `light`: no groom pass at all, one check, one reviewer. It needs no change to gate.sh, because the gates already accept that shape — `gate.sh plan` wants four headers and one numbered check, `gate.sh evidence` wants one evidence file, and on a task this size three of those headers are one word long. What never scales down, at any profile: the worktree, the draft PR, `gate.sh green`, `gate.sh evidence`, and one reviewer. The workspace is script-driven and takes seconds; it is what makes a run revertible and visible, and cutting it saves nothing measurable. One reviewer is the cheapest independent look at a diff there is — without it a small run is a solo run awarding itself a green tick. Adds the floor, which nothing documented before: a typo, a version bump, a comment fix should not enter the loop at all. Triage reasons about blast radius, so it will honestly answer `superlight` for a one-liner — that is not a licence to spend a worktree and a PR on it. Below the floor the cost is the operator's attention, not the tokens: a PR that says nothing teaches them to stop reading PRs. Operator override: `arguments: [profile, ref]` + `argument-hint`, so `/adw-run light SM-12` names the profile and triage is skipped. Claude Code's `:` is a plugin namespace (`/my-plugin:review`), not an option separator — `/adw-run:light` would parse as a skill `light` in a plugin `adw-run` — so arguments are the mechanism, and an unpassed named argument expands to an empty string, which makes "nobody chose" a normal branch. - scripts/gate.sh: 'superlight' added to the profile alternation. Without it "**Profile:** superlight" matches the `light` inside the word and the run is groomed as light. Order in the alternation is irrelevant — ERE is leftmost-longest, so both alternatives start at the same offset and the longer one wins either way; verified by swapping them and watching the tests stay green. superlight also skips the GROOM_LOG.md requirement, since no pass was meant to happen. The blocker check still runs: a blocker stops the run at every profile. - scripts/review.sh: `superlight` resolves to the light fan-out rather than dying as unknown. - groom-harden, slice-review, task-plan, the AGENTS.md block, the dev-prompt template and README updated to four profiles. Tests: 5 new assertions. superlight passes G1 with no ledger at all, still fails on an open blocker, and is recognised by review.sh instead of being rejected by name. Verified non-vacuous by mutation — dropping `superlight` from gate.sh's alternation fails 2, dropping it from review.sh fails 1. Suite: 116 passed, 0 failed, 1 known gap, under bash 5.2 and 3.2. VERSION 2.1.0 → 2.2.0. Refs RUS-190, RUS-191 --- README.md | 27 +++++-- install.sh | 4 +- scripts/gate.sh | 22 ++++-- scripts/review.sh | 9 ++- skills/core/adw-run/SKILL.md | 101 +++++++++++++++++++++++---- skills/core/groom-harden/SKILL.md | 3 +- skills/core/slice-review/SKILL.md | 5 +- skills/core/task-plan/SKILL.md | 3 +- skills/core/workflow-triage/SKILL.md | 44 +++++++++--- templates/AGENTS.snippet.md | 5 +- templates/_dev-prompt-template.md | 2 +- tests/cases/gate-groom.sh | 17 +++++ tests/cases/review-profiles.sh | 8 ++- 13 files changed, 205 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ce62d1e..04c2a52 100644 --- a/README.md +++ b/README.md @@ -124,13 +124,26 @@ three are printed by every run, and `tests/run.sh` ends by naming the surface it ## Profiles — right-size the loop -`workflow-triage` picks from **blast radius**, not diff size, and can escalate mid-run. - -| | `light` | `standard` | `deep` | -| --- | --- | --- | --- | -| Groom | 1 pass | loop-until-dry, 2 lenses | loop-until-dry, all lenses | -| Review | 1 reviewer | 3 lenses + wildcard | all lenses + wildcard + judge | -| Human gates | G7 | G2 + G7 | G2 + G7 | +`workflow-triage` picks from **blast radius**, not diff size, and can escalate mid-run. The operator can +name one instead — `/adw-run superlight`, `/adw-run deep SM-12` — and then it is a decision, not a hint. + +| | `superlight` | `light` | `standard` | `deep` | +| --- | --- | --- | --- | --- | +| Scout | skip | skip | agent | agent, multi-angle | +| Groom | **none** | 1 lens, inline | 2 lenses | all lenses | +| Review | 1 reviewer | 1 reviewer | 3 lenses + wildcard + judge | all lenses + wildcard + judge | +| Human gates | G7 | G7 | G2 + G7 | G2 + G7 | + +The profile **is** the phase list — `adw-run` runs one column, not the whole loop with cheaper settings. +What never scales down: the worktree, the draft PR, `gate.sh green`, `gate.sh evidence`, and one +reviewer. A run with no independent look at the diff is a solo run awarding itself a green tick. + +`superlight` still writes `PLAN.md` and `VALIDATION.md`, because the gates read them — around a dozen +lines, three of the four required headers one word long. No gate is skipped or special-cased for it. + +**And there is a floor.** A typo, a version bump, a comment fix: do it in the session, run +`gate.sh green`, show the diff. Blast radius says `superlight` for a one-liner; that is not a reason to +spend a worktree and a PR on it. Below the floor the cost is the operator's attention, not the tokens. ## Agent identities = what they may not touch diff --git a/install.sh b/install.sh index 93fedd0..c96be20 100755 --- a/install.sh +++ b/install.sh @@ -25,7 +25,7 @@ set -uo pipefail SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -VERSION="2.1.0" +VERSION="2.2.0" SCRIPT_FILES="lib.sh gate.sh guard.sh engines.sh intake.sh review.sh setup-worktree.sh worktree-alloc.sh" die() { echo "[ai-tooling] ERROR: $*" >&2; exit 1; } @@ -53,7 +53,7 @@ cmd_list() { echo "Gates (scripts/ai/):" echo " gate.sh format|static|test|red|green|workspace|committed|groom|plan|evidence|ready|all" echo " guard.sh builder|test-author|reviewer|planner (phase file-scope, mechanical)" - echo " review.sh [validation] --profile light|standard|deep (lens fan-out + judge)" + echo " review.sh [validation] --profile superlight|light|standard|deep (fan-out + judge)" echo " intake.sh fetch |writeback --status|--comment|contract" echo " engines.sh candidates|probe --write|list|pick-review|diversity|run" echo " setup-worktree.sh · worktree-alloc.sh" diff --git a/scripts/gate.sh b/scripts/gate.sh index 15f5502..03978de 100644 --- a/scripts/gate.sh +++ b/scripts/gate.sh @@ -179,19 +179,31 @@ gate_groom() { adw_warn "G1 FAILED: open blocker in $oq — STOP, resolve with the operator." fail=1 fi - if [[ ! -f "$log" ]]; then - adw_warn "G1 FAILED: $log missing — run groom-harden at least once." - return 1 - fi # The lens set IS the coverage. Every lens gets one pass; a lens is repeated only if it found something # major, and then only that lens. Counting "quiet passes" instead rewards re-running the same questions: # on a real run, passes 5 and 6 produced three minors and no majors, for a third of the token budget. local lenses lens closed missing="" profile all_lenses all_lenses="$(adw_cfg LENSES contracts,failure-modes,adversary,meta | tr ',' ' ')" # The profile decides how much coverage this task earns; PLAN.md records it (workflow-triage). + # 'superlight' has to be IN the alternation, or "**Profile:** superlight" matches the 'light' inside it + # and the run is silently groomed as light. Order does not matter — ERE is leftmost-longest, so both + # alternatives start at the same offset and the longer one wins either way. profile="$(grep -m1 -iE '^\s*[-*]?\s*\*\*Profile:\*\*' ".tasks/$id/PLAN.md" 2>/dev/null \ - | grep -oiE 'light|standard|deep' | head -1 | tr '[:upper:]' '[:lower:]')" + | grep -oiE 'superlight|light|standard|deep' | head -1 | tr '[:upper:]' '[:lower:]')" profile="${profile:-$(adw_cfg DEFAULT_PROFILE standard)}" + + # superlight runs no groom pass, so there is no ledger to demand — but the blocker check above still + # applies, because a blocker found while planning stops the run at every profile. + if [[ "$profile" == "superlight" ]]; then + adw_log "G1: profile 'superlight' → no groom pass required" + (( fail )) || adw_log "G1 OK: no open blocker" + return $fail + fi + + if [[ ! -f "$log" ]]; then + adw_warn "G1 FAILED: $log missing — run groom-harden at least once." + return 1 + fi case "$profile" in light) lenses="$(printf '%s' "$all_lenses" | awk '{print $1}')" ;; standard) lenses="$(printf '%s\n' $all_lenses | grep -xE 'contracts|adversary' | tr '\n' ' ')" diff --git a/scripts/review.sh b/scripts/review.sh index e8ad56f..f7c6bd1 100755 --- a/scripts/review.sh +++ b/scripts/review.sh @@ -2,8 +2,9 @@ # scripts/ai/review.sh — review fan-out: one reviewer per rubric lens + a wildcard, run in parallel, # then a judge that dedupes and adversarially verifies. Core file. # -# review.sh [validation-path] [--profile light|standard|deep] [--lenses a,b,c] +# review.sh [validation-path] [--profile superlight|light|standard|deep] [--lenses a,b,c] # +# superlight → same as light: one reviewer is the floor, not something a profile can drop # light → 1 reviewer, no wildcard, no judge # standard → 3 lenses + wildcard, no judge (raw findings) # deep → all configured lenses + wildcard + judge (deduped, verified, ranked) @@ -34,12 +35,14 @@ profile="${profile:-$(adw_cfg DEFAULT_PROFILE standard)}" lenses="${lenses:-$(adw_cfg REVIEW_LENSES correctness,security,performance,architecture,tests)}" case "$profile" in - light) IFS=',' read -ra lens_list <<< "correctness"; wildcard=0; judge=0 ;; + # One reviewer is the floor. A run with none is a solo run awarding itself a green tick, and the + # cheapest independent look at a diff is one agent — so superlight buys a smaller loop, not no review. + superlight|light) IFS=',' read -ra lens_list <<< "correctness"; wildcard=0; judge=0 ;; # wildcard + judge stay on at standard: on the evidence, the majors come from the adversarial angle, # and an unjudged fan-out hands the operator six overlapping lists instead of one verified one. standard) IFS=',' read -ra all <<< "$lenses"; lens_list=("${all[@]:0:3}"); wildcard=1; judge=1 ;; deep) IFS=',' read -ra lens_list <<< "$lenses"; wildcard=1; judge=1 ;; - *) adw_die "unknown profile: $profile (light|standard|deep)" ;; + *) adw_die "unknown profile: $profile (superlight|light|standard|deep)" ;; esac rubric_file="" diff --git a/skills/core/adw-run/SKILL.md b/skills/core/adw-run/SKILL.md index cfacac1..2077fcc 100644 --- a/skills/core/adw-run/SKILL.md +++ b/skills/core/adw-run/SKILL.md @@ -1,6 +1,8 @@ --- name: adw-run -description: Drive the full AI Developer Workflow for one task end to end — intake, triage, scout, plan, groom passes, worktree, RED/GREEN implementation, validation, review fan-out — pausing only at the human gates (plan approval and final review). Use when the operator says "run the workflow", "прогони воркфлоу", "take this task through the loop", or hands over a ticket id and expects the whole loop. +description: Drive the AI Developer Workflow for one task end to end — intake, triage, scout, plan, groom passes, worktree, RED/GREEN implementation, validation, review fan-out — at the depth the task earns, pausing only at the human gates it earns. Use when the operator says "run the workflow", "прогони воркфлоу", "take this task through the loop", or hands over a ticket id and expects the whole loop. +arguments: [profile, ref] +argument-hint: "[superlight|light|standard|deep] [ticket-ref]" --- # adw-run — the orchestrator of one task through the loop @@ -9,41 +11,109 @@ You drive the whole loop for a single task. You do not write production code you enforce gates mechanically, and spawn specialist agents whose identities are defined by what they may not touch. Everything project-specific comes from `.tasks/_STACK.md`; never hardcode a command. -## Human gates (only two — protect them) +## Arguments + +`$profile` — `superlight` · `light` · `standard` · `deep`, when the operator names one. Empty means +they did not, and `workflow-triage` picks. A named profile is a decision, not a hint: record it and +run it. Escalate mid-run only for the reasons in `workflow-triage`, and say so when you do. + +`$ref` — the ticket id or the request itself, handed straight to `task-intake`. + +## Human gates (at most two — protect the ones the profile keeps) - **G2 · plan approval.** After the plan and validation criteria exist and grooming converged, present a compact summary and **stop for the operator**. This is the highest-leverage review point: a bad plan - line becomes hundreds of bad code lines. -- **G7 · merge.** After the review fan-out, present the ranked findings and **stop**. Merging is theirs. + line becomes hundreds of bad code lines. **`standard` and `deep` only** — below those the plan is a + dozen lines the operator would rubber-stamp, and a gate that gets rubber-stamped trains them to skim + the one that matters. +- **G7 · merge.** After the review, present the ranked findings and **stop**. Merging is theirs, at + every profile. Everywhere else you proceed autonomously — **except** on a `blocker`, which always stops the run. +## What each profile actually runs + +The profile is not a label on a run that happens anyway; it *is* the phase list. Read this table before +phase 1 and run only the column you are in. + +| Phase | `superlight` | `light` | `standard` / `deep` | +| --- | --- | --- | --- | +| 0 · triage | skip — profile is explicit | skip if explicit | `workflow-triage` | +| 1 · scout | skip, read the files yourself | skip, read the files yourself | `context-scout` agent | +| 2 · plan | `PLAN.md` ~12 lines + `VALIDATION.md`, 1 check | short but complete | full | +| 2 · `OPEN_QUESTIONS.md` / `CHECKLIST.md` | only if a blocker appears | yes | yes | +| 3 · groom | **skip** | one inline `contracts` pass, no agent | one agent per lens | +| 3 · `GROOM_LOG.md` | not created | created | created | +| 4 · **G2** | skip | skip | **stop** | +| 5 · workspace | worktree + branch + draft PR | same | same | +| 6 · RED | one test where `TEST_CMD` exists, else observation | optional | required | +| 7 · GREEN + `gate.sh green` | required | required | required | +| 8 · validate + `gate.sh evidence` | required | required | required | +| 9 · review | `review.sh --profile superlight` (1 reviewer) | 1 reviewer | fan-out + wildcard (+ judge) | +| 10 · **G7** | **stop** | **stop** | **stop** | + +Two rows never move, whatever the profile. **The workspace** — worktree, branch, draft PR — is +script-driven and takes seconds, and it is what makes a run revertible in one command and visible while +it happens; cutting it saves nothing measurable and loses everything if the session dies. **One +reviewer** — without it a small run is a solo run that awards itself a green tick, and one agent is the +cheapest independent look at a diff there is. + +`superlight` still writes `PLAN.md` and `VALIDATION.md` because the gates read them: `gate.sh plan` +wants the four headers (`Touches`, `Depends-on`, `Out of scope`, `Decisions locked`) and one numbered +check, `gate.sh evidence` wants one evidence file. On a task this size three of those headers are one +word long. Nothing in `gate.sh` is skipped or special-cased for it. + +## When not to run this at all + +The harness has a floor. A typo, a version bump, a comment fix, a one-line config change with an +observable result: do it in the session, run `bash scripts/ai/gate.sh green`, and let the operator read +the diff. `workflow-triage` will honestly answer `superlight` for a one-liner, because it reasons about +blast radius and a one-liner has none — that is not a recommendation to spend a worktree and a PR on it. + +Below the floor the cost is not the tokens, it is the operator's attention: a PR that says nothing +teaches them to stop reading PRs. + ## Phases ### 0 · Intake + triage 1. `task-intake` — turn the request (ticket / prompt / bug report) into `.tasks//` with the goal and a back-reference to its source. -2. `workflow-triage` — pick the profile (`light` / `standard` / `deep`) and record it in `PLAN.md`. - Never ask the operator for the profile; state your choice and its reason in one line. +2. `workflow-triage` — pick the profile and record it in `PLAN.md`. **Skip this when `$profile` was + given**; write the operator's choice into `PLAN.md` instead, marked as theirs. Never ask them to + choose when they did not: state your reasoning in one line and let them override. -### 1 · Scout (agent: `context-scout`) +### 1 · Scout (agent: `context-scout`) — `standard` / `deep` Read-only recon → provenance index. It burns its own context on grep/read/trace and returns conclusions, so your context stays clean and the plan is not anchored on the first file anyone opened. +Below `standard` you read the files yourself: the agent's whole value is protecting a context you are +not going to fill on a task this size. ### 2 · Plan (`task-plan` + `slice-verify`) `PLAN.md` (incl. `Touches` / `Depends-on` / scope OUT / decisions locked) and `VALIDATION.md` (every acceptance line → ≥1 runnable check, with where evidence lands). Uncertainties → `OPEN_QUESTIONS.md`. -### 3 · Groom passes (`groom-harden`, agent: `groom-hardener`) +On `superlight` this is a dozen lines: the four headers `gate.sh plan` requires, three of them one word +long, and one numbered check. Short is the point — but the headers are not optional, because a task with +no stated scope OUT is a task that grows silently. + +### 3 · Groom passes (`groom-harden`, agent: `groom-hardener`) — `light` and up **One fresh-context pass per lens** from `LENSES` in `_STACK.md` — the lens set is the coverage. Each pass reads `GROOM_LOG.md` first and appends its row. A lens that reports blockers or majors re-runs **after its findings are folded in**; minors are recorded and never trigger another pass. Check mechanically: `bash scripts/ai/gate.sh groom `. -### 4 · G2 — stop for the operator +`light` runs the first lens inline, in this session, without spawning `groom-hardener` — one lens does +not need a fresh context to stay honest, and the agent's cost is the context it rebuilds. +`superlight` skips grooming entirely; `gate.sh groom` requires no lens there and passes on the blocker +check alone. + +### 4 · G2 — stop for the operator — `standard` / `deep` Present: goal, scope IN/OUT, the 3–5 decisions locked, open `clarify` assumptions, the check list, the profile, and the estimated tier count. Wait. Fold their corrections back into the artifacts. +Skipped below `standard`. Not because the plan matters less, but because a twelve-line plan is one the +operator approves without reading, and a gate that gets rubber-stamped devalues the one that must not. + ### 5 · Workspace — before any code Worktree, branch, empty start commit, push, **draft PR** — in that order, before a single source file is touched. Then `bash scripts/ai/gate.sh workspace ` must exit 0. @@ -75,6 +145,10 @@ Tests first, written by an agent that may not touch source (`guard.sh test-autho not an import error. No test command configured → say so plainly, skip RED, and rely on observation checks; do not pretend TDD happened. +On `superlight` and `light` the test-author agent is overhead: write the one failing test yourself, then +run the same gate. The guard exists so an implementer cannot bend a test to fit; when you are about to +write both, the honest substitute is writing the test first and not touching it afterwards. + ### 7 · GREEN (agent: `builder`) Builder receives the plan, the tests, and the signatures — not the grooming history. It may not touch test paths (`guard.sh builder`). Gate: `bash scripts/ai/gate.sh green`. **Cap 3 fix attempts per failing gate**; @@ -92,12 +166,13 @@ view of the run — an uncommitted step is invisible to the operator and lost if Run every `VALIDATION.md` check, save evidence to `.tasks//evidence/`. Gate: `bash scripts/ai/gate.sh evidence `. Do not open a PR with red or unrun checks. -### 9 · Review fan-out +### 9 · Review Fill in the PR summary + how-to-verify (including the "Not verified" section), `gh pr ready`, and confirm with `bash scripts/ai/gate.sh ready ` — the run marks its own PR ready, not the operator. Then -`bash scripts/ai/review.sh .tasks//VALIDATION.md --profile ` — -lens reviewers in parallel, plus a wildcard hunting what those lenses cannot see, plus (on `deep`) a judge -that dedupes and adversarially verifies. In parallel, run `harness-improver` on the diff + `FRICTION.md`. +`bash scripts/ai/review.sh .tasks//VALIDATION.md --profile ` — pass the profile you +actually ran, so the fan-out matches it: one reviewer below `standard`, lens reviewers in parallel above, +plus a wildcard hunting what those lenses cannot see and a judge that dedupes and adversarially verifies. +In parallel, run `harness-improver` on the diff + `FRICTION.md`. ### 10 · G7 — stop for the operator Present the ranked, verified findings, the diversity label, **and what was never verified** — NOT RUN diff --git a/skills/core/groom-harden/SKILL.md b/skills/core/groom-harden/SKILL.md index f5de567..d7319ab 100644 --- a/skills/core/groom-harden/SKILL.md +++ b/skills/core/groom-harden/SKILL.md @@ -20,7 +20,8 @@ The ledger is what keeps passes moving forward instead of re-deriving the same t ## Lenses (one per pass, rotating) From `LENSES` in `.tasks/_STACK.md` — the four core ones, plus any domain lenses the project added. The -profile decides how many run: `light` → `contracts`; `standard` → `contracts` + `adversary`; `deep` → all. +profile decides how many run: `superlight` → none at all; `light` → `contracts`, run inline in the +session rather than by an agent; `standard` → `contracts` + `adversary`; `deep` → all. 1. **contracts** — every API/event/persisted contract the slice introduces or touches: source of truth, payload shape, writers/readers, validation, versioning, forbidden writes. Ownership ambiguity between diff --git a/skills/core/slice-review/SKILL.md b/skills/core/slice-review/SKILL.md index a4f1fbe..21b2b22 100644 --- a/skills/core/slice-review/SKILL.md +++ b/skills/core/slice-review/SKILL.md @@ -123,8 +123,9 @@ operator reads one short list instead of six overlapping ones. - Diversity is labelled honestly: `CROSS-ENGINE` (≥2 vendors) > `CROSS-MODEL` (one vendor, ≥2 models, e.g. `claude:opus` + `claude:sonnet`) > `DEGRADED` (one entry — shares the implementer's blind spots, never an independent verdict). -- Profiles: `light` = correctness only; `standard` = first three lenses + **wildcard + judge**; `deep` = - all lenses + wildcard + judge. Set by `workflow-triage`. The wildcard survives every profile above +- Profiles: `superlight` and `light` = correctness only; `standard` = first three lenses + **wildcard + + judge**; `deep` = all lenses + wildcard + judge. Set by `workflow-triage` or named by the operator. + One reviewer is the floor, not a step a profile can remove. The wildcard survives every profile above `light` because the findings that matter most come from outside the technical lenses — "what does a user who already paid see?" is not a correctness question. The judge survives because an unverified fan-out spends the operator's attention on duplicates and plausible-but-wrong findings. diff --git a/skills/core/task-plan/SKILL.md b/skills/core/task-plan/SKILL.md index 43c483a..77dfd21 100644 --- a/skills/core/task-plan/SKILL.md +++ b/skills/core/task-plan/SKILL.md @@ -17,7 +17,8 @@ File: `.tasks//PLAN.md` ### (a) Overview — 10-second what/why/scale - **Type:** slice / feature / tech-task / bug / refactor / audit. - **Intake:** where this came from (ticket URL, operator prompt, bug report) — set by `task-intake`. -- **Profile:** `light` / `standard` / `deep` + the one-line reason — set by `workflow-triage`. +- **Profile:** `superlight` / `light` / `standard` / `deep` + the one-line reason — set by `workflow-triage`, + or by the operator, in which case say so (`operator's choice`). - **Crux** (2–3 sentences): what we're doing and why. - **Affected:** which layers/modules — be concrete for this codebase (API, domain, data/persistence, jobs, client/UI, infra, tooling). diff --git a/skills/core/workflow-triage/SKILL.md b/skills/core/workflow-triage/SKILL.md index dfa1905..d9af4ea 100644 --- a/skills/core/workflow-triage/SKILL.md +++ b/skills/core/workflow-triage/SKILL.md @@ -1,6 +1,6 @@ --- name: workflow-triage -description: Decide how much workflow a task deserves — profile light / standard / deep — and record the choice with its reason in PLAN.md. Use at the start of every run, before planning. Prevents a typo fix from costing twenty agent runs and a persistence migration from getting one shallow review. +description: Decide how much workflow a task deserves — profile superlight / light / standard / deep — and record the choice with its reason in PLAN.md. Use at the start of every run, before planning, unless the operator named a profile themselves. Prevents a typo fix from costing twenty agent runs and a persistence migration from getting one shallow review. --- # workflow-triage — right-size the loop @@ -9,15 +9,23 @@ The full loop costs ~15–20 agent runs. Some tasks deserve it; most don't. Pick blast radius of being wrong**, not from the size of the diff. State the choice in one line and move on — do not ask the operator. +**The operator can name the profile** (`/adw-run deep SM-12`). When they have, do not run this skill to +second-guess them: record their choice in `PLAN.md` marked as theirs and start. Triage decides when +nobody decided. + ## Profiles -| | `light` | `standard` | `deep` | -| --- | --- | --- | --- | -| Scout | skip (read the files directly) | yes | yes, multi-angle | -| Groom lenses | `contracts` | `contracts` + `adversary` | every lens in `LENSES` | -| TDD | optional | yes where a test command exists | yes, RED gate enforced | -| Review | 1 reviewer | 3 lenses + **wildcard** + judge | all lenses + wildcard + judge | -| Human gates | G7 only | G2 + G7 | G2 + G7 | +| | `superlight` | `light` | `standard` | `deep` | +| --- | --- | --- | --- | --- | +| Scout | skip | skip (read the files directly) | yes | yes, multi-angle | +| Groom lenses | **none** | `contracts`, inline | `contracts` + `adversary` | every lens in `LENSES` | +| TDD | one test where a test command exists | optional | yes where a test command exists | yes, RED gate enforced | +| Review | 1 reviewer | 1 reviewer | 3 lenses + **wildcard** + judge | all lenses + wildcard + judge | +| Human gates | G7 only | G7 only | G2 + G7 | G2 + G7 | + +Every profile keeps the worktree, the draft PR, `gate.sh green` and `gate.sh evidence`. What scales is +how much thinking happens *before* the code and how many angles look at it *after* — not whether the +work is tracked or verified. **The wildcard reviewer and the judge stay on at `standard`.** Measured on a real run: every major finding came from an adversarial angle — the grooming `adversary` lens, and the review pass that asked "what does @@ -43,15 +51,29 @@ Go **light** when *all* hold: - the acceptance is observable in a single check - an experienced engineer would review it in under two minutes +Go **superlight** when `light` holds *and* there is nothing for an adversarial reader to find: the +change is self-contained, its acceptance is one observation, and being wrong is noticed immediately +rather than discovered later. A copy change, a config value with a visible effect, a small fix in one +function nobody else calls. Grooming a change like that produces minors about the grooming. + Otherwise **standard**. When genuinely torn, take the heavier one: an unnecessary review pass costs tokens, a missed contract bug costs a migration. +**Below `superlight` there is no profile, there is no run.** A typo, a version bump, a comment: do it +in the session, run `gate.sh green`, show the diff. Triage answers on blast radius, and a one-liner has +none — that is not a licence to spend a worktree and a PR on it. See the floor in `adw-run`. + ## Escalation and de-escalation The profile is not frozen. Escalate mid-run and say why: - grooming surfaces an undefined contract or a trust boundary → `standard` → `deep` - the diff grows past its planned scope → escalate one level - a review round finds a blocker → next round runs `deep` +- a `superlight` task turns out to touch a second caller, or its acceptance needs more than one + observation → `light` or `standard`, before writing code + +An operator-named profile is escalated for the same reasons and no others — tell them at the moment you +do it, with the trigger. Silently upgrading their choice is how a "quick fix" becomes an afternoon. De-escalate only before implementation starts, never to avoid a failing gate. @@ -63,6 +85,12 @@ One line in `PLAN.md` § Overview: **Profile:** deep — touches the reward contract + a profile schema migration (irreversible on rollback). ``` +When the operator named it, say so — the line records who decided, not just what: + +```markdown +**Profile:** superlight — operator's choice. +``` + Plus a `Profile changed:` line under `Decisions locked` if it moves mid-run. ## Do not diff --git a/templates/AGENTS.snippet.md b/templates/AGENTS.snippet.md index 2bc544a..c653172 100644 --- a/templates/AGENTS.snippet.md +++ b/templates/AGENTS.snippet.md @@ -59,7 +59,10 @@ the wrong place. `GROOM_LOG.md`, `evidence/`, `FRICTION.md` as the work warrants. `.tasks/` is **committed** — it is the provenance record and how a fresh session resumes after compaction. - A `blocker` in `OPEN_QUESTIONS.md` is a **STOP** signal: surface it and pause. Never guess past it. -- Profile (`light`/`standard`/`deep`) is chosen from blast radius by `workflow-triage`, not from diff size. +- Profile (`superlight`/`light`/`standard`/`deep`) is chosen from blast radius by `workflow-triage`, not + from diff size — or named by the operator (`/adw-run light SM-12`), in which case it is a decision. + The profile is the phase list: below `standard` there is no scout, no groom agent and no G2 stop. The + worktree, the draft PR, `gate.sh green`, `gate.sh evidence` and one reviewer never scale down. ### Agent identities are defined by what they may not touch diff --git a/templates/_dev-prompt-template.md b/templates/_dev-prompt-template.md index f2fca49..3408ad6 100644 --- a/templates/_dev-prompt-template.md +++ b/templates/_dev-prompt-template.md @@ -6,7 +6,7 @@ session gets no prior conversation. ``` Implement (). Plan: .tasks//PLAN.md. Rules: AGENTS.md. Check kit: .tasks/_STACK.md. -Profile: . +Profile: . BRANCH: - (worktree: yes) diff --git a/tests/cases/gate-groom.sh b/tests/cases/gate-groom.sh index e4d9c1d..2e50f73 100755 --- a/tests/cases/gate-groom.sh +++ b/tests/cases/gate-groom.sh @@ -64,4 +64,21 @@ MD check 'open blocker → fail even with every lens clean' 1 gate "$repo" groom t-1 check_out 'and it points at the file' 'OPEN_QUESTIONS.md' gate "$repo" groom t-1 +# ── superlight: no lens is required, and there is no ledger to demand ───────── +# 'light' is a substring of 'superlight', so a profile parser that does not know the longer word reads +# every superlight run as light and demands a groom pass that was never meant to happen. +task "$repo" t-2 +plan_with_profile superlight > "$repo/.tasks/t-2/PLAN.md" +check 'superlight: no GROOM_LOG.md at all → pass' 0 gate "$repo" groom t-2 +check_out 'and it says no pass was required' 'no groom pass required' gate "$repo" groom t-2 + +cat > "$repo/.tasks/t-2/OPEN_QUESTIONS.md" <<'MD' +## Blockers + +1. **Which store owns the balance?** Nobody could say. + +## Clarify +MD +check 'superlight: a blocker still stops the run' 1 gate "$repo" groom t-2 + done_tests diff --git a/tests/cases/review-profiles.sh b/tests/cases/review-profiles.sh index c95ec60..d0ade77 100755 --- a/tests/cases/review-profiles.sh +++ b/tests/cases/review-profiles.sh @@ -11,7 +11,13 @@ BASE_BRANCH=main INI check 'unknown profile → misuse (exit 2)' 2 review "$repo" 1 --profile bogus -check_out 'and it lists the profiles that exist' 'light|standard|deep' review "$repo" 1 --profile bogus +check_out 'and it lists the profiles that exist' 'superlight|light|standard|deep' \ + review "$repo" 1 --profile bogus + +# superlight is a real profile, not an unknown one: it must get past profile resolution and fail on the +# next thing (the missing rubric), never on its own name. +check_out 'superlight is recognised, and stops at the rubric instead' 'rubric' \ + review "$repo" 1 --profile superlight # Without the rubric there is nothing to review against, and a reviewer with no rubric returns # plausible prose. Refusing is the only safe answer.