@ericrisco/rsc 4.0.0 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,13 @@
1
1
  #!/bin/sh
2
2
  # task-brief PLAN_FILE N
3
- # Extracts task number N from a plan/tasks markdown file into a brief file in the
4
- # sdd-workspace dir, and prints its path.
3
+ # Extracts task N from a plan/tasks markdown file into a brief file in the sdd-workspace dir, and
4
+ # prints its path. N may be written 2, 002 or T002: ids compare as numbers.
5
5
  #
6
- # A task starts at a heading matching: ### T<N> / ### T<N>( / ### T<N> — / ## T<N>
7
- # and ends just before the next ### T / ## heading.
6
+ # Two task shapes are read, because `tasks` prescribes a table and older plans used headings:
7
+ # - a table row `| T002 | ... |` → the table's header, its separator and that row;
8
+ # - a heading `### T2 …` / `## T002 …` → the block up to the next ## / ### heading;
9
+ # - a bold block `**T002 — Interfaces**` → that line and what follows up to a blank line.
10
+ # Both are printed when both exist (the row is the contract, the heading its detail).
8
11
  # If task N is not found, exits nonzero with a clear message.
9
12
 
10
13
  # Resolve the directory of this script portably.
@@ -13,7 +16,7 @@ _SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
16
  usage() {
14
17
  printf 'Usage: task-brief <PLAN_FILE> <N>\n' >&2
15
18
  printf ' PLAN_FILE path to the tasks markdown file\n' >&2
16
- printf ' N task number to extract (e.g. 2 for T2)\n' >&2
19
+ printf ' N task id to extract: 2, 002 or T002\n' >&2
17
20
  exit 1
18
21
  }
19
22
 
@@ -23,41 +26,52 @@ if [ $# -lt 2 ]; then
23
26
  fi
24
27
 
25
28
  PLAN_FILE="$1"
26
- N="$2"
29
+ N="${2#T}"
30
+ N="${N#t}"
27
31
 
28
32
  if [ ! -f "$PLAN_FILE" ]; then
29
33
  printf 'error: plan file not found: %s\n' "$PLAN_FILE" >&2
30
34
  exit 1
31
35
  fi
32
36
 
33
- # Validate N is a positive integer.
34
37
  case "$N" in
35
38
  ''|*[!0-9]*)
36
- printf 'error: N must be a positive integer, got: %s\n' "$N" >&2
39
+ printf 'error: N must be a task number (2, 002 or T002), got: %s\n' "$2" >&2
37
40
  exit 1
38
41
  ;;
39
42
  esac
40
43
 
41
- # Use awk to extract the task block.
42
- # Match start: a heading line (## or ###) followed by T<N> then space, (, —, or end of line.
43
- # Match end: the next heading line starting with ## (which includes ###).
44
- # Exit code: 0 if found, 1 if not found.
45
44
  CONTENT="$(awk -v n="$N" '
46
- BEGIN { printing=0; found=0 }
45
+ function idnum(s) { sub(/^[Tt]/, "", s); return s + 0 }
46
+ BEGIN { n = n + 0; printing = 0; bold = 0; found = 0; head = ""; sep = "" }
47
+ # Table rows: remember the latest header + separator, print the matching row.
48
+ /^\|/ {
49
+ split($0, cell, "|"); id = cell[2]; gsub(/[[:space:]]/, "", id)
50
+ if (id ~ /^[Tt][0-9]+$/) {
51
+ if (idnum(id) == n) { if (!printing) { if (head != "") print head; if (sep != "") print sep }; print; found = 1; next }
52
+ } else if ($0 ~ /^\|[-:| ]+\|[[:space:]]*$/) { sep = $0 }
53
+ else { head = $0; sep = "" }
54
+ if (!printing) next
55
+ }
56
+ # A bold block `**T004 — Interfaces**` (what `tasks` writes under the table): the line and every
57
+ # line after it up to the first blank one. It is the task-local contract an isolated worker needs.
58
+ bold && /^[[:space:]]*$/ { bold = 0 }
59
+ bold { print; next }
60
+ /^\*\*[Tt][0-9]+/ {
61
+ t = $0; sub(/^\*\*/, "", t); sub(/[^Tt0-9].*$/, "", t)
62
+ if (idnum(t) == n) { bold = 1; found = 1; print ""; print; next }
63
+ }
47
64
  /^#{2,3}[[:space:]]/ {
48
- # Check if this heading is the target task T<n>
49
- line=$0
50
- pat="^#{2,3}[[:space:]]+T" n "([[:space:](]|[-]|$)"
51
- if (match(line, pat)) {
52
- printing=1; found=1; print; next
65
+ if (printing) { printing = 0 }
66
+ if (match($0, /^#{2,3}[[:space:]]+[Tt][0-9]+([[:space:](]|-|$)/)) {
67
+ t = $0; sub(/^#{2,3}[[:space:]]+/, "", t); sub(/[^Tt0-9].*$/, "", t)
68
+ if (idnum(t) == n) { printing = 1; found = 1; print ""; print; next }
53
69
  }
54
- # Any other ## / ### heading stops printing
55
- if (printing) { exit }
56
70
  }
57
71
  { if (printing) print }
58
72
  END { if (!found) exit 1 }
59
73
  ' "$PLAN_FILE")" || {
60
- printf 'error: task T%s not found in %s\n' "$N" "$PLAN_FILE" >&2
74
+ printf 'error: task T%s not found in %s (looked for a "| T%s |" table row and a "### T%s" heading)\n' "$N" "$PLAN_FILE" "$N" "$N" >&2
61
75
  exit 1
62
76
  }
63
77
 
@@ -71,7 +85,8 @@ SCRATCH="$(sh "$_SCRIPT_DIR/sdd-workspace")"
71
85
 
72
86
  # Derive a readable unique name from the plan file basename and task number.
73
87
  PLAN_BASENAME="$(basename "$PLAN_FILE" .md)"
74
- OUT="$SCRATCH/brief-${PLAN_BASENAME}-T${N}.txt"
88
+ NUM="$(printf %s "$N" | sed "s/^0*//")"
89
+ OUT="$SCRATCH/brief-${PLAN_BASENAME}-T${NUM:-0}.txt"
75
90
 
76
91
  printf '%s\n' "$CONTENT" > "$OUT"
77
92
  printf '%s\n' "$OUT"
@@ -85,7 +85,7 @@ Three of them carry the weight and fail quietly:
85
85
 
86
86
  Write `02-DOCS/wiki/sdd/plans/<slug>.md`, `<slug>` matching the spec's slug exactly — one plan per
87
87
  spec, same name, because that is how `tasks`, `analyze` and `implement` find it. Then index it in
88
- `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a pointer). If a plan for
88
+ `02-DOCS/wiki/index.md` (the Knowledge map) — never the root `CLAUDE.md`. If a plan for
89
89
  this slug already exists, update it in place and note what changed — never fork a `-v2`.
90
90
 
91
91
  ## Model tier — `heavy` (opt-in routing)
@@ -46,4 +46,4 @@ capability:
46
46
  - "Testing strategy maps each acceptance criterion to a test level (unit/contract/integration/e2e), states what each asserts and what it mocks (e.g. the payments provider), and defers the test runner/tooling to the stack skill"
47
47
  - "Risks section is ranked with trigger + impact + mitigation per risk (e.g. payments timeout, double-reservation race); does NOT claim zero risk"
48
48
  - "Stays stack-neutral above the seam — no framework route decorators, ORM session code, or test-runner flags in the plan"
49
- - "Ends by pointing to the tasks phase and notes indexing the plan in the root CLAUDE.md Knowledge map"
49
+ - "Ends by pointing to the tasks phase and notes indexing the plan in 02-DOCS/wiki/index.md (the Knowledge map), never the root CLAUDE.md"
@@ -145,8 +145,8 @@ unit / contract / integration / e2e. Tooling is the stack skill's job — name t
145
145
  defend it, not to enumerate every option without choosing.
146
146
  - **Empty section = defect.** Every one of the seven must say something real before `tasks` runs.
147
147
  If a section genuinely has no content (e.g. no migration), write "none — <why>", not silence.
148
- - **Index it.** After writing, add the plan to the root `CLAUDE.md` `## Knowledge map` so the
149
- harness wiki and later phases can find it.
148
+ - **Index it.** After writing, add the plan to `02-DOCS/wiki/index.md` (the Knowledge map) so the
149
+ harness wiki and later phases can find it. Never edit the root `CLAUDE.md` — that is `harness`'s, and tier 2.
150
150
  - **Global Constraints carry the verbatim values.** §0 is not a summary of §1 — §1 explains *which*
151
151
  constraints shape the design and *why*; §0 lists the exact values a blind implementer and the
152
152
  reviewer must honor. A value that lives only in prose ("use the project's naming rule") is a value
@@ -12,7 +12,7 @@ origin: risco
12
12
  Tests prove the code works, not that it does what was asked. Review answers that other question: **is the diff correct, well-scoped and faithful to the spec — or did it pass for the wrong reasons?** In ASDD (`../asdd/SKILL.md`) it runs in two modes:
13
13
 
14
14
  - **Per task** — called from `implement`'s loop after each task's self-check. Depth comes from `npx @ericrisco/rsc risk [--base <ref>] [paths…]` (same path table as the sello, works with the sello off): **tier 0** → no review; **tier 1** → one fresh-context reviewer (a subagent that never saw the implementation conversation); **tier 2** → the three-refuter panel below, and the branch will wait for human approval at ship.
15
- - **Closing review** — once, before `ship`: the whole branch against the spec. Forty individually-correct tasks can still fail to be the feature together: a criterion nobody owned, two tasks that each assumed the other handled an edge, scope that crept in a slice at a time. Read the spec's acceptance criteria and "must respect" list against `git diff <base>...HEAD`, not the task reviews.
15
+ - **Closing review** — once, before `ship`: the whole branch against the spec. Forty individually-correct tasks can still fail to be the feature together: a criterion nobody owned, two tasks that each assumed the other handled an edge, scope that crept in a slice at a time. Read the spec's acceptance criteria and "must respect" list against `git diff <base>...HEAD`, not the task reviews. Its record — tier, counts, verdict, each finding with its outcome — goes to `02-DOCS/wiki/sdd/reviews/<slug>.md`.
16
16
 
17
17
  This skill owns **two roles that share one discipline**:
18
18
 
@@ -175,21 +175,28 @@ Two extra finding classes the four inputs unlock, neither reachable from a diff:
175
175
  report one defect three ways, and a lens hunting for something can overstate what it found. Two
176
176
  steps, in this order:
177
177
 
178
- 1. **Group (deterministic).** Each lens ends its report with a ` ```json findings ` block. Save the
179
- three reports and run `npx @ericrisco/rsc review consolidate <reports…>`. It merges findings on
180
- the same file within 3 lines, or quoting the same evidence; keeps the highest severity and every
181
- lens that saw it; and downgrades a blocker or should-fix with no `file:line` or failure scenario to
182
- a question. Several lenses on one finding is a reason to look first, never a reason to raise its
183
- severity.
184
- 2. **Verify (judgement).** For each finding it marks `verify`, dispatch the **`finding-verifier`**
185
- agent with that one finding plus the same four inputs, in parallel. It starts from "this is false"
186
- and returns **CONFIRMED** (reproduced or traced, with the proof), **REFUTED** (cites the guard that
187
- makes it impossible), or **UNPROVEN** (becomes a question). It never gets the lens's reasoning.
178
+ 1. **Group (deterministic).** Each lens ends its report with a ` ```json findings ` block — an
179
+ array of objects in exactly this shape (give it to every reviewer verbatim):
180
+
181
+ ```json
182
+ {"severity":"blocker|should-fix|nit|question","file":"path","line":N,"claim":"…","failure_scenario":"concrete input → wrong result","evidence":"quoted code","class":"bug class"}
183
+ ```
184
+
185
+ Save the reports and run `npx @ericrisco/rsc review consolidate <reports…>`. It merges findings
186
+ on the same place that say the same thing, or that quote the same evidence; keeps the highest
187
+ severity and every lens that saw it; and **downgrades a blocker or should-fix with no
188
+ `file:line` or no `failure_scenario` to a question**. Several lenses on one finding is a reason
189
+ to look first, never a reason to raise its severity.
190
+ 2. **Verify (judgement).** Consolidate marks `verify` on **every blocker and every should-fix**.
191
+ For each, dispatch the **`finding-verifier`** agent with that one finding plus the same four
192
+ inputs, in parallel. It starts from "this is false" and returns **CONFIRMED** (reproduced or
193
+ traced, with the proof), **REFUTED** (cites the guard that makes it impossible), or **UNPROVEN**
194
+ (becomes a question). It never gets the lens's reasoning.
188
195
 
189
196
  Report the consolidated count honestly ("11 reported → 4 unique → 2 confirmed"), and list what was
190
197
  refuted and why: a refuted finding hidden from the author is a review nobody can audit. At tier 1
191
- (one reviewer) there is nothing to group, but a blocker still goes to `finding-verifier` before it
192
- blocks.
198
+ (one reviewer) there is nothing to group, but run `review consolidate` on its one report anyway:
199
+ it applies the downgrade, and every blocker and should-fix still goes to `finding-verifier`.
193
200
 
194
201
  **A finding blocks only if it survives all three filters** — no exceptions, and eagerness to
195
202
  find something is not evidence:
@@ -213,9 +220,10 @@ A review loop that never converges is worse than none: Anthropic saw a single PR
213
220
  Four rules, enforced by `npx @ericrisco/rsc review ledger` (`.rsc/review-ledger.jsonl`) rather
214
221
  than by memory:
215
222
 
216
- 1. **Max 3 rounds per task.** Start each round with `review ledger round <slug>/<task>`; it exits non-zero
217
- on the 4th. That is not "try harder": rethink the approach, or park the task in the decision
218
- queue and continue with independent work.
223
+ 1. **Max 3 rounds per task.** Call `review ledger round <slug>/<task>` before **every** review of
224
+ the task, the first included: round 1 is the task's first review, not its first fix. It exits
225
+ non-zero on the 4th. That is not "try harder": rethink the approach, or park the task in the
226
+ decision queue (a `blocking` entry) and continue with independent work.
219
227
  2. **From round 2, re-review only the new diff** — what changed since the last round, not the task
220
228
  again from scratch.
221
229
  3. **Flag the bug class, not the line.** "Every handler in this file trusts `req.params.id`" — so
@@ -225,9 +233,13 @@ than by memory:
225
233
  normalized title — comes back marked `repeat`; it is never re-raised as new. A repeat means the
226
234
  last fix missed the class, so say that instead.
227
235
 
228
- **Every finding closes with an outcome**: `review ledger outcome <id> fixed|false|intentional|irrelevant`.
229
- `review ledger stats` then shows totals per outcome, the false rate and mean rounds per task — the
230
- only way to tell whether a change to the reviewer made it better or worse.
236
+ **Every finding closes with an outcome**: `review ledger outcome <id> fixed|false|intentional|irrelevant|parked`
237
+ — `parked` when the finding's task went to the decision queue (neither fixed nor dismissed; a human
238
+ decides). `review ledger stats` then shows totals per outcome, the false rate and mean rounds per
239
+ task — the only way to tell whether a change to the reviewer made it better or worse.
240
+
241
+ The ledger is **local**: it lives in `.rsc/` and is never committed, so it does not travel between
242
+ machines or clones. What lasts is what you write from it — review-evals cases and rules (below).
231
243
 
232
244
  ---
233
245
 
@@ -2,7 +2,7 @@
2
2
  name: sdd-init
3
3
  description: "Use when calibrating an existing repo for the rsc SDD chain: detect stack, package manager, test runners and apply/verify commands, refresh the skill registry, and write the runtime config at 02-DOCS/wiki/sdd/config.yaml. NOT the dispatcher that routes SDD phases (that is `asdd`), NOT first-contact user/workspace bootstrap (that is `init`)."
4
4
  tags: [sdd, init, config, testing, registry]
5
- recommends: [sdd, specify, implement, verify]
5
+ recommends: [asdd, constitution, specify, verify]
6
6
  profiles: [core, full]
7
7
  origin: risco
8
8
  ---
@@ -28,13 +28,15 @@ Read-only first:
28
28
 
29
29
  If `02-DOCS/` does not exist, create only the `02-DOCS/wiki/sdd/` path needed for the config. Do not run full harness scaffolding unless the user asked for `harness`.
30
30
 
31
+ **Empty repo — nothing to detect?** Then there is nothing to calibrate yet: `constitution` runs first and decides the stack, and this skill calibrates against that decision (or re-runs once the first manifest exists). Never invent runners for an empty tree.
32
+
31
33
  ## Preflight Choices
32
34
 
33
35
  Ask only when the answer changes behavior; explain each trade-off in one line, in the register `technical_level` sets.
34
36
 
35
37
  | Setting | Default | Options |
36
38
  | --- | --- | --- |
37
- | `execution_mode` | `interactive` | `interactive` pauses at review-risk gates; `automatic` chains phases until a blocker/risk appears. |
39
+ | `execution_mode` | `interactive` | `interactive` pauses at review-risk gates; `automatic` chains phases until a blocker/risk appears. `sdd.execution_mode: automatic` is the switch `asdd`'s autopilot reads. |
38
40
  | `artifact_store` | `02-DOCS/wiki/sdd` | Keep RSC artifacts in `02-DOCS`; do not create an `openspec/` parallel tree. |
39
41
  | `review_budget.line_budget` | `400` | Lower for solo tight review; higher only with explicit approval. |
40
42
  | `delivery_strategy.default` | `ask-on-risk` | `ask-on-risk`, `single-pr`, `autochain`, `exception`. |
@@ -158,7 +160,7 @@ End with the standard SDD result envelope:
158
160
  "status": "complete",
159
161
  "executive_summary": "SDD config calibrated and registry refreshed.",
160
162
  "artifact": "02-DOCS/wiki/sdd/config.yaml",
161
- "next_recommended": "sdd",
163
+ "next_recommended": "constitution|specify",
162
164
  "risk": "low",
163
165
  "skill_resolution": {
164
166
  "used": ["sdd-init"],
@@ -186,4 +188,4 @@ End with the standard SDD result envelope:
186
188
 
187
189
  ## Next
188
190
 
189
- After `sdd-init`, return to `asdd`. If no spec exists, route to `specify`. If the work is ambiguous or architectural, write a proposal first under `02-DOCS/wiki/sdd/proposals/`.
191
+ After `sdd-init`, return to `asdd` with `next_recommended` set to the next phase: `constitution` if there is none (or it is `status: draft`), else `specify` when no spec exists. If the work is ambiguous or architectural, write a proposal first under `02-DOCS/wiki/sdd/proposals/`.
@@ -13,16 +13,16 @@ Ship is the **last step** of ASDD's closing stage (`../asdd/SKILL.md`), after th
13
13
 
14
14
  This skill owns one decision and its safe execution: **how does this work integrate?** It does not write code, run test gates, or read the diff for defects — those phases already happened. It is the **close, not the open**: creating the isolated branch or worktree is `../worktrees/SKILL.md`, taking the gates green is `../verify/SKILL.md`, reading the diff adversarially is `../review/SKILL.md`, and putting merged code onto a server is `../deployment/SKILL.md`.
15
15
 
16
- ## The hard rule: git authorship is Eric, never Claude
16
+ ## The hard rule: git authorship is the responsible person, never an AI
17
17
 
18
- > **Every commit and every PR ships under Eric's name. No `Co-Authored-By: Claude`. No `Co-Authored-By` for any AI. No "🤖 Generated with Claude Code" footer. No "made by an agent" line in the PR body. Nothing that attributes the work to a tool.**
18
+ > **Every commit and every PR ships under the name of the responsible person — the configured git user. No `Co-Authored-By: Claude`. No `Co-Authored-By` for any AI. No "🤖 Generated with Claude Code" footer. No "made by an agent" line in the PR body. Nothing that attributes the work to a tool.**
19
19
 
20
- This is absolute — not a preference to weigh against convenience — because a commit is a permanent, published claim about who wrote something. The work is Eric's; the agent is a tool he used, like an editor or a compiler, and you do not credit the compiler in the commit message. Once a forged trailer is pushed it is in everyone's history and only a rewrite removes it.
20
+ This is absolute — not a preference to weigh against convenience — because a commit is a permanent, published claim about who wrote something. The work is the responsible person's; the agent is a tool they used, like an editor or a compiler, and you do not credit the compiler in the commit message. Once a forged trailer is pushed it is in everyone's history and only a rewrite removes it.
21
21
 
22
22
  Concretely, before any commit or PR:
23
23
 
24
- - **Never** pass `--author` to set a non-Eric author. The repo's configured `user.name` / `user.email` (Eric's) is the author and committer.
25
- - If `git config user.email` is unset or clearly not Eric's, **stop and ask** which identity to commit under — do not guess, and do not substitute an agent identity.
24
+ - **Never** pass `--author` to set another author. The repo's configured `user.name` / `user.email` — the responsible person — is the author and committer.
25
+ - If `git config user.email` is unset or is an agent/bot identity, **stop and ask** which identity to commit under — do not guess, and do not substitute an agent identity.
26
26
  - If you find a Claude/AI trailer in a commit you are about to push (e.g. left over from an upstream tool), **strip it** before the branch lands.
27
27
 
28
28
  Verify it after writing the commit. A non-empty match is a blocker: amend and re-check before the branch goes anywhere.
@@ -48,9 +48,9 @@ Run this before presenting the landing options. Any unchecked item is a stop —
48
48
  - [ ] **On a feature branch, not the trunk** — `git rev-parse --abbrev-ref HEAD` is not `main`/`master`. If work landed directly on the trunk, that is its own problem; flag it, don't paper over it.
49
49
  - [ ] **Rebased / up to date with the base, and tested there** — branch is on top of latest `main`; conflicts resolved locally, not punted to the merge; the checks re-ran on the rebased branch.
50
50
  - [ ] **Risk tier known** — `npx @ericrisco/rsc risk --base main` run; its tier, classes and `human approval` line go into the PR's Risk section.
51
- - [ ] **Decision queue empty** — no task parked for a human decision in this branch, or the PR says which ones remain.
51
+ - [ ] **No `blocking` entry in the decision queue** (`## Decision queue` in `progress/<slug>.md`) — or the PR says which remain and it does not self-merge. `review-after` entries never block; they are listed in the PR body (or the parked summary) for the human.
52
52
  - [ ] **No secrets in the diff** — scan the staged/branch diff for keys, tokens, `.env` values (`git diff main... | grep -iE 'api[_-]?key|secret|password|token|BEGIN .*PRIVATE KEY'`). A hit is a blocker; remove it, and rotate it if it was ever pushed.
53
- - [ ] **Authorship is Eric** — `git config user.email` is Eric's; no AI trailer in any commit on the branch (run the grep above across `main..HEAD`).
53
+ - [ ] **Authorship is the responsible person** — `git config user.email` is the configured human; no AI trailer in any commit on the branch (run the grep above across `main..HEAD`).
54
54
  - [ ] **Commit history is intelligible** — squashed or organized so the history reads as deliberate, not "wip wip fix fix".
55
55
 
56
56
  ```bash
@@ -58,7 +58,7 @@ Run this before presenting the landing options. Any unchecked item is a stop —
58
58
  echo "branch: $(git rev-parse --abbrev-ref HEAD)"
59
59
  echo "clean?: $([ -z "$(git status --short)" ] && echo yes || echo NO-dirty)"
60
60
  echo "behind: $(git rev-list --count HEAD..origin/main 2>/dev/null || echo '?') commits behind origin/main"
61
- git log main..HEAD --format='%an <%ae>' | sort -u # authors on this branch — expect only Eric
61
+ git log main..HEAD --format='%an <%ae>' | sort -u # authors on this branch — expect only the configured user
62
62
  git diff main...HEAD | grep -icE 'api[_-]?key|secret|password|token|BEGIN .*PRIVATE KEY' \
63
63
  | sed 's/^/secret-hits: /'
64
64
  npx @ericrisco/rsc risk --base main # tier, classes, reviewers, human approval
@@ -125,7 +125,13 @@ Stacked PR / feature-track support still fits inside the three landing options:
125
125
 
126
126
  ## Executing each option
127
127
 
128
- **Nothing below runs before the user picks an option.** Merging, pushing, opening a PR and deleting a branch are outward or irreversible — they change shared history or publish to a forge, and no later phase undoes them. A recommendation is not a yes; wait for one.
128
+ **Nothing below runs before an option is picked.** Merging, pushing, opening a PR and deleting a branch are outward or irreversible — they change shared history or publish to a forge, and no later phase undoes them. Interactively, the user picks: a recommendation is not a yes; wait for one.
129
+
130
+ **In autopilot** (the up-front yes, or `execution_mode: automatic` in `config.yaml`) the agent picks by these rules, and there is always a legal path:
131
+
132
+ - **A remote exists** → option 2, the PR. Self-merge only when `rsc risk` says tier 0-1, CI is green and **no `blocking` entry** is open; otherwise leave the PR open for the human. List every `review-after` entry in the PR body.
133
+ - **No remote** → option 3, **park**: leave the branch committed, write the explainer, the open queue entries and the exact merge command for the human (`git switch main && git merge --no-ff feature/<slug>`) into `progress/<slug>.md`, and stop.
134
+ - **Never merge locally around the branch guard.** When `rsc main status` says `closed: true`, the guard denies a local merge — that is the signal to park, not to find another command. Autopilot never discards.
129
135
 
130
136
  ### Option 1 — direct merge
131
137
 
@@ -177,14 +183,15 @@ Implements `02-DOCS/wiki/sdd/specs/<slug>.md`. <the user-facing reason>
177
183
  ## Risk
178
184
 
179
185
  Tier <0|1|2> (`rsc risk`): <classes touched>. Human approval: <required|not required>.
186
+ Decision queue: <open `blocking` entries, or none> · Review after: <each `review-after` entry, or none>.
180
187
 
181
188
  ## How to revert
182
189
 
183
190
  <`git revert <merge-sha>` / the squash commit; plus anything a revert alone does not undo — a migration, a flag, data written>
184
191
  ```
185
192
 
186
- **Merge policy — by risk tier.** Tier 0-1, CI green and the decision queue empty → you may
187
- self-merge (autopilot): `gh pr merge --squash --delete-branch` (or `--merge` to preserve the
193
+ **Merge policy — by risk tier.** Tier 0-1, CI green and no `blocking` entry in the decision queue
194
+ → you may self-merge (autopilot): `gh pr merge --squash --delete-branch` (or `--merge` to preserve the
188
195
  history; squash when the branch history is noisy, preserve when each commit is meaningful). **Tier
189
196
  2 waits for human approval** — a human's approving review on the PR, or their explicit yes in the
190
197
  conversation. An agent's approval never counts as a human's: no refuter, reviewer subagent or bot
@@ -203,7 +210,7 @@ Never stack to hide review risk. Stack because each slice is independently revie
203
210
 
204
211
  ### Option 3 — park or discard
205
212
 
206
- - **Park:** leave the branch, push it so it's not lost (`git push -u origin feature/<slug>`), and log *why it's parked* to `02-DOCS/wiki/sdd/decisions.md`. Do not merge.
213
+ - **Park:** leave the branch committed, push it if a remote exists (`git push -u origin feature/<slug>`), and log *why it's parked* to `02-DOCS/wiki/sdd/decisions.md`. Do not merge. A branch parked *ready to land* (autopilot, no remote) also gets its explainer, open queue entries and the exact merge command in `progress/<slug>.md`, so the human lands it with one command.
207
214
  - **Discard:** deletion is **destructive and unrecoverable** once the branch is gone from both sides, so it takes an explicit confirmation that quotes the branch name (the literal `yes, delete feature/<slug>`) before `git branch -D`. Anything ambiguous means keep it. Log the discard and the reason so the dead-end is remembered, not re-attempted.
208
215
 
209
216
  **Park and discard do NOT reap.** The cleanup default acts only on work that is already in the trunk;
@@ -216,7 +223,7 @@ reaper — it owns its own lifecycle and its tracking has to stay consistent.
216
223
 
217
224
  ## Commit message discipline
218
225
 
219
- The commit is the durable record. Make it describe the change and tie it to the spec — and keep it Eric's.
226
+ The commit is the durable record. Make it describe the change and tie it to the spec — and keep it the responsible person's.
220
227
 
221
228
  - **Subject:** `<gitmoji> type: imperative summary (<spec-slug>)` — `✨ feat:`, `🐛 fix:`, `♻️ refactor:`, etc. Under ~72 chars. The gitmoji is **not optional**: on Claude Code a PreToolUse guard denies a `git commit -m` without one, and the refusal hands back the corrected message. Emoji → intention table: `../git-workflow/references/gitmoji.md`.
222
229
  - **Body:** *why*, not a restatement of the diff. Reference the spec slug and any decision logged in `decisions.md`.
@@ -224,23 +231,24 @@ The commit is the durable record. Make it describe the change and tie it to the
224
231
 
225
232
  ## Model tier — `light` (opt-in routing)
226
233
 
227
- Closing the branch (PR / merge / cleanup) is mechanical, so this phase's default tier is **`light`**. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`; when it is on, follow `../asdd/references/model-routing.md` for resolving and announcing the switch rather than from memory. Routing off or no profile → honor the session model silently, and skip routing on a one-line change. The Eric-only authorship rule is independent of the model and never relaxes.
234
+ Closing the branch (PR / merge / cleanup) is mechanical, so this phase's default tier is **`light`**. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`; when it is on, follow `../asdd/references/model-routing.md` for resolving and announcing the switch rather than from memory. Routing off or no profile → honor the session model silently, and skip routing on a one-line change. The human-authorship rule is independent of the model and never relaxes.
228
235
 
229
236
  ## What ship shows
230
237
 
231
238
  Read `technical_level` from `02-DOCS/wiki/harness/user-profile.md`. It changes the words, **never** the safety checklist or the authorship rule. No profile → use analogies and proceed; don't stall the ship to ask.
232
239
 
233
- In the `orient` voice: the explainer, the checklist result in one line, the three options as one-liners, the recommendation and its *why*, then execute on a yes — e.g. `Clean, rebased, authorship Eric. Recommend PR (main is protected). Open it?`. For a `non-technical` owner, explain the recommended option with an analogy: a protected `main` wants a PR ("asking permission before changing the shared copy"). Explain fast-forward vs `--no-ff` or what squashing trades away only when asked.
240
+ In the `orient` voice: the explainer, the checklist result in one line, the three options as one-liners, the recommendation and its *why*, then execute on a yes — e.g. `Clean, rebased, authorship <git user>. Recommend PR (main is protected). Open it?`. For a `non-technical` owner, explain the recommended option with an analogy: a protected `main` wants a PR ("asking permission before changing the shared copy"). Explain fast-forward vs `--no-ff` or what squashing trades away only when asked.
234
241
 
235
242
  ## Anti-patterns → STOP
236
243
 
237
244
  | Rationalization | Reality |
238
245
  | --- | --- |
239
- | "I'll add `Co-Authored-By: Claude` / a 'Generated with Claude Code' footer to be transparent" | It forges the record. The work is Eric's — no AI trailer, ever. Strip it. |
246
+ | "I'll add `Co-Authored-By: Claude` / a 'Generated with Claude Code' footer to be transparent" | It forges the record. The work is the responsible person's — no AI trailer, ever. Strip it. |
240
247
  | "Review didn't formally approve but it's obviously fine" | No verdict = not ready. Ship runs on APPROVE only. Route back to review. |
241
248
  | "Tier 2, but the three refuters approved — I'll self-merge" | An agent's approval never counts as a human's. Tier 2 waits for human approval. |
242
249
  | "The tree has a couple of stray edits, they're harmless" | A dirty tree means the merge is not the reviewed diff. Clean it or stash it first. |
243
250
  | "`main` is protected but I'll just force-merge, I'm sure" | Protected means PR. Don't bypass the gate the repo deliberately set. |
251
+ | "No remote and the guard denies the merge — I'll merge another way" | The denial is the answer. Park the branch with the merge command for the human. |
244
252
  | "I'll rebase and resolve conflicts during the merge" | Resolve before. A conflicted merge commit hides what actually shipped. |
245
253
  | "The gitmoji is decoration, the conventional type is what matters" | Both ship or neither does. The type is for tooling, the emoji for the human scanning `git log` — and the guard denies the commit either way. |
246
254
  | "This branch is dead, I'll just delete it" | Discard is destructive — confirm with the quoted branch name and log why first. |
@@ -252,7 +260,7 @@ In the `orient` voice: the explainer, the checklist result in one line, the thre
252
260
  Ship is mostly git actions, but the outcome is recorded so the knowledge model stays whole:
253
261
 
254
262
  - **The landing decision** (which of the three options, and why) → append to `02-DOCS/wiki/sdd/decisions.md`, the same append-only log `implement`, `verify`, and `review` write to. Parks and discards are logged with their reason so dead-ends aren't re-walked.
255
- - A **shipped feature** flips its spec under `02-DOCS/wiki/sdd/specs/<slug>.md` to a shipped state (note the merge commit / PR). The harness owns the wiki; ship just keeps the `sdd/` rows in `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a short pointer) honest.
263
+ - A **shipped feature** flips its spec under `02-DOCS/wiki/sdd/specs/<slug>.md` to a shipped state (note the merge commit / PR). The harness owns the wiki; ship just keeps the `sdd/` rows in `02-DOCS/wiki/index.md` (the Knowledge map) honest — never the root `CLAUDE.md`.
256
264
  - An **archive bundle** closes the loop under `02-DOCS/wiki/sdd/archive/<slug>/`:
257
265
  - `final-report.md` — what shipped, why, landing decision, links.
258
266
  - `apply-progress.md` — copy or link to `progress/<slug>.md`.
@@ -125,7 +125,7 @@ Then they see an empty-cart message and the "pay" button is disabled
125
125
 
126
126
  A criterion that needs a human to "decide if it's good enough" is not done yet — sharpen it or move the soft part to *Points to clarify*.
127
127
 
128
- **Six of those sections are enforced, not suggested, each from its own date.** For a spec dated 2026-09-06 or later, `spec:gate` refuses the file if *Cost of not building it* or *The cheapest alternative* is missing or still holding the template's guidance; from 2026-11-03 it also refuses one missing any of the four ASDD questions — *What the user can do*, *What result they get*, *What it must respect*, *How it proves it* (Spanish headings accepted: *Qué podrá hacer el usuario*, *Qué resultado obtiene*, *Qué debe respetar*, *Cómo lo demuestra*). Specs written before each date are exempt — they cannot answer honestly after the fact. The first two ask whether the thing should exist at all, because every other gate compares artifacts to each other and a wrong-but-coherent spec passes all of them. The four make the spec something a long autonomous run can be checked against: tests prove the code works, not that it does what was asked.
128
+ **Six of those sections are enforced, not suggested, each from its own date.** For a spec dated 2026-09-06 or later, `rsc spec gate` refuses the file if *Cost of not building it* or *The cheapest alternative* is missing or still holding the template's guidance; from 2026-11-03 it also refuses one missing any of the four ASDD questions — *What the user can do*, *What result they get*, *What it must respect*, *How it proves it* (Spanish headings accepted: *Qué podrá hacer el usuario*, *Qué resultado obtiene*, *Qué debe respetar*, *Cómo lo demuestra*). Specs written before each date are exempt — they cannot answer honestly after the fact. The first two ask whether the thing should exist at all, because every other gate compares artifacts to each other and a wrong-but-coherent spec passes all of them. The four make the spec something a long autonomous run can be checked against: tests prove the code works, not that it does what was asked.
129
129
 
130
130
  ### Points to clarify carry a type
131
131
 
@@ -193,13 +193,11 @@ property of the **file**, not of your confidence:
193
193
  > Every template section carries content or an explicit typed open point, and no section holds an
194
194
  > unmarked assumption.
195
195
 
196
- Run `npm run spec:gate <path>` on the spec you just wrote (no path: the whole corpus). It reports the
197
- missing and the empty sections by name, and it prints what it does **not** check — the unmarked
198
- assumption is yours to catch, because no parser can. A red gate means the spec is not closed: name
199
- the section that failed and fix it before step 9.
200
-
201
- A gate that has never been seen fail is not known to work, so this one ships with the test that
202
- watches it fail and pass (`tests/spec-gate.test.js`).
196
+ Run `npx @ericrisco/rsc spec gate <path>` on the spec you just wrote (no path: every spec in the
197
+ project's `02-DOCS/wiki/sdd/specs/`). It prints `PASS`/`FAIL` per file, reports the missing and the
198
+ empty sections by name, exits non-zero on any `FAIL`, and prints what it does **not** check — the
199
+ unmarked assumption is yours to catch, because no parser can. A red gate means the spec is not
200
+ closed: name the section that failed and fix it before step 9.
203
201
 
204
202
  ### Fresh-eyes spec review (step 8, scaled to the stakes)
205
203
 
@@ -244,7 +242,11 @@ ask → user answers → fold it in → say what changed → show the revised sp
244
242
  **approved in autopilot, not item by item**, in the spec's `status:`. The spec then becomes the
245
243
  artifact the user reviews *after the fact*, and the work claims correspondingly less certainty
246
244
  about having captured their intent. Writing "aprobada" flat, when what happened was a blanket
247
- go-ahead, is the record telling a small lie about the strongest gate in the chain.
245
+ go-ahead, is the record telling a small lie about the strongest gate in the chain. The same goes
246
+ for *What result they get*: with no human to approve a reference, write one that can be checked
247
+ mechanically (the expected transcript, response or output), and record it as a `review-after`
248
+ entry in the decision queue (`## Decision queue`, `02-DOCS/wiki/sdd/progress/<slug>.md`) — the
249
+ run continues, the human confirms the reference afterwards.
248
250
  - **Rejection is information — keep it.** If the user turns the spec down, revise it in place and
249
251
  append the reason to `## Revisions`. Never delete and start clean: what they rejected, and why,
250
252
  is the most useful thing in the file.
@@ -67,7 +67,7 @@ capability:
67
67
  - "Stays strictly WHAT/WHY: no framework, token format, database table, endpoint shape, email provider, library, or file path appears in the spec — implementation-laden answers are flagged as points to clarify instead of guessed."
68
68
  - "Produces the spec sections: Problem & why, Cost of not building it, The cheapest alternative, Goals, Non-goals / out of scope (including what an agent might add 'just in case'), Users & context, What the user can do, What result they get (against a human-approved reference), What it must respect (checked against the decisions log or the project's brain), How it proves it (what evidence remains), Behaviour (main + edge + error paths), Acceptance criteria, Points to clarify — and neither of the two doubt sections is left as a formality: 'Cost of not building it' names a concrete consequence, and 'The cheapest alternative' names something that would solve most of it without building this, plus why that is not enough."
69
69
  - "Writes testable acceptance criteria in Given/When/Then form that verify could later check (e.g. expired-link refusal, unknown-email revealing nothing)."
70
- - "Writes the file to 02-DOCS/wiki/sdd/specs/<slug>.md (kebab-case slug), indexes it in the root CLAUDE.md Knowledge map under sdd/specs, and logs the spec creation to the canonical SDD decisions log at 02-DOCS/wiki/sdd/decisions.md."
70
+ - "Writes the file to 02-DOCS/wiki/sdd/specs/<slug>.md (kebab-case slug), indexes it in 02-DOCS/wiki/index.md under sdd/specs (never the root CLAUDE.md), and logs the spec creation to the canonical SDD decisions log at 02-DOCS/wiki/sdd/decisions.md."
71
71
  - "Lists honest Points to clarify as the handoff rather than inventing values, and TYPES each one (pregunta abierta / suposición tomada / decisión diferida / área no formulable) so clarify knows whether to ask it, validate it, or leave it alone; a suposición carries its basis."
72
72
  - "Ends by pointing to the clarify phase as the next step in the SDD chain."
73
73
 
@@ -109,7 +109,7 @@ something `clarify` can validate instead of re-ask.
109
109
  ## When to stop
110
110
 
111
111
  Stop when the frontier is empty: every branch visited, nothing left silently
112
- assumed. Then run the exit gate (`npm run spec:gate <path>`) on the written
112
+ assumed. Then run the exit gate (`npx @ericrisco/rsc spec gate <path>`) on the written
113
113
  file — the bound is a property of the artifact, not of your confidence.
114
114
  Lingering unknowns are fine, and belong in *Points to clarify*, typed. Over-asking to feel thorough is itself an anti-pattern: it burns the
115
115
  user's patience and pushes them to rubber-stamp answers they haven't thought
@@ -98,7 +98,7 @@ costliest of the four.>
98
98
  - **Status** moves `draft` → `clarified` (after the `clarify` phase) → `planned`
99
99
  (once `plan` exists). Don't invent other states. An approval obtained as a
100
100
  blanket "just run it" is recorded as **approved in autopilot**, not flat.
101
- - **Before you call it written**, run `npm run spec:gate <path>`. It checks that
101
+ - **Before you call it written**, run `npx @ericrisco/rsc spec gate <path>`. It checks that
102
102
  every section here carries content or an explicit open point, and prints the
103
103
  clauses it cannot check (the unmarked assumption is yours).
104
104
  - **The enforced sections are dated, not retroactive.** `Cost of not building
@@ -71,5 +71,5 @@ capability:
71
71
  - "Orders by dependency (migration/schema → data → service → API), so no task precedes what it depends on; marks `[P]` only on file/state-disjoint tasks and defaults to sequential when unsure."
72
72
  - "Uses TDD-shaped slicing: a failing-test task before its implementation task, whose done-check is 'the test that was red is now green'."
73
73
  - "Checks coverage both ways: every task traces to a criterion and every criterion has a task; a task with no criterion is cut/flagged as scope creep (route to clarify/specify), and either gap is a defect analyze will flag."
74
- - "Appends the table under a `## Tasks` heading INSIDE 02-DOCS/wiki/sdd/plans/auth.md (not a new file) and confirms the plan is indexed in the root CLAUDE.md Knowledge map under sdd/."
74
+ - "Appends the table under a `## Tasks` heading INSIDE 02-DOCS/wiki/sdd/plans/auth.md (not a new file) and confirms the plan is indexed in 02-DOCS/wiki/index.md under sdd/ (never the root CLAUDE.md)."
75
75
  - "Adds a final closer task gating on all done-checks passing plus `scripts/verify.sh` green, and hands off to `analyze` next (then implement) — not straight to implementation."
@@ -41,7 +41,8 @@ omitted. A task with a control **not run** is not done; a closing record with on
41
41
 
42
42
  ```text
43
43
  LOCATE → find the spec, the task done-checks, and which stack(s) changed
44
- RUN → execute the relevant stack scripts/verify.sh (lint, type, test+coverage, audit)
44
+ RUN → execute the relevant stack scripts/verify.sh (lint, type, test+coverage, audit),
45
+ or the generic gate from config.yaml when no stack ships one
45
46
  WALK → check every task done-check and every spec acceptance criterion against real output
46
47
  ROLLBACK→ write how to undo the change (revert sha, migration down, flag off) into the record
47
48
  RECORD → write the verification record under 02-DOCS/wiki/sdd/, index it
@@ -91,7 +92,7 @@ Rules for RUN:
91
92
  - A `GAP` is the third thing, and it is the one that looks most like a pass: **the layer ran and had nothing to fail with.** The fastapi gate reports it when coverage runs with no `--cov-fail-under` configured; the Go gate reports it always, because `go test -cover` takes no threshold. The script deliberately does not fail on a GAP — that judgement is yours. **Treat a GAP as an unverified criterion, which fails the verdict**, and record it (see the mapping below). A gate that reports and a gate that checks are indistinguishable on screen, and only the broken one is guaranteed to stay green.
92
93
  - A non-zero exit from a tool that actually ran is a hard FAIL. Record the failing tool and its output.
93
94
  - Run from the directory the stack skill documents (Go runs from the module root; others from the subproject root). Don't guess paths.
94
- - If no `scripts/verify.sh` exists for a touched stack, that's a gap — say the gate is incomplete and recommend the user add the stack skill, rather than hand-rolling a one-off check that drifts from the real gate.
95
+ - **No stack skill ships a `scripts/verify.sh` for this stack?** Run the **generic gate**: the test, lint, type, build and audit commands `sdd-init` recorded in `02-DOCS/wiki/sdd/config.yaml`, each recorded as pass, fail or **not run**. It is a full gate, not a lesser one. Procedure: `references/generic-gate.md`.
95
96
 
96
97
  #### A home-grown gate must prove it can fail — and that it can pass
97
98
 
@@ -106,7 +107,7 @@ The dangerous way a checker breaks is not a crash — it is **fail-open**: nothi
106
107
 
107
108
  **Over-blocking is not the safe side.** It feels like caution and it is not: a gate that fires on correct work gets muted, worked around, or wedges the pipeline that depends on it, which is the same damage as a gate that checks nothing with the sign reversed. And it is *harder* to notice, because the failure arrives dressed as diligence.
108
109
 
109
- Where this bites hardest is a check that matches text rather than structure — **"the path appears in the string" is not "the write targets that location"**. Our own integrity gate shipped that defect: twelve mutants proved it could fail, none asked whether it could pass. → `02-DOCS/wiki/harness/puertas-y-mecanismos.md`
110
+ Where this bites hardest is a check that matches text rather than structure — **"the path appears in the string" is not "the write targets that location"**. Our own integrity gate shipped that defect: twelve mutants proved it could fail, none asked whether it could pass — and the first real run blocked every reading of a protected file as if it were a write to it.
110
111
 
111
112
  So when you write the negative control, write the positive one beside it, and prefer matching **structure** (a parsed tool call, a resolved path, an exit code) over matching text that happens to contain the thing you care about.
112
113
 
@@ -188,7 +189,7 @@ source_state: 3f9a1c2 (clean)
188
189
 
189
190
  **A dismissed finding carries evidence, one line each.** A fix is self-evidencing — the test now passes. A dismissal is not: "not a real problem" is indistinguishable from "did not check". Name the command, the `file:line`, or the test that disproves it, or write "ninguno".
190
191
 
191
- Append-only spirit: don't overwrite a prior run's record; a new run is a new dated file. The record is the receipt the `review` and `ship` phases trust.
192
+ Append-only spirit: don't overwrite a prior run's record; a new run is a new dated file — a re-run on the same day writes `<slug>-YYYY-MM-DD-r2.md` (then `-r3`, …). The record is the receipt the `review` and `ship` phases trust.
192
193
 
193
194
  ### 5 — VERDICT
194
195
 
@@ -94,7 +94,7 @@ capability:
94
94
  - "Treats the mypy SKIP as UNVERIFIED rather than a pass, and does not let 'everything else was green' stand in for the skipped layer."
95
95
  - "WALKS all three done-checks and all three acceptance criteria, and catches that AC2/T2 (duplicate POST is a no-op) has NO test at all — the two named tests cover AC1 and AC3 only — marking it UNVERIFIED rather than passing it."
96
96
  - "Does NOT fix a failing test inline; if a check fails it records it and hands off to debug, and refuses to fix-then-claim."
97
- - "RECORDS a dated verification file under 02-DOCS/wiki/sdd/verifications/ and indexes it in the root CLAUDE.md Knowledge map."
97
+ - "RECORDS a dated verification file under 02-DOCS/wiki/sdd/verifications/ and indexes it in 02-DOCS/wiki/index.md (never the root CLAUDE.md)."
98
98
  - "Returns a VERDICT that is PASS only if EVERY item has passing evidence; a single unverified acceptance criterion makes the whole verdict FAIL — and refuses to call it 'done' otherwise."
99
99
  - "On PASS, points to the review phase; on FAIL, routes failing checks to debug and missing coverage back to implement."
100
100
  - "Treats a coverage GAP (the layer ran with no threshold configured, so it could not have failed) as an unverified criterion that fails the verdict — not as a pass — and records it as SUSTITUIDA: a report in place of a gate, unable to detect coverage regressing."
@@ -0,0 +1,43 @@
1
+ # Generic gate — when no stack skill ships `scripts/verify.sh`
2
+
3
+ A project in a stack the catalog has no gate for (a plain Node CLI, a Rust crate, a shell tool) is
4
+ still verified. The gate is the commands the project itself declares, as `sdd-init` recorded them.
5
+ This is a first-class path, not a fallback to apologise for.
6
+
7
+ ## Where the commands come from
8
+
9
+ `02-DOCS/wiki/sdd/config.yaml` → `testing.commands.verify` (and `testing.commands.apply` for the
10
+ suite), filled by `sdd-init` from the manifests (`package.json` scripts, `Makefile`, `Cargo.toml`,
11
+ `pyproject.toml`…). No config → run `sdd-init` first; do not pick commands from memory.
12
+
13
+ ## The five layers
14
+
15
+ | Layer | What counts | Typical source |
16
+ | --- | --- | --- |
17
+ | test | the full suite | `npm test`, `cargo test`, `pytest` |
18
+ | lint | the project's linter | `npm run lint`, `cargo clippy` |
19
+ | type | the type checker | `tsc --noEmit`, `mypy` |
20
+ | build | the build or compile | `npm run build`, `cargo build` |
21
+ | audit | dependency vulnerabilities | `npm audit`, `cargo audit`, `pip-audit` |
22
+
23
+ ## Procedure
24
+
25
+ 1. List the five layers. For each, take the command from `config.yaml`; if it declares none, look
26
+ for one in the manifest and say where you found it.
27
+ 2. Run each command once, after the last edit, from the project root. Capture the exit code and the
28
+ relevant output verbatim.
29
+ 3. Record every layer as exactly one of:
30
+ - **pass** — the command ran and exited 0;
31
+ - **fail** — the command ran and exited non-zero (a hard FAIL of the verdict);
32
+ - **not run** — no command is declared or the tool is missing. Label it as `verify` does:
33
+ `NO-APLICA` (no such surface, e.g. no types in plain JS), `HERRAMIENTA-AUSENTE` (the surface
34
+ exists, nothing checked it).
35
+ 4. Write the layers under `## Stack gate` in the record, as `generic gate (config.yaml) → …`, one
36
+ line per layer, with the command that ran.
37
+
38
+ ## Rules that still apply
39
+
40
+ - A `not run` layer whose surface exists fails the verdict, exactly like a SKIP in a stack script.
41
+ - A command the project wrote (a custom check script) is a home-grown gate: watch it fail and pass
42
+ before trusting it.
43
+ - Suggest a stack skill if one fits — but the generic gate is the verdict for this run.