Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39ac61c411 | ||
|
|
2cb6b54a5e | ||
|
|
1448963823 | ||
|
|
58687c68a6 | ||
|
|
0c7fac3686 | ||
|
|
cba0fe4337 | ||
|
|
06592805b6 | ||
|
|
1d140ec525 | ||
|
|
2fcc15e9e4 | ||
|
|
b2af4e1129 | ||
|
|
3c8d2f5613 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"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.",
|
||||
"version": "0.13.3",
|
||||
"version": "0.16.0",
|
||||
"author": {
|
||||
"name": "Mathias",
|
||||
"url": "https://git.d-ma.be/mathias"
|
||||
|
||||
@@ -110,10 +110,25 @@ Antigravity and Mistral Vibe targets were removed — unused.
|
||||
task doctor # or bin/doctor
|
||||
```
|
||||
|
||||
Reports dangling links, links resolving into a foreign checkout, skill-count
|
||||
mismatches, stray entries, and leftover per-skill Claude symlinks. Non-zero
|
||||
exit on drift. It also runs at session start via the plugin, so a drifting
|
||||
host says so rather than waiting to be discovered.
|
||||
Reports dangling links, links into a source nobody configured, skills in the
|
||||
checkout with no link, stray entries, leftover per-skill Claude symlinks, and a
|
||||
Claude Code plugin sitting on an older generation than the host already has.
|
||||
Non-zero exit on drift. It also runs at session start via the plugin, so a
|
||||
drifting host says so rather than waiting to be discovered.
|
||||
|
||||
All of that compares copies already on the machine — deliberately, since it
|
||||
runs in a session hook where latency shows. That leaves one case invisible: a
|
||||
host where the checkout, the marketplace clone and the installed plugin all
|
||||
agree, and all three are a release behind origin.
|
||||
|
||||
```bash
|
||||
task doctor -- --online # or bin/doctor --online
|
||||
```
|
||||
|
||||
One `git ls-remote` against origin, opt-in. Belongs in a scheduled per-host job,
|
||||
not the session hook. An unreachable remote is reported as its own fault rather
|
||||
than as staleness — "I could not check" and "you are behind" are different
|
||||
answers and only one of them is about the host.
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
+3
-1
@@ -9,18 +9,20 @@ the SKILL.md, not the table.
|
||||
<!-- BEGIN skills-table -->
|
||||
| Skill | Description | Use when | Surfaces |
|
||||
|-------|-------------|----------|----------|
|
||||
| `adr` | Architecture Decision Records — house format, and how to record a decision you cannot make yet | Recording an architectural decision; revisiting one; or deciding between options where the right answer genuinely is not knowable yet | claude-code, library |
|
||||
| `atdd` | Acceptance Test-Driven Development workflow | Feature development with acceptance criteria | 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 | claude-code, library |
|
||||
| `clean-code` | Clean code principles with Go adaptation | Code review, refactoring, new code | claude-code, library |
|
||||
| `code-review` | Code smell detection + Go-specific checklist | Pre-merge review | claude-code, library |
|
||||
| `cognitive-load` | Cognitive complexity analysis (CLI score) | Identifying hotspots, before refactoring | claude-code, library |
|
||||
| `debug` | Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious | claude-code, library |
|
||||
| `debug` | Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious — or a bug report with no failure text, which this skill refuses rather than guesses at | claude-code, library |
|
||||
| `discovery-framing` | Problem-before-solution discovery/framing method | Framing/validating a problem before any solution; opportunity sizing | claude-code, library |
|
||||
| `dream` | Reflective consolidation pass over an agent's memory directory | Pruning/merging memory files, rebuilding a memory index | claude-code, library, chat |
|
||||
| `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 | claude-code, library |
|
||||
| `gitea-ci` | Gitea CI/CD pipeline with act_runner, buildah, k3s, GitHub mirror | Setting up or debugging CI on this infra | 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 | claude-code, library, chat |
|
||||
| `lineage` | Backward decision archaeology — was this tried before, and does the reason it failed still hold | Before proposing something that feels new; when an idea keeps resurfacing; when you inherit code whose reason is gone | 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 | claude-code, library, chat |
|
||||
| `planning` | Planning and task breakdown | Sprint planning, task decomposition | claude-code, library |
|
||||
| `playwright` | Playwright E2E testing — setup, HTMX patterns, SSE streams, CI wiring | Adding browser tests to any project with a web UI | claude-code, library |
|
||||
|
||||
+6
-1
@@ -114,7 +114,12 @@ tasks:
|
||||
# session_id is its only required field, and nine skills omitted it.
|
||||
sh '{{.TASKFILE_DIR}}/bin/check-session-log' '{{.TASKFILE_DIR}}' || fail=1
|
||||
|
||||
# 6. Plugin manifest version must match the latest tag, or installers
|
||||
# 6. Every skill says when it stops and hands back (#5). Skills whose
|
||||
# rule is not written yet are listed in bin/escalation-pending.txt,
|
||||
# which only shrinks.
|
||||
sh '{{.TASKFILE_DIR}}/bin/check-escalation' '{{.TASKFILE_DIR}}' || fail=1
|
||||
|
||||
# 7. Plugin manifest version must match the latest tag, or installers
|
||||
# never see the update (Claude Code gates updates on this field).
|
||||
if [ -f '{{.PLUGIN_MANIFEST}}' ]; then
|
||||
manifest_v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' '{{.PLUGIN_MANIFEST}}' | head -1)
|
||||
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
# check-escalation — every skill states when it stops and hands back.
|
||||
#
|
||||
# #5 decided this belongs in the body, not the frontmatter: no harness reads
|
||||
# frontmatter beyond name/description, so a `failure_criteria:` key would be a
|
||||
# guarantee nothing honours. The model reads the body.
|
||||
#
|
||||
# #37 then had to define which skills need one, and every obvious answer was
|
||||
# bad: a hardcoded list rots, sniffing the text for "does it write anything" is
|
||||
# unfalsifiable, and a frontmatter flag is the rejected option wearing a hat.
|
||||
#
|
||||
# So there is no acting set. EVERY skill answers the question. A read-only skill
|
||||
# answers it in one line — "read-only; findings are the output, nothing is
|
||||
# handed back" — which turns an absence into a claim someone can disagree with.
|
||||
#
|
||||
# bin/escalation-pending.txt is a ratchet for skills whose real rule has not
|
||||
# been written yet (a stop condition nobody thought about is not worth
|
||||
# enforcing). It is self-cleaning: a listed skill that HAS a section fails, so
|
||||
# the list cannot outlive its reason.
|
||||
#
|
||||
# Usage: check-escalation [checkout-root]
|
||||
|
||||
set -u
|
||||
|
||||
ROOT=${1:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
||||
PENDING="$ROOT/bin/escalation-pending.txt"
|
||||
fail=0
|
||||
|
||||
listed() {
|
||||
[ -f "$PENDING" ] || return 1
|
||||
sed 's/#.*//' "$PENDING" | grep -qx "[[:space:]]*$1[[:space:]]*"
|
||||
}
|
||||
|
||||
for dir in "$ROOT"/skills/*/; do
|
||||
[ -f "$dir/SKILL.md" ] || continue
|
||||
name=$(basename "$dir")
|
||||
|
||||
if grep -q '^## Escalation' "$dir/SKILL.md"; then
|
||||
if listed "$name"; then
|
||||
echo "ERROR: skills/$name has an Escalation section but is still in bin/escalation-pending.txt — remove it from the list" >&2
|
||||
fail=1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
listed "$name" && continue
|
||||
echo "ERROR: skills/$name has no '## Escalation' section — say when it stops and hands back, or say it never does" >&2
|
||||
fail=1
|
||||
done
|
||||
|
||||
# A pending entry for a skill that no longer exists is a stale exemption, and a
|
||||
# stale exemption is how a gate quietly stops covering something. Looped without
|
||||
# a pipe on purpose: a `while read` on the right of a pipe runs in a subshell,
|
||||
# so `fail=1` would be set in a process that exits before it is read.
|
||||
if [ -f "$PENDING" ]; then
|
||||
entries=$(sed 's/#.*//' "$PENDING" | tr -d '[:blank:]' | grep -v '^$' || true)
|
||||
for entry in $entries; do
|
||||
[ -f "$ROOT/skills/$entry/SKILL.md" ] && continue
|
||||
echo "ERROR: bin/escalation-pending.txt lists '$entry', which has no skills/$entry/SKILL.md" >&2
|
||||
fail=1
|
||||
done
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
+41
-2
@@ -10,6 +10,7 @@
|
||||
# doctor human report, non-zero exit on drift
|
||||
# doctor --quiet print only when something is wrong (SessionStart hook)
|
||||
# doctor --json machine-readable findings
|
||||
# doctor --online ALSO ask origin for the latest tag (one network call)
|
||||
#
|
||||
# POSIX sh on purpose: it runs on macOS and Linux, before any toolchain is
|
||||
# guaranteed present, and inside a session-start hook where latency shows.
|
||||
@@ -21,10 +22,12 @@ set -u
|
||||
|
||||
QUIET=0
|
||||
JSON=0
|
||||
ONLINE=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--quiet) QUIET=1 ;;
|
||||
--json) JSON=1 ;;
|
||||
--quiet) QUIET=1 ;;
|
||||
--json) JSON=1 ;;
|
||||
--online) ONLINE=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0 ;;
|
||||
@@ -288,6 +291,42 @@ if [ -n "$installed_v" ] && [ -n "$available_v" ] && [ "$installed_v" != "$avail
|
||||
add FAIL plugin-version "Claude Code has skills@mathias $installed_v installed but $available_v is available on this host — /plugin update skills@mathias (a host stale enough to matter is running the old doctor, so the first detection may need this run by hand)"
|
||||
fi
|
||||
|
||||
# --- optional: the release this host has never fetched ----------------------
|
||||
# Every check above compares copies already present, so a host whose checkout,
|
||||
# marketplace clone and installed plugin all agree reads as clean while sitting
|
||||
# a release behind origin (#38). Closing that needs a network call, which the
|
||||
# default run must not make — hence opt-in, for a scheduled per-host job rather
|
||||
# than the SessionStart hook.
|
||||
#
|
||||
# $DOCTOR_LATEST_TAG_CMD is the test seam; it prints the newest remote tag.
|
||||
if [ "$ONLINE" -eq 1 ]; then
|
||||
if [ -n "${DOCTOR_LATEST_TAG_CMD:-}" ]; then
|
||||
remote_tag=$(eval "$DOCTOR_LATEST_TAG_CMD" 2>/dev/null | tail -1 | tr -d '[:space:]')
|
||||
else
|
||||
# The URL is never printed: a remote rewritten by url.insteadOf can expand
|
||||
# into a live token.
|
||||
origin=$(git -C "$CHECKOUT" config --get remote.origin.url 2>/dev/null || true)
|
||||
if [ -n "$origin" ]; then
|
||||
remote_tag=$(git ls-remote --tags --refs "$origin" 2>/dev/null \
|
||||
| sed 's#.*refs/tags/##' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1 | tr -d '[:space:]')
|
||||
else
|
||||
remote_tag=''
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$remote_tag" ]; then
|
||||
# Not drift. A host that cannot reach origin has an unanswered question,
|
||||
# not a stale library, and saying otherwise would make the two look alike.
|
||||
add WARN offline "could not reach origin for the latest tag — this run proves nothing about staleness"
|
||||
else
|
||||
remote_v=${remote_tag#v}
|
||||
if [ -n "$available_v" ] && [ "$remote_v" != "$available_v" ] \
|
||||
&& [ "$(newer_version "$available_v" "$remote_v")" = "$remote_v" ]; then
|
||||
add FAIL behind "this host has $available_v; origin has released $remote_v — run 'task update' and '/plugin update skills@mathias'"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- output -----------------------------------------------------------------
|
||||
if [ "$JSON" -eq 1 ]; then
|
||||
printf '{"checkout":"%s","expected":%s,"plugin":{"installed":"%s","available":"%s"},"findings":[%s]}\n' \
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Skills whose escalation rule has not been written yet (#37).
|
||||
#
|
||||
# This list only shrinks. A skill here is exempt from the '## Escalation'
|
||||
# requirement until someone writes its real stop conditions; a skill that HAS a
|
||||
# section and is still listed fails the gate, so the list cannot outlive its
|
||||
# reason.
|
||||
#
|
||||
# The ones already done were the advisory and read-only skills, where the honest
|
||||
# answer is a line. What is left is the acting set — skills that write files,
|
||||
# dispatch agents, touch credentials or move releases — and each of those needs
|
||||
# a rule someone thought about, not a rote paragraph. Per #19, one at a time.
|
||||
|
||||
atdd
|
||||
dream
|
||||
experiment-spec
|
||||
feature-spec
|
||||
gitea-ci
|
||||
own-the-task
|
||||
planning
|
||||
playwright
|
||||
problem-analysis
|
||||
refactoring
|
||||
regulatory-risk-assessment
|
||||
secret-hygiene
|
||||
session-retrospective
|
||||
spec-driven-dev
|
||||
superpowers-brainstorming
|
||||
superpowers-dispatching-parallel-agents
|
||||
superpowers-executing-plans
|
||||
superpowers-finishing-a-development-branch
|
||||
superpowers-receiving-code-review
|
||||
superpowers-requesting-code-review
|
||||
superpowers-subagent-driven-development
|
||||
superpowers-using-git-worktrees
|
||||
superpowers-using-superpowers
|
||||
superpowers-verification-before-completion
|
||||
superpowers-writing-plans
|
||||
superpowers-writing-skills
|
||||
tdd
|
||||
trainer
|
||||
user-stories
|
||||
web-shot
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
name: adr
|
||||
surfaces: [claude-code, library]
|
||||
summary: "Architecture Decision Records — house format, and how to record a decision you cannot make yet"
|
||||
use_when: "Recording an architectural decision; revisiting one; or deciding between options where the right answer genuinely is not knowable yet"
|
||||
description: Write an Architecture Decision Record in this estate's format — YAML frontmatter, Status/Context/Decision/Consequences/Validation — including the case where the tradeoff is genuinely context-dependent and forcing a resolution would be worse than preserving the tension. Use when recording or superseding an architectural decision, or when a decision keeps getting re-litigated because nobody wrote down why.
|
||||
---
|
||||
|
||||
# ADR
|
||||
|
||||
## Overview
|
||||
|
||||
An ADR is the *record* of a decision — the artifact someone reads in eight months when
|
||||
they are about to undo it without knowing why it exists. It does not confer the decision;
|
||||
a person with authority does that, and this skill never is that person.
|
||||
|
||||
Two things make one useful, and neither is the prose quality: a **format consistent
|
||||
enough to grep across repos**, and an honest account of **what was not decided**.
|
||||
|
||||
## Before you write
|
||||
|
||||
Both of these change what goes in the ADR, so they are not cross-references, they are
|
||||
steps:
|
||||
|
||||
1. **`lineage`** — was this decided before and reversed? The reason it was dropped is
|
||||
usually the most valuable sentence in your Context, and if an earlier ADR exists,
|
||||
yours supersedes it rather than ignoring it.
|
||||
2. **`grill-me`** (Full Grill) — find the hole now. It is cheaper here than in the
|
||||
Consequences section of the ADR that supersedes yours.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Any choice where a reasonable alternative existed and was not taken
|
||||
- Superseding or amending an earlier ADR (never edit the old one — see Status)
|
||||
- A decision that keeps getting re-litigated, which means the reasoning was never recorded
|
||||
- A tradeoff that is genuinely context-dependent — see *Preserving a productive tension*
|
||||
|
||||
**Do not use for:** implementation choices with no alternative worth naming, or anything
|
||||
a code comment answers. An ADR per function is an ADR nobody reads.
|
||||
|
||||
## The format
|
||||
|
||||
**Where it goes:** `docs/adr/NNNN-kebab-slug.md`, zero-padded to four digits, numbered
|
||||
sequentially within the repo. Frontmatter you cannot locate is not greppable, and half
|
||||
the value of the format is that `docs/adr/*` is a known path in every repo here.
|
||||
|
||||
**The number is repo-local.** `cad` ADR-0002 and `infra` ADR-0002 are different
|
||||
documents. When referring across repos — including in `supersedes` — qualify it:
|
||||
`cad#0002`. Within a repo the bare number is fine.
|
||||
|
||||
Frontmatter first. It exists so the corpus is machine-readable — "which ADRs does 0004
|
||||
supersede", "what is still `proposed`" — and a bespoke header block, however well
|
||||
written, cannot answer those.
|
||||
|
||||
```markdown
|
||||
---
|
||||
adr: 0004
|
||||
title: <the decision, as a sentence — not "database choice">
|
||||
status: proposed | accepted | superseded | amended
|
||||
date: 2026-08-29
|
||||
deciders: <who accepted it, by name — or "pending: <name>" while proposed>
|
||||
supersedes: <adr number, or ->
|
||||
---
|
||||
|
||||
# ADR-0004 — <same title>
|
||||
|
||||
## Status
|
||||
|
||||
**The frontmatter is authoritative for status and date.** This section exists for the one
|
||||
thing the frontmatter cannot hold: *why* it moved — what superseded it and on what
|
||||
evidence, or what accepting it was contingent on. If it would only restate the YAML,
|
||||
delete it.
|
||||
|
||||
## Context
|
||||
|
||||
The forces. What is true that makes this a decision rather than a default — constraints,
|
||||
the failure that prompted it, what was tried. Name the unknowns explicitly; a decision
|
||||
made under uncertainty is not weaker for saying so, it is auditable.
|
||||
|
||||
## Decision
|
||||
|
||||
One sentence of what was decided, then why *this* option rather than the named
|
||||
alternatives. Address the strongest case against, not the weakest.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:** what this buys.
|
||||
|
||||
**Negative / watch:** what it costs, and the specific thing to watch that would tell you
|
||||
it was wrong. "Some added complexity" is not a consequence; "the event schema is now the
|
||||
coupling surface between cad and every executor — version it deliberately" is.
|
||||
|
||||
## Validation
|
||||
|
||||
How you will know. A link to the experiment or the metric that proves or kills it, and
|
||||
the condition that fires a revisit. An ADR with no validation section is a preference
|
||||
with a template around it.
|
||||
```
|
||||
|
||||
## Preserving a productive tension
|
||||
|
||||
Some tradeoffs are not unresolved because nobody thought hard enough. They are
|
||||
context-dependent, the context is not settled, and picking now means picking blind.
|
||||
Forcing resolution there produces a confident record that is wrong half the time.
|
||||
|
||||
Record the tension instead, in the Decision section:
|
||||
|
||||
```markdown
|
||||
## Decision
|
||||
|
||||
Provisional: **<option A>**, on this reasoning: <what makes it safe to pick now>.
|
||||
|
||||
<The strongest case for the option you did NOT take, and why it does not win today. The
|
||||
plain format demands this and a preserved tension does not get an exemption — the whole
|
||||
point is that the loser is still live.>
|
||||
|
||||
### The tension, preserved
|
||||
|
||||
- **Option A** — <what it assumes, what it costs>
|
||||
- **Option B** — <what it assumes, what it costs>
|
||||
|
||||
**What we do not know:** <the variable that decides it>
|
||||
|
||||
**Preservation strategy:** <what we build now so BOTH stay reachable. This is the
|
||||
technical content of the ADR, not a one-liner — enumerate it. It is usually the part both
|
||||
options need anyway, which is often most of the work: the schema, the interface seam, the
|
||||
idempotency key. Name each piece and say which option it keeps alive.>
|
||||
|
||||
**Resolution triggers:** <plural, and one per unknown. Two independent unknowns do not
|
||||
collapse into one condition, and a trigger may exist that neither unknown implies.>
|
||||
```
|
||||
|
||||
Two rules keep this from becoming an excuse:
|
||||
|
||||
1. **A preserved tension still ships a decision.** "We will decide later" is not one.
|
||||
Prefer the option whose correctness does not depend on the unknown, say so, and mark
|
||||
it provisional.
|
||||
|
||||
**When no such option exists** — and often none does, because a tension is productive
|
||||
precisely when both options are hostage to the same unknown — do not manufacture a
|
||||
reason that one of them is. That reads as reasoned and will not be re-examined, which
|
||||
makes it worse than an honest arbitrary pick. Fall back, in order: the option that is
|
||||
cheaper to reverse; the one with the lower switching cost given your preservation
|
||||
strategy; the one already built. Then **say that is why you picked it.** "Chosen
|
||||
because it is the cheaper mistake" is a real justification and an auditable one.
|
||||
2. **The resolution trigger must be observable by someone who is not you.** "When it
|
||||
becomes a problem" is not a trigger. "When executor count passes five and they are all
|
||||
long-lived services in one cluster" is. A trigger nobody can evaluate is a decision
|
||||
that quietly becomes permanent.
|
||||
|
||||
`cad`'s ADR-0001 does this informally under *"Known refinements (recorded, deliberately
|
||||
not built yet)"* — sovereignty deferred to a 3-class model *"until a client actually opts
|
||||
into an EU provider"*. That clause is the pattern: option, cost, and the event that
|
||||
forces the choice.
|
||||
|
||||
## Status discipline
|
||||
|
||||
- **Never edit an accepted ADR to change its decision.** Write a new one and set
|
||||
`supersedes`. The record of what was believed, and when, is the whole value.
|
||||
- **Amend** for corrections that do not change the decision (a broken link, a clarified
|
||||
consequence). Note the amendment inline with its date.
|
||||
- **`proposed` is not a parking space.** An ADR that sits proposed for a month *with no
|
||||
named decider* is a decision nobody is making; either accept it provisionally or close
|
||||
it as declined. With a named decider it is a decision waiting on a person, which is a
|
||||
different thing and is chased rather than closed.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| Anti-pattern | Why it fails |
|
||||
|---|---|
|
||||
| Consequences that are all positive | Then it was not a decision, it was an announcement |
|
||||
| "We chose X because it is better" | Names no alternative and no force; unfalsifiable |
|
||||
| Written after the code shipped | Reverse-engineers a justification. Record the reasoning while it is still contested |
|
||||
| One ADR amended repeatedly | Destroys the history the format exists to keep |
|
||||
| No Validation section | Nothing distinguishes a good decision from a lucky one |
|
||||
| A tension "resolved" by picking arbitrarily | The next person re-litigates it, having lost the reasoning |
|
||||
|
||||
## Escalation
|
||||
|
||||
Writes a file, so it acts — but it decides nothing.
|
||||
|
||||
Stop and hand back when: the deciders are not you — which, for an agent, is always. An
|
||||
ADR records a decision someone with authority made; writing one to *create* that
|
||||
authority is how a preference acquires a document number.
|
||||
|
||||
So the deliverable is a draft marked `proposed` with **the person who must accept it named
|
||||
in `deciders` as `pending: <name>`**, and that hand-back stated in the response, not left
|
||||
implied. Naming the decider is the escalation, not a formality — an unnamed one is how a
|
||||
proposal sits for a month.
|
||||
|
||||
That is the intended state for an agent-authored ADR, and it is not the parking-space
|
||||
failure below: a `proposed` ADR with a named decider is waiting on someone; one without is
|
||||
waiting on nobody.
|
||||
|
||||
Also stop when the Context cannot be written without inventing a constraint. A missing
|
||||
force means the decision has not been thought through yet, and an ADR built on an
|
||||
invented one is worse than none: it will be cited.
|
||||
|
||||
## Cross-References
|
||||
|
||||
- `lineage` and `grill-me` — see *Before you write*; they change the content, so they are
|
||||
steps rather than references.
|
||||
- `spec-driven-dev` / `feature-spec` — an ADR is the *why*; a spec is the *what*. If you
|
||||
are describing behaviour, you are writing a spec.
|
||||
@@ -258,3 +258,13 @@ decide
|
||||
- `mathias/cad-atlas#11` renders the same `cad stuck` output as a pipeline overlay — read that
|
||||
instead of this skill's text output when a visual "where is everything stuck" view is wanted
|
||||
over working one row in depth.
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only by construction, and the Iron Laws above are the escalation rule: never close
|
||||
an issue, never dispatch, never write a label. Everything that is the operator's call
|
||||
leaves as a `DECISION NEEDED` block (question / options / recommendation / blocks).
|
||||
|
||||
Stop and hand back when: the stuck queue is empty; a diagnosis needs a write you are not
|
||||
allowed to make; or two runs in a row produce the same finding, which means the finding
|
||||
is not being acted on and repeating it is noise.
|
||||
|
||||
@@ -284,3 +284,11 @@ During refactoring:
|
||||
- `references/python-adaptation.md` — Python conventions: error chaining, dataclasses, the truthiness trap
|
||||
- Load `solid` skill for SOLID principles with Go examples
|
||||
- Load `refactoring` skill for systematic refactoring techniques
|
||||
|
||||
## Escalation
|
||||
|
||||
Advisory during the REFACTOR phase. Suggests; does not restructure behaviour.
|
||||
|
||||
Stop and hand back when: the cleanup you are proposing changes behaviour. That is not
|
||||
refactoring, it is a change, and it needs its own failing test first — hand back to
|
||||
`tdd`.
|
||||
|
||||
@@ -285,3 +285,12 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||
- Load `refactoring` skill when recommending fixes
|
||||
- Load `test-design` skill for reviewing test quality
|
||||
- Load `solid` skill for SOLID principle violations
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only analysis. Findings are the output; nothing is handed back mid-review.
|
||||
|
||||
Stop and hand back when: the diff is large enough that a review would be a skim — say so
|
||||
and ask for it in parts, rather than producing confident coverage of a fraction of it; or
|
||||
the problem is structural rather than pointwise, in which case name it and hand off to
|
||||
`solid` or `refactoring` instead of filing twenty line-level comments about one cause.
|
||||
|
||||
@@ -347,3 +347,12 @@ type UserWriter interface {
|
||||
- Load `refactoring` skill for systematic techniques to reduce identified hotspots
|
||||
- Load `solid` skill for structural fixes (coupling, cohesion)
|
||||
- Load `clean-code` skill for naming improvements
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only measurement. The score is the output.
|
||||
|
||||
Stop and hand back when: the measurement disagrees with the reader's lived experience of
|
||||
the code. A metric that says "fine" about code everyone finds hard is evidence about the
|
||||
metric, not about the code, and continuing to defend the number is how a measure becomes
|
||||
a target.
|
||||
|
||||
+70
-15
@@ -2,8 +2,8 @@
|
||||
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.
|
||||
use_when: "Any test failure or bug whose cause is not immediately obvious — or a bug report with no failure text, which this skill refuses rather than guesses at"
|
||||
description: Systematic hypothesis-first debugging. Requires the failure itself — error text, stack, run id or log — and names what it needs and stops if the report has none, rather than hypothesising into a vacuum. Given evidence, generates 3-5 ranked, falsifiable hypotheses with verification steps before suggesting any fix. Use when encountering a bug, test failure, or unexpected behavior.
|
||||
---
|
||||
|
||||
# Debug
|
||||
@@ -12,7 +12,7 @@ description: Systematic hypothesis-first debugging. Generate 3-5 ranked hypothes
|
||||
|
||||
Debugging is hypothesis generation, not fix generation. The first instinct — "try this and see if it works" — wastes time and teaches nothing. A disciplined debug session produces a small set of ranked, falsifiable hypotheses, each with a concrete verification command. The fix comes after one hypothesis is confirmed, not before.
|
||||
|
||||
**Core principle:** A hypothesis you cannot verify in one command is not a hypothesis — it is a guess.
|
||||
**Core principle:** A hypothesis you cannot verify with a single bounded, decisive observation is not a hypothesis — it is a guess. In a repo that observation is usually one command. Above one it may be an instrumented run, or partitioning the last twenty CI runs by trigger type; what makes it a hypothesis is that the observation *discriminates*, not that it fits on one line.
|
||||
|
||||
## When to Use
|
||||
|
||||
@@ -25,6 +25,27 @@ Debugging is hypothesis generation, not fix generation. The first instinct — "
|
||||
- Compile errors with clear messages (just fix the typo)
|
||||
- Already-diagnosed bugs where you know the cause (go straight to TDD with a regression test)
|
||||
|
||||
### Is it actually diagnosed?
|
||||
|
||||
That last exclusion is the one people get wrong, because a confident wrong diagnosis feels
|
||||
exactly like a right one. It is diagnosed if you can state the mechanism *and* name the
|
||||
observation that would have proved it wrong. If the evidence is "we changed X and it got
|
||||
better", that is correlation with a deployment, and this skill applies.
|
||||
|
||||
The strongest tell is a run of failed fixes — see below. Three plausible fixes that did
|
||||
not eliminate the symptom is not bad luck, it is the diagnosis being wrong.
|
||||
|
||||
### Intake: what you need before hypothesising
|
||||
|
||||
Before generating anything, confirm you have the failure itself — the error text, the
|
||||
stack, the failing run's id, the log. If the report is "it's flaky again" with no
|
||||
artifact, **name the specific thing you need and stop**. Iron Law 1 forbids "try X and
|
||||
see"; hypothesising into a vacuum is the same failure with better grammar, and it is
|
||||
worse because the output looks like analysis.
|
||||
|
||||
This is the rule `cad-babysit` already enforces from the other side: read the issue's own
|
||||
comments and the job log *first*.
|
||||
|
||||
## Iron Laws
|
||||
|
||||
1. **Never suggest "try X and see what happens."** Every hypothesis must have a specific expected outcome if correct.
|
||||
@@ -36,8 +57,9 @@ Debugging is hypothesis generation, not fix generation. The first instinct — "
|
||||
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.
|
||||
whether config and environment actually propagated. One run *that reproduces the failure* narrows the search to a
|
||||
single component. On an intermittent fault a passing run narrows nothing, so leave the
|
||||
instrumentation in across several runs rather than concluding from one.
|
||||
|
||||
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.
|
||||
@@ -53,6 +75,10 @@ than fixes that partially worked.) Each one has been narrowing a window rather t
|
||||
closing it, and a fourth guess buys another tenth of the rate plus a layer of
|
||||
interacting guards nobody can reason about.
|
||||
|
||||
Intake still gates this section. A report of three failed fixes with no artifacts fires
|
||||
both rules at once, and intake wins: you cannot aim hypotheses at a premise you have no
|
||||
evidence about either. Ask for the log, then aim.
|
||||
|
||||
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
|
||||
@@ -76,18 +102,28 @@ For each hypothesis, capture three things:
|
||||
- **Verification:** the single command or file inspection that confirms or denies it
|
||||
- **Expected outcome if correct:** the specific output you would see
|
||||
|
||||
Order by likelihood. The most likely cause is hypothesis 1.
|
||||
Order by likelihood, most likely first. **The ordering is the ranking — do not also
|
||||
attach `high`/`medium`/`low` labels.** With five slots and three labels the labels stop
|
||||
discriminating (two hypotheses are genuinely both "high" and the label cannot say which
|
||||
comes first), and a reader then has two orderings that can disagree. Position already
|
||||
carries it.
|
||||
|
||||
Where you can say *why* one outranks another — a prior brain entry, a matching symptom,
|
||||
a recent change in that area — say that instead of a label. That is information; a label
|
||||
is a restatement of the order it sits next to.
|
||||
|
||||
### Step 3: Output
|
||||
|
||||
Use this format:
|
||||
|
||||
```
|
||||
HYPOTHESIS 1 (likelihood: high): <mechanism in one sentence>
|
||||
VERIFY: <exact command or file:line to inspect>
|
||||
HYPOTHESIS 1: <mechanism in one sentence>
|
||||
WHY FIRST: <what makes it likeliest — a matching symptom, a recent change, a brain entry>
|
||||
VERIFY: <exact command, or the bounded observation that discriminates>
|
||||
EXPECTED IF CORRECT: <specific output, value, or condition>
|
||||
|
||||
HYPOTHESIS 2 (likelihood: medium): <mechanism>
|
||||
HYPOTHESIS 2: <mechanism>
|
||||
WHY FIRST: <or why it ranks here rather than first>
|
||||
VERIFY: <exact command>
|
||||
EXPECTED IF CORRECT: <specific output>
|
||||
|
||||
@@ -109,15 +145,15 @@ End with the recommendation, not a fix.
|
||||
**Output:**
|
||||
|
||||
```
|
||||
HYPOTHESIS 1 (likelihood: high): parser indexes into pages[0] before checking len(pages) > 0; empty PDFs produce a zero-page document
|
||||
HYPOTHESIS 1: parser indexes into pages[0] before checking len(pages) > 0; empty PDFs produce a zero-page document
|
||||
VERIFY: rg -n 'pages\[0\]' internal/parser/
|
||||
EXPECTED IF CORRECT: at least one site reads pages[0] without a preceding length check
|
||||
|
||||
HYPOTHESIS 2 (likelihood: medium): the test fixture is a zero-byte file rather than a valid empty PDF; pdf library returns nil pages slice instead of empty
|
||||
HYPOTHESIS 2: the test fixture is a zero-byte file rather than a valid empty PDF; pdf library returns nil pages slice instead of empty
|
||||
VERIFY: ls -la testdata/empty.pdf && file testdata/empty.pdf
|
||||
EXPECTED IF CORRECT: file size 0 bytes or "data" rather than "PDF document"
|
||||
|
||||
HYPOTHESIS 3 (likelihood: low): a recently changed dependency reordered the page-extraction API; pages[0] now refers to metadata, not content
|
||||
HYPOTHESIS 3: a recently changed dependency reordered the page-extraction API; pages[0] now refers to metadata, not content
|
||||
VERIFY: git log --oneline -10 -- go.sum | grep -i pdf
|
||||
EXPECTED IF CORRECT: a pdf-library bump in the last few commits
|
||||
|
||||
@@ -131,6 +167,7 @@ RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.
|
||||
| "Maybe try restarting the service" | Not a mechanism. Not falsifiable. Teaches nothing if it works. |
|
||||
| "Could be a race condition" | Mechanism without specifics. Which two operations race? On what state? |
|
||||
| "Let me add some print statements and see" | Skips hypothesis generation. Generates noise, not understanding. |
|
||||
| Answering "what's the fix?" from a report with no artifact | Hypothesising into a vacuum. The output looks like analysis, which is what makes it worse than saying "I need the log". |
|
||||
| Single hypothesis presented as fact | If you are sure, write the regression test, do not run a debug skill. |
|
||||
| Mixing hypotheses with fix suggestions | The skill is diagnose-only. The fix is a TDD task on its hypothesis. |
|
||||
|
||||
@@ -139,7 +176,8 @@ RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.
|
||||
The brain holds prior debug sessions across the project. Use it to skip rediscovering known failure modes.
|
||||
|
||||
**At debug start:**
|
||||
- Run `brain_query` with the error message snippet + the package name. Past sessions may have logged identical or similar failures with their resolved hypotheses.
|
||||
- Run `brain_query` with the error message snippet + the package name. On an intake stop you have neither — query the symptom and the component instead ("signed artifact missing CI"), which is worth doing precisely because it may name the artifact to ask for. Past sessions may have logged identical or similar failures with their resolved hypotheses.
|
||||
- **A hit is evidence, so cite it inline in the hypothesis it informs** — `(brain: <entry-name>)` — rather than mentioning it once at the top. If a prior entry is why a hypothesis ranks first, the reader needs that at the hypothesis, not in a preamble. If a prior entry contradicts your ranking, say so and rank it anyway if the evidence here is better; the brain records what was true then.
|
||||
|
||||
**After a hypothesis is confirmed:**
|
||||
- Run `brain_write` with the failure signature → confirmed mechanism. Future debug sessions on the same area get the answer immediately.
|
||||
@@ -156,7 +194,12 @@ treats `pass` as success, `fail` as failure, `skip` as neither.
|
||||
**At end of each phase:**
|
||||
- `session_log` with `{session_id: "<this session's id>", skill: "debug", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}`
|
||||
|
||||
**Phases for this skill:** read-failure, generate-hypotheses, output
|
||||
**Phases for this skill:** intake, read-failure, generate-hypotheses, output
|
||||
|
||||
`intake` exists so that refusing correctly is recordable. A run that stops for want of a
|
||||
run id has done the right thing and logs `intake` / `pass`; without that phase the
|
||||
refusal logs as a failed `read-failure`, and a metric that penalises the skill's best
|
||||
behaviour teaches the next model to hypothesise into the vacuum instead.
|
||||
|
||||
**Status semantics:**
|
||||
- `pass` — the phase's intended outcome was reached.
|
||||
@@ -179,6 +222,18 @@ Techniques kept in `debug/references/`, inherited from the deleted
|
||||
- `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.
|
||||
including as one of the fixes already tried. A bumped timeout is a symptom. Not during an
|
||||
intake stop: if you are not analysing yet, a symptom-analysis reference is on the wrong
|
||||
side of the gate.
|
||||
- `find-polluter.sh` — run when a test passes alone and fails in the suite, or when
|
||||
something in the tree appears that no test admits to creating.
|
||||
|
||||
## Escalation
|
||||
|
||||
Diagnose only — Iron Law 3 is already an escalation rule: the fix is a separate TDD cycle,
|
||||
not this skill's output.
|
||||
|
||||
Stop and hand back when: the report contains no failure text and no locator, in which case
|
||||
name the specific artifact you need (run id, job log, stack trace) and stop rather than
|
||||
hypothesising into a vacuum; or the evidence needed to discriminate between your top two
|
||||
hypotheses requires access you do not have.
|
||||
|
||||
@@ -152,3 +152,12 @@ hand-wavy. Crisp solution + fuzzy problem = reverse-engineered justification. St
|
||||
|
||||
> Named instances of both war stories are client-tagged and held out of this public skill
|
||||
> pending client-store decision. The transferable lesson lives here; the wrapper stays out.
|
||||
|
||||
## Escalation
|
||||
|
||||
Advisory. Produces a problem brief; commits to nothing.
|
||||
|
||||
Stop and hand back when: the evidence needed to size a problem requires talking to
|
||||
customers you cannot reach. Sizing a problem from assumptions and presenting it as
|
||||
discovery is worse than saying the number is unknown — it launders a guess into a brief
|
||||
that later decisions will cite.
|
||||
|
||||
@@ -148,3 +148,12 @@ This skill maps to the **convergence moments** in the Double Diamond workflow:
|
||||
- **Mid-Diamond 2 (Develop):** Use Quick Poke when an agent's experiment result is ambiguous and you're deciding whether to continue or pivot.
|
||||
|
||||
The skill does NOT replace human judgment at convergence points — it sharpens the plan so that judgment is better informed.
|
||||
|
||||
## Escalation
|
||||
|
||||
Advisory. Produces sharper questions and a better plan; decides nothing.
|
||||
|
||||
Stop and hand back when: the plan's weak point is a decision that is the operator's to
|
||||
make rather than a gap in reasoning — surface it as a question with options and a
|
||||
recommendation, and do not grill past it. Also stop when a round produces no new
|
||||
objection: interrogation past that point manufactures doubt rather than finding it.
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: lineage
|
||||
surfaces: [claude-code, library]
|
||||
summary: "Backward decision archaeology — was this tried before, and does the reason it failed still hold"
|
||||
use_when: "Before proposing something that feels new; when an idea keeps resurfacing; when you inherit code whose reason is gone"
|
||||
description: Backward decision archaeology. Find out whether an idea was already tried here, why it was dropped, and whether that reason still applies — using git history, decision records and the issue tracker rather than asking whoever is still around. Use before proposing a change that feels novel, when inheriting unexplained code, or when the same idea keeps being re-litigated.
|
||||
---
|
||||
|
||||
# Lineage
|
||||
|
||||
## Overview
|
||||
|
||||
Most "new" ideas in an established codebase are revivals. The valuable question is never
|
||||
"is this a good idea" in the abstract — it is **"was this tried, what killed it, and is
|
||||
the killer still alive?"**
|
||||
|
||||
A past failure is not evidence against trying again. It is context for trying again
|
||||
differently, and the difference between those two readings is usually one commit message.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Before proposing something that feels new in a codebase older than you
|
||||
- When an idea keeps resurfacing and being re-argued from scratch
|
||||
- When you inherit code and cannot tell whether a constraint is load-bearing or vestigial
|
||||
- Before writing an ADR — the reason a thing was dropped belongs in your Context section
|
||||
|
||||
**Do not use for:** code you wrote this week, or a question the current code answers.
|
||||
Archaeology on a live construction site is just reading.
|
||||
|
||||
## The tool
|
||||
|
||||
```sh
|
||||
skills/lineage/lineage.sh <term> [repo-path]
|
||||
```
|
||||
|
||||
Runs the searches people skip because they are fiddly to type. In particular
|
||||
`git log -S<term>`, which finds commits where the term's **occurrence count changed** —
|
||||
so it surfaces the *removal* as well as the introduction. A plain `--grep` never finds a
|
||||
deletion, and the deletion is the whole point.
|
||||
|
||||
It also lists files that once carried the term and no longer exist, and names the two
|
||||
sources it cannot search for you.
|
||||
|
||||
**A no-match is not a finding.** The first version of this tool said a null meant "this is
|
||||
new here"; a pressure test caught it asserting exactly that about the most-discussed
|
||||
removed feature in this repo — the search term merely had a space in it. One string
|
||||
against one repo proves nothing. Try at least three variants (the identifier name, a
|
||||
one-word form, the name it would have had) and the brain and tracker searches before
|
||||
telling anyone an idea is novel. This is the same rule as the anti-patterns table below,
|
||||
and it is there because the tool broke it.
|
||||
|
||||
Note also that `--grep` takes a regex and `-S` takes a literal string, so a term
|
||||
containing `.`, `[`, `*` or `+` matches differently in the two halves of the output.
|
||||
|
||||
## The sequence
|
||||
|
||||
**1. Search on the identifier, not the concept.** If the thing had a name in the code —
|
||||
a function, a flag, a config key — `git log --all -S<name>` typically returns exactly two
|
||||
commits, its birth and its death, on one screen. That is the fastest path to the answer in
|
||||
this whole sequence.
|
||||
|
||||
Path-scoped log (`git log --all -- <file>`) is the weaker move and it is the one people
|
||||
reach for: a function deleted from a file that still exists is the *dominant* removal
|
||||
shape, and the path search buries it under every other commit that touched the file.
|
||||
|
||||
Concept words are the fallback, and they are lossy — a term with a space in it will miss
|
||||
the record entirely. Try several spellings before you conclude anything.
|
||||
|
||||
**2. Read the removal commit's full body, not its subject.**
|
||||
|
||||
```sh
|
||||
git log -1 --format=%B <sha>
|
||||
git show --stat <sha>
|
||||
```
|
||||
|
||||
This is where the answer almost always is. A subject line says *what*; the body says
|
||||
*why*, and the why is what you came for. Read the diff too — what was removed alongside
|
||||
it tells you how load-bearing it was.
|
||||
|
||||
**3. Search outside git — that is where this estate keeps its reasoning.** Two sources,
|
||||
both routinely decisive, neither reachable from a commit log:
|
||||
|
||||
- **The brain** (`brain_query`). Postmortems and generalized rules live there. In the run
|
||||
that shaped this skill, the single best artifact — the rule abstracted from the incident,
|
||||
with detection one-liners — came from the brain and from nowhere else.
|
||||
- **The issue tracker, including comments.** The commit records *that* something was
|
||||
removed; the argument for it is usually in a comment. Search closed issues especially —
|
||||
a closed issue is where a decision usually is.
|
||||
|
||||
A git-only search finds the what and misses the why.
|
||||
|
||||
**4. Test whether the failure conditions still hold — empirically, not from the record.**
|
||||
This is the step that makes the whole exercise worth doing, and it is the one that gets
|
||||
hand-waved. Write the conditions down as separate claims, then **run a command per claim**:
|
||||
|
||||
| Claim from the record | How you check it today |
|
||||
|---|---|
|
||||
| "absolute links break across two path roots" | `readlink` one, `test -e` it on this host |
|
||||
| "it is gitignored, so nobody sees the rot" | `grep` the ignore file |
|
||||
| "the detector does not cover it" | `grep` the detector for the path it scans |
|
||||
|
||||
Twenty seconds each, and it converts *"the record claims X"* into *"X is true right now"* —
|
||||
which is the only form of the answer worth giving. A constraint that has since been
|
||||
removed turns a dead idea live; one that is still true kills a revival on the spot.
|
||||
|
||||
**5. Separate the mechanism from the goal.** A revival proposal usually bundles both, and
|
||||
they can have opposite verdicts: the goal is still worth wanting, the mechanism is the one
|
||||
that failed. Splitting them is often the whole finding — you end up agreeing with the
|
||||
proposer and rejecting their implementation, which is a far more useful answer than yes
|
||||
or no.
|
||||
|
||||
## What "it was tried and failed" can mean
|
||||
|
||||
| Reading | Tell | What to do |
|
||||
|---|---|---|
|
||||
| **Structurally impossible** | The removal commit says *why* it cannot work, not that it broke | Believe it. Look for a different mechanism for the same goal |
|
||||
| **Failed under conditions that changed** | The blocker names a version, a host, a tool that has since moved | Re-open it, citing what changed |
|
||||
| **Failed on execution** | It broke, was reverted, nobody diagnosed it | Genuinely undecided. The revival is legitimate; budget for the diagnosis |
|
||||
| **Never actually tried** | Discussed, an issue exists, no commit | There is no evidence either way. Say so rather than inheriting someone's pessimism |
|
||||
| **Still there, in a different shape** | The idea returned under another name | The most missable one — check before you build it twice |
|
||||
|
||||
## Output
|
||||
|
||||
Answer the question that was asked, then show your work:
|
||||
|
||||
- **Verdict** — tried / not tried / tried and later restored differently
|
||||
- **The decisive artifact** — commit sha, issue number, or ADR, so the reader can check
|
||||
- **What killed it**, quoted from the source rather than paraphrased
|
||||
- **Whether that still applies**, per condition, not as a single yes or no
|
||||
|
||||
If the decisive evidence is one commit message, quote it. A conclusion that makes the
|
||||
reader re-run your search has not saved them anything.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| Anti-pattern | Why it fails |
|
||||
|---|---|
|
||||
| `--grep` only | Finds discussion, misses deletions. The deletion is the evidence |
|
||||
| Reading the commit subject and stopping | Subjects say what; you need why |
|
||||
| Treating a past failure as a verdict | Conditions change. That is the entire question |
|
||||
| Git-only search | The argument is usually in the issue tracker |
|
||||
| "It was tried and it failed" with no sha | Unverifiable, and it will be re-litigated next quarter |
|
||||
| Assuming absence means it was never considered | It may have been rejected in a conversation. Say what you searched |
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only. Reports what the record says; changes nothing and revives nothing.
|
||||
|
||||
Stop and hand back when: the record is genuinely silent — no commit, no issue, no ADR.
|
||||
State what you searched and that nothing exists, rather than inferring intent from
|
||||
absence. Someone deciding not to build a thing and nobody ever considering it look
|
||||
identical in git, and only one of them is an argument.
|
||||
|
||||
Also stop when the answer turns on why a *person* did something and the record does not
|
||||
say. Ask them. Reconstructing a colleague's reasoning from a diff is how a guess acquires
|
||||
a citation.
|
||||
|
||||
## Cross-References
|
||||
|
||||
- `adr` — what you find here belongs in the Context section of the ADR you are about to
|
||||
write. If the thing was decided before, your ADR supersedes that one; say so.
|
||||
- `debug` — the same discipline aimed at a failure rather than a decision: evidence
|
||||
before hypothesis, and the removal commit is evidence.
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/bin/sh
|
||||
# lineage.sh — decision archaeology for one term.
|
||||
#
|
||||
# Answers "why does X exist, was it tried before, and is this 'new' idea a
|
||||
# revival?"
|
||||
#
|
||||
# Its real value is that it makes you run `git log -S` at all — that is a nudge,
|
||||
# not a capability. -S finds commits where the term's occurrence COUNT changed,
|
||||
# so it surfaces a removal; --grep only finds people talking about one. On an
|
||||
# identifier name (`wire_claude_repo`) -S typically returns exactly two commits,
|
||||
# birth and death, which is the fastest path to the answer in this whole file.
|
||||
#
|
||||
# Note the two searches do not match the same way: --grep takes a regex, -S takes
|
||||
# a literal string. A term containing . [ * + behaves differently in each.
|
||||
#
|
||||
# Usage: lineage.sh <term> [repo-path]
|
||||
#
|
||||
# Read-only: nothing here writes, and it never touches the index or worktree.
|
||||
|
||||
set -u
|
||||
|
||||
[ $# -ge 1 ] || { echo "usage: lineage.sh <term> [repo-path]" >&2; exit 2; }
|
||||
TERM_=$1
|
||||
REPO=${2:-.}
|
||||
|
||||
git -C "$REPO" rev-parse --git-dir >/dev/null 2>&1 || {
|
||||
echo "lineage: $REPO is not a git repository" >&2; exit 2; }
|
||||
|
||||
# Say which repo, always. The default is the cwd, and in a worktree-heavy setup
|
||||
# that is routinely not the repo the caller means — a confident null against the
|
||||
# wrong tree is the worst thing this tool can produce.
|
||||
TOPLEVEL=$(git -C "$REPO" rev-parse --show-toplevel 2>/dev/null || printf '%s' "$REPO")
|
||||
printf 'searching: %s term: "%s"\n' "$TOPLEVEL" "$TERM_"
|
||||
|
||||
FMT='%h %ad %an %s'
|
||||
DATE='--date=short'
|
||||
found=0
|
||||
|
||||
section() { printf '\n== %s ==\n' "$1"; }
|
||||
|
||||
# 1. Commits whose MESSAGE mentions it — the stated reasoning.
|
||||
msgs=$(git -C "$REPO" log --all -i --grep="$TERM_" --format="$FMT" $DATE 2>/dev/null || true)
|
||||
if [ -n "$msgs" ]; then
|
||||
found=1
|
||||
section "Commits that talk about it (message)"
|
||||
printf '%s\n' "$msgs"
|
||||
fi
|
||||
|
||||
# 2. Commits where the term was ADDED or REMOVED — the actual history.
|
||||
# -S counts occurrences, so a deletion shows up here and nowhere else.
|
||||
picks=$(git -C "$REPO" log --all -S"$TERM_" --format="$FMT" $DATE 2>/dev/null || true)
|
||||
if [ -n "$picks" ]; then
|
||||
found=1
|
||||
section "Commits where it appeared or disappeared (-S)"
|
||||
printf '%s\n' "$picks"
|
||||
printf '\n Oldest is the introduction. Anything after it that removed the term is\n'
|
||||
printf ' the interesting one: read that commit before proposing this again.\n'
|
||||
fi
|
||||
|
||||
# 3. Files that once contained it and no longer exist.
|
||||
deleted=$(git -C "$REPO" log --all --diff-filter=D --name-only --format='%h' -S"$TERM_" 2>/dev/null \
|
||||
| grep -v '^[0-9a-f]\{7,\}$' | grep -v '^$' | sort -u || true)
|
||||
if [ -n "$deleted" ]; then
|
||||
found=1
|
||||
section "Files deleted that carried it"
|
||||
printf '%s\n' "$deleted"
|
||||
printf '\n Recover one with: git -C %s show <sha>^:<path>\n' "$REPO"
|
||||
fi
|
||||
|
||||
# 4. Decision records naming it.
|
||||
docs=$(git -C "$REPO" grep -l -i "$TERM_" -- 'docs/adr/*' 'docs/decisions/*' 'docs/*.md' 'ADR*' 2>/dev/null || true)
|
||||
if [ -n "$docs" ]; then
|
||||
found=1
|
||||
section "Decision records naming it"
|
||||
printf '%s\n' "$docs"
|
||||
fi
|
||||
|
||||
# This estate keeps most of its reasoning OUTSIDE the repo, so the two searches
|
||||
# this tool cannot run are usually the decisive ones. Say so every time rather
|
||||
# than letting their absence read as a null result.
|
||||
section "Not searched here — do these by hand"
|
||||
printf 'brain: brain_query "%s" — decisions and postmortems live there, not in docs/\n' "$TERM_"
|
||||
printf 'gitea: closed issues and their COMMENTS. The argument for a removal is\n'
|
||||
printf ' usually in a comment; the commit only records that it happened.\n'
|
||||
|
||||
if [ "$found" -eq 0 ]; then
|
||||
printf '\nno match for the exact string "%s" in %s.\n' "$TERM_" "$TOPLEVEL"
|
||||
printf 'This is NOT evidence the idea is new. It is one string against one repo.\n'
|
||||
printf 'Before concluding anything, try: the identifier or function name; a\n'
|
||||
printf 'one-word variant (spaces and punctuation break the match); the name it\n'
|
||||
printf 'would have had; and the brain and issue searches above.\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '\n-- what this cannot tell you --------------------------------------------\n'
|
||||
printf 'Why it was removed, if it was. The commit subject is a summary; read the\n'
|
||||
printf 'body, the PR, and the issue it closed. A past failure under conditions that\n'
|
||||
printf 'no longer hold is not evidence against trying again — it is context for\n'
|
||||
printf 'trying again differently.\n'
|
||||
@@ -261,3 +261,12 @@ Domain types know nothing about HTTP, SQL, or external APIs.
|
||||
- `references/python-adaptation.md` — the Python rewrite: Protocol over ABC, and what does not carry over from Go
|
||||
- Load `clean-code` skill for naming and structure
|
||||
- Load `code-review` skill for detecting violations during review
|
||||
|
||||
## Escalation
|
||||
|
||||
Advisory. Names principle violations and suggests structure; writes no code.
|
||||
|
||||
Stop and hand back when: applying a principle would require a change larger than the one
|
||||
under discussion. "This wants dependency inversion" on a two-line fix is a design
|
||||
conversation, not a review comment, and forcing it through as one is how a small change
|
||||
acquires an architecture.
|
||||
|
||||
@@ -151,3 +151,12 @@ stays in the client boundary**:
|
||||
- Council/forum names, internal stage labels, internal tooling, named contacts, strategy
|
||||
specifics, deal terms → client dir/org only, local models only, never the public brain.
|
||||
- This skill carries the **pattern**; the client carries the **wrapper**. Never merge them.
|
||||
|
||||
## Escalation
|
||||
|
||||
Advisory. Prepares or chairs a gate; the council decides, not this skill.
|
||||
|
||||
Stop and hand back when: a discipline's evidence is missing rather than weak. A gap on a
|
||||
gating discipline means do not bring the ask — bring a feedback-only ask instead. Marking
|
||||
it "cleared with a note" is precisely the gate theatre the anti-patterns section is
|
||||
about.
|
||||
|
||||
+2
-2
@@ -365,7 +365,7 @@ Before marking work complete:
|
||||
- [ ] Tests use real code (mocks only if crossing a system boundary)
|
||||
- [ ] Edge cases and errors covered
|
||||
|
||||
Can't check all boxes? You skipped TDD. Start over.
|
||||
Can't check **or deliberately strike** all boxes? You skipped TDD. Start over. A box struck with a stated reason is answered; a box left hanging is not.
|
||||
|
||||
## When Stuck
|
||||
|
||||
@@ -374,7 +374,7 @@ Can't check all boxes? You skipped TDD. Start over.
|
||||
| Don't know how to test | Write wished-for API. Write assertion first. Ask Mathias. |
|
||||
| Test too complicated | Design too complicated. Simplify interface. |
|
||||
| Must mock everything | Code too coupled. Use dependency injection. |
|
||||
| Test setup huge | Extract helpers with `t.Helper()`. Still complex? Simplify design. |
|
||||
| Test setup huge | Extract helpers (`t.Helper()` in Go, a local `__tracebackhide__ = True` in Python). Still complex? Simplify design. |
|
||||
|
||||
## Debugging Integration
|
||||
|
||||
|
||||
@@ -82,3 +82,11 @@ created in a CAD session should trace to a TELOS GOAL. Every GOAL
|
||||
traces to a PROBLEM.
|
||||
|
||||
Traceability chain: PROBLEM → GOAL → SPEC → TICKET → IMPL → TEST
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only. Loads intention context; changes nothing.
|
||||
|
||||
Stop and hand back when: the brain is unreachable or returns nothing for the wing. Working
|
||||
from a remembered TELOS is the failure this skill exists to prevent — say the load failed
|
||||
rather than reconstructing it from memory.
|
||||
|
||||
@@ -309,3 +309,11 @@ Apply this checklist to every new test:
|
||||
- Load `tdd` skill for the full TDD workflow
|
||||
- Load `code-review` skill for test quality review during pre-merge review
|
||||
- See `clean-code/references/code-smells.md` for testing-specific smells
|
||||
|
||||
## Escalation
|
||||
|
||||
Read-only scoring of existing tests.
|
||||
|
||||
Stop and hand back when: the tests are fine and the *design under test* is what makes them
|
||||
awkward. Hard-to-test is a design finding, not a test finding, and rewriting the tests
|
||||
around it hides the thing worth knowing.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
# Tests for bin/check-escalation.
|
||||
#
|
||||
# #5 asked whether SKILL.md needs first-class failure/escalation fields and
|
||||
# decided: not frontmatter (nothing reads it), a body section (the model reads
|
||||
# that). #37 then had to define WHICH skills need one, and every obvious answer
|
||||
# was bad — a hardcoded list rots, content sniffing is unfalsifiable, a
|
||||
# frontmatter flag is the rejected option through the back door.
|
||||
#
|
||||
# So there is no acting set. EVERY skill answers the question; a read-only one
|
||||
# answers it in a line. The classification problem disappears, and "this skill
|
||||
# never hands back" becomes a stated claim rather than an absence.
|
||||
#
|
||||
# bin/escalation-pending.txt is a ratchet for skills whose real rule has not
|
||||
# been written yet. It is self-cleaning: a skill that has a section AND is still
|
||||
# listed fails, so the list cannot silently outlive its reason.
|
||||
|
||||
set -u
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
SUT="$ROOT/bin/check-escalation"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
WORK=${TMPDIR:-/tmp}/check_escalation_test.$$
|
||||
|
||||
if [ ! -f "$SUT" ]; then
|
||||
printf 'FAIL: %s does not exist\n' "$SUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() { rm -rf "$WORK"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# fixture <name> — an empty checkout with a bin/ for the pending list.
|
||||
fixture() {
|
||||
_f="$WORK/$1"
|
||||
mkdir -p "$_f/skills" "$_f/bin"
|
||||
: > "$_f/bin/escalation-pending.txt"
|
||||
printf '%s' "$_f"
|
||||
}
|
||||
|
||||
# skill <root> <name> <has-section>
|
||||
skill() {
|
||||
mkdir -p "$1/skills/$2"
|
||||
{
|
||||
printf -- '---\nname: %s\ndescription: d\n---\n\n# %s\n' "$2" "$2"
|
||||
[ "$3" = yes ] && printf '\n## Escalation\n\nStop and hand back when X. Hand back by: saying so.\n'
|
||||
} > "$1/skills/$2/SKILL.md"
|
||||
}
|
||||
|
||||
# expect <rc> <label> <fixture> [needle]
|
||||
expect() {
|
||||
_want=$1; _label=$2; _fx=$3; _needle=${4:-}
|
||||
_out=$(sh "$SUT" "$_fx" 2>&1); _rc=$?
|
||||
if [ "$_rc" -ne "$_want" ]; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s (want rc=%s got %s)\n%s\n' "$_label" "$_want" "$_rc" "$_out"; return
|
||||
fi
|
||||
if [ -n "$_needle" ] && ! printf '%s' "$_out" | grep -q "$_needle"; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s (rc ok, report never says %s)\n%s\n' "$_label" "$_needle" "$_out"; return
|
||||
fi
|
||||
pass=$((pass + 1)); printf ' ok %s\n' "$_label"
|
||||
}
|
||||
|
||||
f=$(fixture ok); skill "$f" alpha yes
|
||||
expect 0 'a skill with an Escalation section passes' "$f"
|
||||
|
||||
f=$(fixture missing); skill "$f" alpha no
|
||||
expect 1 'a skill with no section fails, naming it' "$f" 'alpha'
|
||||
|
||||
f=$(fixture pending); skill "$f" alpha no; printf 'alpha\n' > "$f/bin/escalation-pending.txt"
|
||||
expect 0 'a listed skill is allowed through the ratchet' "$f"
|
||||
|
||||
f=$(fixture done); skill "$f" alpha yes; printf 'alpha\n' > "$f/bin/escalation-pending.txt"
|
||||
expect 1 'a listed skill that now HAS a section fails — remove it from the list' "$f" 'escalation-pending'
|
||||
|
||||
f=$(fixture stale); skill "$f" alpha yes; printf 'ghost\n' > "$f/bin/escalation-pending.txt"
|
||||
expect 1 'a listed skill that does not exist fails' "$f" 'ghost'
|
||||
|
||||
f=$(fixture comments); skill "$f" alpha no
|
||||
printf '# a comment, and a blank line follow\n\nalpha\n' > "$f/bin/escalation-pending.txt"
|
||||
expect 0 'comments and blank lines in the list are ignored' "$f"
|
||||
|
||||
printf '\ncheck_escalation: %d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
@@ -224,5 +224,72 @@ manifest "$f/ck" 0.7.0
|
||||
manifest "$f/home/.claude/plugins/marketplaces/mathias" 0.9.0
|
||||
assert_fails "$f" '0.9.0' 'a newer marketplace clone is reported'
|
||||
|
||||
# --- 9. --online: the release this host has never fetched ------------------
|
||||
# Everything above compares copies already on the host, so a host where the
|
||||
# checkout, the marketplace clone and the installed plugin all agree reads as
|
||||
# clean while being a release behind origin. Offline by design (#30); this is
|
||||
# the opt-in that closes it (#38).
|
||||
#
|
||||
# The remote lookup is stubbed through $DOCTOR_LATEST_TAG_CMD so no test needs
|
||||
# a network or a real remote.
|
||||
|
||||
online_fixture() {
|
||||
_f=$(new_fixture "$1"); wire_clean "$_f"
|
||||
installed_state "$_f" "$2"
|
||||
manifest "$_f/ck" "$2"
|
||||
printf '%s' "$_f"
|
||||
}
|
||||
|
||||
# assert_online <fixture> <stub-tag> <want-rc> <needle> <label>
|
||||
assert_online() {
|
||||
_out=$(HOME="$1/home" DOCTOR_CHECKOUT="$1/ck" \
|
||||
DOCTOR_LATEST_TAG_CMD="printf %s\\n $2" sh "$DOCTOR" --online 2>&1); _rc=$?
|
||||
if [ "$3" -eq 0 ] && [ "$_rc" -ne 0 ]; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s\n exited %s on a current host\n got: %s\n' "$5" "$_rc" "$_out"; return
|
||||
fi
|
||||
if [ "$3" -ne 0 ] && [ "$_rc" -eq 0 ]; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s\n exited 0; staleness went unreported\n' "$5"; return
|
||||
fi
|
||||
if [ -n "$4" ] && ! printf '%s' "$_out" | grep -q -- "$4"; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s\n report never mentions "%s"\n got: %s\n' "$5" "$4" "$_out"; return
|
||||
fi
|
||||
pass=$((pass + 1)); printf ' ok %s\n' "$5"
|
||||
}
|
||||
|
||||
f=$(online_fixture behind 0.8.0)
|
||||
assert_online "$f" v0.9.0 1 '0.9.0' '--online reports a release the host has not fetched'
|
||||
|
||||
f=$(online_fixture current 0.9.0)
|
||||
assert_online "$f" v0.9.0 0 '' '--online stays quiet when the host is current'
|
||||
|
||||
# A host AHEAD of the newest tag is the normal state between a release commit
|
||||
# and its tag; that is not drift (same reasoning as check's ordering rule).
|
||||
f=$(online_fixture ahead 0.9.1)
|
||||
assert_online "$f" v0.9.0 0 '' '--online does not complain about being ahead'
|
||||
|
||||
# Without the flag, no lookup happens at all — the stub would report a newer
|
||||
# tag and must be ignored.
|
||||
f=$(online_fixture offline 0.8.0)
|
||||
_out=$(HOME="$f/home" DOCTOR_CHECKOUT="$f/ck" \
|
||||
DOCTOR_LATEST_TAG_CMD="printf v9.9.9\\n" sh "$DOCTOR" 2>&1); _rc=$?
|
||||
if [ "$_rc" -eq 0 ]; then
|
||||
pass=$((pass + 1)); printf ' ok the default run performs no remote lookup\n'
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL the default run performs no remote lookup\n got: %s\n' "$_out"
|
||||
fi
|
||||
|
||||
# A remote that cannot be reached is not drift and must not be reported as it.
|
||||
# An unreachable remote produces no output at all, so it gets its own run
|
||||
# rather than an empty stub argument — "prints nothing" and "prints an empty
|
||||
# string" are different things and only the first is the case under test.
|
||||
f=$(online_fixture unreachable 0.9.0)
|
||||
_out=$(HOME="$f/home" DOCTOR_CHECKOUT="$f/ck" \
|
||||
DOCTOR_LATEST_TAG_CMD="true" sh "$DOCTOR" --online 2>&1); _rc=$?
|
||||
if [ "$_rc" -ne 0 ] && printf '%s' "$_out" | grep -q 'could not'; then
|
||||
pass=$((pass + 1)); printf ' ok --online reports an unreachable remote as its own fault\n'
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL --online reports an unreachable remote as its own fault\n rc=%s got: %s\n' "$_rc" "$_out"
|
||||
fi
|
||||
|
||||
printf '\ndoctor: %d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
# Tests for skills/lineage/lineage.sh — the decision-archaeology helper.
|
||||
#
|
||||
# The techniques this automates are the ones people skip because they are
|
||||
# fiddly to type: `git log -S` to find when a thing was introduced AND removed,
|
||||
# --diff-filter=D to find files that once existed, and grepping decision records
|
||||
# for the term. A skill that only *describes* those is a skill that gets read and
|
||||
# not run (#11, #19's "small robust tools over prose").
|
||||
|
||||
set -u
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
SUT="$ROOT/skills/lineage/lineage.sh"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
WORK=${TMPDIR:-/tmp}/lineage_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
|
||||
|
||||
# A repo where a thing was built, written about, and then removed.
|
||||
mkdir -p "$WORK/repo/docs/adr"
|
||||
cd "$WORK/repo" || exit 1
|
||||
git init -q .
|
||||
git config user.email t@example.com
|
||||
git config user.name test
|
||||
|
||||
printf 'func WidgetCache() {}\n' > cache.go
|
||||
git add -A && git commit -qm "feat: add WidgetCache for lookup speed"
|
||||
|
||||
printf -- '---\nadr: 0001\ntitle: use a WidgetCache\n---\nWe chose WidgetCache over a database index.\n' > docs/adr/0001-widgetcache.md
|
||||
git add -A && git commit -qm "docs: record the WidgetCache decision"
|
||||
|
||||
git rm -q cache.go
|
||||
git commit -qm "revert: drop WidgetCache, it went stale under concurrent writes"
|
||||
|
||||
cd - >/dev/null || exit 1
|
||||
out=$(sh "$SUT" WidgetCache "$WORK/repo" 2>&1)
|
||||
|
||||
check() {
|
||||
if printf '%s' "$out" | grep -q "$1"; then
|
||||
pass=$((pass + 1)); printf ' ok %s\n' "$2"
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL %s\n output had no %s\n%s\n' "$2" "$1" "$out"
|
||||
fi
|
||||
}
|
||||
|
||||
check 'add WidgetCache' 'finds the commit that introduced it'
|
||||
check 'drop WidgetCache' 'finds the commit that removed it — the revival question'
|
||||
check '0001-widgetcache.md' 'finds the decision record naming it'
|
||||
check 'cache.go' 'names the file that no longer exists'
|
||||
|
||||
# A no-match must NOT be reported as evidence the idea is new. The first version
|
||||
# of this tool said "it means this is new here" and a pressure test caught it
|
||||
# asserting exactly that about the most-discussed removed feature in this repo —
|
||||
# the search term simply had a space in it. That is the inference the skill's own
|
||||
# anti-pattern table forbids, printed in the tool's voice.
|
||||
out=$(sh "$SUT" NeverExisted "$WORK/repo" 2>&1)
|
||||
if printf '%s' "$out" | grep -q 'NOT evidence the idea is new' \
|
||||
&& printf '%s' "$out" | grep -qi 'identifier'; then
|
||||
pass=$((pass + 1)); printf ' ok a no-match refuses to imply novelty and suggests other terms\n'
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL a no-match refuses to imply novelty and suggests other terms\n%s\n' "$out"
|
||||
fi
|
||||
|
||||
# The repo actually searched is always stated — a confident null against the
|
||||
# wrong tree is this tool's worst failure mode, and the default is the cwd.
|
||||
out=$(sh "$SUT" WidgetCache "$WORK/repo" 2>&1)
|
||||
if printf '%s' "$out" | grep -q "searching: "; then
|
||||
pass=$((pass + 1)); printf ' ok names the repo it searched\n'
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL names the repo it searched\n%s\n' "$out"
|
||||
fi
|
||||
|
||||
# The sources it cannot reach are named every run, so their absence never reads
|
||||
# as a null result.
|
||||
if printf '%s' "$out" | grep -q 'brain_query' && printf '%s' "$out" | grep -qi 'closed issues'; then
|
||||
pass=$((pass + 1)); printf ' ok names the sources it cannot search\n'
|
||||
else
|
||||
fail=$((fail + 1)); printf ' FAIL names the sources it cannot search\n%s\n' "$out"
|
||||
fi
|
||||
|
||||
printf '\nlineage: %d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user