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.
11 KiB
Conversation Transcript - ENVIRONMENT.md as an Optional Third Session-Level File 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. Torben's instructions and decisions are quoted verbatim; issue text, file excerpts, tool output and the Mass-Update Gate's printout are real, taken from the session; the assistant's reasoning is condensed. No second-hand material - no subagent was used. No credentials appeared; the Gitea MCP calls used the session's own configured access, not a token pasted into chat. One of two transcripts cut from this session; the other covers coverage reporting in CI (Gitea #10) and thedist exportleak it uncovered - bundled into the same publish, but a different subject.
Covers Gitea issue #24: a third root document alongside USER.md and SOUL.md, recording what
this checkout works through - harness, published skills, MCP servers, connectors, git remotes,
where CI runs. Shipped as stack 1.8.0 in commit a243a4a, issue closed. The session opened
with a mismatch between the issue number Torben named and the work he described, and resolving
that by asking is the first turn.
Turn 1 - the issue number did not match the task
Torben's instruction, verbatim:
/stack-dev implementiert #10"Implementiere eine kleine Erweiterung im dev skillset: eine checkout spezifische Datei soll verzeichnen, über welche skills/connectoren/mcp-server/remotes gearbeitet wird. Das sind für Entwicklungen tendenziell konstante Werte, du musst sie aber immer wieder abfragen. Das wäre also Ein File, was beim bootstrapping vom jeweiligen harness (hier: Claude Code) verlinkt wird. Es soll optional sein."
The assistant read #10 first. Its title is "Coverage-Reporting: erst messen, dann eine Schwelle setzen" - pytest-cov in CI, no threshold until the number is known. Nothing to do with a checkout-specific environment file. A scan of all twelve open issues found no issue matching the description either.
Decision: ask rather than guess. Two readings led to materially different work, and both were plausible - a transposed digit, or two requests in one message. The alternative considered and rejected was implementing the described feature and treating "#10" as a slip, which would have silently dropped a work package Torben may have meant.
Torben's answers to the three questions:
- Scope: "Beides in dieser Session" - both work packages, not one.
- Placement: "Root, für jede Instanz" - not dev-only.
- Name: "ENVIRONMENT.md".
The described feature had no issue, so one was filed as #24 (prio/2, size/M) before the
changelog entry was written, so the entry could name a number that already existed.
Turn 2 - why not instructions/dev/
Torben's phrasing was "eine kleine Erweiterung im dev skillset", which points at
instructions/dev/. The assistant surfaced a hard constraint from
instructions/CONTRACT.md before offering the choice:
tools/wikitool instructions verifyalso reports anything under it that is referenced from outside it, because such a reference would dangle the momentdist exportruns.
So a CLAUDE.md link into instructions/dev/ is a boundary violation unless it is wrapped in
<!-- dist:strip-start/end --> markers - the one sanctioned crossing, used today by exactly one
routing line in AGENTS.md. That is more mechanism for less reach.
Rejected: dev-only placement. The content - which MCP server answers which question, which
remote publish talks to - is not development-specific. A distributed instance running content
sessions has the same questions. Torben chose root.
Turn 3 - the three properties that separate it from the Personalization Plane
The existing pattern (kb/concepts/Personalization Plane.md, stack 1.1.0) is: ship a
.template, fill it during setup, check it with doctor. ENVIRONMENT.md reuses the shape and
diverges on three points, each deliberate.
Optional, and doctor never FAILs. check_environment() returns OK when the file is
absent, OK when filled, and WARN only for a renamed-but-unfilled template. Quoting the
docstring written for it:
Missing it costs a session some questions, not correctness, so this check never FAILs - the whole point of the file is that it is optional, and a FAIL would make it mandatory by the back door.
Rejected: FAIL on missing, which is what personalization does. That check is right for
USER.md/SOUL.md, which are an operating requirement; here it would have converted "optional"
into "mandatory with a nicer word".
The one case still worth reporting is the failure mode a plain existence check misses: a file
that is present, loaded into every session, and answers nothing. Same sentinel as the
personalization pair, wikitool:template-unfilled.
Gitignored, not committed. USER.md/SOUL.md are committed here and excluded from
dist export by the root allowlist. ENVIRONMENT.md goes further and is gitignored, on the
argument that two clones of one repo are two different environments - a committed copy would
hand the second clone answers that are wrong rather than missing, and wrong is worse here
because the file gets believed.
That created a trap worth pinning: the careless pattern ENVIRONMENT.md* swallows the template
too. docs verify now asserts both halves - ENVIRONMENT.md in REQUIRED_IGNORE_CANARIES,
ENVIRONMENT.md.template in REQUIRED_TRACKED_PATHS. The .gitignore entry is anchored
(/ENVIRONMENT.md) per that file's own header rule.
Context, not authority. Written into the template, AGENTS.md and the file itself: it
describes what is there, not what is permitted. A remote listed in it does not authorize a
git push - invariant 5 still routes through wikitool publish - an MCP server listed in it
opens no gate, and nothing in it is a source under invariant 3. No credentials: it sits in
plaintext in the working tree and in every agent's context.
Turn 4 - import or link in CLAUDE.md
instructions/CONTRACT.md frames this as a question about when the decision is made: an
import for what is decided in passing, a link for what is looked up deliberately. An import is
the strongest and most expensive load in the layer, charged to every session on that harness.
Decision: @ENVIRONMENT.md, an import. The reasoning written into CLAUDE.md:
a session that has to go look the answer up will instead ask the user again, which is the cost the file exists to remove.
The file is also the first import that may legitimately never exist. CLAUDE.md already
tolerates unresolved imports - USER.md/SOUL.md do not exist during the setup session - so
the tolerance was already proven; here it becomes a permanent state rather than a transitional
one.
Two knock-on edits the change forced: CLAUDE.md's closing paragraph said "The fourth import is
the harness-specific one", which the new import made wrong, so both ordinals became "the last
import". AGENTS.md got a file-naming row and a separate ## Environment section, because
the other harnesses read AGENTS.md and never see CLAUDE.md.
Rejected: a wikitool environment command to scaffold or query the file. It is read often
and written rarely; a command for it would be machinery with no consumer. The template plus two
instruction steps is the whole surface.
Turn 5 - where the file gets created
Two entry points, both optional, both with the same rule:
instructions/bootstrap.mdstep 5 (new), for a clone - which never has the file, because it is gitignored.instructions/setup-instance.mdstep 9 (new), for a fresh distribution.
Inserting a step into setup-instance.md meant renumbering 9-13 to 10-14, and one cross-
reference inside step 6 pointed at the doctor step by number ("Schritt 11") and had to move to
12. Three other documents reference "Schritt 6 (Personalization)" - INSTALL.md twice,
bootstrap.md once, CHANGES.md three times - and step 6 did not move, so those stayed correct.
The rule written into both steps, and into the template:
eintragen was ablesbar ist, einmal fragen, "weiß ich nicht" akzeptieren, einen leeren Abschnitt löschen statt ihn mit Plausiblem zu füllen
A guessed line is worse than a missing one, for the same reason the file is gitignored.
Turn 6 - human docs, then publish
Per the stack-dev skill's rule that a stack change is unfinished until the human docs describe
it: README.md's architecture tree gained a row and its CLAUDE.md line was corrected;
INSTALL.md gained a paragraph in "Weg C", a line in the doctor summary and a troubleshooting
entry for environment: WARN; tools/CONTRACT.md's doctor row names the new check.
Verification before publishing: 730 tests, docs verify, instructions verify,
lint --fail-on-error, and a full replay of the CI smoke test - dist export into a scratch
directory, git init, stub personalization, venv, instructions sync, index rebuild,
sources rebuild-index, then doctor/docs verify/instructions verify/lint/
migrate status. doctor reported environment: ENVIRONMENT.md absent (optional) there, which
is the intended answer for a fresh instance.
publish exited 42 at the Mass-Update Gate (23 files, threshold 10). Per invariant 6 the
full breakdown was reproduced to Torben and the session stopped. His clearance, verbatim:
"Wenn wir im Rahmen der ci scripts nichts mehr brauchen ist der aktuelle publish freigegeben"
The assistant checked nightly.yml and release.yml first - neither runs pytest, and the new
doctor check can never FAIL, so neither needed anything - then published with the token.
Turn 7 - the file for this checkout
Torben, verbatim:
"Erstelle eine Environment.md für diese Session. Sei kurz und prägnant, die meisten Infos solltest du ja haben."
A first version had carried the template's full preamble. Rewritten to 40 lines holding only
values: Claude Code primary with Codex/Copilot/Vibe alongside, six published skills including
stack-dev, the gitea-mcp server (with the standing rule that Gitea is read through it and
not through curl), origin at ssh://git@gitea.nehmer.net:10101/torben/llm-wiki-test1.git with
the private-repo token note, CI on Gitea Actions with its three workflows, plus two standing
facts - this repo is a testbed rather than the production instance, and contracts/instructions
are English while kb/ content and changelog entries are German.
doctor then reported environment: ENVIRONMENT.md present and filled, and
git check-ignore -v confirmed .gitignore:111:/ENVIRONMENT.md keeps it out of every commit.
Outcome
| Version | 1.8.0 (--minor: new capability, backwards compatible) |
| Commit | a243a4a (shared with the coverage work, 23 files, +589/-24) |
| Issue | #24 filed and closed in this session |
| Tests | 730 passing; new tests for three doctor outcomes, the dist export split, both ignore-canary halves |
| Verified | docs verify, instructions verify, lint --fail-on-error, full setup-instance replay against a fresh export |
| CI | Run 87 green, all nine steps; release run 88 green |
| Gate | Mass-Update Gate, exit 42, cleared by Torben after the file list was shown |