1040 lines
35 KiB
Markdown
1040 lines
35 KiB
Markdown
# 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/<agent>/` (Linux/Mac) bzw.
|
||
`%USERPROFILE%\.disclaw\workspaces\<agent>\` (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/<agent>/`-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<string, string>): 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=<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 <workspace>/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", "<ws>/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:<x>` 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 `<workspaces_root>/.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` → `<disclaw>`, `home` → `~`, bekannte Token-Regexes → `<redacted>`.
|
||
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 `<ws>/.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:<name>` 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
|
||
`<ws>/.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.
|