# DisClaw — Entwicklungsplan Autor: Senior Developer Datum: 2026-04-08 Basis: `ARCHITECTURE.md`, `CLAUDE.md`, `docs/ai-engineer-analyse.md`, `docs/cli-feature-answers.md`, aktueller Stand unter `src/`. Der Plan ist umsetzungsnah. Er legt fest, was gebaut wird, wie, in welcher Reihenfolge, und wo der Code erweiterbar bleiben muss. Zeitangaben gibt es bewusst nicht — nur logische Reihenfolge und Abhängigkeiten. ## Revision-Log - 2026-04-08: Initialversion. - 2026-04-08: AI-Engineer-Antworten eingearbeitet (siehe `docs/cli-feature-answers.md`). Workspace-Location auf `~/.disclaw/workspaces/` (außerhalb DisClaw-Repo) geändert, Windows-Spawn-Fix via `cross-spawn` ergänzt, Phase 2 (`--resume`) freigegeben, `.claude/settings.json`-Schema und Hooks-Layout präzisiert, Abschnitt 6 durch Verweis auf Antworten-Dokument ersetzt. --- ## 1. Status Quo ### Was existiert und funktioniert (vermutlich) - `src/index.ts` — Bootstrap, lädt Env und Config, initialisiert DB, startet Bot, Graceful Shutdown. - `src/config/loader.ts` — Lädt `.env` und `disclaw.yaml`. - `src/db/database.ts` — `better-sqlite3`-Wrapper, `workspaces` und `conversations` Tabellen mit Prepared Statements, Migrationen via `IF NOT EXISTS`, WAL und Foreign Keys aktiv. - `src/db/schema.sql` — SQL-Schema (separat vorhanden, aber Migration läuft aktuell aus dem Inline-String in `database.ts`, Duplikation). - `src/bot.ts` — Discord Client mit Intents `Guilds`, `GuildMessages`, `MessageContent`. Auto-Erstellung/Adoption des Management-Channels. Slash-Command-Registrierung pro Guild. Basic `messageCreate` Dispatch. - `src/router.ts` — Channel→Workspace Lookup, naives 2000-Zeichen-Splitting an Line-Breaks, Typing-Indikator alle 5 s, schreibt User- und Assistant-Messages in `conversations`. - `src/commands/new-agent.ts` — Slash-Command `/new-agent` mit Name-Validierung (Regex), Channel-Create, Workspace-Erstellung, DB-Insert. - `src/agent/identity.ts` — `agent.yaml`, `CLAUDE.md` und `.claude/settings.json` Templates werden beim Create geschrieben. - `src/agent/runner.ts` — Spawnt `claude -p "" --output-format json`, baut History als Freitext, parst JSON-Output in drei Formaten, Timeout, AbortController-Support. ### Was kaputt oder problematisch ist 1. **P0 Security**: `src/agent/runner.ts:187` setzt `shell: true` — direkter Command-Injection-Vektor über Discord-User-Input (siehe AI-Engineer-Analyse §4.1). 2. **P0 Secret Leak**: `src/agent/runner.ts:181` vererbt `process.env` vollständig an den Kindprozess → `DISCORD_BOT_TOKEN` und andere Secrets sind im Claude-Prozess sichtbar (§4.5). 3. **Kein Queueing pro Channel**: Mehrere gleichzeitige Nachrichten im selben Channel starten parallele `claude`-Prozesse im gleichen `cwd`. Race Conditions bei Tool-Calls und Session-Updates sind garantiert (§2 "Per-Channel-FIFO-Queue"). 4. **Agent-Name-Regex unzureichend gegen Path Traversal**: Kein expliziter `path.resolve`-Containment-Check in `new-agent.ts`. Regex akzeptiert `a-a` aber kein explizites Path-Check-Gate existiert (§4.3). 5. **Message-Splitting zerreißt Codeblöcke**: `router.ts:splitMessage()` respektiert keine ``` ``` ``` Fences (§2). 6. **History-Injektion statt `--resume`**: `runner.ts:buildPrompt()` konkateniert Historie als Freitext — kein Prompt-Caching, falsche Rollenzuordnung (§1). 7. **`.claude/settings.json` Template ist zu permissiv**: `allow: ["Read", "Write", "Edit", "Bash", ...]` ohne Pfad-Restriktionen und ohne `deny`-Liste. Kein Hook-Guard. 8. **`CLAUDE.md` Template hat keine Prompt-Injection-Resistenz-Klausel**. 9. **Keine User-Rate-Limits, kein globales Concurrency-Cap**. 10. **Keine Tests**. Kein `devDependencies`-Eintrag für ein Test-Framework. 11. **Keine strukturierten Logs**. Nur `console.log`. Schwierig zu filtern/parsen. 12. **Keine `.env.example`** im Repo (zu verifizieren bei `ls`). 13. **Doppelte Schema-Quelle**: `src/db/schema.sql` und Inline-`SCHEMA_SQL` in `database.ts` können divergieren. 14. **Typing-Refresh auf 5 s**: Discord-Typing läuft ~10 s, 5 s ist okay aber unnötig häufig; 8 s reicht (§2). ### Was komplett fehlt - `/list-agents`, `/delete-agent` Commands. - Session-ID-Persistenz (`--resume`). - Streaming-Output. - Attachment-Handling. - PreToolUse-Hook / Audit-Log. - Profile-basiertes Workspace-Scaffolding (researcher/developer/writer/ops). - Per-Channel-Queue, globaler Concurrency-Semaphore. - Structured Logger (pino o. ä.). - Zod-Validation für Config und CLI-JSON-Output. - Tests (Unit und Integration). - `docs/cli-feature-probe.md` — Test-Script, das das tatsächliche Verhalten der installierten `claude` CLI gegen unsere Annahmen verifiziert. --- ## 2. Technische Entscheidungen (locked in) ### 2.1 Context-Persistenz: Hybrid (`--resume` primär, DB-Rehydrierung Fallback) **Was**: Session-ID pro Workspace in der DB persistieren. Runner ruft `claude -p "" --resume --output-format json --bare` auf. Wenn keine Session-ID existiert oder `--resume` fehlschlägt, wird aus den letzten N DB-Einträgen ein Bootstrap-Prompt gebaut und eine neue Session gestartet. Die neue `session_id` aus dem JSON-Output wird **nach jedem Run** in der DB aktualisiert (defensiv gegen Fork-Verhalten älterer CLI-Versionen). **Warum**: Prompt Caching von Anthropic greift nur bei stabilen Präfixen innerhalb einer Session. Neue Session pro Nachricht = 0 % Cache-Hit, jede CLAUDE.md wird neu tokenisiert. Mit `--resume` erwartete Einsparung 60–90 % Input-Tokens (siehe ai-engineer-analyse.md §1). Bestätigt über `cli-feature-answers.md` Frage 1+2: `-p` + `--resume` ist offiziell dokumentiert, `session_id` liegt **top-level** im JSON-Output. **Wie**: - Neue DB-Spalten in `workspaces`: `claude_session_id TEXT`, `session_updated_at TEXT`. Migration additiv (`ALTER TABLE ... ADD COLUMN`). - Runner-API bekommt einen `SessionStore`-Typ injiziert (damit der Runner testbar bleibt). - Session-ID lesen: `JSON.parse(stdout).session_id` (top-level). Nach **jedem** Run per `store.set(ws.id, json.session_id)` persistieren, auch wenn die ID identisch zur vorigen ist — fängt Fork-Verhalten (`--fork-session`, CLI-Upgrades, `cleanupPeriodDays`-Expiry) transparent auf. - Fallback bei `--resume` Fehler: Error-String auf `session .* not found` oder ähnlich matchen → `claude_session_id = NULL` setzen und erneut starten ohne `--resume` (optional mit `--continue` als zweiter Fallback-Stufe). - Die `conversations`-Tabelle bleibt Source of Truth für Rehydrierung nach Bot-Neustart oder vollständigem Session-Verlust. - Metriken: `usage`-Block aus JSON-Output parsen (`cache_read_input_tokens`, `cache_creation_input_tokens`, `input_tokens`, `output_tokens`) und pro Run loggen. Exakter Pfad (`result.usage.*` oder top-level `usage.*`) wird empirisch via Probe-Script verifiziert. ```ts // src/agent/runner.ts (Skizze) export interface SessionStore { get(workspaceId: number): string | null; set(workspaceId: number, sessionId: string): void; clear(workspaceId: number): void; } async function runOnce(ws: Workspace, msg: string, store: SessionStore): Promise { const sid = store.get(ws.id); const args = [ "-p", msg, "--output-format", "json", "--bare", "--append-system-prompt-file", path.join(ws.workspace_path, "CLAUDE.md"), ]; if (sid) args.push("--resume", sid); const res = await spawnClaude(args, ws.workspace_path); if (res.ok) { // Immer updaten — fängt Fork/Cleanup-Edge-Cases ab if (res.sessionId) store.set(ws.id, res.sessionId); return res; } if (sid && isResumeFailure(res.error)) { store.clear(ws.id); return runOnce(ws, msg, store); // einmaliger Retry ohne --resume } throw res.error; } ``` **Status**: Freigegeben. `cli-feature-answers.md` Frage 1+2 bestätigt, dass `-p "..." --resume --output-format json` offiziell dokumentiert ist und `session_id` als Top-Level-Feld im JSON-Output liegt. Kein Blocker mehr. Das Probe-Script verifiziert lediglich noch die genauen Pfade des `usage`-Blocks und bleibt als Regression-Check im Repo. ### 2.2 Prozessmodell: Spawn pro Nachricht (beibehalten) **Was**: Ein Bot-Prozess, ein `claude`-Kindprozess pro Nachricht, terminiert nach Antwort. **Warum**: Persistente Prozesse sind im MVP nicht vertretbar (150–300 MB RSS pro idle Agent, Supervisor nötig, Windows-Signal-Semantik fragil, interaktiver Modus ist TTY-first). Spawn-pro-Nachricht ist einfacher, isolierter, crash-resistenter. Der Kaltstart-Overhead wird durch `--resume` + Prompt Caching teilweise kompensiert. **Wie**: Bestehender `runner.ts`-Ansatz, aber mit `shell: false`, sanitizedEnv, Windows-Pfad-Auflösung, SessionStore, und abstrahiertem Event-Stream-Return-Type, damit Phase 5 (Streaming) ohne API-Bruch nachgerüstet werden kann. **Windows-Spawn-Entscheidung (locked in)**: `cross-spawn` als Dependency. Grund: `claude` wird auf Windows als `claude.cmd`-Wrapper installiert (`%APPDATA%\npm\claude.cmd`), nicht als `.exe`. Seit Node 20.12 (CVE-2024-27980) blockt Node `child_process.spawn("foo.cmd", args, { shell: false })` bzw. verlangt explizites Arg-Quoting — `spawn("claude", args)` scheitert außerdem, weil Node ohne `shell: true` keine `.cmd`-Extension im PATH-Lookup anhängt. `cross-spawn` abstrahiert beides (löst `.cmd`-Wrapper auf, quoted Args korrekt, identisches Verhalten Linux/Mac/Windows). **Trade-off**: Eine Mini-Dependency (~30 kB, keine transitiven Deps) gegen ~50 Zeilen Windows-Workaround-Code (where-lookup + `.cmd`-Wrapper-Parsing + direkter `node cli.js`-Spawn). Die Eigenbau-Variante bleibt als Fallback dokumentiert, falls `cross-spawn` jemals ausfällt, aber ist für den MVP nicht die erste Wahl — der Pragmatik-Gewinn wiegt die Dependency auf. **Zusätzlich**: Beim Bot-Start einmalig `CLAUDE_PATH` aus `.env` lesen; falls leer, `where claude` (Windows) / `which claude` (Unix) automatisch ausführen, Ergebnis in-memory cachen. Fehlermeldung mit klarem Hinweis auf `CLAUDE_PATH`, falls nicht auffindbar. `windowsHide: true`, `stdio: ["pipe", "pipe", "pipe"]`, `child.stdout.setEncoding("utf8")`. ### 2.3 Per-Channel-Queue **Was**: FIFO-Queue pro `channel_id`, die alle Runs im gleichen Channel serialisiert. Parallele Channels bleiben parallel. **Warum**: Siehe §1.3 Status Quo — ohne Queue laufen race-anfällige parallele Runs im selben Workspace. **Wie**: In-Memory `Map>` im Router. Neue Tasks werden an die letzte Promise der Queue angehängt. Kein externer Queue-Service, kein Redis — MVP-tauglich. Zusätzlich: globaler Semaphore (max. 4 parallele `claude`-Prozesse) über ein simples Counting-Primitive. ```ts // src/runtime/channel-queue.ts export class ChannelQueue { private queues = new Map>(); enqueue(channelId: string, task: () => Promise): Promise { const prev = this.queues.get(channelId) ?? Promise.resolve(); const next = prev.catch(() => undefined).then(task); this.queues.set(channelId, next); next.finally(() => { if (this.queues.get(channelId) === next) this.queues.delete(channelId); }); return next; } } ``` ### 2.4 Message-Splitting: Eigenbau, codeblock-aware **Was**: Eigene Funktion `splitForDiscord(text, limit=1900)`, die Fences (``` ```) erkennt, offene Blöcke beim Split schließt und im nächsten Chunk wieder öffnet. Bei Ausgaben >8 k Zeichen: stattdessen als `.md`-Attachment senden. **Warum**: Keine externe Library erfüllt das 1:1 (die üblichen "split-to-discord"-Pakete sind unmaintained oder fehlerhaft bei Fences). Eigenbau ist ~60 Zeilen und testbar. Limit 1900 statt 2000 lässt Puffer für Prefix `(1/3)`. **Wie**: Siehe Algorithmus-Skizze in `docs/ai-engineer-analyse.md` §2 "2000-Zeichen-Splitting". Ablegen unter `src/discord/split.ts` mit Unit-Tests. ### 2.5 Workspace-Isolation: Permissions + PreToolUse-Hook **Was**: `.claude/settings.json` bekommt ein Default-Deny für Pfade außerhalb `./` und für gefährliche Bash-Muster. Zusätzlich PreToolUse-Hook, der jeden Tool-Call mit absolutem Pfad gegen `workspace_abs_path` matched und abbricht, wenn außerhalb. **Warum**: Permissions allein reichen nicht, weil `Read(./**)` sich mit `cd ..` via Bash aushebeln lässt. Der Hook ist die zweite Verteidigungslinie und gleichzeitig der Audit-Log-Hook. **Wie**: - `src/agent/templates/settings.default.json` als Template mit Platzhalter `{{workspace_abs}}`. - Schema-Struktur wie in `cli-feature-answers.md` Frage 3 bestätigt: Permissions unter `permissions.allow` / `permissions.ask` / `permissions.deny`, mit zusätzlich `permissions.defaultMode` und `permissions.additionalDirectories`. Hooks als dreistufige Struktur: `hooks.[].matcher` + `hooks.[].hooks[].{type, command, if, timeout}`. - Hook-Script als Node-File: `src/agent/hooks/guard-tool.ts`, das beim Workspace-Create nach `./.claude/hooks/guard-tool.cjs` gerendert wird (compile-to-CJS damit der Hook ohne Build-Step ausführbar ist). - Template-Beispiel (das tatsächliche Schema): ```json { "$schema": "https://json.schemastore.org/claude-code-settings.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" }, "hooks": { "PreToolUse": [ { "matcher": "Bash|Read|Write|Edit", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-tool.cjs\"", "timeout": 10 } ] } ] }, "cleanupPeriodDays": 90 } ``` - **Headless-Gotcha**: Im `-p`-Modus bricht ein nicht durch `permissions.allow` gedecktes Tool den Run ab (kein interaktiver Prompt). Das `allow`-Set muss die für das Profil nötigen Tools explizit enthalten — dokumentieren im README und in jedem Profile-Template. ### 2.6 Secrets/Env-Handling **Was**: `sanitizedEnv()` Funktion, die Secret-artige Variablen entfernt, bevor der `claude`-Prozess gestartet wird. Whitelist statt Blacklist wo möglich. **Warum**: Siehe §4.5 der Analyse — sonst kann jeder Agent per `echo $DISCORD_BOT_TOKEN` den Bot-Token exfiltrieren. **Wie**: - Neue Datei: `src/runtime/env.ts`, exportiert `sanitizedEnv(extra?: Record)`. - Entfernt: `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_SECRET`, `DISCORD_PUBLIC_KEY`, alles matching `/^(DISCLAW_SECRET_|SECRET_|TOKEN_|API_KEY_?)/i`, plus eine konfigurierbare Block-Liste in `disclaw.yaml`. - Fügt hinzu: `CI=true`, `DISCLAW_AGENT=1`, `DISCLAW_AGENT_NAME=`. ### 2.9 Workspace-Location: Standardmäßig außerhalb des DisClaw-Repos **Was**: Workspaces liegen per Default unter `~/.disclaw/workspaces//` (Linux/Mac) bzw. `%USERPROFILE%\.disclaw\workspaces\\` (Windows). Der Pfad ist über `disclaw.yaml` → `workspaces_root` konfigurierbar, der Default ist aber **immer absolut außerhalb des DisClaw-Repo-Roots**. Das bisherige `./workspaces/` im Repo wird nicht mehr als Default verwendet (nur noch als Opt-in für Development/Tests — dann mit Warning-Log beim Bot-Start). **Warum**: Siehe `cli-feature-answers.md` Frage 8. Claude Code walkt beim Start parent directories hoch und **konkateniert** alle gefundenen `CLAUDE.md`-Dateien in den System-Prompt. Liegt der Agent-Workspace unter `C:\Code\side\disclaw\workspaces\\`, dann sieht Claude zwangsläufig auch: 1. `C:\Code\side\disclaw\CLAUDE.md` (DisClaw-Projekt-Identität) 2. `~/.claude/CLAUDE.md` (User-Scope, developer-spezifisch) 3. ggf. weitere Parent-Scope-Dateien. Das kontaminiert die Agent-Identität: Der Agent "weiß" plötzlich, dass er Teil von DisClaw ist, und erbt den Ton/Anweisungen der Developer-eigenen User-CLAUDE.md. Weder reproduzierbar noch sicher. **Zwei Verteidigungslinien, kombiniert**: 1. **Strukturell**: Workspaces liegen außerhalb des DisClaw-Repos. Dadurch existiert oberhalb keine Projekt-CLAUDE.md. `~/.claude/CLAUDE.md` bleibt ein Rest-Leak-Vektor. 2. **Explizit**: Alle CLI-Calls nutzen `--bare` (skippt CLAUDE.md-Walk-Up, Hooks-Discovery, Skills, Plugins, MCP, Auto-Memory komplett) **und** die Agent-Identität wird über `--append-system-prompt-file /CLAUDE.md` explizit injiziert. Das ist reproduzierbar und zukunftssicher — laut Doku wird `--bare` ohnehin der zukünftige Default für `-p`. **Konsequenz für bestehenden Code**: - `disclaw.yaml`-Default: `workspaces_root: "~/.disclaw/workspaces"` (Tilde-Expansion + `path.resolve` gegen `os.homedir()`). - `src/config/loader.ts`: Tilde-Expansion implementieren, absolute Pfade per `path.resolve` normalisieren. - `src/agent/runner.ts`: Immer `--bare` + `--append-system-prompt-file` an den Call anhängen. - `src/commands/new-agent.ts`: Workspace unter dem neuen Root anlegen, Channel-Name und DB-Einträge identisch wie vorher. - **Migrationshinweis für bestehende Workspaces**: Ein `/migrate-workspaces`-Command (Phase 6+) oder manuelle Instruktion im README: Bestehende `./workspaces//`-Ordner nach `~/.disclaw/workspaces//` verschieben, DB-Spalte `workspace_path` per `UPDATE` anpassen, Channel-Mapping bleibt. Im MVP reicht ein dokumentierter manueller Schritt. - **Dev-Opt-in**: Wenn `workspaces_root` innerhalb des DisClaw-Repos liegt (erkannt via `path.relative(repoRoot, wsRoot)` startet nicht mit `..`), loggt der Bot beim Start eine Warnung und setzt zusätzlich `claudeMdExcludes` im gerenderten `.claude/settings.json` mit absoluten Pfaden für die DisClaw-Projekt-CLAUDE.md und alle `.claude/rules/**`-Globs des Parents. **Quelle / Status**: Bestätigt via `cli-feature-answers.md` Frage 8 mit Zitat aus `code.claude.com/docs/en/memory`. Konfidenz: hoch. ### 2.7 Logging und Error-Handling **Was**: `pino` als strukturierter JSON-Logger. Child-Logger pro Komponente (`bot`, `router`, `runner`, `commands.new-agent`). Error-Handler-Strategie: Fehler werden geloggt (mit Stack), der Discord-Channel bekommt eine **sanitized** Nachricht (keine absoluten Pfade, keine Stack Traces). **Warum**: `console.log` ist bei mehreren Agenten und paralleler Ausführung unlesbar. Pino ist schnell, JSON-first, und Standard in Node-Server-Apps. Pretty-Printing nur in dev via `pino-pretty`. **Wie**: - Neue Datei: `src/runtime/logger.ts`, exportiert `rootLogger` und `childLogger(component: string)`. - Sanitizer: `src/runtime/sanitize.ts`, entfernt absolute Pfade (Regex auf `ROOT_DIR`), Tokens, Home-Pfad. ### 2.8 Test-Strategie: Vitest, Unit-first **Was**: `vitest` als Test-Runner. Unit-Tests für reine Funktionen (splitForDiscord, sanitizedEnv, parseClaudeJsonOutput, agent-name-validation, channel-queue). Integration-Tests mit gemockten Discord- und CLI-Clients. Keine E2E-Tests im MVP (zu fragil gegen echte Discord-API). **Warum**: Vitest hat minimale Config, läuft nativ mit TypeScript via esbuild, und ist für diese Projektgröße angemessener als Jest. **Wie**: - `tests/unit/` für reine Funktionen. - `tests/integration/` für Router + Runner mit Mock-Spawner, Mock-DiscordChannel. - Mock-Strategie für `claude` CLI: Ein Fake-Spawner der ein `Fake-Claude`-Script aufruft (`tests/fixtures/fake-claude.mjs`), das deterministische JSON-Antworten ausgibt. So testet man die echten spawn-Pfade, ohne von einer echten Claude-Installation abhängig zu sein. - Discord: `@discordjs/core`-Mocks oder manuell via Interface-Abstraktion — siehe §5 "Discord-Adapter". --- ## 3. Tools und Libraries ### Wird verwendet | Paket | Version | Zweck | Begründung | |---|---|---|---| | `discord.js` | ^14.16.0 | Discord-Client | Schon drin, de-facto Standard. | | `better-sqlite3` | ^11.0.0 | SQLite-DB | Schon drin, synchrone API passt zum Handler-Modell. | | `dotenv` | ^16.4.0 | `.env`-Loader | Schon drin. | | `yaml` | ^2.6.0 | YAML-Parser für `disclaw.yaml` und `agent.yaml` | Schon drin. | | `zod` | ^3.23 | Runtime-Validierung für Config, CLI-JSON-Output, Slash-Command-Args | Sichert Grenzen zwischen untrusted Input und Code. Klein, ohne Runtime-Abhängigkeiten. | | `cross-spawn` | ^7 | Plattformübergreifender `child_process.spawn`-Ersatz | Löst Windows-`.cmd`-Wrapper korrekt auf (`claude.cmd`), quoted Windows-Args sicher, umgeht CVE-2024-27980 (Node 20.12+). Ohne Shell, also kein Command-Injection-Risiko. ~30 kB, keine transitiven Deps. Siehe §2.2. | | `@types/cross-spawn` | ^6 | Typdefinitionen | devDep. | | `pino` | ^9 | Strukturiertes Logging | Schnell, JSON-first, Child-Logger. | | `pino-pretty` | ^11 | Dev-only Pretty-Printer | Nur in `devDependencies`. | | `vitest` | ^2 | Test-Runner | Native TS, schnell, minimal Config. | | `@types/node` | ^22 | Typdefinitionen | Schon drin. | | `typescript` | ^5.7 | Compiler | Schon drin. | ### Wird explizit NICHT verwendet - **Kein ORM (Prisma/Drizzle)**: Overkill für 2 Tabellen. `better-sqlite3`-Prepared-Statements sind schnell und typisiert genug via manuelle Interfaces. - **Kein Express/Fastify**: Kein HTTP-Endpoint im MVP. Wenn später ein Web-Dashboard dazu kommt, Fastify. - **Kein Redis/BullMQ**: Single-Process-Queue reicht. Externe Queues erst bei Multi-Instance-Deployment. - **Kein `discord-markdown` o. ä.**: Splitting ist simpel genug für Eigenbau und genauer kontrollierbar. - **Kein Jest**: Vitest ist leichter, schneller, direkt TS-fähig. - **Kein `@anthropic-ai/sdk` oder `claude-agent-sdk`**: ADR-001 sagt CLI-only. - **Kein `winston`**: Pino ist schneller und hat bessere Child-Logger-Semantik. --- ## 4. Phasenplan Jede Phase ist in sich lauffähig. Abhängigkeiten sind explizit genannt. Nummerierung ist nicht chronologisch, sondern logisch. ### Phase 0 — Härtung (P0, blockiert jede Live-Nutzung) **Ziele**: Sicherheitslücken schließen, bevor irgendein echter Discord-Server angeschlossen wird. **Tasks**: 1. `src/agent/runner.ts`: `shell: true` entfernen; stattdessen `cross-spawn` verwenden (siehe §2.2). Neue Helper-Datei `src/runtime/resolve-claude.ts`: liest `CLAUDE_PATH` aus `.env` oder führt einmalig `where claude`/`which claude` aus, cached das Ergebnis in-memory, wirft eine klare Fehlermeldung bei Nichtfund. 2. `src/runtime/env.ts` (neu): `sanitizedEnv()` wie in §2.6. In `runner.ts` statt `...process.env` verwenden. 3. **Workspace-Root-Umstellung (§2.9)**: `disclaw.yaml`-Default auf `~/.disclaw/workspaces` umstellen. `src/config/loader.ts`: Tilde-Expansion + `path.resolve`. `src/commands/new-agent.ts`: Workspace unter dem neuen Root anlegen. Warning-Log, wenn der konfigurierte Root innerhalb des DisClaw-Repos liegt. Manueller Migrationshinweis für bestehende `./workspaces//`-Ordner im README. 4. `src/commands/new-agent.ts`: Name-Regex behalten, zusätzlich Path-Containment-Check: ```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"); ``` 5. `src/agent/identity.ts`: `.claude/settings.json`-Template härten — Schema wie in §2.5 (mit `permissions.allow/ask/deny/defaultMode` und dreistufigem `hooks`-Block). Deny für `../**`, Deny-Patterns für gefährliche Bash-Kommandos. 6. `src/agent/identity.ts`: `CLAUDE.md`-Template bekommt die Prompt-Injection-Resistenz-Klausel (Punkt 5 aus §3 der Analyse). 7. `src/agent/runner.ts`: CLI-Call immer mit `--bare --append-system-prompt-file /CLAUDE.md` aufbauen (siehe §2.9), damit Parent-`CLAUDE.md`-Leaks strukturell ausgeschlossen sind — auch für Dev-Mode-Workspaces innerhalb des Repos. 8. `tests/unit/sanitize-env.test.ts`, `tests/unit/path-traversal.test.ts`, `tests/unit/resolve-claude.test.ts` (mocked), `tests/unit/workspace-root-resolve.test.ts` (Tilde-Expansion, Warning bei Repo-internem Pfad). **Definition of Done**: - Kein `shell: true` mehr im Code; Spawning läuft über `cross-spawn` mit resolvtem Claude-Pfad. - Ein Test demonstriert, dass `DISCORD_BOT_TOKEN` nicht im Child-Env landet. - Ein Test demonstriert, dass `name: "../etc"` als Slash-Command-Input abgewiesen wird. - `.claude/settings.json` in einem frisch erstellten Workspace enthält die Deny-Liste und das korrekte `hooks.PreToolUse[].matcher`-Layout. - `CLAUDE.md` enthält den Prompt-Injection-Resistenz-Satz. - Default-`workspaces_root` zeigt auf `~/.disclaw/workspaces`, Tilde wird korrekt expandiert, Warning bei repo-internem Pfad. - CLI-Calls enthalten `--bare` und `--append-system-prompt-file /CLAUDE.md`; ein Test (mit Fake-Claude) verifiziert, dass kein Parent-CLAUDE.md-Leak auftritt. **Abhängigkeiten**: Keine. Erste Phase. ### Phase 1 — Solides MVP **Ziele**: Ein Agent antwortet zuverlässig auf Discord, mehrfache Messages im selben Channel werden serialisiert, Fehler sind verständlich, Logs sind strukturiert, Grundstruktur ist testbar. **Tasks**: 1. `src/runtime/logger.ts` (neu): Pino-Setup, `rootLogger`, `childLogger(component)`. In `index.ts`, `bot.ts`, `router.ts`, `runner.ts`, `commands/new-agent.ts` `console.*` ersetzen. 2. `src/runtime/sanitize.ts` (neu): `sanitizeForDiscord(text, rootDir)` — entfernt Abspfade. 3. `src/runtime/channel-queue.ts` (neu): `ChannelQueue`-Klasse wie in §2.3. In `router.ts` integrieren. 4. `src/runtime/concurrency.ts` (neu): `Semaphore(max: number)`-Klasse. Globaler Cap aus `disclaw.yaml` → `max_concurrent_agents: 4`. 5. `src/discord/split.ts` (neu): `splitForDiscord()` wie in §2.4. `router.ts` verwendet ihn. 6. `src/agent/runner.ts`: API-Refactoring auf `RunResult = { text: string, sessionId?: string, toolCalls?: ToolCall[] }`. Return-Type vorbereiten für spätere Streaming-Unterstützung (`runAgent()` gibt heute `Promise`, später zusätzlich `runAgentStream()`). 7. `src/runtime/zod-schemas.ts` (neu): Zod-Schemas für `disclaw.yaml`, `agent.yaml`, Claude-CLI-JSON-Output. `parseClaudeJsonOutput()` validiert via Zod. 8. `src/config/loader.ts`: Validierung über Zod statt `as` Cast. 9. `src/db/database.ts`: Schema-Inline-String entfernen, stattdessen `src/db/schema.sql` laden (Single Source of Truth). 10. `tests/unit/split-for-discord.test.ts` (Fences, lange Zeilen, Fallback), `tests/unit/channel-queue.test.ts`, `tests/unit/parse-claude-output.test.ts`. 11. `tests/integration/router.test.ts`: mockt DB, DiscordChannel (Interface), Spawner — verifiziert das End-to-End-Verhalten bei parallelen Nachrichten (Reihenfolge, keine Race). 12. `.env.example` hinzufügen. 13. `package.json`: `test`-Script, `devDependencies` für vitest, pino-pretty, zod, pino. 14. `docs/cli-feature-probe.md` (neu): Manuelles Regression-Script, das die kritischen Annahmen aus `cli-feature-answers.md` (Session-ID top-level, `usage`-Block-Pfade, stream-json-Event-Typen, Session-Slug-Regel) gegen die lokal installierte CLI verifiziert und Resultate dokumentiert. Läuft als Sanity-Check bei CLI-Upgrades. **Definition of Done**: - Bot startet, erstellt Management-Channel, akzeptiert `/new-agent`, antwortet im Agent-Channel korrekt auf eine Nachricht. - Drei schnell hintereinander gesendete Nachrichten im gleichen Channel werden in exakter Reihenfolge verarbeitet (verifiziert im Integration-Test). - Log-Output ist strukturiertes JSON (mit `pino-pretty` in dev). - `npm test` läuft grün, coverage für `split`, `channel-queue`, `sanitize-env`, `parse-claude-output`, `router`. - Kein `console.log` mehr im Produktionscode (nur in Tests/Probe-Scripts erlaubt). **Abhängigkeiten**: Phase 0. ### Phase 2 — Context-Effizienz (`--resume`) — FREIGEGEBEN **Ziele**: Prompt-Cache-Hits, drastisch gesenkte Input-Tokens, saubere Historie-Semantik. **Status**: Freigegeben durch `cli-feature-answers.md` Frage 1+2. `-p` + `--resume` ist dokumentiert, `session_id` liegt top-level im JSON-Output, Session-ID bleibt beim Resume stabil (es sei denn `--fork-session`). **Tasks**: 1. DB-Migration: `ALTER TABLE workspaces ADD COLUMN claude_session_id TEXT; ALTER TABLE workspaces ADD COLUMN session_updated_at TEXT;`. In `schema.sql` pflegen, in `database.ts` als Migration-Step addieren (simpler `try { ALTER } catch {}`-Block reicht). 2. `src/db/database.ts`: Neue Methoden `setSession(workspaceId, sessionId)`, `getSession(workspaceId)`, `clearSession(workspaceId)`. `setSession` aktualisiert auch `session_updated_at` mit aktuellem ISO-Timestamp. 3. `src/agent/runner.ts`: Session-Flow wie in §2.1. **Nach jedem erfolgreichen Run** `store.set(ws.id, json.session_id)` aufrufen — auch wenn die ID gleich geblieben ist (defensive Update-after-run). Fallback-Pfad implementieren: Bei `--resume`-Fehler `store.clear` + Retry ohne `--resume` (optional `--continue` als Zwischenstufe). 4. Die History-Freitext-Injektion nur noch als Cold-Start-Rehydrierung verwenden (strukturierter als heute, mit klaren `` Blocks). 5. `tests/integration/session-resume.test.ts`: Fake-Claude gibt beim ersten Call eine `session_id` top-level zurück, der zweite Call muss diese via `--resume ` wieder verwenden. Fehlschlag simulieren → Fallback-Pfad wird getroffen. Edge-Case: Fake-Claude gibt beim zweiten Call eine **neue** `session_id` zurück (Fork-Szenario) → DB muss auf neue ID updaten. 6. Metrik-Logging: `usage`-Block aus JSON-Output parsen. Die exakten Pfade (`result.usage.cache_read_input_tokens` vs. top-level `usage.cache_read_input_tokens`) werden im Probe-Script empirisch verifiziert und dann hartcodiert. Log-Line: `log.info({ input_tokens, cache_read, cache_create, output_tokens }, "claude_run")`. 7. Optional (Nice-to-have): Neue Tabelle `agent_runs(workspace_id, ts, input_tokens, cache_read, cache_create, output_tokens, duration_ms)` für Pro-Agent-Metriken. **Definition of Done**: - Zweite Nachricht im gleichen Channel verwendet dieselbe `session_id` (verifiziert via Fake-Claude). - Session-ID überlebt Bot-Neustart. - Bei bewusstem Löschen der Session-Datei reagiert der Runner mit automatischem Fallback, speichert neue Session-ID, keine User-sichtbare Fehlermeldung. - `session_id` wird nach jedem Run in der DB geschrieben, auch wenn identisch. - Log-Line zeigt gecachte Tokens. **Abhängigkeiten**: Phase 0, Phase 1. ### Phase 3 — UX-Feinschliff **Ziele**: Discord-Ausgabe fühlt sich gut an. Lange Antworten, Codeblöcke, Attachments, Typing-Refresh. **Tasks**: 1. `src/router.ts`: Typing-Refresh-Intervall auf 8 s. Typing wird nur so lange ausgesendet, wie der `runAgent`-Call läuft. 2. `src/discord/send-response.ts` (neu): `sendResponse(channel, text, { asAttachmentAfter = 8000 })`. Wenn Text > Schwelle → als `.md`-Attachment senden. 3. Attachment-Support eingehend: `router.ts` prüft `message.attachments`. Download in `workspaces//.disclaw-inbox/-.ext`. Im Prompt referenziert. Nach erfolgreicher Antwort: TTL-Cleanup-Task. 4. Input-Limit: User-Nachricht > 4000 Zeichen → Reaction `⚠️` und Hinweis im Channel. 5. Reaction `👀` sobald Message in Queue landet; `✅` bei Erfolg; `❌` bei Fehler. Alle `.catch(() => {})`, nicht blockierend. 6. `tests/unit/send-response.test.ts`, `tests/unit/attachment-inbox.test.ts` (mit tmp-Dir). **Definition of Done**: - Lange Antworten werden korrekt gesplittet ohne kaputte Fences. - Sehr lange Antworten werden als `.md`-File gesendet. - Bilder aus Discord landen im Workspace und Claude kann sie via Read-Tool sehen. - Typing-Indikator bleibt die ganze Laufzeit sichtbar, ohne "pausen". **Abhängigkeiten**: Phase 1. ### Phase 4 — Skills und Permissions (Profile) **Ziele**: Agents sind pro Rolle sinnvoll voreingestellt. Hook-Guard ist aktiv. Audit-Log läuft. **Tasks**: 1. `src/agent/profiles/` (neu): Ordner mit `researcher.md`, `developer.md`, `writer.md`, `ops.md`, `sandboxed.md` als `CLAUDE.md`-Templates und `*.settings.json` als `.claude/settings.json`-Templates. Platzhalter `{{name}}`, `{{role}}`, `{{workspace_abs}}`. 2. `src/agent/profiles/index.ts`: `ProfileRegistry` — maps name → `{ claudeMd: Template, settings: Template, hooks: Template[] }`. 3. `src/commands/new-agent.ts`: Neuer Option-Parameter `profile` mit Choices. Default = `developer`. 4. `src/agent/identity.ts`: `setupAgentWorkspace()` nimmt `profile` und rendert Templates aus der Registry. 5. `src/agent/hooks/guard-tool.cjs` (generiert beim Create): Node-Script, das als PreToolUse-Hook jeden Tool-Call gegen den Workspace-Pfad prüft, außerhalb → exit 2 mit Block-Reason. Jeder Call wird zusätzlich in `workspaces//.disclaw-audit.jsonl` geschrieben. Event-Typen gemäß `cli-feature-answers.md` Frage 3: `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `Stop`. Wir nutzen primär `PreToolUse` mit `matcher: "Bash|Read|Write|Edit"`. 6. `.claude/settings.json`-Template referenziert den Hook im dreistufigen Layout (`hooks.PreToolUse[].hooks[].{type, command, timeout}`), siehe §2.5. 7. `tests/unit/profile-registry.test.ts`, `tests/integration/hook-guard.test.ts` (mit Fake-Claude, der einen bösartigen Tool-Call simuliert). **Definition of Done**: - `/new-agent name:r profile:researcher` erstellt einen Agenten mit read-only-Permissions. - Ein Hook-Test verifiziert, dass `Bash(cat ../../other/secret.txt)` abgewiesen und im Audit-Log protokolliert wird. - Alle fünf Profile sind vorhanden und passen zu ihrer §3-Tabelle der Analyse. **Abhängigkeiten**: Phase 0 (Settings-Template). Settings-Schema ist durch `cli-feature-answers.md` Frage 3 verifiziert. ### Phase 5 — Streaming **Ziele**: Antworten erscheinen inkrementell im Discord-Channel. **Tasks**: 1. `src/agent/runner.ts`: Zweiter Export `runAgentStream(): AsyncIterable`. Intern: `claude -p "" --output-format stream-json --verbose --include-partial-messages --bare --append-system-prompt-file /CLAUDE.md` (plus `--resume` wie in Phase 2). Die Flags `--verbose` und `--include-partial-messages` sind laut `cli-feature-answers.md` Frage 4 **zwingend**, sonst emittiert die CLI nur das Final-`result`-Event ohne Text-Deltas. 2. NDJSON-Parser: Zeilenweise `JSON.parse`. Dispatch nach `.type`: `system` (init/api_retry, skip), `assistant` (Text- und Tool-Use-Blocks im Native-API-Format), `user` (Tool-Results), `stream_event` (partial deltas — interessant ist `.event.delta.type === "text_delta"` mit `.event.delta.text`), `result` (Final-Event, enthält session_id und usage). Parser **defensiv** gegen unbekannte Typen (skip statt crash) — CLI-Updates können neue Subtypes einführen. 3. `src/discord/stream-to-discord.ts` (neu): Nimmt einen `AsyncIterable`, akkumuliert Text-Deltas, editiert die Bot-Message alle ~1200 ms (Discord-Rate-Limit), rolled auf neue Message bei >1900 Zeichen. Bei `result`-Event: finalen Stand commitet. 4. `router.ts`: Feature-Flag `streaming: true/false` aus `disclaw.yaml`. Fallback auf Bulk-Mode bei Fehlern. 5. `tests/integration/streaming.test.ts` mit Fake-Claude, der ein NDJSON-Scripted-Log auf stdout schreibt (system → assistant → mehrere stream_events mit text_deltas → result). **Definition of Done**: - Streaming ist per Flag aktivierbar und funktioniert End-to-End. - Edits respektieren Discord-Rate-Limits (min. 1200 ms zwischen Edits). - Bei Fehler im Stream wird die Message sauber finalisiert. **Abhängigkeiten**: Phase 1 (Runner-API vorbereitet). Phase 2 (Session-Resume bleibt auch im Stream-Modus aktiv). ### Phase 6+ — Zukunft Klar: nicht jetzt planen, aber Pfade offenhalten. - **`/list-agents`, `/delete-agent`, `/agent-config`**: Command-Handler-Interface aus §5 macht das zu einer 30-Minuten-Sache pro Command. `/delete-agent` muss Channel + Workspace + DB-Einträge + `~/.claude/projects//`-Session atomar löschen. - **User-Ratelimit**: Token-Bucket in `src/runtime/ratelimit.ts`, Map. - **Daily-Budget pro Agent**: DB-Tabelle `agent_usage(workspace_id, day, runs)`, Check vor jedem Run. - **Agent-zu-Agent**: Ein Agent kann eine Nachricht an einen anderen Agent-Channel adressieren → Router erkennt und delegiert. - **Docker-Isolation**: `DockerRunner` als zweite Implementierung des `AgentEngine`-Interface (§5). - **Web-Dashboard**: Fastify-Server, der `DisclawDatabase` teilt. - **Alternative AI-Engine**: Ein `ClaudeSdkEngine` gegen `claude-agent-sdk` für Nutzer ohne Claude-Pro-Abo. Gleiches `AgentEngine`-Interface. --- ## 5. Erweiterbarkeit (Fokus!) Der Code ist so zu strukturieren, dass neue Features ohne Core-Änderungen andocken. Kernmechanismen: ### 5.1 Schichtentrennung ``` src/ bot.ts Discord-Adapter (Event → Router) router.ts Routing + Queue + Sanitize (kennt Runner abstrakt) discord/ Alles Discord-nahe: split, send, stream-to-discord agent/ engine.ts AgentEngine-Interface (siehe 5.6) runner.ts ClaudeCliEngine: implements AgentEngine identity.ts Workspace-Create (delegiert an Profile) profiles/ Templates pro Profil hooks/ Pre/Post-Hook-Scripts commands/ index.ts CommandRegistry (siehe 5.2) new-agent.ts list-agents.ts (später) delete-agent.ts (später) runtime/ channel-queue.ts concurrency.ts env.ts logger.ts sanitize.ts ratelimit.ts (später) db/ database.ts Repository-Stil, keine Business-Logik schema.sql config/ loader.ts ``` Regel: `runtime/`, `db/`, `discord/` sind Leaf-Module, kennen sich nicht gegenseitig über die Grenzen hinweg. `agent/` kennt `runtime/` und `db/`-Typen, aber **nicht** `discord.js`. `router.ts` ist der einzige Ort, der Discord und Agent zusammenbringt. ### 5.2 Plugin-Interface für Slash-Commands Neues Interface, das neue Commands an einer Stelle registriert: ```ts // src/commands/types.ts import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js"; import { DisclawDatabase } from "../db/database"; import { DisclawConfig } from "../config/loader"; export interface CommandContext { db: DisclawDatabase; config: DisclawConfig; managementChannelId: string | null; } export interface DisclawCommand { definition: SlashCommandBuilder; handle(interaction: ChatInputCommandInteraction, ctx: CommandContext): Promise; } ``` ```ts // src/commands/index.ts import { newAgentCommand } from "./new-agent"; // import { listAgentsCommand } from "./list-agents"; export const commands: DisclawCommand[] = [ newAgentCommand, // listAgentsCommand, ]; ``` `bot.ts` registriert alle Commands in einer Schleife und dispatched via `commandName`-Lookup. Neuer Command = neue Datei + ein Array-Eintrag. Keine Änderung an `bot.ts` nötig. ### 5.3 Profile-Templates als Registry ```ts // src/agent/profiles/index.ts export interface ProfileTemplate { name: string; description: string; renderClaudeMd(ctx: { name: string; role: string }): string; renderSettings(ctx: { workspaceAbs: string; name: string }): Record; hooks?: HookDefinition[]; } export const profiles: Record = { developer: developerProfile, researcher: researcherProfile, writer: writerProfile, ops: opsProfile, sandboxed: sandboxedProfile, }; ``` Ein neuer Profil-Typ (z. B. `data-scientist`) ist eine neue Datei + ein Registry-Eintrag. `identity.ts` muss nicht angefasst werden. ### 5.4 Hook-System (Pre/Post Message, Pre/Post Spawn) ```ts // src/agent/hooks.ts export type HookEvent = | { type: "preMessage"; workspace: Workspace; message: string } | { type: "postMessage"; workspace: Workspace; response: string } | { type: "preSpawn"; workspace: Workspace; args: string[] } | { type: "postSpawn"; workspace: Workspace; result: RunResult }; export type Hook = (event: HookEvent) => Promise; export class HookBus { private hooks: Hook[] = []; register(h: Hook) { this.hooks.push(h); } async emit(e: HookEvent): Promise<{ cancel: boolean; reason?: string }> { ... } } ``` `router.ts` und `runner.ts` emittieren an einen global injizierten `HookBus`. Beispiel-Hooks: Audit-Logger, Ratelimit, Daily-Budget, Metriken. Alle außerhalb des Core-Codes, einfach pluggable. **Wichtig**: Die `.claude/`-Hooks (PreToolUse etc.) sind ein anderer Mechanismus — die werden von Claude Code selbst gefeuert. Das HookBus hier ist DisClaw-intern. ### 5.5 MCP-Server pro Workspace (vorbereitet, später aktiviert) Das Profile-Template kann optional eine `mcpServers`-Sektion in die `.claude/settings.json` rendern. Die Liste der verfügbaren MCPs ist in `src/agent/mcp/registry.ts` zentral gepflegt. Aktivierung pro Profil deklarativ: ```ts developerProfile.mcpServers = ["git"]; opsProfile.mcpServers = ["docker", "k8s"]; ``` Der Renderer fügt die korrekten Stanzas in `settings.json` ein. Kein Code-Change im Runner nötig. ### 5.6 Abstraktion der AI-Engine ```ts // src/agent/engine.ts export interface AgentEngine { run(opts: RunOptions): Promise; runStream?(opts: RunOptions): AsyncIterable; } ``` `ClaudeCliEngine` ist heute die einzige Implementierung. Später könnten `ClaudeSdkEngine` (direkte API) oder `DockerClaudeCliEngine` (CLI in Container) als Drop-in-Alternativen dazu kommen. Der Router kennt nur das Interface. ### 5.7 Discord-Adapter-Abstraktion (für Testbarkeit) Alle Discord-Interaktionen im Router gehen über ein kleines Interface `ChannelSink`: ```ts export interface ChannelSink { send(text: string): Promise<{ id: string }>; edit(messageId: string, text: string): Promise; sendTyping(): Promise; sendFile(name: string, content: Buffer): Promise; react(messageId: string, emoji: string): Promise; } ``` In Produktion wraps das einen `TextChannel`. In Tests ist es ein Mock. So sind Router-Tests komplett ohne `discord.js` möglich. --- ## 6. Offene Fragen an den AI Engineer Die ursprünglichen 10 Fragen wurden am 2026-04-08 vom AI Engineer beantwortet. Siehe `docs/cli-feature-answers.md` für Volltext, Quellen und Konfidenzangaben. **Verbleibende Rest-Unsicherheiten** (alle empirisch via Probe-Script zu bestätigen, kein Blocker für Phase-Start): 1. **Exakte JSON-Pfade des `usage`-Blocks** (`result.usage.cache_read_input_tokens` vs. top-level `usage.cache_read_input_tokens`): Die Existenz der Felder ist durch das API-Layer garantiert, aber der Output-Shape der CLI wird in Phase 2 per Probe-Script empirisch verifiziert und dann im Parser hartcodiert. 2. **`stream-json` Event-Schema-Stabilität**: Die groben Typen (`system`, `assistant`, `user`, `stream_event`, `result`) sind dokumentiert, aber Subtypes und Feld-Layouts können zwischen CLI-Versionen driften. Mitigation: Parser defensiv (skip unknown), Regression-Test im Probe-Script. 3. **Session-Slug-Transformation auf Linux/Mac**: Windows-Slug-Regel (`C:\Code\side\disclaw` → `C--Code-side-disclaw`) ist empirisch bestätigt, Linux-Regel (`/home/nick/...` → `-home-nick-...`) ist aus Analogie abgeleitet. Muss auf Linux-Runner geprüft werden, sobald `/delete-agent` implementiert wird (Phase 6+). 4. **Multimodal-Passthrough ohne Tool-Round-Trip**: `@./path/image.png` im Prompt funktioniert via Read-Tool + Vision; ein direkter Vision-Input-Block ohne Round-Trip existiert im CLI-`-p`-Modus nicht und würde eine Migration zum Agent SDK erfordern. Scope-Entscheidung: bleibt Phase 6+. --- ## 7. Risiken und Mitigation | Risiko | Wahrscheinlichkeit | Impact | Mitigation | |---|---|---|---| | `--resume` funktioniert nicht wie erwartet in der installierten CLI | Niedrig | Hoch (Phase 2 blockiert) | Per `cli-feature-answers.md` Frage 1+2 offiziell dokumentiert und bestätigt. Probe-Script als Regression-Check bleibt. Fallback-Pfad (DB-Rehydrierung + `--continue`) bleibt als Degradation verfügbar. | | CLAUDE.md-Walk-Up kontaminiert Agenten-Identität (Parent-Projekt-CLAUDE.md oder `~/.claude/CLAUDE.md` leaked in System-Prompt) | Mittel (strukturell gegeben, wenn nicht mitigiert) | Hoch (Reproduzierbarkeit, Sicherheit, Identitäts-Leak) | §2.9: Workspaces außerhalb des DisClaw-Repos (`~/.disclaw/workspaces/`) **plus** `--bare` + `--append-system-prompt-file` bei jedem Call. Integration-Test mit Fake-Claude, der den empfangenen System-Prompt echoed, und Assertion, dass ausschließlich die Workspace-`CLAUDE.md` drin ist. Für Dev-Mode-Workspaces innerhalb des Repos zusätzlich `claudeMdExcludes` im Settings. | | Windows-Spawn schlägt auf frischer Node-20.12+-Installation fehl (`claude.cmd`, CVE-2024-27980) | Mittel (default), niedrig (nach Phase 0) | Hoch (DisClaw startet auf Windows gar nicht) | §2.2: `cross-spawn` als Dependency, `where claude`-Lookup mit absolutem Pfad-Cache, klare Fehlermeldung wenn nicht auffindbar. CI-Test auf Windows-Runner (GitHub Actions `windows-latest`) verifiziert Smoke-Test-Spawn. | | Command Injection bleibt unentdeckt in einem Codepfad | Niedrig (nach Phase 0) | Kritisch | `shell: false` als Lint-Regel im CI. Code-Review-Checkliste. Integration-Test mit bösartigem Input. | | Prompt-Injection bricht Permissions aus | Mittel | Hoch | Defense in depth: CLAUDE.md-Klausel, settings.json deny, PreToolUse-Hook, Audit-Log. Keine davon allein reicht. | | `.claude/settings.json`-Schema ändert sich zwischen CLI-Versionen | Mittel | Mittel | Schema-Version beim Workspace-Create detektieren (`claude --version` parsen), Template pro Release pflegen. Warnung bei unbekannter Version. | | Parallele Nachrichten im selben Channel korrumpieren Workspace-Dateien | Hoch (ohne Queue) | Hoch | Per-Channel-Queue in Phase 1. Integration-Test. | | Secret-Exfiltration via `echo $DISCORD_BOT_TOKEN` | Hoch (aktuell) | Kritisch | `sanitizedEnv()` in Phase 0. Test, der den resultierenden Env-Set prüft. | | Discord-Rate-Limits bei Streaming | Niedrig | Mittel | Min. 1200 ms zwischen Edits, neue Message bei Überlauf. discord.js 429-Handling nutzen. | | Speicher-Leaks bei Long-Running-Bot-Prozess | Niedrig | Mittel | Pino-Logs + `process.memoryUsage()` alle 5 min als Debug-Log. `channelQueues`-Map räumt sich selbst auf. | | Zwei DisClaw-Instanzen gegen dieselbe DB/Workspace | Niedrig | Hoch | Lock-File im Workspaces-Root beim Start. Fehlschlag, wenn Lock existiert und PID noch läuft. | | Benutzer editieren `CLAUDE.md` manuell mit Injection | Mittel | Niedrig (der Benutzer "schadet" sich selbst) | Dokumentiert, kein technischer Fix. | | PreToolUse-Hook-Script ist fehlerhaft und blockt legitime Calls | Mittel | Mittel | Hook schreibt Audit-Log auch bei Block; `/reset-permissions`-Command später; im MVP manuelles Editieren der Settings. | | Test-Suite wird träge, wenn echte `claude` CLI involviert ist | Mittel | Niedrig | Unit-Tests nutzen Fake-Spawner. Echte-CLI-Tests nur in `tests/probe/` und manuell. |