Teil A aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). Folge-Issues: #178 (Intake-Lauf, baut hierauf auf), #179 (Leitlinien-Export, definiert den Export-Kopf, den diese Erfassung ausschließt). Kommandonamen sind beim Bau noch änderbar, die Eigenschaften unten nicht. Gegen den Baum geprüft am 2026-10-04 (Stand 8ff22ad).
Problem
Doku aus einem Git-Repository kommt heute nur von Hand in eine Instanz: Dateien nach incoming/ kopieren, umbenennen, um der globalen Stem-Regel auszuweichen, pro Unterordner ein Bundle. Nichts hält fest, aus welchem Repo und Commit ein Bundle stammt, und nichts meldet, wenn sich das Repo seitdem geändert hat. Ein Ordner-Bundle lässt sich außerdem nicht als Ganzes durch eine neue Fassung ersetzen (raw_cmd._folder_collision_message: "A folder has no --replaces"; --page scheitert an verschachtelten Bundles, weil _plan_file_moves alle raw_files: in einem Verzeichnis verlangt, raw_cmd.py Z. 251-257).
Befund nebenbei, wird hier mit behoben:config.RAW_IGNORE_NAMES ({".gitkeep", ".DS_Store", "CONTRACT.md"}) wird in config.iter_raw_files per Dateiname in jeder Tiefe angewandt. Gemeint ist nur raw/CONTRACT.md (die Stage-Vertragsdatei), getroffen wird aber jede CONTRACT.md unter raw/ - ein erfasstes */CONTRACT.md (das chemenu-Beispiel in #171) wäre für sources coverage und lint unsichtbar. Heute liegt keine solche Datei unter raw/ (geprüft), der Fehler wird erst durch diese Erfassung erreichbar. Dieselbe Funktion ist die, in der _capture.json ausgenommen werden muss (Punkt 4).
Ref-Regel: ein Branchname (main) oder ein Tag-Muster (v* → das jüngste passende Tag nach Versionssortierung, git ls-remote --tags + -v:refname-Semantik). Aufgelöst zu genau einem Commit; bei einem annotierten Tag ist das der gepeelte Commit (^{}-Zeile).
Holt diesen Commit per Ref-Name flach (--depth 1) in einen gitignorierten Cache: /tools/.wikitool_capture/<sha256 der URL>/, ein Bare-Repo pro URL, Zugriff unter chemenu.filelock. Nie per SHA - das verlangt serverseitig allowReachableSHA1InWant (siehe Punkt 3, warum das auch nie nötig ist).
Dateien werden als Blobs gelesen (git ls-tree -r -z <commit> + git cat-file), nie über einen ausgecheckten Arbeitsbaum: so ändern core.autocrlf, Smudge-Filter oder eine Host-Git-Konfiguration kein Byte. Ziel: byte-identisch zum Blob.
Glob-Abgleich in Python, nicht über Git:git ls-tree unterstützt keine :(glob)-Pathspecs (geprüft: "Pfadspezifikationsangabe wird von diesem Befehl nicht unterstützt: 'glob'"), und PurePosixPath.full_match gibt es erst ab Python 3.13 (Mindestversion ist 3.11). Semantik wie Gits glob-Pathspec: */?/[...] überspannen kein /, ** als ganzes Segment überspannt beliebig viele Verzeichnisse (auch null). Eigene Übersetzung nach Regex, mit eigenen Tests; fnmatch taugt nicht (sein * überspannt /).
Schreibt die Dateien, die auf mindestens einen Glob passen, mit ihren Repo-Pfaden nach incoming/<bundle>/. Existiert incoming/<bundle> schon, Refusal vor jedem Schreiben. Pfadbudget (_util.path_budget_problem_for, gemessen am späteren Ziel raw/<YYYY>/<MM>/<bundle>/<repo-pfad>) wird vor dem Schreiben geprüft; ein zu langer Pfad ist eine Refusal mit den betroffenen Pfaden, kein stilles Auslassen.
Schreibt incoming/<bundle>/_capture.json: schema (1), repo, ref, commit, paths (die Globs), captured (ISO-8601 UTC), fidelity, authority, files (Liste der erfassten Pfade, sortiert). Das Manifest ist die einzige Deklaration, dass und wie die Instanz dieses Repo verfolgt - es gibt keine zweite Konfigurationsdatei.
Ausschlüsse, mechanisch, je namentlich ausgegeben: Dateien, deren erste Zeile mit <!-- wikitool:export beginnt (Leitlinien-Export, #179); Symlinks (Modus 120000) und Submodule (160000); Dateien über web_capture.MAX_BYTES (25 MiB, das Limit von raw fetch); Pfade mit einem Segment, das mit . beginnt (raw accept verweigert einen Ordner mit versteckten Einträgen, _walk_folder, und iter_raw_files überspringt sie - ohne diesen Ausschluss scheiterte jede Erfassung mit .github/** erst beim Accept); jede Datei namens _capture.json (reserviert, Punkt 4); Git-LFS-Zeiger (Blob beginnt mit version https://git-lfs.github.com/spec/v1) - erfasst würde sonst der Zeiger statt des Inhalts.
Keine Zugangsdaten in wikitool: Git läuft mit den Zugangsdaten des Hosts (toolpaths.git(), der vom Preflight erfasste Pfad). Erlaubt sind nur ssh://, https:// und die scp-Kurzform user@host:pfad; eine https://-URL mit Passwort-Anteil im Userinfo (https://user:token@…) wird abgewiesen, weil das Manifest committet wird. Git wird mit GIT_ALLOW_PROTOCOL=ssh:https aufgerufen, damit ext::, fd::, file:// usw. auch auf Git-Ebene abgewiesen werden. Unbeaufsichtigt lauffähig (#178): GIT_TERMINAL_PROMPT=0, stdin geschlossen, Timeout je Git-Aufruf; ein Repo, das nach Zugangsdaten fragt, ist "nicht erreichbar", kein Hänger.
--update <raw-bundle> liest URL, Ref-Regel und Globs aus dem Manifest des bestehenden Bundles und erfasst dessen aktuellen Stand nach incoming/<gleicher bundle-name>/ - der Aufruf, den der Intake-Lauf (#178) benutzt. --fidelity/--authority sind hier optional (Vorgabe: Werte des alten Manifests); angegeben überschreiben sie diese - der sanktionierte Korrekturweg, analog zu raw accept --replaces. Dieselbe URL-Prüfung wie beim Erstaufruf: ein Manifest unter raw/ ist committeter Text, kein vertrauenswürdiger Befehl.
2. raw accept für ein erfasstes Bundle
Erstaufnahme: wie heute ein Ordner (raw accept incoming/<bundle>). Trägt der Ordner ein _capture.json, kommen fidelity/authoritynur aus dem Manifest; --fidelity/--authority auf der Kommandozeile sind dann eine Refusal (eine zweite Quelle für denselben Wert wäre die zweite Deklaration, die das Manifest ausschließen soll). Korrigiert wird über raw capture --update.
raw accept --replaces-bundle <raw-bundle> incoming/<bundle> ersetzt ein Ordner-Bundle als Ganzes an seiner bestehenden Adresse (raw/<YYYY>/<MM>/<bundle>/ bleibt, kein neuer Shard; die Stem-Kollision mit sich selbst ist hier keine). Refusal, ohne dass sich unter raw/ etwas ändert, wenn: eins der beiden Bundles kein _capture.json hat; die repo-URLs der Manifeste abweichen; der Ordnername in incoming/ vom Bundlenamen abweicht (wie --replaces bei Dateinamen); --page oder --replaces mitgegeben ist; eine Datei des Bundles mehr als eine besitzende Quellseite hat (wie _replace).
Dateien, die es in der neuen Fassung nicht mehr gibt, werden entfernt, leer gewordene Verzeichnisse mit rmdir (nie rekursiv) - git log ist das Archiv, wie bei --replaces. Invariante: nach dem Lauf enthält das Bundle genau die Dateien des neuen Manifests plus _capture.json, sonst nichts; incoming/<bundle>/ ist leer und entfernt.
raw_files: ändert der Befehl nicht (wie --replaces): welche Quellseite eine neue Datei bekommt, ist Ingest-Urteil, nicht Mechanik. Weichen fidelity/authority des neuen Manifests von den Werten der besitzenden Quellseite(n) ab, werden sie dort überschrieben (wie _replace mit _overwrite_capture_fields_on_page).
Ausgabe: je Datei A/M/D (M = Bytes verschieden). Gruppiert nach besitzender Quellseite (provenance.source_pages_by_raw_file), deren zitierende Seiten (provenance.citing_pages) einmal je Quellseite, nicht je Datei - Fußnoten-Qualifier ([[Source - X|datei]]) sind freie Strings und lassen keine verlässliche Zuordnung pro Datei zu. Folgezeilen: A → touch "<Quellseite>" --add raw_files=<pfad> oder eine neue Quellseite; D → touch "<Quellseite>" --remove raw_files=<pfad>, und wo das die Seite ohne raw_files: ließe, instructions/page-lifecycle.md § Delete.
--replaces und --page verweigern ein Ziel innerhalb eines erfassten Bundles (Verzeichnis mit _capture.json). Beides würde den Bundle-Inhalt vom Manifest-Commit lösen; ein flaches erfasstes Bundle (nur README.md, AGENTS.md) fiele sonst nicht unter die bestehende Verschachtelungs-Refusal. Die Refusal nennt raw capture --update + --replaces-bundle.
3. raw status (nur lesend, schreibt nichts außer in den Cache)
Liest jedes _capture.json unter raw/, löst je Repo die Ref-Regel per git ls-remote auf. Weicht der Commit ab, wird nur der neue Commit geholt und gegen den Bundle-Inhalt unter raw/ abgeglichen, nicht gegen den alten Commit: das Bundle ist nach Kriterium 1 byte-identisch zum alten Commit, und so muss kein unbeworbener SHA geholt werden. Abgleich mit denselben Globs und denselben Ausschlüssen wie raw capture (eine gemeinsame Funktion, nicht zwei), sonst erschiene jede ausgeschlossene Datei dauerhaft als A.
Ausgabe je Repo alt → neu und je Datei A/M/D, gruppiert wie bei --replaces-bundle. Ein Repo, dessen Commit sich bewegt hat, ohne dass sich innerhalb der Globs etwas ändert, erzeugt keine Meldung. Ein nicht erreichbares Repo, eine abgewiesene URL oder ein unlesbares _capture.json ist eine Zeile in der Ausgabe, kein Abbruch; der Exit-Status ist 0, solange der Befehl selbst lief.
--json für den Intake-Lauf (#178): je Repo bundle, repo, ref, old, new, changed (bool), files ([{path, status}]), error (oder null).
4. Abdeckung:_capture.json ist ein reservierter Name, Metadatum des Bundles und keine Quelle. config.iter_raw_files lässt es aus (in jeder Tiefe - raw capture nimmt nie eine Repo-Datei dieses Namens auf, also kann jedes Vorkommen nur ein Manifest sein); sources coverage und lint melden es nicht als unabgedeckt, und es steht in keinem raw_files:. Im selben Zug wird der CONTRACT.md-Ausschluss auf genau raw/CONTRACT.md verankert (Befund oben); .gitkeep/.DS_Store bleiben namensbasiert.
Akzeptanzkriterien
Nach raw capture + raw accept enthält das Bundle genau die Dateien, die im Repo zum Manifest-Commit auf einen Glob passen und nicht ausgeschlossen sind, byte-identisch zum Blob (auch bei gesetztem core.autocrlf=true im Test) und mit ihren Repo-Pfaden, plus _capture.json; nicht passende Dateien (z. B. *.yaml neben docs/**/*.md) fehlen. docs/**/*.md trifft docs/a.md und docs/x/y/b.md, docs/*.md trifft docs/x/b.md nicht.
Nach --replaces-bundle gilt dasselbe für den neuen Commit; im Repo gelöschte Dateien und leer gewordene Verzeichnisse sind aus dem Bundle verschwunden; der Bundle-Pfad unter raw/ ist unverändert; das Manifest nennt den neuen Commit; kein raw_files: wurde verändert.
--replaces-bundle mit abweichender repo-URL, abweichendem Ordnernamen oder einem Bundle ohne _capture.json wird abgewiesen, ohne dass sich unter raw/ oder in incoming/ etwas ändert.
raw accept --replaces und --page mit einem Ziel in einem erfassten Bundle werden abgewiesen; außerhalb erfasster Bundles verhalten sie sich unverändert.
Eine Datei mit <!-- wikitool:export in Zeile 1, eine Datei unter .github/, ein Symlink, ein Submodul, ein LFS-Zeiger und eine Repo-Datei _capture.json werden nie erfasst und je namentlich gemeldet; raw status meldet keine davon als A.
Eine Repo-URL mit ext::/fd::/file:// und eine https://-URL mit Passwort im Userinfo werden von raw capture, raw capture --update und raw status abgewiesen; im Test führt ein ext::-Transport nachweislich keinen Befehl aus.
Kein Kommando liest oder schreibt Zugangsdaten; der Arbeitsbaum enthält nach jedem Lauf keine. Ein Repo, das nach Zugangsdaten fragt, blockiert nicht, sondern erscheint in raw status als nicht erreichbar.
raw status meldet ein Repo mit geänderter Doku mit korrekter A/M/D-Liste und meldet es nach --replaces-bundle nicht mehr; Änderungen außerhalb der Globs lösen keine Meldung aus; ein Tag-Muster folgt nur neuen Tags, nicht neuen Commits auf main; ein nicht erreichbares Repo ist eine Zeile neben den übrigen, kein Abbruch.
_capture.json erscheint nicht in sources coverage; eine CONTRACT.md in einem Bundle unter raw/ erscheint dort, solange keine Quellseite sie abdeckt; raw/CONTRACT.md erscheint weiterhin nicht.
Ohne _capture.json unter raw/ verhält sich jedes bestehende Kommando unverändert (bestehende Tests grün, ohne Anpassung).
Tests laufen gegen lokale Test-Repositories, nicht gegen das Netz; der dafür nötige file-Transport wird nur von einer Test-Fixture freigeschaltet, nie über eine Option oder Umgebungsvariable, die das Kommando selbst auswertet. pytest, docs verify, instructions verify grün; CI grün.
raw status an einem echten Repo ausprobiert (aus #171 hierher verschoben): an diesem Repo selbst, --ref 'v*', Ergebnis im Abschluss-Body genannt.
Betroffene Stellen
tools/chemenu/commands/raw_cmd.py: raw capture, raw status, --replaces-bundle, Manifest-Pflicht beim Accept eines erfassten Ordners, Refusal für --replaces/--page in erfassten Bundles. Neues Modul für den Git-Teil, Ref-Auflösung, Glob-Abgleich und Ausschlüsse (Muster: web_capture.py; Git über toolpaths.git()).
tools/chemenu/config.py: RAW_IGNORE_NAMES/iter_raw_files (Punkt 4). tools/chemenu/provenance.py nur lesend genutzt (source_pages_by_raw_file, citing_pages).
.gitignore: /tools/.wikitool_capture/ (ankergebunden, nach Regel 1 der Datei), dazu die docs verify-Kanarie in tools/chemenu/commands/docs_verify.py.
Kommando-Records (cli_contract.record an den neuen/geänderten Kommandos), dann tools/CONTRACT.md per wikitool docs contract neu erzeugen.
raw/CONTRACT.md: neuer Abschnitt "Getting a repository in" (Kommando, Manifest, Ausschlüsse, Zugangsdaten), Bundle-Ersatz als zweite Form unter Rules § "Replaceable as a whole", _capture.json als reservierter Name.
instructions/wiki-ingest/SKILL.md: neue Fassung eines erfassten Bundles - Editionsdiff mit git diff lesen, A/D über touch --add/--remove raw_files=, leere Quellseite über page-lifecycle.md § Delete; bei Zitaten aus einem erfassten Bundle --file mit dem bundle-relativen Pfad (docs/runbook.md), weil Basisnamen wie README.md kollidieren.
tools/README.md, README.md, CHANGES.md (über version bump), tools/chemenu/tests/ (neue Testdatei, z. B. test_raw_capture.py; Konventionen in instructions/dev/testing-conventions.md).
Versionsteil
--minor: neue Kommandos, neue optionale Manifestdatei, ein bisher abgewiesener raw accept-Aufruf wird erlaubt. Vorwärts drop-in: ohne _capture.json ändert sich kein Verhalten. Der verankerte CONTRACT.md-Ausschluss kann in einer Instanz mit einer CONTRACT.md unterhalb eines Bundles einen neuen Abdeckungsbefund erzeugen - ein Lint-Befund über eine bisher übersehene Datei, kein Bruch. Rückwärts: eine ältere Version meldet _capture.json in sources coverage als unabgedeckt und kennt --replaces-bundle nicht - ein Lint-Befund, keine kaputte Instanz. Drop-in in beide Richtungen.
Teil A aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). Folge-Issues: #178 (Intake-Lauf, baut hierauf auf), #179 (Leitlinien-Export, definiert den Export-Kopf, den diese Erfassung ausschließt). Kommandonamen sind beim Bau noch änderbar, die Eigenschaften unten nicht. Gegen den Baum geprüft am 2026-10-04 (Stand `8ff22ad`).
## Problem
Doku aus einem Git-Repository kommt heute nur von Hand in eine Instanz: Dateien nach `incoming/` kopieren, umbenennen, um der globalen Stem-Regel auszuweichen, pro Unterordner ein Bundle. Nichts hält fest, aus welchem Repo und Commit ein Bundle stammt, und nichts meldet, wenn sich das Repo seitdem geändert hat. Ein Ordner-Bundle lässt sich außerdem nicht als Ganzes durch eine neue Fassung ersetzen (`raw_cmd._folder_collision_message`: "A folder has no --replaces"; `--page` scheitert an verschachtelten Bundles, weil `_plan_file_moves` alle `raw_files:` in einem Verzeichnis verlangt, `raw_cmd.py` Z. 251-257).
**Befund nebenbei, wird hier mit behoben:** `config.RAW_IGNORE_NAMES` (`{".gitkeep", ".DS_Store", "CONTRACT.md"}`) wird in `config.iter_raw_files` per Dateiname in jeder Tiefe angewandt. Gemeint ist nur `raw/CONTRACT.md` (die Stage-Vertragsdatei), getroffen wird aber jede `CONTRACT.md` unter `raw/` - ein erfasstes `*/CONTRACT.md` (das chemenu-Beispiel in #171) wäre für `sources coverage` und `lint` unsichtbar. Heute liegt keine solche Datei unter `raw/` (geprüft), der Fehler wird erst durch diese Erfassung erreichbar. Dieselbe Funktion ist die, in der `_capture.json` ausgenommen werden muss (Punkt 4).
## Entwurf
**1. `raw capture <repo-url> --ref <ref-regel> --path <glob>... --name <bundle> --fidelity <v> --authority <v>`**
- Ref-Regel: ein Branchname (`main`) oder ein Tag-Muster (`v*` → das jüngste passende Tag nach Versionssortierung, `git ls-remote --tags` + `-v:refname`-Semantik). Aufgelöst zu genau einem Commit; bei einem annotierten Tag ist das der gepeelte Commit (`^{}`-Zeile).
- Holt diesen Commit **per Ref-Name** flach (`--depth 1`) in einen gitignorierten Cache: `/tools/.wikitool_capture/<sha256 der URL>/`, ein Bare-Repo pro URL, Zugriff unter `chemenu.filelock`. Nie per SHA - das verlangt serverseitig `allowReachableSHA1InWant` (siehe Punkt 3, warum das auch nie nötig ist).
- **Dateien werden als Blobs gelesen** (`git ls-tree -r -z <commit>` + `git cat-file`), nie über einen ausgecheckten Arbeitsbaum: so ändern `core.autocrlf`, Smudge-Filter oder eine Host-Git-Konfiguration kein Byte. Ziel: byte-identisch zum Blob.
- **Glob-Abgleich in Python, nicht über Git:** `git ls-tree` unterstützt keine `:(glob)`-Pathspecs (geprüft: "Pfadspezifikationsangabe wird von diesem Befehl nicht unterstützt: 'glob'"), und `PurePosixPath.full_match` gibt es erst ab Python 3.13 (Mindestversion ist 3.11). Semantik wie Gits `glob`-Pathspec: `*`/`?`/`[...]` überspannen kein `/`, `**` als ganzes Segment überspannt beliebig viele Verzeichnisse (auch null). Eigene Übersetzung nach Regex, mit eigenen Tests; `fnmatch` taugt nicht (sein `*` überspannt `/`).
- Schreibt die Dateien, die auf mindestens einen Glob passen, mit ihren Repo-Pfaden nach `incoming/<bundle>/`. Existiert `incoming/<bundle>` schon, Refusal vor jedem Schreiben. Pfadbudget (`_util.path_budget_problem_for`, gemessen am späteren Ziel `raw/<YYYY>/<MM>/<bundle>/<repo-pfad>`) wird vor dem Schreiben geprüft; ein zu langer Pfad ist eine Refusal mit den betroffenen Pfaden, kein stilles Auslassen.
- Schreibt `incoming/<bundle>/_capture.json`: `schema` (1), `repo`, `ref`, `commit`, `paths` (die Globs), `captured` (ISO-8601 UTC), `fidelity`, `authority`, `files` (Liste der erfassten Pfade, sortiert). Das Manifest ist die einzige Deklaration, dass und wie die Instanz dieses Repo verfolgt - es gibt keine zweite Konfigurationsdatei.
- **Ausschlüsse, mechanisch, je namentlich ausgegeben:** Dateien, deren erste Zeile mit `<!-- wikitool:export` beginnt (Leitlinien-Export, #179); Symlinks (Modus `120000`) und Submodule (`160000`); Dateien über `web_capture.MAX_BYTES` (25 MiB, das Limit von `raw fetch`); **Pfade mit einem Segment, das mit `.` beginnt** (`raw accept` verweigert einen Ordner mit versteckten Einträgen, `_walk_folder`, und `iter_raw_files` überspringt sie - ohne diesen Ausschluss scheiterte jede Erfassung mit `.github/**` erst beim Accept); jede Datei namens `_capture.json` (reserviert, Punkt 4); Git-LFS-Zeiger (Blob beginnt mit `version https://git-lfs.github.com/spec/v1`) - erfasst würde sonst der Zeiger statt des Inhalts.
- **Keine Zugangsdaten in `wikitool`:** Git läuft mit den Zugangsdaten des Hosts (`toolpaths.git()`, der vom Preflight erfasste Pfad). Erlaubt sind nur `ssh://`, `https://` und die `scp`-Kurzform `user@host:pfad`; eine `https://`-URL mit Passwort-Anteil im Userinfo (`https://user:token@…`) wird abgewiesen, weil das Manifest committet wird. Git wird mit `GIT_ALLOW_PROTOCOL=ssh:https` aufgerufen, damit `ext::`, `fd::`, `file://` usw. auch auf Git-Ebene abgewiesen werden. Unbeaufsichtigt lauffähig (#178): `GIT_TERMINAL_PROMPT=0`, stdin geschlossen, Timeout je Git-Aufruf; ein Repo, das nach Zugangsdaten fragt, ist "nicht erreichbar", kein Hänger.
- **`--update <raw-bundle>`** liest URL, Ref-Regel und Globs aus dem Manifest des bestehenden Bundles und erfasst dessen aktuellen Stand nach `incoming/<gleicher bundle-name>/` - der Aufruf, den der Intake-Lauf (#178) benutzt. `--fidelity`/`--authority` sind hier optional (Vorgabe: Werte des alten Manifests); angegeben überschreiben sie diese - der sanktionierte Korrekturweg, analog zu `raw accept --replaces`. Dieselbe URL-Prüfung wie beim Erstaufruf: ein Manifest unter `raw/` ist committeter Text, kein vertrauenswürdiger Befehl.
**2. `raw accept` für ein erfasstes Bundle**
- Erstaufnahme: wie heute ein Ordner (`raw accept incoming/<bundle>`). Trägt der Ordner ein `_capture.json`, kommen `fidelity`/`authority` **nur** aus dem Manifest; `--fidelity`/`--authority` auf der Kommandozeile sind dann eine Refusal (eine zweite Quelle für denselben Wert wäre die zweite Deklaration, die das Manifest ausschließen soll). Korrigiert wird über `raw capture --update`.
- **`raw accept --replaces-bundle <raw-bundle> incoming/<bundle>`** ersetzt ein Ordner-Bundle als Ganzes an seiner bestehenden Adresse (`raw/<YYYY>/<MM>/<bundle>/` bleibt, kein neuer Shard; die Stem-Kollision mit sich selbst ist hier keine). Refusal, ohne dass sich unter `raw/` etwas ändert, wenn: eins der beiden Bundles kein `_capture.json` hat; die `repo`-URLs der Manifeste abweichen; der Ordnername in `incoming/` vom Bundlenamen abweicht (wie `--replaces` bei Dateinamen); `--page` oder `--replaces` mitgegeben ist; eine Datei des Bundles mehr als eine besitzende Quellseite hat (wie `_replace`).
- Dateien, die es in der neuen Fassung nicht mehr gibt, werden entfernt, leer gewordene Verzeichnisse mit `rmdir` (nie rekursiv) - `git log` ist das Archiv, wie bei `--replaces`. **Invariante:** nach dem Lauf enthält das Bundle genau die Dateien des neuen Manifests plus `_capture.json`, sonst nichts; `incoming/<bundle>/` ist leer und entfernt.
- `raw_files:` ändert der Befehl **nicht** (wie `--replaces`): welche Quellseite eine neue Datei bekommt, ist Ingest-Urteil, nicht Mechanik. Weichen `fidelity`/`authority` des neuen Manifests von den Werten der besitzenden Quellseite(n) ab, werden sie dort überschrieben (wie `_replace` mit `_overwrite_capture_fields_on_page`).
- Ausgabe: je Datei `A`/`M`/`D` (`M` = Bytes verschieden). Gruppiert nach besitzender Quellseite (`provenance.source_pages_by_raw_file`), deren zitierende Seiten (`provenance.citing_pages`) einmal je Quellseite, nicht je Datei - Fußnoten-Qualifier (`[[Source - X|datei]]`) sind freie Strings und lassen keine verlässliche Zuordnung pro Datei zu. Folgezeilen: `A` → `touch "<Quellseite>" --add raw_files=<pfad>` oder eine neue Quellseite; `D` → `touch "<Quellseite>" --remove raw_files=<pfad>`, und wo das die Seite ohne `raw_files:` ließe, `instructions/page-lifecycle.md` § Delete.
- **`--replaces` und `--page` verweigern ein Ziel innerhalb eines erfassten Bundles** (Verzeichnis mit `_capture.json`). Beides würde den Bundle-Inhalt vom Manifest-Commit lösen; ein flaches erfasstes Bundle (nur `README.md`, `AGENTS.md`) fiele sonst nicht unter die bestehende Verschachtelungs-Refusal. Die Refusal nennt `raw capture --update` + `--replaces-bundle`.
**3. `raw status` (nur lesend, schreibt nichts außer in den Cache)**
- Liest jedes `_capture.json` unter `raw/`, löst je Repo die Ref-Regel per `git ls-remote` auf. Weicht der Commit ab, wird nur der **neue** Commit geholt und gegen den **Bundle-Inhalt unter `raw/`** abgeglichen, nicht gegen den alten Commit: das Bundle ist nach Kriterium 1 byte-identisch zum alten Commit, und so muss kein unbeworbener SHA geholt werden. Abgleich mit denselben Globs **und denselben Ausschlüssen** wie `raw capture` (eine gemeinsame Funktion, nicht zwei), sonst erschiene jede ausgeschlossene Datei dauerhaft als `A`.
- Ausgabe je Repo `alt → neu` und je Datei `A`/`M`/`D`, gruppiert wie bei `--replaces-bundle`. Ein Repo, dessen Commit sich bewegt hat, ohne dass sich innerhalb der Globs etwas ändert, erzeugt keine Meldung. Ein nicht erreichbares Repo, eine abgewiesene URL oder ein unlesbares `_capture.json` ist eine Zeile in der Ausgabe, kein Abbruch; der Exit-Status ist 0, solange der Befehl selbst lief.
- `--json` für den Intake-Lauf (#178): je Repo `bundle`, `repo`, `ref`, `old`, `new`, `changed` (bool), `files` (`[{path, status}]`), `error` (oder null).
**4. Abdeckung:** `_capture.json` ist ein reservierter Name, Metadatum des Bundles und keine Quelle. `config.iter_raw_files` lässt es aus (in jeder Tiefe - `raw capture` nimmt nie eine Repo-Datei dieses Namens auf, also kann jedes Vorkommen nur ein Manifest sein); `sources coverage` und `lint` melden es nicht als unabgedeckt, und es steht in keinem `raw_files:`. Im selben Zug wird der `CONTRACT.md`-Ausschluss auf genau `raw/CONTRACT.md` verankert (Befund oben); `.gitkeep`/`.DS_Store` bleiben namensbasiert.
## Akzeptanzkriterien
- [ ] Nach `raw capture` + `raw accept` enthält das Bundle genau die Dateien, die im Repo zum Manifest-Commit auf einen Glob passen und nicht ausgeschlossen sind, byte-identisch zum Blob (auch bei gesetztem `core.autocrlf=true` im Test) und mit ihren Repo-Pfaden, plus `_capture.json`; nicht passende Dateien (z. B. `*.yaml` neben `docs/**/*.md`) fehlen. `docs/**/*.md` trifft `docs/a.md` und `docs/x/y/b.md`, `docs/*.md` trifft `docs/x/b.md` nicht.
- [ ] Nach `--replaces-bundle` gilt dasselbe für den neuen Commit; im Repo gelöschte Dateien und leer gewordene Verzeichnisse sind aus dem Bundle verschwunden; der Bundle-Pfad unter `raw/` ist unverändert; das Manifest nennt den neuen Commit; kein `raw_files:` wurde verändert.
- [ ] `--replaces-bundle` mit abweichender `repo`-URL, abweichendem Ordnernamen oder einem Bundle ohne `_capture.json` wird abgewiesen, ohne dass sich unter `raw/` oder in `incoming/` etwas ändert.
- [ ] `raw accept --replaces` und `--page` mit einem Ziel in einem erfassten Bundle werden abgewiesen; außerhalb erfasster Bundles verhalten sie sich unverändert.
- [ ] Eine Datei mit `<!-- wikitool:export` in Zeile 1, eine Datei unter `.github/`, ein Symlink, ein Submodul, ein LFS-Zeiger und eine Repo-Datei `_capture.json` werden nie erfasst und je namentlich gemeldet; `raw status` meldet keine davon als `A`.
- [ ] Eine Repo-URL mit `ext::`/`fd::`/`file://` und eine `https://`-URL mit Passwort im Userinfo werden von `raw capture`, `raw capture --update` und `raw status` abgewiesen; im Test führt ein `ext::`-Transport nachweislich keinen Befehl aus.
- [ ] Kein Kommando liest oder schreibt Zugangsdaten; der Arbeitsbaum enthält nach jedem Lauf keine. Ein Repo, das nach Zugangsdaten fragt, blockiert nicht, sondern erscheint in `raw status` als nicht erreichbar.
- [ ] `raw status` meldet ein Repo mit geänderter Doku mit korrekter A/M/D-Liste und meldet es nach `--replaces-bundle` nicht mehr; Änderungen außerhalb der Globs lösen keine Meldung aus; ein Tag-Muster folgt nur neuen Tags, nicht neuen Commits auf `main`; ein nicht erreichbares Repo ist eine Zeile neben den übrigen, kein Abbruch.
- [ ] `_capture.json` erscheint nicht in `sources coverage`; eine `CONTRACT.md` in einem Bundle unter `raw/` erscheint dort, solange keine Quellseite sie abdeckt; `raw/CONTRACT.md` erscheint weiterhin nicht.
- [ ] Ohne `_capture.json` unter `raw/` verhält sich jedes bestehende Kommando unverändert (bestehende Tests grün, ohne Anpassung).
- [ ] Tests laufen gegen lokale Test-Repositories, nicht gegen das Netz; der dafür nötige `file`-Transport wird nur von einer Test-Fixture freigeschaltet, nie über eine Option oder Umgebungsvariable, die das Kommando selbst auswertet. `pytest`, `docs verify`, `instructions verify` grün; CI grün.
- [ ] `raw status` an einem echten Repo ausprobiert (aus #171 hierher verschoben): an diesem Repo selbst, `--ref 'v*'`, Ergebnis im Abschluss-Body genannt.
## Betroffene Stellen
- `tools/chemenu/commands/raw_cmd.py`: `raw capture`, `raw status`, `--replaces-bundle`, Manifest-Pflicht beim Accept eines erfassten Ordners, Refusal für `--replaces`/`--page` in erfassten Bundles. Neues Modul für den Git-Teil, Ref-Auflösung, Glob-Abgleich und Ausschlüsse (Muster: `web_capture.py`; Git über `toolpaths.git()`).
- `tools/chemenu/config.py`: `RAW_IGNORE_NAMES`/`iter_raw_files` (Punkt 4). `tools/chemenu/provenance.py` nur lesend genutzt (`source_pages_by_raw_file`, `citing_pages`).
- `.gitignore`: `/tools/.wikitool_capture/` (ankergebunden, nach Regel 1 der Datei), dazu die `docs verify`-Kanarie in `tools/chemenu/commands/docs_verify.py`.
- Kommando-Records (`cli_contract.record` an den neuen/geänderten Kommandos), dann `tools/CONTRACT.md` per `wikitool docs contract` neu erzeugen.
- `raw/CONTRACT.md`: neuer Abschnitt "Getting a repository in" (Kommando, Manifest, Ausschlüsse, Zugangsdaten), Bundle-Ersatz als zweite Form unter Rules § "Replaceable as a whole", `_capture.json` als reservierter Name.
- `instructions/wiki-ingest/SKILL.md`: neue Fassung eines erfassten Bundles - Editionsdiff mit `git diff` lesen, `A`/`D` über `touch --add/--remove raw_files=`, leere Quellseite über `page-lifecycle.md` § Delete; bei Zitaten aus einem erfassten Bundle `--file` mit dem bundle-relativen Pfad (`docs/runbook.md`), weil Basisnamen wie `README.md` kollidieren.
- `tools/README.md`, `README.md`, `CHANGES.md` (über `version bump`), `tools/chemenu/tests/` (neue Testdatei, z. B. `test_raw_capture.py`; Konventionen in `instructions/dev/testing-conventions.md`).
## Versionsteil
**`--minor`**: neue Kommandos, neue optionale Manifestdatei, ein bisher abgewiesener `raw accept`-Aufruf wird erlaubt. Vorwärts drop-in: ohne `_capture.json` ändert sich kein Verhalten. Der verankerte `CONTRACT.md`-Ausschluss kann in einer Instanz mit einer `CONTRACT.md` unterhalb eines Bundles einen neuen Abdeckungsbefund erzeugen - ein Lint-Befund über eine bisher übersehene Datei, kein Bruch. Rückwärts: eine ältere Version meldet `_capture.json` in `sources coverage` als unabgedeckt und kennt `--replaces-bundle` nicht - ein Lint-Befund, keine kaputte Instanz. Drop-in in beide Richtungen.
Changelog: Body gegen den Baum (8ff22ad) geprüft und präzisiert. Neu: Befund RAW_IGNORE_NAMES blendet jede CONTRACT.md unter raw/ aus - wird hier mitbehoben (Punkt 4). Korrigiert: _capture.json ist nicht "wie der Header eines raw fetch-Bundles" (der steht in der .md und ist abgedeckt), sondern ein reservierter Name in iter_raw_files; raw status vergleicht den neuen Commit gegen den Bundle-Inhalt unter raw/ statt gegen den alten Commit (kein Fetch eines unbeworbenen SHA); page-lifecycle.md gilt nur für eine leer werdende Quellseite, A/D laufen über touch --add/--remove raw_files=. Ergänzt: Blob-Lesen statt Checkout (autocrlf), Glob-Abgleich in Python (ls-tree kann keine :(glob)-Pathspec, full_match erst ab 3.13), Ausschluss versteckter Pfade/LFS-Zeiger/_capture.json, Refusal für URLs mit Passwort, GIT_TERMINAL_PROMPT=0 + Timeout, Cache-Pfad /tools/.wikitool_capture/, Manifest als einzige Quelle für fidelity/authority beim Accept, Refusal für --replaces/--page in erfassten Bundles, Ausgabe gruppiert je Quellseite, --json-Felder, Kriterium "an echtem Repo ausprobiert" aus #171 übernommen. Versionsteil unverändert --minor.
**Changelog:** Body gegen den Baum (`8ff22ad`) geprüft und präzisiert. Neu: Befund `RAW_IGNORE_NAMES` blendet jede `CONTRACT.md` unter `raw/` aus - wird hier mitbehoben (Punkt 4). Korrigiert: `_capture.json` ist nicht "wie der Header eines `raw fetch`-Bundles" (der steht in der `.md` und ist abgedeckt), sondern ein reservierter Name in `iter_raw_files`; `raw status` vergleicht den neuen Commit gegen den Bundle-Inhalt unter `raw/` statt gegen den alten Commit (kein Fetch eines unbeworbenen SHA); `page-lifecycle.md` gilt nur für eine leer werdende Quellseite, `A`/`D` laufen über `touch --add/--remove raw_files=`. Ergänzt: Blob-Lesen statt Checkout (autocrlf), Glob-Abgleich in Python (`ls-tree` kann keine `:(glob)`-Pathspec, `full_match` erst ab 3.13), Ausschluss versteckter Pfade/LFS-Zeiger/`_capture.json`, Refusal für URLs mit Passwort, `GIT_TERMINAL_PROMPT=0` + Timeout, Cache-Pfad `/tools/.wikitool_capture/`, Manifest als einzige Quelle für `fidelity`/`authority` beim Accept, Refusal für `--replaces`/`--page` in erfassten Bundles, Ausgabe gruppiert je Quellseite, `--json`-Felder, Kriterium "an echtem Repo ausprobiert" aus #171 übernommen. Versionsteil unverändert `--minor`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Teil A aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). Folge-Issues: #178 (Intake-Lauf, baut hierauf auf), #179 (Leitlinien-Export, definiert den Export-Kopf, den diese Erfassung ausschließt). Kommandonamen sind beim Bau noch änderbar, die Eigenschaften unten nicht. Gegen den Baum geprüft am 2026-10-04 (Stand
8ff22ad).Problem
Doku aus einem Git-Repository kommt heute nur von Hand in eine Instanz: Dateien nach
incoming/kopieren, umbenennen, um der globalen Stem-Regel auszuweichen, pro Unterordner ein Bundle. Nichts hält fest, aus welchem Repo und Commit ein Bundle stammt, und nichts meldet, wenn sich das Repo seitdem geändert hat. Ein Ordner-Bundle lässt sich außerdem nicht als Ganzes durch eine neue Fassung ersetzen (raw_cmd._folder_collision_message: "A folder has no --replaces";--pagescheitert an verschachtelten Bundles, weil_plan_file_movesalleraw_files:in einem Verzeichnis verlangt,raw_cmd.pyZ. 251-257).Befund nebenbei, wird hier mit behoben:
config.RAW_IGNORE_NAMES({".gitkeep", ".DS_Store", "CONTRACT.md"}) wird inconfig.iter_raw_filesper Dateiname in jeder Tiefe angewandt. Gemeint ist nurraw/CONTRACT.md(die Stage-Vertragsdatei), getroffen wird aber jedeCONTRACT.mdunterraw/- ein erfasstes*/CONTRACT.md(das chemenu-Beispiel in #171) wäre fürsources coverageundlintunsichtbar. Heute liegt keine solche Datei unterraw/(geprüft), der Fehler wird erst durch diese Erfassung erreichbar. Dieselbe Funktion ist die, in der_capture.jsonausgenommen werden muss (Punkt 4).Entwurf
1.
raw capture <repo-url> --ref <ref-regel> --path <glob>... --name <bundle> --fidelity <v> --authority <v>main) oder ein Tag-Muster (v*→ das jüngste passende Tag nach Versionssortierung,git ls-remote --tags+-v:refname-Semantik). Aufgelöst zu genau einem Commit; bei einem annotierten Tag ist das der gepeelte Commit (^{}-Zeile).--depth 1) in einen gitignorierten Cache:/tools/.wikitool_capture/<sha256 der URL>/, ein Bare-Repo pro URL, Zugriff unterchemenu.filelock. Nie per SHA - das verlangt serverseitigallowReachableSHA1InWant(siehe Punkt 3, warum das auch nie nötig ist).git ls-tree -r -z <commit>+git cat-file), nie über einen ausgecheckten Arbeitsbaum: so änderncore.autocrlf, Smudge-Filter oder eine Host-Git-Konfiguration kein Byte. Ziel: byte-identisch zum Blob.git ls-treeunterstützt keine:(glob)-Pathspecs (geprüft: "Pfadspezifikationsangabe wird von diesem Befehl nicht unterstützt: 'glob'"), undPurePosixPath.full_matchgibt es erst ab Python 3.13 (Mindestversion ist 3.11). Semantik wie Gitsglob-Pathspec:*/?/[...]überspannen kein/,**als ganzes Segment überspannt beliebig viele Verzeichnisse (auch null). Eigene Übersetzung nach Regex, mit eigenen Tests;fnmatchtaugt nicht (sein*überspannt/).incoming/<bundle>/. Existiertincoming/<bundle>schon, Refusal vor jedem Schreiben. Pfadbudget (_util.path_budget_problem_for, gemessen am späteren Zielraw/<YYYY>/<MM>/<bundle>/<repo-pfad>) wird vor dem Schreiben geprüft; ein zu langer Pfad ist eine Refusal mit den betroffenen Pfaden, kein stilles Auslassen.incoming/<bundle>/_capture.json:schema(1),repo,ref,commit,paths(die Globs),captured(ISO-8601 UTC),fidelity,authority,files(Liste der erfassten Pfade, sortiert). Das Manifest ist die einzige Deklaration, dass und wie die Instanz dieses Repo verfolgt - es gibt keine zweite Konfigurationsdatei.<!-- wikitool:exportbeginnt (Leitlinien-Export, #179); Symlinks (Modus120000) und Submodule (160000); Dateien überweb_capture.MAX_BYTES(25 MiB, das Limit vonraw fetch); Pfade mit einem Segment, das mit.beginnt (raw acceptverweigert einen Ordner mit versteckten Einträgen,_walk_folder, unditer_raw_filesüberspringt sie - ohne diesen Ausschluss scheiterte jede Erfassung mit.github/**erst beim Accept); jede Datei namens_capture.json(reserviert, Punkt 4); Git-LFS-Zeiger (Blob beginnt mitversion https://git-lfs.github.com/spec/v1) - erfasst würde sonst der Zeiger statt des Inhalts.wikitool: Git läuft mit den Zugangsdaten des Hosts (toolpaths.git(), der vom Preflight erfasste Pfad). Erlaubt sind nurssh://,https://und diescp-Kurzformuser@host:pfad; einehttps://-URL mit Passwort-Anteil im Userinfo (https://user:token@…) wird abgewiesen, weil das Manifest committet wird. Git wird mitGIT_ALLOW_PROTOCOL=ssh:httpsaufgerufen, damitext::,fd::,file://usw. auch auf Git-Ebene abgewiesen werden. Unbeaufsichtigt lauffähig (#178):GIT_TERMINAL_PROMPT=0, stdin geschlossen, Timeout je Git-Aufruf; ein Repo, das nach Zugangsdaten fragt, ist "nicht erreichbar", kein Hänger.--update <raw-bundle>liest URL, Ref-Regel und Globs aus dem Manifest des bestehenden Bundles und erfasst dessen aktuellen Stand nachincoming/<gleicher bundle-name>/- der Aufruf, den der Intake-Lauf (#178) benutzt.--fidelity/--authoritysind hier optional (Vorgabe: Werte des alten Manifests); angegeben überschreiben sie diese - der sanktionierte Korrekturweg, analog zuraw accept --replaces. Dieselbe URL-Prüfung wie beim Erstaufruf: ein Manifest unterraw/ist committeter Text, kein vertrauenswürdiger Befehl.2.
raw acceptfür ein erfasstes Bundleraw accept incoming/<bundle>). Trägt der Ordner ein_capture.json, kommenfidelity/authoritynur aus dem Manifest;--fidelity/--authorityauf der Kommandozeile sind dann eine Refusal (eine zweite Quelle für denselben Wert wäre die zweite Deklaration, die das Manifest ausschließen soll). Korrigiert wird überraw capture --update.raw accept --replaces-bundle <raw-bundle> incoming/<bundle>ersetzt ein Ordner-Bundle als Ganzes an seiner bestehenden Adresse (raw/<YYYY>/<MM>/<bundle>/bleibt, kein neuer Shard; die Stem-Kollision mit sich selbst ist hier keine). Refusal, ohne dass sich unterraw/etwas ändert, wenn: eins der beiden Bundles kein_capture.jsonhat; dierepo-URLs der Manifeste abweichen; der Ordnername inincoming/vom Bundlenamen abweicht (wie--replacesbei Dateinamen);--pageoder--replacesmitgegeben ist; eine Datei des Bundles mehr als eine besitzende Quellseite hat (wie_replace).rmdir(nie rekursiv) -git logist das Archiv, wie bei--replaces. Invariante: nach dem Lauf enthält das Bundle genau die Dateien des neuen Manifests plus_capture.json, sonst nichts;incoming/<bundle>/ist leer und entfernt.raw_files:ändert der Befehl nicht (wie--replaces): welche Quellseite eine neue Datei bekommt, ist Ingest-Urteil, nicht Mechanik. Weichenfidelity/authoritydes neuen Manifests von den Werten der besitzenden Quellseite(n) ab, werden sie dort überschrieben (wie_replacemit_overwrite_capture_fields_on_page).A/M/D(M= Bytes verschieden). Gruppiert nach besitzender Quellseite (provenance.source_pages_by_raw_file), deren zitierende Seiten (provenance.citing_pages) einmal je Quellseite, nicht je Datei - Fußnoten-Qualifier ([[Source - X|datei]]) sind freie Strings und lassen keine verlässliche Zuordnung pro Datei zu. Folgezeilen:A→touch "<Quellseite>" --add raw_files=<pfad>oder eine neue Quellseite;D→touch "<Quellseite>" --remove raw_files=<pfad>, und wo das die Seite ohneraw_files:ließe,instructions/page-lifecycle.md§ Delete.--replacesund--pageverweigern ein Ziel innerhalb eines erfassten Bundles (Verzeichnis mit_capture.json). Beides würde den Bundle-Inhalt vom Manifest-Commit lösen; ein flaches erfasstes Bundle (nurREADME.md,AGENTS.md) fiele sonst nicht unter die bestehende Verschachtelungs-Refusal. Die Refusal nenntraw capture --update+--replaces-bundle.3.
raw status(nur lesend, schreibt nichts außer in den Cache)_capture.jsonunterraw/, löst je Repo die Ref-Regel pergit ls-remoteauf. Weicht der Commit ab, wird nur der neue Commit geholt und gegen den Bundle-Inhalt unterraw/abgeglichen, nicht gegen den alten Commit: das Bundle ist nach Kriterium 1 byte-identisch zum alten Commit, und so muss kein unbeworbener SHA geholt werden. Abgleich mit denselben Globs und denselben Ausschlüssen wieraw capture(eine gemeinsame Funktion, nicht zwei), sonst erschiene jede ausgeschlossene Datei dauerhaft alsA.alt → neuund je DateiA/M/D, gruppiert wie bei--replaces-bundle. Ein Repo, dessen Commit sich bewegt hat, ohne dass sich innerhalb der Globs etwas ändert, erzeugt keine Meldung. Ein nicht erreichbares Repo, eine abgewiesene URL oder ein unlesbares_capture.jsonist eine Zeile in der Ausgabe, kein Abbruch; der Exit-Status ist 0, solange der Befehl selbst lief.--jsonfür den Intake-Lauf (#178): je Repobundle,repo,ref,old,new,changed(bool),files([{path, status}]),error(oder null).4. Abdeckung:
_capture.jsonist ein reservierter Name, Metadatum des Bundles und keine Quelle.config.iter_raw_fileslässt es aus (in jeder Tiefe -raw capturenimmt nie eine Repo-Datei dieses Namens auf, also kann jedes Vorkommen nur ein Manifest sein);sources coverageundlintmelden es nicht als unabgedeckt, und es steht in keinemraw_files:. Im selben Zug wird derCONTRACT.md-Ausschluss auf genauraw/CONTRACT.mdverankert (Befund oben);.gitkeep/.DS_Storebleiben namensbasiert.Akzeptanzkriterien
raw capture+raw acceptenthält das Bundle genau die Dateien, die im Repo zum Manifest-Commit auf einen Glob passen und nicht ausgeschlossen sind, byte-identisch zum Blob (auch bei gesetztemcore.autocrlf=trueim Test) und mit ihren Repo-Pfaden, plus_capture.json; nicht passende Dateien (z. B.*.yamlnebendocs/**/*.md) fehlen.docs/**/*.mdtrifftdocs/a.mdunddocs/x/y/b.md,docs/*.mdtrifftdocs/x/b.mdnicht.--replaces-bundlegilt dasselbe für den neuen Commit; im Repo gelöschte Dateien und leer gewordene Verzeichnisse sind aus dem Bundle verschwunden; der Bundle-Pfad unterraw/ist unverändert; das Manifest nennt den neuen Commit; keinraw_files:wurde verändert.--replaces-bundlemit abweichenderrepo-URL, abweichendem Ordnernamen oder einem Bundle ohne_capture.jsonwird abgewiesen, ohne dass sich unterraw/oder inincoming/etwas ändert.raw accept --replacesund--pagemit einem Ziel in einem erfassten Bundle werden abgewiesen; außerhalb erfasster Bundles verhalten sie sich unverändert.<!-- wikitool:exportin Zeile 1, eine Datei unter.github/, ein Symlink, ein Submodul, ein LFS-Zeiger und eine Repo-Datei_capture.jsonwerden nie erfasst und je namentlich gemeldet;raw statusmeldet keine davon alsA.ext::/fd::/file://und einehttps://-URL mit Passwort im Userinfo werden vonraw capture,raw capture --updateundraw statusabgewiesen; im Test führt einext::-Transport nachweislich keinen Befehl aus.raw statusals nicht erreichbar.raw statusmeldet ein Repo mit geänderter Doku mit korrekter A/M/D-Liste und meldet es nach--replaces-bundlenicht mehr; Änderungen außerhalb der Globs lösen keine Meldung aus; ein Tag-Muster folgt nur neuen Tags, nicht neuen Commits aufmain; ein nicht erreichbares Repo ist eine Zeile neben den übrigen, kein Abbruch._capture.jsonerscheint nicht insources coverage; eineCONTRACT.mdin einem Bundle unterraw/erscheint dort, solange keine Quellseite sie abdeckt;raw/CONTRACT.mderscheint weiterhin nicht._capture.jsonunterraw/verhält sich jedes bestehende Kommando unverändert (bestehende Tests grün, ohne Anpassung).file-Transport wird nur von einer Test-Fixture freigeschaltet, nie über eine Option oder Umgebungsvariable, die das Kommando selbst auswertet.pytest,docs verify,instructions verifygrün; CI grün.raw statusan einem echten Repo ausprobiert (aus #171 hierher verschoben): an diesem Repo selbst,--ref 'v*', Ergebnis im Abschluss-Body genannt.Betroffene Stellen
tools/chemenu/commands/raw_cmd.py:raw capture,raw status,--replaces-bundle, Manifest-Pflicht beim Accept eines erfassten Ordners, Refusal für--replaces/--pagein erfassten Bundles. Neues Modul für den Git-Teil, Ref-Auflösung, Glob-Abgleich und Ausschlüsse (Muster:web_capture.py; Git übertoolpaths.git()).tools/chemenu/config.py:RAW_IGNORE_NAMES/iter_raw_files(Punkt 4).tools/chemenu/provenance.pynur lesend genutzt (source_pages_by_raw_file,citing_pages)..gitignore:/tools/.wikitool_capture/(ankergebunden, nach Regel 1 der Datei), dazu diedocs verify-Kanarie intools/chemenu/commands/docs_verify.py.cli_contract.recordan den neuen/geänderten Kommandos), danntools/CONTRACT.mdperwikitool docs contractneu erzeugen.raw/CONTRACT.md: neuer Abschnitt "Getting a repository in" (Kommando, Manifest, Ausschlüsse, Zugangsdaten), Bundle-Ersatz als zweite Form unter Rules § "Replaceable as a whole",_capture.jsonals reservierter Name.instructions/wiki-ingest/SKILL.md: neue Fassung eines erfassten Bundles - Editionsdiff mitgit difflesen,A/Dübertouch --add/--remove raw_files=, leere Quellseite überpage-lifecycle.md§ Delete; bei Zitaten aus einem erfassten Bundle--filemit dem bundle-relativen Pfad (docs/runbook.md), weil Basisnamen wieREADME.mdkollidieren.tools/README.md,README.md,CHANGES.md(überversion bump),tools/chemenu/tests/(neue Testdatei, z. B.test_raw_capture.py; Konventionen ininstructions/dev/testing-conventions.md).Versionsteil
--minor: neue Kommandos, neue optionale Manifestdatei, ein bisher abgewiesenerraw accept-Aufruf wird erlaubt. Vorwärts drop-in: ohne_capture.jsonändert sich kein Verhalten. Der verankerteCONTRACT.md-Ausschluss kann in einer Instanz mit einerCONTRACT.mdunterhalb eines Bundles einen neuen Abdeckungsbefund erzeugen - ein Lint-Befund über eine bisher übersehene Datei, kein Bruch. Rückwärts: eine ältere Version meldet_capture.jsoninsources coverageals unabgedeckt und kennt--replaces-bundlenicht - ein Lint-Befund, keine kaputte Instanz. Drop-in in beide Richtungen.Changelog: Body gegen den Baum (
8ff22ad) geprüft und präzisiert. Neu: BefundRAW_IGNORE_NAMESblendet jedeCONTRACT.mdunterraw/aus - wird hier mitbehoben (Punkt 4). Korrigiert:_capture.jsonist nicht "wie der Header einesraw fetch-Bundles" (der steht in der.mdund ist abgedeckt), sondern ein reservierter Name initer_raw_files;raw statusvergleicht den neuen Commit gegen den Bundle-Inhalt unterraw/statt gegen den alten Commit (kein Fetch eines unbeworbenen SHA);page-lifecycle.mdgilt nur für eine leer werdende Quellseite,A/Dlaufen übertouch --add/--remove raw_files=. Ergänzt: Blob-Lesen statt Checkout (autocrlf), Glob-Abgleich in Python (ls-treekann keine:(glob)-Pathspec,full_matcherst ab 3.13), Ausschluss versteckter Pfade/LFS-Zeiger/_capture.json, Refusal für URLs mit Passwort,GIT_TERMINAL_PROMPT=0+ Timeout, Cache-Pfad/tools/.wikitool_capture/, Manifest als einzige Quelle fürfidelity/authoritybeim Accept, Refusal für--replaces/--pagein erfassten Bundles, Ausgabe gruppiert je Quellseite,--json-Felder, Kriterium "an echtem Repo ausprobiert" aus #171 übernommen. Versionsteil unverändert--minor.