[eric] repo: untrack CLAUDE.md docs (root + per-package) + gitignore them as personal AI working notes, not contributor docs

This commit is contained in:
ciregenz
2026-06-23 22:32:54 -07:00
parent 0eb5a2edbe
commit 7e0c7c5de0
4 changed files with 2 additions and 178 deletions
+2
View File
@@ -47,6 +47,8 @@ openswarm-cloud
# leading slash so this doesn't accidentally ignore backend/apps/analytics/.
/analytics
.claude/
# CLAUDE.md docs (root + per-package) are personal AI working notes, not contributor docs.
CLAUDE.md
# Local-only operator helpers (never commit)
scripts/set-fly-*.sh
# Local-only background dev-team state/docs (personal, never commit)
-62
View File
@@ -1,62 +0,0 @@
# backend/CLAUDE.md
FastAPI orchestrator. Entry: `backend/main.py` (uvicorn `:8324`, REST `/api/*`, WS `/ws/*`, Swagger `/docs`). See root `CLAUDE.md` for repo-wide constraints.
## Coding precedences
Full precedences live in root [CLAUDE.md](../.claude/CLAUDE.md). Always: **understand the end goal before coding** (what does the user actually need?); **reuse before you write** (grep existing routes / SubApps / helpers, most needs already have one); ~300 LOC/file ceiling; downward-tree imports; comments only when necessary (the non-obvious WHY), one line each; **no em-dashes or en-dashes anywhere** (`—`, `–`); say IDK to the user when you don't know, then go find out; test after meaningful changes; weigh speed, efficiency, robustness, UX, and security on every change.
## Run / test
- Dev: `bash backend/run.sh` (creates `.venv/`, installs `requirements.txt`, runs uvicorn with `--reload`).
- Tests: `pip install -r requirements-dev.txt && pytest tests/`.
- `requirements-dev.txt` is deliberately kept out of `requirements.txt` so `pytest` etc. don't ship in the production DMG. Sync both files when adding deps that need to exist in either place.
## Layout
- `apps/agents/`: agent orchestration, WS manager, MCP plumbing.
- `providers/registry.py`: resolves primary + aux models across Anthropic, OpenAI, Google, OpenRouter, and custom OpenAI-compatible providers. **Always go through here; never hardcode a model ID.**
- `mcp_preflight.py`: vague-prompt classifier that surfaces the one-click MCP-connect modal.
- `mcp_meta_server.py`, `mcp_registry.py`: MCP discovery + registry.
- `9router_gpt5_patch.js`: patch loaded into 9router to translate OpenAI `max_tokens` semantics.
- `apps/nine_router.py`: supervises the 9router subprocess on `:20128`.
- `apps/subscription/router.py`: OAuth + Stripe callbacks for openswarm-pro signup.
- `apps/outputs/`: view renderer (HTML/JS/CSS iframes, sandboxed Python execution).
- `auth.py`: per-install bearer token. **Generated BEFORE the HTTP bind** so the Electron shell can read it from disk; don't reorder.
## MCP gate
- Dispatch flows through `_build_mcp_servers`. This is the only place MCP tools become reachable.
- `session.active_mcps` defaults to empty; the user opts in via `MCPSearch` + `MCPActivate` (HITL).
- New MCP-related code path? It must respect this gate. Don't add side channels.
- Suggestions surfaced to users must come from the vetted/default set, not the full upstream registry.
## Providers / models
- Primary model: per-session user choice, resolved by `providers.registry`.
- Aux model (preflight, classifier, summarizers): pick the **cheap tier of the user's configured provider**. Haiku for Anthropic, GPT-5-mini for OpenAI, Gemini Flash for Google, etc. Never hardcode Haiku.
## Dev vs production
What runs under `bash backend/run.sh` is not what ships in the DMG/EXE. Test the packaged build for any change touching the items below.
- **Python:** dev uses system Python 3 in `backend/.venv/` with `pip install -r requirements.txt`. Prod ships bundled standalone Python 3.13 with deps pre-installed; no venv at runtime.
- **Reload:** dev runs `uvicorn --reload` so module-level side effects re-fire on edit. Prod has no reload; module-level code runs exactly once at startup.
- **Imports / paths:** `__file__` and `Path(__file__).parent` resolve inside the packaged Python tree in prod. Avoid `cwd`-relative or working-dir-relative imports.
- **Pip / new deps:** add to `requirements.txt` (fully pinned). The packaged build snapshots deps at build time; runtime `pip install` is not available in prod.
- **Bearer token (`auth.py`):** generated before the HTTP bind so the Electron shell can read it from disk; this ordering is load-bearing in both dev AND prod. Don't reorder.
- **MCP bundles (`mcp-bundles/`):** vendored esbuild output. Production reads from the packaged tree; regenerate via the bundle script rather than editing.
## Common pitfalls
- New endpoint? Use a pydantic request/response model and `@typechecked`.
- Pinning matters; `requirements.txt` is fully pinned for reproducibility.
- Token middleware already scrubs bearer tokens from logs; don't re-add raw logging.
- MCP bundles in `mcp-bundles/` are esbuild output; regenerate via the bundle script rather than editing.
## Hardening precedences (learned the hard way)
- **Compaction must actually trim, not just mark.** `_build_history_prefix` honors `session.compacted_through_msg_id`; the auto path leaves the SDK session intact (preserves prompt cache, the ~70% aux-cost win), the manual `/compact` button sets `needs_fresh_session=True` because the user is opting in to the cache-loss tradeoff for a visible shrink. Never flatten to `User:/Assistant:` text and never throw away tool turns; the model loses fidelity if you do.
- **SSRF guard is async + multi-record.** `tools/ssrf_guard.py` uses `loop.getaddrinfo` (non-blocking, covers IPv4 + IPv6) and rejects if ANY resolved record is private. Loopback (`127/8`, `::1`) is intentionally ALLOWED because App Builder previews on `127.0.0.1:<random>`; the real desktop-app SSRF threat is cloud metadata (`169.254.169.254`) and corporate LAN, not localhost. Per-redirect re-validation via `safe_fetch`; never use `follow_redirects=True` directly in a fetch path that takes a user-supplied URL.
- **Inline-text attachments need a combined cap, not just per-file.** `prompt/attachments.py` tracks `text_total_chars` so a pile of `.txt` files can't silently blow the context window past the per-file 512KB. Picked 1.5M chars as a 1M-window-model-friendly default.
- **`ENABLE_TOOL_SEARCH=auto`, NOT `1`, on the 9Router/sub paths.** We briefly forced `1` (to save ~9K first-message tokens) and it 400'd the Claude subscription route: forcing `1` marks every tool `defer_loading=true`, and Anthropic rejects `defer_loading=true` + `cache_control` on the same tool (our prompt-cache flip sets `cache_control`). `auto` eager-loads tools when they fit the schema budget (no defer flag, no collision) and only defers when needed, which is exactly what we want; the bare default is what's unsafe (CLI's `tengu_defer_all_bn4` defers 16 tools with no way to load them). So: `auto` everywhere on these paths, never `1`.
-51
View File
@@ -1,51 +0,0 @@
# electron/CLAUDE.md
Electron 40.x (CastLabs DRM build) desktop shell + auto-updater via GitHub Releases. Entry: `main.js`. Version is in `package.json`. See root `CLAUDE.md` for repo-wide constraints.
## Coding precedences
Full precedences live in root [CLAUDE.md](../.claude/CLAUDE.md). Always: **understand the end goal before coding** (what does the user actually need?); **reuse before you write** (grep existing IPC handlers / helpers in `main.js`, most needs already have one); ~300 LOC/file ceiling; downward-tree imports; comments only when necessary (the non-obvious WHY), one line each; **no em-dashes or en-dashes anywhere** (`—`, `–`); say IDK to the user when you don't know, then go find out; test the packaged build path after meaningful changes (not just dev); weigh speed (startup time), efficiency (memory), robustness (auto-updater, OAuth windows), UX, and security (signed binaries, no plaintext secrets) on every change.
## Build / release
- Local build: `npm run build` produces `build-staging/` containing the frontend dist, backend bundle, standalone Python 3.13, and the 9router binary. Build artifacts are ephemeral; not git-tracked.
- macOS release: requires Apple ID, app-specific password, and team ID env vars. App is signed + notarized.
- Windows release: signed via Azure code signing in CI (`.github/workflows/release-windows.yml`); triggers on `v*` tags.
## Bundling
- Python 3.13 is bundled via python-build-standalone, so users do not need a system Python.
- 9router binary is pulled at build time by `scripts/fetch-router.sh` / `fetch-router.ps1`. The version pin (`0.3.60`) is load-bearing for cross-provider WebSearch; see root `CLAUDE.md`.
## Versioning
- Source of truth: `electron/package.json` `version`. Bump alongside any user-facing release; CI tags off it.
- Bump only when cutting a release; coordinate with the publish flow rather than landing version bumps speculatively.
## Dev vs production
The packaged DMG/EXE behaves differently from `bash run.sh` in ways that silently break code:
- **Paths:** `__dirname` and `app.getAppPath()` resolve inside an `asar` archive in production. Use `app.getPath('userData')` for writable storage; `process.resourcesPath` points to unpacked resources.
- **Python:** bundled standalone Python 3.13 lives under `process.resourcesPath/python/`, not the system Python. Spawn it explicitly; don't assume `python3` is on `PATH`.
- **9router:** the binary lives under `process.resourcesPath/9router/`, not downloaded at runtime. Spawn from the bundled path.
- **Backend startup:** in dev, `run.sh` launches uvicorn directly. In prod, `main.js` spawns the bundled Python + backend. Bearer token must be on disk before the HTTP bind so the shell can read it.
- **Auto-updater:** only fires in signed production builds. Staging/test builds must use a separate channel (via `electron-builder` `--config`) to avoid clobbering the stable feed.
- **Deep links (`openswarm://`):** registered via `app.setAsDefaultProtocolClient`. OAuth and Stripe return flows depend on this; test on a packaged build, not the dev shell.
- **Code signing:** macOS unsigned/un-notarized builds get Gatekeeper-blocked; Windows unsigned builds trigger SmartScreen. CI signs on `v*` tags only.
- **Platform splits:** `process.platform`, path separators, line endings, and macOS-only flows (notarization, dock icon, menu bar) require explicit handling for both targets.
For any change touching paths, subprocess spawning, IPC, deep links, or the auto-updater: build with `npm run build` and run the produced DMG or EXE before reporting done.
## Pitfalls
- `build-staging/` is regenerated on every build; never commit it.
- Auto-updater reads the latest release feed from GitHub; staging/test builds should use a separate channel to avoid pushing unsigned bits to users.
## Hardening precedences (learned the hard way)
- **Renderer origin (port) must be stable across launches.** `localStorage` is keyed by full origin including port, so `server.listen(0)` (OS-assigned random port) was wiping onboarding state every restart and re-triggering the tour. Pin to a preferred port (currently `4173`), fall back to OS-assigned only if held. Same rule applies to any future renderer-side persisted state.
- **`webSecurity` stays on.** The file:// segfault was fixed by serving over `http://127.0.0.1:<port>`, NOT by disabling web security. Any future "let's just disable CSP" fix is wrong; find the underlying file:// quirk and route around it (loopback HTTP is the canonical workaround).
- **Mitigations must fail silently in BOTH directions.** When adding a workaround for an Electron / Chromium / platform-level crash you can't fix in our code, every option has two failure modes: (a) the mitigation doesn't help, and (b) the mitigation activates incorrectly and breaks something that was working. The latter is far worse than the former because it produces NEW failure modes we have to debug. Pick options that fail-quiet in both directions; reject options that fail-loud when they succeed (e.g. "hide window on sleep" successfully prevents a crash but every sleep-wake cycle now has a confused user).
- **Per-platform scope every workaround.** If the crash signature is OS-specific (e.g. macOS 26 NSEvent, Windows TSF), gate the workaround behind `process.platform === '<os>'`. Don't apply Mac dodges on Windows (or vice versa) "just to be consistent" — that's pure surface-area expansion for zero crash-fix benefit, and risks introducing new platform-specific regressions on the OS that wasn't crashing.
- **Crash watchdogs need EVERY guard, not most of them.** A naive "process died → relaunch" loop kills more sessions than it saves because it fires on intentional Cmd+Q, auto-updater swaps, and startup crash loops. Mandatory guards: (1) platform check, (2) packaged-only check, (3) clean-quit lock written by `before-quit`, (4) updater-in-progress lock, (5) minimum parent uptime (rules out startup crash loops), (6) relaunch frequency cap (e.g. ≤3 relaunches per hour). See `electron/crash-watchdog.js` for the canonical implementation. If you're adding another supervisor process for a different reason, copy this guard pattern verbatim.
-65
View File
@@ -1,65 +0,0 @@
# frontend/CLAUDE.md
React 18 + TypeScript + webpack 5 + Redux. Entry: `src/app/Main.tsx`. Dev server on `:3000` proxies REST and WebSocket to backend on `:8324`. See root `CLAUDE.md` for repo-wide constraints.
## Coding precedences
Full precedences live in root [CLAUDE.md](../.claude/CLAUDE.md). Always: **understand the end goal before coding** (what does the user actually need?); **reuse before you write** (grep existing components / hooks / Redux slices, most needs already have one); ~300 LOC/file ceiling; downward-tree imports (`shared/` → `app/components/` → `pages/`); comments only when necessary (the non-obvious WHY), one line each; **no em-dashes or en-dashes anywhere** (`—`, `–`); say IDK to the user when you don't know, then go find out; manually exercise the UI after meaningful changes; weigh speed (no double renders), efficiency, robustness, UX (loading/error/animation states), and security on every change.
## Run
- Dev (full stack): `bash run.sh`.
- Dev (frontend only): `bash frontend/run.sh` (runs `npm install` then `npm run dev`).
- No JS/TS test runner is wired up. Changes must be manually exercised in the running app before merging.
## Key concepts
- **Spatial dashboard.** Agents are draggable nodes on a canvas; layout + selection state lives in Redux.
- **Settings draft persistence.** `AppSettings.dismissed_mcp_suggestions` is a map of MCP id → ISO timestamp; preserve this shape when modifying settings serialization.
- **Onboarding wizard** (`src/app/components/Onboarding/`). 8-step agentic cursor walkthrough. Cursor offsets, fit-to-view, AC popup timing, and group-meta dedup were each delicate to land; verify visually after touching this code. Note: steps 3/5/6 launch real agent sessions that hit the cloud's analytics ingest, so don't treat them as visual-only.
- **SignInDialog** (`src/app/components/overlays/SignInDialog.tsx`, opened from the Settings account card). Optional sign-in that captures `user_id` + email via Google OAuth or email magic link, hitting the cloud's `/api/auth/{google,email}/*`. Sign-in is never required to use the app.
- **Custom providers.** `AppSettings.custom_providers: CustomProvider[]` supports any OpenAI-compatible endpoint (e.g. LM Studio).
## Conventions
- TS only; no PropTypes.
- No eslint/prettier config; match nearby files.
- Onboarding-copy placeholders shaped like real API keys (`sk-ant-api03-…`) are already allowlisted in `.gitleaks.toml`. Reuse the existing placeholder rather than introducing new "example" tokens.
- MCP suggestion UI must surface only the vetted/default set; never expose the full upstream registry to users.
## Dev vs production
The dev server (`webpack-dev-server` on `:3000`) and the packaged DMG/EXE serve the app differently. Test in the packaged build for any change touching the items below.
- **Server:** dev uses webpack-dev-server with HMR and a `/api/*` + `/ws/*` proxy to `:8324`. Prod loads built static `dist/` from inside an Electron `asar` archive via `file://`.
- **API + WS:** in both modes, the backend lives at `localhost:8324` with bearer-token auth. Dev relies on the dev-server proxy; prod fetches direct. Don't bake in dev-only proxy assumptions.
- **Asset paths:** `public/` files are served at `/` in dev. In prod, asset URLs resolve relative to a `file://` document; prefer relative imports/URLs over absolute `/foo.png`.
- **Source maps + HMR:** dev only. Prod runs minified bundles; console errors land in the Terminal pane's `[FRONTEND]` lines.
When you touch routing, fetch wiring, asset loading, or WS plumbing, build with `npm run build` and run the packaged DMG/EXE to verify.
## UI/UX precedences (user-visible text and surfaces)
This app ships to non-developers. Anything a user sees has to read like a person wrote it, not like an error reached the screen.
- **Plain English, no jargon.** No "aux provider", "context window", "AsyncClient", "code 400". If a non-engineer wouldn't know the word, don't say it.
- **Never dump backend errors / stack traces / model JSON into a toast.** Log the raw error to console for devs; show the user a short, friendly ask + the next action they can take ("Try removing it, or pick a model with a bigger window in Settings."). The popup is for guidance, not debugging.
- **Compact, breathing room, single message.** No 8-line walls of text in a 300px-wide column. Layout: one short sentence + 1-2 inline action buttons. Buttons read as actions ("Shrink it", "Remove"), not labels ("Summarize instead").
- **Match the app's design tokens.** Use `c.bg.surface`, `c.border.medium`, `c.accent.primary` from `claudeTokens`. Avoid raw MUI `Alert variant="filled"` blocks of saturated yellow/red — they read as dev-mode warnings, not user dialogs.
- **No alarming colors for normal flow.** "This file is too big" is a routine choice, not a warning. Warning/error coloring is reserved for actual failures the user can't recover from.
- **Friendly without being cute.** Conversational, not chirpy. "Want me to shrink it down to a summary?" not "Whoops! That file is huge!".
- **Minimalist by default. Less is more.** One short message, one subtle animation, one verb. Do NOT add rotating progress messages, multi-line status text, percentage counters, or step-by-step explainers unless the user explicitly needs them. A pulsing dot + "Shrinking" beats a 4-message carousel + spinner + progress bar every time. The user knows what they clicked; we just need to confirm we're alive.
- **Animations are subtle.** Pulse, fade, soft scale (≤1.0× to 0.6×). No bounce, no flashing, no harsh blinking, no rotating spinners with multiple emoji. Easing: `ease-in-out`. Duration: 1-1.5s for ambient states (loading), 150-250ms for state changes (hover, mode flip).
- **Transient popups MUST fade in/out, not snap-cut.** Any element that appears or disappears in response to user action (oversize popup, error toast, recovery chip, send-block banner) must use MUI `Fade` with `timeout={{ enter: 200, exit: 220 }}` and `unmountOnExit`. Pattern: hold a `lastSnapshot` ref so the exit animation renders the same content it had a moment ago instead of going blank mid-fade. Snap-cuts feel anxious; 200ms fades feel calm.
- **Long waits get an honest hint AFTER 10s, not upfront.** If an operation usually finishes in 2s but occasionally takes 60s, don't lie by always showing "this may take a minute". Mount a delayed hint that fades in only after 10s of waiting — silent for fast cases, reassuring for slow ones. See `SlowHint` in `ChatInput/view/ChatInputOverlays.tsx`.
- **Don't expose absolute filesystem paths to users.** Tooltips, file chips, and labels should show only the file's basename (e.g. `llama2.pdf`), never the temp-dir path (`/var/folders/s7/.../self-swarm-uploads/llama2.pdf`). Users don't care where their file landed in temp, and a 200-char tooltip dangling over the chat input is ugly. If the user genuinely needs the path, expose it via a "copy path" action, not a hover tooltip.
- **State changes from one button must invalidate downstream estimates.** If a button claims to shrink/clear/reset something the next user action depends on, you have to invalidate the cached estimate too. Example: clicking "Compact memory" calls `/compact` server-side, but the renderer's `tokens.input` was a snapshot from the previous round-trip — leaving it stale makes the next send re-fire the same "over context window" banner, looking like the button did nothing. Always pair an action with the redux update that its UX promise implies.
## Pitfalls
- Direct LLM calls from the frontend bypass the backend's provider routing and MCP gate. Don't add them; route through `/api/*` instead.
- Webpack-dev-server hot reload occasionally loses WS state; full page reload after backend restarts.
- MUI `Menu`/`Popover`/`Modal` portals dropped over an Electron `<webview>` inside the zoom/pan canvas eat clicks intermittently. Webviews are a separate compositor layer that CSS `pointer-events` and z-index can't reliably beat. Don't float a menu over a webview: open it into empty canvas, use plain `position:fixed` JSX you control, or make it a direct action (right-click does the thing, no menu).
- Reset/clear handlers must clear local component state, not just the Redux slice. `clearSessionMessages` only wipes `session.messages`; AgentChat's `showResumeBubble`, `awaitingResponse`, and message queue are React state and survive, leaving a stale "thinking" bubble or Resume button. Clear both.
- Don't GC a session/draft on unmount without checking it isn't mid-launch. `launchAndSendFirstMessage` races against route-change unmount; deleting a draft that already has user messages orphans the backend session and reads as "everything got wiped" on reopen. Guard on message count.
- Don't write placeholder strings into fields the UI renders as real data. The Google Workspace pill showed "Google Workspace account" because the connected email fell back to `f"{tool.name} account"`; leave the field empty and let the UI's empty-state handle it.