From d8f70aa188805961fb9fb500dc8f759dbd79a767 Mon Sep 17 00:00:00 2001 From: Nick Tabeling Date: Wed, 8 Apr 2026 16:44:12 +0200 Subject: [PATCH] forgejo management-token + organization/tasks created + workflow/board --- .forgejo/PULL_REQUEST_TEMPLATE.md | 25 + .obsidian/workspace.json | 29 +- docs/backlog.md | 1040 +++++++++++++++++++++++++++++ docs/workflow.md | 496 ++++++++++++++ scripts/bootstrap-forgejo.py | 372 +++++++++++ tasks/BOARD.md | 94 +++ tasks/DIS-001.md | 49 ++ tasks/DIS-002.md | 47 ++ tasks/DIS-003.md | 44 ++ tasks/DIS-004.md | 44 ++ tasks/DIS-005.md | 48 ++ tasks/DIS-006.md | 56 ++ tasks/DIS-007.md | 42 ++ tasks/DIS-008.md | 45 ++ tasks/DIS-101.md | 42 ++ tasks/DIS-102.md | 41 ++ tasks/DIS-103.md | 42 ++ tasks/DIS-104.md | 42 ++ tasks/DIS-105.md | 41 ++ tasks/DIS-106.md | 43 ++ tasks/DIS-107.md | 45 ++ tasks/DIS-108.md | 42 ++ tasks/DIS-109.md | 45 ++ tasks/DIS-110.md | 41 ++ tasks/EPICS.md | 131 ++++ 25 files changed, 2981 insertions(+), 5 deletions(-) create mode 100644 .forgejo/PULL_REQUEST_TEMPLATE.md create mode 100644 docs/backlog.md create mode 100644 docs/workflow.md create mode 100644 scripts/bootstrap-forgejo.py create mode 100644 tasks/BOARD.md create mode 100644 tasks/DIS-001.md create mode 100644 tasks/DIS-002.md create mode 100644 tasks/DIS-003.md create mode 100644 tasks/DIS-004.md create mode 100644 tasks/DIS-005.md create mode 100644 tasks/DIS-006.md create mode 100644 tasks/DIS-007.md create mode 100644 tasks/DIS-008.md create mode 100644 tasks/DIS-101.md create mode 100644 tasks/DIS-102.md create mode 100644 tasks/DIS-103.md create mode 100644 tasks/DIS-104.md create mode 100644 tasks/DIS-105.md create mode 100644 tasks/DIS-106.md create mode 100644 tasks/DIS-107.md create mode 100644 tasks/DIS-108.md create mode 100644 tasks/DIS-109.md create mode 100644 tasks/DIS-110.md create mode 100644 tasks/EPICS.md diff --git a/.forgejo/PULL_REQUEST_TEMPLATE.md b/.forgejo/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..fc41e47 --- /dev/null +++ b/.forgejo/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +## Summary + +- + +## Closes + +Closes # + +## Test Plan + +- [ ] Unit-Tests geschrieben und grün (`npm test`) +- [ ] `npm run build` grün +- [ ] `npm run lint` grün (wenn vorhanden) +- [ ] Manuelle Verifikation: + +## Breaking Changes + +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) diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 62b22dc..332f41b 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -13,12 +13,12 @@ "state": { "type": "markdown", "state": { - "file": "docs/ai-engineer-analyse.md", + "file": "tasks/BOARD.md", "mode": "preview", "source": false }, "icon": "lucide-file", - "title": "ai-engineer-analyse" + "title": "BOARD" } } ] @@ -183,10 +183,29 @@ "bases:Neue Base erstellen": false } }, - "active": "8a9c3ac5d2b3b989", + "active": "1decbe58679087d4", "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/ai-engineer-analyse.md" + "docs/development-plan.md" ] } \ No newline at end of file diff --git a/docs/backlog.md b/docs/backlog.md new file mode 100644 index 0000000..3f751f8 --- /dev/null +++ b/docs/backlog.md @@ -0,0 +1,1040 @@ +# DisClaw — Issue-Backlog + +Autor: Projekt- und Git-Manager-Agent +Datum: 2026-04-08 +Quelle: `docs/development-plan.md` §4, präzisiert durch `docs/ai-engineer-analyse.md` und +`docs/cli-feature-answers.md`. + +Dieses Dokument ist die **Single Source of Truth für den Backlog**, bevor die Issues +nach Forgejo synchronisiert werden. Jede Issue-Karte hat eine lokale ID (`DIS-XXX`), +die das Bootstrap-Script auf die echte Forgejo-Issue-Nummer mappt. + +**Konvention**: +- Phase 0 und Phase 1 sind **granular** geschnitten (0,5–2 Dev-Tage pro Issue, einzeln testbar). +- Phase 2–5 sind auf **Epic-Level** — werden am Phasen-Anfang verfeinert. +- Phase 6+ ist reiner Platzhalter. + +--- + +## Phase 0 — Härtung (8 Issues) + +### DIS-001: Workspace-Root nach `~/.disclaw/workspaces/` verschieben + +**Phase**: 0 +**Labels**: `phase:0`, `type:feat`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/workspace-root-home` + +**Issue-Body**: +``` +## Ziel +Workspaces werden standardmäßig unter `~/.disclaw/workspaces//` (Linux/Mac) bzw. +`%USERPROFILE%\.disclaw\workspaces\\` (Windows) angelegt. Der Pfad ist über +`disclaw.yaml` → `workspaces_root` konfigurierbar; der Default zeigt aber immer außerhalb +des DisClaw-Repo-Roots. + +## Kontext +Referenz: `docs/development-plan.md` §2.9 und Phase 0, Task 3. +Grund: Claude Code walkt beim Start Parent-Directories hoch und konkateniert alle +gefundenen `CLAUDE.md`-Dateien in den System-Prompt. Liegt ein Workspace unterhalb von +`C:\Code\side\disclaw\`, erbt jeder Agent die Projekt-`CLAUDE.md` von DisClaw selbst. +Das kontaminiert die Agenten-Identität und ist nicht reproduzierbar. + +## Scope +- **In**: + - `disclaw.yaml` Default-Wert auf `~/.disclaw/workspaces` ändern. + - `src/config/loader.ts` um Tilde-Expansion erweitern (`~` → `os.homedir()`), absolute + Pfade per `path.resolve` normalisieren. + - `src/commands/new-agent.ts` legt Workspaces unter dem resolvierten Root an. + - Warning-Log beim Bot-Start, wenn `workspaces_root` innerhalb des DisClaw-Repo-Roots + liegt (erkannt via `path.relative(repoRoot, wsRoot)` startet nicht mit `..`). + - Manueller Migrationshinweis für bestehende `./workspaces//`-Ordner im README. +- **Out**: + - Automatische Migration bestehender Workspaces (kommt in Phase 6+). + - `claudeMdExcludes`-Rendering (gehört zu DIS-005, wenn Dev-Mode aktiv). + - `--bare` + `--append-system-prompt-file` (gehört zu DIS-004). + +## Definition of Done +- [ ] `disclaw.yaml` hat Default `workspaces_root: "~/.disclaw/workspaces"`. +- [ ] `src/config/loader.ts` expandiert `~` zuverlässig auf Linux, Mac, Windows. +- [ ] `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` deckt Tilde-Expansion, + absolute-path-Normalisierung und Repo-internen Warn-Case ab. +- [ ] README-Abschnitt „Migration bestehender Workspaces" hinzugefügt. +- [ ] `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` + +## Branch +`phase-0/workspace-root-home` + +## Abhängigkeiten +- Keine. +``` + +--- + +### DIS-002: `shell: false` + `cross-spawn` Windows-Fix im Runner + +**Phase**: 0 +**Labels**: `phase:0`, `type:fix`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/harden-spawn` + +**Issue-Body**: +``` +## Ziel +Command-Injection über Discord-User-Input eliminieren. `shell: true` aus +`src/agent/runner.ts` entfernen und durch `cross-spawn` ersetzen. Windows-`.cmd`-Wrapper +(`claude.cmd`) wird korrekt aufgelöst, CVE-2024-27980 wird umgangen. + +## Kontext +Referenz: `docs/development-plan.md` §2.2, Phase 0 Task 1. +`src/agent/runner.ts:187` nutzt aktuell `shell: true`. Jeder Discord-User-Input landet +damit direkt in der Shell-Interpretation → Command-Injection-Vektor. + +## Scope +- **In**: + - `cross-spawn` und `@types/cross-spawn` als Dependencies hinzufügen. + - `src/runtime/resolve-claude.ts` (neu): `resolveClaude()`-Funktion, liest `CLAUDE_PATH` + aus `.env`, fällt bei leer auf `where claude` / `which claude` zurück, cached Ergebnis + in-memory, wirft verständliche Fehlermeldung wenn nicht auffindbar. + - `src/agent/runner.ts`: `spawn` → `cross-spawn`-Import, `shell: false`, + `windowsHide: true`, `stdio: ["pipe", "pipe", "pipe"]`, `child.stdout.setEncoding("utf8")`. +- **Out**: + - `sanitizedEnv()` (eigenes Issue DIS-003). + - `--bare` / `--append-system-prompt-file` (DIS-004). + +## Definition of Done +- [ ] Kein `shell: true` mehr im gesamten `src/`-Baum (grep-verifiziert). +- [ ] `cross-spawn` in `package.json#dependencies`, Types in `devDependencies`. +- [ ] `resolveClaude()` wirft klare Fehlermeldung wenn `claude` nicht auf PATH und + `CLAUDE_PATH` leer ist. +- [ ] Unit-Test `tests/unit/resolve-claude.test.ts` mockt `which`/`where`-Aufrufe und + prüft: (a) `.env`-Pfad-Override, (b) Fallback auf PATH-Lookup, (c) 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` + +## Branch +`phase-0/harden-spawn` + +## Abhängigkeiten +- Keine. +``` + +--- + +### DIS-003: `sanitizedEnv()` — Secrets aus Child-Env entfernen + +**Phase**: 0 +**Labels**: `phase:0`, `type:fix`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/sanitize-env` + +**Issue-Body**: +``` +## Ziel +Kein Secret (insbesondere `DISCORD_BOT_TOKEN`) darf im Environment des `claude`-Kindprozesses +landen. Ein Agent kann heute per `echo $DISCORD_BOT_TOKEN` den Bot-Token exfiltrieren. + +## Kontext +Referenz: `docs/development-plan.md` §2.6, Phase 0 Task 2. +`src/agent/runner.ts:181` vererbt aktuell `process.env` vollständig. Analyse +`docs/ai-engineer-analyse.md` §4.5. + +## Scope +- **In**: + - `src/runtime/env.ts` (neu): `sanitizedEnv(extra?: Record): NodeJS.ProcessEnv`. + Entfernt: `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_SECRET`, `DISCORD_PUBLIC_KEY`, alles + matching `/^(DISCLAW_SECRET_|SECRET_|TOKEN_|API_KEY_?)/i`. Fügt hinzu: `CI=true`, + `DISCLAW_AGENT=1`, `DISCLAW_AGENT_NAME=`. + - `src/agent/runner.ts` verwendet `sanitizedEnv({ DISCLAW_AGENT_NAME: ws.agent_name })` + statt `...process.env`. + - Konfigurierbare Block-Liste über `disclaw.yaml` → `env_blocklist: []` (optional, + additiv zur Default-Liste). +- **Out**: + - Whitelist-statt-Blacklist-Modus (kann später nachgezogen werden). + +## Definition of Done +- [ ] `sanitizedEnv()` ist pur, hat keine Seiteneffekte, akzeptiert optionale Extras. +- [ ] Unit-Test `tests/unit/sanitize-env.test.ts` verifiziert: + (a) `DISCORD_BOT_TOKEN` wird entfernt, + (b) `SECRET_FOO`, `TOKEN_BAR`, `API_KEY` werden entfernt, + (c) `PATH`, `HOME`, `USERPROFILE` bleiben, + (d) Extras überschreiben Default-Set, + (e) `env_blocklist` aus Config 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` (neues optionales Feld `env_blocklist`) +- `tests/unit/sanitize-env.test.ts` + +## Branch +`phase-0/sanitize-env` + +## Abhängigkeiten +- Keine. Kann parallel zu DIS-002 laufen (betrifft unterschiedliche Zeilen im Runner, + aber beide editieren `runner.ts` — bei Merge-Konflikt gewinnt DIS-002 zuerst, DIS-003 + rebased). +``` + +--- + +### DIS-004: Runner mit `--bare` + `--append-system-prompt-file` aufrufen + +**Phase**: 0 +**Labels**: `phase:0`, `type:feat`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/bare-and-append-identity` + +**Issue-Body**: +``` +## Ziel +Jeder CLI-Call verwendet `--bare` (skippt CLAUDE.md-Walk-Up, Hooks, Skills, Plugins, MCP, +Auto-Memory) und injiziert die Agenten-Identität explizit über +`--append-system-prompt-file /CLAUDE.md`. Parent-CLAUDE.md-Leaks werden +strukturell unmöglich. + +## Kontext +Referenz: `docs/development-plan.md` §2.9, Phase 0 Task 7. +Quelle: `docs/cli-feature-answers.md` Frage 8. `--bare` wird laut Doku zukünftiger +Default für `-p`. + +## Scope +- **In**: + - `src/agent/runner.ts`: CLI-Args-Builder immer `--bare` und `--append-system-prompt-file` + anhängen, mit `path.join(ws.workspace_path, "CLAUDE.md")`. + - Integration-Test mit Fake-Claude (`tests/fixtures/fake-claude.mjs`), der den + empfangenen System-Prompt als JSON echoed, Assertion: ausschließlich die Workspace- + `CLAUDE.md` ist drin, keine Parent-Leaks. +- **Out**: + - `--resume`-Flag (gehört zu Phase 2). + - Streaming-Flags (Phase 5). + +## Definition of Done +- [ ] `runner.ts` baut Args mit `["-p", msg, "--output-format", "json", "--bare", + "--append-system-prompt-file", "/CLAUDE.md"]` (plus Existing). +- [ ] Integration-Test `tests/integration/no-parent-claude-md-leak.test.ts` nutzt + Fake-Claude und verifiziert den empfangenen System-Prompt-Gehalt. +- [ ] 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` + +## Branch +`phase-0/bare-and-append-identity` + +## Abhängigkeiten +- Blockiert durch: DIS-002 (gemeinsames Spawn-Setup; einfacher auf sauberer Basis). +``` + +--- + +### DIS-005: Path-Traversal-Check in `/new-agent` + +**Phase**: 0 +**Labels**: `phase:0`, `type:fix`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/path-traversal-check` + +**Issue-Body**: +``` +## Ziel +`/new-agent name:` akzeptiert keinen Namen, der aus dem Workspace-Root ausbricht, +auch nicht in Edge-Cases (`..`, Unicode-Homoglyphen, `.`, leere Strings). + +## Kontext +Referenz: `docs/development-plan.md` Phase 0 Task 4. Analyse §4.3. +Aktuell gibt es eine Regex-Validierung in `src/commands/new-agent.ts`, aber keinen +expliziten `path.resolve`-Containment-Check. Defense in depth: Regex plus Containment. + +## Scope +- **In**: + - `src/commands/new-agent.ts`: Nach Regex-Validierung: + ```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 („Ungültiger Agent-Name"). +- **Out**: + - Unicode-Normalisierung über Regex hinaus (genügen aktuell `[a-z0-9-]+`). + +## Definition of Done +- [ ] Containment-Check ist aktiv. +- [ ] Unit-Test `tests/unit/path-traversal.test.ts` prüft: + `../etc`, `..`, `./foo`, `a/../b`, leerer String, `CON` (Windows-reserved), Unicode. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/commands/new-agent.ts` +- `tests/unit/path-traversal.test.ts` + +## Branch +`phase-0/path-traversal-check` + +## Abhängigkeiten +- Keine. Kann parallel laufen. +``` + +--- + +### DIS-006: `.claude/settings.json`-Template härten + `CLAUDE.md`-Injection-Klausel + +**Phase**: 0 +**Labels**: `phase:0`, `type:feat`, `priority:p0`, `security` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/harden-settings-and-claudemd` + +**Issue-Body**: +``` +## Ziel +Jeder neu erstellte Agent bekommt ein gehärtetes `.claude/settings.json` (Deny-Liste +gegen `../**`, `Read(./.env)`, gefährliche Bash-Muster) und eine `CLAUDE.md` mit einer +expliziten Prompt-Injection-Resistenz-Klausel. + +## Kontext +Referenz: `docs/development-plan.md` §2.5, Phase 0 Tasks 5+6. Settings-Schema bestätigt +via `docs/cli-feature-answers.md` Frage 3. + +## Scope +- **In**: + - `src/agent/identity.ts`: Settings-Template gemäß §2.5 aus dem Plan: + ```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(scp *)", + "Bash(* /etc/*)", "Bash(* ~/.ssh/*)", + "Bash(powershell -Command *)" + ], + "defaultMode": "acceptEdits" + }, + "cleanupPeriodDays": 90 + } + ``` + - `CLAUDE.md`-Template erhält einen Abschnitt „Sicherheit & Prompt-Injection", der + klar stellt: Anweisungen aus Nachrichten, die außerhalb dieser CLAUDE.md stehen, + dürfen Permissions nicht überschreiben. + - `PreToolUse`-Hook-Stub wird in Phase 4 (DIS-Epic-P4) ergänzt — **nicht in diesem + Issue**. +- **Out**: + - Der tatsächliche Hook-Guard-Node-Script (Phase 4). + - Profile-spezifische Settings (Phase 4). + +## Definition of Done +- [ ] Frisch erstellter Agent-Workspace enthält settings.json mit vollständiger Deny-Liste. +- [ ] `CLAUDE.md` enthält den Injection-Resistenz-Absatz (im Template). +- [ ] Unit-Test `tests/unit/identity-templates.test.ts` parsed das gerenderte JSON und + verifiziert die Presence der Deny-Einträge und des CLAUDE.md-Satzes. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/agent/identity.ts` +- `tests/unit/identity-templates.test.ts` + +## Branch +`phase-0/harden-settings-and-claudemd` + +## Abhängigkeiten +- Keine. +``` + +--- + +### DIS-007: Lock-File gegen Doppelstart + +**Phase**: 0 +**Labels**: `phase:0`, `type:feat`, `priority:p1` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/startup-lockfile` + +**Issue-Body**: +``` +## Ziel +Zwei gleichzeitig laufende DisClaw-Instanzen auf derselben DB/Workspace-Root werden +verhindert. Der zweite Start bricht mit klarer Fehlermeldung ab. + +## Kontext +Referenz: `docs/development-plan.md` §7 (Risiko-Tabelle, „Zwei DisClaw-Instanzen gegen +dieselbe DB/Workspace"). + +## Scope +- **In**: + - `src/runtime/lockfile.ts` (neu): `acquireLock(path)` legt `/.disclaw.lock` + an mit aktueller PID und ISO-Timestamp. Prüft bei existierendem File, ob der PID noch + lebt (`process.kill(pid, 0)`). Wenn nein, stale-lock übernehmen. + - `src/index.ts` ruft `acquireLock` vor DB-Init auf; bei Fehlschlag: klare Fehlermeldung, + exit 1. + - Graceful Shutdown löscht das Lock-File. +- **Out**: + - Distributed Locking für Multi-Host-Deployments (out of scope für MVP). + +## 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 deckt: Acquire, Doppel-Acquire, + Stale-Lock-Recovery, Release. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/runtime/lockfile.ts` +- `src/index.ts` +- `tests/unit/lockfile.test.ts` + +## Branch +`phase-0/startup-lockfile` + +## Abhängigkeiten +- Blockiert durch: DIS-001 (Lock liegt unter dem finalen `workspaces_root`). +``` + +--- + +### DIS-008: Forgejo Actions CI (build, test, lint) + +**Phase**: 0 +**Labels**: `phase:0`, `type:ci`, `priority:p1` +**Milestone**: Phase 0 — Härtung +**Branch**: `phase-0/ci-pipeline` + +**Issue-Body**: +``` +## Ziel +Jeder PR gegen `main` triggert eine CI-Pipeline, die `npm run build`, `npm test` und +(sobald ESLint eingeführt) `npm run lint` ausführt. Ohne grüne Checks kein Merge. + +## Kontext +Referenz: `docs/development-plan.md` Phase 0 Task 8 (implizit durch §7 Risiko-Mitigation +„CI-Test auf Windows-Runner"). Forgejo Actions ist API-kompatibel mit GitHub Actions. + +## Scope +- **In**: + - `.forgejo/workflows/ci.yml`: Trigger `pull_request` gegen `main`. Matrix: + `ubuntu-latest`, `windows-latest`. Steps: checkout, setup-node 20, `npm ci`, + `npm run build`, `npm test`. + - ESLint-Config-Basis (`.eslintrc.cjs` mit `no-child-process-shell-true`-Custom-Rule + oder einfach Grep-Guard in einem Script), `package.json` `lint`-Script. + - Branch-Protection für `main` verlangt die Checks `build`, `test`, `lint` (setup + via API-Call, siehe `docs/workflow.md` §2.4). +- **Out**: + - Deployment-Jobs, Code-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`-Script existiert und läuft lokal. +- [ ] README-Abschnitt „CI" verweist auf den Workflow. + +## Dateien (erwartet) +- `.forgejo/workflows/ci.yml` +- `.eslintrc.cjs` +- `package.json` +- `README.md` + +## Branch +`phase-0/ci-pipeline` + +## Abhängigkeiten +- Sollte spät in Phase 0 kommen (nachdem mindestens DIS-002, DIS-003, DIS-005 im Code + sind), damit die Lint-Rules auf sauberer Basis greifen. +``` + +--- + +## Phase 1 — Solides MVP (10 Issues) + +### DIS-101: Pino-Logger einführen, `console.*` ersetzen + +**Phase**: 1 +**Labels**: `phase:1`, `type:refactor`, `priority:p1` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/pino-logger` + +**Issue-Body**: +``` +## Ziel +Strukturiertes JSON-Logging über `pino`. Child-Logger pro Komponente. Keine +`console.log/.error` mehr im Produktionscode. + +## Kontext +Referenz: `docs/development-plan.md` §2.7, Phase 1 Task 1. + +## Scope +- **In**: `src/runtime/logger.ts` mit `rootLogger` und `childLogger(component)`. Alle + `console.*`-Calls in `src/index.ts`, `src/bot.ts`, `src/router.ts`, `src/agent/runner.ts`, + `src/commands/new-agent.ts` ersetzen. `pino-pretty` in `devDependencies`, in dev als + Pretty-Transport. +- **Out**: Log-Sampling, Log-Levels-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, dass `childLogger` den + `component`-Key setzt. +- [ ] `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` + +## Branch +`phase-1/pino-logger` + +## Abhängigkeiten +- Phase 0 abgeschlossen. +``` + +--- + +### DIS-102: `sanitizeForDiscord(text, rootDir)` — Pfade aus User-Output entfernen + +**Phase**: 1 +**Labels**: `phase:1`, `type:feat`, `priority:p1`, `security` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/sanitize-for-discord` + +**Issue-Body**: +``` +## Ziel +Absolute Pfade, Home-Dir und Tokens werden vor dem Discord-Send aus Agent-Antworten +entfernt. Verhindert Info-Leak in öffentliche Channels. + +## Kontext +Referenz: `docs/development-plan.md` §2.7 (Error-Handler-Strategie), Phase 1 Task 2. + +## Scope +- **In**: `src/runtime/sanitize.ts` mit `sanitizeForDiscord(text: string, ctx: { repoRoot: string; home: string })`. + Ersetzt Regex-basiert `repoRoot` → ``, `home` → `~`, bekannte Token-Regexes → ``. + Router ruft es 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 test` grün. + +## Dateien (erwartet) +- `src/runtime/sanitize.ts` +- `src/router.ts` +- `tests/unit/sanitize-for-discord.test.ts` + +## Branch +`phase-1/sanitize-for-discord` + +## Abhängigkeiten +- Blockiert durch: DIS-101 (gemeinsames Refactoring-Fenster im Router). +``` + +--- + +### DIS-103: `ChannelQueue` — Per-Channel-FIFO + +**Phase**: 1 +**Labels**: `phase:1`, `type:feat`, `priority:p0` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/channel-queue` + +**Issue-Body**: +``` +## Ziel +Mehrere Nachrichten im selben Channel werden strikt seriell abgearbeitet. Parallele +Channels bleiben parallel. Keine Race Conditions mehr auf Workspace-Dateien. + +## Kontext +Referenz: `docs/development-plan.md` §2.3, Phase 1 Task 3. Status-Quo §1 (3). + +## Scope +- **In**: + - `src/runtime/channel-queue.ts` mit `ChannelQueue`-Klasse wie in §2.3 skizziert. + - Integration in `src/router.ts`: jeder `messageCreate`-Handler läuft durch + `channelQueue.enqueue(channelId, () => runAgent(...))`. +- **Out**: Persistente Queue über Prozessneustart. + +## Definition of Done +- [ ] Unit-Test `tests/unit/channel-queue.test.ts` verifiziert: FIFO-Ordnung, + Error-Recovery (eine fehlgeschlagene Task blockiert die Queue nicht), Map-Cleanup + nach leerer Queue. +- [ ] Integration-Test `tests/integration/channel-queue-router.test.ts` mit Mock-Router: + drei schnell hintereinander enqueued Messages werden in Reihenfolge abgearbeitet. +- [ ] `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` + +## Branch +`phase-1/channel-queue` + +## Abhängigkeiten +- Blockiert durch: DIS-101. +``` + +--- + +### DIS-104: Globaler Semaphore (`max_concurrent_agents`) + +**Phase**: 1 +**Labels**: `phase:1`, `type:feat`, `priority:p1` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/concurrency-semaphore` + +**Issue-Body**: +``` +## Ziel +Ein globaler Cap auf parallele `claude`-Prozesse verhindert Ressourcen-Thrashing. +Default 4, konfigurierbar via `disclaw.yaml` → `max_concurrent_agents`. + +## Kontext +Referenz: `docs/development-plan.md` §2.3 (zweiter Absatz), Phase 1 Task 4. + +## Scope +- **In**: `src/runtime/concurrency.ts` mit simpler `Semaphore(max: number)`-Klasse + (`acquire()/release()`). In `src/agent/runner.ts` umschließt der Spawn-Call + ein `semaphore.acquire()` / `try { ... } finally { semaphore.release() }`. +- **Out**: Dynamisches Auto-Scaling. + +## Definition of Done +- [ ] Unit-Test `tests/unit/semaphore.test.ts` deckt: Acquire bei freien Slots, + Queueing bei vollen Slots, Release gibt wartende Tasks frei, Error-Path hält + den Counter konsistent. +- [ ] Config-Default 4, in `disclaw.yaml` kommentiert. +- [ ] `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` + +## Branch +`phase-1/concurrency-semaphore` + +## Abhängigkeiten +- Keine harten. Kann parallel zu DIS-103 laufen. +``` + +--- + +### DIS-105: `splitForDiscord` — Codeblock-aware Message-Splitting + +**Phase**: 1 +**Labels**: `phase:1`, `type:feat`, `priority:p0` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/split-for-discord` + +**Issue-Body**: +``` +## 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 +Referenz: `docs/development-plan.md` §2.4, Phase 1 Task 5. Status-Quo §1 (5). + +## Scope +- **In**: + - `src/discord/split.ts` mit `splitForDiscord(text: string, limit = 1900): string[]`. + - Fence-Erkennung (```` ``` ````), Sprache-Tag wird im neuen Chunk beibehalten. + - Sehr lange Ausgaben (> 8000 Zeichen) bleiben als ein String — Fallback auf + Attachment passiert in Phase 3 (DIS-Epic-P3). + - Integration in `src/router.ts`. +- **Out**: Attachment-Fallback (Phase 3), Streaming-Split (Phase 5). + +## Definition of Done +- [ ] Unit-Test `tests/unit/split-for-discord.test.ts` deckt: Plain-Text-Split an + Newlines, Codeblock in der Mitte (wird geschlossen/eröffnet mit gleichem Tag), + mehrere Fences, sehr lange Einzelzeile (hard-split), leerer Input. +- [ ] `router.ts` nutzt die neue Funktion. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/discord/split.ts` +- `src/router.ts` +- `tests/unit/split-for-discord.test.ts` + +## Branch +`phase-1/split-for-discord` + +## Abhängigkeiten +- Blockiert durch: DIS-101. +``` + +--- + +### DIS-106: Runner-API-Refactoring → `RunResult`-Typ + +**Phase**: 1 +**Labels**: `phase:1`, `type:refactor`, `priority:p1` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/runner-runresult-api` + +**Issue-Body**: +``` +## Ziel +`runAgent()` liefert einen typisierten `RunResult = { text, sessionId?, usage?, toolCalls? }`. +Bereitet Phase 2 (`sessionId` speichern) und Phase 5 (`runAgentStream()`) ohne API-Bruch +vor. + +## Kontext +Referenz: `docs/development-plan.md` Phase 1 Task 6, §5.6 (`AgentEngine`-Interface). + +## Scope +- **In**: + - `src/agent/engine.ts` (neu): `AgentEngine`-Interface, `RunOptions`, `RunResult`, + `StreamEvent`-Stub (wird in Phase 5 gefüllt). + - `src/agent/runner.ts`: Rückgabetyp auf `RunResult` vereinheitlichen. Parser + extrahiert `text` und optional `sessionId`, `usage`. + - Router nutzt nur noch `result.text`. +- **Out**: + - `SessionStore`-Integration (Phase 2). + - Streaming-Implementierung (Phase 5). + +## Definition of Done +- [ ] `AgentEngine`-Interface existiert, `ClaudeCliEngine` implementiert es. +- [ ] Router compiled gegen `RunResult`, kein `any`. +- [ ] Unit-Test `tests/unit/parse-claude-output.test.ts` deckt verschiedene JSON-Formate + ab (text direkt, nested `result.content`, fehlerhafte Outputs). +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/agent/engine.ts` +- `src/agent/runner.ts` +- `src/router.ts` +- `tests/unit/parse-claude-output.test.ts` + +## Branch +`phase-1/runner-runresult-api` + +## Abhängigkeiten +- Blockiert durch: DIS-004 (bare/append fließt in Args ein). +``` + +--- + +### DIS-107: Zod-Schemas für Config, `agent.yaml`, CLI-JSON-Output + +**Phase**: 1 +**Labels**: `phase:1`, `type:feat`, `priority:p1` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/zod-schemas` + +**Issue-Body**: +``` +## Ziel +Alle Grenzen zwischen untrusted Input und Code sind Zod-validiert: `disclaw.yaml`, +`agent.yaml`, Claude-CLI-JSON-Output. Keine `as`-Casts mehr. + +## Kontext +Referenz: `docs/development-plan.md` §3 (`zod`), Phase 1 Tasks 7+8. + +## Scope +- **In**: + - `src/runtime/zod-schemas.ts` (neu): `disclawConfigSchema`, `agentYamlSchema`, + `claudeJsonOutputSchema`. + - `src/config/loader.ts`: Parse → Zod → `DisclawConfig`. + - `src/agent/runner.ts`: `parseClaudeJsonOutput` validiert via Zod (safe-parse, logged + bei Fehler, fällt auf Best-Effort-Text zurück). +- **Out**: Schema-Generation aus TypeScript-Types. + +## Definition of Done +- [ ] Alle `as`-Casts aus `loader.ts` und `runner.ts` entfernt. +- [ ] Unit-Test `tests/unit/zod-schemas.test.ts` deckt: valide/invalide YAMLs, + valide/invalide CLI-JSON-Samples. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/runtime/zod-schemas.ts` +- `src/config/loader.ts` +- `src/agent/runner.ts` +- `tests/unit/zod-schemas.test.ts` + +## Branch +`phase-1/zod-schemas` + +## Abhängigkeiten +- Blockiert durch: DIS-106 (gemeinsamer Runner-Touch). +``` + +--- + +### DIS-108: Schema-SSOT: `src/db/schema.sql` ist die Single Source + +**Phase**: 1 +**Labels**: `phase:1`, `type:refactor`, `priority:p2` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/schema-ssot` + +**Issue-Body**: +``` +## Ziel +Kein inline `SCHEMA_SQL`-String mehr in `database.ts`. `schema.sql` wird zur Laufzeit +geladen und ausgeführt. Verhindert Schema-Drift. + +## Kontext +Referenz: `docs/development-plan.md` Status-Quo §1 (13), Phase 1 Task 9. + +## Scope +- **In**: `src/db/database.ts` liest `schema.sql` via `fs.readFileSync` und führt es + beim Init aus. Inline-String wird entfernt. +- **Out**: Echte Migrationen mit Version-Tracking (kommt mit Phase 2). + +## Definition of Done +- [ ] Inline-SCHEMA_SQL existiert nicht mehr. +- [ ] Unit-Test `tests/unit/database-init.test.ts` verifiziert, dass eine frische DB + alle erwarteten Tabellen hat. +- [ ] `npm test` grün. + +## Dateien (erwartet) +- `src/db/database.ts` +- `tests/unit/database-init.test.ts` + +## Branch +`phase-1/schema-ssot` + +## Abhängigkeiten +- Keine. +``` + +--- + +### DIS-109: `.env.example` + Vitest-Setup + `npm test`-Skript + +**Phase**: 1 +**Labels**: `phase:1`, `type:chore`, `priority:p1` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/tooling-vitest-envexample` + +**Issue-Body**: +``` +## Ziel +`.env.example` ist im Repo. Vitest ist als Test-Runner konfiguriert. `npm test` +funktioniert Out-of-the-Box. + +## Kontext +Referenz: `docs/development-plan.md` §2.8, Phase 1 Tasks 12+13. Status-Quo §1 (10, 12). + +## Scope +- **In**: + - `.env.example` mit allen relevanten Variablen (`DISCORD_BOT_TOKEN=`, + `DISCORD_GUILD_ID=`, `CLAUDE_PATH=`). + - `vitest.config.ts` minimal (default genügt). + - `package.json`: `"test": "vitest run"`, `"test:watch": "vitest"`, + `vitest`, `@types/node` in devDeps. +- **Out**: Coverage-Reports, CI-Reporter-Config. + +## Definition of Done +- [ ] `.env.example` existiert und wird im README erwähnt. +- [ ] `npm test` läuft lokal (mindestens ein Dummy-Test grün) auf sauberem `npm ci`. +- [ ] `package.json#devDependencies` enthält Vitest. + +## Dateien (erwartet) +- `.env.example` +- `vitest.config.ts` +- `package.json` +- `README.md` + +## Branch +`phase-1/tooling-vitest-envexample` + +## Abhängigkeiten +- Sollte früh in Phase 1 kommen, damit nachfolgende Issues direkt Tests schreiben können. +``` + +--- + +### DIS-110: `docs/cli-feature-probe.md` — Manuelles Regression-Script + +**Phase**: 1 +**Labels**: `phase:1`, `type:docs`, `priority:p2` +**Milestone**: Phase 1 — Solides MVP +**Branch**: `phase-1/cli-feature-probe-doc` + +**Issue-Body**: +``` +## Ziel +Ein dokumentiertes manuelles Script, das die kritischen CLI-Annahmen aus +`cli-feature-answers.md` gegen die lokal installierte `claude`-CLI verifiziert: +Session-ID top-level, `usage`-Block-Pfade, `stream-json`-Event-Typen, Session-Slug-Regel. + +## Kontext +Referenz: `docs/development-plan.md` Phase 1 Task 14, §6 (Offene Rest-Unsicherheiten). + +## Scope +- **In**: `docs/cli-feature-probe.md` mit kopierbaren Probe-Commands (siehe + `cli-feature-answers.md` für die Basis). Tabelle „Erwartet / Beobachtet" als + Vorlage zum Ausfüllen. +- **Out**: Automatisierung der Probes (bleibt manuell bis Phase 2). + +## Definition of Done +- [ ] Dokument existiert, enthält mindestens die 4 Probes aus den offenen Fragen. +- [ ] Jeder Probe ist eine Expected-Output-Zeile zugeordnet. + +## Dateien (erwartet) +- `docs/cli-feature-probe.md` + +## Branch +`phase-1/cli-feature-probe-doc` + +## Abhängigkeiten +- Keine. +``` + +--- + +## Phase 2 — Context-Effizienz (`--resume`) — Epic-Level (4 Issues) + +Am Phasen-Anfang verfeinern. + +### DIS-EP-201: DB-Migration + `SessionStore`-Methoden +`claude_session_id`, `session_updated_at` additiv hinzufügen, DB-Repo-Methoden +`getSession/setSession/clearSession` inklusive Timestamp-Update. Siehe Plan §2.1. + +### DIS-EP-202: Runner-Integration `--resume` mit Fallback +Runner nutzt `SessionStore`, führt Retry ohne `--resume` bei Resume-Fehler durch, updated +nach jedem Run die ID defensiv. Siehe Plan §2.1. + +### DIS-EP-203: Usage-Metriken logging + `agent_runs`-Tabelle +`usage`-Block aus JSON-Output parsen (Pfade empirisch via Probe-Script bestimmen), pro +Run als strukturierter Log-Event + optional in neue `agent_runs`-Tabelle. + +### DIS-EP-204: Integration-Tests Session-Resume + Fork-Case +Fake-Claude-Fixture simuliert Erst-Run, Resume-Run, Fork-Szenario (neue Session-ID im +zweiten Run), Resume-Fehler → Fallback. Siehe Plan Phase 2 DoD. + +--- + +## Phase 3 — UX-Feinschliff — Epic-Level (4 Issues) + +### DIS-EP-301: Typing-Refresh 8s + `sendResponse`-Helper + Attachment-Fallback +Siehe Plan Phase 3 Tasks 1+2. + +### DIS-EP-302: Attachment-Inbox für eingehende Discord-Bilder +Download in `/.disclaw-inbox/`, Prompt-Referenz, TTL-Cleanup. Plan Phase 3 Task 3. + +### DIS-EP-303: Input-Limit + User-Feedback-Reactions (`👀/✅/❌/⚠️`) +Plan Phase 3 Tasks 4+5. + +### DIS-EP-304: Unit-Tests für `send-response` und Attachment-Inbox +Plan Phase 3 Task 6. + +--- + +## Phase 4 — Skills und Permissions (Profile) — Epic-Level (5 Issues) + +### DIS-EP-401: Profile-Registry-Grundgerüst +`src/agent/profiles/index.ts` + `ProfileTemplate`-Interface, Platzhalter-Rendering. +Plan §5.3, Phase 4 Tasks 1+2. + +### DIS-EP-402: Fünf Profiles — developer, researcher, writer, ops, sandboxed +Templates für `CLAUDE.md` und `settings.json` pro Rolle. Plan Phase 4 Task 1. + +### DIS-EP-403: `/new-agent profile:` Option +Slash-Command-Erweiterung mit Choice-Liste. Plan Phase 4 Task 3. + +### DIS-EP-404: PreToolUse-Hook `guard-tool.cjs` + Audit-Log +Hook-Script wird beim Workspace-Create nach `.claude/hooks/guard-tool.cjs` gerendert. +Jeder Tool-Call gegen `workspace_abs_path` geprüft, Audit-Log nach +`/.disclaw-audit.jsonl`. Plan §2.5, Phase 4 Tasks 5+6. + +### DIS-EP-405: Integration-Tests Hook-Guard + Profile-Permissions +Fake-Claude löst bösartigen Tool-Call aus → Hook muss ihn blocken. Plan Phase 4 Task 7. + +--- + +## Phase 5 — Streaming — Epic-Level (4 Issues) + +### DIS-EP-501: `runAgentStream()` mit `stream-json` + `--verbose` + `--include-partial-messages` +Zweiter Engine-Export, NDJSON-Output vom CLI. Plan Phase 5 Task 1. + +### DIS-EP-502: Defensiver NDJSON-Event-Parser +Dispatch nach `type`, unbekannte Events skippen, session_id + usage aus `result`-Event +extrahieren. Plan Phase 5 Task 2. + +### DIS-EP-503: `stream-to-discord` — Edit-Debouncing 1200ms, Rollover bei 1900 Zeichen +Respektiert Discord-Rate-Limits. Plan Phase 5 Task 3. + +### DIS-EP-504: Feature-Flag + Fallback + Integration-Tests +`streaming: true/false` in `disclaw.yaml`, automatischer Bulk-Fallback bei Stream-Fehler. +Plan Phase 5 Tasks 4+5. + +--- + +## Phase 6+ — Zukunft (Platzhalter) + +Nicht jetzt planen. Wird beim Phasen-Übergang Phase 5 → 6 verfeinert. + +**Themen** (aus `docs/development-plan.md` §4 Phase 6+): + +- `/list-agents`, `/delete-agent`, `/agent-config` Slash-Commands +- User-Rate-Limits (Token-Bucket) +- Daily-Budget pro Agent (`agent_usage`-Tabelle) +- Agent-zu-Agent-Kommunikation (Router-Delegation) +- Docker-Isolation (`DockerRunner` als zweite `AgentEngine`-Implementierung) +- Web-Dashboard (Fastify + shared DB) +- Alternative AI-Engine (`ClaudeSdkEngine` via `claude-agent-sdk`) +- `/migrate-workspaces`-Command für Legacy-In-Repo-Workspaces +- Linux/Mac Session-Slug-Verifikation für `/delete-agent` + +Label `phase:6`, keine Milestones jetzt. + +--- + +## Start-Reihenfolge der ersten 3 Issues + +Beim Start von Phase 0 zieht der erste Developer-Agent die folgenden Issues **in dieser +Reihenfolge**: + +### 1. DIS-002 — `shell: false` + `cross-spawn` Windows-Fix +**Warum zuerst**: Größter akuter Security-Fix (Command-Injection) und strukturelle +Voraussetzung für alle weiteren Runner-Änderungen. Danach ist der Runner auf jedem +Betriebssystem überhaupt erst wieder zuverlässig spawn-fähig. Blockt DIS-004 und DIS-106. +Keine Dependencies, sofort startbar. + +### 2. DIS-003 — `sanitizedEnv()` +**Warum direkt danach**: Zweiter P0-Security-Fix (Token-Exfiltration via +`$DISCORD_BOT_TOKEN`). Minimale Code-Berührung am Runner, hohes Risiko-Reward-Verhältnis. +Kein struktureller Abhängigkeitsgraph zu DIS-002 außer Merge-Reihenfolge auf +`runner.ts` — deshalb als zweites. + +### 3. DIS-001 — Workspace-Root nach `~/.disclaw/workspaces/` +**Warum drittes**: Strukturelle Voraussetzung für DIS-004 (`--append-system-prompt-file` +setzt einen sauberen Workspace-Pfad voraus) und DIS-007 (Lock-File liegt unter dem neuen +Root). Keine direkte Kollision mit DIS-002/003 (anderer Code-Pfad: `loader.ts`, +`new-agent.ts`, nicht `runner.ts`), kann also in einem parallelen Branch gestartet +werden, sobald DIS-002 gemerged ist — aber die serielle Reihenfolge ist sicherer, wenn +nur ein Developer-Agent aktiv ist. + +Ab dem Abschluss dieser drei Issues sind DIS-004, DIS-005, DIS-006, DIS-007 alle +unblocked und können parallel bearbeitet werden. DIS-008 (CI) kommt bewusst am Ende +von Phase 0, damit Lint-Rules auf sauberer Basis greifen. diff --git a/docs/workflow.md b/docs/workflow.md new file mode 100644 index 0000000..132b640 --- /dev/null +++ b/docs/workflow.md @@ -0,0 +1,496 @@ +# 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 `) trivial. + +### 1.2 Naming-Convention für Feature-Branches + +``` +phase-/ +``` + +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-/`. +4. Implementierung gegen die Definition of Done des Issues. +5. Commits folgen Conventional-Commits (§5). +6. Push: `git push -u origin phase-/`. +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= +``` + +**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 +<1–2 Sätze, was erreicht werden soll.> + +## Kontext +Referenz: `docs/development-plan.md` §, ggf. `docs/ai-engineer-analyse.md` §. + + +## Scope +- **In**: +- **Out**: + +## Definition of Done +- [ ] +- [ ] +- [ ] 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/` — +- `tests/` — + +## Branch +`phase-/` + +## Abhängigkeiten +- Blockiert durch: # (falls vorhanden) +- Blockiert: # (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 +<1–3 Sätze: was ändert dieser PR und warum.> + +## Closes +Closes # + +## Changes +- + +## Test plan +- [ ] `npm run build` läuft grün +- [ ] `npm test` läuft grün +- [ ] Manueller Smoke-Test: +- [ ] Neue Dateien haben Unit-Tests (falls anwendbar) + +## Screenshots / Logs + + +## Breaking Changes +- [ ] Keine +- [ ] Ja: + +## 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: + +``` +(): + + + + +``` + +**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-/ + ``` +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-/`. +8. **PR öffnen**: Via `tea pulls create` oder Forgejo-Web-UI. PR-Template ausfüllen, + `Closes #` 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 ` auf einem neuen Branch + `phase-/revert-`, 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/.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-/` | +| 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) | diff --git a/scripts/bootstrap-forgejo.py b/scripts/bootstrap-forgejo.py new file mode 100644 index 0000000..cb5f470 --- /dev/null +++ b/scripts/bootstrap-forgejo.py @@ -0,0 +1,372 @@ +#!/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//` 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 /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 /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\", \"/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:` 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.") diff --git a/tasks/BOARD.md b/tasks/BOARD.md new file mode 100644 index 0000000..953fb43 --- /dev/null +++ b/tasks/BOARD.md @@ -0,0 +1,94 @@ +# 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 2–6+) +*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:` 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 | diff --git a/tasks/DIS-001.md b/tasks/DIS-001.md new file mode 100644 index 0000000..d58ad81 --- /dev/null +++ b/tasks/DIS-001.md @@ -0,0 +1,49 @@ +--- +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//` (Linux/Mac) bzw. +`%USERPROFILE%\.disclaw\workspaces\\` (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 diff --git a/tasks/DIS-002.md b/tasks/DIS-002.md new file mode 100644 index 0000000..6f82494 --- /dev/null +++ b/tasks/DIS-002.md @@ -0,0 +1,47 @@ +--- +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 diff --git a/tasks/DIS-003.md b/tasks/DIS-003.md new file mode 100644 index 0000000..9b1725e --- /dev/null +++ b/tasks/DIS-003.md @@ -0,0 +1,44 @@ +--- +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=` · 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 diff --git a/tasks/DIS-004.md b/tasks/DIS-004.md new file mode 100644 index 0000000..31f7e2e --- /dev/null +++ b/tasks/DIS-004.md @@ -0,0 +1,44 @@ +--- +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 /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 /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", "/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 diff --git a/tasks/DIS-005.md b/tasks/DIS-005.md new file mode 100644 index 0000000..d73130b --- /dev/null +++ b/tasks/DIS-005.md @@ -0,0 +1,48 @@ +--- +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:` 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 diff --git a/tasks/DIS-006.md b/tasks/DIS-006.md new file mode 100644 index 0000000..e748872 --- /dev/null +++ b/tasks/DIS-006.md @@ -0,0 +1,56 @@ +--- +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 diff --git a/tasks/DIS-007.md b/tasks/DIS-007.md new file mode 100644 index 0000000..8eacd75 --- /dev/null +++ b/tasks/DIS-007.md @@ -0,0 +1,42 @@ +--- +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 `/.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 diff --git a/tasks/DIS-008.md b/tasks/DIS-008.md new file mode 100644 index 0000000..8b7a301 --- /dev/null +++ b/tasks/DIS-008.md @@ -0,0 +1,45 @@ +--- +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 diff --git a/tasks/DIS-101.md b/tasks/DIS-101.md new file mode 100644 index 0000000..9f3c44c --- /dev/null +++ b/tasks/DIS-101.md @@ -0,0 +1,42 @@ +--- +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 diff --git a/tasks/DIS-102.md b/tasks/DIS-102.md new file mode 100644 index 0000000..cbdf9d1 --- /dev/null +++ b/tasks/DIS-102.md @@ -0,0 +1,41 @@ +--- +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 → ``, home → `~`, Token-Patterns → `` · 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 diff --git a/tasks/DIS-103.md b/tasks/DIS-103.md new file mode 100644 index 0000000..0089a5f --- /dev/null +++ b/tasks/DIS-103.md @@ -0,0 +1,42 @@ +--- +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 diff --git a/tasks/DIS-104.md b/tasks/DIS-104.md new file mode 100644 index 0000000..bf335c7 --- /dev/null +++ b/tasks/DIS-104.md @@ -0,0 +1,42 @@ +--- +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 diff --git a/tasks/DIS-105.md b/tasks/DIS-105.md new file mode 100644 index 0000000..3a68c89 --- /dev/null +++ b/tasks/DIS-105.md @@ -0,0 +1,41 @@ +--- +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 diff --git a/tasks/DIS-106.md b/tasks/DIS-106.md new file mode 100644 index 0000000..2a7403b --- /dev/null +++ b/tasks/DIS-106.md @@ -0,0 +1,43 @@ +--- +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 diff --git a/tasks/DIS-107.md b/tasks/DIS-107.md new file mode 100644 index 0000000..53861f5 --- /dev/null +++ b/tasks/DIS-107.md @@ -0,0 +1,45 @@ +--- +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 diff --git a/tasks/DIS-108.md b/tasks/DIS-108.md new file mode 100644 index 0000000..032d74c --- /dev/null +++ b/tasks/DIS-108.md @@ -0,0 +1,42 @@ +--- +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 diff --git a/tasks/DIS-109.md b/tasks/DIS-109.md new file mode 100644 index 0000000..7012ceb --- /dev/null +++ b/tasks/DIS-109.md @@ -0,0 +1,45 @@ +--- +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 diff --git a/tasks/DIS-110.md b/tasks/DIS-110.md new file mode 100644 index 0000000..6a1495c --- /dev/null +++ b/tasks/DIS-110.md @@ -0,0 +1,41 @@ +--- +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 diff --git a/tasks/EPICS.md b/tasks/EPICS.md new file mode 100644 index 0000000..f4c5052 --- /dev/null +++ b/tasks/EPICS.md @@ -0,0 +1,131 @@ +# DisClaw — Epics (Phase 2–6+) + +> 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 ` 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:` 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 |