diff --git a/Taskfile.yml b/Taskfile.yml index 1263462..9a745c7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -114,7 +114,12 @@ tasks: # session_id is its only required field, and nine skills omitted it. sh '{{.TASKFILE_DIR}}/bin/check-session-log' '{{.TASKFILE_DIR}}' || fail=1 - # 6. Plugin manifest version must match the latest tag, or installers + # 6. Every skill says when it stops and hands back (#5). Skills whose + # rule is not written yet are listed in bin/escalation-pending.txt, + # which only shrinks. + sh '{{.TASKFILE_DIR}}/bin/check-escalation' '{{.TASKFILE_DIR}}' || fail=1 + + # 7. Plugin manifest version must match the latest tag, or installers # never see the update (Claude Code gates updates on this field). if [ -f '{{.PLUGIN_MANIFEST}}' ]; then manifest_v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' '{{.PLUGIN_MANIFEST}}' | head -1) diff --git a/bin/check-escalation b/bin/check-escalation new file mode 100755 index 0000000..05ff934 --- /dev/null +++ b/bin/check-escalation @@ -0,0 +1,64 @@ +#!/bin/sh +# check-escalation — every skill states when it stops and hands back. +# +# #5 decided this belongs in the body, not the frontmatter: no harness reads +# frontmatter beyond name/description, so a `failure_criteria:` key would be a +# guarantee nothing honours. The model reads the body. +# +# #37 then had to define which skills need one, and every obvious answer was +# bad: a hardcoded list rots, sniffing the text for "does it write anything" is +# unfalsifiable, and a frontmatter flag is the rejected option wearing a hat. +# +# So there is no acting set. EVERY skill answers the question. A read-only skill +# answers it in one line — "read-only; findings are the output, nothing is +# handed back" — which turns an absence into a claim someone can disagree with. +# +# bin/escalation-pending.txt is a ratchet for skills whose real rule has not +# been written yet (a stop condition nobody thought about is not worth +# enforcing). It is self-cleaning: a listed skill that HAS a section fails, so +# the list cannot outlive its reason. +# +# Usage: check-escalation [checkout-root] + +set -u + +ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)} +PENDING="$ROOT/bin/escalation-pending.txt" +fail=0 + +listed() { + [ -f "$PENDING" ] || return 1 + sed 's/#.*//' "$PENDING" | grep -qx "[[:space:]]*$1[[:space:]]*" +} + +for dir in "$ROOT"/skills/*/; do + [ -f "$dir/SKILL.md" ] || continue + name=$(basename "$dir") + + if grep -q '^## Escalation' "$dir/SKILL.md"; then + if listed "$name"; then + echo "ERROR: skills/$name has an Escalation section but is still in bin/escalation-pending.txt — remove it from the list" >&2 + fail=1 + fi + continue + fi + + listed "$name" && continue + echo "ERROR: skills/$name has no '## Escalation' section — say when it stops and hands back, or say it never does" >&2 + fail=1 +done + +# A pending entry for a skill that no longer exists is a stale exemption, and a +# stale exemption is how a gate quietly stops covering something. Looped without +# a pipe on purpose: a `while read` on the right of a pipe runs in a subshell, +# so `fail=1` would be set in a process that exits before it is read. +if [ -f "$PENDING" ]; then + entries=$(sed 's/#.*//' "$PENDING" | tr -d '[:blank:]' | grep -v '^$' || true) + for entry in $entries; do + [ -f "$ROOT/skills/$entry/SKILL.md" ] && continue + echo "ERROR: bin/escalation-pending.txt lists '$entry', which has no skills/$entry/SKILL.md" >&2 + fail=1 + done +fi + +exit $fail diff --git a/bin/escalation-pending.txt b/bin/escalation-pending.txt new file mode 100644 index 0000000..baa7243 --- /dev/null +++ b/bin/escalation-pending.txt @@ -0,0 +1,42 @@ +# Skills whose escalation rule has not been written yet (#37). +# +# This list only shrinks. A skill here is exempt from the '## Escalation' +# requirement until someone writes its real stop conditions; a skill that HAS a +# section and is still listed fails the gate, so the list cannot outlive its +# reason. +# +# The ones already done were the advisory and read-only skills, where the honest +# answer is a line. What is left is the acting set — skills that write files, +# dispatch agents, touch credentials or move releases — and each of those needs +# a rule someone thought about, not a rote paragraph. Per #19, one at a time. + +atdd +dream +experiment-spec +feature-spec +gitea-ci +own-the-task +planning +playwright +problem-analysis +refactoring +regulatory-risk-assessment +secret-hygiene +session-retrospective +spec-driven-dev +superpowers-brainstorming +superpowers-dispatching-parallel-agents +superpowers-executing-plans +superpowers-finishing-a-development-branch +superpowers-receiving-code-review +superpowers-requesting-code-review +superpowers-subagent-driven-development +superpowers-using-git-worktrees +superpowers-using-superpowers +superpowers-verification-before-completion +superpowers-writing-plans +superpowers-writing-skills +tdd +trainer +user-stories +web-shot diff --git a/skills/cad-babysit/SKILL.md b/skills/cad-babysit/SKILL.md index f00eef9..c75ebea 100644 --- a/skills/cad-babysit/SKILL.md +++ b/skills/cad-babysit/SKILL.md @@ -258,3 +258,13 @@ decide - `mathias/cad-atlas#11` renders the same `cad stuck` output as a pipeline overlay — read that instead of this skill's text output when a visual "where is everything stuck" view is wanted over working one row in depth. + +## Escalation + +Read-only by construction, and the Iron Laws above are the escalation rule: never close +an issue, never dispatch, never write a label. Everything that is the operator's call +leaves as a `DECISION NEEDED` block (question / options / recommendation / blocks). + +Stop and hand back when: the stuck queue is empty; a diagnosis needs a write you are not +allowed to make; or two runs in a row produce the same finding, which means the finding +is not being acted on and repeating it is noise. diff --git a/skills/clean-code/SKILL.md b/skills/clean-code/SKILL.md index ac92fa9..8b0eff7 100644 --- a/skills/clean-code/SKILL.md +++ b/skills/clean-code/SKILL.md @@ -284,3 +284,11 @@ During refactoring: - `references/python-adaptation.md` — Python conventions: error chaining, dataclasses, the truthiness trap - Load `solid` skill for SOLID principles with Go examples - Load `refactoring` skill for systematic refactoring techniques + +## Escalation + +Advisory during the REFACTOR phase. Suggests; does not restructure behaviour. + +Stop and hand back when: the cleanup you are proposing changes behaviour. That is not +refactoring, it is a change, and it needs its own failing test first — hand back to +`tdd`. diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md index 5f8cb82..6a1c936 100644 --- a/skills/code-review/SKILL.md +++ b/skills/code-review/SKILL.md @@ -285,3 +285,12 @@ treats `pass` as success, `fail` as failure, `skip` as neither. - Load `refactoring` skill when recommending fixes - Load `test-design` skill for reviewing test quality - Load `solid` skill for SOLID principle violations + +## Escalation + +Read-only analysis. Findings are the output; nothing is handed back mid-review. + +Stop and hand back when: the diff is large enough that a review would be a skim — say so +and ask for it in parts, rather than producing confident coverage of a fraction of it; or +the problem is structural rather than pointwise, in which case name it and hand off to +`solid` or `refactoring` instead of filing twenty line-level comments about one cause. diff --git a/skills/cognitive-load/SKILL.md b/skills/cognitive-load/SKILL.md index 9e42752..e09df8a 100644 --- a/skills/cognitive-load/SKILL.md +++ b/skills/cognitive-load/SKILL.md @@ -347,3 +347,12 @@ type UserWriter interface { - Load `refactoring` skill for systematic techniques to reduce identified hotspots - Load `solid` skill for structural fixes (coupling, cohesion) - Load `clean-code` skill for naming improvements + +## Escalation + +Read-only measurement. The score is the output. + +Stop and hand back when: the measurement disagrees with the reader's lived experience of +the code. A metric that says "fine" about code everyone finds hard is evidence about the +metric, not about the code, and continuing to defend the number is how a measure becomes +a target. diff --git a/skills/debug/SKILL.md b/skills/debug/SKILL.md index 91bca8a..11de0e5 100644 --- a/skills/debug/SKILL.md +++ b/skills/debug/SKILL.md @@ -182,3 +182,13 @@ Techniques kept in `debug/references/`, inherited from the deleted including as one of the fixes already tried. A bumped timeout is a symptom. - `find-polluter.sh` — run when a test passes alone and fails in the suite, or when something in the tree appears that no test admits to creating. + +## Escalation + +Diagnose only — Iron Law 3 is already an escalation rule: the fix is a separate TDD cycle, +not this skill's output. + +Stop and hand back when: the report contains no failure text and no locator, in which case +name the specific artifact you need (run id, job log, stack trace) and stop rather than +hypothesising into a vacuum; or the evidence needed to discriminate between your top two +hypotheses requires access you do not have. diff --git a/skills/discovery-framing/SKILL.md b/skills/discovery-framing/SKILL.md index 80da3bc..9cb4d6e 100644 --- a/skills/discovery-framing/SKILL.md +++ b/skills/discovery-framing/SKILL.md @@ -152,3 +152,12 @@ hand-wavy. Crisp solution + fuzzy problem = reverse-engineered justification. St > Named instances of both war stories are client-tagged and held out of this public skill > pending client-store decision. The transferable lesson lives here; the wrapper stays out. + +## Escalation + +Advisory. Produces a problem brief; commits to nothing. + +Stop and hand back when: the evidence needed to size a problem requires talking to +customers you cannot reach. Sizing a problem from assumptions and presenting it as +discovery is worse than saying the number is unknown — it launders a guess into a brief +that later decisions will cite. diff --git a/skills/grill-me/SKILL.md b/skills/grill-me/SKILL.md index b4416d2..f436ce5 100644 --- a/skills/grill-me/SKILL.md +++ b/skills/grill-me/SKILL.md @@ -148,3 +148,12 @@ This skill maps to the **convergence moments** in the Double Diamond workflow: - **Mid-Diamond 2 (Develop):** Use Quick Poke when an agent's experiment result is ambiguous and you're deciding whether to continue or pivot. The skill does NOT replace human judgment at convergence points — it sharpens the plan so that judgment is better informed. + +## Escalation + +Advisory. Produces sharper questions and a better plan; decides nothing. + +Stop and hand back when: the plan's weak point is a decision that is the operator's to +make rather than a gap in reasoning — surface it as a question with options and a +recommendation, and do not grill past it. Also stop when a round produces no new +objection: interrogation past that point manufactures doubt rather than finding it. diff --git a/skills/solid/SKILL.md b/skills/solid/SKILL.md index 86db264..5e6c6fc 100644 --- a/skills/solid/SKILL.md +++ b/skills/solid/SKILL.md @@ -261,3 +261,12 @@ Domain types know nothing about HTTP, SQL, or external APIs. - `references/python-adaptation.md` — the Python rewrite: Protocol over ABC, and what does not carry over from Go - Load `clean-code` skill for naming and structure - Load `code-review` skill for detecting violations during review + +## Escalation + +Advisory. Names principle violations and suggests structure; writes no code. + +Stop and hand back when: applying a principle would require a change larger than the one +under discussion. "This wants dependency inversion" on a two-line fix is a design +conversation, not a review comment, and forcing it through as one is how a small change +acquires an architecture. diff --git a/skills/stage-gate-review/SKILL.md b/skills/stage-gate-review/SKILL.md index 84e50c7..85cc3c3 100644 --- a/skills/stage-gate-review/SKILL.md +++ b/skills/stage-gate-review/SKILL.md @@ -151,3 +151,12 @@ stays in the client boundary**: - Council/forum names, internal stage labels, internal tooling, named contacts, strategy specifics, deal terms → client dir/org only, local models only, never the public brain. - This skill carries the **pattern**; the client carries the **wrapper**. Never merge them. + +## Escalation + +Advisory. Prepares or chairs a gate; the council decides, not this skill. + +Stop and hand back when: a discipline's evidence is missing rather than weak. A gap on a +gating discipline means do not bring the ask — bring a feedback-only ask instead. Marking +it "cleared with a note" is precisely the gate theatre the anti-patterns section is +about. diff --git a/skills/telos-load/SKILL.md b/skills/telos-load/SKILL.md index 4f821aa..14444a7 100644 --- a/skills/telos-load/SKILL.md +++ b/skills/telos-load/SKILL.md @@ -82,3 +82,11 @@ created in a CAD session should trace to a TELOS GOAL. Every GOAL traces to a PROBLEM. Traceability chain: PROBLEM → GOAL → SPEC → TICKET → IMPL → TEST + +## Escalation + +Read-only. Loads intention context; changes nothing. + +Stop and hand back when: the brain is unreachable or returns nothing for the wing. Working +from a remembered TELOS is the failure this skill exists to prevent — say the load failed +rather than reconstructing it from memory. diff --git a/skills/test-design/SKILL.md b/skills/test-design/SKILL.md index 6cebdb7..50e787d 100644 --- a/skills/test-design/SKILL.md +++ b/skills/test-design/SKILL.md @@ -309,3 +309,11 @@ Apply this checklist to every new test: - Load `tdd` skill for the full TDD workflow - Load `code-review` skill for test quality review during pre-merge review - See `clean-code/references/code-smells.md` for testing-specific smells + +## Escalation + +Read-only scoring of existing tests. + +Stop and hand back when: the tests are fine and the *design under test* is what makes them +awkward. Hard-to-test is a design finding, not a test finding, and rewriting the tests +around it hides the thing worth knowing. diff --git a/tests/check_escalation_test.sh b/tests/check_escalation_test.sh new file mode 100644 index 0000000..642d86a --- /dev/null +++ b/tests/check_escalation_test.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# Tests for bin/check-escalation. +# +# #5 asked whether SKILL.md needs first-class failure/escalation fields and +# decided: not frontmatter (nothing reads it), a body section (the model reads +# that). #37 then had to define WHICH skills need one, and every obvious answer +# was bad — a hardcoded list rots, content sniffing is unfalsifiable, a +# frontmatter flag is the rejected option through the back door. +# +# So there is no acting set. EVERY skill answers the question; a read-only one +# answers it in a line. The classification problem disappears, and "this skill +# never hands back" becomes a stated claim rather than an absence. +# +# bin/escalation-pending.txt is a ratchet for skills whose real rule has not +# been written yet. It is self-cleaning: a skill that has a section AND is still +# listed fails, so the list cannot silently outlive its reason. + +set -u + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +SUT="$ROOT/bin/check-escalation" + +pass=0 +fail=0 +WORK=${TMPDIR:-/tmp}/check_escalation_test.$$ + +if [ ! -f "$SUT" ]; then + printf 'FAIL: %s does not exist\n' "$SUT" + exit 1 +fi + +cleanup() { rm -rf "$WORK"; } +trap cleanup EXIT INT TERM + +# fixture — an empty checkout with a bin/ for the pending list. +fixture() { + _f="$WORK/$1" + mkdir -p "$_f/skills" "$_f/bin" + : > "$_f/bin/escalation-pending.txt" + printf '%s' "$_f" +} + +# skill +skill() { + mkdir -p "$1/skills/$2" + { + printf -- '---\nname: %s\ndescription: d\n---\n\n# %s\n' "$2" "$2" + [ "$3" = yes ] && printf '\n## Escalation\n\nStop and hand back when X. Hand back by: saying so.\n' + } > "$1/skills/$2/SKILL.md" +} + +# expect