Compare commits

..

15 Commits

Author SHA1 Message Date
Kevin Veen-Birkenbach
c0f813b792 feat(plan): add objective-oriented planning skill
Some checks are pending
🧪 Test / 🧪 Lock + lint (push) Waiting to run
Turn a request into a plan whose every item is an outcome with a
verification, then execute it. The run opens with active-listening, since
robot never asks once it starts, and it implements the whole plan
statically before robot takes over the run-deploy-observe-fix loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:01:56 +02:00
Kevin Veen-Birkenbach
8f231d49e7 docs(skills): cluster CI failures before pulling artifacts
Move the artifact-download guidance out of the generic dialectic loop and
into triage, where it now runs per error-message cluster instead of per
failing job: group the failing jobs by their actual failing line, fetch one
representative's artifacts per cluster, and run the dialectic per cluster.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:01:56 +02:00
Kevin Veen-Birkenbach
545c213f88 docs(skills): fetch CI artifacts before theorising
A job log usually names an artifact path and stops there; the assertion
text, server exception and state dumps live inside the artifact. Both
triage and dialectic now require downloading them before forming a
thesis, since theorising from the log alone while an unread artifact
holds the answer is the most expensive mistake in the loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 12:05:19 +02:00
Kevin Veen-Birkenbach
04f24bb16d feat(test-fix): add iterative run-diagnose-fix loop skill
Reuses the test skill for discovery and owns the loop after the first
red result: one root cause and one fix per iteration, with explicit
stop conditions so a stuck loop reports instead of thrashing. Skipping
or loosening tests is a report item, never a fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 15:13:44 +02:00
Kevin Veen-Birkenbach
26585a86fc feat(test): add skill for picking Makefile test targets
Discovers test* targets from the Makefile instead of guessing a runner,
so the project's env vars and build prerequisites are not bypassed, and
offers them as a selection list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 15:13:44 +02:00
Kevin Veen-Birkenbach
cd32841847 feat(refactor): require a test net and SRP, SPOT, KISS, DRY
Some checks failed
🧪 Test / 🧪 Lock + lint (push) Has been cancelled
🔄 Update / 🧠 Update skills-lock.json (push) Has been cancelled
Before editing, the skill now establishes a safety net: run existing
coverage for a green baseline, or write characterization tests against
the current behaviour where the code is testable logic. Those tests must
still pass unchanged afterwards, so a test that had to be edited means
the refactor moved behaviour and gets reverted.

The refactor itself is now governed by SRP, SPOT, KISS and DRY, and no
file containing program code may exceed 250 lines. Description and
markup languages are exempt, as is a file the language or framework
genuinely forbids splitting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 18:35:04 +02:00
Kevin Veen-Birkenbach
1bc7711f38 feat(refactor): add skill for cleaning up the uncommitted change set
The skill refactors every file that is currently uncommitted in the git
working tree up to the project's coding standards, without changing
behaviour and without widening the scope to untouched files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 18:35:04 +02:00
Kevin Veen-Birkenbach
27aaabf305 feat(autotune): add skill for generating and tuning skills
The skill generates new skills and optimizes existing ones for token
usage, error rate, focus and persisted learnings. Every decision is
confirmed through single-select (radio button) questions, and the skill
only runs on an explicit operator trigger.

A UserPromptSubmit hook reminds the operator at most once an hour that
/autotune exists; the reminder is a hint for the operator, never a
trigger for the agent. The install-time settings patch registers it
alongside the caveman and ponytail plugins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 18:35:03 +02:00
Kevin Veen-Birkenbach
a42d6b76f4 Merge branch 'master' of github.com:kevinveenbirkenbach/skills
Some checks failed
🧪 Test / 🧪 Lock + lint (push) Has been cancelled
2026-07-25 18:15:01 +02:00
Kevin Veen-Birkenbach
9ac5444628 feat(skills): add no-defaults skill
Forbids substantive defaults for parameters, settings and env lookups
across languages; only false, null and "" are free. A missing value
must fail loudly at the earliest point instead of silently becoming a
plausible wrong answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 18:14:31 +02:00
Kevin Veen-Birkenbach
94031f5a66 feat(observe): watch a target and triage new failures on a loop
Add the observe skill: /observe <target> drives a dynamic /loop that polls a named CI run, pipeline, build or log, diffs the failures against a session baseline, and applies triage (hence dialectic per job) to newly appeared failures only. Chains onto the existing triage and dialectic skills; hardened against re-triaging fixed failures and against idle-polling harness-tracked work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 09:11:02 +02:00
Kevin Veen-Birkenbach
55f9935c63 feat(skills): add active-listening, dialectic, triage, and robot
- active-listening: gather every requirement before acting; ask until nothing
  needed is open, then reflect back.
- dialectic: thesis / adversarial antithesis / synthesis looped to a ~99%
  thesis, deep-inspecting every accessible source; calls active-listening.
- triage: drive a failing pipeline to green by applying dialectic to every
  failing job independently.
- robot: autonomous-execution contract - no questions once the goal is clear,
  never stop before it is reached, only cleared (non-denied/non-ask) commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:16:47 +02:00
Kevin Veen-Birkenbach
91b9845c1b feat(skills): add commit workflow, explain and help-skills
comments-clean purges explanatory comments before every operator-requested stage or commit and penalizes own violations into persistent memory; commit-no-verify commits the exact staging index with hooks skipped, the invocation itself being the per-commit authorization; commit-next chains both and stages the next logical bundle with an explain and confidence briefing; explain walks code and its collaborators in execution order and offers a Mermaid schema in the affected README; help-skills lists every project, global and plugin skill with triggers and composition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:16:47 +02:00
Kevin Veen-Birkenbach
30edba08bc feat(confidence): report calibrated confidence on every stage and fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:36:30 +02:00
Kevin Veen-Birkenbach
3d754c6e0e feat(autoskill): propose a skill when the operator repeats a pattern
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:36:30 +02:00
22 changed files with 1108 additions and 5 deletions

View File

@@ -26,7 +26,7 @@ Copy the skills into a specific project (`<repo>/.agents/skills` and `<repo>/.cl
make project TARGET=/path/to/repo make project TARGET=/path/to/repo
``` ```
Both commands also enable the caveman and ponytail plugins in the target's `.claude/settings.json` so their modes auto-activate on session start; existing settings are preserved. Both commands also patch the target's `.claude/settings.json`: the caveman and ponytail plugins are enabled so their modes auto-activate on session start, and the `autotune` reminder hook is registered so the agent points you at `/autotune` at most once an hour. Existing settings are preserved.
Restart your agent afterwards so it loads the new skills. Restart your agent afterwards so it loads the new skills.

View File

@@ -1,11 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
// Enable the caveman and ponytail plugins in a Claude Code settings.json so // Enable the caveman and ponytail plugins in a Claude Code settings.json so
// their SessionStart hooks auto-activate both modes. Merges non-destructively: // their SessionStart hooks auto-activate both modes, and register the autotune
// existing marketplaces and enabled plugins are preserved, unparseable files // reminder hook. Merges non-destructively: existing marketplaces, plugins and
// are left untouched. // hooks are preserved, unparseable files are left untouched.
"use strict"; "use strict";
const fs = require("fs"); const fs = require("fs");
const path = require("path");
const settingsPath = process.argv[2]; const settingsPath = process.argv[2];
if (!settingsPath) { if (!settingsPath) {
@@ -38,5 +39,15 @@ for (const [name, entry] of Object.entries(MARKETPLACES)) {
} }
data.enabledPlugins = Object.assign(data.enabledPlugins || {}, PLUGINS); data.enabledPlugins = Object.assign(data.enabledPlugins || {}, PLUGINS);
const reminder = path.join(path.dirname(settingsPath), "skills", "autotune", "hooks", "reminder.sh");
data.hooks = data.hooks || {};
data.hooks.UserPromptSubmit = data.hooks.UserPromptSubmit || [];
const registered = JSON.stringify(data.hooks.UserPromptSubmit).includes("autotune/hooks/reminder.sh");
if (!registered) {
data.hooks.UserPromptSubmit.push({
hooks: [{ type: "command", command: `bash ${JSON.stringify(reminder)}` }],
});
}
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n"); fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n");
console.log(`skills: enabled caveman + ponytail plugins in ${settingsPath}`); console.log(`skills: enabled caveman + ponytail plugins and the autotune reminder in ${settingsPath}`);

View File

@@ -0,0 +1,38 @@
---
name: active-listening
description: >
Gather every requirement before acting. Trigger at the start of any task
whose scope, constraints, or success criteria are not fully pinned down, and
whenever another skill needs an operator-only fact — ask focused questions
until nothing needed is still open, then reflect the understanding back.
Portable across projects.
---
Do not act on a half-understood request. Listen actively until you could state
the task, its constraints, and its definition of done without guessing.
## Procedure
1. **Surface the unknowns.** List every open question the task leaves: ambiguous
scope, unstated constraints, success criteria, edge cases, which files or
systems are in play, what "done" means, and any decision only the operator
can make.
2. **Derive what you can first.** Remove from that list every question you can
answer yourself from the code, the logs, the git history, or a sensible
default. Active listening is not offloading your own investigation onto the
operator.
3. **Ask, focused.** Put the genuinely-open questions to the operator — grouped,
concrete, answerable, with a recommended default where one exists. Prefer a
few high-leverage questions over a long interrogation. Use the host's
structured question mechanism when one exists.
4. **Listen and integrate.** Fold each answer in. A new answer often opens a new
question — keep going.
5. **Loop until closed.** Repeat until no needed information is still open.
6. **Reflect back.** Restate the task, its constraints, and its definition of
done in your own words for a final confirmation before acting.
## Rules
- Never invent an answer to an open question to avoid asking.
- Never ask what you can determine yourself.
- One genuinely-blocking unknown is enough reason to ask; do not proceed past it.

30
skills/autoskill/SKILL.md Normal file
View File

@@ -0,0 +1,30 @@
---
name: autoskill
description: >
Detect operator repetition and turn it into a skill. Trigger when the
operator gives an instruction, correction, or prompt pattern for roughly
the third time in a conversation (or says they keep repeating themselves),
and the pattern is general enough to reuse. Portable across projects.
---
When you notice the operator repeating the same kind of instruction,
correction, or prompt pattern (about three occurrences, exact wording may
vary), do the following:
1. Finish the current task first; never interrupt work in progress.
2. Then propose, in one short paragraph: the repeated pattern you observed,
a skill name suggestion, the trigger phrasing, and what the skill would
do. Ask whether to create it.
3. On approval, write the skill:
- a conversation shortcut or expansion: add it to the `shortcuts` skill
table instead of creating a new skill.
- project-specific behavior: create it in the project's own skills
directory (e.g. `skills/<name>/SKILL.md` in the repository).
- portable behavior: create `skills/<name>/SKILL.md` in the operator's
skills repository.
Follow the local naming conventions and keep the skill a thin,
single-purpose instruction; route to an authoritative doc when one
exists instead of duplicating it.
4. Do not propose a skill for one-off work, secrets, or anything whose
repetition is already covered by an existing skill; when an existing
skill almost fits, propose extending it instead.

92
skills/autotune/SKILL.md Normal file
View File

@@ -0,0 +1,92 @@
---
name: autotune
description: >
Generate new agent skills and optimize existing ones so the agent works more
efficiently: fewer input and output tokens, fewer errors, more focus, and
learnings that persist across sessions. Every proposal is confirmed with the
operator through single-select (radio button) questions. Trigger ONLY on an
explicit operator request (/autotune, "autotune", "tune the skills"); never
run it on your own initiative. Portable across projects.
---
Autotune turns observed friction into durable skills. It runs on demand only,
it changes nothing without an explicit answer from the operator, and every
skill it writes or rewrites lands in the operator's skills repository.
## Trigger discipline
- Run **only** when the operator asks: `/autotune`, "autotune", "tune the
skills", or an equivalent instruction.
- The 60-minute reminder hook is a hint for the operator, not a trigger. When it
fires, print the hint and continue the operator's actual request.
- Never propose, write, or edit a skill as a side effect of unrelated work.
## Step 1: collect evidence
Before asking anything, gather concrete friction from the current session and,
where readable, from the project's memory and agent instruction files:
- **Token waste**: repeated file re-reads, whole-file reads where a range was
enough, long tool output pasted back, re-running a command instead of
grepping a saved log, verbose report prose.
- **Errors**: rejected tool calls, permission prompts, retried commands,
corrections the operator had to give twice.
- **Focus loss**: work that drifted beyond the request, unrequested tests or
deploys, questions that stalled an autonomous run.
- **Lost learnings**: facts re-derived this session that a memory file or a
skill should already have carried.
List each finding as one line: `symptom -> cost -> candidate fix`. Skip
anything that happened once and is not a pattern.
## Step 2: ask, do not decide
Use `AskUserQuestion` with `multiSelect: false` so every question renders as
radio buttons. Never assume an answer, never batch several decisions into one
option, and never write a file before the answers are in.
Ask in this order, at most four questions per call:
1. **Scope** - "What should autotune do this run?" Options: `Create a new
skill`, `Optimize an existing skill`, `Both`, plus the ranked findings if
more than one candidate exists.
2. **Target** - which finding becomes a skill, or which existing skill gets
optimized. One option per candidate, each with its measured cost in the
description.
3. **Shape** - for a new skill: `Standalone skill`, `Row in the shortcuts
skill`, `Extend an existing skill`, `Memory entry instead of a skill`.
For an optimization: `Tighten wording only`, `Change the trigger`, `Add
rules`, `Split into two skills`, `Merge into another skill`.
4. **Placement** - `Operator skills repository (portable)` or
`This project's skills directory (project-specific)`.
Present the drafted skill text (name, trigger phrasing, body outline) and ask
for a final `Write it` / `Revise it` / `Discard` confirmation before touching
disk. Rewrites of an existing skill additionally show a before/after diff of
the changed lines in that confirmation.
## Step 3: write
- Portable skills go to `skills/<name>/SKILL.md` in the operator's skills
repository; project-specific ones to that project's skills directory.
- Frontmatter carries `name` and a `description` that states what the skill
does **and** when to trigger it - the description is the only part loaded into
every session, so it decides whether the skill ever fires.
- Body is thin and single-purpose: the smallest instruction that changes
behaviour. Route to an authoritative doc instead of duplicating it.
- Optimizing an existing skill means the file gets shorter or sharper, not
longer. Delete rules the agent already follows, merge duplicated ones, and
cut every sentence that does not change an action.
- Report what changed in at most three lines, then refresh the reminder stamp
so the next hint is an hour out: `date +%s > ~/.claude/.autotune-stamp`.
## Rules
- One skill per run unless the operator picked `Both`; a broad rewrite of the
whole collection is not what autotune is for.
- Never write a skill for one-off work, for secrets, or for behaviour an
existing skill already covers - propose extending that skill instead.
- A skill that saves tokens but loses correctness is a regression; validation,
error handling, and security steps are never the thing that gets trimmed.
- Efficiency claims stay honest: name the observed friction the skill removes,
never a guessed percentage.

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# UserPromptSubmit hook: emit the autotune hint at most once per interval.
# Stdout lands in the agent's context, which is why the payload tells the agent
# to show the hint and keep going instead of running autotune by itself.
set -euo pipefail
interval="${AUTOTUNE_INTERVAL_SECONDS:-3600}"
stamp="${AUTOTUNE_STAMP:-${HOME}/.claude/.autotune-stamp}"
now="$(date +%s)"
last=0
if [[ -f "${stamp}" ]]; then
read -r last <"${stamp}" || true
[[ "${last}" =~ ^[0-9]+$ ]] || last=0
fi
if ((now - last < interval)); then
exit 0
fi
mkdir -p "$(dirname "${stamp}")"
printf '%s\n' "${now}" >"${stamp}"
cat <<'HINT'
AUTOTUNE HINT: over an hour since the last hint. Show the operator this notice
verbatim as a short block, then continue with their request. Do NOT run
autotune, do not propose skills, do not ask questions about it.
💡 /autotune - generate a new skill or optimize an existing one
(fewer tokens, fewer errors, more focus, learnings that persist).
Runs only when you trigger it.
HINT

View File

@@ -0,0 +1,33 @@
---
name: comments-clean
description: >
Purge explanatory comments from staged and unstaged changes. Run
AUTOMATICALLY, without being asked, every time the operator requests a
commit or stage in any wording (commit, commit no-verify, stage next,
socon, sobu, socobu, "commite ..."), BEFORE executing that request; also
on /comments-clean or when the operator complains about comments.
Explanatory comments are bad practice: they narrate code instead of
letting it speak. Portable across projects.
---
When the operator asks to commit or stage, run this audit first and only
then perform the requested git action; never ask whether to run it.
Audit every staged and unstaged change (`git diff` plus `git diff --cached`,
added lines only) and delete every explanatory comment:
1. Delete: restated code, step narration, section banners, "Note that",
rationale prose, apology comments, and any comment whose information the
code, task name, or commit message already carries. When in doubt,
delete.
2. Keep only: parameter/exit-code docs, nocheck/noqa/shellcheck directives,
and project-sanctioned exception markers that name a concrete trip-wire.
A relabeled narration is not an exception; delete it instead.
3. Re-run the project's comment lint if one exists and re-stage the files
that were already staged.
4. Penalty: if this audit finds anything you wrote yourself, admit it in
the report (file and count, no excuses), lower your confidence numbers
for the affected bundle, and write the incident to persistent agent
memory (extend the existing minimal-comments feedback entry with the
date and the pattern you slipped on) so the calibration survives the
session.

View File

@@ -0,0 +1,20 @@
---
name: commit-next
description: >
Commit the staged bundle with --no-verify, then stage the next logical
bundle and brief the operator on it. Trigger on /commit-next or when the
operator asks to commit and stage the next bundle in one step. Portable
across projects.
---
1. Run the commit-no-verify skill on the currently staged changes
(comments-clean audit first, then `git commit --no-verify`, exact
staging index only).
2. Group the remaining uncommitted changes into logical bundles (one
concern per bundle: same failure class, same mechanism, or same role)
and stage the next one; never mix concerns to save a commit.
3. For the newly staged bundle run the explain skill (what the changes do
and how the pieces interact, file:line-anchored) followed by the
confidence skill (fix confidence, break risk, evidence, residuals).
4. Close with what remains unstaged and wait for the operator's commit
decision; never auto-commit the newly staged bundle.

View File

@@ -0,0 +1,15 @@
---
name: commit-no-verify
description: >
Commit the currently staged changes with --no-verify (skip the pre-commit
hooks). Trigger on /commit-no-verify or when the operator asks for a
no-verify commit in any wording. Invoking this skill IS the explicit
per-commit authorization that --no-verify requires; it never carries over
to later commits. Portable across projects.
---
1. Run the comments-clean audit over the staged and unstaged changes first.
2. Commit exactly the staging index with `git commit --no-verify` and a
Conventional-Commits message derived from the staged diff; never add
unstaged files, never push.
3. Report the created commit hash and what remains uncommitted.

View File

@@ -0,0 +1,29 @@
---
name: confidence
description: >
Report calibrated confidence unprompted. Trigger whenever you stage
changes, declare a fix or bundle ready, or summarize completed work the
operator may commit or deploy — the operator must never have to ask "how
sure are you?". Portable across projects.
---
Every stage summary, fix report, or ready-to-commit bundle ends with a
confidence block:
1. **Fix confidence** (percent): probability the change does what it
claims. **Break risk** (percent): probability it breaks anything else.
Two separate numbers, never one blended figure.
2. **Evidence**: the strongest proof lines only — what was executed,
reproduced, enumerated, or read from authoritative source. Distinguish
proven (ran it, saw it) from derived (reasoned statically).
3. **Residuals**: name exactly what keeps the number below 100 and what
would close the gap (live deploy, missing artifact, unexercised path).
Never a bare percentage without its residuals.
Calibration rules:
- Number the claim you verified, not the effort you spent; discount after
any error the operator caught in the same area.
- Distinguish "fixes the observed failure" from "makes the job green" when
hidden layers may follow, and give both numbers.
- Static analysis alone caps at the low nineties; only executed evidence
(test, reproduction, live probe) justifies more.

52
skills/dialectic/SKILL.md Normal file
View File

@@ -0,0 +1,52 @@
---
name: dialectic
description: >
Reach a defensible conclusion through a dialectical process — thesis,
adversarial antithesis, synthesis — iterated until one thesis survives every
attack at ~99% confidence. Trigger for any consequential root-cause, design,
or decision where being wrong is expensive and the answer is not yet certain.
Deep-inspects every accessible source. Portable across projects.
---
Reach the truth by dialectic, not by first guess. Do not stop at a plausible
answer; drive the loop until a thesis survives every attack at ~99% confidence.
## Before you start: listen
Invoke the `active-listening` skill first. Do not begin the dialectic until you
have gathered every requirement, constraint, and success criterion the task
needs. A dialectic run on the wrong question wastes the whole loop. Return to
`active-listening` mid-loop whenever an objection turns on a fact only the
operator holds.
## The loop
1. **Thesis.** Form the strongest initial claim you can, grounded in evidence,
not assumption. Deep-inspect every source you can reach: the source code and
git history; the running containers (exec, logs, inspect through whatever
make or CLI helpers the project exposes); CI logs and artifacts; tests; docs.
Cite `file:line` and command output — never a bare assertion.
2. **Antithesis.** Spawn MULTIPLE independent skeptics (parallel subagents or a
workflow) whose only job is to REFUTE the thesis. Each re-inspects the
sources itself and defaults to "refuted" on any concrete hole. Give diverse
skeptics diverse lenses: correctness, an alternate mechanism, does-it-
reproduce, does-the-fix-break-something-else.
3. **Synthesis.** Fold every surviving objection back into a refined thesis. A
skeptic that found a real hole replaces or corrects the thesis; a skeptic
that failed to refute raises confidence.
4. **Iterate.** Repeat thesis → antithesis → synthesis until a full skeptic
round finds no new hole and the thesis holds at ~99%. If confidence stalls
below 99%, name the exact residual and the evidence that would close it, then
go get that evidence (more inspection, or `active-listening` for an
operator-only fact) and loop again.
## Rules
- Executed evidence beats reasoning: run it, exec into it, read the log. A
thesis backed only by static reasoning caps in the low nineties.
- The antithesis MUST be independent — never let the thesis-author be its own
sole skeptic.
- Diversity over redundancy: skeptics with different lenses catch failure modes
identical skeptics miss.
- Report the final thesis with its evidence AND the losing objections, so the
operator sees why it survived.

25
skills/explain/SKILL.md Normal file
View File

@@ -0,0 +1,25 @@
---
name: explain
description: >
Explain code and its interactions. Trigger when the operator asks what a
piece of code does, how a mechanism works, why components interact the way
they do, or invokes /explain on a file, function, role, or module.
Portable across projects.
---
Explain the named code so an engineer who has never seen it can follow it:
1. Read the code and every collaborator it touches (callers, callees,
templates, configs) before explaining; never explain from the name alone.
2. Lead with the purpose in one sentence, then walk the flow in execution
order: inputs, transformations, side effects, outputs. Name the real
files and line references.
3. Make the interactions explicit: which component induces, triggers, or
consumes which, and where the single source of truth lives. Point out
trip-wires (non-obvious ordering, escaping, caching, permission edges)
as part of the flow, not as an appendix.
4. Afterwards ask whether to persist a Mermaid diagram of the explained
mechanism under a `## Schema` heading in the README.md next to the
affected code (create the heading if missing, replace its previous
diagram if present). On approval, keep the diagram to the explained
scope: components as nodes, induction/data flow as labeled edges.

View File

@@ -0,0 +1,25 @@
---
name: help-skills
description: >
List and explain every skill available in the current session. Trigger on
/help-skills, "welche skills gibt es", "skill übersicht", or when the
operator asks what skills exist or what a skill does. Portable across
projects.
---
Produce a one-shot overview of every skill the session can invoke:
1. Sources, in this order: the session's available-skills listing, the
project's own skills (`skills/` and `.claude/skills/` in the repo), the
user-global skills (`~/.claude/skills/`), and plugin-provided skills.
Deduplicate by name; note when a project skill shadows a global one.
2. Output one table per origin (project, global, plugin): skill name,
trigger (slash command or phrasing), one-line purpose in your own words
— compress the description, do not paste it.
3. When the operator names a specific skill or topic, explain only the
matching skills in more depth: what they do, when they fire on their
own, and how they compose (e.g. commit-next → commit-no-verify →
comments-clean).
4. End with the activation hint: project installers or `make install` in
the skills repository refresh the live copies; a restart loads renamed
or new skills.

108
skills/no-defaults/SKILL.md Normal file
View File

@@ -0,0 +1,108 @@
---
name: no-defaults
description: >
Forbid default values for parameters, settings, and environment lookups in
every language. Load and apply AUTOMATICALLY on every task that writes,
edits, generates, refactors, or reviews code, config, templates, schemas,
or infrastructure — never wait to be asked. A default silently substitutes
a guess for a value the caller failed to supply, so a misconfiguration
becomes wrong behaviour instead of a loud failure. Demand strict, explicit
values and fail loudly when one is missing. `false`, `null` and `""` are the
only free defaults; every other default needs a written justification.
Portable across projects.
---
A default value is a guess, written at the point where the caller's intent is
least known, and it turns a missing value into a plausible wrong answer instead
of an error. Two callers then read the same call differently: one means "use the
default", the other simply forgot. That ambiguity is the defect this rule exists
to prevent.
Never write a substantive default. Require the value. When it is absent, fail
immediately, loudly, and with a message that names the missing thing.
## The rule
1. Every parameter, option, setting, environment lookup, column, and template
variable MUST be supplied explicitly by its caller.
2. A missing value MUST abort at the earliest possible moment — argument
parsing, config load, container start — never at the point of use, and never
by proceeding with a substitute.
3. The error MUST name the missing key and where to set it.
4. Optionality, when a value genuinely may be absent, MUST be modelled as an
explicit named case that the call site handles, never as a silent
substitution. `None`/`null` handled in a visible branch is fine; a fallback
baked into the accessor is not.
## The empty defaults are free
`false`, `null` (`None`, `nil`, `undefined`), and the empty string `""` MAY be
used as defaults without justification. They are the one class that carries no
guess: they denote absence itself rather than inventing a value the caller might
have meant. `${VAR:-}`, `d.get(k)`, `a ?? null`, and `def f(a=None)` are fine.
Every other default value — a number, a non-empty string, a non-empty list or
map, an enum member, a path, a timeout, a retry count — MUST carry an inline
comment stating why that specific value is correct for every caller that omits
it. No comment, no default. Use whatever exception-comment form the project
sanctions; where none exists, a plain comment on or directly above the default.
The comment is the point: it forces you to name the assumption, so a reviewer
can disagree with it. "Sensible", "standard", and "usual" name nothing — if the
justification does not survive being written down, the default does not survive
either.
## Banned constructs, by language
Each `Never` below means the default is a substantive value. The same construct
with `false`, `null`, or `""` is permitted, and with any other value is permitted
only when justified in a comment.
| Language | Never | Instead |
|---|---|---|
| Bash | `${VAR:-x}`, `${VAR:=x}`, `: "${VAR:=x}"` | `: "${VAR:?set VAR in <file>}"` |
| Python | `def f(a=1)`, `d.get(k, x)`, `getattr(o, n, x)`, `os.environ.get(k, x)`, `argparse(default=)` | required args, `d[k]`, `os.environ[k]`, `add_argument(required=True)` |
| Jinja / Ansible | `\| default(x)`, `lookup('env', k) \| default('')` | `\| mandatory`, `assert` on the var |
| JS / TS | `function f(a = 1)`, `const {a = 1} = o`, `a \|\| x`, `a ?? x` | required params, explicit `if (a === undefined) throw` |
| Go | relying on zero values | constructor that validates every field |
| Rust | `unwrap_or`, `unwrap_or_default`, `#[serde(default)]` | `ok_or(Error::MissingX)?`, `#[serde(deny_unknown_fields)]` |
| Java / C# | optional parameters, `??`, `getOrDefault` | required constructor args, explicit null check that throws |
| SQL | `DEFAULT` on a column | `NOT NULL` and supply it in every insert |
| Make | `VAR ?= x` | `VAR := $(or $(VAR),$(error VAR is required))` |
| Docker / Compose | `${VAR:-x}`, `ENV` placeholders standing in for config | `${VAR:?VAR is required}` |
| Terraform / Helm | `variable { default = }`, `default` in `values.yaml` | no default; fail on the missing input |
## How to apply
Before writing any parameter, ask: what happens if the caller omits it? If the
answer is anything other than "it fails and says so", rewrite it.
While editing, treat every `default`, `?:`, `??`, `||`, `:-`, `:=`, `or`,
`fallback`, and `getOrElse` in a value-resolution position as a finding unless
its substitute is `false`, `null` or `""`, or a comment already justifies it.
Grep your own diff for them before presenting it.
Pushing a default one layer up is not a fix: a caller that passes a constant it
invented has only moved the guess. Follow the value to its true owner —
the operator, the inventory, the deployment config — and require it there.
Removing an existing default is a behaviour change. Do not do it silently as a
drive-by: name it, and let the operator decide whether it lands in this change
or its own.
## What does not count as a justification
"It is conventional", "every other project does it", "the value is obvious",
and "it keeps the signature short" justify nothing — they assert that someone
else made the choice, which is the ambiguity this rule exists to remove. A valid
justification names why this value is right for a caller who says nothing.
An operator asking for a specific default, in this conversation, for this value,
settles it without a comment.
## Penalty
If this audit finds an unjustified default you wrote yourself: say so in the
report, with file and count and no excuses; lower the confidence numbers for the affected
bundle; and write the incident to persistent agent memory, recording the date
and the construct you reached for, so the calibration survives the session.

60
skills/observe/SKILL.md Normal file
View File

@@ -0,0 +1,60 @@
---
name: observe
description: >
Watch a named target (a CI run, a pipeline, a build, a log) on a fixed
cadence and apply the triage skill whenever new failures appear. Trigger on
/observe <target> or when the operator asks to keep monitoring something and
fix regressions as they surface. Portable across projects.
---
Keep a target under continuous watch and act only when it degrades. The loop is
the watcher; triage is the response. Do not re-investigate failures already
resolved this session, and do not idle-poll work the harness will notify you
about.
## Before you start: fix the target
The operator names one concrete target: a CI run id or url, a pipeline, a
branch's checks, a build, or a log file. Restate it in one line and record the
current failure set as the baseline (the failures already known or already
fixed this session). Everything the watch reports is diffed against that
baseline, so a stale baseline re-triages solved failures.
## The loop
Drive this with `/loop` in dynamic mode (no fixed interval unless the operator
gave one): after each observation, schedule the next wake-up yourself and pass
the same `/observe` prompt back so the next firing repeats the watch.
1. **Poll the target.** Pull its current state (job list, check conclusions, the
log's tail). Enumerate the failures exactly as the `triage` skill's step 1
does: failure, cancelled, timed-out; a downstream aggregate that fails only
because an upstream did is not its own failure.
2. **Diff against the baseline.** A failure is *new* only if it is not in the
baseline and was not already triaged this session. If nothing is new, log one
short line ("still green" / "N known failures, no change") and reschedule.
3. **Triage each new failure.** Invoke the `triage` skill on the new set only —
which applies `dialectic` per failing job to a ~99% root cause and a real
fix. Never mask (no retry-until-pass, no disabled check, no soft-skip). Fold
each newly fixed failure into the baseline so it is not re-triaged.
4. **Reschedule.** Pick the next delay from what you are waiting on: an active
run that changes every few minutes gets a check matched to its cadence; a
quiet or long-running target gets a long fallback (1200s+). Keep watching
until the target reaches a terminal state (run completed, pipeline finished)
AND every failure it produced has a verified fix, then stop the loop.
## Rules
- The loop is a fallback timer, not a busy-wait: if the harness will notify you
when tracked work finishes (a background task, a spawned agent), wait for that
notification instead of polling for it; reserve the timed wake-up for external
state the harness cannot see (a remote CI run, a deploy queue).
- Executed evidence beats reasoning: read the actual failing log line and the
artifact, not the job title — triage and dialectic carry this through.
- One baseline, updated in place: dedup every observation against all failures
seen so far, or a flaky-then-fixed failure reappears every cycle and the watch
never converges.
- Report only on change: a new failure and its triage verdict, or a terminal
"target is green, watch closed". Silence between changes is correct.
- Never invent results for a run still in progress; report what the poll
actually returned.

66
skills/plan/SKILL.md Normal file
View File

@@ -0,0 +1,66 @@
---
name: plan
description: >
Turn a request into an objective-oriented plan and hand it to the robot skill
for autonomous execution. Trigger on /plan or when the operator asks for a
plan, a breakdown, or a strategy for a task before any work starts. Portable
across projects.
---
Produce a plan whose every item is an outcome with a check, then execute it
autonomously. A plan that lists activities instead of objectives cannot be
verified, and an unverifiable plan cannot be handed to a robot.
## Procedure
1. **Interview first.** Open every plan with the `active-listening` skill: ask
until scope, constraints, success criteria, and the operator-only facts are
all pinned, then reflect the understanding back. This is the only point in
the run where questions are allowed, so leave nothing open here.
2. **Pin the objective.** State the end state in one sentence, as a condition
that is either true or false, never as an activity. Name the command or
observation that proves it.
3. **Inspect before decomposing.** Read the code, config, tests, and history the
objective touches. Every plan item must rest on something you have seen, not
on an assumption about how the project works.
4. **Decompose into sub-objectives.** Each item gets: the outcome it reaches,
the verification that proves it, and the items it depends on. Split an item
whenever it needs more than one verification. Order by dependency and mark
the items that are independent, so they can run in parallel.
5. **Resolve the open decisions now.** Any root cause, design choice, or
trade-off the plan rests on gets settled before execution, escalating to the
`dialectic` skill where being wrong is expensive. The robot does not ask, so
an unresolved decision becomes a guess at runtime.
6. **Record the plan as a todo list.** Write the sub-objectives into the
harness's own todo tracking, one entry per item, phrased as the outcome. Keep
a plan file only when the operator asks for one.
7. **Present it and wait.** Show the objective, the ordered sub-objectives with
their verifications, what is deliberately out of scope, and the assumptions
the plan rests on. Stop here: the plan is the deliverable of this step.
8. **Implement it statically, in full.** On the operator's go, write every code,
config, test, and doc change the plan calls for, across all sub-objectives,
before running anything that needs live infrastructure. Verify statically:
read the diff, run the linters, the unit tests, and whatever dry-run or
syntax check the project offers. This step ends only when the whole plan
exists on disk, with no sub-objective left unwritten.
9. **Then iterate under `robot`.** With the static implementation complete,
invoke the `robot` skill with the objective as its goal for the dynamic part:
run, deploy, observe, fix, repeat until every verification passes. It drives
the loop without check-ins, verifies each outcome instead of assuming it, and
reports the items that fall outside its clearance for the operator to run.
## Rules
- One objective per item, in outcome form: "endpoint returns 200 for an expired
token", not "fix the auth middleware".
- No item without a verification. If you cannot name what proves it, the item is
still a wish, not a plan.
- Plan only what the request covers. Speculative future-proofing, refactors
nobody asked for, and abstractions with one caller belong in the out-of-scope
list, not in the plan.
- Static before dynamic: never start the robot loop on a half-written plan. A
deploy that fails on code you had not written yet costs a full cycle and
proves nothing.
- Re-plan on contradicted evidence: when execution disproves an assumption the
plan rests on, revise the plan instead of forcing the original steps through.
- Cite `file:line` for every claim about the current state of the code.

108
skills/refactor/SKILL.md Normal file
View File

@@ -0,0 +1,108 @@
---
name: refactor
description: >
Refactor every file that is currently uncommitted in the git working tree -
staged, unstaged and untracked - up to the project's coding standards,
without changing behaviour. Trigger on /refactor or when the operator asks to
clean up, tidy, or polish the current changes before committing. Portable
across projects.
---
The unit of work is the uncommitted change set, not the repository. Behaviour
stays identical: a refactor that changes what the code does is a bug, not a
cleanup.
## Step 1: fix the file set
Read the set from git, never from memory of what was edited:
```bash
git status --porcelain
```
Take modified, added, renamed and untracked files. Drop deleted files, binaries,
lockfiles, vendored trees and generated output. Restate the resulting list in
one line before touching anything; if it is empty, say so and stop.
Never `git stash` and never revert a working-tree file to compare - other agents
and the operator share this tree. Read the committed version with
`git show HEAD:<path>` into a temporary file instead.
## Step 2: get a safety net first
A refactor is only provably behaviour-preserving if something failed before it
and passes after it. Before editing, check what covers the change set:
- **Coverage exists**: run it now and record the green baseline. A suite that
was already red gets reported, not refactored around.
- **No coverage and the code is testable logic**: write the characterization
tests first, against the current behaviour - including the behaviour you
consider wrong, so the refactor cannot silently change it. Keep them small
and framework-free unless the project already has a framework. Fix the
captured wrongness in a separate change, never in this one.
- **Not sensibly testable**: pure glue, declarative config, a thin wrapper over
a service the test would have to mock into meaninglessness, or a project with
no test setup at all. Say so in one line and refactor without a net; do not
invent a test harness the project never asked for.
The new tests are part of the deliverable and must still pass unchanged after
the refactor. A test that had to be edited to stay green means the behaviour
moved: revert the refactor, not the test.
## Step 3: refactor each file
Read the whole file first, then apply the standards that the surrounding code
and the project's own config already establish (formatter config, linter rules,
existing idioms) over any generic preference of your own.
Four principles govern the result:
- **SRP**: one file, one responsibility; one function, one reason to change. A
unit that needs an "and" to describe it is two units.
- **SPOT**: every fact - a constant, a path, a schema, a rule - lives in exactly
one place and everything else references it.
- **KISS**: the simplest construct that works. No abstraction without a second
caller today, no configuration for a value that never varies.
- **DRY**: the same logic is written once. A literal repeated three times is a
missing constant.
Concretely:
- **File size**: no file containing program code exceeds 250 lines. Split the
overflow along the SRP seam - it is the symptom, not the cause. Description
and markup languages are exempt (Markdown, YAML, JSON, HTML, translation and
data files), as is the rare file the language or framework genuinely forbids
splitting: name that reason in the report, "it was easier" does not qualify.
- **Duplication**: fold repeated logic into the helper that already exists;
create a new one only when two or more call sites need it now.
- **Dead weight**: unused imports, variables, parameters, branches that cannot
be reached, flags with a single value, abstractions with one implementation.
- **Naming**: names that state what a thing is, matching the file's convention.
- **Shape**: split a function that needs a scroll to read; collapse nesting by
returning early; replace magic values with a named constant at its use site.
- **Errors**: never widen a caught exception, never swallow one, never remove a
validation or a security check to make the code shorter.
- **Comments**: invoke the `comments-clean` skill rather than reimplementing its
rules here.
Do not reformat regions you have no substantive fix for - cosmetic churn buries
the real change in the diff. If a file's problem is architectural and cannot be
fixed inside the change set, report it instead of half-doing it.
## Step 4: keep behaviour provable
- Re-run the step 2 net and require the same green result as the baseline.
- Never fold a bug fix, a feature, or a dependency change into a refactor.
Found a real bug? Name it in the report and leave it for a separate change.
- Run the cheap checks the project already has for the touched files (formatter,
linter, the module's own unit tests). Do not start a full suite, a build, or a
deploy on your own initiative - propose it and let the operator trigger it.
- State plainly what was verified and what was not. An unverified refactor is
reported as unverified.
## Step 5: report
One line per file: what changed and why it is safe. Then one line naming what
was deliberately left alone (architectural findings, bugs, files skipped) and
the exact command the operator can run to validate. Do not commit unless the
operator asked for a commit.

38
skills/robot/SKILL.md Normal file
View File

@@ -0,0 +1,38 @@
---
name: robot
description: >
Work fully autonomously to a goal. Trigger when the operator wants hands-off,
uninterrupted execution — the agent drives the task to completion without
check-ins, never stops before the goal is reached, and uses only commands it
is already cleared to run. Portable across projects.
---
Operate as an autonomous robot: given a goal, reach it without hand-holding.
## Contract
- **No questions once the goal is clear.** Clarify only genuinely-blocking
unknowns up front (via the `active-listening` skill), then proceed without
further check-ins. Pick the cleanest option and continue; never pause to
confirm a choice you can make yourself.
- **Never stop before the goal.** Do not end the run, hand back, or declare done
while any part of the goal is unmet — premature termination is forbidden. A
blocker is something to solve or route around, not a reason to quit; if one
step is genuinely, externally impossible, surface exactly why and keep working
on everything else until nothing solvable remains.
- **Stay inside your clearance.** Use ONLY commands you are pre-authorized to
run. Never invoke anything the agent or tool settings mark as denied or as
requiring per-invocation approval (ask) — no commit, push, deploy, or other
approval-gated action unless the operator has already cleared it for this run.
When a needed action is out of clearance, do all the cleared work and report
the single blocked step for the operator to run.
- **Verify, do not assume.** Reaching the goal means proving it: run it, read the
output, confirm the end state. Never report success on a step you did not
actually verify.
## Rules
- Escalate an uncertain root cause or design decision to the `dialectic` skill
rather than guessing; uncertainty is never a reason to stop.
- Sustain the loop across long, multi-step work — the run is finished only when
the whole goal is verifiably met.

95
skills/test-fix/SKILL.md Normal file
View File

@@ -0,0 +1,95 @@
---
name: test-fix
description: >
Run the project's Makefile test targets in a loop - run, diagnose, fix, re-run -
until every selected target is green or the loop is provably stuck. Trigger on
/test-fix or when the operator asks to make the tests pass, fix the failing
tests, or get the suite green. Portable across projects.
---
Same target discovery and selection as the `test` skill - invoke it for step 1
and step 2 rather than reimplementing the grep and the selection list. This
skill owns what happens after the first red result: the fix loop.
The contract is narrow: the code becomes correct, the tests stay honest.
## The loop
One iteration is: run -> read the failure -> one root cause -> one fix ->
re-run. Never batch several speculative fixes into one iteration; a green run
after three simultaneous changes proves nothing about which one mattered.
### Run
Run the failing target alone, not the whole selection - the fastest command
that reproduces the failure. Widen back to the full selection only when that
target is green.
### Diagnose
Read the actual error output before touching a file. Apply the `triage` skill
to reach a verified root cause; for a failure whose cause is genuinely unclear
after one look, apply `dialectic` rather than guessing twice.
Decide explicitly which side is wrong, and say so in one line before editing:
- **Code is wrong**: the test states the intended behaviour. Fix the code.
- **Test is wrong**: the test encodes an outdated or incorrect expectation.
Fixing it is allowed *only* with a stated reason for why the old expectation
was wrong - never because the code disagrees with it.
- **Environment is wrong**: missing dependency, stale container, absent env var,
unbuilt artefact. Fix the environment or report it; do not patch code around
a broken environment.
### Fix
Smallest change that addresses the root cause. Follow the project's existing
idioms and the `no-defaults` and `comments-clean` rules.
Forbidden, in every iteration, without explicit operator approval:
- deleting, skipping, `xfail`-ing, or commenting out a failing test
- loosening an assertion to whatever the code currently produces
- catching or swallowing the exception the test was written to surface
- adding retries, sleeps, or reruns to paper over a flaky failure
- disabling a linter rule, a type check, or a test target from the selection
Any of these is a report item, not a fix. A suite that is green because the
failing test no longer runs is a regression disguised as success.
### Re-run
Re-run the same target. Then, once it passes, re-run every target that was
already green - a fix that breaks a neighbouring suite is not a fix. Only
after the full selection is green is the loop done.
## Stopping
Stop and report, without asking for permission to stop:
- **Green**: full selection passes. Report and stop.
- **No progress**: the same failure survives 3 iterations, or the failure count
stops falling across 3 iterations. Report the root cause reached so far, what
was tried, and why each attempt failed.
- **Fix exceeds the mandate**: the real fix is an API change, a dependency bump,
a schema migration, or a redesign. Name it, show the minimal diff it would
need, and let the operator decide.
- **Oscillation**: fixing A re-breaks B and vice versa. Report both with the
conflict between them - that is a design problem, not a test problem.
- **Flaky**: a target passes and fails without any change between runs. Report
it as flaky with both outputs; never "fix" it by rerunning until green.
Never loop silently. Emit one line per iteration: target, failure, hypothesis,
change made.
## Report
- Per iteration: what failed, the root cause, the fix, the result.
- Final state of every selected target, pass or fail, with the verbatim output
of anything still failing.
- Everything deliberately not done: tests judged wrong but left alone,
environment issues, out-of-mandate fixes.
- The exact command reproducing the final state.
State calibrated confidence in the fixes per the `confidence` skill. Do not
commit unless the operator asked for a commit.

77
skills/test/SKILL.md Normal file
View File

@@ -0,0 +1,77 @@
---
name: test
description: >
Discover the test targets of the project's Makefile, let the operator pick
which ones to run, then run them and report. Trigger on /test or when the
operator asks to run the tests, the test suite, or a specific test target
without naming the exact command. Portable across projects.
---
The Makefile is the source of truth for how this project runs tests. Never
invent a command (`pytest`, `npm test`, `go test`) while a Makefile target
exists - the target carries the project's env vars, build dependencies and
container setup.
## Step 1: find the targets
From the repository root:
```bash
grep -nE '^test[A-Za-z0-9_.-]*:' Makefile
```
Read the matched rules plus their prerequisites, so the selection list can say
what each one actually does (delegated script, container build, sub-targets).
Edge cases, handled explicitly rather than guessed around:
- **No Makefile**: say so in one line, name the test runner the project does
use (from `pyproject.toml`, `package.json`, `tox.ini`, CI workflow), and ask
before running anything.
- **No `test*` target**: report the targets that do exist and stop.
- **Included makefiles** (`include foo.mk`): grep those too.
- **Aggregate targets**: a target whose recipe is only other test targets (e.g.
`test: test-unit test-integration`) is the "run everything" entry - mark it as
such in the list, do not expand it into its parts silently.
## Step 2: offer the selection
Ask with `AskUserQuestion`, `multiSelect: true`, one question. Options are the
discovered targets, each with a one-line description of what it runs. Order the
aggregate/full target first and label it as the complete suite.
The tool takes at most 4 options. With more targets than that:
- print the **complete** discovered list as text first, one line per target, so
nothing is hidden, then
- offer the 4 most useful entries (aggregate first, then the ones matching the
operator's stated intent or the files currently uncommitted), and note that
"Other" accepts any target name from the printed list.
Never silently truncate. If the operator already named a target in their
request, skip the question and run it.
## Step 3: run
Run the selected targets in the order the operator listed them, one `make`
invocation per target, each as its own command so a failure is attributable:
```bash
make <target>
```
Do not add `-k`, do not reorder, do not substitute a faster equivalent. If a
target needs a long timeout (container builds, e2e), set it on the Bash call
rather than backgrounding blindly.
Stop after the first failing target unless the operator asked for all of them -
a later suite running against a broken build produces noise, not information.
## Step 4: report
Per target: pass/fail plus the failing test names and the exact error output,
quoted verbatim. Never paraphrase a failure. Then one line with the exact
command to reproduce the failure alone.
Do not fix what failed unless the operator asks - report first. If the operator
does ask for a fix, apply the `triage` skill to it.

57
skills/triage/SKILL.md Normal file
View File

@@ -0,0 +1,57 @@
---
name: triage
description: >
Triage a failing CI run, pipeline, or job set to a verified root cause and a
real fix. Trigger when a build, test, or deploy pipeline has one or more failed
jobs. Applies the dialectic skill to every failing job independently. Portable
across projects.
---
Drive a red pipeline to green by root cause, not by guesswork. Treat every
failing job as its own investigation, and do not declare a fix until that job's
root cause is proven.
## Procedure
1. **Enumerate every failing job.** Pull the run's job list and isolate each job
whose conclusion is failure, cancelled, or timed-out. A downstream aggregate
job that fails only because an upstream job did is not a separate root cause —
note it and move on.
2. **Cluster the failures by error message.** Group the failing jobs by their
actual failing line, not by job name. Jobs whose messages differ in anything
but the interpolated identifiers (host, app, port, path) belong in separate
clusters.
3. **Download artifacts per cluster.** For each cluster, fetch every artifact of
one representative job: reports, rescue diagnostics, inventories, container
logs. Pull a second member's artifacts only when the representative's
evidence does not explain the whole cluster. The job log usually names an
artifact path and nothing more; the assertion text, the server-side
exception and the state dumps are inside the artifact. Do this even when the
log looks conclusive, because a log that explains the symptom rarely
explains the cause.
4. **Dialectic per cluster.** For EACH cluster, invoke the `dialectic` skill:
form a thesis about the root cause from evidence (the job log, its artifacts,
the code at the run's commit, git history), attack it with independent
skeptics, and iterate to a ~99% thesis. The clusters are independent, so run
their investigations in parallel where the tooling allows.
5. **Distinguish shared vs hidden causes.** A cluster is a hypothesis, not a
proof: if the dialectic shows one cluster splitting into two causes, split it.
When a job hides a second failure behind the first, keep going until the job
is actually green, not just past the first error.
6. **Fix at the root.** Apply the real fix in the repository for each proven root
cause. Never mask a failure — no retry-until-pass, no disabling the check, no
soft-skip. If a failure is genuinely external (upstream outage, flaky infra),
confirm that with evidence and surface it honestly instead of fixing around
it.
7. **Follow the run.** While the run is still in progress, re-check it
periodically and triage each newly failed job as it appears. The run is done
only when it has finished and every failure has a verified fix.
## Rules
- Executed evidence beats reasoning: read the actual failing log line and the
artifact, not the job title.
- Classify each failure: a real defect (fix it), a flake or infra hiccup (prove
it is transient before dismissing), or already-fixed-by-a-later-commit (verify
the fix is present in the run's commit before claiming it).
- Report a per-job verdict: root cause, the evidence that proves it, and the fix.

92
tests/test_autotune.py Normal file
View File

@@ -0,0 +1,92 @@
"""Validate the autotune reminder hook and its settings registration."""
from __future__ import annotations
import json
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SKILL = REPO_ROOT / "skills" / "autotune" / "SKILL.md"
HOOK = REPO_ROOT / "skills" / "autotune" / "hooks" / "reminder.sh"
PATCHER = REPO_ROOT / "scripts" / "enable-plugins.js"
def _run_hook(stamp: Path, interval: str = "3600") -> subprocess.CompletedProcess:
return subprocess.run(
["bash", str(HOOK)],
capture_output=True,
text=True,
check=True,
env={
"PATH": "/usr/bin:/bin",
"HOME": str(stamp.parent),
"AUTOTUNE_STAMP": str(stamp),
"AUTOTUNE_INTERVAL_SECONDS": interval,
},
)
class TestAutotuneSkill(unittest.TestCase):
def test_skill_is_trigger_only(self):
text = SKILL.read_text(encoding="utf-8")
self.assertIn("name: autotune", text)
self.assertIn("multiSelect: false", text)
class TestAutotuneHook(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.stamp = Path(self.tmp.name) / ".claude" / ".autotune-stamp"
def test_first_run_hints_and_stamps(self):
result = _run_hook(self.stamp)
self.assertIn("/autotune", result.stdout)
self.assertTrue(self.stamp.is_file())
def test_second_run_is_silent_within_interval(self):
_run_hook(self.stamp)
self.assertEqual(_run_hook(self.stamp).stdout, "")
def test_hint_returns_after_the_interval(self):
_run_hook(self.stamp)
self.assertIn("/autotune", _run_hook(self.stamp, interval="0").stdout)
def test_corrupt_stamp_does_not_crash(self):
self.stamp.parent.mkdir(parents=True, exist_ok=True)
self.stamp.write_text("not-a-timestamp\n", encoding="utf-8")
self.assertIn("/autotune", _run_hook(self.stamp).stdout)
@unittest.skipUnless(shutil.which("node"), "node not installed")
class TestSettingsRegistration(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.settings = Path(self.tmp.name) / "settings.json"
def _patch(self) -> dict:
subprocess.run(["node", str(PATCHER), str(self.settings)], check=True, capture_output=True)
return json.loads(self.settings.read_text(encoding="utf-8"))
def test_hook_registered_once(self):
self._patch()
data = self._patch()
entries = json.dumps(data["hooks"]["UserPromptSubmit"])
self.assertEqual(entries.count("autotune/hooks/reminder.sh"), 1)
def test_existing_hooks_preserved(self):
self.settings.write_text(
json.dumps({"hooks": {"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "true"}]}]}}),
encoding="utf-8",
)
data = self._patch()
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 2)
if __name__ == "__main__":
unittest.main()