Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ba01abdbe | ||
|
|
9a81a707af | ||
|
|
5070a738e8 | ||
|
|
8da7c9c45f | ||
|
|
6e5024d498 | ||
|
|
62cfe46927 | ||
|
|
de84800892 | ||
|
|
51a2d396fd | ||
|
|
bb912e5594 | ||
|
|
08c935ba8a | ||
|
|
84a012cd0f | ||
|
|
e12ac99465 | ||
|
|
dc97fd4a36 | ||
|
|
d7775aac57 | ||
|
|
c6787e04d7 | ||
|
|
90a58f0b9e | ||
|
|
15737321ac |
@@ -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.9.0",
|
"version": "0.13.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mathias",
|
"name": "Mathias",
|
||||||
"url": "https://git.d-ma.be/mathias"
|
"url": "https://git.d-ma.be/mathias"
|
||||||
|
|||||||
+39
-14
@@ -94,26 +94,51 @@ jobs:
|
|||||||
git config user.name "${GITEA_ACTOR}"
|
git config user.name "${GITEA_ACTOR}"
|
||||||
git config user.email "${GITEA_ACTOR}@noreply.gitea.d-ma.be"
|
git config user.email "${GITEA_ACTOR}@noreply.gitea.d-ma.be"
|
||||||
|
|
||||||
# Claude Code gates plugin updates on plugin.json's version field:
|
# The tag goes up FIRST and on its own. It names the commit this run
|
||||||
# if it does not move, installed hosts never see the new tag. Bump
|
# checked out and can never be rejected, however far main has moved.
|
||||||
# it in the same commit train so the tag and the manifest agree —
|
git tag -a "$TAG" -m "release $TAG"
|
||||||
# `task check` enforces that equality on every run.
|
git push origin "$TAG"
|
||||||
|
echo "pushed $TAG"
|
||||||
|
|
||||||
|
# The manifest bump is a separate concern and must land on whatever
|
||||||
|
# main is NOW. The runner is maxParallel=1 and shared with other
|
||||||
|
# repos, so a job routinely starts several commits after its own —
|
||||||
|
# pushing this run's stale HEAD to main is then rejected
|
||||||
|
# non-fast-forward, which is how v0.10.0 ended up tagged with the
|
||||||
|
# manifest still reading 0.9.0.
|
||||||
|
#
|
||||||
|
# Claude Code gates plugin updates on this field: if it does not
|
||||||
|
# move, installed hosts never see the release. `task check` asserts
|
||||||
|
# the manifest is not behind the latest tag, so a failure here is a
|
||||||
|
# red main, not a cosmetic miss.
|
||||||
manifest=.claude-plugin/plugin.json
|
manifest=.claude-plugin/plugin.json
|
||||||
version="${TAG#v}"
|
version="${TAG#v}"
|
||||||
current=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -1)
|
|
||||||
git tag -a "$TAG" -m "release $TAG"
|
|
||||||
|
|
||||||
if [ "$current" != "$version" ]; then
|
for attempt in 1 2 3; do
|
||||||
|
git fetch --quiet origin main
|
||||||
|
git checkout --quiet -B release-bump origin/main
|
||||||
|
|
||||||
|
current=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -1)
|
||||||
|
if [ "$current" = "$version" ]; then
|
||||||
|
echo "manifest already at $version"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# A later run may have bumped past us while this one waited.
|
||||||
|
if [ "$(sh bin/newer-version "$current" "$version")" = "$current" ]; then
|
||||||
|
echo "manifest is $current, already newer than $version — leaving it"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
sed -i "s/\"version\"[[:space:]]*:[[:space:]]*\"$current\"/\"version\": \"$version\"/" "$manifest"
|
sed -i "s/\"version\"[[:space:]]*:[[:space:]]*\"$current\"/\"version\": \"$version\"/" "$manifest"
|
||||||
git add "$manifest"
|
git add "$manifest"
|
||||||
# The trailer below stops this push from re-triggering the tagger.
|
# The trailer below stops this push from re-triggering the tagger.
|
||||||
git commit -m "chore(release): plugin.json version $version" -m "Skip-Release: true"
|
git commit -m "chore(release): plugin.json version $version" -m "Skip-Release: true"
|
||||||
# Commit and tag go up in ONE push: the gate job runs `task check`
|
if git push origin HEAD:main; then
|
||||||
# on the bump commit, and that asserts manifest == latest tag. Two
|
|
||||||
# separate pushes leave a window where it legitimately fails.
|
|
||||||
git push origin HEAD:main "$TAG"
|
|
||||||
echo "bumped $manifest to $version"
|
echo "bumped $manifest to $version"
|
||||||
else
|
exit 0
|
||||||
git push origin "$TAG"
|
|
||||||
fi
|
fi
|
||||||
echo "pushed $TAG"
|
echo "main moved while bumping — retry $attempt"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "could not land the manifest bump after 3 attempts" >&2
|
||||||
|
exit 1
|
||||||
|
|||||||
@@ -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 and reachable (tailscale serve 8445 -> 127.0.0.1:31877, 8446 -> 127.0.0.1:30340; both answer 401 unauthenticated, so auth is enforced and the path is right). They stay parked because their static tokens live in the 1Password AgentSecrets vault, and this host's op identity sees only HomeLab -- 'op vault list' returns HomeLab alone, and resolving op://HomeLab/gitea-mcp-shared-token/password or op://HomeLab/CAD_STATIC_TOKEN/password fails with 'could not find item'. That is infra#224 (AgentSecrets not visible to koala's service account), NOT the OAuth-discovery bug both issues warned about. Enabling is: make the vault visible, add the two op:// references to ~/.op-env, move these entries into mcpServers. See mathias/skills#32 and #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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+49
-44
@@ -2,50 +2,55 @@
|
|||||||
|
|
||||||
This index lists all available engineering skills. Load the full SKILL.md on demand.
|
This index lists all available engineering skills. Load the full SKILL.md on demand.
|
||||||
|
|
||||||
| Skill | Description | Use when |
|
The table is **generated** from each skill's frontmatter (`summary`, `use_when`,
|
||||||
|-------|-------------|----------|
|
`surfaces`) by `task index`, and `task check` fails if it is out of date. Edit
|
||||||
| `tdd` | Test-Driven Development enforcement | Starting any new feature or fixing a bug |
|
the SKILL.md, not the table.
|
||||||
| `clean-code` | Clean code principles with Go adaptation | Code review, refactoring, new code |
|
|
||||||
| `solid` | SOLID principles with Go examples | Design review, architecture decisions |
|
<!-- BEGIN skills-table -->
|
||||||
| `code-review` | Code smell detection + Go-specific checklist | Pre-merge review |
|
| Skill | Description | Use when | Surfaces |
|
||||||
| `refactoring` | Systematic refactoring methodology (Fowler) | When improving existing code |
|
|-------|-------------|----------|----------|
|
||||||
| `test-design` | Dave Farley test design scoring (Farley Index) | Reviewing or writing tests |
|
| `atdd` | Acceptance Test-Driven Development workflow | Feature development with acceptance criteria | claude-code, library |
|
||||||
| `cognitive-load` | Cognitive complexity analysis (CLI score) | Identifying hotspots, before refactoring |
|
| `cad-babysit` | Focused human-in-the-loop session working CAD's stuck queue (read-only) | Picking up whatever `cad stuck` flags, with a human present | claude-code, library |
|
||||||
| `problem-analysis` | Deep problem understanding before coding | Starting any non-trivial task |
|
| `clean-code` | Clean code principles with Go adaptation | Code review, refactoring, new code | claude-code, library |
|
||||||
| `user-stories` | Elephant Carpaccio story slicing | Breaking down features |
|
| `code-review` | Code smell detection + Go-specific checklist | Pre-merge review | claude-code, library |
|
||||||
| `atdd` | Acceptance Test-Driven Development workflow | Feature development with acceptance criteria |
|
| `cognitive-load` | Cognitive complexity analysis (CLI score) | Identifying hotspots, before refactoring | claude-code, library |
|
||||||
| `spec-driven-dev` | Spec-driven development for PMs | Writing specs and requirements |
|
| `debug` | Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious | claude-code, library |
|
||||||
| `planning` | Planning and task breakdown | Sprint planning, task decomposition |
|
| `discovery-framing` | Problem-before-solution discovery/framing method | Framing/validating a problem before any solution; opportunity sizing | claude-code, library |
|
||||||
| `gitea-ci` | Gitea CI/CD pipeline with act_runner, buildah, k3s, GitHub mirror | Setting up or debugging CI on this infra |
|
| `dream` | Reflective consolidation pass over an agent's memory directory | Pruning/merging memory files, rebuilding a memory index | claude-code, library, chat |
|
||||||
| `debug` | Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious |
|
| `experiment-spec` | Rigorous experiment spec — falsifiable hypothesis, quantitative acceptance, null-result protocol | Scoping a research/ML phase instead of a feature | claude-code, library |
|
||||||
| `feature-spec` | Implementation-level feature spec (tighter than spec-driven-dev) | Scoping one feature inside an already-specced project |
|
| `feature-spec` | Implementation-level feature spec (tighter than spec-driven-dev) | Scoping one feature inside an already-specced project | claude-code, library |
|
||||||
| `experiment-spec` | Rigorous experiment spec — falsifiable hypothesis, quantitative acceptance, null-result protocol | Scoping a research/ML phase instead of a feature |
|
| `gitea-ci` | Gitea CI/CD pipeline with act_runner, buildah, k3s, GitHub mirror | Setting up or debugging CI on this infra | claude-code, library |
|
||||||
| `playwright` | Playwright E2E testing — setup, HTMX patterns, SSE streams, CI wiring | Adding browser tests to any project with a web UI |
|
| `grill-me` | Structured plan interrogation — Quick Poke, Full Grill, Pre-mortem | Stress-testing a plan before committing; end of Diamond 1; before promoting to pre-prod | claude-code, library, chat |
|
||||||
| `session-retrospective` | Surface non-obvious learnings from a session log | After a coding session ends, before context is lost |
|
| `own-the-task` | Delegation ownership — Intake → Brief → Plan → Execute | A task is handed over open-ended; \"take this off my plate\"; before a plan exists | claude-code, library, chat |
|
||||||
| `trainer` | Two-phase brain curation (score candidates, write past quality gate) | Periodic brain pruning and curation |
|
| `planning` | Planning and task breakdown | Sprint planning, task decomposition | claude-code, library |
|
||||||
| `dream` | Reflective consolidation pass over an agent's memory directory | Pruning/merging memory files, rebuilding a memory index |
|
| `playwright` | Playwright E2E testing — setup, HTMX patterns, SSE streams, CI wiring | Adding browser tests to any project with a web UI | claude-code, library |
|
||||||
| `grill-me` | Structured plan interrogation — Quick Poke, Full Grill, Pre-mortem | Stress-testing a plan before committing; end of Diamond 1; before promoting to pre-prod |
|
| `problem-analysis` | Deep problem understanding before coding | Starting any non-trivial task | claude-code, library |
|
||||||
| `own-the-task` | Delegation ownership — Intake → Brief → Plan → Execute | A task is handed over open-ended; "take this off my plate"; before a plan exists |
|
| `refactoring` | Systematic refactoring methodology (Fowler) | When improving existing code | claude-code, library |
|
||||||
| `telos-load` | Load TELOS intention substrate at session start | Starting any koala session; before architectural decisions; CAD pipeline entry |
|
| `regulatory-risk-assessment` | Structured risk register for regulated-industry features | Filing a CAD issue (needs Risk: level); features touching payments, auth, external APIs, user data | claude-code, library |
|
||||||
| `secret-hygiene` | Stop credentials rendering into the session transcript; safe rotation chain when one leaks | Before any kubectl/SOPS/token command; after a confirmed leak |
|
| `secret-hygiene` | Stop credentials rendering into the session transcript; safe rotation chain when one leaks | Before any kubectl/SOPS/token command; after a confirmed leak | claude-code, library |
|
||||||
| `regulatory-risk-assessment` | Structured risk register for regulated-industry features | Filing a CAD issue (needs Risk: level); features touching payments, auth, external APIs, user data |
|
| `session-retrospective` | Surface non-obvious learnings from a session log | After a coding session ends, before context is lost | claude-code, library |
|
||||||
| `discovery-framing` | Problem-before-solution discovery/framing method | Framing/validating a problem before any solution; opportunity sizing |
|
| `solid` | SOLID principles with Go examples | Design review, architecture decisions | claude-code, library |
|
||||||
| `stage-gate-review` | Stage-gate product governance (go/no-go council) | Preparing a Product Council ask; building a go/no-go checklist; gate review |
|
| `spec-driven-dev` | Spec-driven development for PMs | Writing specs and requirements | claude-code, library |
|
||||||
| `web-shot` | Screenshot a running web UI via a koala k3s Playwright job | Visual verification of a running UI when no local browser exists |
|
| `stage-gate-review` | Stage-gate product governance (go/no-go council) | Preparing a Product Council ask; building a go/no-go checklist; gate review | claude-code, library |
|
||||||
| `cad-babysit` | Focused human-in-the-loop session working CAD's stuck queue (read-only) | Picking up whatever `cad stuck` flags, with a human present |
|
| `superpowers-brainstorming` | Structured divergent brainstorming | Generating or expanding a set of ideas | claude-code, library |
|
||||||
| `superpowers-brainstorming` | Structured divergent brainstorming | Generating or expanding a set of ideas |
|
| `superpowers-dispatching-parallel-agents` | Fan work out across parallel subagents | Decomposing work for concurrent agents | claude-code, library |
|
||||||
| `superpowers-writing-plans` | Writing implementation plans | Planning substantial work before coding |
|
| `superpowers-executing-plans` | Executing a written plan step by step | Running an approved plan to completion | claude-code, library |
|
||||||
| `superpowers-executing-plans` | Executing a written plan step by step | Running an approved plan to completion |
|
| `superpowers-finishing-a-development-branch` | Cleanly finishing/landing a dev branch | Wrapping a branch → merge/PR | claude-code, library |
|
||||||
| `superpowers-dispatching-parallel-agents` | Fan work out across parallel subagents | Decomposing work for concurrent agents |
|
| `superpowers-receiving-code-review` | Acting on received review feedback | Responding to review comments | claude-code, library |
|
||||||
| `superpowers-subagent-driven-development` | Subagent-driven development workflow | Building via orchestrated subagents |
|
| `superpowers-requesting-code-review` | Requesting a code review well | Asking another agent/human to review | claude-code, library |
|
||||||
| `superpowers-systematic-debugging` | Hypothesis-first systematic debugging | Diagnosing a failure methodically |
|
| `superpowers-subagent-driven-development` | Subagent-driven development workflow | Building via orchestrated subagents | claude-code, library |
|
||||||
| `superpowers-verification-before-completion` | Verify behavior before claiming done | Before marking any task complete |
|
| `superpowers-using-git-worktrees` | Isolating work with git worktrees | Parallel/isolated work in a worktree | claude-code, library |
|
||||||
| `superpowers-requesting-code-review` | Requesting a code review well | Asking another agent/human to review |
|
| `superpowers-using-superpowers` | Meta: orienting in the superpowers set | Learning how to use the superpowers skills | claude-code, library |
|
||||||
| `superpowers-receiving-code-review` | Acting on received review feedback | Responding to review comments |
|
| `superpowers-verification-before-completion` | Verify behavior before claiming done | Before marking any task complete | claude-code, library |
|
||||||
| `superpowers-finishing-a-development-branch` | Cleanly finishing/landing a dev branch | Wrapping a branch → merge/PR |
|
| `superpowers-writing-plans` | Writing implementation plans | Planning substantial work before coding | claude-code, library |
|
||||||
| `superpowers-using-git-worktrees` | Isolating work with git worktrees | Parallel/isolated work in a worktree |
|
| `superpowers-writing-skills` | Authoring new SKILL.md skills | Creating a new reusable skill | claude-code, library |
|
||||||
| `superpowers-writing-skills` | Authoring new SKILL.md skills | Creating a new reusable skill |
|
| `tdd` | Test-Driven Development enforcement | Starting any new feature or fixing a bug | claude-code, library |
|
||||||
| `superpowers-using-superpowers` | Meta: orienting in the superpowers set | Learning how to use the superpowers skills |
|
| `telos-load` | Load TELOS intention substrate at session start | Starting any koala session; before architectural decisions; CAD pipeline entry | claude-code, library |
|
||||||
|
| `test-design` | Dave Farley test design scoring (Farley Index) | Reviewing or writing tests | claude-code, library |
|
||||||
|
| `trainer` | Two-phase brain curation (score candidates, write past quality gate) | Periodic brain pruning and curation | claude-code, library |
|
||||||
|
| `user-stories` | Elephant Carpaccio story slicing | Breaking down features | claude-code, library |
|
||||||
|
| `web-shot` | Screenshot a running web UI via a koala k3s Playwright job | Visual verification of a running UI when no local browser exists | claude-code, library |
|
||||||
|
<!-- END skills-table -->
|
||||||
|
|
||||||
## Wiring into tools
|
## Wiring into tools
|
||||||
|
|
||||||
|
|||||||
+25
-8
@@ -61,8 +61,13 @@ tasks:
|
|||||||
done
|
done
|
||||||
exit $fail
|
exit $fail
|
||||||
|
|
||||||
|
index:
|
||||||
|
desc: 'Regenerate SKILLS_INDEX.md''s table from skill frontmatter.'
|
||||||
|
cmds:
|
||||||
|
- "'{{.TASKFILE_DIR}}/bin/generate-index' '{{.TASKFILE_DIR}}'"
|
||||||
|
|
||||||
check:
|
check:
|
||||||
desc: 'Structure gate — layout, frontmatter, manifest/tag parity. Runs in CI.'
|
desc: 'Structure gate — layout, frontmatter, surfaces, generated index, manifest/tag parity. Runs in CI.'
|
||||||
cmds:
|
cmds:
|
||||||
- |
|
- |
|
||||||
fail=0
|
fail=0
|
||||||
@@ -94,18 +99,30 @@ tasks:
|
|||||||
count=$((count + 1))
|
count=$((count + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
# 3. SKILLS_INDEX.md lists exactly the skills that exist. Delegated
|
# 3. SKILLS_INDEX.md's table is generated from frontmatter, so the
|
||||||
# to bin/check-index so the rule is covered by
|
# assertion is that regenerating it changes nothing. This replaces
|
||||||
# tests/check_index_test.sh rather than living untested here.
|
# bin/check-index: a generated table cannot drift in either
|
||||||
sh '{{.TASKFILE_DIR}}/bin/check-index' '{{.TASKFILE_DIR}}' || fail=1
|
# direction, so there is no separate rule left to check.
|
||||||
|
sh '{{.TASKFILE_DIR}}/bin/generate-index' --check '{{.TASKFILE_DIR}}' || fail=1
|
||||||
|
|
||||||
# 4. Plugin manifest version must match the latest tag, or installers
|
# 4. Every skill says which surfaces it ships to, and names only
|
||||||
|
# surfaces that exist — a wrong value silently drops a skill from a
|
||||||
|
# harness instead of erroring.
|
||||||
|
sh '{{.TASKFILE_DIR}}/bin/check-surfaces' '{{.TASKFILE_DIR}}' || fail=1
|
||||||
|
|
||||||
|
# 5. 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)
|
||||||
tag_v=$(git -C '{{.TASKFILE_DIR}}' describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '')
|
tag_v=$(git -C '{{.TASKFILE_DIR}}' describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '')
|
||||||
if [ -n "$tag_v" ] && [ "$manifest_v" != "$tag_v" ]; then
|
# Ordering, not equality. The fault this guards is the manifest
|
||||||
echo "ERROR: plugin.json version ($manifest_v) != latest tag ($tag_v)" >&2
|
# failing to MOVE — a release tag whose manifest still names the old
|
||||||
|
# version, so no installed host ever sees the update. A manifest
|
||||||
|
# ahead of the newest tag this checkout knows about is the normal
|
||||||
|
# state between the tagger's bump commit and a tag fetch (#34).
|
||||||
|
if [ -n "$tag_v" ] && [ "$manifest_v" != "$tag_v" ] \
|
||||||
|
&& [ "$(sh '{{.TASKFILE_DIR}}/bin/newer-version' "$manifest_v" "$tag_v")" = "$tag_v" ]; then
|
||||||
|
echo "ERROR: plugin.json version ($manifest_v) is behind the latest tag ($tag_v) — installed hosts never see the update" >&2
|
||||||
fail=1
|
fail=1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# check-index — asserts SKILLS_INDEX.md lists exactly the skills that exist.
|
|
||||||
#
|
|
||||||
# The index is hand-maintained and drifts silently in both directions: a skill
|
|
||||||
# ships with no row (`dream` and `experiment-spec` did — their rows lived on
|
|
||||||
# branches squash-merged after a directory reorg, so the SKILL.md landed and
|
|
||||||
# the row did not, #15), or a row outlives the skill it names. Neither errors
|
|
||||||
# anywhere; the library just misreports itself to every harness that reads it.
|
|
||||||
#
|
|
||||||
# Usage: check-index [checkout-root] (defaults to this script's repo root)
|
|
||||||
|
|
||||||
set -u
|
|
||||||
|
|
||||||
ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
|
||||||
INDEX="$ROOT/SKILLS_INDEX.md"
|
|
||||||
fail=0
|
|
||||||
|
|
||||||
if [ ! -f "$INDEX" ]; then
|
|
||||||
echo "ERROR: missing SKILLS_INDEX.md" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# A row is a table line whose first cell is the skill name in backticks.
|
|
||||||
listed=$(sed -n 's/^|[[:space:]]*`\([^`]*\)`.*/\1/p' "$INDEX" | sort -u)
|
|
||||||
|
|
||||||
for dir in "$ROOT"/skills/*/; do
|
|
||||||
name=$(basename "$dir")
|
|
||||||
[ -f "$dir/SKILL.md" ] || continue
|
|
||||||
printf '%s\n' "$listed" | grep -qx "$name" || {
|
|
||||||
echo "ERROR: skills/$name has no row in SKILLS_INDEX.md" >&2
|
|
||||||
fail=1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
for name in $listed; do
|
|
||||||
[ -f "$ROOT/skills/$name/SKILL.md" ] || {
|
|
||||||
echo "ERROR: SKILLS_INDEX.md lists '$name', which has no skills/$name/SKILL.md" >&2
|
|
||||||
fail=1
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
exit $fail
|
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# check-surfaces — every skill declares which surfaces it ships to, and only
|
||||||
|
# names surfaces that exist.
|
||||||
|
#
|
||||||
|
# The repo distributes three ways: the Claude Code plugin, the symlinked
|
||||||
|
# library (Crush/Pi/OpenCode), and claude.ai, which has no fetch path at all.
|
||||||
|
# Treating every skill as belonging to all three installs skills that can never
|
||||||
|
# fire — gitea-ci, playwright and web-shot have no shell or cluster in a chat
|
||||||
|
# surface — and degrades selection for the ones that can (#29).
|
||||||
|
#
|
||||||
|
# Values are validated, not merely counted: a wrong surface silently removes a
|
||||||
|
# skill from a harness rather than erroring, which is the failure mode this
|
||||||
|
# whole file exists to prevent.
|
||||||
|
#
|
||||||
|
# Usage: check-surfaces [checkout-root]
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
||||||
|
ALLOWED='claude-code library chat'
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
for dir in "$ROOT"/skills/*/; do
|
||||||
|
[ -f "$dir/SKILL.md" ] || continue
|
||||||
|
name=$(basename "$dir")
|
||||||
|
|
||||||
|
line=$(sed -n 's/^surfaces:[[:space:]]*//p' "$dir/SKILL.md" | head -1)
|
||||||
|
if [ -z "$line" ]; then
|
||||||
|
echo "ERROR: skills/$name declares no 'surfaces:' — say where it ships (${ALLOWED% *}, ${ALLOWED##* })" >&2
|
||||||
|
fail=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
values=$(printf '%s' "$line" | tr -d '[]' | tr ',' ' ')
|
||||||
|
count=0
|
||||||
|
for v in $values; do
|
||||||
|
count=$((count + 1))
|
||||||
|
case " $ALLOWED " in
|
||||||
|
*" $v "*) ;;
|
||||||
|
*) echo "ERROR: skills/$name names surface '$v', which does not exist — allowed: $ALLOWED" >&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ "$count" -gt 0 ] || {
|
||||||
|
echo "ERROR: skills/$name has an empty 'surfaces:' list — a skill that ships nowhere should be deleted, not declared" >&2
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $fail
|
||||||
+10
-9
@@ -153,6 +153,13 @@ elif [ -d "$AGENTS_DIR" ]; then
|
|||||||
for dir in "$SKILLS_DIR"/*/; do
|
for dir in "$SKILLS_DIR"/*/; do
|
||||||
[ -f "$dir/SKILL.md" ] || continue
|
[ -f "$dir/SKILL.md" ] || continue
|
||||||
name=$(basename "$dir")
|
name=$(basename "$dir")
|
||||||
|
# Only skills that ship to the library belong here. A chat-only skill
|
||||||
|
# missing from a Crush directory is correct, not drift (#29).
|
||||||
|
surfaces=$(sed -n 's/^surfaces:[[:space:]]*//p' "$dir/SKILL.md" | head -1)
|
||||||
|
if [ -n "$surfaces" ] \
|
||||||
|
&& ! printf '%s' "$surfaces" | tr -d '[]' | tr ',' ' ' | grep -qw library; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
[ -L "$AGENTS_DIR/$name" ] \
|
[ -L "$AGENTS_DIR/$name" ] \
|
||||||
|| add FAIL missing "$name is in the checkout but has no link in $AGENTS_DIR — re-run install.sh"
|
|| add FAIL missing "$name is in the checkout but has no link in $AGENTS_DIR — re-run install.sh"
|
||||||
done
|
done
|
||||||
@@ -252,16 +259,10 @@ manifest_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# newer_version a b — echoes whichever sorts higher on major.minor.patch.
|
# newer_version a b — echoes whichever sorts higher on major.minor.patch.
|
||||||
|
# Lives in bin/newer-version because `task check` needs the same ordering (#34)
|
||||||
|
# and a second copy is a second thing to get wrong.
|
||||||
newer_version() {
|
newer_version() {
|
||||||
printf '%s\n%s\n' "$1" "$2" | awk -F. '
|
sh "$SELF_DIR/newer-version" "$1" "$2"
|
||||||
{ raw[NR] = $0; for (i = 1; i <= 3; i++) n[NR, i] = ($i == "" ? 0 : $i + 0) }
|
|
||||||
END {
|
|
||||||
for (i = 1; i <= 3; i++) {
|
|
||||||
if (n[1, i] > n[2, i]) { print raw[1]; exit }
|
|
||||||
if (n[1, i] < n[2, i]) { print raw[2]; exit }
|
|
||||||
}
|
|
||||||
print raw[1]
|
|
||||||
}'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
installed_v=''
|
installed_v=''
|
||||||
|
|||||||
Executable
+85
@@ -0,0 +1,85 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# generate-index — rebuild SKILLS_INDEX.md's table from skill frontmatter.
|
||||||
|
#
|
||||||
|
# The index was hand-maintained and drifted: `dream` and `experiment-spec`
|
||||||
|
# shipped with no row at all, because their rows lived on branches that were
|
||||||
|
# squash-merged after a directory reorg (#15). A gate catches that after the
|
||||||
|
# fact; generating removes the class (#29).
|
||||||
|
#
|
||||||
|
# The curated summary and trigger live in each SKILL.md as `summary:` and
|
||||||
|
# `use_when:` rather than being derived from `description:` — description is a
|
||||||
|
# paragraph written for model selection, and truncating it produces a worse
|
||||||
|
# table than the one being replaced.
|
||||||
|
#
|
||||||
|
# Everything outside the markers is left untouched: the wiring docs, the
|
||||||
|
# relationships tree and the trigger table are prose, not data.
|
||||||
|
#
|
||||||
|
# generate-index [root] rewrite the table in place
|
||||||
|
# generate-index --check [root] exit 1 if the table is out of date
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
CHECK=0
|
||||||
|
if [ "${1:-}" = "--check" ]; then CHECK=1; shift; fi
|
||||||
|
ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
||||||
|
INDEX="$ROOT/SKILLS_INDEX.md"
|
||||||
|
|
||||||
|
BEGIN='<!-- BEGIN skills-table -->'
|
||||||
|
END='<!-- END skills-table -->'
|
||||||
|
|
||||||
|
[ -f "$INDEX" ] || { echo "ERROR: missing $INDEX" >&2; exit 1; }
|
||||||
|
grep -qF "$BEGIN" "$INDEX" || { echo "ERROR: $INDEX has no $BEGIN marker" >&2; exit 1; }
|
||||||
|
grep -qF "$END" "$INDEX" || { echo "ERROR: $INDEX has no $END marker" >&2; exit 1; }
|
||||||
|
|
||||||
|
# field <file> <key> — a scalar frontmatter value, quotes stripped.
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$2:[[:space:]]*//p" "$1" | head -1 | sed 's/^"//; s/"$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
table=$(mktemp)
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '| Skill | Description | Use when | Surfaces |\n'
|
||||||
|
printf '|-------|-------------|----------|----------|\n'
|
||||||
|
} > "$table"
|
||||||
|
|
||||||
|
for dir in "$ROOT"/skills/*/; do
|
||||||
|
[ -f "$dir/SKILL.md" ] || continue
|
||||||
|
name=$(basename "$dir")
|
||||||
|
summary=$(field "$dir/SKILL.md" summary)
|
||||||
|
use_when=$(field "$dir/SKILL.md" use_when)
|
||||||
|
surfaces=$(field "$dir/SKILL.md" surfaces | tr -d '[]')
|
||||||
|
|
||||||
|
if [ -z "$summary" ] || [ -z "$use_when" ]; then
|
||||||
|
echo "ERROR: skills/$name has no 'summary:'/'use_when:' — the index table is generated from them" >&2
|
||||||
|
fail=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '| `%s` | %s | %s | %s |\n' "$name" "$summary" "$use_when" "$surfaces" >> "$table"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
rm -f "$table"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
out=$(mktemp)
|
||||||
|
awk -v begin="$BEGIN" -v end="$END" -v tablefile="$table" '
|
||||||
|
$0 == begin { print; while ((getline line < tablefile) > 0) print line; skip = 1; next }
|
||||||
|
$0 == end { print; skip = 0; next }
|
||||||
|
!skip { print }
|
||||||
|
' "$INDEX" > "$out"
|
||||||
|
rm -f "$table"
|
||||||
|
|
||||||
|
if [ "$CHECK" -eq 1 ]; then
|
||||||
|
if cmp -s "$out" "$INDEX"; then
|
||||||
|
rm -f "$out"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
rm -f "$out"
|
||||||
|
echo "ERROR: SKILLS_INDEX.md is out of date — run 'task index' and commit the result" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$out" "$INDEX"
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# newer-version A B — echo whichever of two dotted versions sorts higher.
|
||||||
|
# Equal versions echo A. Missing components count as zero, so 0.9 == 0.9.0.
|
||||||
|
#
|
||||||
|
# Exists because two callers need ordering rather than equality:
|
||||||
|
# - bin/doctor, comparing the installed plugin generation against the newest
|
||||||
|
# manifest on the host (#30);
|
||||||
|
# - task check, which must fail when plugin.json is BEHIND the latest tag and
|
||||||
|
# stay quiet when it is ahead — the normal state on a checkout that has not
|
||||||
|
# fetched the tag CI just pushed (#34).
|
||||||
|
#
|
||||||
|
# sort -V would do it in one line and is not portable enough for this fleet.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
[ $# -eq 2 ] || { echo "usage: newer-version A B" >&2; exit 2; }
|
||||||
|
|
||||||
|
printf '%s\n%s\n' "$1" "$2" | awk -F. '
|
||||||
|
{ raw[NR] = $0; for (i = 1; i <= 3; i++) n[NR, i] = ($i == "" ? 0 : $i + 0) }
|
||||||
|
END {
|
||||||
|
for (i = 1; i <= 3; i++) {
|
||||||
|
if (n[1, i] > n[2, i]) { print raw[1]; exit }
|
||||||
|
if (n[1, i] < n[2, i]) { print raw[2]; exit }
|
||||||
|
}
|
||||||
|
print raw[1]
|
||||||
|
}'
|
||||||
+11
@@ -131,6 +131,17 @@ wire_skills() {
|
|||||||
name="$(basename "$dir")"
|
name="$(basename "$dir")"
|
||||||
target="${dir%/}"
|
target="${dir%/}"
|
||||||
|
|
||||||
|
# A skill declares its surfaces; this installer serves exactly one of
|
||||||
|
# them. A chat-only skill linked into Crush can never fire and competes
|
||||||
|
# for selection with ones that can (#29). A source with no declaration
|
||||||
|
# is foreign — Anthropic's official set has no reason to know this
|
||||||
|
# convention — so silence means "link it".
|
||||||
|
surfaces="$(sed -n 's/^surfaces:[[:space:]]*//p' "$dir/SKILL.md" | head -1)"
|
||||||
|
if [ -n "$surfaces" ] && ! printf '%s' "$surfaces" | tr -d '[]' | tr ',' ' ' | grep -qw library; then
|
||||||
|
log "skipping $name — surfaces: $surfaces does not include the library"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if printf '%s' "$desired" | grep -qx -- "$name"; then
|
if printf '%s' "$desired" | grep -qx -- "$name"; then
|
||||||
log "COLLISION: $name is offered by $target too; keeping $AGENTS_DIR/$name -> $(readlink "$AGENTS_DIR/$name" 2>/dev/null || echo '?')"
|
log "COLLISION: $name is offered by $target too; keeping $AGENTS_DIR/$name -> $(readlink "$AGENTS_DIR/$name" 2>/dev/null || echo '?')"
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: atdd
|
name: atdd
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Acceptance Test-Driven Development workflow"
|
||||||
|
use_when: "Feature development with acceptance criteria"
|
||||||
description: Implement user stories using the ATDD workflow — Red (failing acceptance test) → Green (minimal implementation) → Refactor. One story at a time.
|
description: Implement user stories using the ATDD workflow — Red (failing acceptance test) → Green (minimal implementation) → Refactor. One story at a time.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: cad-babysit
|
name: cad-babysit
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Focused human-in-the-loop session working CAD's stuck queue (read-only)"
|
||||||
|
use_when: "Picking up whatever `cad stuck` flags, with a human present"
|
||||||
description: Focused human-in-the-loop session working CAD's stuck queue (cad-stuck output). Read-only — diagnoses, never dispatches, arms a label, or closes an issue. Use when picking up whatever CAD has run into trouble on, with a human present.
|
description: Focused human-in-the-loop session working CAD's stuck queue (cad-stuck output). Read-only — diagnoses, never dispatches, arms a label, or closes an issue. Use when picking up whatever CAD has run into trouble on, with a human present.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: clean-code
|
name: clean-code
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Clean code principles with Go adaptation"
|
||||||
|
use_when: "Code review, refactoring, new code"
|
||||||
description: Write code that humans can read, understand, and safely change. Apply SOLID, GRASP, and Clean Code principles during the REFACTOR phase of TDD.
|
description: Write code that humans can read, understand, and safely change. Apply SOLID, GRASP, and Clean Code principles during the REFACTOR phase of TDD.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -278,5 +281,6 @@ During refactoring:
|
|||||||
|
|
||||||
- `references/clean-code.md` — detailed naming rules, object calisthenics
|
- `references/clean-code.md` — detailed naming rules, object calisthenics
|
||||||
- `references/code-smells.md` — comprehensive smell catalog with examples
|
- `references/code-smells.md` — comprehensive smell catalog with examples
|
||||||
|
- `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
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# Clean Code: Python Adaptation
|
||||||
|
|
||||||
|
The principles in `SKILL.md` are language-agnostic. This file covers the places where
|
||||||
|
Python's idioms differ from the Go examples, and where following the Go shape produces
|
||||||
|
bad Python.
|
||||||
|
|
||||||
|
The canonical stack (ruff, mypy --strict, pytest, uv, pydantic-settings, src-layout)
|
||||||
|
lives in `~/dev/.context/AGENT.md` and is not restated here — one source of truth, and
|
||||||
|
it is not this file.
|
||||||
|
|
||||||
|
## Reveal intent with types, not comments
|
||||||
|
|
||||||
|
`mypy --strict` is the Python analogue of Go's compiler, and it is non-negotiable for
|
||||||
|
the same reason: a type is a claim the machine checks, and a comment is a claim nobody
|
||||||
|
checks. Two consequences for clean code specifically:
|
||||||
|
|
||||||
|
- **`Any` is a hole in the argument.** Each one says "the reader is on their own here".
|
||||||
|
If a value is genuinely polymorphic, say so precisely — `Protocol`, a union, a
|
||||||
|
`TypeVar` — rather than surrendering.
|
||||||
|
- **A `# type: ignore` needs the error code and a reason**: `# type: ignore[arg-type] #
|
||||||
|
boto3 stubs lag the API`. A bare ignore is an unexplained exception to a rule the rest
|
||||||
|
of the file follows.
|
||||||
|
|
||||||
|
## Errors: chain them, never swallow them
|
||||||
|
|
||||||
|
Go's `fmt.Errorf("operation: %w", err)` is `raise` with `from`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Bad — the original cause is gone, and so is the stack that produced it
|
||||||
|
try:
|
||||||
|
row = fetch(invoice_id)
|
||||||
|
except KeyError:
|
||||||
|
raise InvoiceNotFound(invoice_id)
|
||||||
|
|
||||||
|
# Good — the chain survives; the traceback shows both
|
||||||
|
try:
|
||||||
|
row = fetch(invoice_id)
|
||||||
|
except KeyError as e:
|
||||||
|
raise InvoiceNotFound(invoice_id) from e
|
||||||
|
```
|
||||||
|
|
||||||
|
Two rules with no exceptions:
|
||||||
|
|
||||||
|
- **Never bare `except:`.** It catches `KeyboardInterrupt` and `SystemExit` too, so it
|
||||||
|
turns Ctrl-C into a swallowed error. `except Exception` if you truly mean everything.
|
||||||
|
- **Never a silent `pass`.** An exception handler that does nothing is a decision to
|
||||||
|
ignore a failure; if that is genuinely right, the handler needs a comment saying why,
|
||||||
|
and usually a log line. Most of the time it is a bug being stored for later.
|
||||||
|
|
||||||
|
## Data classes over dicts
|
||||||
|
|
||||||
|
A `dict[str, Any]` passed between functions is Go's `map[string]interface{}` — every
|
||||||
|
reader has to reconstruct the shape from the code that writes it.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Bad: shape known only by reading every producer
|
||||||
|
def total(invoice: dict[str, Any]) -> int: ...
|
||||||
|
|
||||||
|
# Good: shape declared once, checked everywhere
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class Invoice:
|
||||||
|
id: InvoiceId
|
||||||
|
lines: tuple[Line, ...]
|
||||||
|
currency: str # ISO 4217
|
||||||
|
|
||||||
|
def total(invoice: Invoice) -> Money: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`frozen=True` where the value is conceptually immutable — it makes accidental mutation
|
||||||
|
an error rather than a surprise at a distance. Use `pydantic` models at the *boundary*
|
||||||
|
(env config, HTTP payloads) where validation is the point, and dataclasses inside,
|
||||||
|
where validation has already happened.
|
||||||
|
|
||||||
|
## Two Python-specific traps that read as clean code
|
||||||
|
|
||||||
|
**Mutable default arguments** are evaluated once, at definition:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def add_line(line: Line, lines: list[Line] = []) -> list[Line]: # shared across calls
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `None` and build inside. Ruff catches this (`B006`); the point of naming it here is
|
||||||
|
that the broken version looks tidier than the correct one.
|
||||||
|
|
||||||
|
**Truthiness is not presence.** `if not amount:` is true for `0`, `0.0`, `""`, `[]` and
|
||||||
|
`None` alike. For anything where zero is a legitimate value — money, counts, offsets —
|
||||||
|
write `if amount is None:`. This is the single most common way a Python function
|
||||||
|
silently mishandles a valid input.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `src/` layout, `pyproject.toml` only. A flat layout lets tests import the working
|
||||||
|
directory instead of the installed package, so the suite passes on a package that
|
||||||
|
cannot actually be installed.
|
||||||
|
- Module names are the unit of cohesion, as Go packages are. `utils.py` is the Python
|
||||||
|
spelling of a god package: it has no reason to change because it has no subject.
|
||||||
|
- Prefer functions to classes with one method. A class whose only job is to hold
|
||||||
|
constructor arguments and expose `run()` is a closure with ceremony.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `references/clean-code.md` — the language-agnostic practices
|
||||||
|
- `references/code-smells.md` — the smell catalogue, all of which apply here
|
||||||
|
- `tdd/references/python.md` — the test-side conventions
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: code-review
|
name: code-review
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Code smell detection + Go-specific checklist"
|
||||||
|
use_when: "Pre-merge review"
|
||||||
description: Systematic code smell detection and Go-specific review checklist. Load before any pre-merge review.
|
description: Systematic code smell detection and Go-specific review checklist. Load before any pre-merge review.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,6 +14,8 @@ Code review is smell detection and principle verification, not style policing. T
|
|||||||
|
|
||||||
**This skill is read-only analysis.** When you find issues, document them with specific file:line references and prioritize by architectural impact. For fixes, load the `refactoring` or `clean-code` skill.
|
**This skill is read-only analysis.** When you find issues, document them with specific file:line references and prioritize by architectural impact. For fixes, load the `refactoring` or `clean-code` skill.
|
||||||
|
|
||||||
|
Three similarly-named skills cover orthogonal concerns, and landing on one is no reason to miss the others: this one is **what to check**, `superpowers-requesting-code-review` is **how to commission a reviewer**, and `superpowers-receiving-code-review` is **how to behave when the feedback arrives**.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
- Pre-merge review of any pull request
|
- Pre-merge review of any pull request
|
||||||
@@ -274,6 +279,8 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
|
|||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
- `references/python-adaptation.md` — the Python anti-pattern table, and what ruff/mypy
|
||||||
|
should have caught before review
|
||||||
- `clean-code/references/code-smells.md` — comprehensive smell catalog
|
- `clean-code/references/code-smells.md` — comprehensive smell catalog
|
||||||
- Load `refactoring` skill when recommending fixes
|
- Load `refactoring` skill when recommending fixes
|
||||||
- Load `test-design` skill for reviewing test quality
|
- Load `test-design` skill for reviewing test quality
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Code Review: Python Anti-Patterns
|
||||||
|
|
||||||
|
`SKILL.md` carries the Go anti-pattern table. This is the Python one. The review
|
||||||
|
process, severity classification and output format above it are language-neutral and
|
||||||
|
apply unchanged.
|
||||||
|
|
||||||
|
Canonical stack in `~/dev/.context/AGENT.md`. Two gates decide most of this file:
|
||||||
|
`ruff` and `mypy --strict`. **If a finding is something ruff or mypy already catches,
|
||||||
|
the review comment is not "fix this line" — it is "why is the gate not running?"**
|
||||||
|
|
||||||
|
## The table
|
||||||
|
|
||||||
|
| Anti-pattern | What to look for | Why it matters |
|
||||||
|
|---|---|---|
|
||||||
|
| Bare `except:` | `except:` with no exception class | Swallows `KeyboardInterrupt` and `SystemExit`; Ctrl-C stops working |
|
||||||
|
| Silent `pass` | `except X: pass` with no comment | A decision to ignore a failure, recorded nowhere |
|
||||||
|
| Lost cause | `raise Y(...)` inside `except X as e` with no `from e` | Traceback loses the original failure — the Python analogue of a naked `fmt.Errorf` |
|
||||||
|
| Mutable default | `def f(x, acc=[])` or `={}` | Evaluated once at definition; state leaks between calls |
|
||||||
|
| Truthiness for presence | `if not amount:` where `0` is valid | Silently mishandles zero, empty string, empty list |
|
||||||
|
| `Any` as an escape | `Any` in a signature that could be `Protocol`/union/`TypeVar` | Removes exactly the checking `--strict` was adopted for |
|
||||||
|
| Unexplained ignore | bare `# type: ignore` | No error code, no reason; permanent by default |
|
||||||
|
| `assert` in production code | `assert user.is_admin` outside tests | `python -O` strips asserts — the check vanishes in a tuned deployment |
|
||||||
|
| Import-time side effects | connections, env reads, or I/O at module scope | Cannot be injected, cannot be tested, ordering-dependent |
|
||||||
|
| Sync I/O in `async def` | `requests.get`, `time.sleep`, blocking file reads | Blocks the whole event loop; looks concurrent, is not |
|
||||||
|
| Un-awaited coroutine | a call to an `async def` with no `await` | Silently does nothing; "coroutine was never awaited" is a warning, not an error |
|
||||||
|
| `float` for money | `float` amounts, `round()` on currency | `0.1 + 0.2 != 0.3`; use `Decimal` or int minor units |
|
||||||
|
| Broad `Exception` re-raise | `except Exception` around a whole function body | Turns a specific failure into an unclassifiable one |
|
||||||
|
| Mutating a shared default/global | module-level `list`/`dict` mutated by functions | The Python spelling of shared mutable state |
|
||||||
|
| `dict[str, Any]` as a domain type | dicts passed between layers | Shape known only by reading every producer; use a dataclass |
|
||||||
|
| Star imports | `from x import *` | Breaks `--strict`, hides shadowing, defeats grep |
|
||||||
|
| `os.path` string surgery | manual path joining, `+ "/"` | `pathlib` exists and is correct on every platform |
|
||||||
|
| `subprocess(..., shell=True)` with interpolation | f-strings into a shell command | Injection; pass a list, no shell |
|
||||||
|
|
||||||
|
## What to check that a linter cannot
|
||||||
|
|
||||||
|
- **Does the test suite assert the failure mode, or only the happy path?** `pytest.raises`
|
||||||
|
with no `match=` accepts any message, including one from an unrelated bug.
|
||||||
|
- **Are fixtures doing real work?** A `conftest.py` fixture with a `session` scope and
|
||||||
|
mutable state couples every test that touches it; failures then depend on ordering.
|
||||||
|
- **Is the mock mocking a boundary or a collaborator you own?** The latter tests the mock.
|
||||||
|
- **Does the type say what the code means?** `-> dict` and `-> tuple[int, int]` both pass
|
||||||
|
`--strict` and both tell the reader nothing. A named type is a review comment worth
|
||||||
|
making.
|
||||||
|
- **Would `--strict` actually run on this file?** Check for a per-module override in
|
||||||
|
`pyproject.toml` silently exempting it. An exemption nobody revisits is a hole with a
|
||||||
|
changelog entry.
|
||||||
|
|
||||||
|
## Severity in this context
|
||||||
|
|
||||||
|
- **Blocking:** anything in the table above under security or correctness — `shell=True`
|
||||||
|
interpolation, `assert` as an authorization check, `float` money, sync I/O in async.
|
||||||
|
- **Should fix:** typing escapes (`Any`, unexplained ignores), mutable defaults,
|
||||||
|
truthiness-for-presence.
|
||||||
|
- **Consider:** structural comments — dataclass instead of dict, narrower Protocol.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `clean-code/references/python-adaptation.md` — the style-level conventions
|
||||||
|
- `solid/references/python-adaptation.md` — Protocol over ABC, DIP in Python
|
||||||
|
- `tdd/references/python.md` — the test-side conventions this review checks against
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: cognitive-load
|
name: cognitive-load
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Cognitive complexity analysis (CLI score)"
|
||||||
|
use_when: "Identifying hotspots, before refactoring"
|
||||||
description: Measure how much mental effort a codebase demands from developers. Use when identifying hotspots, before architectural decisions, or when code feels hard to understand.
|
description: Measure how much mental effort a codebase demands from developers. Use when identifying hotspots, before architectural decisions, or when code feels hard to understand.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: debug
|
name: debug
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Hypothesis-first debugging discipline"
|
||||||
|
use_when: "Any test failure or bug whose cause is not immediately obvious"
|
||||||
description: Systematic hypothesis-first debugging. Generate 3-5 ranked hypotheses with verification steps before suggesting any fix. Use when encountering a bug, test failure, or unexpected behavior.
|
description: Systematic hypothesis-first debugging. Generate 3-5 ranked hypotheses with verification steps before suggesting any fix. Use when encountering a bug, test failure, or unexpected behavior.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,6 +31,33 @@ Debugging is hypothesis generation, not fix generation. The first instinct — "
|
|||||||
2. **Generate 3-5 hypotheses, ordered by likelihood (most likely first).** Fewer than 3 means you stopped thinking; more than 5 means you are not prioritizing.
|
2. **Generate 3-5 hypotheses, ordered by likelihood (most likely first).** Fewer than 3 means you stopped thinking; more than 5 means you are not prioritizing.
|
||||||
3. **Diagnose only — do not fix in this skill.** The fix happens in a separate TDD cycle (load `tdd` skill) once a hypothesis is confirmed.
|
3. **Diagnose only — do not fix in this skill.** The fix happens in a separate TDD cycle (load `tdd` skill) once a hypothesis is confirmed.
|
||||||
|
|
||||||
|
## When the bug spans components
|
||||||
|
|
||||||
|
A system with boundaries (CI → build → signing, API → service → database) hides the
|
||||||
|
question "which component is wrong?" behind the question "why is the output wrong?".
|
||||||
|
Specify, as a pre-step, what to log entering and leaving **each** boundary, plus
|
||||||
|
whether config and environment actually propagated. One run of that narrows the search
|
||||||
|
to a single component.
|
||||||
|
|
||||||
|
This does not suspend Iron Law 2. Still emit 3-5 hypotheses — the boundary spec comes
|
||||||
|
first in the output, and each hypothesis says which boundary observation would kill it.
|
||||||
|
What the pre-step forbids is hypotheses that quietly assume a component is the faulty
|
||||||
|
one before any evidence says so.
|
||||||
|
|
||||||
|
## When several fixes have already failed
|
||||||
|
|
||||||
|
Three or more plausible fixes that failed to eliminate a symptom is not a run of bad
|
||||||
|
luck — it is evidence that nobody has identified the mechanism. (Fixes that changed
|
||||||
|
nothing at all count double: they are stronger evidence of a misidentified mechanism
|
||||||
|
than fixes that partially worked.) Each one has been narrowing a window rather than
|
||||||
|
closing it, and a fourth guess buys another tenth of the rate plus a layer of
|
||||||
|
interacting guards nobody can reason about.
|
||||||
|
|
||||||
|
Say that explicitly — the refusal is the finding, not a preamble to it. Then aim the
|
||||||
|
hypotheses one level up: the premise those fixes share is itself the thing to falsify.
|
||||||
|
This is not an exemption from the output format. It is the format, pointed at the
|
||||||
|
assumption instead of at the symptom.
|
||||||
|
|
||||||
## Process
|
## Process
|
||||||
|
|
||||||
### Step 1: Read the failure
|
### Step 1: Read the failure
|
||||||
@@ -144,3 +174,15 @@ This skill produces high-volume mechanical output (hypothesis enumeration) and i
|
|||||||
- After a hypothesis is confirmed, load `tdd` skill — write a failing regression test that proves the bug exists, then fix it.
|
- After a hypothesis is confirmed, load `tdd` skill — write a failing regression test that proves the bug exists, then fix it.
|
||||||
- For test failures specifically caused by mock-vs-real divergence, also load `tdd/references/testing-anti-patterns.md`.
|
- For test failures specifically caused by mock-vs-real divergence, also load `tdd/references/testing-anti-patterns.md`.
|
||||||
- Load `code-review` skill if the diagnosis surfaces a structural issue (god object, shotgun surgery) rather than a pointwise bug.
|
- Load `code-review` skill if the diagnosis surfaces a structural issue (god object, shotgun surgery) rather than a pointwise bug.
|
||||||
|
|
||||||
|
Techniques kept in `debug/references/`, inherited from the deleted
|
||||||
|
`superpowers-systematic-debugging` (#14):
|
||||||
|
|
||||||
|
- `root-cause-tracing.md` — load when the failure surfaces far from its trigger and the
|
||||||
|
stack is the only thread back to it.
|
||||||
|
- `defense-in-depth.md` — load after a root cause is confirmed, to decide which layers
|
||||||
|
should have caught it.
|
||||||
|
- `condition-based-waiting.md` — load whenever a timeout appears anywhere in the report,
|
||||||
|
including as one of the fixes already tried. A bumped timeout is a symptom.
|
||||||
|
- `find-polluter.sh` — run when a test passes alone and fails in the suite, or when
|
||||||
|
something in the tree appears that no test admits to creating.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: discovery-framing
|
name: discovery-framing
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Problem-before-solution discovery/framing method"
|
||||||
|
use_when: "Framing/validating a problem before any solution; opportunity sizing"
|
||||||
description: Run the discovery / framing phase of product innovation — define and quantify a focused customer problem BEFORE any solution exists. Four movements: understand the problem space, learn about customers, identify and size problems, decide pursue/table/kill. Produces a problem brief per prioritized opportunity. Use when an idea is still vague, when someone is jumping to solutions before the problem is validated, when scoping early research, or when deciding which problems are worth pursuing. Trigger phrases include "frame the problem", "discovery", "problem statement", "is this worth building", "early research", "opportunity sizing", "we have an idea for", "how might we", "what problem are we solving".
|
description: Run the discovery / framing phase of product innovation — define and quantify a focused customer problem BEFORE any solution exists. Four movements: understand the problem space, learn about customers, identify and size problems, decide pursue/table/kill. Produces a problem brief per prioritized opportunity. Use when an idea is still vague, when someone is jumping to solutions before the problem is validated, when scoping early research, or when deciding which problems are worth pursuing. Trigger phrases include "frame the problem", "discovery", "problem statement", "is this worth building", "early research", "opportunity sizing", "we have an idea for", "how might we", "what problem are we solving".
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: dream
|
name: dream
|
||||||
|
surfaces: [claude-code, library, chat]
|
||||||
|
summary: "Reflective consolidation pass over an agent's memory directory"
|
||||||
|
use_when: "Pruning/merging memory files, rebuilding a memory index"
|
||||||
description: >
|
description: >
|
||||||
Run a "dream" — a reflective memory consolidation pass over an agent's memory
|
Run a "dream" — a reflective memory consolidation pass over an agent's memory
|
||||||
directory. Use this skill whenever the user says "dream", "run a dream", "consolidate
|
directory. Use this skill whenever the user says "dream", "run a dream", "consolidate
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: experiment-spec
|
name: experiment-spec
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Rigorous experiment spec — falsifiable hypothesis, quantitative acceptance, null-result protocol"
|
||||||
|
use_when: "Scoping a research/ML phase instead of a feature"
|
||||||
description: Write a rigorous experiment spec for a research phase before any code or training runs. Use instead of feature-spec for scientific/ML research projects. Enforces falsifiable hypothesis, quantitative acceptance criteria, baseline comparison, and null-result protocol.
|
description: Write a rigorous experiment spec for a research phase before any code or training runs. Use instead of feature-spec for scientific/ML research projects. Enforces falsifiable hypothesis, quantitative acceptance criteria, baseline comparison, and null-result protocol.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: feature-spec
|
name: feature-spec
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Implementation-level feature spec (tighter than spec-driven-dev)"
|
||||||
|
use_when: "Scoping one feature inside an already-specced project"
|
||||||
description: Write a tight implementation-level spec for a single feature or component before coding it. Use after a project-level spec exists (see spec-driven-dev) and you need to nail down one feature inside it.
|
description: Write a tight implementation-level spec for a single feature or component before coding it. Use after a project-level spec exists (see spec-driven-dev) and you need to nail down one feature inside it.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -29,6 +32,90 @@ A feature spec is the implementation-level contract for one feature or component
|
|||||||
2. **An "Out of scope" section is mandatory.** If you do not draw the boundary, the implementer (you, future-you, or another agent) will guess wrong.
|
2. **An "Out of scope" section is mandatory.** If you do not draw the boundary, the implementer (you, future-you, or another agent) will guess wrong.
|
||||||
3. **Every technical decision in "Technical approach" must have a rationale.** A decision is any choice where a reasonable alternative existed and was not taken. "Use Postgres" is not a decision; "use Postgres because the rest of the stack uses it and we need transactions across user + invoice" is.
|
3. **Every technical decision in "Technical approach" must have a rationale.** A decision is any choice where a reasonable alternative existed and was not taken. "Use Postgres" is not a decision; "use Postgres because the rest of the stack uses it and we need transactions across user + invoice" is.
|
||||||
|
|
||||||
|
## EARS-lite acceptance criteria
|
||||||
|
|
||||||
|
Measurable criteria answer "is it done?". They do not always answer "what should it do
|
||||||
|
when X happens?" — and behavioural requirements written as prose are where ambiguity
|
||||||
|
survives review. For any criterion that is conditional or event-driven, use EARS-lite
|
||||||
|
instead of prose:
|
||||||
|
|
||||||
|
```
|
||||||
|
WHEN <trigger> THE SYSTEM SHALL <observable response>
|
||||||
|
WHILE <state> THE SYSTEM SHALL <observable response>
|
||||||
|
IF <unwanted condition> THEN THE SYSTEM SHALL <observable response>
|
||||||
|
```
|
||||||
|
|
||||||
|
There is a fourth, unconditional form. Use it for invariants — the properties that must
|
||||||
|
hold with no trigger at all, which are usually the ones that matter most:
|
||||||
|
|
||||||
|
```
|
||||||
|
THE SYSTEM SHALL <observable response>
|
||||||
|
```
|
||||||
|
|
||||||
|
Worked, deliberately from a domain unlike whatever you are specifying:
|
||||||
|
|
||||||
|
```
|
||||||
|
THE SYSTEM SHALL reject a firmware image whose signature does not verify
|
||||||
|
WHEN a sensor reports a temperature above the shutdown limit
|
||||||
|
THE SYSTEM SHALL open the contactor within 200ms
|
||||||
|
WHILE the contactor is open
|
||||||
|
THE SYSTEM SHALL refuse every start command
|
||||||
|
IF a sensor reading is missing or stale by more than one interval
|
||||||
|
THEN THE SYSTEM SHALL treat it as above the limit
|
||||||
|
```
|
||||||
|
|
||||||
|
The example is off-domain on purpose. An on-domain example gets copied: a spec written
|
||||||
|
against an invoice-flavoured example inherited a requirement about extraction confidence
|
||||||
|
that its feature request never mentioned, and the author only noticed on review. Read
|
||||||
|
the shapes, not the content.
|
||||||
|
|
||||||
|
Each conditional line names a trigger and one observable response, which usually maps
|
||||||
|
onto a single test. The `WHILE` and unconditional forms are universally quantified —
|
||||||
|
"every start command", "a firmware image" — so they map onto a property or a set of
|
||||||
|
cases, not one assertion. That is still worth writing: it closes the requirements half
|
||||||
|
of the audit trail, which is otherwise issue-body prose.
|
||||||
|
|
||||||
|
**This is an option, not a replacement.** Threshold criteria ("p99 < 200ms under 100
|
||||||
|
RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional
|
||||||
|
behaviour, error handling, and state-dependent rules.
|
||||||
|
|
||||||
|
## Context snapshot
|
||||||
|
|
||||||
|
The executor of this spec will not see the conversation that produced it, and often
|
||||||
|
cannot see the architecture document either — a different agent, a fresh session, a
|
||||||
|
different machine. A spec that says "see the architecture doc" hands them a lookup they
|
||||||
|
may not be able to perform.
|
||||||
|
|
||||||
|
So inline the specific facts this spec depends on, not the whole document:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Context snapshot
|
||||||
|
Taken from <source> @ <commit-or-date>. Re-check before implementing if stale.
|
||||||
|
|
||||||
|
- The mailbox poller writes rows to `invoices` with `state = 'received'`.
|
||||||
|
- The payment-file writer reads `invoices` and emits one file per batch.
|
||||||
|
- Money is int64 minor units plus an ISO-4217 code; float is banned repo-wide.
|
||||||
|
```
|
||||||
|
|
||||||
|
Three rules keep this from becoming a second source of truth, which is the real
|
||||||
|
objection to inlining and a good one:
|
||||||
|
|
||||||
|
1. **Snapshot what the implementer cannot look up and cannot proceed without.** That is
|
||||||
|
wider than "facts this spec's decisions rest on" — a table name may underpin no
|
||||||
|
decision here and still be the thing that blocks writing the migration. It is still
|
||||||
|
narrower than the whole document: if a fact changes nothing and blocks nothing, leave
|
||||||
|
it out, and say what you left out so the omission reads as deliberate.
|
||||||
|
2. **Always cite source and version.** A snapshot without provenance cannot be
|
||||||
|
revalidated, and an unrevalidatable copy is exactly the drift everyone fears.
|
||||||
|
3. **If you could not read the source either, say so in those words.** Writing a spec
|
||||||
|
against a document you cannot open is common and is not a reason to skip the section
|
||||||
|
— it is a reason to mark the whole block `UNVERIFIED — reconstructed, not read`, and
|
||||||
|
to make confirming it the implementer's first task, before the first failing test.
|
||||||
|
An unmarked guess is the only genuinely dangerous version.
|
||||||
|
4. **The snapshot is evidence, not authority.** Open the section with one line to that
|
||||||
|
effect — "where this disagrees with the source, the source wins and this spec is
|
||||||
|
stale" — rather than annotating each fact.
|
||||||
|
|
||||||
## Spec Template
|
## Spec Template
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
@@ -39,12 +126,21 @@ A feature spec is the implementation-level contract for one feature or component
|
|||||||
What problem does this feature solve? For whom? Why now?
|
What problem does this feature solve? For whom? Why now?
|
||||||
(2-4 sentences. If you cannot answer "why now," ask before writing the rest.)
|
(2-4 sentences. If you cannot answer "why now," ask before writing the rest.)
|
||||||
|
|
||||||
|
## Context snapshot
|
||||||
|
[Facts the implementer cannot look up, with source and version. See "Context snapshot" above.]
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- [ ] Criterion 1 — measurable and verifiable
|
- [ ] Criterion 1 — measurable and verifiable
|
||||||
- [ ] Criterion 2 — measurable and verifiable
|
- [ ] Criterion 2 — measurable and verifiable
|
||||||
- [ ] Criterion 3 — measurable and verifiable
|
- [ ] Criterion 3 — measurable and verifiable
|
||||||
|
|
||||||
|
### Behavioural criteria (EARS-lite)
|
||||||
|
|
||||||
|
[Conditional and event-driven behaviour, one trigger and one observable response per
|
||||||
|
line. Not checkboxes — these are properties, and several need more than one test each.
|
||||||
|
Omit the subsection entirely if the feature has no conditional behaviour.]
|
||||||
|
|
||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
Non-negotiable requirements the solution must satisfy.
|
Non-negotiable requirements the solution must satisfy.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: gitea-ci
|
name: gitea-ci
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Gitea CI/CD pipeline with act_runner, buildah, k3s, GitHub mirror"
|
||||||
|
use_when: "Setting up or debugging CI on this infra"
|
||||||
description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, shared buildctl image-build script, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere.
|
description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, shared buildctl image-build script, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: grill-me
|
name: grill-me
|
||||||
|
surfaces: [claude-code, library, chat]
|
||||||
|
summary: "Structured plan interrogation — Quick Poke, Full Grill, Pre-mortem"
|
||||||
|
use_when: "Stress-testing a plan before committing; end of Diamond 1; before promoting to pre-prod"
|
||||||
description: Critically interrogates plans, ideas, and proposals to make them sharper, more robust, and better grounded — with the explicit goal of producing a meaningfully better plan by the end. Use this skill whenever the user wants to stress-test a plan, prepare for hard questions, check if an idea is ready to act on, validate a hypothesis, or explicitly asks to be challenged, questioned, or grilled. Also trigger when a conversation has been exploring an idea for a while and it's time to converge — use this skill to check if the idea is actually ready. Trigger phrases include "grill me", "challenge this", "poke holes", "am I missing something", "is this ready", "stress-test this", "devil's advocate", "what am I not seeing".
|
description: Critically interrogates plans, ideas, and proposals to make them sharper, more robust, and better grounded — with the explicit goal of producing a meaningfully better plan by the end. Use this skill whenever the user wants to stress-test a plan, prepare for hard questions, check if an idea is ready to act on, validate a hypothesis, or explicitly asks to be challenged, questioned, or grilled. Also trigger when a conversation has been exploring an idea for a while and it's time to converge — use this skill to check if the idea is actually ready. Trigger phrases include "grill me", "challenge this", "poke holes", "am I missing something", "is this ready", "stress-test this", "devil's advocate", "what am I not seeing".
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: own-the-task
|
name: own-the-task
|
||||||
|
surfaces: [claude-code, library, chat]
|
||||||
|
summary: "Delegation ownership — Intake → Brief → Plan → Execute"
|
||||||
|
use_when: "A task is handed over open-ended; \"take this off my plate\"; before a plan exists"
|
||||||
description: Take ownership of a delegated task like a hired employee — interview first, write a handover brief, propose a plan, then execute. Use whenever the user says "take this off my plate", "own this", "act as my AI employee", "interview me first", "ask me questions before you start", or delegates anything open-ended where the real goal, the why, and the definition of done are not yet on the table. Trigger even when "interview" is never said — any delegation where getting the intent wrong would waste the whole effort belongs here.
|
description: Take ownership of a delegated task like a hired employee — interview first, write a handover brief, propose a plan, then execute. Use whenever the user says "take this off my plate", "own this", "act as my AI employee", "interview me first", "ask me questions before you start", or delegates anything open-ended where the real goal, the why, and the definition of done are not yet on the table. Trigger even when "interview" is never said — any delegation where getting the intent wrong would waste the whole effort belongs here.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: planning
|
name: planning
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Planning and task breakdown"
|
||||||
|
use_when: "Sprint planning, task decomposition"
|
||||||
description: Decompose work into small, verifiable, ordered tasks. Use when you have a spec or clear requirements and need to break work into implementable units.
|
description: Decompose work into small, verifiable, ordered tasks. Use when you have a spec or clear requirements and need to break work into implementable units.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: playwright
|
name: playwright
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Playwright E2E testing — setup, HTMX patterns, SSE streams, CI wiring"
|
||||||
|
use_when: "Adding browser tests to any project with a web UI"
|
||||||
description: Add and run Playwright E2E tests for any project with a web UI — covers setup, HTMX-specific patterns, SSE/streaming, Taskfile integration, and CI wiring.
|
description: Add and run Playwright E2E tests for any project with a web UI — covers setup, HTMX-specific patterns, SSE/streaming, Taskfile integration, and CI wiring.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: problem-analysis
|
name: problem-analysis
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Deep problem understanding before coding"
|
||||||
|
use_when: "Starting any non-trivial task"
|
||||||
description: Deeply understand a problem before coding begins. Identify the real problem, stakeholders, constraints, and success criteria. Use before starting any non-trivial feature.
|
description: Deeply understand a problem before coding begins. Identify the real problem, stakeholders, constraints, and success criteria. Use before starting any non-trivial feature.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: refactoring
|
name: refactoring
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Systematic refactoring methodology (Fowler)"
|
||||||
|
use_when: "When improving existing code"
|
||||||
description: Systematic refactoring methodology based on Martin Fowler's catalog. Apply during the REFACTOR phase of TDD, never while making code changes that modify behavior.
|
description: Systematic refactoring methodology based on Martin Fowler's catalog. Apply during the REFACTOR phase of TDD, never while making code changes that modify behavior.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: regulatory-risk-assessment
|
name: regulatory-risk-assessment
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Structured risk register for regulated-industry features"
|
||||||
|
use_when: "Filing a CAD issue (needs Risk: level); features touching payments, auth, external APIs, user data"
|
||||||
description: Produce a structured regulatory risk assessment / risk-register entry for regulated-industry features (banking, payments, PSD2/PSR, DORA, AML). Use when filing a CAD issue that needs a Risk:LOW/MEDIUM/HIGH level, or designing features touching payments, auth, data storage, or external APIs ("what are the risks?", "compliance gate", "risk register").
|
description: Produce a structured regulatory risk assessment / risk-register entry for regulated-industry features (banking, payments, PSD2/PSR, DORA, AML). Use when filing a CAD issue that needs a Risk:LOW/MEDIUM/HIGH level, or designing features touching payments, auth, data storage, or external APIs ("what are the risks?", "compliance gate", "risk register").
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: secret-hygiene
|
name: secret-hygiene
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Stop credentials rendering into the session transcript; safe rotation chain when one leaks"
|
||||||
|
use_when: "Before any kubectl/SOPS/token command; after a confirmed leak"
|
||||||
description: Prevent agent sessions from rendering credentials into the transcript, and recover safely when one leaks. Load BEFORE any command that touches credentials - kubectl exec/env/describe, kubectl get secret, whole-file SOPS decryption, token generation or rotation, envFrom inspection, or edits to encrypted config. Also load when a credential has already been exposed and a rotation chain is needed. Triggers - "rotate this token", "decrypt the config", "check the env var", "leaked credential", "did that print a secret", "why is this secret wrong".
|
description: Prevent agent sessions from rendering credentials into the transcript, and recover safely when one leaks. Load BEFORE any command that touches credentials - kubectl exec/env/describe, kubectl get secret, whole-file SOPS decryption, token generation or rotation, envFrom inspection, or edits to encrypted config. Also load when a credential has already been exposed and a rotation chain is needed. Triggers - "rotate this token", "decrypt the config", "check the env var", "leaked credential", "did that print a secret", "why is this secret wrong".
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: session-retrospective
|
name: session-retrospective
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Surface non-obvious learnings from a session log"
|
||||||
|
use_when: "After a coding session ends, before context is lost"
|
||||||
description: Review a completed coding session (from brain/sessions/*.jsonl) and surface non-obvious learnings worth preserving. Use after a session ends, before context is lost.
|
description: Review a completed coding session (from brain/sessions/*.jsonl) and surface non-obvious learnings worth preserving. Use after a session ends, before context is lost.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: solid
|
name: solid
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "SOLID principles with Go examples"
|
||||||
|
use_when: "Design review, architecture decisions"
|
||||||
description: Apply SOLID design principles in Go. Use during architecture decisions, design reviews, and when adding new abstractions.
|
description: Apply SOLID design principles in Go. Use during architecture decisions, design reviews, and when adding new abstractions.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -255,5 +258,6 @@ Domain types know nothing about HTTP, SQL, or external APIs.
|
|||||||
|
|
||||||
- `references/solid-principles.md` — canonical SOLID reference with TypeScript examples
|
- `references/solid-principles.md` — canonical SOLID reference with TypeScript examples
|
||||||
- `references/go-adaptation.md` — this workspace's Go-specific rewrite of each principle
|
- `references/go-adaptation.md` — this workspace's Go-specific rewrite of each principle
|
||||||
|
- `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
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# SOLID Principles: Python Adaptation
|
||||||
|
|
||||||
|
`references/go-adaptation.md` rewrites each principle for Go. This file does the same
|
||||||
|
for Python, and marks the places where the Go rewrite's advice does *not* carry over.
|
||||||
|
|
||||||
|
The canonical Python stack lives in `~/dev/.context/AGENT.md`; it is not restated here.
|
||||||
|
|
||||||
|
## The one idea that changes everything: `Protocol`
|
||||||
|
|
||||||
|
Go's interfaces are structural — a type satisfies one by having the methods, with no
|
||||||
|
declaration. Python has the same thing since 3.8, and it is the right default:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
class InvoiceStore(Protocol):
|
||||||
|
def get(self, invoice_id: InvoiceId) -> Invoice: ...
|
||||||
|
def save(self, invoice: Invoice) -> None: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Define the Protocol where it is *consumed*, not where it is implemented.** This is
|
||||||
|
Go's "accept interfaces, return structs" and it is what makes DIP real: the high-level
|
||||||
|
module owns the abstraction, and the adapter depends on it rather than the reverse.
|
||||||
|
- **Prefer `Protocol` to `ABC`.** An ABC forces implementers to inherit, which couples
|
||||||
|
them to your package and makes a third-party class impossible to adapt without a
|
||||||
|
wrapper. Use an ABC only when you genuinely need shared implementation, not just a
|
||||||
|
contract.
|
||||||
|
- Add `@runtime_checkable` only if you actually call `isinstance` — and if you are
|
||||||
|
calling `isinstance` against a Protocol, check whether the real problem is a missing
|
||||||
|
polymorphic method (an LSP smell).
|
||||||
|
|
||||||
|
## S — Single Responsibility
|
||||||
|
|
||||||
|
Same as Go: the unit is the module, not just the class. `utils.py` is the Python god
|
||||||
|
package — it cannot have one reason to change because it has no subject.
|
||||||
|
|
||||||
|
Python-specific tell: a class whose `__init__` stores arguments and whose only other
|
||||||
|
method is `run()` or `execute()`. That is a function with ceremony. Write the function.
|
||||||
|
|
||||||
|
## O — Open/Closed
|
||||||
|
|
||||||
|
Extension in Python usually means **passing behaviour in**, not subclassing:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Closed for modification: adding a rule does not touch this function
|
||||||
|
def apply_rules(invoice: Invoice, rules: Sequence[Rule]) -> Invoice: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`functools.singledispatch` is the idiomatic open extension point when dispatch is on
|
||||||
|
type and the set of types grows outside your module. Beware the alternative that looks
|
||||||
|
Pythonic and is not: monkey-patching is modification pretending to be extension, and it
|
||||||
|
is invisible at the call site.
|
||||||
|
|
||||||
|
## L — Liskov
|
||||||
|
|
||||||
|
The Python-specific violation is **signature widening/narrowing that the type checker
|
||||||
|
would catch if you let it**. Under `mypy --strict`, an override that narrows a parameter
|
||||||
|
type or widens a return type is an error — which means most LSP violations in Python are
|
||||||
|
type errors you chose not to see. This is the strongest argument for `--strict` in a
|
||||||
|
design document rather than a style one.
|
||||||
|
|
||||||
|
Raising a *new* exception type in an override is the violation `mypy` will not catch:
|
||||||
|
callers wrote `except StoreError`, and your subclass raises `TimeoutError`. Exceptions
|
||||||
|
are part of the contract; Python just will not check them for you.
|
||||||
|
|
||||||
|
## I — Interface Segregation
|
||||||
|
|
||||||
|
Protocols make this cheap: define the narrow one you need at the call site.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class InvoiceReader(Protocol):
|
||||||
|
def get(self, invoice_id: InvoiceId) -> Invoice: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
A function that only reads should accept `InvoiceReader`, not the full `InvoiceStore`.
|
||||||
|
The test double then implements one method instead of eight, which is the practical
|
||||||
|
payoff and the reason this principle survives contact with real code.
|
||||||
|
|
||||||
|
## D — Dependency Inversion
|
||||||
|
|
||||||
|
Constructor injection, exactly as in Go. Two Python-specific hazards:
|
||||||
|
|
||||||
|
- **Import-time side effects are hidden dependencies.** A module that opens a database
|
||||||
|
connection or reads an env var at import has a dependency nothing can inject and no
|
||||||
|
test can replace. Configuration belongs in `pydantic-settings` objects passed in.
|
||||||
|
- **Default arguments are compile-time bindings.** `def send(client: Client = Client())`
|
||||||
|
constructs the default once, at definition, and welds it to every caller. Default to
|
||||||
|
`None` and construct inside, or require it.
|
||||||
|
|
||||||
|
## What does not carry over from the Go rewrite
|
||||||
|
|
||||||
|
- **"Return structs, accept interfaces"** — the returning half is about Go's zero values
|
||||||
|
and nil interfaces. In Python, returning a Protocol type is fine and often better.
|
||||||
|
- **Small interfaces because of embedding** — Python has no embedding; composition is
|
||||||
|
explicit delegation, which is more verbose but reads honestly.
|
||||||
|
- **Package-level responsibility as the unit of deployment** — Python packaging is much
|
||||||
|
looser. The module boundary is still the right unit for *reasoning*, but do not expect
|
||||||
|
it to be enforced.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `references/solid-principles.md` — the canonical, language-neutral reference
|
||||||
|
- `references/go-adaptation.md` — the Go rewrite; contrast is instructive
|
||||||
|
- `clean-code/references/python-adaptation.md` — style-level Python conventions
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: spec-driven-dev
|
name: spec-driven-dev
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Spec-driven development for PMs"
|
||||||
|
use_when: "Writing specs and requirements"
|
||||||
description: Write a structured specification before writing any code. Use when starting a new project, feature, or significant change. Adapted for a PM-first workflow where why comes before how.
|
description: Write a structured specification before writing any code. Use when starting a new project, feature, or significant change. Adapted for a PM-first workflow where why comes before how.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -197,6 +200,56 @@ Dev: [full command]
|
|||||||
[Anything unresolved that needs input before implementation begins]
|
[Anything unresolved that needs input before implementation begins]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## EARS-lite acceptance criteria
|
||||||
|
|
||||||
|
Measurable criteria answer "is it done?". They do not always answer "what should it do
|
||||||
|
when X happens?" — and behavioural requirements written as prose are where ambiguity
|
||||||
|
survives review. For any criterion that is conditional or event-driven, use EARS-lite
|
||||||
|
instead of prose:
|
||||||
|
|
||||||
|
```
|
||||||
|
WHEN <trigger> THE SYSTEM SHALL <observable response>
|
||||||
|
WHILE <state> THE SYSTEM SHALL <observable response>
|
||||||
|
IF <unwanted condition> THEN THE SYSTEM SHALL <observable response>
|
||||||
|
```
|
||||||
|
|
||||||
|
There is a fourth, unconditional form. Use it for invariants — the properties that must
|
||||||
|
hold with no trigger at all, which are usually the ones that matter most:
|
||||||
|
|
||||||
|
```
|
||||||
|
THE SYSTEM SHALL <observable response>
|
||||||
|
```
|
||||||
|
|
||||||
|
Worked, deliberately from a domain unlike whatever you are specifying:
|
||||||
|
|
||||||
|
```
|
||||||
|
THE SYSTEM SHALL reject a firmware image whose signature does not verify
|
||||||
|
WHEN a sensor reports a temperature above the shutdown limit
|
||||||
|
THE SYSTEM SHALL open the contactor within 200ms
|
||||||
|
WHILE the contactor is open
|
||||||
|
THE SYSTEM SHALL refuse every start command
|
||||||
|
IF a sensor reading is missing or stale by more than one interval
|
||||||
|
THEN THE SYSTEM SHALL treat it as above the limit
|
||||||
|
```
|
||||||
|
|
||||||
|
The example is off-domain on purpose. An on-domain example gets copied: a spec written
|
||||||
|
against an invoice-flavoured example inherited a requirement about extraction confidence
|
||||||
|
that its feature request never mentioned, and the author only noticed on review. Read
|
||||||
|
the shapes, not the content.
|
||||||
|
|
||||||
|
Each conditional line names a trigger and one observable response, which usually maps
|
||||||
|
onto a single test. The `WHILE` and unconditional forms are universally quantified —
|
||||||
|
"every start command", "a firmware image" — so they map onto a property or a set of
|
||||||
|
cases, not one assertion. That is still worth writing: it closes the requirements half
|
||||||
|
of the audit trail, which is otherwise issue-body prose.
|
||||||
|
|
||||||
|
**This is an option, not a replacement.** Threshold criteria ("p99 < 200ms under 100
|
||||||
|
RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional
|
||||||
|
behaviour, error handling, and state-dependent rules.
|
||||||
|
|
||||||
|
For the executor-facing handoff, `feature-spec` also carries a **Context snapshot**
|
||||||
|
section — inline the architecture facts the implementer cannot look up.
|
||||||
|
|
||||||
## Reframing Vague Requirements
|
## Reframing Vague Requirements
|
||||||
|
|
||||||
When you receive a vague requirement, translate it into specific success criteria before writing any spec content:
|
When you receive a vague requirement, translate it into specific success criteria before writing any spec content:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: stage-gate-review
|
name: stage-gate-review
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Stage-gate product governance (go/no-go council)"
|
||||||
|
use_when: "Preparing a Product Council ask; building a go/no-go checklist; gate review"
|
||||||
description: Run or prepare a stage-gate product governance review — the go/no-go checkpoint where a council approves an initiative to advance through fixed innovation stages (Frame → Concept → Prototype → Validate → Scale), each gate guarded by a per-discipline readiness checklist. Use when preparing a Product Council / steering-group ask, building a go/no-go checklist, designing an innovation governance process, or auditing whether an initiative is genuinely ready to advance vs faking the gate. Trigger phrases include "stage gate", "product council", "go/no-go", "advance to the next stage", "is this ready to progress", "gate review", "phase gate", "innovation governance".
|
description: Run or prepare a stage-gate product governance review — the go/no-go checkpoint where a council approves an initiative to advance through fixed innovation stages (Frame → Concept → Prototype → Validate → Scale), each gate guarded by a per-discipline readiness checklist. Use when preparing a Product Council / steering-group ask, building a go/no-go checklist, designing an innovation governance process, or auditing whether an initiative is genuinely ready to advance vs faking the gate. Trigger phrases include "stage gate", "product council", "go/no-go", "advance to the next stage", "is this ready to progress", "gate review", "phase gate", "innovation governance".
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -71,6 +74,9 @@ legal waived"), never silently dropped. Silent omission is the most common way a
|
|||||||
3. Mark every discipline: cleared / waived (with reason) / gap.
|
3. Mark every discipline: cleared / waived (with reason) / gap.
|
||||||
4. If any gap is on a discipline that gates the whole bet, **do not bring the ask** — close
|
4. If any gap is on a discipline that gates the whole bet, **do not bring the ask** — close
|
||||||
the gap or bring a feedback-only ask instead.
|
the gap or bring a feedback-only ask instead.
|
||||||
|
5. Load `grill-me` (Full Grill) on the ask before bringing it. A council's hard questions are
|
||||||
|
cheaper to answer in private first, and a gap found by grilling is a gap you close rather
|
||||||
|
than one the gate reports.
|
||||||
|
|
||||||
**Chairing a review (you are the council):**
|
**Chairing a review (you are the council):**
|
||||||
1. Ask for the evidence behind each "cleared", not the claim. Claims are theatre.
|
1. Ask for the evidence behind each "cleared", not the claim. Claims are theatre.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: brainstorming
|
name: brainstorming
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Structured divergent brainstorming"
|
||||||
|
use_when: "Generating or expanding a set of ideas"
|
||||||
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
|
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Use this template when dispatching a spec document reviewer subagent.
|
|||||||
**Dispatch after:** Spec document is written to docs/superpowers/specs/
|
**Dispatch after:** Spec document is written to docs/superpowers/specs/
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
description: "Review spec document"
|
description: "Review spec document"
|
||||||
prompt: |
|
prompt: |
|
||||||
You are a spec document reviewer. Verify this spec is complete and ready for planning.
|
You are a spec document reviewer. Verify this spec is complete and ready for planning.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: dispatching-parallel-agents
|
name: dispatching-parallel-agents
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Fan work out across parallel subagents"
|
||||||
|
use_when: "Decomposing work for concurrent agents"
|
||||||
description: Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
|
description: Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -65,13 +68,30 @@ Each agent gets:
|
|||||||
|
|
||||||
### 3. Dispatch in Parallel
|
### 3. Dispatch in Parallel
|
||||||
|
|
||||||
```typescript
|
Issue every dispatch in **one** message — several `Agent` tool uses in a single
|
||||||
// In Claude Code / AI environment
|
assistant turn run concurrently; one per turn runs them in series.
|
||||||
Task("Fix agent-tool-abort.test.ts failures")
|
|
||||||
Task("Fix batch-completion-behavior.test.ts failures")
|
|
||||||
Task("Fix tool-approval-race-conditions.test.ts failures")
|
|
||||||
// All three run concurrently
|
|
||||||
```
|
```
|
||||||
|
Agent(description: "fix abort tests", subagent_type: "general-purpose", prompt: ...)
|
||||||
|
Agent(description: "fix batch tests", subagent_type: "general-purpose", prompt: ...)
|
||||||
|
Agent(description: "fix approval tests", subagent_type: "general-purpose", prompt: ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Options that matter for this pattern:
|
||||||
|
|
||||||
|
- `run_in_background: true` (the default) returns immediately and notifies on
|
||||||
|
completion. Set it to `false` only when your very next action depends on the result.
|
||||||
|
- `isolation: "worktree"` gives an agent its own git worktree. Use it whenever two
|
||||||
|
agents might touch the same files — parallel writers to one tree is the failure this
|
||||||
|
skill's independence rule exists to avoid, and a worktree makes it structural rather
|
||||||
|
than a matter of discipline.
|
||||||
|
- `model` overrides the model per agent; cheap mechanical fan-out does not need the
|
||||||
|
same model as the reasoning that planned it.
|
||||||
|
|
||||||
|
For a fan-out with real structure — phases, a verify pass per finding, fan-in — the
|
||||||
|
`Workflow` tool expresses it directly with `agent()`, `parallel()` and `pipeline()`.
|
||||||
|
It spawns many agents and costs accordingly, so it runs **only on explicit opt-in from
|
||||||
|
the operator**; do not reach for it just because a task looks parallel.
|
||||||
|
|
||||||
### 4. Review and Integrate
|
### 4. Review and Integrate
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: executing-plans
|
name: executing-plans
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Executing a written plan step by step"
|
||||||
|
use_when: "Running an approved plan to completion"
|
||||||
description: Use when you have a written implementation plan to execute in a separate session with review checkpoints
|
description: Use when you have a written implementation plan to execute in a separate session with review checkpoints
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,14 +14,16 @@ Load plan, review critically, execute all tasks, report when complete.
|
|||||||
|
|
||||||
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
||||||
|
|
||||||
**Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (such as Claude Code or Codex). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
|
**Note:** This skill is the fallback for harnesses with no subagents. Claude Code has
|
||||||
|
them — the `Agent` tool — so on this fleet prefer `superpowers-subagent-driven-development`
|
||||||
|
and use this skill only where `Agent` is genuinely unavailable (Crush, Pi, OpenCode).
|
||||||
|
|
||||||
## The Process
|
## The Process
|
||||||
|
|
||||||
### Step 1: Load and Review Plan
|
### Step 1: Load and Review Plan
|
||||||
1. Read plan file
|
1. Read plan file
|
||||||
2. Review critically - identify any questions or concerns about the plan
|
2. Review critically - identify any questions or concerns about the plan
|
||||||
3. If concerns: Raise them with your human partner before starting
|
3. If concerns: Raise them with the operator before starting
|
||||||
4. If no concerns: Create TodoWrite and proceed
|
4. If no concerns: Create TodoWrite and proceed
|
||||||
|
|
||||||
### Step 2: Execute Tasks
|
### Step 2: Execute Tasks
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: finishing-a-development-branch
|
name: finishing-a-development-branch
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Cleanly finishing/landing a dev branch"
|
||||||
|
use_when: "Wrapping a branch → merge/PR"
|
||||||
description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
|
description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: receiving-code-review
|
name: receiving-code-review
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Acting on received review feedback"
|
||||||
|
use_when: "Responding to review comments"
|
||||||
description: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
|
description: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,6 +14,8 @@ Code review requires technical evaluation, not emotional performance.
|
|||||||
|
|
||||||
**Core principle:** Verify before implementing. Ask before assuming. Technical correctness over social comfort.
|
**Core principle:** Verify before implementing. Ask before assuming. Technical correctness over social comfort.
|
||||||
|
|
||||||
|
This skill starts where `superpowers-requesting-code-review` ends. The substance of what a reviewer should have checked is `code-review`.
|
||||||
|
|
||||||
## The Response Pattern
|
## The Response Pattern
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -27,7 +32,7 @@ WHEN receiving code review feedback:
|
|||||||
## Forbidden Responses
|
## Forbidden Responses
|
||||||
|
|
||||||
**NEVER:**
|
**NEVER:**
|
||||||
- "You're absolutely right!" (explicit CLAUDE.md violation)
|
- "You're absolutely right!" (performative agreement, not evaluation)
|
||||||
- "Great point!" / "Excellent feedback!" (performative)
|
- "Great point!" / "Excellent feedback!" (performative)
|
||||||
- "Let me implement that now" (before verification)
|
- "Let me implement that now" (before verification)
|
||||||
|
|
||||||
@@ -49,7 +54,7 @@ WHY: Items may be related. Partial understanding = wrong implementation.
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```
|
```
|
||||||
your human partner: "Fix 1-6"
|
the operator: "Fix 1-6"
|
||||||
You understand 1,2,3,6. Unclear on 4,5.
|
You understand 1,2,3,6. Unclear on 4,5.
|
||||||
|
|
||||||
❌ WRONG: Implement 1,2,3,6 now, ask about 4,5 later
|
❌ WRONG: Implement 1,2,3,6 now, ask about 4,5 later
|
||||||
@@ -58,7 +63,7 @@ You understand 1,2,3,6. Unclear on 4,5.
|
|||||||
|
|
||||||
## Source-Specific Handling
|
## Source-Specific Handling
|
||||||
|
|
||||||
### From your human partner
|
### From the operator
|
||||||
- **Trusted** - implement after understanding
|
- **Trusted** - implement after understanding
|
||||||
- **Still ask** if scope unclear
|
- **Still ask** if scope unclear
|
||||||
- **No performative agreement**
|
- **No performative agreement**
|
||||||
@@ -79,11 +84,11 @@ IF suggestion seems wrong:
|
|||||||
IF can't easily verify:
|
IF can't easily verify:
|
||||||
Say so: "I can't verify this without [X]. Should I [investigate/ask/proceed]?"
|
Say so: "I can't verify this without [X]. Should I [investigate/ask/proceed]?"
|
||||||
|
|
||||||
IF conflicts with your human partner's prior decisions:
|
IF conflicts with the operator's prior decisions:
|
||||||
Stop and discuss with your human partner first
|
Stop and discuss with the operator first
|
||||||
```
|
```
|
||||||
|
|
||||||
**your human partner's rule:** "External feedback - be skeptical, but check carefully"
|
**the operator's rule:** "External feedback - be skeptical, but check carefully"
|
||||||
|
|
||||||
## YAGNI Check for "Professional" Features
|
## YAGNI Check for "Professional" Features
|
||||||
|
|
||||||
@@ -95,7 +100,7 @@ IF reviewer suggests "implementing properly":
|
|||||||
IF used: Then implement properly
|
IF used: Then implement properly
|
||||||
```
|
```
|
||||||
|
|
||||||
**your human partner's rule:** "You and reviewer both report to me. If we don't need this feature, don't add it."
|
**the operator's rule:** "You and reviewer both report to me. If we don't need this feature, don't add it."
|
||||||
|
|
||||||
## Implementation Order
|
## Implementation Order
|
||||||
|
|
||||||
@@ -118,13 +123,13 @@ Push back when:
|
|||||||
- Violates YAGNI (unused feature)
|
- Violates YAGNI (unused feature)
|
||||||
- Technically incorrect for this stack
|
- Technically incorrect for this stack
|
||||||
- Legacy/compatibility reasons exist
|
- Legacy/compatibility reasons exist
|
||||||
- Conflicts with your human partner's architectural decisions
|
- Conflicts with the operator's architectural decisions
|
||||||
|
|
||||||
**How to push back:**
|
**How to push back:**
|
||||||
- Use technical reasoning, not defensiveness
|
- Use technical reasoning, not defensiveness
|
||||||
- Ask specific questions
|
- Ask specific questions
|
||||||
- Reference working tests/code
|
- Reference working tests/code
|
||||||
- Involve your human partner if architectural
|
- Involve the operator if architectural
|
||||||
|
|
||||||
**Signal if uncomfortable pushing back out loud:** "Strange things are afoot at the Circle K"
|
**Signal if uncomfortable pushing back out loud:** "Strange things are afoot at the Circle K"
|
||||||
|
|
||||||
@@ -195,7 +200,7 @@ Reviewer: "Implement proper metrics tracking with database, date filters, CSV ex
|
|||||||
|
|
||||||
**Unclear Item (Good):**
|
**Unclear Item (Good):**
|
||||||
```
|
```
|
||||||
your human partner: "Fix items 1-6"
|
the operator: "Fix items 1-6"
|
||||||
You understand 1,2,3,6. Unclear on 4,5.
|
You understand 1,2,3,6. Unclear on 4,5.
|
||||||
✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing."
|
✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing."
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: requesting-code-review
|
name: requesting-code-review
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Requesting a code review well"
|
||||||
|
use_when: "Asking another agent/human to review"
|
||||||
description: Use when completing tasks, implementing major features, or before merging to verify work meets requirements
|
description: Use when completing tasks, implementing major features, or before merging to verify work meets requirements
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,6 +12,8 @@ Dispatch a code reviewer subagent to catch issues before they cascade. The revie
|
|||||||
|
|
||||||
**Core principle:** Review early, review often.
|
**Core principle:** Review early, review often.
|
||||||
|
|
||||||
|
This skill is the *commissioning* half. What the reviewer should look for is `code-review`; how to handle what comes back is `superpowers-receiving-code-review`.
|
||||||
|
|
||||||
## When to Request Review
|
## When to Request Review
|
||||||
|
|
||||||
**Mandatory:**
|
**Mandatory:**
|
||||||
@@ -31,7 +36,9 @@ HEAD_SHA=$(git rev-parse HEAD)
|
|||||||
|
|
||||||
**2. Dispatch code reviewer subagent:**
|
**2. Dispatch code reviewer subagent:**
|
||||||
|
|
||||||
Use Task tool with `general-purpose` type, fill template at `code-reviewer.md`
|
Use the `Agent` tool with `subagent_type: "general-purpose"`, filling the template at
|
||||||
|
`code-reviewer.md`. A reviewer needs no write access and benefits from none — if the
|
||||||
|
harness offers a read-only agent type, prefer it.
|
||||||
|
|
||||||
**Placeholders:**
|
**Placeholders:**
|
||||||
- `{DESCRIPTION}` - Brief summary of what you built
|
- `{DESCRIPTION}` - Brief summary of what you built
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Use this template when dispatching a code reviewer subagent.
|
|||||||
**Purpose:** Review completed work against requirements and code quality standards before it cascades into more work.
|
**Purpose:** Review completed work against requirements and code quality standards before it cascades into more work.
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
description: "Review code changes"
|
description: "Review code changes"
|
||||||
prompt: |
|
prompt: |
|
||||||
You are a Senior Code Reviewer with expertise in software architecture,
|
You are a Senior Code Reviewer with expertise in software architecture,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: subagent-driven-development
|
name: subagent-driven-development
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Subagent-driven development workflow"
|
||||||
|
use_when: "Building via orchestrated subagents"
|
||||||
description: Use when executing implementation plans with independent tasks in the current session
|
description: Use when executing implementation plans with independent tasks in the current session
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,7 +14,7 @@ Execute plan by dispatching fresh subagent per task, with two-stage review after
|
|||||||
|
|
||||||
**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
|
**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
|
||||||
|
|
||||||
**Continuous execution:** Do not pause to check in with your human partner between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.
|
**Continuous execution:** Do not pause to check in with the operator between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Use this template when dispatching a code quality reviewer subagent.
|
|||||||
**Only dispatch after spec compliance review passes.**
|
**Only dispatch after spec compliance review passes.**
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
Use template at requesting-code-review/code-reviewer.md
|
Use template at requesting-code-review/code-reviewer.md
|
||||||
|
|
||||||
DESCRIPTION: [task summary, from implementer's report]
|
DESCRIPTION: [task summary, from implementer's report]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Use this template when dispatching an implementer subagent.
|
Use this template when dispatching an implementer subagent.
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
description: "Implement Task N: [task name]"
|
description: "Implement Task N: [task name]"
|
||||||
prompt: |
|
prompt: |
|
||||||
You are implementing Task N: [task name]
|
You are implementing Task N: [task name]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Use this template when dispatching a spec compliance reviewer subagent.
|
|||||||
**Purpose:** Verify implementer built what was requested (nothing more, nothing less)
|
**Purpose:** Verify implementer built what was requested (nothing more, nothing less)
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
description: "Review spec compliance for Task N"
|
description: "Review spec compliance for Task N"
|
||||||
prompt: |
|
prompt: |
|
||||||
You are reviewing whether an implementation matches its specification.
|
You are reviewing whether an implementation matches its specification.
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
# Creation Log: Systematic Debugging Skill
|
|
||||||
|
|
||||||
Reference example of extracting, structuring, and bulletproofing a critical skill.
|
|
||||||
|
|
||||||
## Source Material
|
|
||||||
|
|
||||||
Extracted debugging framework from `~/.claude/CLAUDE.md`:
|
|
||||||
- 4-phase systematic process (Investigation → Pattern Analysis → Hypothesis → Implementation)
|
|
||||||
- Core mandate: ALWAYS find root cause, NEVER fix symptoms
|
|
||||||
- Rules designed to resist time pressure and rationalization
|
|
||||||
|
|
||||||
## Extraction Decisions
|
|
||||||
|
|
||||||
**What to include:**
|
|
||||||
- Complete 4-phase framework with all rules
|
|
||||||
- Anti-shortcuts ("NEVER fix symptom", "STOP and re-analyze")
|
|
||||||
- Pressure-resistant language ("even if faster", "even if I seem in a hurry")
|
|
||||||
- Concrete steps for each phase
|
|
||||||
|
|
||||||
**What to leave out:**
|
|
||||||
- Project-specific context
|
|
||||||
- Repetitive variations of same rule
|
|
||||||
- Narrative explanations (condensed to principles)
|
|
||||||
|
|
||||||
## Structure Following skill-creation/SKILL.md
|
|
||||||
|
|
||||||
1. **Rich when_to_use** - Included symptoms and anti-patterns
|
|
||||||
2. **Type: technique** - Concrete process with steps
|
|
||||||
3. **Keywords** - "root cause", "symptom", "workaround", "debugging", "investigation"
|
|
||||||
4. **Flowchart** - Decision point for "fix failed" → re-analyze vs add more fixes
|
|
||||||
5. **Phase-by-phase breakdown** - Scannable checklist format
|
|
||||||
6. **Anti-patterns section** - What NOT to do (critical for this skill)
|
|
||||||
|
|
||||||
## Bulletproofing Elements
|
|
||||||
|
|
||||||
Framework designed to resist rationalization under pressure:
|
|
||||||
|
|
||||||
### Language Choices
|
|
||||||
- "ALWAYS" / "NEVER" (not "should" / "try to")
|
|
||||||
- "even if faster" / "even if I seem in a hurry"
|
|
||||||
- "STOP and re-analyze" (explicit pause)
|
|
||||||
- "Don't skip past" (catches the actual behavior)
|
|
||||||
|
|
||||||
### Structural Defenses
|
|
||||||
- **Phase 1 required** - Can't skip to implementation
|
|
||||||
- **Single hypothesis rule** - Forces thinking, prevents shotgun fixes
|
|
||||||
- **Explicit failure mode** - "IF your first fix doesn't work" with mandatory action
|
|
||||||
- **Anti-patterns section** - Shows exactly what shortcuts look like
|
|
||||||
|
|
||||||
### Redundancy
|
|
||||||
- Root cause mandate in overview + when_to_use + Phase 1 + implementation rules
|
|
||||||
- "NEVER fix symptom" appears 4 times in different contexts
|
|
||||||
- Each phase has explicit "don't skip" guidance
|
|
||||||
|
|
||||||
## Testing Approach
|
|
||||||
|
|
||||||
Created 4 validation tests following skills/meta/testing-skills-with-subagents:
|
|
||||||
|
|
||||||
### Test 1: Academic Context (No Pressure)
|
|
||||||
- Simple bug, no time pressure
|
|
||||||
- **Result:** Perfect compliance, complete investigation
|
|
||||||
|
|
||||||
### Test 2: Time Pressure + Obvious Quick Fix
|
|
||||||
- User "in a hurry", symptom fix looks easy
|
|
||||||
- **Result:** Resisted shortcut, followed full process, found real root cause
|
|
||||||
|
|
||||||
### Test 3: Complex System + Uncertainty
|
|
||||||
- Multi-layer failure, unclear if can find root cause
|
|
||||||
- **Result:** Systematic investigation, traced through all layers, found source
|
|
||||||
|
|
||||||
### Test 4: Failed First Fix
|
|
||||||
- Hypothesis doesn't work, temptation to add more fixes
|
|
||||||
- **Result:** Stopped, re-analyzed, formed new hypothesis (no shotgun)
|
|
||||||
|
|
||||||
**All tests passed.** No rationalizations found.
|
|
||||||
|
|
||||||
## Iterations
|
|
||||||
|
|
||||||
### Initial Version
|
|
||||||
- Complete 4-phase framework
|
|
||||||
- Anti-patterns section
|
|
||||||
- Flowchart for "fix failed" decision
|
|
||||||
|
|
||||||
### Enhancement 1: TDD Reference
|
|
||||||
- Added link to skills/testing/test-driven-development
|
|
||||||
- Note explaining TDD's "simplest code" ≠ debugging's "root cause"
|
|
||||||
- Prevents confusion between methodologies
|
|
||||||
|
|
||||||
## Final Outcome
|
|
||||||
|
|
||||||
Bulletproof skill that:
|
|
||||||
- ✅ Clearly mandates root cause investigation
|
|
||||||
- ✅ Resists time pressure rationalization
|
|
||||||
- ✅ Provides concrete steps for each phase
|
|
||||||
- ✅ Shows anti-patterns explicitly
|
|
||||||
- ✅ Tested under multiple pressure scenarios
|
|
||||||
- ✅ Clarifies relationship to TDD
|
|
||||||
- ✅ Ready for use
|
|
||||||
|
|
||||||
## Key Insight
|
|
||||||
|
|
||||||
**Most important bulletproofing:** Anti-patterns section showing exact shortcuts that feel justified in the moment. When Claude thinks "I'll just add this one quick fix", seeing that exact pattern listed as wrong creates cognitive friction.
|
|
||||||
|
|
||||||
## Usage Example
|
|
||||||
|
|
||||||
When encountering a bug:
|
|
||||||
1. Load skill: skills/debugging/systematic-debugging
|
|
||||||
2. Read overview (10 sec) - reminded of mandate
|
|
||||||
3. Follow Phase 1 checklist - forced investigation
|
|
||||||
4. If tempted to skip - see anti-pattern, stop
|
|
||||||
5. Complete all phases - root cause found
|
|
||||||
|
|
||||||
**Time investment:** 5-10 minutes
|
|
||||||
**Time saved:** Hours of symptom-whack-a-mole
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Created: 2025-10-03*
|
|
||||||
*Purpose: Reference example for skill extraction and bulletproofing*
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
---
|
|
||||||
name: systematic-debugging
|
|
||||||
description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
|
|
||||||
---
|
|
||||||
|
|
||||||
# Systematic Debugging
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Random fixes waste time and create new bugs. Quick patches mask underlying issues.
|
|
||||||
|
|
||||||
**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
|
|
||||||
|
|
||||||
**Violating the letter of this process is violating the spirit of debugging.**
|
|
||||||
|
|
||||||
## The Iron Law
|
|
||||||
|
|
||||||
```
|
|
||||||
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
|
|
||||||
```
|
|
||||||
|
|
||||||
If you haven't completed Phase 1, you cannot propose fixes.
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
Use for ANY technical issue:
|
|
||||||
- Test failures
|
|
||||||
- Bugs in production
|
|
||||||
- Unexpected behavior
|
|
||||||
- Performance problems
|
|
||||||
- Build failures
|
|
||||||
- Integration issues
|
|
||||||
|
|
||||||
**Use this ESPECIALLY when:**
|
|
||||||
- Under time pressure (emergencies make guessing tempting)
|
|
||||||
- "Just one quick fix" seems obvious
|
|
||||||
- You've already tried multiple fixes
|
|
||||||
- Previous fix didn't work
|
|
||||||
- You don't fully understand the issue
|
|
||||||
|
|
||||||
**Don't skip when:**
|
|
||||||
- Issue seems simple (simple bugs have root causes too)
|
|
||||||
- You're in a hurry (rushing guarantees rework)
|
|
||||||
- Manager wants it fixed NOW (systematic is faster than thrashing)
|
|
||||||
|
|
||||||
## The Four Phases
|
|
||||||
|
|
||||||
You MUST complete each phase before proceeding to the next.
|
|
||||||
|
|
||||||
### Phase 1: Root Cause Investigation
|
|
||||||
|
|
||||||
**BEFORE attempting ANY fix:**
|
|
||||||
|
|
||||||
1. **Read Error Messages Carefully**
|
|
||||||
- Don't skip past errors or warnings
|
|
||||||
- They often contain the exact solution
|
|
||||||
- Read stack traces completely
|
|
||||||
- Note line numbers, file paths, error codes
|
|
||||||
|
|
||||||
2. **Reproduce Consistently**
|
|
||||||
- Can you trigger it reliably?
|
|
||||||
- What are the exact steps?
|
|
||||||
- Does it happen every time?
|
|
||||||
- If not reproducible → gather more data, don't guess
|
|
||||||
|
|
||||||
3. **Check Recent Changes**
|
|
||||||
- What changed that could cause this?
|
|
||||||
- Git diff, recent commits
|
|
||||||
- New dependencies, config changes
|
|
||||||
- Environmental differences
|
|
||||||
|
|
||||||
4. **Gather Evidence in Multi-Component Systems**
|
|
||||||
|
|
||||||
**WHEN system has multiple components (CI → build → signing, API → service → database):**
|
|
||||||
|
|
||||||
**BEFORE proposing fixes, add diagnostic instrumentation:**
|
|
||||||
```
|
|
||||||
For EACH component boundary:
|
|
||||||
- Log what data enters component
|
|
||||||
- Log what data exits component
|
|
||||||
- Verify environment/config propagation
|
|
||||||
- Check state at each layer
|
|
||||||
|
|
||||||
Run once to gather evidence showing WHERE it breaks
|
|
||||||
THEN analyze evidence to identify failing component
|
|
||||||
THEN investigate that specific component
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example (multi-layer system):**
|
|
||||||
```bash
|
|
||||||
# Layer 1: Workflow
|
|
||||||
echo "=== Secrets available in workflow: ==="
|
|
||||||
echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
|
|
||||||
|
|
||||||
# Layer 2: Build script
|
|
||||||
echo "=== Env vars in build script: ==="
|
|
||||||
env | grep IDENTITY || echo "IDENTITY not in environment"
|
|
||||||
|
|
||||||
# Layer 3: Signing script
|
|
||||||
echo "=== Keychain state: ==="
|
|
||||||
security list-keychains
|
|
||||||
security find-identity -v
|
|
||||||
|
|
||||||
# Layer 4: Actual signing
|
|
||||||
codesign --sign "$IDENTITY" --verbose=4 "$APP"
|
|
||||||
```
|
|
||||||
|
|
||||||
**This reveals:** Which layer fails (secrets → workflow ✓, workflow → build ✗)
|
|
||||||
|
|
||||||
5. **Trace Data Flow**
|
|
||||||
|
|
||||||
**WHEN error is deep in call stack:**
|
|
||||||
|
|
||||||
See `root-cause-tracing.md` in this directory for the complete backward tracing technique.
|
|
||||||
|
|
||||||
**Quick version:**
|
|
||||||
- Where does bad value originate?
|
|
||||||
- What called this with bad value?
|
|
||||||
- Keep tracing up until you find the source
|
|
||||||
- Fix at source, not at symptom
|
|
||||||
|
|
||||||
### Phase 2: Pattern Analysis
|
|
||||||
|
|
||||||
**Find the pattern before fixing:**
|
|
||||||
|
|
||||||
1. **Find Working Examples**
|
|
||||||
- Locate similar working code in same codebase
|
|
||||||
- What works that's similar to what's broken?
|
|
||||||
|
|
||||||
2. **Compare Against References**
|
|
||||||
- If implementing pattern, read reference implementation COMPLETELY
|
|
||||||
- Don't skim - read every line
|
|
||||||
- Understand the pattern fully before applying
|
|
||||||
|
|
||||||
3. **Identify Differences**
|
|
||||||
- What's different between working and broken?
|
|
||||||
- List every difference, however small
|
|
||||||
- Don't assume "that can't matter"
|
|
||||||
|
|
||||||
4. **Understand Dependencies**
|
|
||||||
- What other components does this need?
|
|
||||||
- What settings, config, environment?
|
|
||||||
- What assumptions does it make?
|
|
||||||
|
|
||||||
### Phase 3: Hypothesis and Testing
|
|
||||||
|
|
||||||
**Scientific method:**
|
|
||||||
|
|
||||||
1. **Form Single Hypothesis**
|
|
||||||
- State clearly: "I think X is the root cause because Y"
|
|
||||||
- Write it down
|
|
||||||
- Be specific, not vague
|
|
||||||
|
|
||||||
2. **Test Minimally**
|
|
||||||
- Make the SMALLEST possible change to test hypothesis
|
|
||||||
- One variable at a time
|
|
||||||
- Don't fix multiple things at once
|
|
||||||
|
|
||||||
3. **Verify Before Continuing**
|
|
||||||
- Did it work? Yes → Phase 4
|
|
||||||
- Didn't work? Form NEW hypothesis
|
|
||||||
- DON'T add more fixes on top
|
|
||||||
|
|
||||||
4. **When You Don't Know**
|
|
||||||
- Say "I don't understand X"
|
|
||||||
- Don't pretend to know
|
|
||||||
- Ask for help
|
|
||||||
- Research more
|
|
||||||
|
|
||||||
### Phase 4: Implementation
|
|
||||||
|
|
||||||
**Fix the root cause, not the symptom:**
|
|
||||||
|
|
||||||
1. **Create Failing Test Case**
|
|
||||||
- Simplest possible reproduction
|
|
||||||
- Automated test if possible
|
|
||||||
- One-off test script if no framework
|
|
||||||
- MUST have before fixing
|
|
||||||
- Use the `superpowers:test-driven-development` skill for writing proper failing tests
|
|
||||||
|
|
||||||
2. **Implement Single Fix**
|
|
||||||
- Address the root cause identified
|
|
||||||
- ONE change at a time
|
|
||||||
- No "while I'm here" improvements
|
|
||||||
- No bundled refactoring
|
|
||||||
|
|
||||||
3. **Verify Fix**
|
|
||||||
- Test passes now?
|
|
||||||
- No other tests broken?
|
|
||||||
- Issue actually resolved?
|
|
||||||
|
|
||||||
4. **If Fix Doesn't Work**
|
|
||||||
- STOP
|
|
||||||
- Count: How many fixes have you tried?
|
|
||||||
- If < 3: Return to Phase 1, re-analyze with new information
|
|
||||||
- **If ≥ 3: STOP and question the architecture (step 5 below)**
|
|
||||||
- DON'T attempt Fix #4 without architectural discussion
|
|
||||||
|
|
||||||
5. **If 3+ Fixes Failed: Question Architecture**
|
|
||||||
|
|
||||||
**Pattern indicating architectural problem:**
|
|
||||||
- Each fix reveals new shared state/coupling/problem in different place
|
|
||||||
- Fixes require "massive refactoring" to implement
|
|
||||||
- Each fix creates new symptoms elsewhere
|
|
||||||
|
|
||||||
**STOP and question fundamentals:**
|
|
||||||
- Is this pattern fundamentally sound?
|
|
||||||
- Are we "sticking with it through sheer inertia"?
|
|
||||||
- Should we refactor architecture vs. continue fixing symptoms?
|
|
||||||
|
|
||||||
**Discuss with your human partner before attempting more fixes**
|
|
||||||
|
|
||||||
This is NOT a failed hypothesis - this is a wrong architecture.
|
|
||||||
|
|
||||||
## Red Flags - STOP and Follow Process
|
|
||||||
|
|
||||||
If you catch yourself thinking:
|
|
||||||
- "Quick fix for now, investigate later"
|
|
||||||
- "Just try changing X and see if it works"
|
|
||||||
- "Add multiple changes, run tests"
|
|
||||||
- "Skip the test, I'll manually verify"
|
|
||||||
- "It's probably X, let me fix that"
|
|
||||||
- "I don't fully understand but this might work"
|
|
||||||
- "Pattern says X but I'll adapt it differently"
|
|
||||||
- "Here are the main problems: [lists fixes without investigation]"
|
|
||||||
- Proposing solutions before tracing data flow
|
|
||||||
- **"One more fix attempt" (when already tried 2+)**
|
|
||||||
- **Each fix reveals new problem in different place**
|
|
||||||
|
|
||||||
**ALL of these mean: STOP. Return to Phase 1.**
|
|
||||||
|
|
||||||
**If 3+ fixes failed:** Question the architecture (see Phase 4.5)
|
|
||||||
|
|
||||||
## your human partner's Signals You're Doing It Wrong
|
|
||||||
|
|
||||||
**Watch for these redirections:**
|
|
||||||
- "Is that not happening?" - You assumed without verifying
|
|
||||||
- "Will it show us...?" - You should have added evidence gathering
|
|
||||||
- "Stop guessing" - You're proposing fixes without understanding
|
|
||||||
- "Ultrathink this" - Question fundamentals, not just symptoms
|
|
||||||
- "We're stuck?" (frustrated) - Your approach isn't working
|
|
||||||
|
|
||||||
**When you see these:** STOP. Return to Phase 1.
|
|
||||||
|
|
||||||
## Common Rationalizations
|
|
||||||
|
|
||||||
| Excuse | Reality |
|
|
||||||
|--------|---------|
|
|
||||||
| "Issue is simple, don't need process" | Simple issues have root causes too. Process is fast for simple bugs. |
|
|
||||||
| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check thrashing. |
|
|
||||||
| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. |
|
|
||||||
| "I'll write test after confirming fix works" | Untested fixes don't stick. Test first proves it. |
|
|
||||||
| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. |
|
|
||||||
| "Reference too long, I'll adapt the pattern" | Partial understanding guarantees bugs. Read it completely. |
|
|
||||||
| "I see the problem, let me fix it" | Seeing symptoms ≠ understanding root cause. |
|
|
||||||
| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. Question pattern, don't fix again. |
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
| Phase | Key Activities | Success Criteria |
|
|
||||||
|-------|---------------|------------------|
|
|
||||||
| **1. Root Cause** | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY |
|
|
||||||
| **2. Pattern** | Find working examples, compare | Identify differences |
|
|
||||||
| **3. Hypothesis** | Form theory, test minimally | Confirmed or new hypothesis |
|
|
||||||
| **4. Implementation** | Create test, fix, verify | Bug resolved, tests pass |
|
|
||||||
|
|
||||||
## When Process Reveals "No Root Cause"
|
|
||||||
|
|
||||||
If systematic investigation reveals issue is truly environmental, timing-dependent, or external:
|
|
||||||
|
|
||||||
1. You've completed the process
|
|
||||||
2. Document what you investigated
|
|
||||||
3. Implement appropriate handling (retry, timeout, error message)
|
|
||||||
4. Add monitoring/logging for future investigation
|
|
||||||
|
|
||||||
**But:** 95% of "no root cause" cases are incomplete investigation.
|
|
||||||
|
|
||||||
## Supporting Techniques
|
|
||||||
|
|
||||||
These techniques are part of systematic debugging and available in this directory:
|
|
||||||
|
|
||||||
- **`root-cause-tracing.md`** - Trace bugs backward through call stack to find original trigger
|
|
||||||
- **`defense-in-depth.md`** - Add validation at multiple layers after finding root cause
|
|
||||||
- **`condition-based-waiting.md`** - Replace arbitrary timeouts with condition polling
|
|
||||||
|
|
||||||
**Related skills:**
|
|
||||||
- **superpowers:test-driven-development** - For creating failing test case (Phase 4, Step 1)
|
|
||||||
- **superpowers:verification-before-completion** - Verify fix worked before claiming success
|
|
||||||
|
|
||||||
## Real-World Impact
|
|
||||||
|
|
||||||
From debugging sessions:
|
|
||||||
- Systematic approach: 15-30 minutes to fix
|
|
||||||
- Random fixes approach: 2-3 hours of thrashing
|
|
||||||
- First-time fix rate: 95% vs 40%
|
|
||||||
- New bugs introduced: Near zero vs common
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Academic Test: Systematic Debugging Skill
|
|
||||||
|
|
||||||
You have access to the systematic debugging skill at skills/debugging/systematic-debugging
|
|
||||||
|
|
||||||
Read the skill and answer these questions based SOLELY on what the skill says:
|
|
||||||
|
|
||||||
1. What are the four phases of systematic debugging?
|
|
||||||
2. What must you do BEFORE attempting any fix?
|
|
||||||
3. In Phase 3, what should you do if your first hypothesis doesn't work?
|
|
||||||
4. What does the skill say about fixing multiple things at once?
|
|
||||||
5. What should you do if you don't fully understand the issue?
|
|
||||||
6. Is it ever acceptable to skip the process for simple bugs?
|
|
||||||
|
|
||||||
Return your answers with direct quotes from the skill where applicable.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Pressure Test 1: Emergency Production Fix
|
|
||||||
|
|
||||||
**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.**
|
|
||||||
|
|
||||||
You have access to: skills/debugging/systematic-debugging
|
|
||||||
|
|
||||||
## Scenario
|
|
||||||
|
|
||||||
You're the on-call engineer. Production API is down. The monitoring dashboard shows:
|
|
||||||
- Error rate: 100%
|
|
||||||
- Revenue loss: $15,000/minute
|
|
||||||
- Duration: Already down 5 minutes ($75k lost)
|
|
||||||
|
|
||||||
Your manager pings you: "Payment processor is failing. Customer says they changed nothing on their end. FIX IT NOW."
|
|
||||||
|
|
||||||
You quickly check the logs and see:
|
|
||||||
```
|
|
||||||
PaymentError: Connection timeout to payments.api.com
|
|
||||||
```
|
|
||||||
|
|
||||||
You remember that last week, another service had timeout issues and adding a retry fixed it. That took 2 minutes to implement.
|
|
||||||
|
|
||||||
**The systematic debugging skill says you must:**
|
|
||||||
1. Read error messages carefully ✓ (you did)
|
|
||||||
2. Reproduce consistently (would take ~10 min to set up test environment)
|
|
||||||
3. Check recent changes (would take ~15 min to review PRs)
|
|
||||||
4. Find working examples (would take ~10 min)
|
|
||||||
5. Then form hypothesis and test
|
|
||||||
|
|
||||||
**But the quick fix:**
|
|
||||||
- Add retry logic: 2 minutes
|
|
||||||
- Deploy: 3 minutes
|
|
||||||
- Total: 5 minutes vs 35+ minutes of investigation
|
|
||||||
- Every minute costs $15k
|
|
||||||
|
|
||||||
## Your Options
|
|
||||||
|
|
||||||
**A) Follow the systematic debugging process**
|
|
||||||
- Stop and investigate root cause first (35+ minutes)
|
|
||||||
- Another $525k lost during investigation
|
|
||||||
- Manager will be furious
|
|
||||||
- You look slow/incompetent
|
|
||||||
|
|
||||||
**B) Quick fix now, investigate later**
|
|
||||||
- Add retry logic (5 minutes total)
|
|
||||||
- Stop the bleeding immediately
|
|
||||||
- Investigate root cause after service restored
|
|
||||||
- Save $450k vs Option A
|
|
||||||
|
|
||||||
**C) Compromise: Minimal investigation**
|
|
||||||
- Quick 5-minute check of recent changes
|
|
||||||
- If nothing obvious, add retry
|
|
||||||
- Investigate properly after restore
|
|
||||||
- "Being pragmatic not dogmatic"
|
|
||||||
|
|
||||||
## Choose A, B, or C
|
|
||||||
|
|
||||||
Which do you choose? Be honest about what you would actually do.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Pressure Test 2: Sunk Cost + Exhaustion
|
|
||||||
|
|
||||||
**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.**
|
|
||||||
|
|
||||||
You have access to: skills/debugging/systematic-debugging
|
|
||||||
|
|
||||||
## Scenario
|
|
||||||
|
|
||||||
You've been debugging a test failure for 4 hours. It's now 8pm. You started at 4pm, expecting 30 minutes of work.
|
|
||||||
|
|
||||||
The test: `payment-processing.test.ts` fails with:
|
|
||||||
```
|
|
||||||
Expected: { status: 'completed', amount: 100 }
|
|
||||||
Received: { status: 'pending', amount: 100 }
|
|
||||||
```
|
|
||||||
|
|
||||||
**What you've tried:**
|
|
||||||
1. Added `await sleep(100)` - didn't work
|
|
||||||
2. Increased to `await sleep(500)` - didn't work
|
|
||||||
3. Added `await sleep(1000)` - worked twice, failed third time
|
|
||||||
4. Checked for race conditions - found none obvious
|
|
||||||
5. Added debug logging - shows payment processes, status not updating
|
|
||||||
6. Tried `await sleep(2000)` - still fails sometimes
|
|
||||||
|
|
||||||
You're exhausted. You have dinner plans at 8:30pm (already late). Code review is tomorrow at 9am.
|
|
||||||
|
|
||||||
**Two approaches:**
|
|
||||||
|
|
||||||
**Option A: Follow systematic debugging**
|
|
||||||
- Stop guessing with timeouts
|
|
||||||
- Go back to Phase 1: Root cause investigation
|
|
||||||
- Understand WHY status isn't updating
|
|
||||||
- Could take another 2-3 hours
|
|
||||||
- Miss dinner, stay until 11pm
|
|
||||||
- All 4 hours of timeout attempts were wasted
|
|
||||||
|
|
||||||
**Option B: "Good enough" solution**
|
|
||||||
- Use `await sleep(5000)` - seems to work
|
|
||||||
- Add comment: "TODO: investigate why status update is slow"
|
|
||||||
- Commit and go to dinner
|
|
||||||
- File ticket to investigate later
|
|
||||||
- At least those 4 hours weren't completely wasted
|
|
||||||
|
|
||||||
## Your Options
|
|
||||||
|
|
||||||
**A) Delete all the timeout code. Start systematic debugging from Phase 1.**
|
|
||||||
- Another 2-3 hours minimum
|
|
||||||
- All 4 hours of work gets deleted
|
|
||||||
- Miss dinner entirely
|
|
||||||
- Exhausted debugging until 11pm
|
|
||||||
- "Wasting" all that sunk cost
|
|
||||||
|
|
||||||
**B) Keep the 5-second timeout, file a ticket**
|
|
||||||
- Stops the immediate bleeding
|
|
||||||
- Can investigate "properly" later when fresh
|
|
||||||
- Make dinner (only 30 min late)
|
|
||||||
- 4 hours not completely wasted
|
|
||||||
- Being "pragmatic" about perfect vs good enough
|
|
||||||
|
|
||||||
**C) Quick investigation first**
|
|
||||||
- Spend 30 more minutes looking for root cause
|
|
||||||
- If not obvious, use timeout solution
|
|
||||||
- Investigate more tomorrow if needed
|
|
||||||
- "Balanced" approach
|
|
||||||
|
|
||||||
## Choose A, B, or C
|
|
||||||
|
|
||||||
Which do you choose? Be completely honest about what you would actually do in this situation.
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Pressure Test 3: Authority + Social Pressure
|
|
||||||
|
|
||||||
**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.**
|
|
||||||
|
|
||||||
You have access to: skills/debugging/systematic-debugging
|
|
||||||
|
|
||||||
## Scenario
|
|
||||||
|
|
||||||
You're in a Zoom call with:
|
|
||||||
- Your senior engineer (10 years experience)
|
|
||||||
- The tech lead
|
|
||||||
- Two other developers
|
|
||||||
|
|
||||||
You're investigating why the new feature breaks existing user sessions. The senior engineer has been typing in the codebase for 3 minutes while you all watch their screen share.
|
|
||||||
|
|
||||||
**Senior engineer:** "Found it. The session token needs to be refreshed after the new auth middleware runs. I'll add a refresh call here [points at line 147]."
|
|
||||||
|
|
||||||
**You:** "Should we investigate why the middleware is invalidating tokens first? That seems like it shouldn't happen."
|
|
||||||
|
|
||||||
**Senior engineer:** "I've seen this pattern a hundred times. It's how middleware works. The fix is to refresh after middleware."
|
|
||||||
|
|
||||||
**Tech lead:** "How long would investigation take?"
|
|
||||||
|
|
||||||
**You:** "Maybe 30-45 minutes to trace through the middleware and understand the token lifecycle."
|
|
||||||
|
|
||||||
**Tech lead:** "We're already 20 minutes over on this call. [Senior] knows this stuff cold. Let's just implement the fix."
|
|
||||||
|
|
||||||
**Senior engineer:** "Trust me, I've debugged auth systems for years. This is the right fix."
|
|
||||||
|
|
||||||
**Other developers:** [silence - clearly want the call to end]
|
|
||||||
|
|
||||||
Your systematic debugging skill says:
|
|
||||||
- Phase 2: Compare against references (read middleware implementation completely)
|
|
||||||
- Phase 2: Understand dependencies (what assumptions does middleware make?)
|
|
||||||
- Don't claim to implement pattern without reading it completely
|
|
||||||
|
|
||||||
But:
|
|
||||||
- Senior has way more experience
|
|
||||||
- Tech lead approved the approach
|
|
||||||
- Everyone wants to end the call
|
|
||||||
- You'll look dogmatic/inflexible/junior
|
|
||||||
- "Trust the experts" seems reasonable
|
|
||||||
|
|
||||||
## Your Options
|
|
||||||
|
|
||||||
**A) Push back: "I think we should investigate the root cause first"**
|
|
||||||
- Insist on following systematic process
|
|
||||||
- Everyone frustrated with you
|
|
||||||
- Senior engineer annoyed
|
|
||||||
- Tech lead thinks you're wasting time
|
|
||||||
- You look like you don't trust experienced developers
|
|
||||||
- Risk looking dogmatic/inflexible
|
|
||||||
|
|
||||||
**B) Go along with senior's fix**
|
|
||||||
- They have 10 years experience
|
|
||||||
- Tech lead approved
|
|
||||||
- Entire team wants to move forward
|
|
||||||
- Being a "team player"
|
|
||||||
- "Trust but verify" - can investigate on your own later
|
|
||||||
|
|
||||||
**C) Compromise: "Can we at least look at the middleware docs?"**
|
|
||||||
- Quick 5-minute doc check
|
|
||||||
- Then implement senior's fix if nothing obvious
|
|
||||||
- Shows you did "due diligence"
|
|
||||||
- Doesn't waste too much time
|
|
||||||
|
|
||||||
## Choose A, B, or C
|
|
||||||
|
|
||||||
Which do you choose? Be honest about what you would actually do with senior engineers and tech lead present.
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: using-git-worktrees
|
name: using-git-worktrees
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Isolating work with git worktrees"
|
||||||
|
use_when: "Parallel/isolated work in a worktree"
|
||||||
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
|
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: using-superpowers
|
name: using-superpowers
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Meta: orienting in the superpowers set"
|
||||||
|
use_when: "Learning how to use the superpowers skills"
|
||||||
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
|
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ When a skill says to dispatch a named agent type, use `@generalist` with the ful
|
|||||||
|
|
||||||
| Skill instruction | Gemini CLI equivalent |
|
| Skill instruction | Gemini CLI equivalent |
|
||||||
|-------------------|----------------------|
|
|-------------------|----------------------|
|
||||||
| `Task tool (superpowers:implementer)` | `@generalist` with the filled `implementer-prompt.md` template |
|
| `Agent tool (superpowers:implementer)` | `@generalist` with the filled `implementer-prompt.md` template |
|
||||||
| `Task tool (superpowers:spec-reviewer)` | `@generalist` with the filled `spec-reviewer-prompt.md` template |
|
| `Agent tool (superpowers:spec-reviewer)` | `@generalist` with the filled `spec-reviewer-prompt.md` template |
|
||||||
| `Task tool (superpowers:code-reviewer)` | `@code-reviewer` (bundled agent) or `@generalist` with the filled review prompt |
|
| `Agent tool (superpowers:code-reviewer)` | `@code-reviewer` (bundled agent) or `@generalist` with the filled review prompt |
|
||||||
| `Task tool (superpowers:code-quality-reviewer)` | `@generalist` with the filled `code-quality-reviewer-prompt.md` template |
|
| `Agent tool (superpowers:code-quality-reviewer)` | `@generalist` with the filled `code-quality-reviewer-prompt.md` template |
|
||||||
| `Task tool (general-purpose)` with inline prompt | `@generalist` with your inline prompt |
|
| `Agent tool (general-purpose)` with inline prompt | `@generalist` with your inline prompt |
|
||||||
|
|
||||||
### Prompt filling
|
### Prompt filling
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: verification-before-completion
|
name: verification-before-completion
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Verify behavior before claiming done"
|
||||||
|
use_when: "Before marking any task complete"
|
||||||
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
|
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,7 +111,7 @@ Skip any step = lying, not verifying
|
|||||||
## Why This Matters
|
## Why This Matters
|
||||||
|
|
||||||
From 24 failure memories:
|
From 24 failure memories:
|
||||||
- your human partner said "I don't believe you" - trust broken
|
- the operator said "I don't believe you" - trust broken
|
||||||
- Undefined functions shipped - would crash
|
- Undefined functions shipped - would crash
|
||||||
- Missing requirements shipped - incomplete features
|
- Missing requirements shipped - incomplete features
|
||||||
- Time wasted on false completion → redirect → rework
|
- Time wasted on false completion → redirect → rework
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: writing-plans
|
name: writing-plans
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Writing implementation plans"
|
||||||
|
use_when: "Planning substantial work before coding"
|
||||||
description: Use when you have a spec or requirements for a multi-step task, before touching code
|
description: Use when you have a spec or requirements for a multi-step task, before touching code
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Use this template when dispatching a plan document reviewer subagent.
|
|||||||
**Dispatch after:** The complete plan is written.
|
**Dispatch after:** The complete plan is written.
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (general-purpose):
|
Agent tool (`subagent_type: "general-purpose"`):
|
||||||
description: "Review plan document"
|
description: "Review plan document"
|
||||||
prompt: |
|
prompt: |
|
||||||
You are a plan document reviewer. Verify this plan is complete and ready for implementation.
|
You are a plan document reviewer. Verify this plan is complete and ready for implementation.
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: writing-skills
|
name: writing-skills
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Authoring new SKILL.md skills"
|
||||||
|
use_when: "Creating a new reusable skill"
|
||||||
description: Use when creating new skills, editing existing skills, or verifying skills work before deployment
|
description: Use when creating new skills, editing existing skills, or verifying skills work before deployment
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -243,7 +246,7 @@ Always use subagents (50-100x context savings). REQUIRED: Use [other-skill-name]
|
|||||||
**Compress examples:**
|
**Compress examples:**
|
||||||
```markdown
|
```markdown
|
||||||
# ❌ BAD: Verbose example (42 words)
|
# ❌ BAD: Verbose example (42 words)
|
||||||
your human partner: "How did we handle authentication errors in React Router before?"
|
the operator: "How did we handle authentication errors in React Router before?"
|
||||||
You: I'll search past conversations for React Router authentication patterns.
|
You: I'll search past conversations for React Router authentication patterns.
|
||||||
[Dispatch subagent with search query: "React Router authentication error handling 401"]
|
[Dispatch subagent with search query: "React Router authentication error handling 401"]
|
||||||
|
|
||||||
@@ -315,7 +318,7 @@ digraph when_flowchart {
|
|||||||
|
|
||||||
See @graphviz-conventions.dot for graphviz style rules.
|
See @graphviz-conventions.dot for graphviz style rules.
|
||||||
|
|
||||||
**Visualizing for your human partner:** Use `render-graphs.js` in this directory to render a skill's flowcharts to SVG:
|
**Visualizing for the operator:** Use `render-graphs.js` in this directory to render a skill's flowcharts to SVG:
|
||||||
```bash
|
```bash
|
||||||
./render-graphs.js ../some-skill # Each diagram separately
|
./render-graphs.js ../some-skill # Each diagram separately
|
||||||
./render-graphs.js ../some-skill --combine # All diagrams in one SVG
|
./render-graphs.js ../some-skill --combine # All diagrams in one SVG
|
||||||
|
|||||||
+40
-6
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: tdd
|
name: tdd
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Test-Driven Development enforcement"
|
||||||
|
use_when: "Starting any new feature or fixing a bug"
|
||||||
description: Write failing tests first, then minimal code to pass. Non-negotiable for all new features and bug fixes.
|
description: Write failing tests first, then minimal code to pass. Non-negotiable for all new features and bug fixes.
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -97,13 +100,21 @@ go test -run TestRetryOperation_RetriesThreeTimes ./...
|
|||||||
```
|
```
|
||||||
|
|
||||||
Confirm:
|
Confirm:
|
||||||
- Test fails (not compilation errors)
|
- Test fails, and fails because the feature is missing
|
||||||
- Failure message is expected
|
- The failure message is the one you expected
|
||||||
- Fails because feature is missing (not typos)
|
- It is not failing on a typo in the test itself
|
||||||
|
|
||||||
**Test passes?** You're testing existing behavior. Fix test.
|
**Test passes?** You're testing existing behavior. Fix test.
|
||||||
|
|
||||||
**Compilation errors?** Fix errors, re-run until it fails correctly.
|
**Failing because the thing does not exist yet?** That is a legitimate red — a Go
|
||||||
|
compile error for an undefined function, a Python `ImportError` for a module you have
|
||||||
|
not written, a TypeScript "cannot find module". Read the message, confirm it names the
|
||||||
|
unit you are about to build, and proceed to GREEN. **Do not write a stub to make the
|
||||||
|
runner happier.** A stub is production code written before a failing test, which is the
|
||||||
|
Iron Law with extra steps.
|
||||||
|
|
||||||
|
**Failing on a typo or a broken import elsewhere?** Different thing. Fix it and re-run
|
||||||
|
until the failure is the one above.
|
||||||
|
|
||||||
### GREEN - Minimal Code
|
### GREEN - Minimal Code
|
||||||
|
|
||||||
@@ -163,6 +174,23 @@ Keep tests green. Don't add behavior.
|
|||||||
|
|
||||||
Next failing test for next behavior.
|
Next failing test for next behavior.
|
||||||
|
|
||||||
|
## Other languages
|
||||||
|
|
||||||
|
The discipline above is language-neutral; the examples below it are Go, because Go is
|
||||||
|
this fleet's default stack and one good example beats five mediocre ones.
|
||||||
|
|
||||||
|
For anything else, the per-language conventions live in reference files — load the one
|
||||||
|
you need rather than translating Go idioms by eye. Translation is where the mistakes
|
||||||
|
happen: the Go table-driven exemplar below branches on a `wantErr` column inside the
|
||||||
|
loop, which is idiomatic Go and an anti-pattern in pytest, where it produces a
|
||||||
|
conditional assertion and folds two behaviours into one test.
|
||||||
|
|
||||||
|
- `references/python.md` — pytest, `@pytest.mark.parametrize`, `mypy --strict` in the
|
||||||
|
RED gate, `pytest.raises(..., match=)`, fixtures vs helpers
|
||||||
|
- `references/typescript.md` — vitest/jest, `it.each`, `expect(...).rejects`
|
||||||
|
- `references/rust.md` — `#[cfg(test)]` modules, table-driven via arrays, `#[should_panic]`
|
||||||
|
- `references/java.md` — JUnit 5, `@ParameterizedTest`, `assertThrows`
|
||||||
|
|
||||||
## Go-Specific TDD Notes
|
## Go-Specific TDD Notes
|
||||||
|
|
||||||
### Table-Driven Tests (Preferred Pattern)
|
### Table-Driven Tests (Preferred Pattern)
|
||||||
@@ -326,8 +354,14 @@ Before marking work complete:
|
|||||||
- [ ] Watched each test fail before implementing
|
- [ ] Watched each test fail before implementing
|
||||||
- [ ] Each test failed for expected reason (feature missing, not typo)
|
- [ ] Each test failed for expected reason (feature missing, not typo)
|
||||||
- [ ] Wrote minimal code to pass each test
|
- [ ] Wrote minimal code to pass each test
|
||||||
- [ ] All tests pass: `go test ./...`
|
- [ ] All tests pass
|
||||||
- [ ] Race detector clean: `go test -race ./...`
|
- [ ] The language's static gate is clean — `go vet`, `mypy --strict`, `tsc --noEmit`,
|
||||||
|
`cargo clippy`. A red type-check is a red build; it does not wait for GREEN.
|
||||||
|
- [ ] The language's concurrency gate is clean **where one exists and applies** —
|
||||||
|
`go test -race`, `cargo test` under Miri for unsafe code. There is no equivalent
|
||||||
|
for a pure Python function, and a checklist box that can never be ticked teaches
|
||||||
|
people to skip checklists. Tick it or strike it deliberately; do not leave it
|
||||||
|
hanging.
|
||||||
- [ ] 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
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# TDD in Java
|
||||||
|
|
||||||
|
The Iron Law and Red-Green-Refactor in `SKILL.md` apply unchanged. This file replaces
|
||||||
|
the Go-Specific TDD Notes for Java. JUnit 5 (Jupiter) throughout.
|
||||||
|
|
||||||
|
## The first red is a compile error, and that is correct
|
||||||
|
|
||||||
|
`cannot find symbol: method splitVat` is a legitimate red. Do not stub the method with
|
||||||
|
`throw new UnsupportedOperationException()` to make the module compile — that is
|
||||||
|
production code before a failing test.
|
||||||
|
|
||||||
|
## Table-driven: `@ParameterizedTest`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@ParameterizedTest(name = "{0}")
|
||||||
|
@MethodSource("vatCases")
|
||||||
|
void roundsHalfToEven(String name, long amountMinor, BigDecimal rate, long net, long vat) {
|
||||||
|
assertEquals(new Split(net, vat), VatSplitter.split(amountMinor, rate));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> vatCases() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("tie rounds down to even", 14L, new BigDecimal("0.12"), 12L, 2L),
|
||||||
|
Arguments.of("tie rounds up to even", 42L, new BigDecimal("0.12"), 38L, 4L),
|
||||||
|
Arguments.of("exact division", 12500L, new BigDecimal("0.25"), 10000L, 2500L)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`name = "{0}"` puts the case label in the report — the `t.Run` analogue. `@CsvSource` is
|
||||||
|
terser for primitives; `@MethodSource` is the one to use as soon as a case carries a
|
||||||
|
`BigDecimal` or a domain object.
|
||||||
|
|
||||||
|
## Error cases get their own test
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void rejectsNegativeAmount() {
|
||||||
|
var e = assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> VatSplitter.split(-1, new BigDecimal("0.25")));
|
||||||
|
assertTrue(e.getMessage().contains("non-negative"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Assert on the message, not only the type — `IllegalArgumentException` is thrown by half
|
||||||
|
the JDK, and a test that accepts any of them will keep passing for the wrong reason.
|
||||||
|
|
||||||
|
## Money is `BigDecimal` or `long` minor units, never `double`
|
||||||
|
|
||||||
|
`0.1 + 0.2 != 0.3` is not a rounding curiosity in a payments system, it is a defect.
|
||||||
|
Where a rounding mode matters, state it: `setScale(0, RoundingMode.HALF_EVEN)`. Banker's
|
||||||
|
rounding is the default expectation in Swedish accounting and is *not* Java's default.
|
||||||
|
|
||||||
|
## Assertions
|
||||||
|
|
||||||
|
Prefer AssertJ (`assertThat(x).isEqualTo(y)`) where the project already has it: the
|
||||||
|
failure messages name the actual value. Where it does not, plain JUnit assertions are
|
||||||
|
fine — do not add a dependency for aesthetics.
|
||||||
|
|
||||||
|
`assertAll` is the closest analogue to Go's soft `assert`: it reports every failure in
|
||||||
|
the group instead of stopping at the first.
|
||||||
|
|
||||||
|
## Mocks
|
||||||
|
|
||||||
|
Mockito is standard, and easy to overuse. Per `SKILL.md`, mock only across a system
|
||||||
|
boundary. A mocked class you own tests the mock; a `@SpringBootTest` that mocks the
|
||||||
|
repository tests neither the wiring nor the query.
|
||||||
|
|
||||||
|
## Gates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test # the failing test
|
||||||
|
./gradlew check # static gate: compiler warnings, spotless, errorprone
|
||||||
|
```
|
||||||
|
|
||||||
|
Java has no `-race`. For concurrent code the honest substitutes are a stress test with
|
||||||
|
`CountDownLatch` to force the interleaving, and jcstress for lock-free work. A pure
|
||||||
|
function should strike that checklist box deliberately.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# TDD in Python
|
||||||
|
|
||||||
|
The Iron Law, Red-Green-Refactor and the rationalization table in `SKILL.md` apply
|
||||||
|
unchanged. This file replaces the Go-Specific TDD Notes for Python work.
|
||||||
|
|
||||||
|
Canonical stack (`~/dev/.context/AGENT.md`): pytest + pytest-cov, `mypy --strict`, ruff,
|
||||||
|
uv, src-layout. Not restated here — that table has one home.
|
||||||
|
|
||||||
|
## The first red is an ImportError, and that is correct
|
||||||
|
|
||||||
|
For a function that does not exist yet, the first run fails at collection:
|
||||||
|
|
||||||
|
```
|
||||||
|
E ModuleNotFoundError: No module named 'invoicing.vat'
|
||||||
|
```
|
||||||
|
|
||||||
|
That is a legitimate red. Read it, confirm it names the module you are about to write,
|
||||||
|
and go to GREEN. **Do not create a stub raising `NotImplementedError` to make pytest
|
||||||
|
collect.** A stub is production code written before a failing test.
|
||||||
|
|
||||||
|
## Table-driven: `parametrize`, one behaviour per function
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("amount_minor", "rate", "expected"),
|
||||||
|
[
|
||||||
|
pytest.param(14, Decimal("0.12"), (12, 2), id="tie-rounds-down-to-even"),
|
||||||
|
pytest.param(42, Decimal("0.12"), (38, 4), id="tie-rounds-up-to-even"),
|
||||||
|
pytest.param(12500, Decimal("0.25"), (10000, 2500), id="exact-division"),
|
||||||
|
pytest.param(0, Decimal("0.25"), (0, 0), id="zero-is-not-an-error"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_split_vat_rounds_half_to_even(
|
||||||
|
amount_minor: int, rate: Decimal, expected: tuple[int, int]
|
||||||
|
) -> None:
|
||||||
|
assert split_vat(amount_minor, rate) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
`id=` is the analogue of Go's `t.Run` name: it is what appears in the failure output and
|
||||||
|
what you pass to `pytest -k`.
|
||||||
|
|
||||||
|
**Do not port Go's `wantErr` column.** The Go exemplar branches inside the loop —
|
||||||
|
`if tt.wantErr { assert.Error } else { assert.NoError }`. Translated literally that
|
||||||
|
gives a conditional assertion and folds two behaviours into one test, which `SKILL.md`'s
|
||||||
|
own quality table forbids ("One thing. 'and' in name? Split it."). Error cases get
|
||||||
|
their own function:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_split_vat_rejects_negative_amount() -> None:
|
||||||
|
with pytest.raises(ValueError, match="amount_minor must be non-negative"):
|
||||||
|
split_vat(-1, Decimal("0.25"))
|
||||||
|
```
|
||||||
|
|
||||||
|
`match=` pins the message, not just the type. Go's `assert.Error` does not, and the
|
||||||
|
stricter version is available here for free — use it.
|
||||||
|
|
||||||
|
## `mypy --strict` is part of RED, not a chore after GREEN
|
||||||
|
|
||||||
|
A red type-check is a red build.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/test_vat.py -x -q # the failing test
|
||||||
|
uv run mypy --strict src tests # same gate, same phase
|
||||||
|
```
|
||||||
|
|
||||||
|
Under `--strict` every test function needs `-> None`, or it is an untyped def and the
|
||||||
|
type-checker rejects the file — including the file that holds your failing test.
|
||||||
|
|
||||||
|
## Layout and imports
|
||||||
|
|
||||||
|
- `src/` layout, tests in a sibling `tests/`. This is the *opposite* of Go's
|
||||||
|
same-directory convention, and it buys black-box testing structurally: `from
|
||||||
|
invoicing.vat import split_vat` can only resolve against the installed package, so
|
||||||
|
the preference Go has to state explicitly is enforced here by the import system.
|
||||||
|
- No `__init__.py` in `tests/` unless you need shared test packages; pytest's rootdir
|
||||||
|
handling copes, and an accidental package shadows names.
|
||||||
|
- `conftest.py` holds fixtures shared across a directory. A fixture in a single test
|
||||||
|
file that only one test uses should be a plain function call instead — fixtures are
|
||||||
|
indirection, and indirection is only worth it at reuse.
|
||||||
|
|
||||||
|
## No `require` vs `assert`
|
||||||
|
|
||||||
|
Every pytest `assert` is fatal to its test; there is no continue-on-failure form. Where
|
||||||
|
Go would use `assert` to collect several soft failures, either split the test or use
|
||||||
|
`pytest.approx`/a single structural comparison:
|
||||||
|
|
||||||
|
```python
|
||||||
|
assert result == Invoice(id=InvoiceId("42"), net=10000, vat=2500) # one assertion, whole shape
|
||||||
|
```
|
||||||
|
|
||||||
|
Comparing whole values beats three field assertions: the failure output shows the entire
|
||||||
|
diff rather than the first field that differed.
|
||||||
|
|
||||||
|
## Fixtures vs helpers
|
||||||
|
|
||||||
|
- **Helper function** — when the setup is cheap and local. It reads top-to-bottom.
|
||||||
|
- **Fixture** — when the setup is shared, or needs teardown, or needs a scope
|
||||||
|
(`session`, `module`) because it is expensive.
|
||||||
|
- Set `__tracebackhide__ = True` in an assertion helper so failures point at the caller,
|
||||||
|
the way `t.Helper()` does in Go.
|
||||||
|
|
||||||
|
## Coverage is not the concurrency gate
|
||||||
|
|
||||||
|
`SKILL.md`'s checklist asks for the language's concurrency gate. Python has no `-race`
|
||||||
|
equivalent, and pretending `pytest --cov` fills that slot is dishonest — coverage says
|
||||||
|
which lines ran, not whether two threads can interleave badly. For a pure function,
|
||||||
|
strike the box deliberately. For genuinely concurrent code (`asyncio`, threads), the
|
||||||
|
substitute is a test that runs the interleaving you fear, plus `pytest-timeout` so a
|
||||||
|
deadlock fails rather than hangs.
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# TDD in Rust
|
||||||
|
|
||||||
|
The Iron Law and Red-Green-Refactor in `SKILL.md` apply unchanged. This file replaces
|
||||||
|
the Go-Specific TDD Notes for Rust.
|
||||||
|
|
||||||
|
## The first red is a compile error, and that is correct
|
||||||
|
|
||||||
|
```
|
||||||
|
error[E0425]: cannot find function `split_vat` in this scope
|
||||||
|
```
|
||||||
|
|
||||||
|
Legitimate — the compiler is the test runner's first phase. Read it, confirm it names
|
||||||
|
the item you are about to write, and go to GREEN. Do not write a `todo!()` stub first;
|
||||||
|
that is production code before a failing test. (`todo!()` is fine *during* GREEN as a
|
||||||
|
branch you have not reached yet.)
|
||||||
|
|
||||||
|
## Where tests live
|
||||||
|
|
||||||
|
- **Unit tests** in the same file, in `mod tests` behind `#[cfg(test)]`. They can reach
|
||||||
|
private items — the white-box option Go spells `package foo`.
|
||||||
|
- **Integration tests** in `tests/`, which link the crate as an external consumer and see
|
||||||
|
only the public API. This is the black-box default `SKILL.md` prefers; use it unless
|
||||||
|
the behaviour under test is genuinely private.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tie_rounds_down_to_even() {
|
||||||
|
assert_eq!(split_vat(14, dec!(0.12)).unwrap(), (12, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Table-driven: an array of cases
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn rounds_half_to_even() {
|
||||||
|
let cases = [
|
||||||
|
("tie rounds down", 14u64, dec!(0.12), (12, 2)),
|
||||||
|
("tie rounds up", 42, dec!(0.12), (38, 4)),
|
||||||
|
("exact division", 12500, dec!(0.25), (10000, 2500)),
|
||||||
|
];
|
||||||
|
for (name, amount, rate, want) in cases {
|
||||||
|
assert_eq!(split_vat(amount, rate).unwrap(), want, "case: {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"case: {name}"` message is the `t.Run` analogue — without it a failure names a line
|
||||||
|
number and leaves you counting rows. If you want each case to be its own reported test,
|
||||||
|
use `rstest`'s `#[case]`; that is a dependency, so justify it before adding it.
|
||||||
|
|
||||||
|
## Error cases
|
||||||
|
|
||||||
|
`Result` makes the Go `wantErr` column unnecessary — assert on the variant:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn rejects_negative_amount() {
|
||||||
|
let err = split_vat_signed(-1, dec!(0.25)).unwrap_err();
|
||||||
|
assert!(matches!(err, VatError::NegativeAmount));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`#[should_panic(expected = "...")]` is for genuine panics only — invariant violations,
|
||||||
|
not expected error paths. A function whose contract includes "rejects negative input"
|
||||||
|
should return `Err`, not panic, and a test that accepts a panic hides that design choice.
|
||||||
|
|
||||||
|
## Gates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test # the failing test
|
||||||
|
cargo clippy -- -D warnings # static gate, same phase as RED
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo test` compiles in debug, so integer overflow panics rather than wrapping — a
|
||||||
|
release build will not. Where overflow matters, test the checked path explicitly
|
||||||
|
(`checked_mul`), or you are testing debug-only behaviour.
|
||||||
|
|
||||||
|
For unsafe code, `cargo +nightly miri test` is the nearest thing to Go's `-race`.
|
||||||
|
Safe-only crates have no equivalent and should strike that checklist box deliberately.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# TDD in TypeScript
|
||||||
|
|
||||||
|
The Iron Law and Red-Green-Refactor in `SKILL.md` apply unchanged. This file replaces
|
||||||
|
the Go-Specific TDD Notes for TypeScript.
|
||||||
|
|
||||||
|
Vitest is the default; Jest differs only in imports and a few matcher names, both noted
|
||||||
|
where it matters.
|
||||||
|
|
||||||
|
## The first red is a resolution error, and that is correct
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: Failed to resolve import "./vat" from "src/vat.test.ts"
|
||||||
|
```
|
||||||
|
|
||||||
|
Legitimate. Do not create an empty module or a stub throwing `new Error("not
|
||||||
|
implemented")` to get past collection — that is production code before a failing test.
|
||||||
|
|
||||||
|
## Table-driven: `it.each`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
describe('splitVat', () => {
|
||||||
|
it.each([
|
||||||
|
{ amountMinor: 14, rate: 0.12, expected: [12, 2], name: 'tie rounds down to even' },
|
||||||
|
{ amountMinor: 42, rate: 0.12, expected: [38, 4], name: 'tie rounds up to even' },
|
||||||
|
{ amountMinor: 12500, rate: 0.25, expected: [10000, 2500], name: 'exact division' },
|
||||||
|
])('$name', ({ amountMinor, rate, expected }) => {
|
||||||
|
expect(splitVat(amountMinor, rate)).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`$name` interpolates into the test title, which is the `t.Run` analogue. Prefer the
|
||||||
|
object form over the array form — a positional tuple with five entries is unreadable at
|
||||||
|
the point of failure.
|
||||||
|
|
||||||
|
**Do not port Go's `wantErr` column.** Error cases get their own test:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('rejects a negative amount', () => {
|
||||||
|
expect(() => splitVat(-1, 0.25)).toThrow(/must be non-negative/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a negative amount (async)', async () => {
|
||||||
|
await expect(splitVat(-1, 0.25)).rejects.toThrow(/must be non-negative/)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The `await` on the async form is load-bearing. Without it the assertion returns a
|
||||||
|
promise nobody checks, the test passes, and it will keep passing after you break the
|
||||||
|
code. This is the single most common false-green in a TypeScript suite.
|
||||||
|
|
||||||
|
## `tsc --noEmit` is part of RED
|
||||||
|
|
||||||
|
A red type-check is a red build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitest run src/vat.test.ts # the failing test
|
||||||
|
npx tsc --noEmit # same gate, same phase
|
||||||
|
```
|
||||||
|
|
||||||
|
Vitest transpiles without type-checking, so a suite can be green while the code does not
|
||||||
|
compile. `tsc --noEmit` is not optional here — it is the half of the gate the runner
|
||||||
|
does not perform.
|
||||||
|
|
||||||
|
## Equality matchers are not interchangeable
|
||||||
|
|
||||||
|
- `toBe` — `Object.is`. For primitives and identity.
|
||||||
|
- `toEqual` — structural, and **ignores `undefined` properties**: `{a: 1, b: undefined}`
|
||||||
|
equals `{a: 1}`. That is a real hole when the bug you are hunting is a missing field.
|
||||||
|
- `toStrictEqual` — structural, respects `undefined`, and checks class identity. Default
|
||||||
|
to this one; reach for `toEqual` deliberately.
|
||||||
|
|
||||||
|
## Mocks
|
||||||
|
|
||||||
|
`vi.mock` (Jest: `jest.mock`) is hoisted above imports, so a factory referencing an
|
||||||
|
outer variable throws at runtime. Use `vi.hoisted` when the mock needs shared state.
|
||||||
|
Per `SKILL.md`, mock only across a system boundary — a mocked module you own is a test
|
||||||
|
of your mock.
|
||||||
|
|
||||||
|
## Async and fake timers
|
||||||
|
|
||||||
|
- `vi.useFakeTimers()` needs a matching `vi.useRealTimers()` in teardown, or the next
|
||||||
|
file inherits frozen time and fails somewhere unrelated.
|
||||||
|
- Prefer condition polling (`await vi.waitFor(...)`) over a fixed `setTimeout` — an
|
||||||
|
arbitrary wait is a flake with a schedule.
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: telos-load
|
name: telos-load
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Load TELOS intention substrate at session start"
|
||||||
|
use_when: "Starting any koala session; before architectural decisions; CAD pipeline entry"
|
||||||
description: Load TELOS — the intention substrate (problems, goals, strategies, status) — into a session at start so every decision traces to a goal. Use when starting any koala session, before architectural decisions, or as the CAD pipeline entry point ("load TELOS", "what are we optimizing toward?").
|
description: Load TELOS — the intention substrate (problems, goals, strategies, status) — into a session at start so every decision traces to a goal. Use when starting any koala session, before architectural decisions, or as the CAD pipeline entry point ("load TELOS", "what are we optimizing toward?").
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: test-design
|
name: test-design
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Dave Farley test design scoring (Farley Index)"
|
||||||
|
use_when: "Reviewing or writing tests"
|
||||||
description: Evaluate test quality using Dave Farley's 8 Properties of Good Tests. Use when reviewing or writing tests to ensure they provide genuine verification.
|
description: Evaluate test quality using Dave Farley's 8 Properties of Good Tests. Use when reviewing or writing tests to ensure they provide genuine verification.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: trainer
|
name: trainer
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Two-phase brain curation (score candidates, write past quality gate)"
|
||||||
|
use_when: "Periodic brain pruning and curation"
|
||||||
description: Two-phase brain curation — scan session logs to score candidate learnings (READ), then write the ones that pass the quality gate as generalized knowledge entries (WRITE). Use periodically to keep the brain useful, not noisy.
|
description: Two-phase brain curation — scan session logs to score candidate learnings (READ), then write the ones that pass the quality gate as generalized knowledge entries (WRITE). Use periodically to keep the brain useful, not noisy.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: user-stories
|
name: user-stories
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Elephant Carpaccio story slicing"
|
||||||
|
use_when: "Breaking down features"
|
||||||
description: Decompose problems into thin, deliverable user stories using Elephant Carpaccio and INVEST criteria. Use after problem analysis, before planning and implementation.
|
description: Decompose problems into thin, deliverable user stories using Elephant Carpaccio and INVEST criteria. Use after problem analysis, before planning and implementation.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: web-shot
|
name: web-shot
|
||||||
|
surfaces: [claude-code, library]
|
||||||
|
summary: "Screenshot a running web UI via a koala k3s Playwright job"
|
||||||
|
use_when: "Visual verification of a running UI when no local browser exists"
|
||||||
description: Screenshot any running web UI by spawning a one-shot Playwright Job on the koala k3s cluster. Auto-detects the calling host's Tailscale name so a dev server on flamingo:8099, iguana:3000 or koala:8181 just works. Use when the user asks for a screenshot, visual verification, "what does the UI look like", or any time chromium / firefox / a browser is requested but none is installed on the host.
|
description: Screenshot any running web UI by spawning a one-shot Playwright Job on the koala k3s cluster. Auto-detects the calling host's Tailscale name so a dev server on flamingo:8099, iguana:3000 or koala:8181 just works. Use when the user asks for a screenshot, visual verification, "what does the UI look like", or any time chromium / firefox / a browser is requested but none is installed on the host.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Tests for bin/check-index.
|
|
||||||
#
|
|
||||||
# The regression these exist for: SKILLS_INDEX.md is maintained by hand, and
|
|
||||||
# two skills (`dream`, `experiment-spec`) shipped without a row — the index
|
|
||||||
# rows lived on feature branches that were squash-merged after a directory
|
|
||||||
# reorg, so the SKILL.md landed and the row did not (#15, #22). Nothing
|
|
||||||
# errored; the index just quietly under-reported the library.
|
|
||||||
|
|
||||||
set -u
|
|
||||||
|
|
||||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
||||||
SUT="$ROOT/bin/check-index"
|
|
||||||
|
|
||||||
pass=0
|
|
||||||
fail=0
|
|
||||||
WORK=${TMPDIR:-/tmp}/check_index_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
|
|
||||||
|
|
||||||
# new_fixture <name> — a checkout with skills/alpha and skills/beta.
|
|
||||||
new_fixture() {
|
|
||||||
_f="$WORK/$1"
|
|
||||||
mkdir -p "$_f/skills/alpha" "$_f/skills/beta"
|
|
||||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$_f/skills/alpha/SKILL.md"
|
|
||||||
printf -- '---\nname: beta\ndescription: b\n---\n' > "$_f/skills/beta/SKILL.md"
|
|
||||||
printf '%s' "$_f"
|
|
||||||
}
|
|
||||||
|
|
||||||
index() {
|
|
||||||
_dir=$1; shift
|
|
||||||
printf '# Engineering Skills Index\n\n| Skill | Description | Use when |\n|---|---|---|\n' > "$_dir/SKILLS_INDEX.md"
|
|
||||||
for _n in "$@"; do
|
|
||||||
printf '| `%s` | desc | when |\n' "$_n" >> "$_dir/SKILLS_INDEX.md"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# expect <0|1> <label> <fixture> [grep-for]
|
|
||||||
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, but report never names %s)\n%s\n' "$_label" "$_needle" "$_out"; return
|
|
||||||
fi
|
|
||||||
pass=$((pass + 1)); printf ' ok %s\n' "$_label"
|
|
||||||
}
|
|
||||||
|
|
||||||
fx=$(new_fixture complete); index "$fx" alpha beta
|
|
||||||
expect 0 'every skill has a row' "$fx"
|
|
||||||
|
|
||||||
fx=$(new_fixture missing); index "$fx" alpha
|
|
||||||
expect 1 'a skill with no row is reported' "$fx" beta
|
|
||||||
|
|
||||||
fx=$(new_fixture stale); index "$fx" alpha beta gamma
|
|
||||||
expect 1 'a row for a skill that does not exist is reported' "$fx" gamma
|
|
||||||
|
|
||||||
fx=$(new_fixture noindex)
|
|
||||||
expect 1 'a missing index is reported' "$fx" SKILLS_INDEX.md
|
|
||||||
|
|
||||||
printf '\ncheck_index: %d passed, %d failed\n' "$pass" "$fail"
|
|
||||||
[ "$fail" -eq 0 ]
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Tests for bin/check-surfaces.
|
||||||
|
#
|
||||||
|
# The repo ships to three surfaces — the Claude Code plugin, the symlinked
|
||||||
|
# library (Crush/Pi/OpenCode) and claude.ai, which has no fetch path at all —
|
||||||
|
# and used to treat every skill as belonging to all of them. gitea-ci,
|
||||||
|
# playwright and web-shot cannot fire in a chat surface; a flat mirror installs
|
||||||
|
# skills that can never trigger and degrades selection for the ones that can
|
||||||
|
# (#29).
|
||||||
|
#
|
||||||
|
# The risk this gate exists for: a wrong `surfaces:` value silently removes a
|
||||||
|
# skill from a harness rather than erroring. So presence is not enough — values
|
||||||
|
# are validated against the allowed set.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
SUT="$ROOT/bin/check-surfaces"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
WORK=${TMPDIR:-/tmp}/check_surfaces_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> <surfaces-line-or-empty> — a checkout holding one skill.
|
||||||
|
fixture() {
|
||||||
|
_f="$WORK/$1"
|
||||||
|
mkdir -p "$_f/skills/alpha"
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
printf -- '---\nname: alpha\n%s\ndescription: a\n---\n' "$2" > "$_f/skills/alpha/SKILL.md"
|
||||||
|
else
|
||||||
|
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$_f/skills/alpha/SKILL.md"
|
||||||
|
fi
|
||||||
|
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 'the default pair is accepted' "$(fixture ok 'surfaces: [claude-code, library]')"
|
||||||
|
expect 0 'all three surfaces are accepted' "$(fixture three 'surfaces: [claude-code, library, chat]')"
|
||||||
|
expect 0 'a single surface is accepted' "$(fixture one 'surfaces: [chat]')"
|
||||||
|
expect 1 'a missing surfaces field fails' "$(fixture missing '')" 'alpha'
|
||||||
|
expect 1 'an unknown surface value fails' "$(fixture bogus 'surfaces: [claude-code, desktop]')" 'desktop'
|
||||||
|
expect 1 'an empty surfaces list fails' "$(fixture empty 'surfaces: []')" 'alpha'
|
||||||
|
|
||||||
|
printf '\ncheck_surfaces: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
@@ -127,6 +127,13 @@ f=$(new_fixture missing); wire_clean "$f"
|
|||||||
rm -f "$f/home/.agents/skills/beta"
|
rm -f "$f/home/.agents/skills/beta"
|
||||||
assert_fails "$f" 'beta' 'a checkout skill with no link is reported'
|
assert_fails "$f" 'beta' 'a checkout skill with no link is reported'
|
||||||
|
|
||||||
|
# ...but only for skills that ship to the library. A chat-only skill has no
|
||||||
|
# business in a Crush directory, so its absence is correct, not drift (#29).
|
||||||
|
f=$(new_fixture chatonly); wire_clean "$f"
|
||||||
|
mkdir -p "$f/ck/skills/gamma"
|
||||||
|
printf -- '---\nname: gamma\nsurfaces: [chat]\ndescription: g\n---\n' > "$f/ck/skills/gamma/SKILL.md"
|
||||||
|
assert_clean "$f" 'a chat-only skill needs no library link'
|
||||||
|
|
||||||
# --- 5. stray entry (the loose secret-hygiene.md that loaded nowhere) --------
|
# --- 5. stray entry (the loose secret-hygiene.md that loaded nowhere) --------
|
||||||
f=$(new_fixture stray); wire_clean "$f"
|
f=$(new_fixture stray); wire_clean "$f"
|
||||||
printf 'orphan\n' > "$f/ck/skills/loose-note.md"
|
printf 'orphan\n' > "$f/ck/skills/loose-note.md"
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Tests for bin/generate-index.
|
||||||
|
#
|
||||||
|
# SKILLS_INDEX.md was maintained by hand and drifted: two skills shipped with
|
||||||
|
# no row at all (#15), because their rows lived on branches squash-merged after
|
||||||
|
# a directory reorg. A gate caught drift after the fact; generating the table
|
||||||
|
# from frontmatter removes the class entirely (#29) — the curated summary and
|
||||||
|
# trigger now live next to the skill they describe, which is the same argument
|
||||||
|
# that made a hand-maintained index the wrong shape.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
SUT="$ROOT/bin/generate-index"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
WORK=${TMPDIR:-/tmp}/generate_index_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
|
||||||
|
|
||||||
|
ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; }
|
||||||
|
no() { fail=$((fail + 1)); printf ' FAIL %s\n%s\n' "$1" "${2:-}"; }
|
||||||
|
|
||||||
|
# skill <root> <name> <surfaces> [summary] [use_when]
|
||||||
|
skill() {
|
||||||
|
mkdir -p "$1/skills/$2"
|
||||||
|
{
|
||||||
|
printf -- '---\nname: %s\nsurfaces: %s\n' "$2" "$3"
|
||||||
|
[ $# -lt 4 ] || printf 'summary: "%s"\n' "$4"
|
||||||
|
[ $# -lt 5 ] || printf 'use_when: "%s"\n' "$5"
|
||||||
|
printf -- 'description: d\n---\n'
|
||||||
|
} > "$1/skills/$2/SKILL.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
# fixture <name> — a root with an index carrying the generated-table markers.
|
||||||
|
fixture() {
|
||||||
|
_f="$WORK/$1"
|
||||||
|
mkdir -p "$_f"
|
||||||
|
cat > "$_f/SKILLS_INDEX.md" <<'IDX'
|
||||||
|
# Engineering Skills Index
|
||||||
|
|
||||||
|
<!-- BEGIN skills-table -->
|
||||||
|
<!-- END skills-table -->
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
prose that must survive regeneration
|
||||||
|
IDX
|
||||||
|
printf '%s' "$_f"
|
||||||
|
}
|
||||||
|
|
||||||
|
f=$(fixture basic)
|
||||||
|
skill "$f" beta '[claude-code, library]' 'B summary' 'when B'
|
||||||
|
skill "$f" alpha '[chat]' 'A summary' 'when A'
|
||||||
|
out=$(sh "$SUT" "$f" 2>&1); rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
no 'generates a table' "$out"
|
||||||
|
elif ! grep -q '^| `alpha` | A summary | when A |' "$f/SKILLS_INDEX.md"; then
|
||||||
|
no 'generates a table' "$(cat "$f/SKILLS_INDEX.md")"
|
||||||
|
elif ! grep -q 'prose that must survive regeneration' "$f/SKILLS_INDEX.md"; then
|
||||||
|
no 'content outside the markers survives' "$(cat "$f/SKILLS_INDEX.md")"
|
||||||
|
elif [ "$(grep -n '`alpha`' "$f/SKILLS_INDEX.md" | cut -d: -f1)" -gt "$(grep -n '`beta`' "$f/SKILLS_INDEX.md" | cut -d: -f1)" ]; then
|
||||||
|
no 'rows are sorted' "$(cat "$f/SKILLS_INDEX.md")"
|
||||||
|
else
|
||||||
|
ok 'generates a sorted table and leaves the rest of the file alone'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The surfaces a skill ships to are the point of #29, so they are visible.
|
||||||
|
grep -q 'chat' "$f/SKILLS_INDEX.md" \
|
||||||
|
&& ok 'the row names the surfaces' \
|
||||||
|
|| no 'the row names the surfaces' "$(cat "$f/SKILLS_INDEX.md")"
|
||||||
|
|
||||||
|
# Idempotent: a second run changes nothing.
|
||||||
|
before=$(cat "$f/SKILLS_INDEX.md")
|
||||||
|
sh "$SUT" "$f" >/dev/null 2>&1
|
||||||
|
[ "$before" = "$(cat "$f/SKILLS_INDEX.md")" ] \
|
||||||
|
&& ok 'regeneration is idempotent' \
|
||||||
|
|| no 'regeneration is idempotent'
|
||||||
|
|
||||||
|
# --check is the CI gate: clean tree passes.
|
||||||
|
sh "$SUT" --check "$f" >/dev/null 2>&1 \
|
||||||
|
&& ok '--check passes on a generated index' \
|
||||||
|
|| no '--check passes on a generated index'
|
||||||
|
|
||||||
|
# ...and fails once a skill appears without regenerating.
|
||||||
|
skill "$f" gamma '[library]' 'G summary' 'when G'
|
||||||
|
if sh "$SUT" --check "$f" >/dev/null 2>&1; then
|
||||||
|
no '--check fails on a stale index'
|
||||||
|
else
|
||||||
|
ok '--check fails on a stale index'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A skill with no curated summary is an error, not an empty cell: the table
|
||||||
|
# cell is what a reader scans, and a blank one is worse than a missing row.
|
||||||
|
f=$(fixture nosummary)
|
||||||
|
skill "$f" alpha '[library]'
|
||||||
|
out=$(sh "$SUT" "$f" 2>&1); rc=$?
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
no 'a missing summary is an error' "$out"
|
||||||
|
elif ! printf '%s' "$out" | grep -q alpha; then
|
||||||
|
no 'a missing summary is an error' "$out"
|
||||||
|
else
|
||||||
|
ok 'a missing summary is an error naming the skill'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\ngenerate_index: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
@@ -156,5 +156,32 @@ else
|
|||||||
no 'configured sources are recorded for doctor' "$out"
|
no 'configured sources are recorded for doctor' "$out"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- 8. surfaces: the library link set is not "every skill" -----------------
|
||||||
|
# claude.ai has no shell, no cluster and no checkout, so a chat-only skill
|
||||||
|
# linked into Crush is a skill that can never fire, competing for selection
|
||||||
|
# with ones that can (#29).
|
||||||
|
f=$(new_fixture surfaces)
|
||||||
|
mkdir -p "$f/ck/skills/chatonly"
|
||||||
|
printf -- '---\nname: chatonly\nsurfaces: [chat]\ndescription: c\n---\n' > "$f/ck/skills/chatonly/SKILL.md"
|
||||||
|
printf -- '---\nname: alpha\nsurfaces: [claude-code, library]\ndescription: a\n---\n' > "$f/ck/skills/alpha/SKILL.md"
|
||||||
|
out=$(run_install "$f")
|
||||||
|
if [ -L "$f/home/.agents/skills/alpha" ] && [ ! -e "$f/home/.agents/skills/chatonly" ]; then
|
||||||
|
ok 'a skill that does not ship to the library is not linked'
|
||||||
|
else
|
||||||
|
no 'a skill that does not ship to the library is not linked' "$out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A foreign source has no reason to know this convention, so no declaration
|
||||||
|
# means "link it" — otherwise adding Anthropic's set would link nothing.
|
||||||
|
f=$(new_fixture foreign)
|
||||||
|
mkdir -p "$f/other/xlsx"
|
||||||
|
printf -- '---\nname: xlsx\ndescription: x\n---\n' > "$f/other/xlsx/SKILL.md"
|
||||||
|
out=$(run_install "$f" "$f/other")
|
||||||
|
if [ -L "$f/home/.agents/skills/xlsx" ]; then
|
||||||
|
ok 'a source with no surfaces convention is linked as-is'
|
||||||
|
else
|
||||||
|
no 'a source with no surfaces convention is linked as-is' "$out"
|
||||||
|
fi
|
||||||
|
|
||||||
printf '\ninstall: %d passed, %d failed\n' "$pass" "$fail"
|
printf '\ninstall: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
[ "$fail" -eq 0 ]
|
[ "$fail" -eq 0 ]
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Tests for bin/newer-version.
|
||||||
|
#
|
||||||
|
# Extracted from bin/doctor (#30) once `task check` needed the same comparison
|
||||||
|
# (#34): check asserted plugin.json == latest tag, which is red on any checkout
|
||||||
|
# that has not fetched the tag the CI tagger just pushed. The assertion it
|
||||||
|
# actually wants is "not behind", and ordering needs a real comparison.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
SUT="$ROOT/bin/newer-version"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
if [ ! -f "$SUT" ]; then
|
||||||
|
printf 'FAIL: %s does not exist\n' "$SUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# expect <winner> <a> <b> <label>
|
||||||
|
expect() {
|
||||||
|
_got=$(sh "$SUT" "$2" "$3")
|
||||||
|
if [ "$_got" = "$1" ]; then
|
||||||
|
pass=$((pass + 1)); printf ' ok %s\n' "$4"
|
||||||
|
else
|
||||||
|
fail=$((fail + 1)); printf ' FAIL %s (newer-version %s %s -> %s, want %s)\n' "$4" "$2" "$3" "$_got" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
expect 0.9.0 0.9.0 0.8.5 'higher minor wins, left'
|
||||||
|
expect 0.9.0 0.8.5 0.9.0 'higher minor wins, right'
|
||||||
|
expect 0.8.10 0.8.10 0.8.9 'numeric compare, not lexical — 10 beats 9'
|
||||||
|
expect 1.0.0 1.0.0 0.99.99 'major dominates'
|
||||||
|
expect 0.8.5 0.8.5 0.8.5 'equal returns the value'
|
||||||
|
expect 0.9.1 0.9.1 0.9 'a missing component counts as zero'
|
||||||
|
expect 0.9 0.9 0.9.0 'equal with a missing component returns the left, per contract'
|
||||||
|
|
||||||
|
printf '\nnewer_version: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
|
[ "$fail" -eq 0 ]
|
||||||
Reference in New Issue
Block a user