Compare commits

...

3 Commits

Author SHA1 Message Date
torben b1883befc7 docs: Modellwahl nach Pruefbarkeit; stack-dev bricht an den Phasenwechseln fuer den Model-Switch (4.3.3)
CI / verify (push) Successful in 49s
Release / release (push) Successful in 36s
Files changed:
- CHANGES.md
- VERSION
- instructions/claude-code-model-selection.md
- instructions/dev/stack-dev/SKILL.md
2026-09-03 21:30:54 +02:00
torben 56ecfc7fee docs: stack-dev - Issue-Abschluss als nummerierter Schritt 5, Routing-Blurb rebalanciert (4.3.2, #45)
CI / verify (push) Successful in 47s
Release / release (push) Successful in 36s
Files changed:
- CHANGES.md
- VERSION
- instructions/dev/stack-dev/SKILL.md
2026-09-03 20:46:10 +02:00
torben 4e80a07ac7 docs/ befuellt - Stack-Hintergrund fuer vier Themen, Pflegeklausel in AGENTS.md ergaenzt (4.3.1, #45)
CI / verify (push) Successful in 48s
Release / release (push) Successful in 36s
Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- docs/ownership-and-templates.md
- docs/pipeline-rationale.md
- docs/version-model.md
- docs/why-gates-are-code.md
2026-09-03 19:40:39 +02:00
9 changed files with 536 additions and 24 deletions
+7
View File
@@ -275,3 +275,10 @@ and `tools/README.md` are part of the change that introduced a stage, a command
not follow-up work: nobody comes back for them, and a document that describes a repo which no not follow-up work: nobody comes back for them, and a document that describes a repo which no
longer exists is worse than none. The mechanical half - command tables, contracts, ignore longer exists is worse than none. The mechanical half - command tables, contracts, ignore
canaries - is checked by `tools/wikitool docs verify`; the prose half is yours. canaries - is checked by `tools/wikitool docs verify`; the prose half is yours.
`docs/` pages are held to a different clock than those three. A README goes stale on every new
flag; a `docs/` page goes stale only when the reasoning it wrote down stops holding - a gate
that stops living in code, an ownership line that moves, a boundary redrawn - which is rarer
and not tied to any one commit. Nothing checks this by construction: a page there carries no
normative sentence (see [File naming](#file-naming)), so there is no rule for `docs verify` to
check, only a rationale for a session to notice has gone stale and to update or retire.
+125
View File
@@ -20,6 +20,131 @@ their date-only headings.
--- ---
## 4.3.3 - 2026-09-03 - Modellwahl nach Pruefbarkeit statt nach Aufgabenname; stack-dev bricht an den Phasenwechseln fuer den Model-Switch
**Author:** Torben Nehmer
`instructions/claude-code-model-selection.md` routete bisher nach Skill: eine Zeile "Stack
development -> Opus/high" fuer alles, was `tools/`, `types/` oder `instructions/` anfasst. Das ist
zu grob in beide Richtungen - es verteuert die lange, mechanische Mitte einer Stack-Sitzung, und es
sagt nichts darueber, dass Anfang und Ende derselben Sitzung anders zu behandeln sind.
**Die neue Achse ist "was faengt hier einen Fehler ab".** Wo ein Check in Code steht - `pytest`,
`docs verify`, `instructions verify`, CI, die Gates - kostet der Fehler eines schwaecheren Modells
eine Runde und faellt auf. Wo die einzige Durchsetzung eine Sitzung ist, die Prosa liest, faellt
derselbe Fehler gar nicht auf: er wird ausgeliefert und bleibt stehen. Das ist dasselbe Argument,
das `docs/why-gates-are-code.md` fuer Gates fuehrt, angewandt auf die Modellwahl.
Stack-Entwicklung ist damit **nicht mehr eine Zeile, sondern drei**:
| Phase | Was einen Fehler faengt | Modell |
|---|---|---|
| Design, Versionsstelle, Grenzuebertritts-Urteil | nichts | Opus/high |
| Code, Tests, mechanische Doku-Synchronisation | pytest, CI, `docs verify` | Sonnet/high |
| Issue-Abschluss, `docs/`-Veralterung, Changelog-Prosa | nichts, per Konstruktion | Opus/high |
Die Mitte ist die lange Phase und die mit den Checks - dort liegt die Ersparnis. Die beiden
Raender sind kurz (Minuten, nicht Stunden), haben aber keinen maschinellen Waechter: `wikitool`
kennt den Issue-Tracker bewusst nicht, und eine `docs/`-Seite traegt keinen normativen Satz, also
gibt es dort nichts zu verifizieren. Sie oben zu lassen ist billig und schuetzt genau die Arbeit,
die still scheitert.
Zwei Praezisierungen dazu: **Effort ist der billigere Hebel als das Modell** - `medium` steht fuer
Stack-Arbeit bewusst in keiner Zeile, weil Mehrdatei-Konsistenz das ist, was ein reduzierter
Effort zuerst aufgibt; `high` ist die Untergrenze, sobald mehr als eine Datei oder ein Contract
betroffen ist. Und die Asymmetrie ist benannt: eine unnoetige Opus-Phase kostet einmal Geld, eine
ungepruefte Sonnet-Phase kann etwas ausliefern, das nie wieder jemand ansieht.
**Damit die Tabelle ueberhaupt wirksam wird, braucht sie Haltepunkte.** Eine Sitzung kann ihr
eigenes Modell nicht wechseln - das ist `/model` und gehoert dem Nutzer. Eine Empfehlung, die
niemand zum richtigen Zeitpunkt ausspricht, aendert nichts. `instructions/dev/stack-dev/SKILL.md`
bekommt deshalb zwei ausdrueckliche Breaks:
- **Neuer Schritt 3** - "Settle the design before building", mit dem Angebot zum Wechsel nach
unten, sobald der Plan steht und die Arbeit mechanisch wird. Einmal aussprechen, dann so oder
so weiterarbeiten.
- **Schritt 6 (Abschluss) bricht in die Gegenrichtung** - ab dort greift wieder kein Check. Mit
der ausdruecklichen Auflage, die Arbeit **unabhaengig von der Antwort** zu tun: nach dem Publish
auf einen Modellwechsel zu blockieren wuerde genau den Zustand hinterlassen, den Schritt 6
verhindern soll. Lief die Phase auf dem billigeren Modell, gehoert das in die Uebergabe statt
ins Schweigen.
Ein auftauchender Grenzuebertritt ist unter den Decision points ebenfalls als Anlass zum Wechsel
nach oben benannt: `docs verify` prueft, dass ein Uebertritt sich dokumentiert, nie dass die
Stelle richtig gewaehlt war.
Die uebrigen Schritte sind unveraendert und nur umnummeriert (alt 3-5 -> neu 4-6).
---
## 4.3.2 - 2026-09-03 - stack-dev: Issue-Abschluss ist ein nummerierter Schritt, kein Zeiger in einer Routing-Liste
**Author:** Torben Nehmer
Nachfassen zu 4.1.2 (#44), das die Regel geschaerft, aber den Weg zu ihr nicht geaendert hat.
`instructions/dev/issue-tracking.md` bekam damals Schritt 7 ("Closing is the last body update,
not a comment"); `instructions/dev/stack-dev/SKILL.md` bekam nur eine umformulierte Zeile in
seiner Routing-Liste. Eine Stunde spaeter schloss #45 auf exakt dieselbe Weise: gruendlicher
Abschlusskommentar ueber einem Body mit unangehakten Kriterien.
**Die Ursache lag nicht am Text der Regel, sondern an ihrer Erreichbarkeit.** Die nummerierten
Schritte des Skills endeten bei "Verify before publishing". Ein Issue zu schliessen war ueberhaupt
kein Schritt - es hing an einem Zeiger *innerhalb* von Schritt 2, und Schritt 2 ist eine
Routing-Tabelle aus fuenf "read X before Y"-Eintraegen, keine Checkliste. Eine Sitzung folgt dem
Spine, den sie im Kontext hat; was nur hinter einem Link steht, wird genau in dem Moment nicht
aufgeschlagen, in dem es greift - am Ende einer langen Sitzung, wenn der Kontext am vollsten und
die verbleibende Instruktionsflaeche am duennsten ist.
Verschaerfend arbeitete der Blurb gegen seine eigene Regel: fett gesetzt war "keep it current as
the state moves, **not at the end**". Wer den Body unterwegs ungefaehr gepflegt hatte, las daraus
Konformitaet - der eigentliche Abschlusstest stand nur in der verlinkten Datei.
Geaendert:
- **Neuer Schritt 5 in `stack-dev/SKILL.md`** - "Close the issue with a body rewrite, not a
comment", mit dem Test inline (Kriterien abgehakt oder mit Begruendung gestrichen,
Entscheidungen als entschieden formuliert, kein Praesens ueber einen behobenen Defekt,
Verifikation benannt) und dem Verweis auf Schritt 7 fuer die volle Form. Damit steht der
Abschluss auf dem Spine.
- **Schritt-2-Blurb rebalanciert** - beide Haelften binden jetzt sichtbar: fortlaufende Pflege
*und* der Rewrite vor dem Schliessen, mit Verweis auf Schritt 5.
Nichts davon ist maschinell pruefbar, und das bleibt richtig so: `wikitool` kennt den Tracker
nicht und darf ihn nicht lernen, weil es an Instanzen ausliefert, die kein Board haben
(`issue-tracking.md` § "What no tool checks"). Der Skill-Spine ist die einzige Durchsetzung, die
es geben kann - was der Grund ist, den Schritt zu nummerieren statt ihn zu verlinken.
Verallgemeinerbar: eine Regel, die in eine verlinkte Instruction geschrieben wird, erreicht
Sitzungen nur, wenn die nummerierten Schritte des zustaendigen Skills sie in dem Moment
ansteuern, in dem sie greift.
---
## 4.3.1 - 2026-09-03 - docs/ befuellt - Stack-Hintergrund fuer vier Themen, Pflegeklausel in AGENTS.md ergaenzt
**Author:** Torben Nehmer
Gitea #45: die von #38 angelegte, bis dahin leere `docs/` bekommt ihre ersten vier Seiten - frisch
geschrieben, nicht durch Umzug aus `kb/` befuellt, jede ohne normativen Satz und mit Verweis auf
das bindende Dokument statt einer Wiederholung seiner Regeln:
- `docs/pipeline-rationale.md` - warum `raw -> types/tools -> kb -> reports` vier getrennte Stufen
sind und was "never re-derive, always compile" praktisch bedeutet
- `docs/why-gates-are-code.md` - warum Mass-Update-, Publish-Remote- und Iteration-Budget-Gate in
`tools/wikitool` statt in einer Instruktion stehen
- `docs/ownership-and-templates.md` - der Unterschied zwischen stack-eigenen, verbatim
ausgelieferten Dateien und instanz-eigenen `.template`-Dateien
- `docs/version-model.md` - warum Drop-in-Kompatibilitaet und Migrationsbedarf zwei unabhaengige
Fragen sind, illustriert an der 2.0.0-Fallstudie
**AGENTS.md § Changelog:** neue Klausel zur Pflege von `docs/`, ergaenzt neben der bestehenden
Regel zu `README.md`/`EVALS.md`/`tools/README.md`. Eine `docs/`-Seite veraltet nicht wie ein
README bei jedem neuen Flag, sondern nur, wenn die aufgeschriebene Begruendung selbst nicht mehr
traegt - per Konstruktion ungeprueft, da die Seite keinen normativen Satz enthaelt, den
`docs verify` pruefen koennte.
---
## 4.3.0 - 2026-09-03 - docs/ als ausgelieferter Hintergrund-Ort; Decision-Seiten bleiben in kb/, Decay-Skip fuer concept_type: decision ## 4.3.0 - 2026-09-03 - docs/ als ausgelieferter Hintergrund-Ort; Decision-Seiten bleiben in kb/, Decay-Skip fuer concept_type: decision
**Author:** Torben Nehmer **Author:** Torben Nehmer
+1 -1
View File
@@ -1 +1 @@
4.3.0 4.3.3
+71
View File
@@ -0,0 +1,71 @@
# Ownership and Templates
Chemenu ships two kinds of files side by side, and at a glance they look the same: both are
plain markdown, both sit in the repo root or under `kb/`, both get read at session start. But a
stack upgrade treats them completely differently. Some - [AGENTS.md](../AGENTS.md),
[kb/CONTRACT.md](../kb/CONTRACT.md), the per-stage contracts - are identical in every instance
that runs this stack and can simply be overwritten by the next release. Others - `USER.md`,
`SOUL.md`, `kb/CONVENTIONS.md`, `ENVIRONMENT.md` - describe one particular instance, and
overwriting them would silently erase a choice someone made on purpose.
## Two different kinds of truth
The stack-owned files describe how the tool works. `kb/CONTRACT.md` opens by saying it holds
what `tools/wikitool` enforces or what follows mechanically from how it operates - see
[kb/CONTRACT.md](../kb/CONTRACT.md), lines 10-13. That kind of statement doesn't vary by
instance: the compiler behaves the same way regardless of who is running it, so the sentence
describing that behavior can be copied byte-for-byte into every checkout without becoming
wrong anywhere.
The instance-owned files describe a choice: which language pages are written in, what tone the
agent takes, who the operator is, which git remote is authoritative, which MCP servers are
reachable. None of that follows from the tool's mechanics - two instances of the identical
stack can answer all of these differently and both be correct. [AGENTS.md § Personalization](../AGENTS.md#personalization)
frames the split the same way for `kb/CONTRACT.md` versus `kb/CONVENTIONS.md`: "the split is by
who may change the sentence, not by what it is about." A rule about page structure could in
principle have been written per-instance too, but then every instance answering "not German" to
setup would be hand-editing a file the stack also ships, and the next `dist export` merge would
hand the instance's own file back to it, discarding the customization.
## Why silent overwrite is the failure being designed against
A stack update is meant to be a routine, low-risk operation: pull the latest release, get
whatever fixes and features shipped since the last one. That only stays low-risk if the update
knows which files it's allowed to touch. If `USER.md` or `kb/CONVENTIONS.md` were treated the
same as `AGENTS.md` - shipped and periodically re-copied - an upgrade would quietly replace a
description of *this* operator, in *this* language, with whatever placeholder or default the
stack maintainers wrote. The damage wouldn't be loud: nothing crashes, the files still parse,
the agent just starts acting on the wrong premises until someone notices the voice or the
language changed.
Keeping the boundary at the file level, rather than trying to merge changes within a shared
file, means an upgrade never has to guess which lines are "stack" and which are "instance" -
the file itself already answers that.
## Why a `.template`, not just an absent file
The mechanism for instance-owned content is a `.template` file the distribution ships instead
of the real one - `USER.md.template`, `SOUL.md.template`, `kb/CONVENTIONS.md.template`,
`ENVIRONMENT.md.template`. An alternative would have been to ship nothing at all and let a
brand-new instance start from a blank page. The template exists because a blank page doesn't
tell [instructions/setup-instance.md](../instructions/setup-instance.md) what shape the answer
should take, and it gives nothing for a validator to check afterward.
A template carries a placeholder value - a sentinel - in the fields that need a real answer.
Setup interviews the operator and replaces the sentinel with what they actually said. That
gives `doctor` a mechanical way to tell "personalized" from "not yet": a file that still
contains the sentinel hasn't been through setup, regardless of whether the file exists. That's
also why `ENVIRONMENT.md` only warrants a WARN rather than a FAIL when absent - see
[AGENTS.md § Environment](../AGENTS.md#environment) - while a missing or unfilled
`USER.md`/`SOUL.md`/`kb/CONVENTIONS.md` is a harder failure: `ENVIRONMENT.md` describes one
checkout among possibly several and is gitignored for that reason, so its absence is a normal
state rather than a sign setup was skipped.
## The consequence in practice
Running a stack upgrade against an existing instance boils down to: overwrite the verbatim
files, leave the `.template`-sourced files alone. The verbatim files are safe to replace
wholesale because they were never instance-specific to begin with - identical content going
back in changes nothing an instance actually decided. The template-sourced files were filled in
once, by a person, for a reason, and nothing about a newer release of the stack's mechanics
gives it standing to override that.
+64
View File
@@ -0,0 +1,64 @@
# Why the pipeline has four stages
Chemenu could, in principle, be one directory: drop a file in, ask a question, get an answer
computed fresh each time. It isn't built that way. The pipeline in
[AGENTS.md](../AGENTS.md#routing) - `raw/` -> `[types/ + tools/]` -> `kb/` -> `reports/`, with
`work/` alongside rather than inside it - separates *material* from *meaning* from
*byproduct*, and each seam exists because collapsing it costs something specific.
## Why raw material stays untouched
[raw/CONTRACT.md](../raw/CONTRACT.md) keeps a source exactly as it arrived. The reasoning is
simple once stated: the moment someone "cleans up" or reformats a source on the way in, the
thing later claims get checked against is no longer the thing that was actually said. An
immutable `raw/` means a citation always resolves to the original, not to somebody's tidied
memory of it. It also draws a trust boundary in one place instead of scattering it - everything
past `raw/` can be treated as reviewed, because nothing upstream of it silently already was.
## Why extraction happens once, through a schema
[types/type-spec.md](../types/type-spec.md) is what stands between a raw file and a `kb/` page:
a type-spec defines what a conforming instance of a page looks like, and the compiler
(`tools/wikitool`) applies it. The alternative - every query re-reading and re-interpreting the
source on demand - would mean paying the cost of understanding the material every single time,
and getting a slightly different answer each time depending on how the question was phrased.
Extracting once, against a fixed schema, turns "re-read and re-guess" into "look up what was
already compiled." That is the "never re-derive, always compile" principle from
[AGENTS.md](../AGENTS.md): understanding a source is expensive and worth doing exactly once,
after which it becomes a cheap, stable lookup.
## Why a `kb/` page has to stand on its own
[kb/CONTRACT.md](../kb/CONTRACT.md) sets the bar for the compiled layer: a page should answer a
future question without sending the reader back to the source it came from. That's the payoff
of compiling in the first place - if every answer still bottomed out in "go re-read the raw
file," the `kb/` layer would just be a pointer with extra steps, and the cost of extraction
would have bought nothing. A page that stands alone is what makes the corpus fast and
consistent to query: the work of understanding is already sitting there, done.
## Why `reports/` doesn't need to be maintained
[reports/CONTRACT.md](../reports/CONTRACT.md) treats most of what lands in `reports/` -
lint output, telemetry traces - as disposable. The structural content of a lint report can be
recomputed from the tree at any commit, so keeping an old copy around would just be a second
version of something the tool can already answer on demand, and a second copy is exactly the
kind of thing that quietly goes stale. Treating it as derived output rather than a fourth thing
to maintain means there is nothing there to fall out of sync - regenerating it is cheaper than
reconciling it. The one part that genuinely can't be recomputed - the judgment a pass produced -
is carried out into `kb/` or `kb/log.md` before the report itself is discarded, which is the
distinction between what's recomputable and what isn't.
## Where `work/` fits
[work/CONTRACT.md](../work/CONTRACT.md) describes a workshop, not a fifth pipeline stage: a
place for the notes, extracts and open decisions of a task that spans more than one session, on
its way toward becoming a `kb/` page. It sits beside the raw -> kb -> reports flow rather than
inside it - closer in spirit to a desk than to a conveyor belt.
## The shape this produces
Four stages, each answering a different question: `raw/` - what was actually said; `types/` +
`tools/` - how to turn that into structured understanding; `kb/` - what is now known;
`reports/` - what a pass over the corpus noticed in passing. Keeping them separate is what lets
each one be trusted for what it is, instead of every layer having to double as all four at
once.
+84
View File
@@ -0,0 +1,84 @@
# Why the stack version splits compatibility from migration
A stack version number looks like it answers one question. It actually answers two, and the two
are independent of each other.
## Two questions, not one
The first question is whether the new version is a drop-in replacement for the old one - whether
an existing instance can install it, and can also go back, without anyone doing hand-work. That
is what a version number *is*: a promise. The second question is whether the existing corpus in
`kb/` needs to change shape to keep working under the new version. These sound like the same
question, because most of the time a change that breaks compatibility also happens to touch
content, and most of the time a change that leaves content untouched also happens to be
compatible. The correlation is real; it just is not a law. `instructions/dev/version-parts.md`
carries the actual test for telling them apart and the steps that follow from it - this page is
about why the split exists at all.
## Why "kb/ untouched" is not proof of anything
The tempting shortcut is: if no page in `kb/` had to change, the bump can't be that serious. This
is exactly backwards for a class of changes that live entirely outside the corpus - a renamed
release artefact, a Python import path, an environment variable, the URL an instance's own
updater points at. None of those touch a single page. All of them can strand an existing
instance just as thoroughly as a rewritten type-spec would. The corpus is the part of the stack
that looks at itself; the compatibility question is about everything an instance depends on to
keep functioning, most of which the corpus never sees.
## Reading compatibility off the leftmost non-zero component
Semantic versioning gives every component a job, but only one of them is where an existing
instance's tooling actually looks to decide "is this safe." On a `2.x` stack that is MAJOR; on a
still-pre-1.0 `0.x` stack, by the same convention, it's MINOR - the leftmost slot that isn't
pinned to zero is the one an automated updater treats as the compatibility boundary. Bump
anything to its left, or bump that slot itself, and the promise changes. Everything to the right
of it can move as freely as the project likes without touching that promise. This is why the
question "is it boundary-crossing" always resolves to one specific digit, not to a feeling about
how big the change is.
## Downgrade is half the promise
It's natural to test compatibility by only asking "does the upgrade work." The other half -
"can an instance that upgraded put the old version back and land where it started" - carries
equal weight, and it's the half that's easy to forget because forward motion is what everyone is
testing for anyway. A state file the old version can no longer parse, a generated index in a new
shape, a stamp file that got renamed: none of these have to break the upgrade to break the
downgrade. An instance that can go forward but not back has already lost the property a
compatible version number is supposed to guarantee.
## A promise made to a machine, not only to a person
A human reading a changelog can absorb "this technically isn't compatible but it's fine, just
update those two things by hand." An instance's own update mechanism cannot. It reads a version
number, decides whether to pull the new release, and has no channel for nuance - which is exactly
why the update path itself is one of the sharpest ways to cross the boundary invisibly: if the
new version moves where updates come from, the very channel that would have told an instance to
adjust is the channel that just broke. The version number isn't documentation aimed at a reader;
it's an input consumed by code that has no other way to ask.
## The 2.0.0 story
This isn't hypothetical for this stack. The rebranding that produced Chemenu renamed the repo,
the release artefact, and the Python package - and left every page in `kb/` untouched. The first
instinct was a MINOR bump, on the reasoning that nothing in the corpus needed migrating. That
reasoning was correct on its own terms and answered the wrong question. Three things broke
underneath it: every existing instance's `update_url` pointed at a repo path that no longer
existed and, because it's a machine-written file, couldn't be hand-repaired; the release artefact
name changed, breaking every download script and pin against it; and the import name changed,
breaking anything importing the package from outside the shipped tree. The corpus had nothing to
say about any of this, because none of it lived in the corpus.
What caught the mistake was a person looking at the diff and asking whether it really was a
drop-in replacement, not a validator. No check in `docs verify` or anywhere else confirms that a
version part was chosen correctly - it only confirms that a boundary-crossing bump documents
what it breaks. The 2.0.0 entry in `CHANGES.md` carries the corrected reasoning in full, and the
version bump that shipped it was `--major --no-migration`: boundary-crossing and untouched
corpus, at the same time, which is precisely the combination the two-question split exists to
make visible.
## Where the procedure lives
The drop-in test, the catalogue of changes that cross the boundary with no page touched, and the
steps for a boundary-crossing bump - the `--breaking` line, the migration document or
`--no-migration` reason, talking to the user before bumping - are one procedure, kept at one
place: [instructions/dev/version-parts.md](../instructions/dev/version-parts.md).
+57
View File
@@ -0,0 +1,57 @@
# Why gates are code
Chemenu has three hard limits - the Mass-Update Gate, the Publish-Remote Gate, and the
Iteration Budget Gate - and all three live inside `tools/wikitool`, not in a paragraph of
instructions an agent reads and follows. The rules themselves, and what to do when one trips,
are in [AGENTS.md § Gates](../AGENTS.md#gates) and [instructions/gates.md](../instructions/gates.md).
This page is only about the design choice underneath them: why code, and why these three
mechanisms in particular.
## A suggestion an agent can talk itself past
An instruction like "don't publish too much at once" or "don't loop forever" lives in the same
place as every other piece of guidance a session is holding - alongside the task, the user's
last message, and whatever context made the moment feel urgent. Under pressure, or with a
plausible-sounding reason ("this batch is different, it's mechanical"), that guidance can be
reasoned around without anyone deciding to break a rule. Nothing enforces it; it just competes
for attention with everything else in the context window, and sometimes loses.
A check compiled into the tool doesn't have that problem, because it isn't part of the
conversation at all. It runs before the command dispatches, regardless of how convincing the
case for skipping it seemed a moment earlier. The difference isn't that code is smarter than a
well-written instruction - it's that code doesn't get talked into anything.
## Why three different mechanisms, not one
The three gates ask three different questions, and each one's shape follows from what kind of
question it is.
The Mass-Update Gate asks *is this change too large to publish unreviewed* - a judgment that
varies changeset by changeset, so it clears with a `--confirm` token tied to the specific
output the user just read. Approval is scoped to that one publish.
The Publish-Remote Gate asks something underneath that: *is this even the right repository*.
That's not a per-push judgment, it's a standing property of the checkout - true or false for
every publish that checkout will ever attempt, not just this one. A confirm token would let an
agent clear it once and then treat the answer as settled, which is exactly backwards for a
question whose answer shouldn't move at all mid-session. The only way past it is the user
editing `.wikitool-remotes.json` directly, outside the gate's own flow.
The Iteration Budget Gate asks a third kind of question - not "is this instance correct" but
"has this session stopped making progress." That's read from the shape of the call history
itself (call count, repeated identical calls), not from anything about the content of any one
call.
## Numbers that come from measurement, not intuition
The iteration ceiling didn't start where it sits now. It used to run 15-25, borrowed from a
general rule of thumb, until four real ingest runs measured 24, 26, 29 and 30 calls apiece -
every one of them an ordinary workflow doing nothing wrong, and every one of them at or past
where the old ceiling would have refused it. A limit that the normal case keeps tripping stops
functioning as a limit; it becomes background noise a session learns to route `--override-budget`
around as a matter of course, and the whole point of a hard-coded check is that it isn't supposed
to feel routine.
That's the deeper reason these numbers live in a tool rather than in prose: prose is read once
and remembered loosely, but a threshold enforced every call is tested by every call, and a
threshold that fails its own test gets noticed and re-measured rather than quietly ignored.
+60 -16
View File
@@ -6,10 +6,24 @@ description: Which Claude model and effort level to run a Claude Code session, a
# Pick the Claude model and effort level for the task at hand # Pick the Claude model and effort level for the task at hand
Scale the model and effort to how much judgment the task actually needs. Running everything at Scale the model and effort to **what catches a mistake in this part of the work** - not to how
the most capable model and highest effort is safe but wasteful: the gates in [gates.md](gates.md) important the task feels, and not to its name. Running everything at the most capable model and
are enforced in code, not by model judgment, so a weaker model cannot bypass them - it can only highest effort is safe but wasteful: the gates in [gates.md](gates.md) are enforced in code, not
do a worse job of the calls the gates don't cover. by model judgment, so a weaker model cannot bypass them - it can only do a worse job of the calls
the gates don't cover.
That last clause is the whole rule, turned into a test. Where a check lives in code - `pytest`,
`docs verify`, `instructions verify`, CI, the gates - a weaker model's mistake surfaces and costs
one more round. Where the only enforcement is a session reading prose, the same mistake does not
surface at all: it ships, and it stays until someone happens to notice. The two are not the same
risk, and they should not get the same model. This is the argument
[docs/why-gates-are-code.md](../docs/why-gates-are-code.md) makes about gates, applied to who is
holding the keyboard.
Both directions cost something, which is why the axis matters rather than a blanket answer:
over-provisioning is a standing cost paid every session, while under-provisioning in an unchecked
phase is a silent error with a long tail. A corrective session, its bump, its CI runs and its
release together cost more compute than the model difference they were saving.
Claude-Code-only, and imported by CLAUDE.md rather than linked from AGENTS.md: the model names, Claude-Code-only, and imported by CLAUDE.md rather than linked from AGENTS.md: the model names,
the `/code-review` effort dial and the `Agent` tool's `model:` override have no equivalent in the the `/code-review` effort dial and the `Agent` tool's `model:` override have no equivalent in the
@@ -28,17 +42,41 @@ to *make*, not a setting to apply.
## Steps ## Steps
1. **Recommend the session's model and effort by the skill in use**, when asked or when the 1. **Recommend the session's model and effort by what catches a mistake in the phase it is in**,
mismatch is worth one sentence. Say it once and continue working either way - a session that when asked or when the mismatch is worth one sentence. Say it once and continue working either
argues about its own model instead of doing the task has already cost more than the model way - a session that argues about its own model instead of doing the task has already cost
difference: more than the model difference:
| Skill / task | Model | Effort | | Phase / task | What catches a mistake here | Model | Effort |
|---|---|---| |---|---|---|---|
| `wiki-status`, simple `wiki-query` lookups | Sonnet | default | | `wiki-status`, simple `wiki-query` lookups | the answer is re-checkable against the corpus | Sonnet | default |
| `wiki-lint` | Sonnet | default | | `wiki-lint` | `lint` itself is the check | Sonnet | default |
| `wiki-ingest`, `wiki-manage`, judgment-heavy `wiki-query` | Sonnet | high | | `wiki-ingest`, `wiki-manage`, judgment-heavy `wiki-query` | `lint` and `docs verify`, partly - the judgment about a claim is not covered | Sonnet | high |
| Stack development: `tools/`, `types/`, `instructions/` as code | Opus | high | | Stack dev: design, the version part, a boundary-crossing judgment | nothing - `docs verify` checks that a crossing documents itself, never that the part was right | Opus | high |
| Stack dev: code, tests, mechanical doc sync (command tables, contract rows) | `pytest`, `docs verify`, `instructions verify`, CI | Sonnet | high |
| Stack dev: closing an issue, `docs/` staleness, changelog prose | nothing, by construction - see below | Opus | high |
**Stack development is not one row**, which is the point of splitting it. The middle phase is
where the tokens are and where the checks are, so it is the phase worth running cheaper. The
two around it have no mechanical guard at all - a `docs/` page carries no normative sentence,
so there is nothing for `docs verify` to check ([AGENTS.md](../AGENTS.md) § File naming), and
the same holds for whatever tracker an instance keeps its open work in, which `wikitool`
deliberately knows nothing about. Those two phases are short - minutes, not hours - so keeping
them on the stronger model is cheap, and it protects the only work in the session that fails
silently.
**Effort is the cheaper lever than the model.** Reach for it first: `medium` deliberately does
not appear in this table for stack work, because multi-file consistency is exactly what a
reduced effort level gives up. Sonnet at `high` is the floor for anything touching more than
one file or a contract; `default` is for a single-file mechanical edit with a test behind it.
**A session cannot switch its own model**, so these rows only become real if someone offers the
switch at the moment the phase changes - once, without arguing about it, and never as a reason
to stop work that is already underway.
<!-- dist:strip-start -->
In this repo those moments are named: the `stack-dev` skill breaks for them at its steps 3
(design settled, work turns mechanical) and 6 (publish done, the unchecked tail begins).
<!-- dist:strip-end -->
2. **Pick a spawned subagent's model by what it does**, via the `Agent` tool's `model:` 2. **Pick a spawned subagent's model by what it does**, via the `Agent` tool's `model:`
parameter - the values are `haiku`, `sonnet`, `opus`, `fable`: parameter - the values are `haiku`, `sonnet`, `opus`, `fable`:
@@ -66,8 +104,14 @@ to *make*, not a setting to apply.
mechanical one - `wikitool` carries the mechanical part regardless of which model is mechanical one - `wikitool` carries the mechanical part regardless of which model is
supervising it. supervising it.
- **Unsure which row applies?** Default to Sonnet at high effort, not the most capable model at - **Unsure which row applies?** Default to Sonnet at high effort, not the most capable model at
the highest effort. Under-provisioning costs one worse answer in one session; reflexively the highest effort. Under-provisioning *where a check exists* costs one worse answer in one
over-provisioning is a standing cost paid every session. session; reflexively over-provisioning is a standing cost paid every session.
- **Unsure whether the phase is checked?** Treat it as unchecked. The asymmetry is not symmetric:
a needless Opus phase costs money once, an unchecked Sonnet phase can ship something nobody
looks at again.
- **Mid-session and the phase changed, but nobody switched?** Do the work anyway - never block a
publish or an issue close on a model the session cannot change itself. Say which phase ran on
which model in the handover, so the gap is visible rather than silent.
## Scope ## Scope
+67 -7
View File
@@ -45,22 +45,44 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
[issue-tracking.md](../issue-tracking.md) - open work lives in Gitea issues, one per work [issue-tracking.md](../issue-tracking.md) - open work lives in Gitea issues, one per work
package, labelled `area/`, `kind/`, `prio/` and `size/`. There is no `TODO.md`. **The body package, labelled `area/`, `kind/`, `prio/` and `size/`. There is no `TODO.md`. **The body
of the issue you are working on is this session's plan file:** keep it current as the state of the issue you are working on is this session's plan file:** keep it current as the state
moves, not at the end, so an interrupted session leaves a body the next one can resume from. moves, so an interrupted session leaves a body the next one can resume from, *and* rewrite it
Read it before filing something for later, before editing or closing an issue, or before to its final state before closing. Both halves bind; the second is step 6 below. Read it
deciding what to pick up next. before filing something for later, before editing or closing an issue, or before deciding
what to pick up next.
[testing-conventions.md](../testing-conventions.md) - the suite runs against a deliberately [testing-conventions.md](../testing-conventions.md) - the suite runs against a deliberately
empty machine; what the autouse fixture already neutralizes, and what a test still has to empty machine; what the autouse fixture already neutralizes, and what a test still has to
establish itself. Read it before adding or changing a test. establish itself. Read it before adding or changing a test.
[version-parts.md](../version-parts.md) - which part a change bumps: the drop-in test, the [version-parts.md](../version-parts.md) - which part a change bumps: the drop-in test, the
catalogue of breaks that cross the compatibility boundary with `kb/` untouched, and what to catalogue of breaks that cross the compatibility boundary with `kb/` untouched, and what to
put in front of the user before a breaking bump. Read it before step 3. put in front of the user before a breaking bump. Read it before step 4.
[corpus-policy.md](../corpus-policy.md) - what "curated enough" means for the shared [corpus-policy.md](../corpus-policy.md) - what "curated enough" means for the shared
demo/testbed `kb/`, the measurable floors that define it, and what a reactive fix may and may demo/testbed `kb/`, the measurable floors that define it, and what a reactive fix may and may
not do to corpus content. Read it before judging whether the corpus can exercise a change, or not do to corpus content. Read it before judging whether the corpus can exercise a change, or
before any fix that would touch `kb/` content. before any fix that would touch `kb/` content.
More instructions are added here incrementally as stack-development needs come up - this More instructions are added here incrementally as stack-development needs come up - this
list grows without needing this skill file to change shape. list grows without needing this skill file to change shape.
3. **Raise the version, if the change ships.** A change under `tools/`, `types/`, 3. **Settle the design before building - and break there for the model switch.** These are two
different kinds of work, and the split is not stylistic: design, the version part and any
boundary judgment have **no** mechanical guard, while the code and tests that follow have
`pytest`, `docs verify`, `instructions verify` and CI behind them.
So when the design is settled - the issue body says what will be built, the open questions are
answered - stop and say so, in one sentence:
> Der Plan steht, ab hier ist die Arbeit mechanisch und durch Tests/CI abgedeckt. Wenn du auf
> Opus bist, ist jetzt der Moment für `/model sonnet` bei Effort `high`.
**You cannot make this switch yourself** - the session's model is the user's `/model`, not a
setting an agent applies. Offer it once and keep working either way; a session that argues
about its own model has already cost more than the difference. If the design turns out not to
be settled after all - a boundary crossing surfaces, an assumption breaks - that is a reason to
offer the switch back up, not to decide it alone.
Effort is the cheaper lever than the model, and `high` is the floor for anything touching more
than one file or a contract. Full table and reasoning:
[claude-code-model-selection.md](../../claude-code-model-selection.md).
4. **Raise the version, if the change ships.** A change under `tools/`, `types/`,
`instructions/`, `AGENTS.md` or a `CONTRACT.md` reaches every future instance, so it needs a `instructions/`, `AGENTS.md` or a `CONTRACT.md` reaches every future instance, so it needs a
version and a changelog entry: version and a changelog entry:
@@ -97,13 +119,48 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
Prose-only changes (`README.md`, `INSTALL.md`, `EVALS.md`) and the workflows under `.gitea/` Prose-only changes (`README.md`, `INSTALL.md`, `EVALS.md`) and the workflows under `.gitea/`
do not need a bump - CI's version gate is scoped to what changes behaviour. do not need a bump - CI's version gate is scoped to what changes behaviour.
4. **Verify before publishing.** `tools/wikitool docs verify`, `tools/wikitool instructions 5. **Verify before publishing.** `tools/wikitool docs verify`, `tools/wikitool instructions
verify`, and the relevant `pytest` run in `tools/` - the same checks any stack change must verify`, and the relevant `pytest` run in `tools/` - the same checks any stack change must
pass, run explicitly rather than assumed. CI (`.gitea/workflows/ci.yml`) runs these plus a pass, run explicitly rather than assumed. CI (`.gitea/workflows/ci.yml`) runs these plus a
full `setup-instance.md` replay against a fresh `dist export`; a push to `main` that moves full `setup-instance.md` replay against a fresh `dist export`; a push to `main` that moves
`VERSION` additionally triggers a tagged release. **CI does the tagging** - a session never `VERSION` additionally triggers a tagged release. **CI does the tagging** - a session never
creates a tag, which is what keeps AGENTS.md invariant 5 intact. creates a tag, which is what keeps AGENTS.md invariant 5 intact.
6. **Close the issue with a body rewrite, not a comment.** The last act of a session that
finished a work package, and the one most easily skipped: by here the change is published and
the issue feels done. It is not. The body is the version everyone reads afterwards and nobody
revisits, so it is the one place the debt comes due at the worst moment.
**Break here too, in the other direction.** Everything left in the session - this rewrite,
whether a `docs/` page's reasoning went stale, the changelog prose - is the unchecked kind of
work again, the mirror of step 3. If the session dropped to Sonnet there, say so now:
> Ab hier greift kein maschineller Check mehr - Issue-Body, `docs/`-Veralterung und
> Changelog-Prosa prüft nichts. Wenn du zurück auf Opus willst, ist jetzt der Moment.
Then **do the work regardless of the answer.** Never block a close on a model switch: the
change is already published, and a session that stops here leaves exactly the state this step
exists to prevent. If it ran on the cheaper model, name that in the handover rather than
leaving it silent.
Rewrite it to its final state *first*, then close. The test is what a reader who opens the
closed issue tomorrow would conclude:
- every acceptance criterion ticked, or struck with the reason it was dropped
- proposals that were decided read as decided; a "to decide" section has become the decision
and its reasoning
- nothing left in the present tense about a defect that no longer exists
- what was verified is named - which checks ran, which CI run - not a commit hash alone
Then one short comment naming what changed against the previous state, and nothing else.
**A closing report in a comment does not satisfy this**, however thorough: it reads as
complete to whoever writes it and leaves a body still phrased as open work. Nothing
mechanical catches it - `wikitool` does not know this tracker exists and must not learn it,
since it ships to instances that have no board - so this step is the only enforcement there
is. #44 and #45 both closed exactly this way, the second an hour after the rule was written.
[issue-tracking.md](../issue-tracking.md) step 7 has the full shape.
## Decision points ## Decision points
- **Touches both stack code and wiki content in one session?** Apply this skill's rules to the - **Touches both stack code and wiki content in one session?** Apply this skill's rules to the
@@ -114,7 +171,10 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
user decides whether it is worth that: show them what breaks, what an instance has to do about user decides whether it is worth that: show them what breaks, what an instance has to do about
it, and the alternatives (avoid the break with a shim, defer and batch it with the next one, it, and the alternatives (avoid the break with a shim, defer and batch it with the next one,
or split it behind a deprecation window), then recommend one and wait for a go-ahead. or split it behind a deprecation window), then recommend one and wait for a go-ahead.
[version-parts.md](../version-parts.md) step 4 has the full shape. [version-parts.md](../version-parts.md) step 4 has the full shape. A surfacing boundary crossing
is also a reason to offer the model switch back up (step 3): the judgment it needs has no
mechanical guard, and `docs verify` only checks that a crossing documents itself, never that the
part was chosen correctly.
## Scope ## Scope