Compare commits

...

3 Commits

Author SHA1 Message Date
torben e00eae08e8 Coverage-Untergrenze 85 % in tools/.coveragerc, gegen beobachtete 87,0 % (4.7.2, schließt #10, eröffnet #51)
CI / verify (push) Successful in 57s
Release / release (push) Successful in 38s
Files changed:
- .gitea/workflows/ci.yml
- CHANGES.md
- EVALS.md
- VERSION
- tools/.coveragerc
2026-09-04 19:19:38 +02:00
torben fe55ad2a9c Coverage-Beobachtung nachgezogen: 87,0 % / 6498 / 975 (Lauf 163), Artefakt-Abruf geklärt (#10)
CI / verify (push) Successful in 58s
Files changed:
- .gitea/workflows/ci.yml
- EVALS.md
2026-09-04 19:12:40 +02:00
torben 24593c5608 Doku-Hälfte zu 4.7.0: redundant_see_also in tools/CONTRACT.md und wiki-lint, xref-remove-Falle benannt (4.7.1, #49)
CI / verify (push) Successful in 52s
Release / release (push) Successful in 35s
Files changed:
- CHANGES.md
- VERSION
- instructions/wiki-lint/SKILL.md
- tools/CONTRACT.md
2026-09-04 18:46:55 +02:00
7 changed files with 154 additions and 28 deletions
+12 -6
View File
@@ -120,12 +120,12 @@ jobs:
# container is no longer a special environment worth a second run. # container is no longer a special environment worth a second run.
# See instructions/dev/testing-conventions.md. # See instructions/dev/testing-conventions.md.
# #
# Coverage is reported, not enforced: there is deliberately no # Coverage is measured and enforced at a floor of 85% against a measured
# `--cov-fail-under` yet (Gitea #10). The threshold gets set in its own # 87.0% - `fail_under` in tools/.coveragerc, not a flag here, so the
# later commit, with the measured number as its justification - one # number sits next to the reasoning that produced it. It was set only
# picked before the number is either too low to bite or too high to # after the number had been watched across 38 runs (Gitea #10, closed).
# survive the next honest commit, and the second kind gets lowered # A red suite from this floor means coverage actually fell; the two
# instead of earned. Config: tools/.coveragerc. # points of headroom already absorb a new thin Typer wrapper.
run: | run: |
set -eu set -eu
cd tools cd tools
@@ -138,6 +138,12 @@ jobs:
# v3, not v4 - v4 is restricted on this Gitea instance; v3 is what is # v3, not v4 - v4 is restricted on this Gitea instance; v3 is what is
# proven here (torben/gitea-mcp@ci-build, ci-build.yaml, runs # proven here (torben/gitea-mcp@ci-build, ci-build.yaml, runs
# 42-45). # 42-45).
#
# The artifact is downloadable from the run page, but the Actions
# artifact REST endpoints report `total_count: 0` for it - v3 writes
# through the older artifact API, which those endpoints do not read.
# An empty list is not a failed upload. See EVALS.md § "How much of the
# stack the suite reaches"; do not re-derive this.
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
+72
View File
@@ -35,6 +35,78 @@ dev-checkout concern - readable here, never shipped as something to parse.
--- ---
## 4.7.2 - 2026-09-04 - Coverage-Untergrenze bei 85 %, gegen beobachtete 87,0 %
**Author:** Torben Nehmer
<!-- wikitool:bumps -->
- Coverage-Untergrenze 85 % in tools/.coveragerc
<!-- /wikitool:bumps -->
Die Suite hat jetzt einen Boden: `fail_under = 85` in `tools/.coveragerc`, gemessen gegen 87,0 %
(CI-Lauf 163, 6498 Statements, 975 Tests). Damit ist Gitea #10 geschlossen — das Issue, das die
Messung eingerichtet und die Schwelle danach **absichtlich** zurückgehalten hat, bis die Zahl
beobachtet war.
Die Beobachtung ist der eigentliche Inhalt dieses Bumps. Zwischen der ersten Messung (86,9 % von
5105 Statements, 730 Tests, Lauf 87, Stack 1.8.1) und heute ist der gemessene Code um ein Viertel
gewachsen und die Suite um ein Drittel, über 38 grüne Läufe — und die Quote hat sich um einen
Zehntelpunkt bewegt. Eine Untergrenze, die auf dieser Beobachtung steht, ist etwas anderes als
eine gegriffene Zahl.
**85 und nicht 87, und das ist keine Bequemlichkeit.** Der Coverage-Bericht unterscheidet drei
Sorten ungedeckter Zeilen, und nur eine davon bedeutet Arbeit (`EVALS.md` § „How much of the
stack the suite reaches"). Ein neuer dünner Typer-Wrapper senkt den Gesamtwert, ohne dass
irgendetwas schlechter geworden wäre — seine Logik liegt daneben und ist dort getestet. Eine
Schwelle auf dem gemessenen Wert würde genau an diesem Commit rot, und eine Schwelle, die aus
einem Nicht-Grund rot wird, wird gesenkt statt verdient. Das ist die Fehlerweise, die #10
verhindern wollte, nur von der anderen Seite. Die zwei Punkte sind der Platz, den die Taxonomie
verlangt.
`fail_under` steht in der Konfiguration und nicht als `--cov-fail-under` im CI-Schritt: so sitzt
die Zahl neben der Begründung, die sie erzeugt hat, und gilt für jeden `--cov`-Lauf statt nur für
den einen, den CI schreibt.
Was der Boden **nicht** tut: die drei echten Lücken schließen (`provenance_cmd.py` 44 %,
`migrate_cmd.py` 65 %, `type_resolver.py` 79 %). Er friert den erreichten Stand ein. Diese Liste
ist die einzige, die sich nicht bewegt hat, während alles um sie herum wuchs — `migrate_cmd.py`
ist sogar von 71 % gefallen, weil das Modul gewachsen ist und die neuen Zeilen ungetestet ankamen.
Das ist Gitea #51.
Mitgenommen, weil es dieselbe Frage beantwortet: der Coverage-Bericht **ist** als Artefakt
abrufbar, über die Run-Seite. Die Actions-Artefakt-Endpunkte melden dafür `total_count: 0`, weil
`upload-artifact@v3` über die ältere Artifact-API ablegt, die diese Endpunkte nicht lesen. Eine
leere Liste ist kein fehlgeschlagener Upload — steht jetzt in `EVALS.md` und im Kommentar an der
`Coverage report`-Stufe, damit die naheliegende „Korrektur" auf v4 (hier eingeschränkt) niemandem
mehr einfällt.
---
## 4.7.1 - 2026-09-04 - redundant_see_also in tools/CONTRACT.md und wiki-lint dokumentiert; xref-remove-Falle benannt
**Author:** Torben Nehmer
<!-- wikitool:bumps -->
- redundant_see_also in tools/CONTRACT.md und wiki-lint; xref-remove-Falle beim Aufräumen benannt
<!-- /wikitool:bumps -->
Die Doku-Hälfte von `4.7.0`, beim Abschluss nachgezogen. Der Befund war ausgeliefert, aber
`tools/CONTRACT.md`s `lint`-Zeile zählt die Befunde auf und kannte ihn nicht - eine Instanz hätte
eine Sektion im Report gefunden, die ihr Contract nicht erklärt. `docs verify` prüft, dass die
Kommandotabelle existiert, nie was in einer Zeile steht; genau die Lücke, für die AGENTS.md
„a stack change is not finished until the human docs describe it" geschrieben ist.
**Die eigentliche Änderung ist aber die Warnung in `wiki-lint`**, und sie ist keine Prosa-Politur.
Der neue Befund liest sich wie etwas, das Schritt 7 („repariere, was mechanisch ist") abräumt, und
der naheliegende Griff wäre `xref remove` - das die Referenz **beidseitig** löscht. Angewandt auf
`Wine see-also Wine GE` neben `Wine GE depends-on Wine` hätte das die schwache *und* die
spezifische Kante entfernt, und das Paar sagte danach gar nichts mehr. Ein Befund, dessen
offensichtliche Reparatur Daten zerstört, ist schlechter als kein Befund: Schritt 1 nennt die
Falle jetzt beim Namen und verweist auf `xref add` (fasst nur die Quellseite an) oder aufs
Berichten. Dieselbe Asymmetrie hat in #30 schon einmal Daten gekostet.
---
## 4.7.0 - 2026-09-04 - Link-Katalog: authored, alternative-to, addresses; entity→entity-Lineage; Lint-Befund gegen redundante see-also ## 4.7.0 - 2026-09-04 - Link-Katalog: authored, alternative-to, addresses; entity→entity-Lineage; Lint-Befund gegen redundante see-also
**Author:** Torben Nehmer **Author:** Torben Nehmer
+34 -14
View File
@@ -249,29 +249,49 @@ created but not yet written reports broken links. That is the scaffold saying it
### How much of the stack the suite reaches ### How much of the stack the suite reaches
Coverage is measured in CI and reported, never enforced - `pytest --cov`, config in Coverage is measured in CI - `pytest --cov`, config in `tools/.coveragerc`, HTML and XML
`tools/.coveragerc`, HTML and XML uploaded as the `coverage-<run id>` artifact of every run. uploaded as the `coverage-<run id>` artifact of every run.
There is no `--cov-fail-under`: a threshold is owed (Gitea #10), in its own commit, once the **Fetch that artifact from the run's own page, not from the API**: `upload-artifact@v3` writes
number has been watched long enough to freeze the state it actually reached. through the older artifact API, and the Actions artifact REST endpoints answer `total_count: 0`
for a run whose artifact the run page offers for download. The upload works; only the listing
does not see it. Do not re-derive this, and do not read the empty list as a failed upload.
**First measurement, 2026-08-31, stack 1.8.1: 86.9% of 5105 statements across `chemenu/`, It is enforced at a floor of **85%** (`fail_under` in `tools/.coveragerc`), which is what a red
730 tests** - as reported by CI run 87, not by the local run that preceded the last commit of suite from this axis means: coverage actually fell, not that a wrapper was added. The floor was
that release. Reproduce it with `cd tools && .venv/bin/python -m pytest -q --cov` (needs set only after the number had been watched - Gitea #10 held it back for exactly that, and the
`pytest-cov`, which is CI-only and deliberately absent from `tools/requirements.txt` - an two points between 85 and the measured 87.0% are the room the taxonomy below asks for. A
instance runs the wiki, it does not measure this suite). threshold at the measured number goes red on the next thin Typer wrapper, and a threshold that
goes red for a non-reason gets lowered rather than earned.
The total is the least interesting number here. What the report is for is *which* modules sit **Measured 2026-09-04, stack 4.7.1: 87.0% of 6498 statements across `chemenu/`, 975 tests** -
CI run 163. The first measurement, at stack 1.8.1 on 2026-08-31, was 86.9% of 5105 statements
over 730 tests (CI run 87). Both are what CI reported, never a local run: the local number
preceding a release measures a tree that is one commit short of the published one.
The pair says more than either number does. Between them the measured code grew by a quarter
and the suite by a third, and the quota moved by a tenth of a point - which is the observation a
threshold was waiting for, rather than the total itself. Reproduce either with
`cd tools && .venv/bin/python -m pytest -q --cov` (needs `pytest-cov`, which is CI-only and
deliberately absent from `tools/requirements.txt` - an instance runs the wiki, it does not
measure this suite).
The total stays the least interesting number here. What the report is for is *which* modules sit
low, and three kinds have to be told apart before any of it turns into work: low, and three kinds have to be told apart before any of it turns into work:
- **Thin Typer wrappers**, where the logic lives beside them and is tested there: - **Thin Typer wrappers**, where the logic lives beside them and is tested there:
`eval_cmd.py` (36%), `types_cmd.py` (52%), `cli.py` (52%). Low coverage on a wrapper is `eval_cmd.py` (36%), `types_cmd.py` (40%), `search.py` (49%), `cli.py` (54%),
evidence of a good cut, not of a missing test. `links_cmd.py` (61%). Low coverage on a wrapper is evidence of a good cut, not of a missing
test - `search.py`'s uncovered block is its command body alone, while the backends under
`chemenu/search/` that do the work sit between 91% and 98%.
- **Code that reaches the network or the filesystem's outside**, where the interesting half is - **Code that reaches the network or the filesystem's outside**, where the interesting half is
already injectable and tested through the seam: `version.py`'s `fetch_latest()` takes a already injectable and tested through the seam: `version.py`'s `fetch_latest()` takes a
`fetcher` parameter for exactly that, and the real network line stays uncovered on purpose. `fetcher` parameter for exactly that, and the real network line stays uncovered on purpose.
- **Genuine gaps**, where uncovered lines are logic nobody exercises: `provenance_cmd.py` - **Genuine gaps**, where uncovered lines are logic nobody exercises: `provenance_cmd.py`
(44%), `migrate_cmd.py` (71%), `type_resolver.py` (79%). This is the list worth reading, and (44%), `migrate_cmd.py` (65%), `type_resolver.py` (79%). This is the list worth reading, and
the reason step 2 of #10 is not a formality. the only one of the three that has not moved while everything around it did:
`provenance_cmd.py` sits where it sat, and `migrate_cmd.py` fell from 71% because it grew and
its new lines arrived untested. The floor freezes this; it does not close it. Closing it is
Gitea #51.
## Scoring a session ## Scoring a session
+1 -1
View File
@@ -1 +1 @@
4.7.0 4.7.2
+12 -2
View File
@@ -26,8 +26,18 @@ never something an agent has to remember.
unreadable frontmatter, broken wikilinks, dangling frontmatter references, orphan pages, unreadable frontmatter, broken wikilinks, dangling frontmatter references, orphan pages,
catalog drift, missing fields, duplicate titles, filename/title mismatches, broken catalog drift, missing fields, duplicate titles, filename/title mismatches, broken
`raw_files:` references, raw files claimed by more than one source page, invalid type paths, `raw_files:` references, raw files claimed by more than one source page, invalid type paths,
schema failures and citation/frontmatter drift. **Do not re-derive any of it by reading schema failures, citation/frontmatter drift, and edges whose label is missing, not authorised
pages.** by the source collection, or redundant beside a specific label on the reverse direction.
**Do not re-derive any of it by reading pages.**
The *Redundant see-also* section is the one that looks mechanical and is not - do **not**
clear it under step 7. It names a `see-also` edge standing beside a specific label on the
reverse direction, and the obvious repair destroys the thing worth keeping: `xref remove`
clears the reference in *both* directions (see [tools/CONTRACT.md](../../tools/CONTRACT.md)),
so removing the weak edge takes the labelled one with it and the pair ends up saying nothing
at all. Either relabel the weak edge to something true with `xref add`, which only ever
touches the source page, or leave it and report it at step 9. Clearing a batch of these is a
planned corpus sweep with its own run, never a reaction inside a lint.
**To see more of the report, read the file - never run `lint` again.** A second run costs a **To see more of the report, read the file - never run `lint` again.** A second run costs a
budget slot and re-measures a corpus that has not changed. The file at step 9 overwrites this budget slot and re-measures a corpus that has not changed. The file at step 9 overwrites this
+22 -4
View File
@@ -11,10 +11,27 @@
# measure the stack's tests - so an addopts entry would break the plain # measure the stack's tests - so an addopts entry would break the plain
# `pytest -q` that every local run and the CI "Tests" step use. # `pytest -q` that every local run and the CI "Tests" step use.
# #
# No `fail_under` yet, on purpose: Gitea #10 sets the threshold in a separate, # `fail_under` lives here rather than as a `--cov-fail-under` flag in the CI
# later commit, once the measured number exists to justify it. A threshold # step, so the number sits next to the reasoning that produced it and applies to
# picked before the number is either too low to bite or too high to survive the # any `--cov` run, not just the one CI happens to write.
# next honest commit - and the second kind gets lowered rather than earned. #
# 85, against a measured 87.0% (CI run 163, 6498 statements, 975 tests). Gitea
# #10 held this back until the number had been watched: the first measurement
# was 86.9% of 5105 statements over 730 tests (CI run 87), and between the two
# the measured code grew by a quarter and the suite by a third while the quota
# moved a tenth of a point. That stability is what the threshold rests on.
#
# The two points of headroom are not slack. They are the room the report's own
# taxonomy asks for: a new thin Typer wrapper lowers the total without anything
# having got worse, because its logic is tested beside it (see EVALS.md § "How
# much of the stack the suite reaches"). A threshold at the measured number
# would go red on exactly that commit, and a threshold that goes red for a
# non-reason gets lowered rather than earned - which is the failure mode #10
# existed to avoid, arriving from the other side.
#
# What this number does *not* do is close the genuine gaps - provenance_cmd.py,
# migrate_cmd.py, type_resolver.py. It freezes the state that was reached; the
# gaps are their own work, tracked separately.
[run] [run]
source = chemenu source = chemenu
omit = omit =
@@ -23,3 +40,4 @@ omit =
[report] [report]
show_missing = True show_missing = True
precision = 1 precision = 1
fail_under = 85
+1 -1
View File
@@ -50,7 +50,7 @@ tools/wikitool <command> --help
| `index rebuild [--dry-run]` | Regenerate the catalog from every page's frontmatter: `kb/index.md` becomes a map (statistics, one row per collection and per area, links to the shards) and the page tables are written to a generated `INDEX.md` in each collection. An area past 50 rows gets its own shard. Stale shards from removed collections/areas are deleted in the same pass | | `index rebuild [--dry-run]` | Regenerate the catalog from every page's frontmatter: `kb/index.md` becomes a map (statistics, one row per collection and per area, links to the shards) and the page tables are written to a generated `INDEX.md` in each collection. An area past 50 rows gets its own shard. Stale shards from removed collections/areas are deleted in the same pass |
| `log append --op ingest\|query\|lint\|create\|update\|delete\|rename --title "..." [--body "..."\|--body-file path]` | Append a formatted entry to `kb/log.md` | | `log append --op ingest\|query\|lint\|create\|update\|delete\|rename --title "..." [--body "..."\|--body-file path]` | Append a formatted entry to `kb/log.md` |
| `log status` | Read-only: count `ingest` entries logged since the last `lint` entry - the deterministic trigger behind the Maintenance Schedule's "every 10 sources" full-lint cadence | | `log status` | Read-only: count `ingest` entries logged since the last `lint` entry - the deterministic trigger behind the Maintenance Schedule's "every 10 sources" full-lint cadence |
| `lint [--json] [--markdown out.md] [--full] [--fail-on-error]` | Structural + provenance checks: broken wikilinks, dangling frontmatter references, orphan pages, index drift, schema gaps, duplicate titles, title mismatches, uncovered raw files, broken `raw_files:` refs, raw files claimed by more than one source page, unmarked provenance, citation/frontmatter drift, unbalanced generated-region markers, edges whose label is missing or not authorised by the source collection's `outbound:` (both hard once `kb_version` has reached the release that introduced labelled edges - advisory below it, so a corpus mid-migration is not refused by the check measuring it), quote-limit overages (>2 blockquoted lines/page, advisory only). Prints only the sections that found something and always writes the full report to `reports/Lint Report <date>.md` (or `--markdown`), naming the path - `--full` prints everything, `--json` prints the findings and writes nothing | | `lint [--json] [--markdown out.md] [--full] [--fail-on-error]` | Structural + provenance checks: broken wikilinks, dangling frontmatter references, orphan pages, index drift, schema gaps, duplicate titles, title mismatches, uncovered raw files, broken `raw_files:` refs, raw files claimed by more than one source page, unmarked provenance, citation/frontmatter drift, unbalanced generated-region markers, edges whose label is missing or not authorised by the source collection's `outbound:` (both hard once `kb_version` has reached the release that introduced labelled edges - advisory below it, so a corpus mid-migration is not refused by the check measuring it), `see-also` edges whose reverse direction already carries a specific label (advisory only - redundant rather than wrong, and never migration-gated, since no version turns the redundancy into an error), quote-limit overages (>2 blockquoted lines/page, advisory only). Prints only the sections that found something and always writes the full report to `reports/Lint Report <date>.md` (or `--markdown`), naming the path - `--full` prints everything, `--json` prints the findings and writes nothing |
| `search ["<text>"] [--field <predicate> ...] [--kind/--subtype/--collection/--tag <v>] [--regex] [--limit N] [--sort [-]<field>] [--backend <name>] [--matches] [--json]` | Find pages in `kb/` without reading the index. Text search runs through a pluggable backend (`rg` today); `--field` predicates are evaluated on frontmatter - `f=v`, `f~substring`, `'f>=v'`, `'f:*'` (present), `'!f'` (absent), repeatable and ANDed. With no text this is a pure structured query. Results carry kind/summary/confidence so a hit can be judged without opening the page. A page whose frontmatter does not parse can match no positive predicate, so it is **named** rather than dropped: `--json` always carries an `unreadable` list of `{path, reason}` (usually empty), and the table form writes the same lines to stderr. `--regex` is applied by `rg` alone, whose engine is linear; the ranking boosts for title and summary are literal-containment only, so a non-literal pattern is ranked by match count. `rg` is killed after 30 s and reported as a failure. Read-only, and **exempt from the Iteration Budget Gate** | | `search ["<text>"] [--field <predicate> ...] [--kind/--subtype/--collection/--tag <v>] [--regex] [--limit N] [--sort [-]<field>] [--backend <name>] [--matches] [--json]` | Find pages in `kb/` without reading the index. Text search runs through a pluggable backend (`rg` today); `--field` predicates are evaluated on frontmatter - `f=v`, `f~substring`, `'f>=v'`, `'f:*'` (present), `'!f'` (absent), repeatable and ANDed. With no text this is a pure structured query. Results carry kind/summary/confidence so a hit can be judged without opening the page. A page whose frontmatter does not parse can match no positive predicate, so it is **named** rather than dropped: `--json` always carries an `unreadable` list of `{path, reason}` (usually empty), and the table form writes the same lines to stderr. `--regex` is applied by `rg` alone, whose engine is linear; the ranking boosts for title and summary are literal-containment only, so a non-literal pattern is ranked by match count. `rg` is killed after 30 s and reported as a failure. Read-only, and **exempt from the Iteration Budget Gate** |
| `confidence decay [--apply]` | Recompute every page's derived `confidence` as `confidence_base * (1 - 0.01/month)`, floored at 0.2; dry-run by default | | `confidence decay [--apply]` | Recompute every page's derived `confidence` as `confidence_base * (1 - 0.01/month)`, floored at 0.2; dry-run by default |
| `confidence init-base [--apply]` | One-time backfill: set `confidence_base` from the current `confidence` on pages that predate the derived-confidence model | | `confidence init-base [--apply]` | One-time backfill: set `confidence_base` from the current `confidence` on pages that predate the derived-confidence model |