Skip to content

Latest commit

 

History

History
182 lines (127 loc) · 15.3 KB

File metadata and controls

182 lines (127 loc) · 15.3 KB
description Use Bun instead of Node.js, npm, pnpm, or vite.
globs *.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json
alwaysApply false

Default to using Bun instead of Node.js.

  • Use bun <file> instead of node <file> or ts-node <file>
  • Use bun test instead of jest or vitest
  • Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild
  • Use bun install instead of npm install or yarn install or pnpm install
  • Use bun run <script> instead of npm run <script> or yarn run <script> or pnpm run <script>
  • Use bunx <package> <command> instead of npx <package> <command>
  • Bun automatically loads .env, so don't use dotenv.

APIs

  • Bun.serve() supports WebSockets, HTTPS, and routes. Don't use express.
  • bun:sqlite for SQLite. Don't use better-sqlite3.
  • Bun.redis for Redis. Don't use ioredis.
  • Bun.sql for Postgres. Don't use pg or postgres.js.
  • WebSocket is built-in. Don't use ws.
  • Prefer Bun.file over node:fs's readFile/writeFile
  • Bun.$ls instead of execa.

Testing

Use bun test to run tests. Tests are in tests/.

import { test, expect } from "bun:test";

test("hello world", () => {
  expect(1).toBe(1);
});

Documentation Rules

When making any change, update docs before committing. Map changes to affected sections:

Change type Update in README Update in CLAUDE.md
New/removed CLI command ## CLI Commands
New/changed config field ### 2. Configure workspaces table
New/removed source adapter File structure, test coverage, ## Extending Patchwork Modules table
New/removed module or util ## File Structure Modules table
Issue status flow change ### Issue Status Flow ### Issue Status Flow
New notification channel ## Extending Patchwork Modules table
Test count change ### Test Coverage
Critical impl detail ### Critical Implementation Details

If a change doesn't fit the table, ask: "Would a new developer need to know this to use or extend the project?" → README. "Would Claude need this to implement correctly?" → CLAUDE.md.

Patchwork Architecture

Project Purpose

Autonomous SWE agent that automates developer work. Pulls tasks from project management tools (Asana, Linear, Jira) and error trackers (Sentry), triages them with Claude Code, implements solutions in isolated git worktrees, and provides an interactive React web dashboard for approval. Telegram notifications are optional. Bug fixing via Sentry is one feature — the core value is automating any development task from any source.

Workspaces

Projects are grouped into workspaces (one per client/org). Each workspace optionally has a Telegram bot and a list of projects. Auth tokens live inside each source config in SQLite. This allows managing multiple clients with separate credentials from a single daemon instance. All config is managed through the web dashboard at http://localhost:7842/settings.

Key Modules

Module Purpose
src/cli.ts CLI entrypoint — patchwork start, patchwork stop, patchwork doctor, patchwork update, patchwork version, patchwork help. Flags: --config, --log-level. Version read from package.json at runtime. doctor checks Bun/claude/gh/config/repo paths. update checks npm registry and self-updates via bun install -g
src/constants.ts Shared constants: FAILURE_HINTS, CallbackAction, CallbackData — used by telegram, dashboard, and daemon
src/dashboard.ts DashboardServer — Hono + Bun.serve() at localhost:7842, serves React dashboard from dist/, WebSocket /ws for live updates, SPA fallback
src/api/router.ts Hono app combining all API routes
src/api/issues.ts Issue CRUD + action endpoints (GET /api/issues, POST /api/issues/:id/action, GET /api/status)
src/api/config.ts Config CRUD, setup wizard, connection testing endpoints
src/api/middleware.ts CORS middleware
src/daemon.ts Main orchestrator — per-workspace runtimes (optional Telegram + adapters), poll loop, queue, executeAction(), config from SQLite with live reload
src/config.ts Config types, JSON loading (legacy migration only), validation helpers: findProjectConfig(), allProjects()
src/db.ts bun:sqlite WAL mode. IssueDB for issue tracking, ConfigDB for config storage (settings, workspaces, projects, sources, telegram)
src/queue.ts Severity-based priority queue, concurrency control, dedup, pause/resume
src/sources/types.ts NormalizedIssue, SourceAdapter interface, Severity type
src/sources/sentry.ts Sentry source: poll errors (excludes CSP + noise), fetch stacktraces, resolve on accept
src/sources/asana.ts Asana source: poll tasks, fetch comments, severity from tags/custom fields
src/sources/linear.ts Linear source: GraphQL API, poll issues with team/project/status/assignee/label/priority filters, transition to Done on accept
src/sources/jira.ts Jira source: REST API v3, JQL builder for simple mode, raw JQL for advanced mode, transition on accept
src/worker/claude.ts runClaudeText / runClaudeJson — spawn claude -p with --output-format stream-json, model selection, session resume. Streams stdout line-by-line with onEvent callback for real-time events. parseStreamJson() extracts text + session ID from NDJSON events
src/worker/git.ts Worktree create/remove, push, PR via gh, diff helpers
src/worker/triage.ts Claude triage with read-only tools → { fixable, confidence, plan, failed, failureReason, sessionId }. Uses sonnet model. reviseTriage() resumes existing session with user feedback → revised plan, no codebase re-exploration
src/worker/fix.ts Claude implements task in worktree, auto-commits tests first then code, then runs lintCommand (if set) and commits formatting fixups, validation. Returns failureReason on failure. Uses opus model. Resumes triage session when available
src/notifications/telegram.ts Optional. HTML messages with forum topics (each issue = own topic), inline keyboards, long-poll for callbacks
src/utils/html.ts esc() HTML escape helper for Telegram HTML parse mode
src/utils/logger.ts ANSI colored stderr logger, optional file output
dashboard/src/main.tsx React entry point with QueryClient, BrowserRouter
dashboard/src/router.tsx React Router config with setup wizard guard (redirects to /setup if no config)
dashboard/src/api.ts Typed API client for all endpoints
dashboard/src/pages/IssuesPage.tsx Two-panel issue list + detail with filters, WebSocket real-time updates
dashboard/src/pages/SettingsPage.tsx General settings + workspace/project/integration management
dashboard/src/pages/SetupPage.tsx First-run setup wizard (5 steps)
dashboard/src/hooks/useWebSocket.ts WebSocket hook with auto-reconnect
dashboard/vite.config.ts Vite config with API proxy for development

Issue Status Flow

new → triaging → pending_confirmation [user clicks "Start"] → triaged_actionable → working → pending_approval → accepted/discarded/skipped
               → triaged_not_actionable → [user clicks "Retry"] → new (re-triages from scratch)
               → failed (on triage error, work error, or daemon restart recovery) → [user clicks "Retry"] → new or pending_confirmation

When triage: false in config, issues skip triage and go straight to pending_confirmation.

Confirmation shows the triage result (or a simple "ready to start" card) with Start / Skip buttons. Work does not start until the user confirms. After the work completes, the approval flow (Accept / Discard) works as before.

Queue pauses when an issue reaches pending_confirmation (waiting for "Start") or pending_approval (waiting for "Accept"/"Discard"), and resumes after any callback action.

Critical Implementation Details

  1. Claude nesting: worker/claude.ts deletes the CLAUDECODE env var before spawning claude CLI to avoid "cannot nest" errors.

  2. Claude stream-json output: Both runClaudeText and runClaudeJson use --output-format stream-json with --verbose (required when combined with -p/--print). Stdout is streamed line-by-line; optional onEvent callback fires tool_use/text/result events in real-time. parseStreamJson() parses collected lines to extract session ID, result text, token counts (message_start.message.usage.input_tokens, message_delta.usage.output_tokens), and cost_usd from the result event. Token/cost stats are shown in Telegram messages as ⏱ 45s · 🪙 1.2K in / 456 out · 💰 $0.023.

  3. Worktree paths: {repoParent}/.patchwork-worktrees/fix-{source}-{sourceId} — sibling dir to the repo, not inside it.

  4. Branch naming: patchwork/fix-{source}-{sourceId}

  5. Auto-commit strategy: fix.ts auto-commits tests first (separate commit), then code changes. Falls back to single commit if no test files changed. If lintCommand is set, it runs after Claude's commits: formatting fixups are committed as style: apply lint/format fixes, and non-zero exit fails the task with fix:lint_failed.

  6. Telegram is optional: WorkspaceConfig.telegram is optional. When absent, WorkspaceRuntime.telegram is null and all Telegram calls are guarded with ws.telegram?.. The web dashboard provides full interactive control (action buttons, streaming progress) regardless of Telegram. Workspaces with the same bot token still share one polling connection to avoid Telegram 409 conflicts.

  7. Telegram callback data: Format is action:source:sourceId — colons in sourceId are handled by splitting on first two colons only. Actions: fix (start fix), accept (accept fix), discard (discard fix), skip (skip issue).

  8. Telegram forum topics: Each issue gets its own forum topic via createForumTopic (telegram_thread_id stores the message_thread_id). All pipeline updates are sent within the topic via message_thread_id. Topics are closed via closeForumTopic on completion. Severity-based icon colors: critical=red, high=orange, medium=blue, low=green. telegram_message_id tracks the latest message with action buttons. Streaming progress edits are throttled to 3s. The Telegram group must have forum/topics mode enabled.

  9. Rate limiting: Both Sentry and Asana adapters retry on 429 with backoff. Sentry tracks remaining quota from response headers.

  10. Sentry noise filter: CSP (logger:csp), timeout, network, and CORS errors are filtered both at the API query level and client-side before entering the pipeline.

  11. Triage error handling: Triage timeouts (exit 143) and errors are marked as failed, not triaged_not_actionable. Can be retried.

  12. Config from SQLite: Config is stored in SQLite (settings, workspaces, projects, source_configs, telegram_configs tables). On first run, config.json is auto-imported into SQLite. All config changes are managed via the web dashboard. API config mutations trigger daemon.reloadFromDB() for live reload without restart.

  13. Dashboard-only mode: If no config exists in DB and no config.json, daemon starts the web dashboard only and auto-opens browser to /setup. After wizard completion, daemon loads config and starts polling.

  14. Queue pause/resume: Queue pauses on pending_approval, resumes after accept/discard/skip callback.

  15. Daemon shutdown: process.exit(0) after stop, 10s drain timeout. SIGINT/SIGTERM handled cleanly.

  16. PR descriptions: Structured markdown with Summary, Context, Approach, Changes, and Test Plan sections. The test plan is generated by Claude at the end of the fix prompt — a ## Test Plan section with bullet steps. Parsed from the fix result and stored in the test_plan DB column, then included in the PR body.

  17. Model selection: Triage uses --model sonnet (fast, cheap), fix uses --model opus (thorough). Hardcoded in daemon.ts.

  18. Triage feedback loop: When an issue is at pending_confirmation, users can submit feedback via the FeedbackBox component (⌘↵ to submit). POST /api/issues/:id/feedback calls reviseTriage() in src/worker/triage.ts, which resumes the existing triage session (--resume <session_id>) and sends the feedback as a new message. Claude can re-read files but skips full codebase exploration. Returns a revised { fixable, confidence, reason, plan }. Stream progress is broadcast via WebSocket. User feedback stored in user_feedback DB column.

  19. Session continuity: Triage captures session_id from Claude's stream-json output, stored in the session_id DB column. Fix stage passes --resume <session-id> so Claude resumes with all triage context (explored files, analysis) already loaded. When resuming, the fix prompt is shorter since context is preserved.

  20. Failure reason codes: Every failure stores a machine-readable failure_reason in the DB. Codes: triage:timeout, triage:claude_failed, fix:timeout, fix:no_changes, fix:tests_failed, fix:commit_failed, fix:claude_failed, worktree:create_failed, accept:push_failed, accept:pr_failed, pipeline:unexpected. Shown in dashboard failure cards with human-readable hints from FAILURE_HINTS.

  21. Issue logs: Triage writes raw Claude NDJSON to /tmp/patchwork-{id}-triage.ndjson; fix writes to /tmp/patchwork-{id}-fix.ndjson.

  22. Streaming progress: createStreamReporter in daemon.ts tracks tool_use + result events from the Claude stream. Broadcasts { type: "stream_progress", issueId, stage, tool, elapsed, toolCallCount, costUsd } to web dashboard via WebSocket. Also updates Telegram if available (throttled to 3s via editMessage).

  23. React web dashboard: Built with React + Tailwind CSS + Vite. Served by Hono from dist/ at localhost:7842. Features: issue list with filters (workspace/project/source/status), issue detail with triage/diff/actions, streaming progress, settings management, setup wizard. API routes via Hono (src/api/). WebSocket /ws for real-time issue_update and stream_progress events. Dev mode: bun run dev runs Vite on :5173 with proxy to API on :7842.

  24. Shared action execution: Daemon.executeAction(data: CallbackData) is the single entry point for all actions (from both Telegram callbacks and dashboard REST). Returns { ok, error?, issue? }. handleCallback is now a thin Telegram wrapper calling executeAction.

  25. Build pipeline: bun run build runs Vite to build the React dashboard into dist/. bun run dev starts the daemon with --watch and Vite dev server concurrently. Production: bun run start builds then starts.

Auth Tokens

Auth tokens are stored in SQLite (in source_configs.config JSON and telegram_configs columns). On first run, tokens from config.json are auto-imported.

Data Directory

All runtime data lives in ~/.patchwork/:

  • config.json — legacy config (auto-imported into SQLite on first run)
  • sqlite.db / sqlite.db-wal / sqlite.db-shm — database
  • patchwork.pid — daemon PID file

Files That Should Never Be Committed

  • .patchwork-worktrees/ — temporary worktrees
  • patchwork.stdout.log / patchwork.stderr.log — service logs
  • dist/ — built dashboard output (generated by bun run build)