Compare commits

...
6 Commits
Author SHA1 Message Date
mathias 1d140ec525 feat(check): every skill states when it stops and hands back
CI / Check / Test (push) Successful in 1s
CI / Tag release (push) Successful in 1s
Implements #5's decision. The hard part was #37's, not #5's: which skills need
an escalation rule? A hardcoded list rots, sniffing the prose for "does this
write anything" is unfalsifiable, and a frontmatter flag is the option #5
rejected wearing a hat.

So there is no acting set. Every skill answers the question, and a read-only one
answers it in a line. That turns an absence into a claim someone can disagree
with, which is the whole point — "this skill never hands back" is a design
decision worth stating, and today it was indistinguishable from nobody having
thought about it.

Eleven written here, all advisory or read-only, where the honest answer is short
and the value is in being specific rather than generic. `cognitive-load` stops
when the metric disagrees with the people reading the code, because that is
evidence about the metric. `test-design` stops when the tests are fine and the
design under test is what makes them awkward. `code-review` stops when the diff
is big enough that a review would be a skim, and says so rather than producing
confident coverage of a fraction.

The remaining thirty are the acting set — writes, dispatches, credentials,
releases — and each needs a rule someone thought about rather than a rote
paragraph, so they sit in bin/escalation-pending.txt. That list only shrinks,
and it is self-cleaning: a listed skill that HAS a section fails the gate, so an
exemption cannot outlive its reason. Mutation-verified both ways.

Six test cases including the stale-entry case, because an exemption for a skill
that no longer exists is how a gate quietly stops covering something.

Refs #37, closes the mechanism half
Bump-Type: minor
2026-08-29 14:56:04 +02:00
mathias 2fcc15e9e4 chore(release): plugin.json version 0.14.0
Skip-Release: true
2026-08-29 12:53:49 +00:00
mathias b2af4e1129 feat(doctor): --online, for the release a host has never fetched
CI / Check / Test (push) Successful in 1s
CI / Tag release (push) Successful in 1s
Everything doctor checks compares copies already on the machine, deliberately —
it runs in a SessionStart hook where latency shows, and its header promises no
network I/O. That leaves exactly one case invisible, and #30 walked straight
into it: a host whose checkout, marketplace clone and installed plugin all agree
reads as clean while all three sit a release behind origin.

`--online` closes it with one `git ls-remote`, opt-in, for a scheduled per-host
job rather than the session hook. Comparison is by order, not equality, so a
host ahead of the newest tag — normal between a release commit and its tag —
stays quiet.

An unreachable remote is a WARN naming itself, not a staleness FAIL. "I could
not check" and "you are behind" are different answers, and collapsing them would
make a network blip look like a stale library.

The remote URL is never printed: a remote rewritten by url.insteadOf expands
into a live token, which is a documented way this fleet has leaked one before.

Five fixture cases through a $DOCTOR_LATEST_TAG_CMD seam, including one that
asserts the DEFAULT run performs no lookup at all — the promise in the header is
now a test, not a comment. Verified live on koala:

  $ bin/doctor --online
  FAIL behind: this host has 0.12.1; origin has released 0.13.4
  $ bin/doctor
  skills doctor: clean — 41 skills

Closes #38
Bump-Type: minor
2026-08-29 14:52:52 +02:00
mathias 3c8d2f5613 chore(release): plugin.json version 0.13.4
Skip-Release: true
2026-08-29 12:50:31 +00:00
mathias 1d368d4b5d docs(skills): drop the Mode 2 routing notes, fix the contributing steps
CI / Check / Test (push) Successful in 1s
CI / Tag release (push) Successful in 1s
Seven skills carried a `## Mode 2 Routing Note` citing a routing pod ("Plan 6")
that does not exist. Several admitted in their own text that nothing about the
skill changes between modes — so it was roughly ten lines of context on every
load for zero behavioural effect. The content is not worthless, just not
per-skill: it now lives as one table in README.md, with the caveat that nothing
depends on it.

The related line inside the session_log sections stays, because that one does
motivate behaviour — but it no longer cites a component that has never shipped.
It now says what is true: pass-rate is what a future routing decision would be
based on, and a skill that never logs contributes no evidence either way.

While in README: the contributing steps were stale enough to fail CI for anyone
who followed them. They said to add a row to SKILLS_INDEX.md by hand — the table
is generated now, and `task check` fails on a hand-edited diff — and listed none
of the four frontmatter keys the gates require. Step 4 now names the
pressure-test bar from #19 rather than leaving it to be discovered.

Closes #36
Bump-Type: patch
2026-08-29 14:50:14 +02:00
mathias a565b04e91 chore(release): plugin.json version 0.13.3
Skip-Release: true
2026-08-29 12:49:56 +00:00
24 changed files with 468 additions and 48 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "skills", "name": "skills",
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.", "description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
"version": "0.13.2", "version": "0.14.0",
"author": { "author": {
"name": "Mathias", "name": "Mathias",
"url": "https://git.d-ma.be/mathias" "url": "https://git.d-ma.be/mathias"
+59 -9
View File
@@ -110,10 +110,25 @@ Antigravity and Mistral Vibe targets were removed — unused.
task doctor # or bin/doctor task doctor # or bin/doctor
``` ```
Reports dangling links, links resolving into a foreign checkout, skill-count Reports dangling links, links into a source nobody configured, skills in the
mismatches, stray entries, and leftover per-skill Claude symlinks. Non-zero checkout with no link, stray entries, leftover per-skill Claude symlinks, and a
exit on drift. It also runs at session start via the plugin, so a drifting Claude Code plugin sitting on an older generation than the host already has.
host says so rather than waiting to be discovered. Non-zero exit on drift. It also runs at session start via the plugin, so a
drifting host says so rather than waiting to be discovered.
All of that compares copies already on the machine — deliberately, since it
runs in a session hook where latency shows. That leaves one case invisible: a
host where the checkout, the marketplace clone and the installed plugin all
agree, and all three are a release behind origin.
```bash
task doctor -- --online # or bin/doctor --online
```
One `git ls-remote` against origin, opt-in. Belongs in a scheduled per-host job,
not the session hook. An unreachable remote is reported as its own fault rather
than as staleness — "I could not check" and "you are behind" are different
answers and only one of them is about the host.
## Versioning ## Versioning
@@ -130,13 +145,48 @@ before `task skills:install`; default is `main` for always-current.
## Contributing a skill ## Contributing a skill
1. Add a directory `<short-kebab-name>/`. 1. Add a directory `skills/<short-kebab-name>/`.
2. Create `<short-kebab-name>/SKILL.md` — start with one-paragraph 2. Create `skills/<short-kebab-name>/SKILL.md` with this frontmatter — all four
purpose, then triggers ("use when …"), then mechanics. keys are gated by `task check`, and the index table is generated from the
3. Add a row to `SKILLS_INDEX.md` (description + use-when). last three:
4. Commit directly to `main`. Conventional-commit prefix
```yaml
---
name: <short-kebab-name>
surfaces: [claude-code, library] # any of claude-code | library | chat
summary: "<one line for the index table>"
use_when: "<the trigger, for the index table>"
description: <the paragraph the model selects on>
---
```
Then: one-paragraph purpose, triggers ("use when …"), mechanics.
3. Run `task index` and commit the regenerated `SKILLS_INDEX.md`. Do **not**
hand-edit the table — `task check` regenerates it and fails on a diff.
4. Pressure-test it before shipping: run the scenario against a subagent that
does *not* have the skill, write the skill against what that run actually
did, then re-run with it. See `superpowers-writing-skills` and #19.
5. Commit directly to `main`. Conventional-commit prefix
(`feat:` / `fix:` / `docs:`). Add `Bump-Type: minor` footer when (`feat:` / `fix:` / `docs:`). Add `Bump-Type: minor` footer when
adding a new skill or expanding scope; default patch covers edits. adding a new skill or expanding scope; default patch covers edits.
This repo is markdown-only and public — never include secrets, client This repo is markdown-only and public — never include secrets, client
names, or anything under NDA in a skill. names, or anything under NDA in a skill.
## Mode 2 routing candidates
These notes used to sit in each SKILL.md as a `## Mode 2 Routing Note` section.
They changed nothing about how the skill is invoked — several said so outright —
and cost context on every load, so they live here instead. Nothing in the repo
depends on this table; it is a record of which skills looked mechanical enough
to route to a local model, should a routing layer ever exist.
| Skill | Note |
|---|---|
| `cad-babysit` | Phases 2-4 (reading comments/logs, classifying fault vs outcome) are judgment-heavy and should stay Mode 1. Phase 1 (running `cad stuck` and grouping its output) is mechanical and a Mode 2 routing candidate once Plan 6 ships the routing pod. |
| `code-review` | This skill is invoked identically in Mode 1 (cloud Claude) and Mode 2 (client-local with supervisor routing). The routing layer (Plan 6) does not exist yet; treat this skill as Mode 1 only for now. Findings format and severity classification are unchanged across modes. |
| `debug` | This skill produces high-volume mechanical output (hypothesis enumeration) and is a candidate for Mode 2 routing to a local model in the future. Until Plan 6 ships the routing pod, treat as Mode 1 only. The hypothesis format and discipline are identical regardless of which model generates them. |
| `feature-spec` | Spec writing requires interpretation of intent and tradeoff reasoning — work where Claude generally outperforms a 9-13B local model. This skill is intentionally **not** a Mode 2 routing candidate. In Mode 2, the routing layer should leave `feature-spec` calls on Claude. |
| `session-retrospective` | Reading long session logs and pulling themes is high-volume, mechanical pattern-recognition work — a strong Mode 2 routing candidate. Until Plan 6 ships the routing pod, treat as Mode 1 only. |
| `tdd` | This skill is invoked identically whether the agent is running in Mode 1 (cloud Claude, no routing) or Mode 2 (client-local, supervisor routing layer). The routing pod (Plan 6) does not exist yet; until it does, treat this skill as Mode 1 only. The discipline does not change between modes — only the model behind the call. |
| `trainer` | Trainer is the same shape as `session-retrospective` — long-context reading, mechanical filtering — and is a Mode 2 routing candidate. Until Plan 6, treat as Mode 1 only. |
+6 -1
View File
@@ -114,7 +114,12 @@ tasks:
# session_id is its only required field, and nine skills omitted it. # session_id is its only required field, and nine skills omitted it.
sh '{{.TASKFILE_DIR}}/bin/check-session-log' '{{.TASKFILE_DIR}}' || fail=1 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). # never see the update (Claude Code gates updates on this field).
if [ -f '{{.PLUGIN_MANIFEST}}' ]; then if [ -f '{{.PLUGIN_MANIFEST}}' ]; then
manifest_v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' '{{.PLUGIN_MANIFEST}}' | head -1) manifest_v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' '{{.PLUGIN_MANIFEST}}' | head -1)
+64
View File
@@ -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
+41 -2
View File
@@ -10,6 +10,7 @@
# doctor human report, non-zero exit on drift # doctor human report, non-zero exit on drift
# doctor --quiet print only when something is wrong (SessionStart hook) # doctor --quiet print only when something is wrong (SessionStart hook)
# doctor --json machine-readable findings # doctor --json machine-readable findings
# doctor --online ALSO ask origin for the latest tag (one network call)
# #
# POSIX sh on purpose: it runs on macOS and Linux, before any toolchain is # POSIX sh on purpose: it runs on macOS and Linux, before any toolchain is
# guaranteed present, and inside a session-start hook where latency shows. # guaranteed present, and inside a session-start hook where latency shows.
@@ -21,10 +22,12 @@ set -u
QUIET=0 QUIET=0
JSON=0 JSON=0
ONLINE=0
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--quiet) QUIET=1 ;; --quiet) QUIET=1 ;;
--json) JSON=1 ;; --json) JSON=1 ;;
--online) ONLINE=1 ;;
-h|--help) -h|--help)
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
exit 0 ;; exit 0 ;;
@@ -288,6 +291,42 @@ if [ -n "$installed_v" ] && [ -n "$available_v" ] && [ "$installed_v" != "$avail
add FAIL plugin-version "Claude Code has skills@mathias $installed_v installed but $available_v is available on this host — /plugin update skills@mathias (a host stale enough to matter is running the old doctor, so the first detection may need this run by hand)" add FAIL plugin-version "Claude Code has skills@mathias $installed_v installed but $available_v is available on this host — /plugin update skills@mathias (a host stale enough to matter is running the old doctor, so the first detection may need this run by hand)"
fi fi
# --- optional: the release this host has never fetched ----------------------
# Every check above compares copies already present, so a host whose checkout,
# marketplace clone and installed plugin all agree reads as clean while sitting
# a release behind origin (#38). Closing that needs a network call, which the
# default run must not make — hence opt-in, for a scheduled per-host job rather
# than the SessionStart hook.
#
# $DOCTOR_LATEST_TAG_CMD is the test seam; it prints the newest remote tag.
if [ "$ONLINE" -eq 1 ]; then
if [ -n "${DOCTOR_LATEST_TAG_CMD:-}" ]; then
remote_tag=$(eval "$DOCTOR_LATEST_TAG_CMD" 2>/dev/null | tail -1 | tr -d '[:space:]')
else
# The URL is never printed: a remote rewritten by url.insteadOf can expand
# into a live token.
origin=$(git -C "$CHECKOUT" config --get remote.origin.url 2>/dev/null || true)
if [ -n "$origin" ]; then
remote_tag=$(git ls-remote --tags --refs "$origin" 2>/dev/null \
| sed 's#.*refs/tags/##' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1 | tr -d '[:space:]')
else
remote_tag=''
fi
fi
if [ -z "$remote_tag" ]; then
# Not drift. A host that cannot reach origin has an unanswered question,
# not a stale library, and saying otherwise would make the two look alike.
add WARN offline "could not reach origin for the latest tag — this run proves nothing about staleness"
else
remote_v=${remote_tag#v}
if [ -n "$available_v" ] && [ "$remote_v" != "$available_v" ] \
&& [ "$(newer_version "$available_v" "$remote_v")" = "$remote_v" ]; then
add FAIL behind "this host has $available_v; origin has released $remote_v — run 'task update' and '/plugin update skills@mathias'"
fi
fi
fi
# --- output ----------------------------------------------------------------- # --- output -----------------------------------------------------------------
if [ "$JSON" -eq 1 ]; then if [ "$JSON" -eq 1 ]; then
printf '{"checkout":"%s","expected":%s,"plugin":{"installed":"%s","available":"%s"},"findings":[%s]}\n' \ printf '{"checkout":"%s","expected":%s,"plugin":{"installed":"%s","available":"%s"},"findings":[%s]}\n' \
+42
View File
@@ -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
+10 -6
View File
@@ -247,12 +247,6 @@ decide
- `fail` — the phase could not be completed (e.g. `cad stuck` errored, Gitea was unreachable). - `fail` — the phase could not be completed (e.g. `cad stuck` errored, Gitea was unreachable).
- `skip` — phase intentionally skipped (e.g. Phase 3 skipped because Phase 2 was conclusive). - `skip` — phase intentionally skipped (e.g. Phase 3 skipped because Phase 2 was conclusive).
## Mode 2 Routing Note
Phases 2-4 (reading comments/logs, classifying fault vs outcome) are judgment-heavy and should
stay Mode 1. Phase 1 (running `cad stuck` and grouping its output) is mechanical and a Mode 2
routing candidate once Plan 6 ships the routing pod.
## Cross-References ## Cross-References
- Depends on `mathias/cad#14`'s `cad stuck` query — this skill is the ritual around it, not a - Depends on `mathias/cad#14`'s `cad stuck` query — this skill is the ritual around it, not a
@@ -264,3 +258,13 @@ routing candidate once Plan 6 ships the routing pod.
- `mathias/cad-atlas#11` renders the same `cad stuck` output as a pipeline overlay — read that - `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 instead of this skill's text output when a visual "where is everything stuck" view is wanted
over working one row in depth. 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.
+8
View File
@@ -284,3 +284,11 @@ During refactoring:
- `references/python-adaptation.md` — Python conventions: error chaining, dataclasses, the truthiness trap - `references/python-adaptation.md` — Python conventions: error chaining, dataclasses, the truthiness trap
- Load `solid` skill for SOLID principles with Go examples - Load `solid` skill for SOLID principles with Go examples
- Load `refactoring` skill for systematic refactoring techniques - 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`.
+8 -3
View File
@@ -262,7 +262,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached. - `fail` — the phase's intended outcome was NOT reached.
- `skip` — phase was skipped intentionally. - `skip` — phase was skipped intentionally.
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Common Go Anti-Patterns ## Common Go Anti-Patterns
@@ -286,6 +286,11 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- Load `test-design` skill for reviewing test quality - Load `test-design` skill for reviewing test quality
- Load `solid` skill for SOLID principle violations - Load `solid` skill for SOLID principle violations
## Mode 2 Routing Note ## Escalation
This skill is invoked identically in Mode 1 (cloud Claude) and Mode 2 (client-local with supervisor routing). The routing layer (Plan 6) does not exist yet; treat this skill as Mode 1 only for now. Findings format and severity classification are unchanged across modes. 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.
+9
View File
@@ -347,3 +347,12 @@ type UserWriter interface {
- Load `refactoring` skill for systematic techniques to reduce identified hotspots - Load `refactoring` skill for systematic techniques to reduce identified hotspots
- Load `solid` skill for structural fixes (coupling, cohesion) - Load `solid` skill for structural fixes (coupling, cohesion)
- Load `clean-code` skill for naming improvements - 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.
+11 -5
View File
@@ -163,11 +163,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached. - `fail` — the phase's intended outcome was NOT reached.
- `skip` — phase was skipped intentionally. - `skip` — phase was skipped intentionally.
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Mode 2 Routing Note
This skill produces high-volume mechanical output (hypothesis enumeration) and is a candidate for Mode 2 routing to a local model in the future. Until Plan 6 ships the routing pod, treat as Mode 1 only. The hypothesis format and discipline are identical regardless of which model generates them.
## Cross-References ## Cross-References
@@ -186,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. 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 - `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. 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.
+9
View File
@@ -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 > 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. > 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.
+1 -5
View File
@@ -254,11 +254,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached (spec rejected, blocked on unresolved questions). - `fail` — the phase's intended outcome was NOT reached (spec rejected, blocked on unresolved questions).
- `skip` — phase was skipped intentionally. - `skip` — phase was skipped intentionally.
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Mode 2 Routing Note
Spec writing requires interpretation of intent and tradeoff reasoning — work where Claude generally outperforms a 9-13B local model. This skill is intentionally **not** a Mode 2 routing candidate. In Mode 2, the routing layer should leave `feature-spec` calls on Claude.
## Cross-References ## Cross-References
+9
View File
@@ -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. - **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. 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.
+1 -5
View File
@@ -125,11 +125,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached (retrospective could not produce coherent candidates). - `fail` — the phase's intended outcome was NOT reached (retrospective could not produce coherent candidates).
- `skip` — phase was skipped intentionally (no novel learnings worth surfacing). - `skip` — phase was skipped intentionally (no novel learnings worth surfacing).
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Mode 2 Routing Note
Reading long session logs and pulling themes is high-volume, mechanical pattern-recognition work — a strong Mode 2 routing candidate. Until Plan 6 ships the routing pod, treat as Mode 1 only.
## Cross-References ## Cross-References
+9
View File
@@ -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 - `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 `clean-code` skill for naming and structure
- Load `code-review` skill for detecting violations during review - 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.
+1 -1
View File
@@ -341,7 +341,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached (gate blocked, rework required). - `fail` — the phase's intended outcome was NOT reached (gate blocked, rework required).
- `skip` — phase was skipped intentionally. - `skip` — phase was skipped intentionally.
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Cross-References ## Cross-References
+9
View File
@@ -151,3 +151,12 @@ stays in the client boundary**:
- Council/forum names, internal stage labels, internal tooling, named contacts, strategy - 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. 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. - 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.
+1 -5
View File
@@ -419,7 +419,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached (test compiled when it shouldn't, test still fails after green attempt, refactor broke tests). - `fail` — the phase's intended outcome was NOT reached (test compiled when it shouldn't, test still fails after green attempt, refactor broke tests).
- `skip` — phase was skipped intentionally (e.g. refactor not warranted). - `skip` — phase was skipped intentionally (e.g. refactor not warranted).
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future `tdd` call to a local model. If your skill never logs, the routing pod sees no data and may default-route or default-not-route in a way that doesn't reflect real performance. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a `tdd` call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way, and a decision made on no evidence is a coin flip wearing a metric.
## Final Rule ## Final Rule
@@ -429,7 +429,3 @@ Otherwise → not TDD
``` ```
No exceptions without Mathias's permission. No exceptions without Mathias's permission.
## Mode 2 Routing Note
This skill is invoked identically whether the agent is running in Mode 1 (cloud Claude, no routing) or Mode 2 (client-local, supervisor routing layer). The routing pod (Plan 6) does not exist yet; until it does, treat this skill as Mode 1 only. The discipline does not change between modes — only the model behind the call.
+8
View File
@@ -82,3 +82,11 @@ created in a CAD session should trace to a TELOS GOAL. Every GOAL
traces to a PROBLEM. traces to a PROBLEM.
Traceability chain: PROBLEM → GOAL → SPEC → TICKET → IMPL → TEST 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.
+8
View File
@@ -309,3 +309,11 @@ Apply this checklist to every new test:
- Load `tdd` skill for the full TDD workflow - Load `tdd` skill for the full TDD workflow
- Load `code-review` skill for test quality review during pre-merge review - Load `code-review` skill for test quality review during pre-merge review
- See `clean-code/references/code-smells.md` for testing-specific smells - 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.
+1 -5
View File
@@ -243,11 +243,7 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
- `fail` — the phase's intended outcome was NOT reached. - `fail` — the phase's intended outcome was NOT reached.
- `skip` — phase was skipped intentionally (e.g. no candidates passed the gate, write skipped). - `skip` — phase was skipped intentionally (e.g. no candidates passed the gate, write skipped).
**Why this matters:** the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data. **Why this matters:** pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.
## Mode 2 Routing Note
Trainer is the same shape as `session-retrospective` — long-context reading, mechanical filtering — and is a Mode 2 routing candidate. Until Plan 6, treat as Mode 1 only.
## Cross-References ## Cross-References
+85
View File
@@ -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 <name> — 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 <root> <name> <has-section>
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 <rc> <label> <fixture> [needle]
expect() {
_want=$1; _label=$2; _fx=$3; _needle=${4:-}
_out=$(sh "$SUT" "$_fx" 2>&1); _rc=$?
if [ "$_rc" -ne "$_want" ]; then
fail=$((fail + 1)); printf ' FAIL %s (want rc=%s got %s)\n%s\n' "$_label" "$_want" "$_rc" "$_out"; return
fi
if [ -n "$_needle" ] && ! printf '%s' "$_out" | grep -q "$_needle"; then
fail=$((fail + 1)); printf ' FAIL %s (rc ok, report never says %s)\n%s\n' "$_label" "$_needle" "$_out"; return
fi
pass=$((pass + 1)); printf ' ok %s\n' "$_label"
}
f=$(fixture ok); skill "$f" alpha yes
expect 0 'a skill with an Escalation section passes' "$f"
f=$(fixture missing); skill "$f" alpha no
expect 1 'a skill with no section fails, naming it' "$f" 'alpha'
f=$(fixture pending); skill "$f" alpha no; printf 'alpha\n' > "$f/bin/escalation-pending.txt"
expect 0 'a listed skill is allowed through the ratchet' "$f"
f=$(fixture done); skill "$f" alpha yes; printf 'alpha\n' > "$f/bin/escalation-pending.txt"
expect 1 'a listed skill that now HAS a section fails — remove it from the list' "$f" 'escalation-pending'
f=$(fixture stale); skill "$f" alpha yes; printf 'ghost\n' > "$f/bin/escalation-pending.txt"
expect 1 'a listed skill that does not exist fails' "$f" 'ghost'
f=$(fixture comments); skill "$f" alpha no
printf '# a comment, and a blank line follow\n\nalpha\n' > "$f/bin/escalation-pending.txt"
expect 0 'comments and blank lines in the list are ignored' "$f"
printf '\ncheck_escalation: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
+67
View File
@@ -224,5 +224,72 @@ manifest "$f/ck" 0.7.0
manifest "$f/home/.claude/plugins/marketplaces/mathias" 0.9.0 manifest "$f/home/.claude/plugins/marketplaces/mathias" 0.9.0
assert_fails "$f" '0.9.0' 'a newer marketplace clone is reported' assert_fails "$f" '0.9.0' 'a newer marketplace clone is reported'
# --- 9. --online: the release this host has never fetched ------------------
# Everything above compares copies already on the host, so a host where the
# checkout, the marketplace clone and the installed plugin all agree reads as
# clean while being a release behind origin. Offline by design (#30); this is
# the opt-in that closes it (#38).
#
# The remote lookup is stubbed through $DOCTOR_LATEST_TAG_CMD so no test needs
# a network or a real remote.
online_fixture() {
_f=$(new_fixture "$1"); wire_clean "$_f"
installed_state "$_f" "$2"
manifest "$_f/ck" "$2"
printf '%s' "$_f"
}
# assert_online <fixture> <stub-tag> <want-rc> <needle> <label>
assert_online() {
_out=$(HOME="$1/home" DOCTOR_CHECKOUT="$1/ck" \
DOCTOR_LATEST_TAG_CMD="printf %s\\n $2" sh "$DOCTOR" --online 2>&1); _rc=$?
if [ "$3" -eq 0 ] && [ "$_rc" -ne 0 ]; then
fail=$((fail + 1)); printf ' FAIL %s\n exited %s on a current host\n got: %s\n' "$5" "$_rc" "$_out"; return
fi
if [ "$3" -ne 0 ] && [ "$_rc" -eq 0 ]; then
fail=$((fail + 1)); printf ' FAIL %s\n exited 0; staleness went unreported\n' "$5"; return
fi
if [ -n "$4" ] && ! printf '%s' "$_out" | grep -q -- "$4"; then
fail=$((fail + 1)); printf ' FAIL %s\n report never mentions "%s"\n got: %s\n' "$5" "$4" "$_out"; return
fi
pass=$((pass + 1)); printf ' ok %s\n' "$5"
}
f=$(online_fixture behind 0.8.0)
assert_online "$f" v0.9.0 1 '0.9.0' '--online reports a release the host has not fetched'
f=$(online_fixture current 0.9.0)
assert_online "$f" v0.9.0 0 '' '--online stays quiet when the host is current'
# A host AHEAD of the newest tag is the normal state between a release commit
# and its tag; that is not drift (same reasoning as check's ordering rule).
f=$(online_fixture ahead 0.9.1)
assert_online "$f" v0.9.0 0 '' '--online does not complain about being ahead'
# Without the flag, no lookup happens at all — the stub would report a newer
# tag and must be ignored.
f=$(online_fixture offline 0.8.0)
_out=$(HOME="$f/home" DOCTOR_CHECKOUT="$f/ck" \
DOCTOR_LATEST_TAG_CMD="printf v9.9.9\\n" sh "$DOCTOR" 2>&1); _rc=$?
if [ "$_rc" -eq 0 ]; then
pass=$((pass + 1)); printf ' ok the default run performs no remote lookup\n'
else
fail=$((fail + 1)); printf ' FAIL the default run performs no remote lookup\n got: %s\n' "$_out"
fi
# A remote that cannot be reached is not drift and must not be reported as it.
# An unreachable remote produces no output at all, so it gets its own run
# rather than an empty stub argument — "prints nothing" and "prints an empty
# string" are different things and only the first is the case under test.
f=$(online_fixture unreachable 0.9.0)
_out=$(HOME="$f/home" DOCTOR_CHECKOUT="$f/ck" \
DOCTOR_LATEST_TAG_CMD="true" sh "$DOCTOR" --online 2>&1); _rc=$?
if [ "$_rc" -ne 0 ] && printf '%s' "$_out" | grep -q 'could not'; then
pass=$((pass + 1)); printf ' ok --online reports an unreachable remote as its own fault\n'
else
fail=$((fail + 1)); printf ' FAIL --online reports an unreachable remote as its own fault\n rc=%s got: %s\n' "$_rc" "$_out"
fi
printf '\ndoctor: %d passed, %d failed\n' "$pass" "$fail" printf '\ndoctor: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ] [ "$fail" -eq 0 ]