Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba0fe4337 | ||
|
|
06592805b6 | ||
|
|
1d140ec525 | ||
|
|
2fcc15e9e4 | ||
|
|
b2af4e1129 | ||
|
|
3c8d2f5613 | ||
|
|
1d368d4b5d | ||
|
|
a565b04e91 | ||
|
|
b616c51a2c | ||
|
|
b8bacfa7b3 | ||
|
|
c377c23e21 | ||
|
|
8f9d5dd8bd | ||
|
|
7ba01abdbe | ||
|
|
9a81a707af |
@@ -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.12.1",
|
"version": "0.15.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mathias",
|
"name": "Mathias",
|
||||||
"url": "https://git.d-ma.be/mathias"
|
"url": "https://git.d-ma.be/mathias"
|
||||||
|
|||||||
@@ -12,5 +12,22 @@
|
|||||||
"type": "http",
|
"type": "http",
|
||||||
"url": "https://koala.tail30472e.ts.net:8444/mcp"
|
"url": "https://koala.tail30472e.ts.net:8444/mcp"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"_parked": {
|
||||||
|
"_reason": "cad and gitea-mcp are proxied, reachable and AUTHENTICATING: tailscale serve 8445 -> 127.0.0.1:31877 and 8446 -> 127.0.0.1:30340; an MCP initialize with the correct bearer returns 200 from both (serverInfo gitea-mcp 0.1.0, cad aed2aaf). They stay parked only because nothing puts those two tokens into the session environment. Both live in the 1Password AgentSecrets vault, and by deliberate design (infra#224, closed) no single identity on koala reads both vaults: koala-homelab is HomeLab-only and resolves ~/.op-env, koala-agentsecrets-write is AgentSecrets-only and lives in ~/.op-agent-write.env. Adding an AgentSecrets reference to ~/.op-env does NOT degrade gracefully -- `op run` fails wholesale and every other token, BRAIN_MCP_TOKEN included, goes unresolved with it. Enabling needs a read-scoped AgentSecrets identity in the session-env path; see mathias/skills#32, #33.",
|
||||||
|
"cad": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8445/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${CAD_MCP_TOKEN}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://koala.tail30472e.ts.net:8446/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer ${GITEA_MCP_STATIC_TOKEN}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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. |
|
||||||
|
|||||||
+10
-1
@@ -110,7 +110,16 @@ tasks:
|
|||||||
# harness instead of erroring.
|
# harness instead of erroring.
|
||||||
sh '{{.TASKFILE_DIR}}/bin/check-surfaces' '{{.TASKFILE_DIR}}' || fail=1
|
sh '{{.TASKFILE_DIR}}/bin/check-surfaces' '{{.TASKFILE_DIR}}' || fail=1
|
||||||
|
|
||||||
# 5. Plugin manifest version must match the latest tag, or installers
|
# 5. A documented session_log call must be one the tool would accept:
|
||||||
|
# session_id is its only required field, and nine skills omitted it.
|
||||||
|
sh '{{.TASKFILE_DIR}}/bin/check-session-log' '{{.TASKFILE_DIR}}' || fail=1
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|||||||
Executable
+64
@@ -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
|
||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# check-session-log — a skill that documents a session_log call must document a
|
||||||
|
# call the tool would actually accept.
|
||||||
|
#
|
||||||
|
# Nine skills documented the payload and none named `session_id`, which is the
|
||||||
|
# tool's only REQUIRED field. An agent following any of them either failed the
|
||||||
|
# call or invented an id — and invented ids do not group, so the pass-rate the
|
||||||
|
# endpoint computes was being assembled from entries that could not be joined
|
||||||
|
# (#36, found by a pressure test against `debug`).
|
||||||
|
#
|
||||||
|
# Also rejected: `duration_ms: <wall-clock>`. An agent does not observe a clock.
|
||||||
|
# A documented field nobody can honestly populate gets fabricated or silently
|
||||||
|
# dropped, and fabricated numbers in a metrics stream are worse than no numbers.
|
||||||
|
#
|
||||||
|
# Usage: check-session-log [checkout-root]
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
for dir in "$ROOT"/skills/*/; do
|
||||||
|
[ -f "$dir/SKILL.md" ] || continue
|
||||||
|
name=$(basename "$dir")
|
||||||
|
|
||||||
|
grep -q 'session_log' "$dir/SKILL.md" || continue
|
||||||
|
|
||||||
|
if ! grep -q 'session_id' "$dir/SKILL.md"; then
|
||||||
|
echo "ERROR: skills/$name documents session_log without session_id — the tool's only required field" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q 'duration_ms:[[:space:]]*<' "$dir/SKILL.md"; then
|
||||||
|
echo "ERROR: skills/$name documents duration_ms as a placeholder an agent cannot measure — omit it or pass a real measurement" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $fail
|
||||||
+39
@@ -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' \
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -234,8 +234,8 @@ wrong" — it means "nothing on the known list is wrong."
|
|||||||
Call `session_log` once at the end of every phase to record the outcome.
|
Call `session_log` once at the end of every phase to record the outcome.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "cad-babysit", phase: "<phase-name>", final_status: "pass" |
|
- `session_log` with `{session_id: "<this session's id>", skill: "cad-babysit", phase: "<phase-name>", final_status: "pass" |
|
||||||
"fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root:
|
"fail" | "skip", message: "<one-line summary>", project_root:
|
||||||
"<absolute path>"}`
|
"<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** query, read-comments, read-actions-logs, classify-fault-vs-outcome,
|
**Phases for this skill:** query, read-comments, read-actions-logs, classify-fault-vs-outcome,
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "code-review", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "code-review", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** phase-0-iron-laws, phase-1-context, phase-2-architectural-analysis, phase-3-go-checklist, phase-4-code-smell-detection, phase-5-test-quality
|
**Phases for this skill:** phase-0-iron-laws, phase-1-context, phase-2-architectural-analysis, phase-3-go-checklist, phase-4-code-smell-detection, phase-5-test-quality
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+44
-8
@@ -12,7 +12,7 @@ description: Systematic hypothesis-first debugging. Generate 3-5 ranked hypothes
|
|||||||
|
|
||||||
Debugging is hypothesis generation, not fix generation. The first instinct — "try this and see if it works" — wastes time and teaches nothing. A disciplined debug session produces a small set of ranked, falsifiable hypotheses, each with a concrete verification command. The fix comes after one hypothesis is confirmed, not before.
|
Debugging is hypothesis generation, not fix generation. The first instinct — "try this and see if it works" — wastes time and teaches nothing. A disciplined debug session produces a small set of ranked, falsifiable hypotheses, each with a concrete verification command. The fix comes after one hypothesis is confirmed, not before.
|
||||||
|
|
||||||
**Core principle:** A hypothesis you cannot verify in one command is not a hypothesis — it is a guess.
|
**Core principle:** A hypothesis you cannot verify with a single bounded, decisive observation is not a hypothesis — it is a guess. In a repo that observation is usually one command. Above one it may be an instrumented run, or partitioning the last twenty CI runs by trigger type; what makes it a hypothesis is that the observation *discriminates*, not that it fits on one line.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
@@ -25,6 +25,27 @@ Debugging is hypothesis generation, not fix generation. The first instinct — "
|
|||||||
- Compile errors with clear messages (just fix the typo)
|
- Compile errors with clear messages (just fix the typo)
|
||||||
- Already-diagnosed bugs where you know the cause (go straight to TDD with a regression test)
|
- Already-diagnosed bugs where you know the cause (go straight to TDD with a regression test)
|
||||||
|
|
||||||
|
### Is it actually diagnosed?
|
||||||
|
|
||||||
|
That last exclusion is the one people get wrong, because a confident wrong diagnosis feels
|
||||||
|
exactly like a right one. It is diagnosed if you can state the mechanism *and* name the
|
||||||
|
observation that would have proved it wrong. If the evidence is "we changed X and it got
|
||||||
|
better", that is correlation with a deployment, and this skill applies.
|
||||||
|
|
||||||
|
The strongest tell is a run of failed fixes — see below. Three plausible fixes that did
|
||||||
|
not eliminate the symptom is not bad luck, it is the diagnosis being wrong.
|
||||||
|
|
||||||
|
### Intake: what you need before hypothesising
|
||||||
|
|
||||||
|
Before generating anything, confirm you have the failure itself — the error text, the
|
||||||
|
stack, the failing run's id, the log. If the report is "it's flaky again" with no
|
||||||
|
artifact, **name the specific thing you need and stop**. Iron Law 1 forbids "try X and
|
||||||
|
see"; hypothesising into a vacuum is the same failure with better grammar, and it is
|
||||||
|
worse because the output looks like analysis.
|
||||||
|
|
||||||
|
This is the rule `cad-babysit` already enforces from the other side: read the issue's own
|
||||||
|
comments and the job log *first*.
|
||||||
|
|
||||||
## Iron Laws
|
## Iron Laws
|
||||||
|
|
||||||
1. **Never suggest "try X and see what happens."** Every hypothesis must have a specific expected outcome if correct.
|
1. **Never suggest "try X and see what happens."** Every hypothesis must have a specific expected outcome if correct.
|
||||||
@@ -76,7 +97,15 @@ For each hypothesis, capture three things:
|
|||||||
- **Verification:** the single command or file inspection that confirms or denies it
|
- **Verification:** the single command or file inspection that confirms or denies it
|
||||||
- **Expected outcome if correct:** the specific output you would see
|
- **Expected outcome if correct:** the specific output you would see
|
||||||
|
|
||||||
Order by likelihood. The most likely cause is hypothesis 1.
|
Order by likelihood, most likely first. **The ordering is the ranking — do not also
|
||||||
|
attach `high`/`medium`/`low` labels.** With five slots and three labels the labels stop
|
||||||
|
discriminating (two hypotheses are genuinely both "high" and the label cannot say which
|
||||||
|
comes first), and a reader then has two orderings that can disagree. Position already
|
||||||
|
carries it.
|
||||||
|
|
||||||
|
Where you can say *why* one outranks another — a prior brain entry, a matching symptom,
|
||||||
|
a recent change in that area — say that instead of a label. That is information; a label
|
||||||
|
is a restatement of the order it sits next to.
|
||||||
|
|
||||||
### Step 3: Output
|
### Step 3: Output
|
||||||
|
|
||||||
@@ -140,6 +169,7 @@ The brain holds prior debug sessions across the project. Use it to skip rediscov
|
|||||||
|
|
||||||
**At debug start:**
|
**At debug start:**
|
||||||
- Run `brain_query` with the error message snippet + the package name. Past sessions may have logged identical or similar failures with their resolved hypotheses.
|
- Run `brain_query` with the error message snippet + the package name. Past sessions may have logged identical or similar failures with their resolved hypotheses.
|
||||||
|
- **A hit is evidence, so cite it inline in the hypothesis it informs** — `(brain: <entry-name>)` — rather than mentioning it once at the top. If a prior entry is why a hypothesis ranks first, the reader needs that at the hypothesis, not in a preamble. If a prior entry contradicts your ranking, say so and rank it anyway if the evidence here is better; the brain records what was true then.
|
||||||
|
|
||||||
**After a hypothesis is confirmed:**
|
**After a hypothesis is confirmed:**
|
||||||
- Run `brain_write` with the failure signature → confirmed mechanism. Future debug sessions on the same area get the answer immediately.
|
- Run `brain_write` with the failure signature → confirmed mechanism. Future debug sessions on the same area get the answer immediately.
|
||||||
@@ -154,7 +184,7 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "debug", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "debug", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** read-failure, generate-hypotheses, output
|
**Phases for this skill:** read-failure, generate-hypotheses, output
|
||||||
|
|
||||||
@@ -163,11 +193,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 +212,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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ If silhouette ≤ 0.20:
|
|||||||
|
|
||||||
**After experiment concludes:**
|
**After experiment concludes:**
|
||||||
- `brain_write` to `jepa-fx/failures/` with any new failure modes discovered
|
- `brain_write` to `jepa-fx/failures/` with any new failure modes discovered
|
||||||
- `session_log` with outcome
|
- `session_log` with `{session_id: "<this session's id>", skill: "experiment-spec", phase: "conclude", final_status: "pass" | "fail" | "skip", message: "<one-line outcome>"}`
|
||||||
|
|
||||||
## Cross-References
|
## Cross-References
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "feature-spec", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "feature-spec", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** draft
|
**Phases for this skill:** draft
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "session-retrospective", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "session-retrospective", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** surfaced
|
**Phases for this skill:** surfaced
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of each phase:**
|
**At end of each phase:**
|
||||||
- `session_log` with `{skill: "spec-driven-dev", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "spec-driven-dev", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** specify, plan, tasks, implement
|
**Phases for this skill:** specify, plan, tasks, implement
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+6
-10
@@ -365,7 +365,7 @@ Before marking work complete:
|
|||||||
- [ ] Tests use real code (mocks only if crossing a system boundary)
|
- [ ] Tests use real code (mocks only if crossing a system boundary)
|
||||||
- [ ] Edge cases and errors covered
|
- [ ] Edge cases and errors covered
|
||||||
|
|
||||||
Can't check all boxes? You skipped TDD. Start over.
|
Can't check **or deliberately strike** all boxes? You skipped TDD. Start over. A box struck with a stated reason is answered; a box left hanging is not.
|
||||||
|
|
||||||
## When Stuck
|
## When Stuck
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ Can't check all boxes? You skipped TDD. Start over.
|
|||||||
| Don't know how to test | Write wished-for API. Write assertion first. Ask Mathias. |
|
| Don't know how to test | Write wished-for API. Write assertion first. Ask Mathias. |
|
||||||
| Test too complicated | Design too complicated. Simplify interface. |
|
| Test too complicated | Design too complicated. Simplify interface. |
|
||||||
| Must mock everything | Code too coupled. Use dependency injection. |
|
| Must mock everything | Code too coupled. Use dependency injection. |
|
||||||
| Test setup huge | Extract helpers with `t.Helper()`. Still complex? Simplify design. |
|
| Test setup huge | Extract helpers (`t.Helper()` in Go, a local `__tracebackhide__ = True` in Python). Still complex? Simplify design. |
|
||||||
|
|
||||||
## Debugging Integration
|
## Debugging Integration
|
||||||
|
|
||||||
@@ -406,20 +406,20 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of `red` phase:**
|
**At end of `red` phase:**
|
||||||
- `session_log` with `{skill: "tdd", phase: "red", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "tdd", phase: "red", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**At end of `green` phase:**
|
**At end of `green` phase:**
|
||||||
- `session_log` with `{skill: "tdd", phase: "green", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "tdd", phase: "green", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**At end of `refactor` phase:**
|
**At end of `refactor` phase:**
|
||||||
- `session_log` with `{skill: "tdd", phase: "refactor", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "tdd", phase: "refactor", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Status semantics:**
|
**Status semantics:**
|
||||||
- `pass` — the phase's intended outcome was reached (red: test fails as expected; green: test passes; refactor: tests still pass after refactor).
|
- `pass` — the phase's intended outcome was reached (red: test fails as expected; green: test passes; refactor: tests still pass after refactor).
|
||||||
- `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.
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -231,10 +231,10 @@ Pass-rate is computed downstream by the `/pass-rate` HTTP endpoint, which
|
|||||||
treats `pass` as success, `fail` as failure, `skip` as neither.
|
treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||||
|
|
||||||
**At end of `read` phase:**
|
**At end of `read` phase:**
|
||||||
- `session_log` with `{skill: "trainer", phase: "read", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "trainer", phase: "read", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**At end of `write` phase:**
|
**At end of `write` phase:**
|
||||||
- `session_log` with `{skill: "trainer", phase: "write", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}`
|
- `session_log` with `{session_id: "<this session's id>", skill: "trainer", phase: "write", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||||
|
|
||||||
**Phases for this skill:** read, write
|
**Phases for this skill:** read, write
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 ]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Tests for bin/check-session-log.
|
||||||
|
#
|
||||||
|
# The regression: nine skills documented a session_log payload and not one of
|
||||||
|
# them named `session_id` — the tool's ONLY required field. Every documented
|
||||||
|
# call was incomplete, so an agent following any of them either failed the call
|
||||||
|
# or invented an id, and invented ids do not group. Found by a pressure test
|
||||||
|
# against `debug`, then found to be fleet-wide (#36).
|
||||||
|
#
|
||||||
|
# Also gated: `duration_ms: <wall-clock>`, which an agent cannot measure. A
|
||||||
|
# documented field nobody can honestly populate gets fabricated or silently
|
||||||
|
# dropped; both are worse than omitting it.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
SUT="$ROOT/bin/check-session-log"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
WORK=${TMPDIR:-/tmp}/check_session_log_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> <body> — a checkout with one skill whose SKILL.md is <body>.
|
||||||
|
fixture() {
|
||||||
|
_f="$WORK/$1"
|
||||||
|
mkdir -p "$_f/skills/alpha"
|
||||||
|
printf -- '---\nname: alpha\ndescription: a\n---\n\n%s\n' "$2" > "$_f/skills/alpha/SKILL.md"
|
||||||
|
printf '%s' "$_f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect 0 'a skill that never mentions session_log is fine' \
|
||||||
|
"$(fixture quiet 'Just a skill. No logging here.')"
|
||||||
|
|
||||||
|
expect 0 'a documented call naming session_id passes' \
|
||||||
|
"$(fixture good 'Call `session_log` with `{session_id: "<id>", skill: "alpha", phase: "red", final_status: "pass"}`')"
|
||||||
|
|
||||||
|
expect 1 'a documented call omitting session_id fails' \
|
||||||
|
"$(fixture bad 'Call `session_log` with `{skill: "alpha", phase: "red", final_status: "pass"}`')" \
|
||||||
|
'session_id'
|
||||||
|
|
||||||
|
expect 1 'documenting duration_ms as wall-clock fails' \
|
||||||
|
"$(fixture clock 'Call `session_log` with `{session_id: "<id>", skill: "alpha", duration_ms: <wall-clock>}`')" \
|
||||||
|
'duration_ms'
|
||||||
|
|
||||||
|
printf '\ncheck_session_log: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
@@ -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 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user