Chemenu 2.1.0 - deterministischer Wissenskompiler
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:
commit
18ae28f918
368 files changed
+50628
No files matched your search
@@ -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"
|
||||
Reference in new issue
Block a user