20 KiB
DisClaw — Projekt- und Git-Workflow
Autor: Projekt- und Git-Manager-Agent
Datum: 2026-04-08
Quelle der Wahrheit für Inhalt: docs/development-plan.md.
Quelle der Wahrheit für den Backlog: docs/backlog.md.
Dieses Dokument definiert, wie an DisClaw gearbeitet wird: wie Branches heißen, wie Forgejo strukturiert ist, wie Issues geschnitten werden, wie ein Developer-Agent (Claude Sonnet 4.6) pro Issue vorgeht, und wann eine Phase als abgeschlossen gilt.
Forgejo-Remote: http://localhost:3000/dev/disclaw.git (lokale Instanz).
1. Branch-Strategie
1.1 main ist protected
mainist protected. Keine direkten Pushes, keine Force-Pushes, kein Rebase.- Änderungen kommen ausschließlich über Pull Requests.
- Jeder PR benötigt mindestens ein Review-Approval und grüne Checks, bevor er gemerged wird.
- Merge-Strategie: Squash-Merge. Eine Aufgabe = ein Commit auf
main. Das hält die Historie linear und macht Rollback (git revert <sha>) trivial.
1.2 Naming-Convention für Feature-Branches
phase-<n>/<kurz-slug>
Beispiele:
phase-0/harden-spawnphase-0/sanitize-envphase-1/channel-queuephase-1/split-for-discord
Begründung: Der Phase-Prefix macht sofort sichtbar, an welchem Phasen-Ziel ein Branch
arbeitet. Das hilft beim Filtern in Forgejo (refs/heads/phase-0/*), beim Review-Priorisieren
und beim mentalen Mapping zu Milestones. Der Kurz-Slug ist imperativ und eindeutig
(harden-spawn, nicht fix-stuff). Keine Trailing-Nummern, keine Issue-IDs im Branch-Namen —
die Verknüpfung passiert im PR via Closes #N.
1.3 Lifecycle eines Feature-Branches
- Issue wird in Forgejo
Readygesetzt und einem Developer-Agent zugewiesen. - Developer-Agent checkt aktuellen
mainaus (git fetch origin && git checkout main && git pull --ff-only). - Developer-Agent erstellt den Branch:
git checkout -b phase-<n>/<slug>. - Implementierung gegen die Definition of Done des Issues.
- Commits folgen Conventional-Commits (§5).
- Push:
git push -u origin phase-<n>/<slug>. - PR gegen
mainöffnen (Template in.forgejo/PULL_REQUEST_TEMPLATE.md). - Review, Anpassungen, grüne Checks.
- Squash-Merge.
- Branch wird automatisch gelöscht (Forgejo-Setting: "Delete branch after merge").
1.4 Keine langlebigen Integration-Branches
- Kein
dev, keinnext, keinphase-1-integration. - Eine Phase gilt als abgeschlossen, wenn alle Issues ihres Milestones gemerged sind und der manuelle Smoke-Test (§8) durch ist.
- Der einzige geteilte Branch ist
main.
2. Forgejo-Projektstruktur
2.1 Labels
Maximal schlank gehalten. Ziel: jeder Developer versteht das Label-Set in < 30 Sekunden.
| Name | Farbe (hex) | Zweck |
|---|---|---|
phase:0 |
#b71c1c |
Phase 0 — Härtung |
phase:1 |
#e65100 |
Phase 1 — Solides MVP |
phase:2 |
#f9a825 |
Phase 2 — Context-Effizienz (--resume) |
phase:3 |
#2e7d32 |
Phase 3 — UX-Feinschliff |
phase:4 |
#1565c0 |
Phase 4 — Skills & Permissions |
phase:5 |
#4527a0 |
Phase 5 — Streaming |
phase:6 |
#424242 |
Phase 6+ — Zukunft / Platzhalter |
type:feat |
#0e8a16 |
Neues Feature |
type:fix |
#d93f0b |
Bugfix |
type:refactor |
#5319e7 |
Umstrukturierung ohne Verhaltensänderung |
type:test |
#006b75 |
Tests oder Test-Infrastruktur |
type:docs |
#1d76db |
Dokumentation |
type:chore |
#bfbfbf |
Build, CI, Dependencies, Housekeeping |
priority:p0 |
#b60205 |
Blocker, muss sofort |
priority:p1 |
#d93f0b |
Wichtig in aktueller Phase |
priority:p2 |
#fbca04 |
Kann in späteren Phasen warten |
blocked |
#000000 |
Hängt an anderem Issue — Body nennt Blocker |
needs-review |
#fbca04 |
PR wartet auf Review |
security |
#b60205 |
Sicherheitsrelevant (CVE, Secret-Leak, Injection) |
Begründung: Phase-Label + Type-Label + Priority-Label sind orthogonal und genügen, um
einen Backlog zu filtern. Zusätzliche Labels wie good-first-issue, help-wanted,
discussion lehnen wir ab — sie sind bei einem Zwei-Agent-Setup (PM + Developer) wertlos
und erzeugen Noise. security ist ein Sonderfall: separat, weil es automatisierte
Eskalations-Policies erlauben soll (z. B. P0 + security → sofortige Zuweisung).
2.2 Milestones
Ein Milestone pro Phase. Titel und Description exakt aus docs/development-plan.md §4
abgeleitet:
| Milestone | Titel | Description (Kurzform der Definition of Done) |
|---|---|---|
| M0 | Phase 0 — Härtung | Kein shell: true, sanitizedEnv, Path-Containment, --bare + --append-system-prompt-file, Workspace-Root außerhalb Repo, CLAUDE.md mit Injection-Klausel, settings.json mit Deny-Liste. Alle P0-Security-Issues geschlossen. |
| M1 | Phase 1 — Solides MVP | Pino-Logger, ChannelQueue, Semaphore, splitForDiscord, Zod-Schemas, codeblock-aware Splitting, .env.example, npm test grün, keine console.* im Prod-Code. |
| M2 | Phase 2 — Context-Effizienz | claude_session_id in DB, Runner nutzt --resume, Fork-Case abgefangen, Usage-Metriken geloggt, Session überlebt Bot-Neustart. |
| M3 | Phase 3 — UX-Feinschliff | Attachment-Download, Reaction-Markers, sendResponse mit Attachment-Fallback, Typing-Refresh 8s. |
| M4 | Phase 4 — Skills & Permissions | Profile-Registry (5 Profiles), PreToolUse-Hook guard-tool.cjs, Audit-Log, profile-Option in /new-agent. |
| M5 | Phase 5 — Streaming | runAgentStream, NDJSON-Parser defensiv, stream-to-discord mit Rate-Limit, Feature-Flag in disclaw.yaml. |
| M6 | Phase 6+ — Zukunft | /list-agents, /delete-agent, /agent-config, Rate-Limits, Budget, Agent-zu-Agent, Docker, Dashboard. Nur Platzhalter, wird bei Phase-Start verfeinert. |
Ein Milestone ist closed, sobald alle seine Issues closed sind und der Smoke-Test
erfolgreich durchgeführt wurde.
2.3 Kanban-Board (Forgejo Project)
Ein einziges Projekt namens DisClaw Delivery mit fünf Spalten. Kein separates Board pro Phase — Phase wird über Label-Filter dargestellt.
| Spalte | Kriterium für Einzug |
|---|---|
Backlog |
Issue existiert, wurde vom PM geschnitten, hat Titel, Phase-Label, Type-Label, Milestone. Noch nicht priorisiert für aktuelle Phase. |
Ready |
Issue hat vollständige Definition of Done, alle Dependencies aufgelöst (keine blocked-Labels), Priority gesetzt, für aktuelle Phase priorisiert. Darf sofort von einem Developer-Agent gezogen werden. |
In Progress |
Ein Developer-Agent ist zugewiesen, ein Feature-Branch existiert und wurde nach origin gepusht (mindestens der erste Commit). |
In Review |
PR ist geöffnet, needs-review-Label gesetzt, CI-Checks laufen oder sind grün. |
Done |
PR ist gemerged, Issue ist geschlossen. Kein manuelles Verschieben — Forgejo erledigt das automatisch über Closes #N im PR-Body. |
Regel: Eine Karte wird rückwärts verschoben, wenn ein Kriterium verloren geht
(z. B. PR wird geschlossen ohne Merge → zurück nach In Progress; neue Abhängigkeit
wird entdeckt → zurück nach Backlog mit blocked-Label).
2.4 Bootstrap via Forgejo-API
Die folgenden Endpunkte setzen Labels, Milestones und das Projekt-Board auf. Alle Pfade sind echte Forgejo-API-Pfade (Forgejo spricht die Gitea-API v1 und deckt zusätzlich eigene Project-Routen ab). Das Bootstrap passiert per Script (siehe §2.5); dieses Dokument erstellt nichts direkt.
Umgebungsvariablen:
FORGEJO_BASE=http://localhost:3000
FORGEJO_OWNER=dev
FORGEJO_REPO=disclaw
FORGEJO_TOKEN=<personal-access-token mit repo-scope>
Labels anlegen: POST /api/v1/repos/{owner}/{repo}/labels
curl -sS -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"phase:0","color":"#b71c1c","description":"Phase 0 — Härtung"}' \
"$FORGEJO_BASE/api/v1/repos/$FORGEJO_OWNER/$FORGEJO_REPO/labels"
Milestones anlegen: POST /api/v1/repos/{owner}/{repo}/milestones
curl -sS -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Phase 0 — Härtung","description":"Kein shell:true, sanitizedEnv, ..."}' \
"$FORGEJO_BASE/api/v1/repos/$FORGEJO_OWNER/$FORGEJO_REPO/milestones"
Issues anlegen: POST /api/v1/repos/{owner}/{repo}/issues
curl -sS -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"...","body":"...","labels":[1,5,12],"milestone":1}' \
"$FORGEJO_BASE/api/v1/repos/$FORGEJO_OWNER/$FORGEJO_REPO/issues"
(Label- und Milestone-IDs kommen aus der Response der Create-Calls oben.)
Branch-Protection für main: POST /api/v1/repos/{owner}/{repo}/branch_protections
curl -sS -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"branch_name":"main",
"enable_push":false,
"required_approvals":1,
"enable_status_check":true,
"status_check_contexts":["build","test","lint"],
"block_on_rejected_reviews":true,
"block_on_outdated_branch":true
}' \
"$FORGEJO_BASE/api/v1/repos/$FORGEJO_OWNER/$FORGEJO_REPO/branch_protections"
Projekte (Kanban-Board): Forgejo unterstützt Projekte pro Repo unter
POST /api/v1/repos/{owner}/{repo}/projects mit {"title":"DisClaw Delivery","board_type":"basic_kanban"}.
Spalten werden nachgelagert angelegt über POST /api/v1/projects/{id}/columns mit
{"title":"Ready"} etc.
Alternative: tea CLI (https://gitea.com/gitea/tea). Die relevanten Kommandos:
tea labels create --name "phase:0" --color "#b71c1c" --description "Phase 0 — Härtung"
tea milestones create --title "Phase 0 — Härtung" --description "..."
tea issues create --title "..." --body "..." --labels "phase:0,type:fix,priority:p0" --milestone "Phase 0 — Härtung"
tea authentifiziert sich über tea login add einmalig mit FORGEJO_TOKEN.
2.5 Bootstrap-Script (skizziert, NICHT angelegt)
Ein späteres Script unter scripts/bootstrap-forgejo.ts soll:
docs/backlog.mdparsen (jedes Issue als YAML-Frontmatter-Block oder strukturiertes Markdown).- Labels anlegen (idempotent: vorher
GET /labels, skip wenn vorhanden). - Milestones anlegen (idempotent).
- Projekt + Spalten anlegen (idempotent).
- Branch-Protection setzen.
- Für jedes Backlog-Issue:
POST /issues,DIS-XXXim Backlog-File durch die echte Issue-Nummer ersetzen. - Jede Karte ins
Backlog-Kanban-Board einhängen.
Abhängigkeiten: node-fetch (oder Node 20 native), js-yaml, nichts weiteres.
Läuft einmalig, ist re-runnable (idempotent). Legt dieses Dokument nicht an.
3. Issue-Template
Jedes Issue folgt exakt diesem Template. Abweichungen werden vom PM vor dem Create
korrigiert. Das Template landet zusätzlich unter
.forgejo/ISSUE_TEMPLATE/feature.md als Forgejo-Template.
## Ziel
<1–2 Sätze, was erreicht werden soll.>
## Kontext
Referenz: `docs/development-plan.md` §<Abschnitt>, ggf. `docs/ai-engineer-analyse.md` §<Abschnitt>.
<Kurze Begründung, warum das jetzt gemacht wird und welches Problem es löst.>
## Scope
- **In**: <konkret was gemacht wird>
- **Out**: <konkret was NICHT gemacht wird — damit kein Scope-Creep>
## Definition of Done
- [ ] <konkreter, überprüfbarer Punkt 1>
- [ ] <konkreter, überprüfbarer Punkt 2>
- [ ] Unit-Tests geschrieben und grün (`npm test`)
- [ ] Keine `console.*` im Produktionscode (falls betroffen)
- [ ] PR-Template ausgefüllt, Review bestanden, CI grün
## Dateien (erwartet)
- `src/<pfad1>` — <zweck>
- `tests/<pfad2>` — <zweck>
## Branch
`phase-<n>/<slug>`
## Abhängigkeiten
- Blockiert durch: #<issue-nr> (falls vorhanden)
- Blockiert: #<issue-nr> (falls vorhanden)
4. PR-Template
Datei: .forgejo/PULL_REQUEST_TEMPLATE.md (Forgejo liest dieses Verzeichnis; Fallback
.gitea/PULL_REQUEST_TEMPLATE.md wird ebenfalls erkannt).
## Summary
<1–3 Sätze: was ändert dieser PR und warum.>
## Closes
Closes #<issue-nr>
## Changes
- <Bullet für jede relevante Datei-Gruppe>
## Test plan
- [ ] `npm run build` läuft grün
- [ ] `npm test` läuft grün
- [ ] Manueller Smoke-Test: <falls sinnvoll; sonst "n/a">
- [ ] Neue Dateien haben Unit-Tests (falls anwendbar)
## Screenshots / Logs
<optional — Log-Snippets bei Runtime-Änderungen>
## Breaking Changes
- [ ] Keine
- [ ] Ja: <Beschreibung + Migrationshinweis>
## Reviewer-Checklist
- [ ] Code-Diff passt zum Issue-Scope (kein Scope-Creep)
- [ ] Definition of Done aus dem Issue ist erfüllt
- [ ] Keine neuen `console.*`-Calls (außer in Tests/Probe-Scripts)
- [ ] Keine Secrets, Tokens oder absoluten Pfade in Logs
- [ ] `shell: false` bei jedem Spawn (falls Spawn betroffen)
- [ ] Sicherheitsrelevanter Code hat mindestens einen negativen Test
5. Commit-Convention
Conventional Commits mit Phase-Scope. Format:
<type>(<scope>): <subject>
<optional body>
<optional footer: Closes #N>
Types (deckungsgleich mit type:*-Labels): feat, fix, refactor, test, docs,
chore, perf, build, ci.
Scope: Phase oder Modul. Bevorzugt Phase bei Phase-spezifischen Changes
(feat(phase-1): add channel queue), Modul bei Cross-Phase-Infrastruktur
(fix(runner): handle ENOENT on claude.cmd).
Beispiele:
feat(phase-0): sanitize child env before spawning claude
Removes DISCORD_BOT_TOKEN and all TOKEN_/SECRET_/API_KEY_ variables
from the environment passed to the claude child process. Adds an
allow-only whitelist helper in src/runtime/env.ts.
Closes #3
fix(runner): resolve claude.cmd via cross-spawn on Windows
CVE-2024-27980 blocks direct .cmd spawning without shell:true on
Node 20.12+. cross-spawn handles the wrapper resolution and arg
quoting transparently.
Closes #2
refactor(router): extract ChannelQueue into runtime/channel-queue.ts
Regel: Subject <= 72 Zeichen, imperativ, kein Punkt am Ende. Body erklärt das Warum, nicht das Was — der Diff zeigt bereits das Was.
Squash-Merge: Beim Merge in main wird der PR-Titel zum finalen Commit-Subject.
Der PM achtet deshalb darauf, dass der PR-Titel Conventional-Commits-konform ist.
6. Developer-Agent-Workflow (Claude Sonnet 4.6)
Ein Developer-Agent arbeitet genau ein Issue pro Branch/PR. Nie Issue-Pooling.
6.1 Ablauf pro Issue
- Ticket ziehen: Issue aus
Readynehmen, sich selbst zuweisen, Status aufIn Progresssetzen. - Branch erstellen:
git fetch origin git checkout main git pull --ff-only git checkout -b phase-<n>/<slug> - Implementieren: Strikt entlang der Definition of Done. Keine zusätzlichen
Änderungen außerhalb des
Dateien (erwartet)-Blocks — wenn eine zusätzliche Datei nötig wird, zuerst das Issue kommentieren, nicht einfach drauflosschreiben. - Tests: Für jede neue reine Funktion ein Unit-Test. Integration-Tests nur, wenn das Issue es explizit verlangt oder ein Mehrkomponenten-Flow betroffen ist.
- Commits: Kleine, thematisch fokussierte Commits mit Conventional-Format. Der finale Squash verdichtet sie ohnehin, aber die Branch-Historie hilft beim Review.
- Lokale Vor-Checks:
Beide müssen grün sein, bevor gepusht wird.npm run build npm test - Pushen:
git push -u origin phase-<n>/<slug>. - PR öffnen: Via
tea pulls createoder Forgejo-Web-UI. PR-Template ausfüllen,Closes #<n>einfügen, Labelneeds-reviewsetzen. SpalteIn Review. - Self-Review-Checklist: Reviewer-Checklist im PR-Body selbst durchgehen, bevor um ein Review gebeten wird. Saves a round-trip.
- Auf Review warten. Bei Change-Requests: Anpassungen als neue Commits (kein Force-Push) auf denselben Branch.
- Nach Merge: Branch wird automatisch gelöscht, Issue schließt automatisch über
Closes #N.
6.2 Rolle des Projekt-/Git-Manager-Agents (dieser Agent)
- Schneidet Issues gemäß
docs/development-plan.mdund schreibt sie indocs/backlog.md. - Priorisiert innerhalb eines Milestones (
priority:p0>p1>p2). - Weist Developer-Agenten zu (im Ein-Developer-Setup trivial, bei mehreren nach Load).
- Koordiniert Reviews, triggert bei festhängenden PRs nach.
- Stellt Phasen-Abschluss fest, führt den Smoke-Test durch (siehe §8).
- Entscheidet über
blocked-Labels und löst Blocker auf, indem fehlende Vor-Issues nachgeschnitten werden. - Schreibt keinen Produktionscode. Ausnahme: Bootstrap-Scripts und Dokumentation.
6.3 Rollback bei kaputtem PR
- PR ist nach Merge kaputt:
git revert <merge-sha>auf einem neuen Branchphase-<n>/revert-<slug>, normaler PR-Flow. Niemalsgit resetaufmain. - PR ist vor Merge kaputt: Branch bleibt offen,
In Review→In Progress, Developer-Agent fixt auf demselben Branch. Kein Force-Push. Keine neuen Branches. - Feature muss komplett zurückgezogen werden: Issue wieder öffnen,
blocked-Label, Begründung als Kommentar, Revert-PR mergen.
7. Test-Gate pro PR
Jeder PR muss vor Merge folgende Gates passieren:
- Build:
npm run build— TypeScript-Compile ohne Fehler. - Unit-Tests:
npm test— Vitest-Suite grün. - Lint (sobald Phase 0 Issue "Add ESLint +
shell:false-Rule" gemerged ist):npm run lintgrün. - Manueller Smoke-Test: Nur bei Änderungen am Bot-Startup, Runner, oder Discord-Integration. Im PR-Template als separater Haken dokumentiert.
CI wird in Phase 0 als eigenes Issue aufgesetzt (siehe docs/backlog.md Issue
DIS-008: Forgejo Actions Workflow mit build, test, lint auf ubuntu-latest
und windows-latest).
Lokal ausführbar:
npm install
npm run build
npm test
Ein fehlgeschlagener Test = kein Merge. Keine --skip-Flags, kein xit-Workaround.
8. Phasen-Übergang
8.1 Definition "Phase abgeschlossen"
Eine Phase ist genau dann abgeschlossen, wenn alle folgenden Bedingungen gelten:
- Alle Issues des Milestones sind im Status
Done(PR gemerged). - Der manuelle Smoke-Test für die Phase ist durchgelaufen (siehe §8.2).
- Der PM hat das Milestone in Forgejo manuell auf
closedgesetzt. - Der User hat die nächste Phase explizit freigegeben (siehe §8.3).
8.2 Smoke-Test pro Phase
Der PM führt einen kurzen End-to-End-Test auf dem aktuellen main durch. Mindest-Umfang:
- Phase 0: Bot startet,
/new-agenterstellt Workspace unter~/.disclaw/workspaces/,.claude/settings.jsonenthält Deny-Liste, ein Test-Prompt mit Eval-Versuch (echo $DISCORD_BOT_TOKEN) kommt leer zurück. - Phase 1: Drei Messages schnell hintereinander im gleichen Channel werden in
Reihenfolge beantwortet, Logs sind strukturiertes JSON,
splitForDiscordschneidet einen langen Codeblock sauber auf. - Phase 2: Zweite Message im selben Channel zeigt
cache_read_input_tokens > 0im Log. - Phase 3: Bild-Upload im Discord wird vom Agent beschrieben.
- Phase 4: Agent mit
profile:researcherkann nicht schreiben; Audit-Log enthält blockierten Tool-Call. - Phase 5: Lange Antwort erscheint inkrementell, finaler Stand stimmt.
Das konkrete Skript wird in Phase 1 als scripts/smoke/<phase>.md-Checkliste gepflegt.
8.3 Keine automatische Phasen-Progression
Der PM startet die nächste Phase nicht automatisch. Der User muss explizit
freigeben ("Phase X ist fertig, starte Phase Y"). Ohne diese Freigabe bleibt der Backlog
für die nächste Phase im Zustand Backlog (noch nicht Ready).
Grund: Phasen-Übergänge sind der natürliche Moment, um den Plan zu überprüfen, die verbleibenden Epic-Level-Issues der nächsten Phase zu verfeinern, und eventuell gelernte Erkenntnisse zurückzuspielen.
9. Anhang — Konventionen zusammengefasst
| Artefakt | Ort | Format |
|---|---|---|
| Branch-Name | Git | phase-<n>/<slug> |
| Commit-Message | Git | Conventional Commits mit Phase-Scope |
| Issue | Forgejo | Template §3, Labels aus §2.1, Milestone aus §2.2 |
| PR | Forgejo | Template §4, squash-merge, Closes #N im Body |
| PR-Template-Datei | Repo | .forgejo/PULL_REQUEST_TEMPLATE.md |
| Issue-Template-Datei | Repo | .forgejo/ISSUE_TEMPLATE/feature.md |
| Backlog | Repo | docs/backlog.md (Source of Truth vor Forgejo-Sync) |
| Workflow | Repo | docs/workflow.md (dieses Dokument) |
| Bootstrap-Script | Repo | scripts/bootstrap-forgejo.ts (noch nicht angelegt) |