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.
7.1 KiB
Conversation Transcript - Two Round-Trip Defects Found by an Ingest Session
Source: Claude Code session (
claude-opus-5), llm-wiki-test1 workspace Collected: 2026-08-31 Participant: Torben Fidelity: faithful summary transcript, not a verbatim log. Command output and measured counts quoted below are real. The subagent's findings are second-hand relative to this session and were independently verified before being acted on - where that verification changed or extended the claim, the transcript says so. No credentials appeared in the session.
Covers two defects an ingest exposed, both fixed the same day: cite add deleting any content
behind the Footnotes block (1.5.1, bb4123b, issue #17) and a source page's reference arrays
being unreachable while xref add wrote a field the schema rejects (1.6.0, ce03749, issue
#18). Both were prio/1. Both had lived under a fully green test suite.
Turn 1 - the ingest reports two tool defects
A subagent ingesting the touch --set transcript published successfully (8524bce) and then
reported two things it had run into. The assistant did not act on the report directly.
SOUL.md treats a confidently asserted fact as the cardinal error, and a subagent's finding is
second-hand; both claims were reproduced first.
Verification of the first claim - and a hold
split_cite_block() was read directly. The mechanism was exactly as reported: everything from
the ## Fußnoten heading to the end of file is taken as the block, only [^id]: lines are kept
from it, and every caller reassembles the page as head + rendered block.
A scan over the corpus then measured the exposure rather than estimating it: 8 pages carrying
74 lines in the doomed position, Detect-Repair Asymmetry worst at 14.
The second ingest was held at this point. It would have called cite add on pages in that
list. Continuing would have destroyed content to save a few minutes.
Verification of the second claim - and a correction of scope
xref_link_source was read: it writes only the target pages and never the source page's own
arrays. Confirmed. types/source.md declares page_ref_fields: [entities, concepts], so the
related: that xref add had written was undeclared, and strip_frontmatter_ref() swept only
declared fields - so nothing could remove it. One command creating a state another could not
undo.
Both were filed as issues (#17, #18) before any code was touched, per
instructions/capture-session.md: what is still open belongs in the tracker, not in a
transcript or in someone's head.
Turn 2 - "Korrigiere zunächst #17"
The fix, and the option that was rejected
The block now ends at the next heading instead of at end of file. Everything after it - and anything inside it that is not a citation definition - is folded back on to the head.
Two properties turned out to matter more than the repair itself:
- The page self-heals. Because the rendered block is always emitted last, the first citation
operation puts a page that had drifted into the broken layout back in order.
xref addmay keep appending at end of file without doing harm - the contradiction between the two commands is defused rather than merely avoided. - Loose text inside the block is rescued, not rejected. The issue's own third acceptance
criterion asked for an abort. That was declined with a reason: the same code path runs under
lintandcorpus_diff, where raising would refuse to read a page instead of reporting it. Rescuing is strictly better than aborting and satisfies the intent - nothing is discarded silently.
Reading the code added a third affected command the issue had not named: rename uses the
same path and would have deleted the same content. And a second loss path with the same cause -
a [^id] referenced only in a section behind the block counted as unreferenced, so cite sync would have pruned its definition as an orphan.
The test was proved red before it was trusted
Rather than asserting that the new test would have caught the defect, the old implementation was reconstructed and run against it:
ALTER Code -> Beziehungen erhalten: False
NEUER Code -> Beziehungen erhalten: True
Corpus repaired and measured
cite sync --all normalised eleven pages (the eight at risk plus three needing only a
re-ordering). Afterwards: 0 pages with content behind the block, and per page an unchanged
count of citation definitions and bullets - checked, not assumed. The line-count asymmetry in
the diff came from summary: being re-wrapped on to one line, not from lost content.
Turn 3 - "Mache mit 18 weiter"
Three defects, one cause.
xref link-source writes both directions. Which field a target lands in follows its
collection: kb/entities/ → entities:, kb/concepts/ → concepts:. The directory is
the field name, so a new collection needs no code change here - it needs a type declaring the
matching field. That was chosen over a type-to-field map precisely because a map is a second
copy of something the type-specs already say.
xref add refuses an undeclared related:, checking both pages before writing either, so a
refusal cannot leave half a link. The message names the fields the type does declare and the
command that fills them.
xref remove sweeps undeclared leftovers, with the field names taken from the type-specs
rather than a constant. An undeclared field that ends up empty is dropped outright: the key was
never valid for that type, and related: [] would keep the page failing validation.
The repair, done with the tool
No rm --yes, no hand-edited frontmatter. xref remove cleared the leftover, xref link-source recorded both concepts in both directions. lint reports no schema error in the
corpus.
The referencing concept page came through the cycle byte-identical - xref remove cleared
its back-reference bidirectionally and link-source restored it exactly. That was the real
proof that the two commands are inverses.
What the session concluded about itself
The denylist shipped in 1.4.0 that morning was right; its pointer was not. It refused
page-reference fields with "use xref add / xref remove" - an unverified claim about another
command's capabilities, and false for exactly the fields a source page has. A refusal that
routes to another command should ship with a test showing that command covers the case.
Both defects lived under a fully green suite. 678 tests passed before the cite add tests
were written; 67 gate tests passed before the counting change earlier the same day. In each case
no test had ever asserted the wrong behaviour, which is how it survived. The same finding
strengthens the premise of issue #8: a suite verifies what it knows about.
Outcome
| Artifact | Result |
|---|---|
| Stack versions | 1.5.1 (PATCH), 1.6.0 (MINOR) |
| Commits | bb4123b, ce03749 |
| Tests | 689 passing, in the normal and the hardened environment (11 new) |
| Corpus | 8 pages de-risked and normalised; the schema error on main repaired |
| Gitea | #17 and #18 closed |