Intake-Lauf: unbeaufsichtigter Repo-Abgleich als PR, main mechanisch einmergen statt neu erzeugen #178

Open
opened 2026-10-04 20:39:49 +00:00 by torben · 0 comments
Owner

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.

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.
torben added the prio/plannedsize/Larea/workflowkind/buildstatus/blocked labels 2026-10-04 20:39:49 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#178