Compare commits

..

No commits in common. "59c5a0c3872841d282e6297f78fb9f42ce21a698" and "8ca3ebffde0779d5293c1c549c79ea42f617fb52" have entirely different histories.

25 changed files with 5 additions and 2981 deletions

View file

@ -1,25 +0,0 @@
## Summary
<!-- 13 Bullet Points was geändert wurde -->
-
## Closes
<!-- Issue-Referenz: Closes #DIS-XXX -->
Closes #
## Test Plan
<!-- Welche Tests wurden hinzugefügt / angepasst? Wie manuell verifiziert? -->
- [ ] Unit-Tests geschrieben und grün (`npm test`)
- [ ] `npm run build` grün
- [ ] `npm run lint` grün (wenn vorhanden)
- [ ] Manuelle Verifikation:
## Breaking Changes
<!-- Ja/Nein. Wenn ja: was muss der User beim Update tun? -->
Nein
## Reviewer-Checkliste
- [ ] Kein `shell: true` in `src/` (`grep -r "shell: true" src/` → leer)
- [ ] Kein `process.env` direkt im Runner (nur via `sanitizedEnv`)
- [ ] Kein absoluter Pfad aus dem Workspace-Root nach außen
- [ ] Tests decken Error-Cases ab, nicht nur Happy Path
- [ ] Kein `console.*` im Produktionscode (Pino stattdessen — ab Phase 1)

View file

@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
"file": "tasks/BOARD.md",
"file": "docs/ai-engineer-analyse.md",
"mode": "preview",
"source": false
},
"icon": "lucide-file",
"title": "BOARD"
"title": "ai-engineer-analyse"
}
}
]
@ -183,29 +183,10 @@
"bases:Neue Base erstellen": false
}
},
"active": "1decbe58679087d4",
"active": "8a9c3ac5d2b3b989",
"lastOpenFiles": [
"tasks/DIS-107.md",
"tasks/DIS-106.md",
"docs/workflow.md",
"docs/backlog.md",
"tasks/DIS-105.md",
"tasks/DIS-104.md",
"tasks/DIS-103.md",
"tasks/DIS-102.md",
"tasks/DIS-101.md",
"tasks/DIS-008.md",
"tasks/DIS-007.md",
"tasks/DIS-006.md",
"tasks/DIS-005.md",
"tasks/DIS-004.md",
"tasks/DIS-003.md",
"tasks/DIS-002.md",
"tasks/DIS-001.md",
"tasks/BOARD.md",
"tasks",
"docs/ai-engineer-analyse.md",
"docs/cli-feature-answers.md",
"docs/development-plan.md"
"docs/development-plan.md",
"docs/ai-engineer-analyse.md"
]
}

File diff suppressed because it is too large Load diff

View file

@ -1,496 +0,0 @@
# 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
- `main` ist **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-spawn`
- `phase-0/sanitize-env`
- `phase-1/channel-queue`
- `phase-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
1. Issue wird in Forgejo `Ready` gesetzt und einem Developer-Agent zugewiesen.
2. Developer-Agent checkt aktuellen `main` aus (`git fetch origin && git checkout main && git pull --ff-only`).
3. Developer-Agent erstellt den Branch: `git checkout -b phase-<n>/<slug>`.
4. Implementierung gegen die Definition of Done des Issues.
5. Commits folgen Conventional-Commits (§5).
6. Push: `git push -u origin phase-<n>/<slug>`.
7. PR gegen `main` öffnen (Template in `.forgejo/PULL_REQUEST_TEMPLATE.md`).
8. Review, Anpassungen, grüne Checks.
9. Squash-Merge.
10. Branch wird automatisch gelöscht (Forgejo-Setting: "Delete branch after merge").
### 1.4 Keine langlebigen Integration-Branches
- Kein `dev`, kein `next`, kein `phase-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`
```bash
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`
```bash
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`
```bash
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`
```bash
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:
```bash
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:
1. `docs/backlog.md` parsen (jedes Issue als YAML-Frontmatter-Block oder strukturiertes
Markdown).
2. Labels anlegen (idempotent: vorher `GET /labels`, skip wenn vorhanden).
3. Milestones anlegen (idempotent).
4. Projekt + Spalten anlegen (idempotent).
5. Branch-Protection setzen.
6. Für jedes Backlog-Issue: `POST /issues`, `DIS-XXX` im Backlog-File durch die echte
Issue-Nummer ersetzen.
7. 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.
```markdown
## Ziel
<12 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).
```markdown
## Summary
<13 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
1. **Ticket ziehen**: Issue aus `Ready` nehmen, sich selbst zuweisen, Status auf
`In Progress` setzen.
2. **Branch erstellen**:
```bash
git fetch origin
git checkout main
git pull --ff-only
git checkout -b phase-<n>/<slug>
```
3. **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.
4. **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.
5. **Commits**: Kleine, thematisch fokussierte Commits mit Conventional-Format.
Der finale Squash verdichtet sie ohnehin, aber die Branch-Historie hilft beim Review.
6. **Lokale Vor-Checks**:
```bash
npm run build
npm test
```
Beide müssen grün sein, bevor gepusht wird.
7. **Pushen**: `git push -u origin phase-<n>/<slug>`.
8. **PR öffnen**: Via `tea pulls create` oder Forgejo-Web-UI. PR-Template ausfüllen,
`Closes #<n>` einfügen, Label `needs-review` setzen. Spalte `In Review`.
9. **Self-Review-Checklist**: Reviewer-Checklist im PR-Body selbst durchgehen, bevor
um ein Review gebeten wird. Saves a round-trip.
10. **Auf Review warten**. Bei Change-Requests: Anpassungen als neue Commits (kein
Force-Push) auf denselben Branch.
11. **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.md` und schreibt sie in `docs/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 Branch
`phase-<n>/revert-<slug>`, normaler PR-Flow. Niemals `git reset` auf `main`.
- **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:
1. **Build**: `npm run build` — TypeScript-Compile ohne Fehler.
2. **Unit-Tests**: `npm test` — Vitest-Suite grün.
3. **Lint** (sobald Phase 0 Issue "Add ESLint + `shell:false`-Rule" gemerged ist):
`npm run lint` grün.
4. **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**:
```bash
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:
1. Alle Issues des Milestones sind im Status `Done` (PR gemerged).
2. Der manuelle Smoke-Test für die Phase ist durchgelaufen (siehe §8.2).
3. Der PM hat das Milestone in Forgejo manuell auf `closed` gesetzt.
4. 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-agent` erstellt Workspace unter `~/.disclaw/workspaces/`,
`.claude/settings.json` enthä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, `splitForDiscord` schneidet
einen langen Codeblock sauber auf.
- **Phase 2**: Zweite Message im selben Channel zeigt `cache_read_input_tokens > 0` im Log.
- **Phase 3**: Bild-Upload im Discord wird vom Agent beschrieben.
- **Phase 4**: Agent mit `profile:researcher` kann 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) |

View file

@ -1,372 +0,0 @@
#!/usr/bin/env python3
"""Bootstrap Forgejo issues for DisClaw. Run once after labels+milestones exist."""
import requests
import sys
TOKEN = "43a3a524105f5ef6049393344cc4d01d3bc96b8b"
BASE = "http://localhost:3000/api/v1/repos/dev/disclaw"
HEADERS = {"Authorization": f"token {TOKEN}", "Content-Type": "application/json"}
# Label IDs (set by bootstrap-labels step):
# phase:0=1, phase:1=2, type:feat=8, type:fix=9, type:refactor=10
# type:test=11, type:docs=12, type:chore=13, type:ci=14
# priority:p0=15, priority:p1=16, priority:p2=17, security=18, blocked=19
PHASE0_ISSUES = [
{
"title": "DIS-001: Workspace-Root nach ~/.disclaw/workspaces/ verschieben",
"milestone": 1,
"labels": [1, 8, 15, 18],
"body": (
"## Ziel\n"
"Workspaces standardmaessig unter `~/.disclaw/workspaces/<agent>/` anlegen -- ausserhalb des DisClaw-Repos. "
"Verhindert CLAUDE.md-Walk-Up-Leak.\n\n"
"## Kontext\n"
"Claude Code walkt Parent-Directories hoch und konkateniert alle CLAUDE.md. "
"Workspace unter `disclaw/workspaces/` erbt die DisClaw-Projekt-Identitaet.\n"
"Referenz: `docs/development-plan.md` Abschnitt 2.9, `docs/cli-feature-answers.md` Frage 8\n\n"
"## Scope\n"
"**In:** `disclaw.yaml` Default aendern, Tilde-Expansion in `src/config/loader.ts`, "
"`new-agent.ts` legt Workspace unter resolvierten Root an, Warning-Log wenn Root innerhalb Repo, README-Migrationshinweis\n"
"**Out:** Automatische Migration, `--bare`-Flags (DIS-004)\n\n"
"## Definition of Done\n"
"- [ ] `disclaw.yaml` Default ist `workspaces_root: \"~/.disclaw/workspaces\"`\n"
"- [ ] `src/config/loader.ts` expandiert `~` auf Linux/Mac/Windows korrekt\n"
"- [ ] `new-agent` legt Workspace unter neuem Root an, DB speichert absoluten Pfad\n"
"- [ ] Warning-Log wenn Root innerhalb Repo\n"
"- [ ] Unit-Test `tests/unit/workspace-root-resolve.test.ts` gruen\n"
"- [ ] README-Abschnitt 'Migration bestehender Workspaces'\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/workspace-root-home`\n\n"
"## Abhaengigkeiten\nKeine."
),
},
{
"title": "DIS-002: shell:false + cross-spawn Windows-Fix im Runner",
"milestone": 1,
"labels": [1, 9, 15, 18],
"body": (
"## Ziel\n"
"Command-Injection ueber Discord-Input eliminieren. `shell: true` aus `runner.ts` entfernen. "
"Windows-`.cmd`-Wrapper korrekt aufloesen, CVE-2024-27980 umgehen.\n\n"
"## Kontext\n"
"`src/agent/runner.ts` nutzt aktuell `shell: true` -- Shell-Injection-Vektor. "
"`spawn('claude', args, { shell: false })` schlaegt auf Windows fehl (nur `claude.cmd`).\n"
"Referenz: `docs/development-plan.md` Abschnitt 2.2\n\n"
"## Scope\n"
"**In:** `cross-spawn` als Dependency, `src/runtime/resolve-claude.ts` mit `resolveClaude()`, "
"`runner.ts`: `shell: false`, `windowsHide: true`, UTF-8-Encoding\n"
"**Out:** `sanitizedEnv()` (DIS-003), `--bare`-Flags (DIS-004)\n\n"
"## Definition of Done\n"
"- [ ] Kein `shell: true` im gesamten `src/`-Baum\n"
"- [ ] `cross-spawn` in `package.json`\n"
"- [ ] `resolveClaude()` wirft klare Fehlermeldung wenn nicht auffindbar\n"
"- [ ] Unit-Test `tests/unit/resolve-claude.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/harden-spawn`\n\n"
"## Abhaengigkeiten\nKeine. **Empfohlener Startpunkt.**"
),
},
{
"title": "DIS-003: sanitizedEnv() -- Secrets aus Child-Env entfernen",
"milestone": 1,
"labels": [1, 9, 15, 18],
"body": (
"## Ziel\n"
"Kein Secret (DISCORD_BOT_TOKEN etc.) im Environment des `claude`-Kindprozesses. "
"Heute exfiltrierbar via `echo $DISCORD_BOT_TOKEN`.\n\n"
"## Kontext\n"
"`runner.ts` vererbt aktuell `process.env` vollstaendig.\n"
"Referenz: `docs/development-plan.md` Abschnitt 2.6\n\n"
"## Scope\n"
"**In:** `src/runtime/env.ts`: `sanitizedEnv(extra?)` entfernt Token/Secret-Pattern, "
"setzt `CI=true`, `DISCLAW_AGENT=1`, optionale `env_blocklist` via `disclaw.yaml`, Runner nutzt `sanitizedEnv`\n"
"**Out:** Whitelist-Modus\n\n"
"## Definition of Done\n"
"- [ ] `sanitizedEnv()` ist pur, keine Seiteneffekte\n"
"- [ ] Unit-Test `tests/unit/sanitize-env.test.ts` deckt alle Pattern-Cases\n"
"- [ ] Runner nutzt `sanitizedEnv` an allen Spawn-Stellen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/sanitize-env`\n\n"
"## Abhaengigkeiten\nKeine. Kann parallel zu DIS-002 laufen."
),
},
{
"title": "DIS-004: Runner mit --bare + --append-system-prompt-file aufrufen",
"milestone": 1,
"labels": [1, 8, 15, 18],
"body": (
"## Ziel\n"
"Jeder CLI-Call verwendet `--bare` (skippt CLAUDE.md-Walk-Up, Hooks, Auto-Memory, MCP) "
"und injiziert Agenten-Identitaet explizit via `--append-system-prompt-file <workspace>/CLAUDE.md`.\n\n"
"## Kontext\n"
"Ohne `--bare` walkt Claude Code alle Parent-Dirs hoch.\n"
"Referenz: `docs/development-plan.md` Abschnitt 2.9, `docs/cli-feature-answers.md` Frage 8\n\n"
"## Scope\n"
"**In:** `runner.ts` Args immer mit `--bare` + `--append-system-prompt-file <ws>/CLAUDE.md`, "
"Integration-Test mit `tests/fixtures/fake-claude.mjs`\n"
"**Out:** `--resume`-Flag (Phase 2), Streaming-Flags (Phase 5)\n\n"
"## Definition of Done\n"
"- [ ] Args enthalten `[\"--bare\", \"--append-system-prompt-file\", \"<ws>/CLAUDE.md\"]`\n"
"- [ ] Integration-Test `tests/integration/no-parent-claude-md-leak.test.ts` gruen\n"
"- [ ] Dokumentations-Kommentar im Runner warum `--bare` zwingend ist\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/bare-and-append-identity`\n\n"
"## Abhaengigkeiten\nBlockiert durch: **#2 (DIS-002)**"
),
},
{
"title": "DIS-005: Path-Traversal-Check in /new-agent",
"milestone": 1,
"labels": [1, 9, 15, 18],
"body": (
"## Ziel\n"
"`/new-agent name:<x>` akzeptiert keinen Namen, der aus dem Workspace-Root ausbricht. "
"Defense in depth: Regex + `path.resolve`-Containment-Check.\n\n"
"## Kontext\n"
"Edge-Cases: `..`, Unicode-Homoglyphen, `.`, leere Strings, Windows-reserved Names.\n"
"Referenz: `docs/development-plan.md` Phase 0 Task 4\n\n"
"## Scope\n"
"**In:** Nach Regex in `new-agent.ts`: Containment-Check via `path.resolve`. Saubere User-Fehlermeldung in Discord.\n\n"
"## Definition of Done\n"
"- [ ] Containment-Check aktiv in `src/commands/new-agent.ts`\n"
"- [ ] Unit-Test `tests/unit/path-traversal.test.ts` deckt `../etc`, `..`, `./foo`, `a/../b`, leerer String, `CON`, Unicode\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/path-traversal-check`\n\n"
"## Abhaengigkeiten\nKeine."
),
},
{
"title": "DIS-006: .claude/settings.json-Template haerten + CLAUDE.md Injection-Klausel",
"milestone": 1,
"labels": [1, 8, 15, 18],
"body": (
"## Ziel\n"
"Jeder neue Agent bekommt ein gehaertetes `.claude/settings.json` (Deny-Liste gegen `../**`, `.env`, "
"gefaehrliche Bash-Muster) und eine `CLAUDE.md` mit Prompt-Injection-Resistenz-Klausel.\n\n"
"## Kontext\n"
"Schema bestaetigt via `docs/cli-feature-answers.md` Frage 3.\n"
"Referenz: `docs/development-plan.md` Abschnitt 2.5\n\n"
"## Scope\n"
"**In:** `src/agent/identity.ts` Settings-Template mit vollstaendiger `permissions.deny`-Liste, "
"`CLAUDE.md`-Template erhaelt Sicherheits-Abschnitt\n"
"**Out:** PreToolUse-Hook-Script (Phase 4), Profile-spezifische Settings (Phase 4)\n\n"
"## Definition of Done\n"
"- [ ] Frisch erstellter Workspace enthaelt `settings.json` mit vollstaendiger Deny-Liste\n"
"- [ ] `CLAUDE.md`-Template enthaelt Injection-Resistenz-Absatz\n"
"- [ ] Unit-Test `tests/unit/identity-templates.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/harden-settings-and-claudemd`\n\n"
"## Abhaengigkeiten\nKeine."
),
},
{
"title": "DIS-007: Lock-File gegen Doppelstart",
"milestone": 1,
"labels": [1, 8, 16],
"body": (
"## Ziel\n"
"Zwei gleichzeitig laufende DisClaw-Instanzen auf derselben DB/Workspace-Root werden verhindert.\n\n"
"## Scope\n"
"**In:** `src/runtime/lockfile.ts`: `acquireLock(path)` legt `.disclaw.lock` mit PID+Timestamp an. "
"Stale-Lock-Recovery via process signal check. Graceful Shutdown loescht Lock.\n\n"
"## Definition of Done\n"
"- [ ] Zweiter Startversuch bricht mit 'DisClaw bereits aktiv (PID X)' ab\n"
"- [ ] Stale-Lock wird automatisch uebernommen\n"
"- [ ] Unit-Test `tests/unit/lockfile.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-0/startup-lockfile`\n\n"
"## Abhaengigkeiten\nBlockiert durch: **#1 (DIS-001)**"
),
},
{
"title": "DIS-008: Forgejo Actions CI (build, test, lint)",
"milestone": 1,
"labels": [1, 14, 16],
"body": (
"## Ziel\n"
"Jeder PR gegen `main` triggert CI: `npm run build`, `npm test`, `npm run lint`. Matrix: Linux + Windows.\n\n"
"## Scope\n"
"**In:** `.forgejo/workflows/ci.yml` mit `pull_request`-Trigger, Matrix `ubuntu-latest`/`windows-latest`, "
"ESLint-Basis-Config, `npm run lint`-Script\n"
"**Out:** Deployment-Jobs, Coverage, Release-Automation\n\n"
"## Definition of Done\n"
"- [ ] CI laeuft gruen auf einem Test-PR\n"
"- [ ] Matrix deckt Linux + Windows ab\n"
"- [ ] `npm run lint` laeuft lokal\n"
"- [ ] README-Abschnitt 'CI' vorhanden\n\n"
"## Branch\n`phase-0/ci-pipeline`\n\n"
"## Abhaengigkeiten\nSollte nach DIS-002, DIS-003, DIS-005 kommen."
),
},
]
PHASE1_ISSUES = [
{
"title": "DIS-101: Pino-Logger einfuehren, console.* ersetzen",
"milestone": 2,
"labels": [2, 10, 16],
"body": (
"## Ziel\nStrukturiertes JSON-Logging ueber `pino`. Child-Logger pro Komponente. Kein `console.*` mehr im Produktionscode.\n\n"
"## Scope\n**In:** `src/runtime/logger.ts` mit `rootLogger` + `childLogger(component)`, "
"alle `console.*` in `src/` ersetzen, `pino-pretty` in `devDependencies`\n\n"
"## Definition of Done\n"
"- [ ] `grep -r 'console\\.' src/` liefert keine Treffer\n"
"- [ ] Pino-Child-Logger mit `component:'runner'` im Output\n"
"- [ ] Unit-Test `tests/unit/logger.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/pino-logger`\n\n## Abhaengigkeiten\nPhase 0 abgeschlossen."
),
},
{
"title": "DIS-102: sanitizeForDiscord -- Pfade aus Agent-Output entfernen",
"milestone": 2,
"labels": [2, 8, 16, 18],
"body": (
"## Ziel\nAbsolute Pfade, Home-Dir und Token-Patterns vor Discord-Send redaktieren. Verhindert Info-Leak.\n\n"
"## Scope\n**In:** `src/runtime/sanitize.ts` mit `sanitizeForDiscord(text, { repoRoot, home })`, "
"Router nutzt Sanitizer vor jedem `channel.send`\n\n"
"## Definition of Done\n"
"- [ ] Test `tests/unit/sanitize-for-discord.test.ts` gruen (Repo-Pfad, Home-Pfad, Token-Regex, keine False-Positives)\n"
"- [ ] Router nutzt Sanitizer an allen Send-Stellen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/sanitize-for-discord`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-103: ChannelQueue -- Per-Channel-FIFO",
"milestone": 2,
"labels": [2, 8, 15],
"body": (
"## Ziel\nMehrere Nachrichten im selben Channel seriell abarbeiten. Parallele Channels bleiben parallel. Keine Race Conditions.\n\n"
"## Scope\n**In:** `src/runtime/channel-queue.ts` mit `ChannelQueue`-Klasse, Integration in `router.ts`\n\n"
"## Definition of Done\n"
"- [ ] Unit-Test `tests/unit/channel-queue.test.ts`: FIFO, Error-Recovery, Map-Cleanup gruen\n"
"- [ ] Integration-Test `tests/integration/channel-queue-router.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/channel-queue`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-104: Globaler Semaphore (max_concurrent_agents)",
"milestone": 2,
"labels": [2, 8, 16],
"body": (
"## Ziel\nGlobaler Cap auf parallele `claude`-Prozesse. Default 4, konfigurierbar via `disclaw.yaml`.\n\n"
"## Scope\n**In:** `src/runtime/concurrency.ts` mit `Semaphore(max)`, Runner umschliesst Spawn-Call\n\n"
"## Definition of Done\n"
"- [ ] Unit-Test `tests/unit/semaphore.test.ts`: Acquire, Queueing, Release, Error-Path gruen\n"
"- [ ] Config-Default 4 in `disclaw.yaml`\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/concurrency-semaphore`\n\n## Abhaengigkeiten\nKeine harten. Kann parallel zu DIS-103."
),
},
{
"title": "DIS-105: splitForDiscord -- Codeblock-aware Message-Splitting",
"milestone": 2,
"labels": [2, 8, 15],
"body": (
"## Ziel\nAntworten > 1900 Zeichen korrekt aufteilen, ohne Codeblöcke zu zerreissen. Fences am Split schliessen und im naechsten Chunk wieder oeffnen.\n\n"
"## Scope\n**In:** `src/discord/split.ts` mit `splitForDiscord(text, limit = 1900): string[]`, Router sendet Chunks sequenziell\n\n"
"## Definition of Done\n"
"- [ ] Unit-Test `tests/unit/split-for-discord.test.ts`: kurz/am Limit/Codeblock/Fence/leer gruen\n"
"- [ ] Kein Chunk ueberschreitet 2000 Zeichen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/split-for-discord`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-106: Runner-API-Refactoring -- RunResult-Typ",
"milestone": 2,
"labels": [2, 10, 16],
"body": (
"## Ziel\n`runner.ts` gibt typsicheren `RunResult` zurueck (discriminated union: success/timeout/cli-error/parse-error).\n\n"
"## Scope\n**In:** `src/agent/types.ts` mit `RunResult`, `runner.ts` gibt `RunResult`, alle Aufrufer handhaben alle Cases\n\n"
"## Definition of Done\n"
"- [ ] `RunResult` ist discriminated union, kein `any`\n"
"- [ ] Unit-Test `tests/unit/runner-result.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/runner-api-runresult`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-107: Zod-Schemas fuer Config, agent.yaml, CLI-JSON-Output",
"milestone": 2,
"labels": [2, 8, 16],
"body": (
"## Ziel\nAlle externen Daten (Config, `agent.yaml`, CLI-Output) via Zod validieren. Fruehzeitige Fehlermeldungen.\n\n"
"## Scope\n**In:** `src/config/schema.ts`, `src/agent/schema.ts`, `zod` in `dependencies`\n\n"
"## Definition of Done\n"
"- [ ] Alle externen Eingaben durch Zod-Parse\n"
"- [ ] Unit-Test `tests/unit/schemas.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/zod-schemas`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-108: DB-Schema SSOT -- src/db/schema.sql",
"milestone": 2,
"labels": [2, 10, 16],
"body": (
"## Ziel\nSQLite-Schema in einer `schema.sql`-Datei (Single Source of Truth). `database.ts` liest und wendet sie an.\n\n"
"## Scope\n**In:** `src/db/schema.sql` mit allen CREATE TABLE-Statements, `database.ts` ohne Inline-DDL\n\n"
"## Definition of Done\n"
"- [ ] `schema.sql` enthaelt alle Tabellen und Indizes\n"
"- [ ] `database.ts` hat kein Inline-DDL mehr\n"
"- [ ] Integration-Test `tests/integration/db-init.test.ts` gruen\n"
"- [ ] `npm run build && npm test` gruen\n\n"
"## Branch\n`phase-1/db-schema-ssot`\n\n## Abhaengigkeiten\nBlockiert durch: **DIS-101**"
),
},
{
"title": "DIS-109: .env.example + Vitest-Setup + npm test-Skript",
"milestone": 2,
"labels": [2, 13, 16],
"body": (
"## Ziel\nNeues Repo-Clone laeuft sofort: `.env.example`, Vitest konfiguriert, `npm test` funktioniert ohne weitere Schritte.\n\n"
"## Scope\n**In:** `.env.example`, `vitest.config.ts`, `package.json` Scripts, `tests/`-Verzeichnis-Struktur\n\n"
"## Definition of Done\n"
"- [ ] `npm test` laeuft gruen auf frischem Clone (nach `npm ci`)\n"
"- [ ] `.env.example` deckt alle Pflichtfelder ab\n"
"- [ ] `vitest.config.ts` konsistent mit `tsconfig.json`\n\n"
"## Branch\n`phase-1/vitest-setup`\n\n## Abhaengigkeiten\nKeine. Frueher in Phase 1 besser."
),
},
{
"title": "DIS-110: docs/cli-feature-probe.md -- Manuelles CLI-Regression-Script",
"milestone": 2,
"labels": [2, 12, 17],
"body": (
"## Ziel\nStrukturiertes Probe-Script verifiziert Annahmen aus `docs/cli-feature-answers.md` gegen die installierte CLI. Muss vor Phase-2-Start ausgefuehrt werden.\n\n"
"## Scope\n**In:** `docs/cli-feature-probe.md` mit step-by-step Kommandos fuer alle 4 Rest-Unsicherheiten (--resume-Flow, session_id, stream-json-Events, usage-Felder)\n\n"
"## Definition of Done\n"
"- [ ] Probe-Dokument enthaelt ausfuehrbare Kommandos fuer alle Unsicherheiten\n"
"- [ ] Dokument von Developer ausgefuehrt und Ergebnisse eingetragen\n\n"
"## Branch\n`phase-1/cli-probe`\n\n## Abhaengigkeiten\nKeine. Muss vor Phase-2-Freigabe abgeschlossen sein."
),
},
]
def create_issues(issues, phase_name):
print(f"\n=== {phase_name} ===")
# Get existing to avoid dupes
r = requests.get(f"{BASE}/issues?type=issues&state=open&limit=50&page=1", headers=HEADERS)
existing = {i["title"] for i in r.json()} if r.ok else set()
r2 = requests.get(f"{BASE}/issues?type=issues&state=closed&limit=50&page=1", headers=HEADERS)
existing |= {i["title"] for i in r2.json()} if r2.ok else set()
for issue in issues:
if issue["title"] in existing:
print(f" SKIP (exists): {issue['title'][:60]}")
continue
r = requests.post(f"{BASE}/issues", headers=HEADERS, json=issue)
if r.ok:
num = r.json()["number"]
print(f" #{num}: {issue['title'][:70]}")
else:
print(f" ERR {r.status_code}: {issue['title'][:60]} -- {r.text[:80]}")
create_issues(PHASE0_ISSUES, "Phase 0")
create_issues(PHASE1_ISSUES, "Phase 1")
print("\nDone.")

View file

@ -1,94 +0,0 @@
# DisClaw — Kanban Board
> **Regeln**: Status nur in der Issue-Datei (`tasks/DIS-XXX.md`) ändern, dann hier die Karte verschieben.
> Developer-Agent nimmt eine "Ready"-Karte, setzt Status auf `in-progress`, erstellt Branch, öffnet PR.
> PM-Agent (oder User) reviewt PR und setzt Status auf `done`.
Letzte Aktualisierung: 2026-04-08
---
## Backlog
*Noch nicht startbar — Abhängigkeiten offen oder Phase noch nicht freigegeben.*
| ID | Titel | Phase | Branch | Blockiert durch |
|----|-------|-------|--------|-----------------|
| [DIS-004](DIS-004.md) | Runner: `--bare` + `--append-system-prompt-file` | 0 | `phase-0/bare-and-append-identity` | DIS-002 |
| [DIS-007](DIS-007.md) | Lock-File gegen Doppelstart | 0 | `phase-0/startup-lockfile` | DIS-001 |
| [DIS-008](DIS-008.md) | Forgejo Actions CI | 0 | `phase-0/ci-pipeline` | DIS-002, DIS-003, DIS-005 |
| [DIS-101](DIS-101.md) | Pino-Logger einführen | 1 | `phase-1/pino-logger` | Phase 0 done |
| [DIS-102](DIS-102.md) | `sanitizeForDiscord` — Pfade redaktieren | 1 | `phase-1/sanitize-for-discord` | DIS-101 |
| [DIS-103](DIS-103.md) | `ChannelQueue` — Per-Channel-FIFO | 1 | `phase-1/channel-queue` | DIS-101 |
| [DIS-104](DIS-104.md) | Globaler Semaphore (`max_concurrent_agents`) | 1 | `phase-1/concurrency-semaphore` | — |
| [DIS-105](DIS-105.md) | `splitForDiscord` — Codeblock-aware Splitting | 1 | `phase-1/split-for-discord` | DIS-101 |
| [DIS-106](DIS-106.md) | Runner-API-Refactoring → `RunResult`-Typ | 1 | `phase-1/runner-api-runresult` | DIS-101 |
| [DIS-107](DIS-107.md) | Zod-Schemas für Config, `agent.yaml`, CLI-Output | 1 | `phase-1/zod-schemas` | DIS-101 |
| [DIS-108](DIS-108.md) | DB-Schema SSOT → `src/db/schema.sql` | 1 | `phase-1/db-schema-ssot` | DIS-101 |
| [DIS-109](DIS-109.md) | `.env.example` + Vitest-Setup + `npm test` | 1 | `phase-1/vitest-setup` | — |
| [DIS-110](DIS-110.md) | `docs/cli-feature-probe.md` — CLI-Regression-Script | 1 | `phase-1/cli-probe` | — |
---
## Ready
*Startbereit — kein Blocker. Agent kann Branch anlegen und loslegen.*
*Empfohlene Startreihenfolge: DIS-002 → DIS-003 → DIS-001*
| ID | Titel | Phase | Branch | Priorität |
|----|-------|-------|--------|-----------|
| [DIS-002](DIS-002.md) | `shell: false` + `cross-spawn` Windows-Fix | 0 | `phase-0/harden-spawn` | p0 |
| [DIS-003](DIS-003.md) | `sanitizedEnv()` — Secrets aus Child-Env entfernen | 0 | `phase-0/sanitize-env` | p0 |
| [DIS-001](DIS-001.md) | Workspace-Root nach `~/.disclaw/workspaces/` | 0 | `phase-0/workspace-root-home` | p0 |
| [DIS-005](DIS-005.md) | Path-Traversal-Check in `/new-agent` | 0 | `phase-0/path-traversal-check` | p0 |
| [DIS-006](DIS-006.md) | `.claude/settings.json`-Template härten + Injection-Klausel | 0 | `phase-0/harden-settings-and-claudemd` | p0 |
---
## In Progress
*Branch existiert, Developer-Agent arbeitet aktiv daran.*
| ID | Titel | Branch | Agent | Gestartet |
|----|-------|--------|-------|-----------|
| — | | | | |
---
## In Review
*PR ist offen, wartet auf Review.*
| ID | Titel | PR | Reviewer |
|----|-------|----|----------|
| — | | | |
---
## Done
*Gemergt in `main`.*
| ID | Titel | Gemergt | PR |
|----|-------|---------|----|
| — | | | |
---
## Epics (Phase 26+)
*Werden am Phasen-Anfang in granulare Issues verfeinert.*
| Epic | Titel | Phase | Status |
|------|-------|-------|--------|
| [DIS-EP-201](EPICS.md#dis-ep-201) | DB-Migration + `SessionStore`-Methoden | 2 | locked |
| [DIS-EP-202](EPICS.md#dis-ep-202) | Runner-Integration `--resume` mit Fallback | 2 | locked |
| [DIS-EP-203](EPICS.md#dis-ep-203) | Usage-Metriken + `agent_runs`-Tabelle | 2 | locked |
| [DIS-EP-204](EPICS.md#dis-ep-204) | Integration-Tests Session-Resume + Fork-Case | 2 | locked |
| [DIS-EP-301](EPICS.md#dis-ep-301) | Typing-Refresh + `sendResponse`-Helper | 3 | locked |
| [DIS-EP-302](EPICS.md#dis-ep-302) | Attachment-Inbox für Discord-Bilder | 3 | locked |
| [DIS-EP-303](EPICS.md#dis-ep-303) | Input-Limit + User-Feedback-Reactions | 3 | locked |
| [DIS-EP-304](EPICS.md#dis-ep-304) | Tests send-response + Attachment-Inbox | 3 | locked |
| [DIS-EP-401](EPICS.md#dis-ep-401) | Profile-Registry-Grundgerüst | 4 | locked |
| [DIS-EP-402](EPICS.md#dis-ep-402) | Fünf Profile (developer/researcher/writer/ops/sandboxed) | 4 | locked |
| [DIS-EP-403](EPICS.md#dis-ep-403) | `/new-agent profile:<name>` Option | 4 | locked |
| [DIS-EP-404](EPICS.md#dis-ep-404) | PreToolUse-Hook `guard-tool.cjs` + Audit-Log | 4 | locked |
| [DIS-EP-405](EPICS.md#dis-ep-405) | Integration-Tests Hook-Guard + Profile-Permissions | 4 | locked |
| [DIS-EP-501](EPICS.md#dis-ep-501) | `runAgentStream()` mit `stream-json` | 5 | locked |
| [DIS-EP-502](EPICS.md#dis-ep-502) | Defensiver NDJSON-Event-Parser | 5 | locked |
| [DIS-EP-503](EPICS.md#dis-ep-503) | `stream-to-discord` — Edit-Debouncing | 5 | locked |
| [DIS-EP-504](EPICS.md#dis-ep-504) | Feature-Flag + Fallback + Integration-Tests | 5 | locked |

View file

@ -1,49 +0,0 @@
---
id: DIS-001
status: ready
phase: 0
priority: p0
labels: [phase:0, type:feat, priority:p0, security]
branch: phase-0/workspace-root-home
assignee: null
started: null
pr: null
merged: null
---
# DIS-001: Workspace-Root nach `~/.disclaw/workspaces/` verschieben
## Ziel
Workspaces werden standardmäßig unter `~/.disclaw/workspaces/<agent>/` (Linux/Mac) bzw.
`%USERPROFILE%\.disclaw\workspaces\<agent>\` (Windows) angelegt. Verhindert CLAUDE.md-Walk-Up-Leak.
## Kontext
Claude Code walkt beim Start Parent-Directories hoch und konkateniert alle gefundenen `CLAUDE.md`.
Liegt ein Workspace unter `disclaw/workspaces/`, erbt jeder Agent die DisClaw-Projekt-CLAUDE.md.
`docs/development-plan.md` §2.9, `docs/cli-feature-answers.md` Frage 8.
## Scope
- **In**: `disclaw.yaml` Default ändern · Tilde-Expansion in `src/config/loader.ts` · `new-agent.ts` legt Workspace unter resolvierten Root an · Warning-Log wenn Root innerhalb Repo · README-Migrationshinweis
- **Out**: Automatische Migration bestehender Workspaces · `--bare`-Flags (DIS-004)
## Definition of Done
- [ ] `disclaw.yaml` Default ist `workspaces_root: "~/.disclaw/workspaces"`
- [ ] `src/config/loader.ts` expandiert `~` auf Linux/Mac/Windows korrekt
- [ ] `new-agent` legt Workspace unter dem neuen Root an, DB speichert absoluten Pfad
- [ ] Warning-Log feuert wenn Root innerhalb des Repos liegt
- [ ] Unit-Test `tests/unit/workspace-root-resolve.test.ts` grün
- [ ] README-Abschnitt „Migration bestehender Workspaces" vorhanden
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `disclaw.yaml`
- `src/config/loader.ts`
- `src/commands/new-agent.ts`
- `tests/unit/workspace-root-resolve.test.ts`
- `README.md`
## Abhängigkeiten
Keine.
## Spec
`docs/backlog.md` #DIS-001

View file

@ -1,47 +0,0 @@
---
id: DIS-002
status: ready
phase: 0
priority: p0
labels: [phase:0, type:fix, priority:p0, security]
branch: phase-0/harden-spawn
assignee: null
started: null
pr: null
merged: null
---
# DIS-002: `shell: false` + `cross-spawn` Windows-Fix im Runner
## Ziel
Command-Injection über Discord-User-Input eliminieren. `shell: true` aus `src/agent/runner.ts`
entfernen, durch `cross-spawn` ersetzen. Windows-`.cmd`-Wrapper wird korrekt aufgelöst,
CVE-2024-27980 umgangen.
## Kontext
`src/agent/runner.ts` nutzt aktuell `shell: true` → jeder Discord-Input landet in der Shell-Interpretation.
`spawn("claude", args, { shell: false })` schlägt auf Windows fehl (nur `claude.cmd` existiert).
`docs/development-plan.md` §2.2, `docs/cli-feature-answers.md` Frage 9.
## Scope
- **In**: `cross-spawn` + `@types/cross-spawn` als Dependencies · `src/runtime/resolve-claude.ts`: `resolveClaude()` liest `CLAUDE_PATH`, fällt auf `where`/`which` zurück, cached in-memory · `runner.ts`: `shell: false`, `windowsHide: true`, `stdio: pipe`, `setEncoding("utf8")`
- **Out**: `sanitizedEnv()` (DIS-003) · `--bare`-Flags (DIS-004)
## Definition of Done
- [ ] Kein `shell: true` mehr im gesamten `src/`-Baum (`grep -r "shell: true" src/` → leer)
- [ ] `cross-spawn` in `package.json#dependencies`
- [ ] `resolveClaude()` wirft klare Fehlermeldung wenn `claude` nicht auffindbar
- [ ] Unit-Test `tests/unit/resolve-claude.test.ts` deckt: `.env`-Override, PATH-Fallback, Error bei Nichtfund
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `package.json`
- `src/runtime/resolve-claude.ts`
- `src/agent/runner.ts`
- `tests/unit/resolve-claude.test.ts`
## Abhängigkeiten
Keine. **Empfohlener Startpunkt — zuerst lösen.**
## Spec
`docs/backlog.md` #DIS-002

View file

@ -1,44 +0,0 @@
---
id: DIS-003
status: ready
phase: 0
priority: p0
labels: [phase:0, type:fix, priority:p0, security]
branch: phase-0/sanitize-env
assignee: null
started: null
pr: null
merged: null
---
# DIS-003: `sanitizedEnv()` — Secrets aus Child-Env entfernen
## Ziel
Kein Secret (`DISCORD_BOT_TOKEN` etc.) darf im Environment des `claude`-Kindprozesses landen.
Ein Agent kann heute per `echo $DISCORD_BOT_TOKEN` den Bot-Token exfiltrieren.
## Kontext
`src/agent/runner.ts` vererbt aktuell `process.env` vollständig.
`docs/development-plan.md` §2.6, `docs/ai-engineer-analyse.md` §4.
## Scope
- **In**: `src/runtime/env.ts`: `sanitizedEnv(extra?)` entfernt `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_SECRET`, Pattern `/^(SECRET_|TOKEN_|API_KEY_?)/i`, setzt `CI=true`, `DISCLAW_AGENT=1`, `DISCLAW_AGENT_NAME=<name>` · optionale `env_blocklist` via `disclaw.yaml` · `runner.ts` nutzt `sanitizedEnv` statt `process.env`
- **Out**: Whitelist-statt-Blacklist-Modus
## Definition of Done
- [ ] `sanitizedEnv()` ist pur, keine Seiteneffekte
- [ ] Unit-Test `tests/unit/sanitize-env.test.ts` deckt: Token entfernt, Pattern-Match, PATH/HOME bleiben, Extras überschreiben, `env_blocklist` wird angewendet
- [ ] Runner nutzt `sanitizedEnv` an allen Spawn-Stellen
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/env.ts`
- `src/agent/runner.ts`
- `src/config/loader.ts`
- `tests/unit/sanitize-env.test.ts`
## Abhängigkeiten
Keine. Kann parallel zu DIS-002 laufen (beide editieren `runner.ts` — bei Merge-Konflikt rebased DIS-003 auf DIS-002).
## Spec
`docs/backlog.md` #DIS-003

View file

@ -1,44 +0,0 @@
---
id: DIS-004
status: backlog
phase: 0
priority: p0
labels: [phase:0, type:feat, priority:p0, security]
branch: phase-0/bare-and-append-identity
assignee: null
started: null
pr: null
merged: null
---
# DIS-004: Runner mit `--bare` + `--append-system-prompt-file` aufrufen
## Ziel
Jeder CLI-Call verwendet `--bare` (skippt CLAUDE.md-Walk-Up, Hooks, Auto-Memory, MCP) und
injiziert die Agenten-Identität explizit über `--append-system-prompt-file <workspace>/CLAUDE.md`.
Parent-CLAUDE.md-Leaks werden strukturell unmöglich.
## Kontext
Claude Code walkt ohne `--bare` alle Parent-Dirs hoch und konkateniert CLAUDE.md-Dateien.
`docs/development-plan.md` §2.9, `docs/cli-feature-answers.md` Frage 8.
## Scope
- **In**: `runner.ts` Args-Builder immer `--bare` + `--append-system-prompt-file <ws>/CLAUDE.md` · Integration-Test mit `tests/fixtures/fake-claude.mjs` der System-Prompt als JSON echoed
- **Out**: `--resume`-Flag (Phase 2) · Streaming-Flags (Phase 5)
## Definition of Done
- [ ] Args enthalten `["--bare", "--append-system-prompt-file", "<ws>/CLAUDE.md"]`
- [ ] Integration-Test `tests/integration/no-parent-claude-md-leak.test.ts` verifiziert: nur Workspace-CLAUDE.md im System-Prompt, keine Parent-Leaks
- [ ] Dokumentations-Kommentar im Runner warum `--bare` zwingend ist
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/agent/runner.ts`
- `tests/fixtures/fake-claude.mjs`
- `tests/integration/no-parent-claude-md-leak.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-002** (sauberes Spawn-Setup muss vorher stehen).
## Spec
`docs/backlog.md` #DIS-004

View file

@ -1,48 +0,0 @@
---
id: DIS-005
status: ready
phase: 0
priority: p0
labels: [phase:0, type:fix, priority:p0, security]
branch: phase-0/path-traversal-check
assignee: null
started: null
pr: null
merged: null
---
# DIS-005: Path-Traversal-Check in `/new-agent`
## Ziel
`/new-agent name:<x>` akzeptiert keinen Namen, der aus dem Workspace-Root ausbricht.
Defense in depth: Regex-Validierung + expliziter `path.resolve`-Containment-Check.
## Kontext
Aktuell gibt es Regex-Validierung in `src/commands/new-agent.ts`, aber keinen
`path.resolve`-Containment-Check. Edge-Cases: `..`, Unicode-Homoglyphen, `.`, leere Strings, Windows-reserved Names.
`docs/development-plan.md` Phase 0 Task 4.
## Scope
- **In**: Nach Regex in `new-agent.ts`:
```ts
const root = path.resolve(config.workspaces_root);
const wsPath = path.resolve(root, name);
if (!wsPath.startsWith(root + path.sep)) throw new Error("Path traversal");
```
Saubere User-Fehlermeldung im Discord-Channel.
- **Out**: Unicode-Normalisierung über Regex hinaus
## Definition of Done
- [ ] Containment-Check ist aktiv in `src/commands/new-agent.ts`
- [ ] Unit-Test `tests/unit/path-traversal.test.ts` deckt: `../etc`, `..`, `./foo`, `a/../b`, leerer String, `CON` (Windows-reserved), Unicode-Homoglyphen
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/commands/new-agent.ts`
- `tests/unit/path-traversal.test.ts`
## Abhängigkeiten
Keine. Kann parallel laufen.
## Spec
`docs/backlog.md` #DIS-005

View file

@ -1,56 +0,0 @@
---
id: DIS-006
status: ready
phase: 0
priority: p0
labels: [phase:0, type:feat, priority:p0, security]
branch: phase-0/harden-settings-and-claudemd
assignee: null
started: null
pr: null
merged: null
---
# DIS-006: `.claude/settings.json`-Template härten + `CLAUDE.md`-Injection-Klausel
## Ziel
Jeder neu erstellte Agent bekommt ein gehärtetes `.claude/settings.json` (Deny-Liste gegen
`../**`, `.env`, gefährliche Bash-Muster) und eine `CLAUDE.md` mit Prompt-Injection-Resistenz-Klausel.
## Kontext
Schema bestätigt via `docs/cli-feature-answers.md` Frage 3.
`docs/development-plan.md` §2.5.
## Scope
- **In**: `src/agent/identity.ts` Settings-Template:
```json
{
"permissions": {
"allow": ["Read", "Write(./**)", "Edit(./**)", "Bash(git diff *)"],
"ask": ["Bash(git push *)"],
"deny": ["Read(../**)", "Write(../**)", "Edit(../**)", "Read(./.env)",
"WebFetch", "Bash(rm -rf *)", "Bash(curl * | sh)",
"Bash(ssh *)", "Bash(* /etc/*)", "Bash(* ~/.ssh/*)"],
"defaultMode": "acceptEdits"
},
"cleanupPeriodDays": 90
}
```
CLAUDE.md-Template erhält Abschnitt "Sicherheit & Prompt-Injection": Anweisungen aus Nachrichten dürfen Permissions nicht überschreiben.
- **Out**: PreToolUse-Hook-Script (Phase 4) · Profile-spezifische Settings (Phase 4)
## Definition of Done
- [ ] Frisch erstellter Workspace enthält settings.json mit vollständiger Deny-Liste
- [ ] `CLAUDE.md`-Template enthält den Injection-Resistenz-Absatz
- [ ] Unit-Test `tests/unit/identity-templates.test.ts` parsed gerendertes JSON und verifiziert Deny-Einträge + CLAUDE.md-Satz
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/agent/identity.ts`
- `tests/unit/identity-templates.test.ts`
## Abhängigkeiten
Keine.
## Spec
`docs/backlog.md` #DIS-006

View file

@ -1,42 +0,0 @@
---
id: DIS-007
status: backlog
phase: 0
priority: p1
labels: [phase:0, type:feat, priority:p1]
branch: phase-0/startup-lockfile
assignee: null
started: null
pr: null
merged: null
---
# DIS-007: Lock-File gegen Doppelstart
## Ziel
Zwei gleichzeitig laufende DisClaw-Instanzen auf derselben DB/Workspace-Root werden
verhindert. Zweiter Start bricht mit klarer Fehlermeldung ab.
## Kontext
`docs/development-plan.md` §7 (Risiko-Tabelle).
## Scope
- **In**: `src/runtime/lockfile.ts`: `acquireLock(path)` legt `<workspaces_root>/.disclaw.lock` mit PID + ISO-Timestamp an. Stale-Lock-Recovery via `process.kill(pid, 0)`. · `src/index.ts` ruft `acquireLock` vor DB-Init auf · Graceful Shutdown löscht Lock
- **Out**: Distributed Locking für Multi-Host
## Definition of Done
- [ ] Zweiter Startversuch bricht mit „DisClaw bereits aktiv (PID X)" ab
- [ ] Stale-Lock (PID tot) wird automatisch übernommen
- [ ] Unit-Test `tests/unit/lockfile.test.ts` mit tmp-Dir: Acquire, Doppel-Acquire, Stale-Recovery, Release
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/lockfile.ts`
- `src/index.ts`
- `tests/unit/lockfile.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-001** (Lock liegt unter finalem `workspaces_root`).
## Spec
`docs/backlog.md` #DIS-007

View file

@ -1,45 +0,0 @@
---
id: DIS-008
status: backlog
phase: 0
priority: p1
labels: [phase:0, type:ci, priority:p1]
branch: phase-0/ci-pipeline
assignee: null
started: null
pr: null
merged: null
---
# DIS-008: Forgejo Actions CI (build, test, lint)
## Ziel
Jeder PR gegen `main` triggert eine CI-Pipeline: `npm run build`, `npm test`, `npm run lint`.
Matrix: Linux + Windows. Ohne grüne Checks kein Merge.
## Kontext
Forgejo Actions ist API-kompatibel mit GitHub Actions.
`docs/development-plan.md` Phase 0 Task 8.
## Scope
- **In**: `.forgejo/workflows/ci.yml` mit `pull_request`-Trigger, Matrix `ubuntu-latest`/`windows-latest`, Steps: checkout → setup-node 20 → `npm ci` → build → test · ESLint-Basis-Config + `no-shell-true`-Guard · `npm run lint`-Script in `package.json`
- **Out**: Deployment-Jobs, Coverage-Upload, Release-Automation
## Definition of Done
- [ ] CI-Workflow liegt unter `.forgejo/workflows/ci.yml`
- [ ] Pipeline läuft grün auf einem Test-PR (manuell verifiziert)
- [ ] Matrix deckt Linux + Windows ab
- [ ] `npm run lint` läuft lokal
- [ ] README-Abschnitt „CI" vorhanden
## Dateien (erwartet)
- `.forgejo/workflows/ci.yml`
- `.eslintrc.cjs`
- `package.json`
- `README.md`
## Abhängigkeiten
Sollte nach DIS-002, DIS-003, DIS-005 kommen (Lint-Rules greifen auf sauberer Basis).
## Spec
`docs/backlog.md` #DIS-008

View file

@ -1,42 +0,0 @@
---
id: DIS-101
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:refactor, priority:p1]
branch: phase-1/pino-logger
assignee: null
started: null
pr: null
merged: null
---
# DIS-101: Pino-Logger einführen, `console.*` ersetzen
## Ziel
Strukturiertes JSON-Logging über `pino`. Child-Logger pro Komponente. Kein `console.*` mehr im Produktionscode.
## Kontext
`docs/development-plan.md` §2.7, Phase 1 Task 1.
## Scope
- **In**: `src/runtime/logger.ts` mit `rootLogger` + `childLogger(component)` · alle `console.*` in `src/index.ts`, `bot.ts`, `router.ts`, `runner.ts`, `new-agent.ts` ersetzen · `pino-pretty` in `devDependencies` als Pretty-Transport
- **Out**: Log-Sampling, Log-Level-Hot-Reload, externe Log-Ziele
## Definition of Done
- [ ] `grep -r "console\." src/` liefert keine Treffer
- [ ] Pino-Child-Logger mit `component:"runner"` etc. im Output sichtbar
- [ ] Unit-Test `tests/unit/logger.test.ts` verifiziert `component`-Key
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/logger.ts`
- `src/index.ts`, `src/bot.ts`, `src/router.ts`, `src/agent/runner.ts`, `src/commands/new-agent.ts`
- `tests/unit/logger.test.ts`
- `package.json`
## Abhängigkeiten
Phase 0 abgeschlossen (alle DIS-00x gemergt).
## Spec
`docs/backlog.md` #DIS-101

View file

@ -1,41 +0,0 @@
---
id: DIS-102
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:feat, priority:p1, security]
branch: phase-1/sanitize-for-discord
assignee: null
started: null
pr: null
merged: null
---
# DIS-102: `sanitizeForDiscord` — Pfade aus Agent-Output entfernen
## Ziel
Absolute Pfade, Home-Dir und Token-Patterns werden vor Discord-Send aus Agent-Antworten redaktiert.
Verhindert Info-Leak in öffentliche Channels.
## Kontext
`docs/development-plan.md` §2.7 (Error-Handler-Strategie), Phase 1 Task 2.
## Scope
- **In**: `src/runtime/sanitize.ts` mit `sanitizeForDiscord(text, { repoRoot, home })` · Regex ersetzt repoRoot → `<disclaw>`, home → `~`, Token-Patterns → `<redacted>` · Router nutzt Sanitizer vor jedem `channel.send`
- **Out**: Content-Moderation, PII-Detection
## Definition of Done
- [ ] Test `tests/unit/sanitize-for-discord.test.ts` deckt: Repo-Pfad, Home-Pfad, Discord-Token-Regex, keine False-Positives auf harmlosem Text
- [ ] Router nutzt Sanitizer an allen Send-Stellen
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/sanitize.ts`
- `src/router.ts`
- `tests/unit/sanitize-for-discord.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101** (gemeinsames Refactoring-Fenster im Router).
## Spec
`docs/backlog.md` #DIS-102

View file

@ -1,42 +0,0 @@
---
id: DIS-103
status: backlog
phase: 1
priority: p0
labels: [phase:1, type:feat, priority:p0]
branch: phase-1/channel-queue
assignee: null
started: null
pr: null
merged: null
---
# DIS-103: `ChannelQueue` — Per-Channel-FIFO
## Ziel
Mehrere Nachrichten im selben Channel werden strikt seriell abgearbeitet. Parallele Channels
bleiben parallel. Keine Race Conditions auf Workspace-Dateien.
## Kontext
`docs/development-plan.md` §2.3, Phase 1 Task 3.
## Scope
- **In**: `src/runtime/channel-queue.ts` mit `ChannelQueue`-Klasse · Integration in `router.ts`: `messageCreate` läuft durch `channelQueue.enqueue(channelId, () => runAgent(...))`
- **Out**: Persistente Queue über Prozessneustart
## Definition of Done
- [ ] Unit-Test `tests/unit/channel-queue.test.ts`: FIFO-Ordnung, Error-Recovery (fehlgeschlagene Task blockiert Queue nicht), Map-Cleanup nach leerer Queue
- [ ] Integration-Test `tests/integration/channel-queue-router.test.ts`: drei schnell enqueued Messages werden in Reihenfolge abgearbeitet
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/channel-queue.ts`
- `src/router.ts`
- `tests/unit/channel-queue.test.ts`
- `tests/integration/channel-queue-router.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101**.
## Spec
`docs/backlog.md` #DIS-103

View file

@ -1,42 +0,0 @@
---
id: DIS-104
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:feat, priority:p1]
branch: phase-1/concurrency-semaphore
assignee: null
started: null
pr: null
merged: null
---
# DIS-104: Globaler Semaphore (`max_concurrent_agents`)
## Ziel
Globaler Cap auf parallele `claude`-Prozesse verhindert Ressourcen-Thrashing. Default 4, konfigurierbar via `disclaw.yaml`.
## Kontext
`docs/development-plan.md` §2.3, Phase 1 Task 4.
## Scope
- **In**: `src/runtime/concurrency.ts` mit `Semaphore(max)` (`acquire()/release()`) · `runner.ts` umschließt Spawn-Call: `semaphore.acquire()` / `try { ... } finally { semaphore.release() }` · Config-Default 4 in `disclaw.yaml` kommentiert
- **Out**: Dynamisches Auto-Scaling
## Definition of Done
- [ ] Unit-Test `tests/unit/semaphore.test.ts`: Acquire bei freien Slots, Queueing bei vollen Slots, Release gibt Wartende frei, Error-Path hält Counter konsistent
- [ ] Config-Default 4 in `disclaw.yaml`
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/runtime/concurrency.ts`
- `src/agent/runner.ts`
- `src/config/loader.ts`
- `disclaw.yaml`
- `tests/unit/semaphore.test.ts`
## Abhängigkeiten
Keine harten. Kann parallel zu DIS-103 laufen.
## Spec
`docs/backlog.md` #DIS-104

View file

@ -1,41 +0,0 @@
---
id: DIS-105
status: backlog
phase: 1
priority: p0
labels: [phase:1, type:feat, priority:p0]
branch: phase-1/split-for-discord
assignee: null
started: null
pr: null
merged: null
---
# DIS-105: `splitForDiscord` — Codeblock-aware Message-Splitting
## Ziel
Antworten > 1900 Zeichen werden korrekt aufgeteilt, ohne Codeblöcke zu zerreißen.
Fences werden am Split geschlossen und im nächsten Chunk wieder geöffnet.
## Kontext
`docs/development-plan.md` §2.4, Phase 1 Task 5.
## Scope
- **In**: `src/discord/split.ts` mit `splitForDiscord(text, limit = 1900): string[]` · Fence-Erkennung (` ``` `), Sprach-Tag wird im neuen Chunk beibehalten · Router nutzt Funktion und sendet Chunks sequenziell
- **Out**: Streaming-Edit (Phase 5)
## Definition of Done
- [ ] Unit-Test `tests/unit/split-for-discord.test.ts` deckt: kurze Nachricht (kein Split), genau am Limit, Codeblock mitten im Split, verschachtelter Fence, leerer Input
- [ ] Kein Chunk überschreitet 2000 Zeichen
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/discord/split.ts`
- `src/router.ts`
- `tests/unit/split-for-discord.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101**.
## Spec
`docs/backlog.md` #DIS-105

View file

@ -1,43 +0,0 @@
---
id: DIS-106
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:refactor, priority:p1]
branch: phase-1/runner-api-runresult
assignee: null
started: null
pr: null
merged: null
---
# DIS-106: Runner-API-Refactoring → `RunResult`-Typ
## Ziel
`runner.ts` gibt einen typsicheren `RunResult`-Typ zurück statt rohem JSON/String.
Ermöglicht saubere Fehlerunterscheidung (Timeout, CLI-Error, Parse-Error, Success).
## Kontext
`docs/development-plan.md` §2 (Runner-Refactoring), Phase 1 Task 6.
## Scope
- **In**: `src/agent/types.ts` mit `RunResult` (discriminated union: `success | timeout | cli-error | parse-error`) · `runner.ts` gibt `RunResult` zurück · `router.ts` und alle Aufrufer nutzen den Typ
- **Out**: Streaming-Variante (Phase 5)
## Definition of Done
- [ ] `RunResult` ist ein discriminated union, kein `any`
- [ ] Alle Aufrufer handhaben alle vier Cases explizit (TypeScript enforcement)
- [ ] Unit-Test `tests/unit/runner-result.test.ts` mit Mock-Spawn für jeden Case
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/agent/types.ts`
- `src/agent/runner.ts`
- `src/router.ts`
- `tests/unit/runner-result.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101** (Logger-Refactoring muss vorher stehen).
## Spec
`docs/backlog.md` #DIS-106

View file

@ -1,45 +0,0 @@
---
id: DIS-107
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:feat, priority:p1]
branch: phase-1/zod-schemas
assignee: null
started: null
pr: null
merged: null
---
# DIS-107: Zod-Schemas für Config, `agent.yaml`, CLI-JSON-Output
## Ziel
Alle externen Daten (Config-Datei, `agent.yaml`, Claude-CLI-JSON-Output) werden via Zod validiert.
Frühzeitige, verständliche Fehlermeldungen bei fehlerhafter Konfiguration.
## Kontext
`docs/development-plan.md` §3 (Zod als Dependency), Phase 1 Task 7.
## Scope
- **In**: `src/config/schema.ts` (DisClaw-Config-Schema) · `src/agent/schema.ts` (`agent.yaml` + CLI-Output-Schema) · `src/config/loader.ts` + `identity.ts` nutzen Schemas · `zod` in `dependencies`
- **Out**: Zod-Transforms für DB-Queries (separate Issue)
## Definition of Done
- [ ] Alle externen Dateneingaben gehen durch Zod-Parse
- [ ] Ungültige `disclaw.yaml` → verständliche Fehlermeldung mit Feldname und erwartetem Typ
- [ ] Unit-Test `tests/unit/schemas.test.ts` für jeden Schema-Typ (valid + invalid cases)
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/config/schema.ts`
- `src/agent/schema.ts`
- `src/config/loader.ts`
- `src/agent/identity.ts`
- `package.json`
- `tests/unit/schemas.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101**.
## Spec
`docs/backlog.md` #DIS-107

View file

@ -1,42 +0,0 @@
---
id: DIS-108
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:refactor, priority:p1]
branch: phase-1/db-schema-ssot
assignee: null
started: null
pr: null
merged: null
---
# DIS-108: DB-Schema SSOT → `src/db/schema.sql`
## Ziel
Das SQLite-Schema ist in einer einzigen `schema.sql`-Datei definiert (Single Source of Truth).
`database.ts` liest und führt die Datei aus statt SQL inline zu haben.
## Kontext
`docs/development-plan.md` §2 (DB-Architektur), Phase 1 Task 8.
## Scope
- **In**: `src/db/schema.sql` mit allen `CREATE TABLE`-Statements (inkl. `conversations`-Index) · `database.ts` liest Schema via `fs.readFileSync` und wendet es über `db.run()` auf die SQLite-Instanz an · Bestehende Inline-SQL entfernen
- **Out**: DB-Migrations-System (kommt mit Phase 2 `session_id`-Spalte als erstes Delta)
## Definition of Done
- [ ] `schema.sql` enthält alle Tabellen und Indizes
- [ ] `database.ts` hat kein Inline-DDL mehr
- [ ] Integration-Test `tests/integration/db-init.test.ts` mit tmp-SQLite verifiziert: Tabellen existieren, Indizes greifen, zweites Ausführen ist idempotent
- [ ] `npm run build && npm test` grün
## Dateien (erwartet)
- `src/db/schema.sql`
- `src/db/database.ts`
- `tests/integration/db-init.test.ts`
## Abhängigkeiten
Blockiert durch: **DIS-101**.
## Spec
`docs/backlog.md` #DIS-108

View file

@ -1,45 +0,0 @@
---
id: DIS-109
status: backlog
phase: 1
priority: p1
labels: [phase:1, type:chore, priority:p1]
branch: phase-1/vitest-setup
assignee: null
started: null
pr: null
merged: null
---
# DIS-109: `.env.example` + Vitest-Setup + `npm test`-Skript
## Ziel
Neues Repo-Clone kommt sofort zum Laufen: `.env.example` zeigt alle Pflichtfelder,
Vitest ist konfiguriert, `npm test` läuft ohne weitere Schritte.
## Kontext
`docs/development-plan.md` §2.8 (Test-Strategie), Phase 1 Task 9.
## Scope
- **In**: `.env.example` mit allen Feldern aus `src/config/loader.ts` (keine echten Werte) · `vitest.config.ts` mit `testEnvironment: "node"`, Coverage-Provider `v8`, Alias-Pfade · `package.json` `test`/`test:watch`/`test:coverage`-Scripts · `tests/` Verzeichnis-Struktur (`unit/`, `integration/`, `fixtures/`)
- **Out**: Browser-Tests, E2E-Tests
## Definition of Done
- [ ] `npm test` läuft grün auf frischem Clone (nach `npm ci`)
- [ ] `.env.example` deckt alle Pflichtfelder ab
- [ ] `vitest.config.ts` vorhanden und konsistent mit `tsconfig.json`
- [ ] Test-Verzeichnis-Struktur steht
## Dateien (erwartet)
- `.env.example`
- `vitest.config.ts`
- `package.json`
- `tests/unit/.gitkeep`
- `tests/integration/.gitkeep`
- `tests/fixtures/.gitkeep`
## Abhängigkeiten
Keine harten. Sollte früh in Phase 1 kommen, damit andere Tests darauf aufbauen können.
## Spec
`docs/backlog.md` #DIS-109

View file

@ -1,41 +0,0 @@
---
id: DIS-110
status: backlog
phase: 1
priority: p2
labels: [phase:1, type:docs, priority:p2]
branch: phase-1/cli-probe
assignee: null
started: null
pr: null
merged: null
---
# DIS-110: `docs/cli-feature-probe.md` — Manuelles CLI-Regression-Script
## Ziel
Ein strukturiertes, manuell ausführbares Probe-Script dokumentiert und verifiziert die
in `docs/cli-feature-answers.md` formulierten Annahmen gegen die tatsächlich installierte CLI.
Besonders kritisch: `--resume`-Verhalten und `session_id`-Pfad im JSON-Output.
## Kontext
`docs/cli-feature-answers.md` (4 Rest-Unsicherheiten) · `docs/development-plan.md` §6.
Muss vor Phase 2-Start ausgeführt und Ergebnisse dokumentiert werden.
## Scope
- **In**: `docs/cli-feature-probe.md` mit step-by-step Kommandos, erwarteter vs. tatsächlicher Output-Template, Checkboxen für jede Annahme · Deckt: `--resume`-Flow, `session_id` Top-Level-Feld, `stream-json`-Event-Typen, `usage`-Felder, Linux-Session-Slug
- **Out**: Automatisiertes Test-Script (overkill für diese Aufgabe)
## Definition of Done
- [ ] `docs/cli-feature-probe.md` enthält ausführbare Kommandos für alle 4 Rest-Unsicherheiten
- [ ] Jede Annahme hat ein "Expected" und "Actual"-Feld zum Ausfüllen
- [ ] Dokument ist von einem Developer ausgeführt und Ergebnisse eingetragen (manuell verifiziert vor Phase 2 Start)
## Dateien (erwartet)
- `docs/cli-feature-probe.md`
## Abhängigkeiten
Keine. Kann jederzeit geschrieben werden, muss vor Phase-2-Freigabe ausgeführt sein.
## Spec
`docs/backlog.md` #DIS-110

View file

@ -1,131 +0,0 @@
# DisClaw — Epics (Phase 26+)
> Diese Epics werden am **Anfang jeder Phase** in granulare Issues verfeinert (analog zu Phase 0/1 im Backlog).
> Bis dahin ist der Status `locked` — kein Branch, keine Entwicklung.
> Freigabe durch explizite Entscheidung des Users nach Phasen-Abschluss.
---
## Phase 2 — Context-Effizienz (`--resume`)
Voraussetzung: Phase 0 + Phase 1 vollständig gemergt. `docs/cli-feature-probe.md` ausgeführt und `--resume`-Verhalten bestätigt.
### DIS-EP-201
**DB-Migration + `SessionStore`-Methoden**
Neue Spalte `session_id` in `workspaces`-Tabelle. `SessionStore`-Klasse mit `get/set/clear`-Methoden.
Branch-Pattern wenn verfeinert: `phase-2/session-store`
### DIS-EP-202
**Runner-Integration `--resume` mit DB-Fallback**
`runner.ts` hängt `--resume <id>` an wenn `session_id` vorhanden. Fallback: strukturierte DB-Rehydrierung mit role-getaggten Turns. ID wird nach jedem Run aus JSON top-level gelesen und in DB aktualisiert.
Branch-Pattern: `phase-2/runner-resume`
### DIS-EP-203
**Usage-Metriken Logging + `agent_runs`-Tabelle**
Cache-Hit-Statistiken aus CLI-Output extrahieren (falls verfügbar). `agent_runs`-Tabelle für Token-Tracking.
Branch-Pattern: `phase-2/usage-metrics`
### DIS-EP-204
**Integration-Tests Session-Resume + Fork-Case**
Tests die `--resume`-Flow end-to-end verifizieren: erfolgreicher Resume, Session nicht gefunden (Fallback), Fork-Case (neue ID nach Resume).
Branch-Pattern: `phase-2/session-tests`
---
## Phase 3 — UX-Feinschliff
Voraussetzung: Phase 2 abgeschlossen.
### DIS-EP-301
**Typing-Refresh 8s + `sendResponse`-Helper + Attachment-Fallback**
Bot zeigt „tippt…" an solange Claude arbeitet, refresht alle 8s. `sendResponse(channel, text)`-Helper kapselt Split + Sanitize + Send.
Branch-Pattern: `phase-3/typing-send-helper`
### DIS-EP-302
**Attachment-Inbox für eingehende Discord-Bilder**
Bilder aus Discord-Attachments werden in einen tmp-Ordner des Workspace-Agents gespeichert und via `@path/to/file.png`-Syntax in den Prompt injiziert.
Branch-Pattern: `phase-3/attachment-inbox`
### DIS-EP-303
**Input-Limit + User-Feedback-Reactions**
Nachrichten > konfigurierbares Limit werden abgewiesen. Reaction-Feedback: 👀 (verarbeitung), ✅ (erfolg), ❌ (fehler), ⚠️ (timeout).
Branch-Pattern: `phase-3/input-limit-reactions`
### DIS-EP-304
**Unit-Tests `sendResponse` + Attachment-Inbox**
Vollständige Unit-Test-Abdeckung für Phase-3-Komponenten.
Branch-Pattern: `phase-3/ux-tests`
---
## Phase 4 — Skills und Permissions (Profile)
Voraussetzung: Phase 3 abgeschlossen.
### DIS-EP-401
**Profile-Registry-Grundgerüst**
`src/agent/profiles/registry.ts` mit `ProfileRegistry`-Interface und Lade-Mechanismus aus `src/agent/profiles/*.ts`.
Branch-Pattern: `phase-4/profile-registry`
### DIS-EP-402
**Fünf Profile: developer, researcher, writer, ops, sandboxed**
Konkrete Profile mit je angepasstem `.claude/settings.json`-Template und CLAUDE.md-Abschnitt.
Branch-Pattern: `phase-4/profiles-builtin`
### DIS-EP-403
**`/new-agent profile:<name>` Option**
`/new-agent name:x role:y profile:developer` nutzt das Profile-Template statt des Default.
Branch-Pattern: `phase-4/new-agent-profile-option`
### DIS-EP-404
**PreToolUse-Hook `guard-tool.cjs` + Audit-Log**
Node.js-Script das als Hook vor Tool-Calls ausgeführt wird. Prüft gegen Deny-Liste, schreibt Audit-Log.
Branch-Pattern: `phase-4/pretooluse-hook`
### DIS-EP-405
**Integration-Tests Hook-Guard + Profile-Permissions**
Tests für Hook-Script und Profile-Settings-Rendering.
Branch-Pattern: `phase-4/profile-tests`
---
## Phase 5 — Streaming
Voraussetzung: Phase 4 abgeschlossen.
### DIS-EP-501
**`runAgentStream()` mit `--output-format stream-json`**
Parallele Runner-Implementierung die NDJSON-Stream vom CLI konsumiert.
Flags: `--verbose --include-partial-messages`.
Branch-Pattern: `phase-5/runner-stream`
### DIS-EP-502
**Defensiver NDJSON-Event-Parser**
`src/agent/stream-parser.ts` mit versioniertem Event-Dispatch, defensivem Parsing, Fallback bei unbekannten Event-Typen.
Branch-Pattern: `phase-5/stream-parser`
### DIS-EP-503
**`stream-to-discord` — Edit-Debouncing 1200ms, Rollover bei 1900 Zeichen**
Discord-Nachricht wird inkrementell editiert während Claude schreibt. Min. 1200ms zwischen Edits. Neue Nachricht bei Überlauf.
Branch-Pattern: `phase-5/stream-to-discord`
### DIS-EP-504
**Feature-Flag + Fallback + Integration-Tests**
`disclaw.yaml``streaming: false` (Default). Graceful Fallback auf Bulk-Send bei Streaming-Fehlern. Integration-Tests.
Branch-Pattern: `phase-5/streaming-flag-tests`
---
## Phase 6+ — Zukunft (Platzhalter)
Noch nicht geschätzt, nicht priorisiert. Ideen aus `docs/development-plan.md` §6+.
| Thema | Kurzbeschreibung |
|-------|-----------------|
| Agent-zu-Agent-Kommunikation | Agenten können einander Aufgaben delegieren via Discord-Mentions |
| `/list-agents`, `/delete-agent` | Management-Channel-Befehle für Workspace-Verwaltung |
| Web-Dashboard | Browser-UI für Agenten-Übersicht, Konversationen, Nutzung |
| Docker-Isolation | Jeder Agent-Workspace in eigenem Container |
| Automatische Workspace-Migration | Migration bestehender `./workspaces/`-Ordner zu neuem Root |
| Multi-Guild-Support | Ein DisClaw-Bot für mehrere Discord-Server |
| MCP-Server pro Workspace | Workspace-spezifische MCP-Konfiguration aktivierbar |