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

214 lines
11 KiB
Markdown

# 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 |