# 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.