Files
chemenu/raw/notes/Conversation Transcript - Comma Bug, Budget Refund and Lint Report Path Session 2026-08-31.md
torben 18ae28f918
CI / verify (push) Failing after 32s
Release / release (push) Successful in 38s
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.
2026-09-01 16:26:14 +02:00

11 KiB

Conversation Transcript - Comma Bug, Budget Refund and Lint Report Path 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. Reconstructed by the assistant at the end of the session. Torben's instructions are quoted verbatim where they are short; the assistant's reasoning is condensed. Command outputs quoted below are real. No credentials appeared in the session. One of three transcripts cut from the same session; the other two cover issue triage and planning, and the harness's auto permission mode.

Covers the fix for Gitea issues #12 and #13, the second defect it uncovered in frontmatter serialization, and the restoration of a raw file that had been renamed to work around the bug. Resulting commits: 40adbb7 (stack 1.2.0), 5426a6e (content correction). Gitea issues #12 and #13 were closed by this session; issue #14 was opened from a gap it exposed.


Turn 1 - /stack-dev "Fixe 12 und 13. verdopple die Tool call Limits zusätzlich."

Torben had just received a rough prioritisation of all open issues (see the companion transcript on issue triage) and picked the top two off it. The added instruction: "verdopple die Tool call Limits zusätzlich. Wir sind da schon immer sehr knapp unterwegs."

The assistant read the affected code before touching it: commands/_util.py, commands/new_page.py, commands/run_budget.py, commands/lint.py, cli.py, tools/CONTRACT.md, instructions/gates.md.

Issue #12 - --set cannot express an array value containing a comma

parse_list() split hard on , with no escape. Shell quoting is no help: the quotes are gone long before the value reaches the parser. A raw_files: path with a comma in the filename was therefore not expressible, and during the ingest of 2026-08-30 the raw file had been renamed to fit the flag - a violation of raw/CONTRACT.md's immutability rule.

Both proposals from the issue were implemented, because they serve different cases:

  • \, is a literal comma that survives the split, implemented as a lookbehind (re.compile(r"(?<!\\),")) plus an unescape per element. This also reaches xref add --entities, which uses the same helper.
  • Repeating --set for an array field now appends instead of replacing. Scalar fields keep "last one wins" - there is nothing to append to. This is the separator-free form and therefore the right one when an element contains a comma.
  • _check_raw_files_exist now names the comma split as the cause and both ways out, explicitly including "never rename the raw file to fit the flag".

The second defect, found by the test rather than by the issue

The end-to-end test - create a raw file whose name contains a comma, reference it with \,, read the written page back - still failed. Escape and append were correct; the file was not.

dump_frontmatter writes lists in flow style ([a, b]), but _format_scalar decided on quoting using a document-level round-trip probe, where a comma is an ordinary character. Inside [...] it is an indicator:

raw_files: [raw/notes/Versioning, CI-CD.md]   # reads back as two elements

So --set would have parsed the value correctly and the write would have taken it apart again. Fixed in frontmatter_io.py:

  • _round_trips_as_string(text, flow=True) probes in the context the value is actually written in - it still asks the YAML loader rather than enumerating rules, which is the file's stated design principle.
  • A new _quote() helper asks the dumper for a one-element flow sequence and strips the brackets, because a bare plain scalar from safe_dump comes back carrying a ... document-end marker - correct for a document, nonsense inside a list.

Verified that existing output is unchanged: tags: [k8s, ci-cd] stays unquoted, year: '1945' stays quoted exactly as before. Only values that used to break silently are now quoted.

Issue #13 - the budget counted friction rather than iteration

Refund on a declined call. The design decision was where to draw the line. Keying the refund on exit code 1 alone would have made lint --fail-on-error free whenever it found something. The assistant keyed it on _util.fail() instead:

  • fail() means the command declined - a rejected argument, or a read-only check reporting findings. Nothing happened; refund.
  • A command that already did its work and then reports a non-zero result raises typer.Exit(1) directly and stays counted. lint --fail-on-error is exactly that case: it has written its report first.

record_and_check() now returns whether it charged, and cli._run_traced calls run_budget.refund() in its finally block when the process left through fail(). The call stays in recent, so the loop-breaker still sees it - the issue itself argues that the loop-breaker, not the counter, is the right instrument for a repeated broken invocation.

Considered and rejected: marking mutations at the write sites (35 sites across 15 files) so the refund could be keyed on "nothing was written". Rejected as fail-open - a new write site that forgets the marker would silently weaken a gate.

Ceiling 30 → 60, per Torben's instruction. The calibration band (5-15 simple, 15-25 complex) was left alone: it describes the work. The ceiling described nothing - it sat so close to the band that the overhead of a real ingest reached it on its own. Pulled through AGENTS.md, instructions/gates.md, tools/CONTRACT.md, README.md, the work plan template, and the unit sizing in migrate-corpus.md (now "near 55 pages; aim for 48 or fewer").

The loop-breaker was deliberately left at 3. The assistant flagged this back to Torben rather than doubling it silently: it is a detector for three identical calls, not a budget, and doubling it would let a stuck agent spin twice as long.

Issue #13 - lint forced a second call

The finding turned out sharper than the issue's headline. Without --markdown, lint wrote no file at all - it dumped the full report to stdout. So there was no path to name, and no way back to a skipped section except a second run.

  • The full report is now always written, by default to reports/Lint Report <date>.md, and the path is printed. --markdown still overrides the target.
  • Only sections that found something are printed. --full prints everything; --json prints the findings and writes nothing.
  • wiki-lint and wiki-status were both updated to say: read the file, do not run lint again. wiki-status step 3 now takes the hub statistic from the report file, because it is a statistic rather than a finding and no longer appears in the printed summary.

Not implemented: exempting lint from the budget entirely (the issue's third proposal). The issue itself says that should be decided separately, and the case has moved - lint now writes a file.

Verification

658 tests pass. Two of the new tests initially failed under the hardened environment from issue #8 (GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null HOME=<empty>) with ERROR No author configured for this instance - the third and fourth instance of that same silent environment dependency. Both now set WIKI_AUTHOR themselves, and the suite is green in both environments.

The refund was measured against the real tool rather than only in tests:

$ tools/wikitool new source --name "Refund Probe" --set "raw_files=raw/notes/Versioning, CI-CD.md"
ERROR raw_files path does not exist: raw/notes/Versioning
  This is one element after splitting the value on commas. ...

$ tools/wikitool budget status
Calls so far: 0 (limit 60)
Recent calls:
  - new source --name Refund Probe --set raw_files=raw/notes/Versioning, CI-CD.md

docs verify, instructions verify and doctor all clean. lint clean over 255 pages.


Turn 2 - the Mass-Update Gate fires, and Torben clears it

publish refused with exit 42 on a 21-file changeset, printing the breakdown by area and the --confirm 8c3ee8ff0ad6 line. The assistant reproduced the full file list in its reply - the groups, the paths and the per-file line counts - and stopped, as the gate's own output demands.

Torben replied: "Freigegeben". The confirmed publish produced 40adbb7:

[main 40adbb7] fix: --set-Arraywerte mit Komma, lint nennt Reportpfad, Budget-Refund und Ceiling 60 (1.2.0)
 21 files changed, 593 insertions(+), 73 deletions(-)

Verified afterwards that HEAD equals origin/main, the tree is clean, and VERSION reads 1.2.0 - rather than trusting the tool's own success line.


Turn 3 - "Benenne die Referenz mit Komma um, damit sie wieder stimmt"

The raw file renamed during the 2026-08-30 ingest got its original name back:

raw/notes/Conversation Transcript - Versioning, CI-CD and Content Migration Session 2026-08-30.md

Done with git mv (100% similarity, history preserved). The source page's raw_files: entry and its **Raw-Dateien:** prose line were corrected, sources rebuild-index rebuilt the provenance index, sources coverage reported 0 uncovered and 0 broken references, lint was clean over 255 pages, and a log append --op update entry recorded what happened and why.

Worth noting: the corrected value is now written quoted in the frontmatter - raw_files: ['raw/notes/…Versioning, CI-CD…'] - which is exactly the second defect fixed earlier in the session. Without that fix the rename would have taken itself apart again on write.

Published as 5426a6e, five files, below the Mass-Update Gate threshold.

The gap this exposed - issue #14

No wikitool command writes raw_files: on an existing page. touch handles the fields that describe the page itself; xref handles the page-reference arrays (related:/sources:/entities:/concepts:), and raw_files: is none of those - it points at a path, not a page title. new source --set raw_files=… writes the field once, at creation, and never again.

AGENTS.md invariant 1 does not forbid hand-editing it - its list names the catalog, log.md, provenance.md, the skill directories, the two JSON files and the page-reference arrays, and raw_files: is in none of them. But it contradicts the core principle that anything mechanical is done by the tool. lint and sources coverage report broken raw_files: references reliably; nothing can repair them. That asymmetry sends an agent into exactly the hand-edits the rest of the stack is built against.

Filed as issue #14, proposing sources relink or, more closely, a raw rename that does the git mv and every referencing source page in one step - the only form where the intermediate state "file gone, reference dangling" never exists.


Outcome

Artifact Result
Stack version 1.2.0 (MINOR - new capability, backwards compatible)
Commits 40adbb7 (fix), 5426a6e (content correction)
Tests 658 passing, in the normal and the hardened environment
Gitea #12 and #13 closed with the reasoning; #14 opened
CI Runs 62, 63, 64 all green