Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56ecfc7fee | |||
| 4e80a07ac7 | |||
| 0b8ca746fa |
@@ -82,6 +82,7 @@ What a file is called says who it is for and how it is loaded. This is a rule, n
|
||||
| `instructions/<name>.md` | Agents | By link, or on explicit request |
|
||||
| `instructions/<name>/SKILL.md` | Agents | By the harness, once published |
|
||||
| `types/<name>.md` | Agents + validator | Via `tools/wikitool types describe`. Split by `root:`: a page type-spec (`root: kb`) belongs to the instance and ships as `.template`; one describing a stack artifact ships verbatim |
|
||||
| `docs/<name>.md` | Agents and humans | By link, or on explicit request - never automatically, and never as instruction |
|
||||
| `INDEX.md` | Both | Generated - never hand-edited |
|
||||
|
||||
A stage may carry both a `README.md` and a `CONTRACT.md`: different readers, different
|
||||
@@ -89,6 +90,16 @@ documents. What it may not carry is the same content twice - a README that resta
|
||||
contract is a second copy that drifts. `docs verify` enforces the specific case that already
|
||||
happened once: no README may hold a copy of the `wikitool` command table.
|
||||
|
||||
**`docs/` carries no normative sentence.** It holds why the stack is built the way it is -
|
||||
background a session consults in passing, not a rule it must follow. Anything that would bind
|
||||
belongs in a `CONTRACT.md` instead, which is what keeps invariant 8 intact here: `docs/` is
|
||||
never a second place a rule could live, only prose about rules that live elsewhere. That is also
|
||||
why nothing verifies its content - there is no rule in it to check. It has no frontmatter, no type, no index, no lint, no decay, no provenance, and no
|
||||
`COLLECTION.md` - which [kb/CONTRACT.md § Collections](kb/CONTRACT.md#collections) forbids
|
||||
outside `kb/` anyway, but the point holds independently: `docs/` stays a plain directory of
|
||||
prose, invisible to everything `tools/wikitool` does except `dist export`, which copies it
|
||||
verbatim. A fresh instance needs the reasoning as much as this one does.
|
||||
|
||||
## Personalization
|
||||
|
||||
`USER.md` and `SOUL.md` are read at session start, if the runtime has not already injected
|
||||
@@ -145,7 +156,8 @@ input schema + compiler output derived (gitignored)
|
||||
work/ tracked scratch, deleted when the run closes
|
||||
```
|
||||
|
||||
Alongside it, not part of it: `instructions/` (what agents are told to do) and this file.
|
||||
Alongside it, not part of it: `instructions/` (what agents are told to do), `docs/` (why the
|
||||
stack is built the way it is - see [File naming](#file-naming)), and this file.
|
||||
|
||||
**By stage** - read the contract for the stage you are writing in:
|
||||
|
||||
@@ -263,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
|
||||
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.
|
||||
|
||||
`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.
|
||||
|
||||
+119
@@ -20,6 +20,125 @@ their date-only headings.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
Gitea #38: `dist export` lieferte bislang keine einzige `kb/`-Seite aus - eine frische Instanz
|
||||
bekam den Stack, aber keinen Grund für seine Form. Die dokumentierte `adr-NNN-`-Konvention in
|
||||
`kb/concepts/COLLECTION.md` existierte zudem nur auf Papier: keine der sieben
|
||||
`concept_type: decision`-Seiten folgte ihr, und `confidence_decay()` lief bedingungslos über sie
|
||||
- ein Kategorienfehler, weil Zeitablauf eine Entscheidung nicht falscher macht, nur Supersession
|
||||
tut das.
|
||||
|
||||
**Neu:** `docs/` - ein inertes Verzeichnis für Stack-Hintergrund (warum der Stack so gebaut ist,
|
||||
nicht was diese Instanz entschieden hat). Keine Frontmatter, kein Typ, kein Index, kein Lint,
|
||||
keine Decay, keine Provenance, keine `COLLECTION.md`. `dist export` liefert es verbatim aus, wie
|
||||
`instructions/` und `types/`. Befüllung folgt in Gitea #45.
|
||||
|
||||
**Verworfen, nach Prüfung:** ein Umzug der sieben Decision-Seiten nach `decisions/`. Der
|
||||
Subtyp-Floor aus #28 verlangt mindestens eine Seite je deklariertem `concept_type`, und ein
|
||||
Umzug hätte `decision` auf null gebracht; dazu zeigen 89 Wikilinks aus `kb/` sowie
|
||||
tool-eigene Frontmatter-Arrays auf die sieben, und `links.py`/`xref add` kennen kein Ziel
|
||||
außerhalb `kb/`. Die sieben bleiben in `kb/concepts/`, ebenso ein zweiter, separat erwogener
|
||||
Rename (`docs verify` → `parity verify`) - der wäre nur nötig gewesen, wenn ein Befehl auf das
|
||||
Verzeichnis `docs/` wirkt, und keiner tut das.
|
||||
|
||||
**Geändert:**
|
||||
- `confidence_decay()` überspringt `concept_type: decision` strukturell (kategorische Ausnahme,
|
||||
nicht als Brücke gebaut - Begründung im Docstring).
|
||||
- `kb/concepts/COLLECTION.md` § Decisions ersetzt die tote ADR-Vorlage durch die real gelebte
|
||||
Form: eine Entscheidung ist eine gewöhnliche Concept-Seite, organische Prosa, kein
|
||||
`adr-NNN-`-Präfix, `**Status:**` optional, Supersession per `supersedes`-Link.
|
||||
- `kb/CONVENTIONS.md` § Naming und `instructions/kb-profiles.md` (Profil `german`) korrigiert -
|
||||
beide dokumentierten noch die verworfene `adr-NNN-`-Namensregel.
|
||||
- `AGENTS.md` § File naming und § Routing: `docs/`-Zeile, plus die Regel, dass `docs/` keinen
|
||||
normativen Satz trägt (das hält Invariante 8 heil - was binden würde, gehört in einen
|
||||
Contract).
|
||||
- `tools/CONTRACT.md`: Klarstellung, dass `docs verify` Dokumentations-Parität prüft, nicht das
|
||||
`docs/`-Verzeichnis, sowie `docs/` in der `dist export`-Zeile ergänzt.
|
||||
|
||||
Additiv und in beide Richtungen drop-in: eine bestehende Instanz ohne `docs/` exportiert
|
||||
weiterhin identisch (leerer `_copy_tree`-Treffer), eine Instanz mit `docs/` bekommt es ab jetzt
|
||||
mitgeliefert. Kein Feld, kein Kommando ändert sein Verhalten für bestehenden Inhalt.
|
||||
|
||||
**Migration:** none required.
|
||||
|
||||
Berührt: `tools/chemenu/commands/confidence_decay.py`, `tools/chemenu/commands/dist_cmd.py`,
|
||||
`tools/chemenu/tests/test_confidence_decay.py`, `tools/chemenu/tests/test_dist_cmd.py`,
|
||||
`kb/concepts/COLLECTION.md`, `kb/CONVENTIONS.md`, `instructions/kb-profiles.md`, `AGENTS.md`,
|
||||
`tools/CONTRACT.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4.2.0 - 2026-09-03 - Korpus-Kuratierungsrichtlinie: Untergrenzen und Leitplanke für reaktive Fixes
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -45,9 +45,10 @@ 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
|
||||
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
|
||||
moves, not at the end, so an interrupted session leaves a body the next one can resume from.
|
||||
Read it before filing something for later, before editing or closing an issue, or before
|
||||
deciding what to pick up next.
|
||||
moves, so an interrupted session leaves a body the next one can resume from, *and* rewrite it
|
||||
to its final state before closing. Both halves bind; the second is step 5 below. Read it
|
||||
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
|
||||
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.
|
||||
@@ -104,6 +105,29 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
|
||||
`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.
|
||||
|
||||
5. **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.
|
||||
|
||||
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
|
||||
|
||||
- **Touches both stack code and wiki content in one session?** Apply this skill's rules to the
|
||||
|
||||
@@ -68,7 +68,7 @@ stack's hardcoded behaviour until the conventions file existed.
|
||||
|---|---|
|
||||
| `language:` | `de` |
|
||||
| `sections:` | `Beziehungen` / `Siehe auch` / `Fußnoten` |
|
||||
| Naming | Human-readable titles with spaces; singular for entities; `adr-NNN-` for decisions; `X vs Y` for comparisons |
|
||||
| Naming | Human-readable titles with spaces; singular for entities; a decision named like any other concept, no `adr-NNN-` prefix; `X vs Y` for comparisons |
|
||||
| Tone | Wikipedia register, with a German buzzword and filler list |
|
||||
| Relationship labels | `hängt ab von` · `verwendet` · `implementiert` · `erweitert` · `ersetzt` · `steht in Konflikt mit` · `benötigt` · `erzeugt` · `konsumiert` · `besitzt` · `pflegt` · `läuft auf` · `verwandt mit` |
|
||||
| Confidence rubric | 0.5 base, +0.2 per supporting source (max +0.6), recency and source-quality bonuses; hedge with "möglicherweise"/"kann" below 0.6, "unsicher"/"unbestätigt" below 0.4 |
|
||||
|
||||
+2
-1
@@ -62,7 +62,8 @@ There is no `## Siehe auch` region any more. It was the reciprocal half of a bid
|
||||
- Human-readable titles with spaces: `Hybrid Search.md`, `Gitea Actions.md` - not kebab-case.
|
||||
- Singular for entities: `ha-core.md`, not `ha-cores.md`.
|
||||
- Comparison pages read as a comparison: `Go vs Rust.md`.
|
||||
- ADRs are prefixed: `adr-001-use-go-modules.md`.
|
||||
- A decision (`concept_type: decision`) is named like any other concept - no `adr-NNN-` prefix.
|
||||
See [kb/concepts/COLLECTION.md § Decisions](concepts/COLLECTION.md#decisions).
|
||||
- Prefer readability over convention when the two conflict.
|
||||
|
||||
What to name a thing: projects use their repository or common name; systems a descriptive
|
||||
|
||||
+18
-10
@@ -27,19 +27,27 @@ tone, relationship labels, the confidence rubric. Neither is restated here.
|
||||
|
||||
`concept` (`tools/wikitool types describe concept`).
|
||||
|
||||
## Decisions and ADRs
|
||||
## Decisions
|
||||
|
||||
An architectural decision is a concept page, prefixed as
|
||||
[kb/CONVENTIONS.md § Naming](../CONVENTIONS.md#naming) says. It records:
|
||||
An architectural decision is an ordinary concept page with `concept_type: decision`
|
||||
(`tools/wikitool types describe concept`) - not a separate format, and not a separate location.
|
||||
There is no `adr-NNN-`-prefixed filename and no dedicated directory: naming follows
|
||||
[kb/CONVENTIONS.md § Naming](../CONVENTIONS.md#naming) like every other concept, and the page
|
||||
lives in `kb/concepts/` like every other concept.
|
||||
|
||||
- **Context** - what forced a decision.
|
||||
- **Decision** - what was chosen.
|
||||
- **Consequences** - what this costs, not only what it buys.
|
||||
- **Status** - proposed / accepted / deprecated / superseded.
|
||||
- Links to every entity the decision affects.
|
||||
The body is organic prose under this collection's usual sections, not a fixed template. What it
|
||||
still has to carry: what was decided, what forced the decision, what it costs (not only what it
|
||||
buys), and a link to every entity the decision affects. A `**Status:**` line is optional - most
|
||||
decision pages in this instance carry none, because the page's own prose already says whether the
|
||||
decision stands.
|
||||
|
||||
A superseded ADR is never deleted or rewritten. The new one declares `supersedes` pointing at
|
||||
it; the old one needs no edge back, because its inbound view renders the replacement.
|
||||
A decision superseded by a later one is never deleted or rewritten. The new page declares
|
||||
`supersedes` pointing at it; the old one needs no edge back, because its inbound view renders the
|
||||
replacement.
|
||||
|
||||
`concept_type: decision` is also the one subtype [kb/CONTRACT.md](../CONTRACT.md)'s confidence
|
||||
machinery treats differently: `confidence decay` skips it structurally, because elapsed time does
|
||||
not falsify a decision - only a later decision superseding it does.
|
||||
|
||||
## Authorised labels
|
||||
|
||||
|
||||
+2
-2
@@ -68,10 +68,10 @@ tools/wikitool <command> --help
|
||||
| `instructions sync [--force]` | Publish every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and delete published skills whose source is gone. Both targets are gitignored, so a fresh clone runs this once - see `instructions/bootstrap.md`. Re-running is also how a drifted copy is repaired: the source always wins. `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it) |
|
||||
| `instructions verify` | Check the instruction layer: flat instructions validate against `types/instruction.schema.yaml`, each `SKILL.md` carries the frontmatter its harness reads, every published copy is byte-identical to its source, no instruction is left that nothing references, and nothing under `instructions/dev/` is referenced from outside it (a `<!-- dist:strip-start/end -->` block is exempt - see [instructions/CONTRACT.md](../instructions/CONTRACT.md)). Missing *every* copy is reported as "run sync", not as drift - that is a clean checkout |
|
||||
| `instructions list [--json]` | List the flat instructions with their descriptions. This is how the layer is discovered; `search` deliberately covers `kb/` only |
|
||||
| `docs verify` | Check the docs that mirror the code: every CLI command documented here (and vice versa), every directory under `kb/` has a `COLLECTION.md` and no directory outside it does, every collection declaring `profile:` and a `required_by_stack:` that agrees with the stack's own list, `kb/CONVENTIONS.md` naming all three tool-owned section headings if it exists at all, every stage contract present, no pre-migration `type: entity` blocks left in the contracts, and the `.gitignore` canaries clear in both directions (nothing ignored under `raw/`/`kb/`, everything ignored under `reports/` and the published skill directories) |
|
||||
| `docs verify` | Check the docs that mirror the code: every CLI command documented here (and vice versa), every directory under `kb/` has a `COLLECTION.md` and no directory outside it does, every collection declaring `profile:` and a `required_by_stack:` that agrees with the stack's own list, `kb/CONVENTIONS.md` naming all three tool-owned section headings if it exists at all, every stage contract present, no pre-migration `type: entity` blocks left in the contracts, and the `.gitignore` canaries clear in both directions (nothing ignored under `raw/`/`kb/`, everything ignored under `reports/` and the published skill directories). The name is about documentation parity, not about the `docs/` directory - it neither reads nor requires one, the same way `kb/` predates the collection it now checks |
|
||||
| `eval sessions [--json]` | List the sessions that have a trace under `reports/telemetry/`, most recent first. Read-only and exempt from the Iteration Budget Gate |
|
||||
| `eval score [--session <id>] [--json] [--markdown out.md] [--save] [--fail-on-error]` | Score one traced session: structural state from `lint`'s own checks (L1) plus trajectory rules over the trace (L2) - was a refused call repeated unchanged, was a gate flag passed without that gate having refused anything, did a publish of `kb/` pages go unlogged. Defaults to the current session. `--save` writes `reports/evals/<date>/<session>.{json,md}`. Read-only over `kb/` and exempt from the budget; see [../EVALS.md](../EVALS.md) |
|
||||
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
|
||||
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
|
||||
| `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** |
|
||||
| `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate |
|
||||
| `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate |
|
||||
|
||||
@@ -11,6 +11,14 @@ Keeping the undecayed anchor in `confidence_base` is what makes repeated runs
|
||||
idempotent - decaying the stored `confidence` in place (the pre-2026-08-13
|
||||
behavior) compounded on every run, because the elapsed-months factor kept
|
||||
growing while the multiplicand had already shrunk.
|
||||
|
||||
Pages with `concept_type: decision` are skipped structurally, not as an
|
||||
interim measure. The formula models staleness - a claim that nobody has
|
||||
re-checked in a while becomes less trustworthy - and a decision is not a
|
||||
claim about the world that time can falsify. What retires a decision is a
|
||||
later decision superseding it, never elapsed months on its own; that is a
|
||||
category the decay formula does not have a term for, so it does not apply
|
||||
one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -118,6 +126,8 @@ def confidence_decay(
|
||||
missing_base = []
|
||||
|
||||
for title, page in sorted(pages.items()):
|
||||
if page.frontmatter.get("concept_type") == "decision":
|
||||
continue
|
||||
confidence = page.frontmatter.get("confidence")
|
||||
if confidence is None:
|
||||
continue
|
||||
|
||||
@@ -347,6 +347,12 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
|
||||
for hook_dir in HOOK_DIRS:
|
||||
plan.update(_copy_tree(config.ROOT / hook_dir, hook_dir, frozenset()))
|
||||
|
||||
# docs/ is stack background - why the stack is built the way it is - and
|
||||
# ships verbatim like instructions/ and types/: it carries no page, no
|
||||
# frontmatter, and (AGENTS.md § File naming) no normative sentence, so
|
||||
# there is nothing instance-owned in it to split off as a .template.
|
||||
plan.update(_copy_tree(config.ROOT / "docs", "docs", frozenset()))
|
||||
|
||||
# `kb/CONTRACT.md` is stack-owned and ships verbatim; everything beside it
|
||||
# under `kb/` is the instance's own and ships only as a `.template`. That is
|
||||
# the personalization split (`USER.md`/`SOUL.md`) one directory down, and
|
||||
@@ -492,7 +498,8 @@ def export_command(
|
||||
):
|
||||
"""Export a contentless, distributable copy of this repo's machinery:
|
||||
AGENTS.md/README.md (dev-instance-only marker blocks removed),
|
||||
instructions/ (no instructions/dev/), types/, tools/ (no venv/caches),
|
||||
instructions/ (no instructions/dev/), types/, docs/ verbatim,
|
||||
tools/ (no venv/caches),
|
||||
the .github/hooks/+.vibe session-tracing config plus .claude/settings.json,
|
||||
kb/CONTRACT.md plus a COLLECTION.md.template per collection and
|
||||
kb/CONVENTIONS.md.template (no pages, no areas), empty
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
from chemenu import config
|
||||
from chemenu.commands import confidence_decay
|
||||
from chemenu.commands.confidence_decay import FLOOR, compute_decay
|
||||
from chemenu.frontmatter_io import read_page
|
||||
from chemenu.frontmatter_io import read_page, write_page
|
||||
|
||||
|
||||
def test_no_decay_at_zero_months():
|
||||
@@ -68,3 +68,22 @@ def test_decay_skips_pages_without_a_base(decay_wiki):
|
||||
confidence_decay.confidence_decay(apply=True)
|
||||
frontmatter, _ = read_page(decay_wiki / "entities/systems/aurora.md")
|
||||
assert frontmatter["confidence"] == 0.9
|
||||
|
||||
|
||||
def test_decay_skips_decision_pages(decay_wiki):
|
||||
"""A decision is not falsified by elapsed time, only by a later decision
|
||||
superseding it - `concept_type: decision` is a categorical skip, not
|
||||
something an old `modified` date should ever decay (Gitea #38)."""
|
||||
decision_path = decay_wiki / "concepts" / "some-decision.md"
|
||||
write_page(
|
||||
decision_path,
|
||||
{
|
||||
"type": "types/concept.md", "concept_type": "decision",
|
||||
"tags": [], "created": "2015-01-01", "modified": "2015-01-01",
|
||||
"related": [], "sources": [], "confidence": 0.9, "confidence_base": 0.9,
|
||||
},
|
||||
"\n# some-decision\n",
|
||||
)
|
||||
confidence_decay.confidence_decay(apply=True)
|
||||
frontmatter, _ = read_page(decision_path)
|
||||
assert frontmatter["confidence"] == 0.9
|
||||
|
||||
@@ -135,6 +135,10 @@ def repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
f"<!-- {config.TEMPLATE_SENTINEL} -->\n# conventions template\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
docs_dir = root / "docs"
|
||||
docs_dir.mkdir()
|
||||
(docs_dir / "why-gates-are-code.md").write_text("# Why gates are code\n", encoding="utf-8")
|
||||
|
||||
for relative in ("raw/CONTRACT.md", "reports/CONTRACT.md", "work/CONTRACT.md"):
|
||||
path = root / relative
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -156,6 +160,11 @@ def test_plan_never_includes_commonplace(repo):
|
||||
assert "commonplace" not in combined
|
||||
|
||||
|
||||
def test_plan_ships_docs_verbatim(repo):
|
||||
plan = dist_cmd.build_plan()
|
||||
assert plan["docs/why-gates-are-code.md"].content == "# Why gates are code\n"
|
||||
|
||||
|
||||
def test_plan_never_includes_instructions_dev(repo):
|
||||
"""instructions/dev/ - flat dev-only instructions and the nested skill
|
||||
that switches a session into tool-development mode - is pruned
|
||||
|
||||
Reference in New Issue
Block a user