evolve-crux — ship a change to crux, rigorously
This skill is for working on crux the tool (your clone of the crux repo), not on a research project that uses crux. It carries one change — a new capability or a fix for something crux keeps getting wrong — through a strictly gated arc:
ideate → build → validate → ship
Each stage has an exit criterion; you do not advance until it's met. The arc is the
same whether the change is a one-line bug fix or a .spec/ epic — only the ceremony scales.
Who's driving (contributor-always)
Assume the person evolving crux is not the maintainer. Because crux ships this skill to its users, everyone runs the same path and ends at a pull request — no auto-detection of who you are, no privileged branch. The maintainer is just the contributor who can self-merge their own PR, plus a small, separate release step (below). This keeps one code path and makes third-party PRs first-class.
Work in a dedicated session inside your crux clone (a fresh chat, not the project you
were researching). No clone yet — e.g. you installed the skills via npx skills add?
git clone https://github.com/mehdiforoozandeh/crux first. For a large build you may
dispatch a worktree sub-agent, but the default is the main thread.
1 · Ideate → a signed-off PRD ◆
Ideation ends when there is a PRD the human has approved — not before any code. Reach
it by a pingpong / grill-me back-and-forth (surface one decision at a time; don't dump
options): what exactly are we adding or fixing, why, the design choice, and — load-bearing
— the acceptance criteria (the concrete checks that later become the validation
gate). Pre-registering "how we'll know it worked" is what makes validate enforceable
instead of vibes.
The PRD scales to the change:
- Bug ("crux keeps mis-splitting inline parens") → a one-line PRD: the wrong behavior, the correct behavior, and the acceptance criterion = a failing selftest that asserts the correct behavior.
- Feature / backlog epic (UI, wiki, autoresearch) → a full grilled PRD (skeleton below).
Feature ideas frequently come from the backlog in .spec/ — one spec document per epic,
indexed by .spec/README.md. If the change advances an epic, say so,
work from that spec's Acceptance criteria, and flip the item's status when it lands.
A spec is upstream of a PRD, not a substitute for one: the spec says what and why for a whole epic, the PRD carries one shippable change through the gate. One spec normally becomes several PRDs. Check the spec's Open questions before writing the PRD — an unresolved one is a decision the PI still owes you.
PRD skeleton (Markdown; the exact text becomes the PR body):
# PRD: <short title>
- **Kind:** feature | fix | refactor - **Spec:** <.spec/NN-name.md, or —>
- **Problem / motivation:** what's missing or wrong, and why it matters.
- **Design decision:** the approach chosen (and the main one rejected + why).
- **Scope:** what this change does *not* do.
- **Acceptance criteria:**
- [ ] <criterion → a selftest assert> # automatable
- [ ] <criterion → manual check> (manual: …) # un-automatable (GUI, Obsidian render)
- **Backward-compat / migration:** does this change vault format? (if yes → §3 gate 4)
Exit criterion: the human has read and approved the PRD as one block.
2 · Build — tests-first ◆→○
The moment the PRD is signed off, translate its acceptance criteria into the harness crux already has:
- Write the asserts first (red). Add new cases to
skills/crux/scaffold/selftest.py— one per automatable acceptance criterion — and runpython skills/crux/scaffold/selftest.py; the new ones should fail. (For a bug, this failing test is the reproduction.) - Implement to green. Edit
skills/crux/scaffold/(engine.py/crux.py/render.py/templates/) until selftest is fully green — new asserts included, none regressed. - Un-automatable criteria (a GUI looking right, Obsidian rendering) stay as a manual
checklist carried in the PRD; you'll walk them by hand in
validate.
Hard constraint — stdlib-only. The engine takes no third-party dependency. If a criterion seems to need one, that's a design problem to raise, not a dep to add.
Exit criterion: selftest green with the new asserts, and code matches the PRD's design decision (not a different one you drifted into).
3 · Validate — the gate ○
A fixed gate; all four must pass before ship. Nothing here is judgment — it's a checklist.
Selftest green.
python skills/crux/scaffold/selftest.py→ all pass, and the count has grown by your new asserts (a feature that added no assert didn't really register its criteria).This includes the agent evals (
.spec/10), but only their deterministic half: fixture certification, the manifest schema, the scorer's arithmetic on canned submissions, and the mutation harness. All of it runs offline from a fresh clone with no API key, which is the property that makes a gate a gate. Scoring a live agent submission never gates — a submission exists only after someone ran an agent, attended, and a check a third-party contributor cannot run is not a checklist item. Run those by hand withpython skills/crux/scaffold/evals.pywhen you change an agent definition or a prompt.Stdlib-only. Grep the diff for imports; fail on any module outside the Python stdlib.
git diff -U0 -- skills/crux/scaffold | grep -E '^\+\s*(import|from) ' | grep -vE '<stdlib names>'Existing vaults still load. Run the engine's read paths —
status,review,validate, and a render — against a vault copy and confirm nothing breaks:- if a real vault is discoverable (e.g. a
cruxvault/in a nearby project), copy it to a scratch dir and validate against the copy — never mutate the original; - otherwise fall back to the committed synthetic fixture
skills/crux/examples/demo_vault/(copy it out first). This synthetic vault is the default gate — it ships in the repo, so every contributor can run it; the real-vault check is an optional local upgrade.
- if a real vault is discoverable (e.g. a
Version / migration. Only if the change alters vault format or verdict/roll-up/view logic: bump
ENGINE_VERSIONinengine.py, and prove an old-format vault migrates or still reads cleanly (the drift warning fromcheck_and_stamp_versionis expected; a crash or wrong verdict is a fail). If format is unchanged, leaveENGINE_VERSIONalone and note "no migration."
Then walk any manual acceptance criteria from the PRD and tick them.
Exit criterion: all four gates pass + every PRD acceptance criterion is ticked. If any fails, go back — do not proceed to ship.
4 · Ship — open a PR ◆
Ship happens only after §3 fully passes. For everyone, ship ends at a green pull request whose body is the PRD:
- Add an Unreleased entry to
CHANGELOG.md(create it if absent): one line under## [Unreleased]describing the change. Contributors do not bump the version or tag. - Branch, commit, push to a fork, open the PR:
gh repo fork mehdiforoozandeh/crux --remote # first time only; skip if you have push git switch -c evolve/<slug> git add -A && git commit -m "<kind>: <short title>" git push -u origin evolve/<slug> gh pr create --title "<kind>: <short title>" --body-file <the PRD>.md - The PR self-documents: reviewer checks the diff against the PRD's pre-registered acceptance criteria and the green selftest — not reverse-engineered intent.
Exit criterion: a PR is open, selftest green in it, PRD as the body.
Maintainer-only: self-merge + cut a release
The maintainer runs the identical arc, then:
- Self-merge their own PR once the gate is green (they may push/merge to
main). - Cut a release as a separate, occasional action — decoupled from any single feature,
not once per PR: roll the accumulated
## [Unreleased]CHANGELOG entries into a version section,git tag vX.Y.Z, push tags. Where/cruxis installed as a symlink into a clone (./install.sh), merging already updates the live skill — no reinstall; npx-installed copies are frozen and update vianpx skills update.
Guardrails
- The gate is non-negotiable. No shipping on an un-green selftest, a new dependency, a broken existing vault, or a format change without a version bump + migration proof.
- Tests before code. Acceptance criteria become asserts first; if a criterion can't be expressed as a check (assert or manual), the PRD isn't done.
- Don't hand-edit generated views in the fixtures (
META.md,EXPERIMENTS.md, ledger blocks) — regenerate them via the engine so the fixture stays honest. - One change per PR. A feature and an unrelated fix are two arcs, two PRDs, two PRs.
- Scope is crux the tool. This skill edits the crux repo clone. It never touches a user's research vault or project code — those are what crux (and this change) serve.