Compare commits
10 Commits
cd32841847
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 478ef1ac9f | |||
| 1e90ac1737 | |||
| 30bdc7c516 | |||
| 89e088b6f4 | |||
| 168a307e4f | |||
| c0f813b792 | |||
| 8f231d49e7 | |||
| 545c213f88 | |||
| 04f24bb16d | |||
| 26585a86fc |
@@ -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.
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
141
skills/dream/SKILL.md
Normal 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
40
skills/dream/hooks/reminder.sh
Executable 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
40
skills/optimize/SKILL.md
Normal 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
66
skills/plan/SKILL.md
Normal 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
95
skills/test-fix/SKILL.md
Normal 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
77
skills/test/SKILL.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
108
tests/test_dream.py
Normal 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
29
tests/test_optimize.py
Normal 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()
|
||||
Reference in New Issue
Block a user