Chemenu 2.1.0 - deterministischer Wissenskompiler
CI / verify (push) Failing after 32s
Release / release (push) Successful in 38s

Chemenu kompiliert Rohnotizen zu einem verlinkten, quellengebundenen Wiki:
raw/ -> types/ + tools/ -> kb/ -> reports/. Was mechanisch ist, macht
tools/wikitool; was Urteil braucht, macht ein Agent unter Contracts, deren
Grenzen in Code durchgesetzt sind statt im Prompt.

Dieser Commit ist der Startpunkt der oeffentlichen Historie. Die vorherige
Entwicklung fand in einer privaten Instanz statt und ist nicht Teil dieses
Repositorys; ihre Erzaehlung steht vollstaendig in CHANGES.md, das mit 44
Eintraegen von 0.1.0 bis 2.1.0 erhalten geblieben ist.

Der mitgelieferte Korpus ist ein Testbett und eine Demo: 170 Seiten ueber den
Stack selbst - Gates, Lint, Versionierung, Suche, das Wiki-Muster. Er
dokumentiert das Werkzeug mit den eigenen Mitteln des Werkzeugs.

Lizenz: AGPL-3.0 fuer den Stack (tools/, types/), CC-BY-4.0 fuer die Inhalte.
Die Grenze zwischen beiden ist der Dateiplan, den dist export berechnet -
siehe NOTICE.
This commit is contained in:
torben committed 2026-09-01 16:26:14 +02:00
commit 18ae28f918
368 files changed
+50628

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
"""L0: the pipeline against a wiki the tools built themselves.
Every other test in this suite asks whether one command does its job. This one
asks whether the commands compose - whether a wiki assembled the documented way
comes out structurally clean, and whether the catalog is a fixed point rather
than something that keeps changing every time it is rebuilt.
That is the hard-oracle floor the eval ladder in EVALS.md rests on: before a
score can say anything about how an agent worked, running the tools correctly has
to be known to produce a clean tree.
The wiki here is built through the CLI and starts empty, deliberately on both
counts. `test_lint.py` already checks lint against the hand-written `kb_dir`
fixture, and a hand-written tree cannot show that `new`, `xref`, `index` and
`lint` agree with each other - only a tree the tools produced can.
"""
import re
import pytest
import chemenu.config as config
from typer.testing import CliRunner
from chemenu.commands.index_build import plan_index
from chemenu.commands.lint import HARD_ERROR_KEYS, has_hard_errors, run_lint
runner = CliRunner()
COLLECTIONS = ("entities", "concepts", "sources", "comparisons")
# The body `new` scaffolds carries placeholder wikilinks - `[[Entity 1]]` and
# friends - for the author to replace. Until they are replaced they point at
# nothing, which is a broken link and is meant to be: the scaffold is not a
# finished page. Stripping them stands in for writing the page.
PLACEHOLDER_LINK = re.compile(r"\[\[(?:Entity|Related Concept|Concept|Source)[^\]]*\]\]")
@pytest.fixture
def empty_kb(tmp_path, monkeypatch):
"""A kb/ with the collection layout and no pages at all."""
kb = tmp_path / "kb"
for collection in COLLECTIONS:
(kb / collection).mkdir(parents=True)
(kb / collection / "COLLECTION.md").write_text(
f"# kb/{collection}/ - Collection Contract\n", encoding="utf-8"
)
raw = tmp_path / "raw"
raw.mkdir()
monkeypatch.setattr(config, "KB_DIR", kb)
monkeypatch.setattr(config, "INDEX_FILE", kb / "index.md")
monkeypatch.setattr(config, "RAW_DIR", raw)
return kb
def invoke(args):
from chemenu.cli import app
result = runner.invoke(app, args)
assert result.exit_code == 0, f"{' '.join(args)} failed:\n{result.output}"
return result
def finish_page(path):
"""Replace the scaffold's placeholder links, as an author would."""
body = path.read_text(encoding="utf-8")
path.write_text(PLACEHOLDER_LINK.sub("", body), encoding="utf-8")
def build_wiki(kb):
"""The documented sequence: create, write, link, rebuild the catalog."""
invoke(["new", "entity", "--name", "Pipeline Host",
"--set", "entity_type=system",
"--set", "summary=A host created by the pipeline test"])
invoke(["new", "concept", "--name", "Pipeline Concept",
"--set", "concept_type=workflow",
"--set", "summary=A concept created by the pipeline test"])
for page in kb.rglob("Pipeline *.md"):
finish_page(page)
invoke(["xref", "add", "--a", "Pipeline Host", "--b", "Pipeline Concept"])
invoke(["index", "rebuild"])
def test_a_scaffolded_page_is_not_yet_a_finished_one(empty_kb):
"""`new` writes placeholder links, so lint reports them until the page is
written. Recorded here because it is easy to mistake for a defect: a freshly
scaffolded page does not lint clean, and should not."""
invoke(["new", "concept", "--name", "Scaffold Only",
"--set", "concept_type=pattern", "--set", "summary=Untouched scaffold"])
report = run_lint(empty_kb)
assert report["broken_links"], \
"the scaffold no longer carries placeholder links - update this test"
assert {f["page"] for f in report["broken_links"]} == {"Scaffold Only"}
def test_a_wiki_built_by_the_tools_lints_clean(empty_kb):
build_wiki(empty_kb)
report = run_lint(empty_kb)
found = {key: report[key] for key in HARD_ERROR_KEYS if report.get(key)}
assert not has_hard_errors(report), f"hard errors after a clean build: {found}"
def test_the_pages_reach_each_other(empty_kb):
"""`xref add` is what makes two pages findable from one another; if it and
the link checker disagreed, the lint above would report a broken link."""
build_wiki(empty_kb)
assert run_lint(empty_kb)["orphan_pages"] == []
def test_the_catalog_covers_what_was_created(empty_kb):
build_wiki(empty_kb)
report = run_lint(empty_kb)
assert report["missing_from_index"] == []
assert report["dangling_index_entries"] == []
def test_rebuilding_the_catalog_changes_nothing(empty_kb):
"""A rebuild has to be a fixed point.
`test_index_build.py` checks that the *planner* is stable - two calls return
the same plan. This is the stronger property: after the plan has been written
to disk, planning again over the changed tree produces the same files with
the same contents, and what is on disk matches. A catalog that drifted on
every rebuild would make lint's index-drift check fire on a tree nobody
touched.
"""
build_wiki(empty_kb)
after_first = plan_index(empty_kb)
invoke(["index", "rebuild"])
after_second = plan_index(empty_kb)
assert after_second == after_first
on_disk = {path: path.read_text(encoding="utf-8") for path in after_second}
assert on_disk == after_second, "the written catalog differs from the plan"