Compare commits

...

10 Commits

Author SHA1 Message Date
478ef1ac9f deleted file
Some checks failed
🧪 Test / 🧪 Lock + lint (push) Has been cancelled
🔄 Update / 🧠 Update skills-lock.json (push) Has been cancelled
2026-08-10 17:14:39 +02:00
1e90ac1737 updated lock
Some checks failed
🧪 Test / 🧪 Lock + lint (push) Has been cancelled
2026-08-10 17:13:29 +02:00
30bdc7c516 feat(optimize): share the efficiency objective
autotune, autoskill and confidence each carried their own idea of what an
improvement is, and autotune's two efficiency rules were the same sentence
twice. They now route to one skill that names three axes to move at once —
fewer tokens, less time, higher quality — and the rules that decide when a
proposal counts: at least one axis better, none damaged, quality never the
currency, observed friction instead of a guessed percentage.

Applying its own rule, autotune gets shorter rather than longer. The test
holds the coupling, so a later rewrite cannot silently drop a route.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:41:52 +02:00
89e088b6f4 feat(autotune): persist skills to repo and mirrors
A skill written only under .claude/skills is lost on the next install, and
one written only into the skills repository is not live until the next
restart, so both writers now do both: locate the operator's skills
repository through an ordered first-hit lookup, write the source of truth
there, then mirror the whole skill directory into the live locations so
hooks and references come along.

The lookup ends in a question to the operator rather than a silent skip
when no repository matches. autoskill routes to the same section instead
of carrying its own copy of the paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:41:51 +02:00
168a307e4f feat(dream): add reflection skill and 12h reminder
Replay the last 24h of a repository — git log and reflog, CI, reviews,
memory, session transcripts — score every action as held, broke or unknown
against its evidence, harden the resulting hypotheses with dialectic, and
persist what survives. The objective is a measurably more confident next
run: a class of error counts as closed only once a test, check, hook or
written rule prevents it.

Derived improvements to the code and to the agent instructions are offered
as single-select questions, and the run ends in autotune and autoskill.

The reminder hook stamps into each repository's git dir, so every repo
carries its own twelve-hour cadence and the hook stays silent outside a
git repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:41:51 +02:00
c0f813b792 feat(plan): add objective-oriented planning skill
Some checks failed
🧪 Test / 🧪 Lock + lint (push) Has been cancelled
🔄 Update / 🧠 Update skills-lock.json (push) Has been cancelled
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
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
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
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
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
16 changed files with 716 additions and 34 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
```
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.
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 two reminder hooks are registered `autotune` points you at `/autotune` at most once an hour, `dream` points you at `/dream` at most once every twelve hours per repository (stamped in that repository's git dir). Existing settings are preserved.
Restart your agent afterwards so it loads the new skills.

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node
// Enable the caveman and ponytail plugins in a Claude Code settings.json so
// their SessionStart hooks auto-activate both modes, and register the autotune
// reminder hook. Merges non-destructively: existing marketplaces, plugins and
// hooks are preserved, unparseable files are left untouched.
// and dream reminder hooks. Merges non-destructively: existing marketplaces,
// plugins and hooks are preserved, unparseable files are left untouched.
"use strict";
const fs = require("fs");
@@ -19,6 +19,7 @@ const MARKETPLACES = {
ponytail: { source: { source: "github", repo: "DietrichGebert/ponytail" } },
};
const PLUGINS = { "caveman@caveman": true, "ponytail@ponytail": true };
const REMINDERS = ["autotune", "dream"];
let data = {};
if (fs.existsSync(settingsPath)) {
@@ -39,15 +40,16 @@ for (const [name, entry] of Object.entries(MARKETPLACES)) {
}
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) {
const alreadyRegistered = JSON.stringify(data.hooks.UserPromptSubmit);
for (const skill of REMINDERS) {
if (alreadyRegistered.includes(`${skill}/hooks/reminder.sh`)) continue;
const reminder = path.join(path.dirname(settingsPath), "skills", skill, "hooks", "reminder.sh");
data.hooks.UserPromptSubmit.push({
hooks: [{ type: "command", command: `bash ${JSON.stringify(reminder)}` }],
});
}
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n");
console.log(`skills: enabled caveman + ponytail plugins and the autotune reminder in ${settingsPath}`);
console.log(`skills: enabled caveman + ponytail plugins and the ${REMINDERS.join(" + ")} reminders in ${settingsPath}`);

View File

@@ -11,7 +11,7 @@
"source": "JuliusBrussee/caveman",
"sourceType": "github",
"skillPath": "skills/caveman/SKILL.md",
"computedHash": "d18cdf73a5f5c496d681a43a6846336b110223bdffa6817b8992529c57f1a815"
"computedHash": "59e1fe0d3eeb4189ee5c467efde567672e5cacb41f157c477a6152ca907d44ea"
},
"caveman-commit": {
"source": "JuliusBrussee/caveman",
@@ -23,13 +23,13 @@
"source": "JuliusBrussee/caveman",
"sourceType": "github",
"skillPath": "skills/caveman-compress/SKILL.md",
"computedHash": "84517cd4cf7a49d8d2bc1baf00f61bc359306c6ad9389756b6937eff958bd374"
"computedHash": "52f2301832b376a765b0ed02445c8bf05874b624052bf9a9c3861d9c3dfcee4b"
},
"caveman-help": {
"source": "JuliusBrussee/caveman",
"sourceType": "github",
"skillPath": "skills/caveman-help/SKILL.md",
"computedHash": "dd85267e76baad76995157e7b9f762dfa557cd58951ee92af0c283f48aa26537"
"computedHash": "c76fd4aa86ad557eee62aacbd4b9dd46499fe3913910e7d596d20d094296b984"
},
"caveman-review": {
"source": "JuliusBrussee/caveman",
@@ -41,7 +41,7 @@
"source": "JuliusBrussee/caveman",
"sourceType": "github",
"skillPath": "skills/caveman-stats/SKILL.md",
"computedHash": "47ce2de3d6cb39a75047b5c962e4eb3da15594e7397c94103e9a104d42626553"
"computedHash": "57c7db449641379e2afd1389fdb17cec8f33d9c8e25b1d28093937b4431895e8"
},
"ponytail": {
"source": "DietrichGebert/ponytail",

View File

@@ -7,6 +7,10 @@ description: >
and the pattern is general enough to reuse. Portable across projects.
---
Its objective is the `optimize` skill's: a skill is worth creating only when it
cuts tokens or time and raises quality — judge every candidate against that
skill's three axes and rules.
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:
@@ -18,10 +22,12 @@ vary), do the following:
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.
- anything else: persist it exactly as the `autotune` skill's
**Persistence** section specifies — locate the operator's skills
repository, write `skills/<name>/SKILL.md` there (or in the current
project for project-specific behavior), then mirror the skill directory
into `~/.claude/skills/` and `~/.agents/skills/`, plus the project's
`.claude/skills/` and `.agents/skills/` when it is project-specific.
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.

View File

@@ -13,6 +13,10 @@ 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.
Its objective is the `optimize` skill's: every proposal must cut tokens or time
and raise quality — follow that skill's three axes and rules, and drop any
candidate that moves none of them.
## Trigger discipline
- Run **only** when the operator asks: `/autotune`, "autotune", "tune the
@@ -65,10 +69,35 @@ 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.
## Persistence
A skill that exists only under `.claude/skills` is lost on the next install, and
one that exists only in the repository is not live until the next restart — so
every skill is written to **both**, in this order:
1. **Locate the operator's skills repository.** Take the first hit: the
`SKILLS_REPO` environment variable; the current repository, when it holds
`skills-lock.json` and a `skills/` directory; `pkgmgr path skills`; a git
checkout named `skills` under the usual roots
(`~/Repositories/*/*/skills`, `~/git`, `~/src`, `~/Projects`) carrying
`skills-lock.json`. If none matches, ask the operator for the path with a
single-select question — never skip the repository and write only the live
copies.
2. **Write the source of truth**: `skills/<name>/SKILL.md` in that repository
(portable skills), or `skills/<name>/SKILL.md` in the current project
(project-specific ones). This is the copy that gets committed.
3. **Mirror it live**, so it works without a reinstall: copy the skill's whole
directory to `~/.claude/skills/<name>/` and `~/.agents/skills/<name>/`, and
for a project-specific skill additionally to that project's
`.claude/skills/<name>/` and `.agents/skills/<name>/`. Copy the directory,
not just `SKILL.md`, so hooks and references come along.
Report all written paths. Deleting or renaming a skill follows the same list in
reverse — remove the mirrors too, or the old name keeps firing.
## 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.
- Write the skill to every location the **Persistence** section lists.
- 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.
@@ -86,7 +115,6 @@ the changed lines in that confirmation.
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.
- The `optimize` skill's rules decide what counts as an improvement: at least
one axis better, none damaged, and the claim named as observed friction rather
than a guessed percentage.

View File

@@ -27,3 +27,9 @@ Calibration rules:
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.
Objective: the `optimize` skill's. The number exists to save the operator a
verification round trip (time), to stop a rework cycle before it starts
(quality), and to replace a long hedging paragraph with two figures and their
residuals (tokens). A confidence block that costs more than it saves — padding,
repeated caveats, a number without residuals — misses its own objective.

141
skills/dream/SKILL.md Normal file
View File

@@ -0,0 +1,141 @@
---
name: dream
description: >
Consolidate the last 24 hours of work in the current repository: what the
agent did, where it was right, where it was wrong, and which hypotheses
follow. Every hypothesis is hardened with the dialectic skill, the survivors
are written to memory, the derived improvements to the repository's code and
to its agent instructions (CLAUDE.md, AGENTS.md) are offered as single-select
questions, and the run ends in autotune and autoskill. The objective is to
raise the confidence of future runs until those mistakes stop recurring.
Trigger on /dream, or
when the operator asks to reflect on, consolidate, or learn from recent work.
Portable across projects.
---
Sleep on the work: replay what happened in this repository, separate what held
from what broke, and keep only the conclusions that survive an attack. A dream
that flatters the dreamer is worthless — an unverified success counts as
unknown, not as a win.
## Objective
The dream exists to raise the confidence of every future run in this repository
until the mistakes it found cannot happen again. Judge every finding, memory
entry, and proposal by one question: does it make the next run measurably less
likely to repeat this failure? What changes nothing about future behaviour is
not worth recording.
A class of error counts as closed only when something structural prevents it —
a test, a lint rule, a CI check, a hook, a written rule in the agent
instructions, or a skill. "Be more careful next time" closes nothing. Report the
confidence the next run can now hold on each closed class, calibrated as the
`confidence` skill requires, and name what is still open.
## Scope
Everything is read from the repository the skill runs in (`git rev-parse
--show-toplevel`); never mix in another project's history or memory. The default
window is the last 24 hours — the operator may widen it (`/dream 7d`). State the
repository and the window in one line before you start.
Run only when the operator asks. The 12-hour reminder hook
(`hooks/reminder.sh`, stamped per repository in its git dir) is a hint for the
operator, not a trigger: print it and continue with their actual request.
## Step 1: recall
Gather the window's actual record, not your recollection of it:
- **History**: `git log --since` across all branches plus `--reflog`, with
`--stat`; the reflog carries the resets, rebases, and amends the log hides.
- **Working state**: `git status`, the staged and unstaged diff, stashes.
- **CI**: the runs created in the window, their conclusions, and the failing
logs of the red ones — the failing log line, not the job title.
- **Reviews**: pull requests and issues touched in the window, and the review
comments on them.
- **Memory**: this project's memory directory and its `MEMORY.md` index — the
findings already recorded, so the dream extends them instead of repeating them.
- **Instructions**: `CLAUDE.md`, `AGENTS.md`, and the project's own skills, to
see which rule a mistake actually violated.
- **Sessions**: the readable session transcripts touched in the window, for the
operator corrections that never reached a commit.
## Step 2: score
Classify every action in the window, each with its evidence (`sha`, `file:line`,
run id):
- **Held** — shipped and still proven: green CI, a passing test that covers it,
no revert, no follow-up fix.
- **Broke** — reverted, amended, hot-fixed by a later commit, red CI, or
corrected by the operator. A failure that ended green is still a failure; count
the detour, not only the destination.
- **Unknown** — no verification exists either way. Unknown is a finding in its
own right, never a quiet pass.
## Step 3: hypothesize
Turn the scored record into falsifiable hypotheses, each written as *cause →
predicted consequence → what would disprove it*. Take a pattern (two or more
occurrences) or a single expensive failure; drop the one-off noise. A hypothesis
that nothing could disprove is not a hypothesis — cut it.
## Step 4: dialectic
Put every surviving hypothesis through the `dialectic` skill: thesis,
independent skeptics, synthesis, iterated to ~99%. What survives becomes a
finding. What gets refuted is recorded as discarded **with the refutation**, so
the next dream does not re-derive it.
## Step 5: write memory
Persist the findings in this project's memory directory: one file per fact, one
pointer line in `MEMORY.md`. Update the file that already covers a fact instead
of adding a duplicate, and delete the memories this run disproved. Absolute
dates, never "yesterday". Nothing that the repository already records — code
structure, git history, `CLAUDE.md` — belongs in memory.
## Step 6: offer the improvements
Two kinds come out of the findings:
- **Code** — the change to this repository's source, tests, config, or CI that
the finding calls for.
- **Agent instructions** — the missing or wrong rule in `CLAUDE.md`,
`AGENTS.md`, the project's own skills, or the contributor docs. Every mistake
the agent repeated in the window is a candidate: quote the line that failed to
prevent it, or name the rule that is absent, and propose the exact wording.
Ask for each one with `AskUserQuestion` and `multiSelect: false` so it renders
as radio buttons: the improvement, the evidence behind it, and the options
`Apply now`, `Record as a task`, `Skip`. Ranked by evidence, one question per
improvement. No repository file changes before an answer comes back.
## Step 7: autotune, then autoskill
With the dialectic done and memory written, run `autotune` — hand it this run's
findings as its evidence so it does not re-collect them — and then `autoskill`
for the operator corrections the window revealed. Both keep their own
confirmation steps; the dream never writes a skill on their behalf.
Close the run by refreshing this repository's reminder stamp, so the next hint
is twelve hours out: `date +%s > "$(git rev-parse --absolute-git-dir)/.dream-stamp"`.
## Rules
- Evidence or unknown: a claim about what held needs a green signal, and its
absence is reported as unknown rather than assumed.
- Honest scoring: reverts, amends, and follow-up fixes are failures of the first
attempt even when the final state is green.
- Read-only until step 6: the dream inspects, and writes memory; repository code
changes only on an explicit radio answer.
- An empty window is a valid result — report "nothing in the last 24 hours in
this repository" and stop. Never invent activity to have something to dream
about.
- Prefer the structural fix over the remembered one: a test, check, hook, or
written rule that makes the error impossible beats a memory entry that only
reminds you of it.
- Report the whole ledger: held, broke, unknown, the surviving findings, the
refuted hypotheses with why they fell, and the confidence each closed error
class now carries into the next run.

40
skills/dream/hooks/reminder.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# UserPromptSubmit hook: emit the dream hint at most once per interval and per
# repository. The stamp lives in the repository's git dir, so every repository
# keeps its own cadence; outside a git repository the hook stays silent.
# 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 dream by itself.
set -euo pipefail
interval="${DREAM_INTERVAL_SECONDS:-43200}"
if [[ -n "${DREAM_STAMP:-}" ]]; then
stamp="${DREAM_STAMP}"
else
git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)" || exit 0
stamp="${git_dir}/.dream-stamp"
fi
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'
DREAM HINT: over twelve hours since the last hint for this repository. Show the
operator this notice verbatim as a short block, then continue with their
request. Do NOT run dream, do not start replaying the history, do not ask
questions about it.
💤 /dream - consolidate the last 24h in this repository: what held, what
broke, which hypotheses follow, into memory and improvement proposals.
Runs only when you trigger it.
HINT

40
skills/optimize/SKILL.md Normal file
View File

@@ -0,0 +1,40 @@
---
name: optimize
description: >
The shared objective of the self-improving skills: raise efficiency — fewer
tokens, less time — and raise quality at the same time. Trigger from autotune,
autoskill and confidence, which route their objective here, and on /optimize
when the operator asks how to make the agent's own work cheaper, faster or
better. Portable across projects.
---
Every skill, rule, or report the agent produces about its own working is judged
on three axes at once. A proposal that cannot name the axis it moves is not an
optimization, it is a preference.
## The three axes
- **Tokens** — fewer input and output tokens for the same result: read the range
instead of the whole file, grep the saved log instead of re-running the
command, route to an authoritative doc instead of duplicating it, report in
lines instead of paragraphs.
- **Time** — fewer round trips and less waiting: independent calls in parallel,
one command that answers the question instead of three that circle it, no
polling for work the harness will report on its own.
- **Quality** — fewer errors and less rework: verification instead of
assumption, and learnings that persist so the same mistake is not paid for
twice.
## Rules
- A change must improve at least one axis and damage none. Output that got
cheaper but lost correctness is a regression, not an optimization.
- Quality is never the currency: validation, error handling, tests, and security
steps are not what gets cut to save tokens or time.
- Measure, do not guess: name the observed friction — the re-read, the retry,
the correction the operator had to give twice — and what it cost. A guessed
percentage is not a measurement.
- The cheapest step is the one that does not run: drop work before optimizing
it.
- Persist it or pay again: an optimization that lives only in this session is
re-derived in the next one, so it belongs in a skill or a memory entry.

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.

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.

View File

@@ -17,20 +17,33 @@ root cause is proven.
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. **Dialectic per failing job.** For EACH failing job, 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 jobs are independent,
so run their investigations in parallel where the tooling allows.
3. **Distinguish shared vs hidden causes.** When several jobs share one root
cause, fix it once. When a job hides a second failure behind the first, keep
going until the job is actually green, not just past the first error.
4. **Fix at the root.** Apply the real fix in the repository for each proven root
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.
5. **Follow the run.** While the run is still in progress, re-check 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.

View File

@@ -37,6 +37,27 @@ class TestAutotuneSkill(unittest.TestCase):
self.assertIn("multiSelect: false", text)
class TestSkillPersistence(unittest.TestCase):
"""Both skill writers must persist to the skills repository AND the live dirs."""
def test_autotune_names_every_target(self):
text = SKILL.read_text(encoding="utf-8")
for target in (
"SKILLS_REPO",
"skills/<name>/SKILL.md",
"~/.claude/skills/",
"~/.agents/skills/",
):
self.assertIn(target, text)
def test_autoskill_routes_to_the_same_persistence(self):
text = (REPO_ROOT / "skills" / "autoskill" / "SKILL.md").read_text(
encoding="utf-8"
)
for target in ("Persistence", "~/.claude/skills/", "~/.agents/skills/"):
self.assertIn(target, text)
class TestAutotuneHook(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
@@ -70,7 +91,9 @@ class TestSettingsRegistration(unittest.TestCase):
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)
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):
@@ -81,11 +104,19 @@ class TestSettingsRegistration(unittest.TestCase):
def test_existing_hooks_preserved(self):
self.settings.write_text(
json.dumps({"hooks": {"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "true"}]}]}}),
json.dumps(
{
"hooks": {
"UserPromptSubmit": [
{"hooks": [{"type": "command", "command": "true"}]}
]
}
}
),
encoding="utf-8",
)
data = self._patch()
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 2)
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 3)
if __name__ == "__main__":

108
tests/test_dream.py Normal file
View File

@@ -0,0 +1,108 @@
"""Validate the dream 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" / "dream" / "SKILL.md"
HOOK = REPO_ROOT / "skills" / "dream" / "hooks" / "reminder.sh"
PATCHER = REPO_ROOT / "scripts" / "enable-plugins.js"
def _run_hook(
cwd: Path, stamp: Path | None = None, interval: str = "43200"
) -> subprocess.CompletedProcess:
env = {
"PATH": "/usr/bin:/bin",
"HOME": str(cwd),
"DREAM_INTERVAL_SECONDS": interval,
}
if stamp is not None:
env["DREAM_STAMP"] = str(stamp)
return subprocess.run(
["bash", str(HOOK)],
capture_output=True,
text=True,
check=True,
cwd=str(cwd),
env=env,
)
class TestDreamSkill(unittest.TestCase):
def test_skill_is_trigger_only(self):
text = SKILL.read_text(encoding="utf-8")
self.assertIn("name: dream", text)
self.assertIn("multiSelect: false", text)
class TestDreamHook(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.cwd = Path(self.tmp.name)
self.stamp = self.cwd / ".claude" / ".dream-stamp"
def test_first_run_hints_and_stamps(self):
result = _run_hook(self.cwd, self.stamp)
self.assertIn("/dream", result.stdout)
self.assertTrue(self.stamp.is_file())
def test_second_run_is_silent_within_interval(self):
_run_hook(self.cwd, self.stamp)
self.assertEqual(_run_hook(self.cwd, self.stamp).stdout, "")
def test_hint_returns_after_the_interval(self):
_run_hook(self.cwd, self.stamp)
self.assertIn("/dream", _run_hook(self.cwd, 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("/dream", _run_hook(self.cwd, self.stamp).stdout)
def test_outside_a_repository_it_stays_silent(self):
self.assertEqual(_run_hook(self.cwd).stdout, "")
@unittest.skipUnless(shutil.which("git"), "git not installed")
def test_stamp_is_per_repository(self):
for name in ("one", "two"):
repo = self.cwd / name
repo.mkdir()
subprocess.run(
["git", "init", "-q", str(repo)], check=True, capture_output=True
)
self.assertIn("/dream", _run_hook(repo).stdout)
self.assertTrue((repo / ".git" / ".dream-stamp").is_file())
self.assertEqual(_run_hook(repo).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("dream/hooks/reminder.sh"), 1)
self.assertEqual(entries.count("autotune/hooks/reminder.sh"), 1)
if __name__ == "__main__":
unittest.main()

29
tests/test_optimize.py Normal file
View File

@@ -0,0 +1,29 @@
"""The self-improving skills must share the optimize objective."""
from __future__ import annotations
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SKILLS = REPO_ROOT / "skills"
ROUTERS = ("autotune", "autoskill", "confidence")
class TestOptimizeObjective(unittest.TestCase):
def test_skill_names_the_three_axes(self):
text = (SKILLS / "optimize" / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: optimize", text)
for axis in ("**Tokens**", "**Time**", "**Quality**"):
self.assertIn(axis, text)
def test_routers_point_at_it(self):
for skill in ROUTERS:
text = (SKILLS / skill / "SKILL.md").read_text(encoding="utf-8")
self.assertIn(
"`optimize` skill", text, f"{skill} does not route to optimize"
)
if __name__ == "__main__":
unittest.main()