Teil B aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). Blockiert durch #177 (Erfassung, raw status, --replaces-bundle).
Problem
Ändert sich die Doku eines verfolgten Repos, soll die Instanz ohne Handarbeit nachziehen - aber nichts davon darf main ohne menschliche Freigabe erreichen. Der Mensch soll dabei nicht jeden Schritt freigeben, sondern das Ergebnis: Doku-Änderung und daraus gelerntes Wissen in einer Ansicht.
Daneben ein Befund, der unabhängig von #171 gilt: reconcile() in git_publish.py zählt beim Überlappungstest (touched_files) auch die generierten Dateien mit. Zwei beliebige Ingests berühren beide kb/index.md und kb/log.md, überlappen also immer und landen im Rebase-Review-Gate (exit 42), obwohl genau diese Dateien mechanisch wiederherstellbar sind (is_generated(), ebenda).
Entwurf
1. Der Lauf - eine neue Instruction/Skill (Arbeitsname repo-intake), die eine Sitzung im Checkout der Instanz unbeaufsichtigt abarbeitet. Host, Harness und Auslöser liegen außerhalb des Stacks; der Auslöser trägt keinen Inhalt, nur "prüf jetzt" (Timer als Grundlinie, Push-Signal aus CI/Webhook/Markierungsdatei optional).
raw status --json - nichts geändert → Ende.
Gibt es eine offene Intake-PR, arbeitet der Lauf auf deren Branch weiter; sonst neuer Branch intake/<YYYY-MM-DD> vom aktuellen main. Eine offene Intake-PR zur Zeit; sie wächst, statt dass eine zweite entsteht.
Branch auf Stand bringen: main einmergen (Punkt 2 unten).
Pro geändertem Repo: raw capture --update → raw accept --replaces-bundle → Ingest gegen den Editionsdiff (wiki-ingest, Abschnitt aus #177) → ein Commit pro Repo.
publish --branch intake/...; bei neuer PR Anlage über das Forge-Werkzeug der Sitzung (gitea-mcp, gh, ...), nie über wikitool. PR-Text je Repo: alter → neuer Commit mit Vergleichslink ins Quell-Repo, A/M/D-Liste, berührte Seiten, und "Konflikt aufgelöst in: ..." falls Punkt 2 eine Seite von Hand lösen musste.
Merge der PR durch den Menschen = Freigabe. Eine ohne Merge geschlossene PR heißt "diese Fassungen nicht": Der Lauf erfasst ein Repo erst wieder, wenn es über den abgelehnten Commit hinaus ist. Den PR-Zustand liest die Sitzung über ihr Forge-Werkzeug; wikitool bleibt forge-neutral.
Ein Mass-Update-Gate (exit 42) oder jede andere Gate-Antwort beendet den Lauf und wird als PR-Kommentar bzw. im Sitzungsprotokoll für den Menschen hinterlassen - nie geöffnet.
2. main mechanisch einmergen (Arbeitsname sync --merge-from main, auf dem Intake-Branch) - Merge statt Rebase, weil der Branch schon publiziert ist und Force-Push verboten bleibt (AGENTS.md Invariante 5). Konflikte werden nach Dateiart aufgelöst:
Datei
Auflösung
LLM nötig
kb/index.md, **/INDEX.md
nach dem Merge neu erzeugen (index rebuild)
nein
kb/provenance.md
neu erzeugen (sources rebuild-index)
nein
kb/log.md
main-Fassung, danach die Einträge, die nur der Branch hat, in ihrer Reihenfolge
nein
Referenz-Arrays im Frontmatter (related:, sources:, entities:, concepts:), modified:
Mengenvereinigung bzw. Maximum
nein
Seitentext, summary:, alles Übrige
Gits normaler Drei-Wege-Merge; nur ein echter Textkonflikt bleibt übrig
nur für diese Seiten
Echte Textkonflikte löst die Sitzung nur in den betroffenen Seiten und nennt sie im PR-Text. Nach dem Merge muss lint ohne neue Fehler durchlaufen (z. B. ein Wikilink auf eine Seite, die main inzwischen umbenannt hat); sonst endet der Lauf mit Hinweis. Der Merge-Commit entsteht über wikitool, nicht über rohes git commit.
3. reconcile() lässt generierte Dateien aus dem Überlappungstest und erzeugt sie nach einem Rebase neu (kb/log.md wie in der Tabelle). Das betrifft jedes publish/sync, nicht nur den Intake-Lauf.
Akzeptanzkriterien
Der Lauf publiziert nie auf main; jede Änderung erreicht main nur über den Merge einer PR durch einen Menschen.
Hat main sich seit Anlage des Intake-Branches bewegt und haben beide Seiten nur generierte Dateien gemeinsam geändert, ist der Branch nach sync --merge-from main konfliktfrei mergebar; kb/index.md, INDEX.md, kb/provenance.md gleichen danach Byte für Byte dem, was index rebuild/sources rebuild-index auf dem Merge-Stand erzeugen; kb/log.md enthält jeden Eintrag beider Seiten genau einmal, main-Einträge zuerst.
Ein Frontmatter-Konflikt nur in Referenz-Arrays oder modified: wird ohne LLM gelöst (Vereinigung, Maximum).
Ein echter Textkonflikt in einer Seite hält den mechanischen Teil nicht auf: alle anderen Dateien sind gelöst, die Konfliktseiten werden namentlich gemeldet.
Zwei Ingests auf divergierten Ständen, die außer generierten Dateien nichts gemeinsam haben, führen bei publish/sync nicht mehr ins Rebase-Review-Gate; überlappende nicht generierte Dateien weiterhin schon.
Kein Schritt verwendet Force-Push oder öffnet ein Gate.
Die Instruction beschreibt Lauf, Ablehnung und Gate-Halt so, dass eine Sitzung ohne Vorwissen sie abarbeiten kann; ein Beispiel für einen Auslöser (Timer) steht in der menschlichen Doku, nicht in der Instruction.
pytest, docs verify, instructions verify grün; CI grün.
Betroffene Stellen
tools/chemenu/commands/git_publish.py (reconcile, touched_files, neue Merge-Variante von sync, Wiederverwendung von is_generated), commands/index_build.py, commands/log_append.py (Log-Vereinigung), frontmatter_io.py (Array-Vereinigung)
Kommando-Record von sync/publish in tools/CONTRACT.md
neue Instruction/Skill unter instructions/, instructions/gates.md (Intake-PR als Freigabeweg; Gate beendet den Lauf), instructions/publish-cycle.md
README.md, tools/README.md
Versionsteil
--minor: neue Option und neue Instruction; die Änderung an reconcile() lässt weniger Fälle ins Gate laufen, ändert aber kein Ergebnis eines Laufs, der es heute ohne Gate schafft. Drop-in.
Teil B aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). **Blockiert durch #177** (Erfassung, `raw status`, `--replaces-bundle`).
## Problem
Ändert sich die Doku eines verfolgten Repos, soll die Instanz ohne Handarbeit nachziehen - aber nichts davon darf `main` ohne menschliche Freigabe erreichen. Der Mensch soll dabei nicht jeden Schritt freigeben, sondern das Ergebnis: Doku-Änderung und daraus gelerntes Wissen in einer Ansicht.
Daneben ein Befund, der unabhängig von #171 gilt: `reconcile()` in `git_publish.py` zählt beim Überlappungstest (`touched_files`) auch die generierten Dateien mit. Zwei beliebige Ingests berühren beide `kb/index.md` und `kb/log.md`, überlappen also immer und landen im Rebase-Review-Gate (exit 42), obwohl genau diese Dateien mechanisch wiederherstellbar sind (`is_generated()`, ebenda).
## Entwurf
**1. Der Lauf** - eine neue Instruction/Skill (Arbeitsname `repo-intake`), die eine Sitzung im Checkout der Instanz unbeaufsichtigt abarbeitet. Host, Harness und Auslöser liegen außerhalb des Stacks; der Auslöser trägt keinen Inhalt, nur "prüf jetzt" (Timer als Grundlinie, Push-Signal aus CI/Webhook/Markierungsdatei optional).
1. `raw status --json` - nichts geändert → Ende.
2. Gibt es eine offene Intake-PR, arbeitet der Lauf auf deren Branch weiter; sonst neuer Branch `intake/<YYYY-MM-DD>` vom aktuellen `main`. **Eine offene Intake-PR zur Zeit; sie wächst, statt dass eine zweite entsteht.**
3. Branch auf Stand bringen: `main` einmergen (Punkt 2 unten).
4. Pro geändertem Repo: `raw capture --update` → `raw accept --replaces-bundle` → Ingest gegen den Editionsdiff (`wiki-ingest`, Abschnitt aus #177) → ein Commit pro Repo.
5. `publish --branch intake/...`; bei neuer PR Anlage über das Forge-Werkzeug der Sitzung (gitea-mcp, `gh`, ...), nie über `wikitool`. PR-Text je Repo: alter → neuer Commit mit Vergleichslink ins Quell-Repo, A/M/D-Liste, berührte Seiten, und "Konflikt aufgelöst in: ..." falls Punkt 2 eine Seite von Hand lösen musste.
6. **Merge der PR durch den Menschen = Freigabe.** Eine ohne Merge geschlossene PR heißt "diese Fassungen nicht": Der Lauf erfasst ein Repo erst wieder, wenn es über den abgelehnten Commit hinaus ist. Den PR-Zustand liest die Sitzung über ihr Forge-Werkzeug; `wikitool` bleibt forge-neutral.
7. Ein Mass-Update-Gate (exit 42) oder jede andere Gate-Antwort beendet den Lauf und wird als PR-Kommentar bzw. im Sitzungsprotokoll für den Menschen hinterlassen - nie geöffnet.
**2. `main` mechanisch einmergen** (Arbeitsname `sync --merge-from main`, auf dem Intake-Branch) - Merge statt Rebase, weil der Branch schon publiziert ist und Force-Push verboten bleibt (AGENTS.md Invariante 5). Konflikte werden nach Dateiart aufgelöst:
| Datei | Auflösung | LLM nötig |
|---|---|---|
| `kb/index.md`, `**/INDEX.md` | nach dem Merge neu erzeugen (`index rebuild`) | nein |
| `kb/provenance.md` | neu erzeugen (`sources rebuild-index`) | nein |
| `kb/log.md` | `main`-Fassung, danach die Einträge, die nur der Branch hat, in ihrer Reihenfolge | nein |
| Referenz-Arrays im Frontmatter (`related:`, `sources:`, `entities:`, `concepts:`), `modified:` | Mengenvereinigung bzw. Maximum | nein |
| Seitentext, `summary:`, alles Übrige | Gits normaler Drei-Wege-Merge; nur ein echter Textkonflikt bleibt übrig | nur für diese Seiten |
Echte Textkonflikte löst die Sitzung **nur in den betroffenen Seiten** und nennt sie im PR-Text. Nach dem Merge muss `lint` ohne neue Fehler durchlaufen (z. B. ein Wikilink auf eine Seite, die `main` inzwischen umbenannt hat); sonst endet der Lauf mit Hinweis. Der Merge-Commit entsteht über `wikitool`, nicht über rohes `git commit`.
**3. `reconcile()` lässt generierte Dateien aus dem Überlappungstest** und erzeugt sie nach einem Rebase neu (`kb/log.md` wie in der Tabelle). Das betrifft jedes `publish`/`sync`, nicht nur den Intake-Lauf.
## Akzeptanzkriterien
- [ ] Der Lauf publiziert nie auf `main`; jede Änderung erreicht `main` nur über den Merge einer PR durch einen Menschen.
- [ ] Hat `main` sich seit Anlage des Intake-Branches bewegt und haben beide Seiten nur generierte Dateien gemeinsam geändert, ist der Branch nach `sync --merge-from main` konfliktfrei mergebar; `kb/index.md`, `INDEX.md`, `kb/provenance.md` gleichen danach Byte für Byte dem, was `index rebuild`/`sources rebuild-index` auf dem Merge-Stand erzeugen; `kb/log.md` enthält jeden Eintrag beider Seiten genau einmal, `main`-Einträge zuerst.
- [ ] Ein Frontmatter-Konflikt nur in Referenz-Arrays oder `modified:` wird ohne LLM gelöst (Vereinigung, Maximum).
- [ ] Ein echter Textkonflikt in einer Seite hält den mechanischen Teil nicht auf: alle anderen Dateien sind gelöst, die Konfliktseiten werden namentlich gemeldet.
- [ ] Zwei Ingests auf divergierten Ständen, die außer generierten Dateien nichts gemeinsam haben, führen bei `publish`/`sync` nicht mehr ins Rebase-Review-Gate; überlappende *nicht* generierte Dateien weiterhin schon.
- [ ] Kein Schritt verwendet Force-Push oder öffnet ein Gate.
- [ ] Die Instruction beschreibt Lauf, Ablehnung und Gate-Halt so, dass eine Sitzung ohne Vorwissen sie abarbeiten kann; ein Beispiel für einen Auslöser (Timer) steht in der menschlichen Doku, nicht in der Instruction.
- [ ] `pytest`, `docs verify`, `instructions verify` grün; CI grün.
## Betroffene Stellen
- `tools/chemenu/commands/git_publish.py` (`reconcile`, `touched_files`, neue Merge-Variante von `sync`, Wiederverwendung von `is_generated`), `commands/index_build.py`, `commands/log_append.py` (Log-Vereinigung), `frontmatter_io.py` (Array-Vereinigung)
- Kommando-Record von `sync`/`publish` in `tools/CONTRACT.md`
- neue Instruction/Skill unter `instructions/`, `instructions/gates.md` (Intake-PR als Freigabeweg; Gate beendet den Lauf), `instructions/publish-cycle.md`
- `README.md`, `tools/README.md`
## Versionsteil
**`--minor`**: neue Option und neue Instruction; die Änderung an `reconcile()` lässt weniger Fälle ins Gate laufen, ändert aber kein Ergebnis eines Laufs, der es heute ohne Gate schafft. Drop-in.
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 B aus dem Design #171 (dort Entscheidungen und verworfene Alternativen). Blockiert durch #177 (Erfassung,
raw status,--replaces-bundle).Problem
Ändert sich die Doku eines verfolgten Repos, soll die Instanz ohne Handarbeit nachziehen - aber nichts davon darf
mainohne menschliche Freigabe erreichen. Der Mensch soll dabei nicht jeden Schritt freigeben, sondern das Ergebnis: Doku-Änderung und daraus gelerntes Wissen in einer Ansicht.Daneben ein Befund, der unabhängig von #171 gilt:
reconcile()ingit_publish.pyzählt beim Überlappungstest (touched_files) auch die generierten Dateien mit. Zwei beliebige Ingests berühren beidekb/index.mdundkb/log.md, überlappen also immer und landen im Rebase-Review-Gate (exit 42), obwohl genau diese Dateien mechanisch wiederherstellbar sind (is_generated(), ebenda).Entwurf
1. Der Lauf - eine neue Instruction/Skill (Arbeitsname
repo-intake), die eine Sitzung im Checkout der Instanz unbeaufsichtigt abarbeitet. Host, Harness und Auslöser liegen außerhalb des Stacks; der Auslöser trägt keinen Inhalt, nur "prüf jetzt" (Timer als Grundlinie, Push-Signal aus CI/Webhook/Markierungsdatei optional).raw status --json- nichts geändert → Ende.intake/<YYYY-MM-DD>vom aktuellenmain. Eine offene Intake-PR zur Zeit; sie wächst, statt dass eine zweite entsteht.maineinmergen (Punkt 2 unten).raw capture --update→raw accept --replaces-bundle→ Ingest gegen den Editionsdiff (wiki-ingest, Abschnitt aus #177) → ein Commit pro Repo.publish --branch intake/...; bei neuer PR Anlage über das Forge-Werkzeug der Sitzung (gitea-mcp,gh, ...), nie überwikitool. PR-Text je Repo: alter → neuer Commit mit Vergleichslink ins Quell-Repo, A/M/D-Liste, berührte Seiten, und "Konflikt aufgelöst in: ..." falls Punkt 2 eine Seite von Hand lösen musste.wikitoolbleibt forge-neutral.2.
mainmechanisch einmergen (Arbeitsnamesync --merge-from main, auf dem Intake-Branch) - Merge statt Rebase, weil der Branch schon publiziert ist und Force-Push verboten bleibt (AGENTS.md Invariante 5). Konflikte werden nach Dateiart aufgelöst:kb/index.md,**/INDEX.mdindex rebuild)kb/provenance.mdsources rebuild-index)kb/log.mdmain-Fassung, danach die Einträge, die nur der Branch hat, in ihrer Reihenfolgerelated:,sources:,entities:,concepts:),modified:summary:, alles ÜbrigeEchte Textkonflikte löst die Sitzung nur in den betroffenen Seiten und nennt sie im PR-Text. Nach dem Merge muss
lintohne neue Fehler durchlaufen (z. B. ein Wikilink auf eine Seite, diemaininzwischen umbenannt hat); sonst endet der Lauf mit Hinweis. Der Merge-Commit entsteht überwikitool, nicht über rohesgit commit.3.
reconcile()lässt generierte Dateien aus dem Überlappungstest und erzeugt sie nach einem Rebase neu (kb/log.mdwie in der Tabelle). Das betrifft jedespublish/sync, nicht nur den Intake-Lauf.Akzeptanzkriterien
main; jede Änderung erreichtmainnur über den Merge einer PR durch einen Menschen.mainsich seit Anlage des Intake-Branches bewegt und haben beide Seiten nur generierte Dateien gemeinsam geändert, ist der Branch nachsync --merge-from mainkonfliktfrei mergebar;kb/index.md,INDEX.md,kb/provenance.mdgleichen danach Byte für Byte dem, wasindex rebuild/sources rebuild-indexauf dem Merge-Stand erzeugen;kb/log.mdenthält jeden Eintrag beider Seiten genau einmal,main-Einträge zuerst.modified:wird ohne LLM gelöst (Vereinigung, Maximum).publish/syncnicht mehr ins Rebase-Review-Gate; überlappende nicht generierte Dateien weiterhin schon.pytest,docs verify,instructions verifygrün; CI grün.Betroffene Stellen
tools/chemenu/commands/git_publish.py(reconcile,touched_files, neue Merge-Variante vonsync, Wiederverwendung vonis_generated),commands/index_build.py,commands/log_append.py(Log-Vereinigung),frontmatter_io.py(Array-Vereinigung)sync/publishintools/CONTRACT.mdinstructions/,instructions/gates.md(Intake-PR als Freigabeweg; Gate beendet den Lauf),instructions/publish-cycle.mdREADME.md,tools/README.mdVersionsteil
--minor: neue Option und neue Instruction; die Änderung anreconcile()lässt weniger Fälle ins Gate laufen, ändert aber kein Ergebnis eines Laufs, der es heute ohne Gate schafft. Drop-in.