| name | building-scwibble |
| description | Guides implementation work on Scwibble — a Chrome extension + Hono-on-Cloudflare-Workers backend that rewrites text to evade AI detectors and match a user's style fingerprint. Use when scaffolding the Worker, adding/editing endpoints, building the extension content script or panel, working on the style-fingerprint loop, or touching the rewrite prompt pipeline. |
Building Scwibble
Scwibble is a humanizer + AI-detector-evasion Chrome extension. Backend is Hono on Cloudflare Workers, AI is the Vercel AI SDK with Cloudflare Workers AI (default @cf/qwen/qwen3-30b-a3b-fp8, escalation @cf/moonshotai/kimi-k2.6) and OpenRouter as fallback. Per-user state lives in Durable Objects, rewrite cache in KV, raw fingerprint samples in R2.
Always read .agents/PLAN.md and .agents/INFO.md before making non-trivial changes — they define scope, modes, and cost constraints.
Hard rules
- Do not introduce paid features, auth, or accounts. MVP is anonymous-UUID only.
- Default model is
@cf/qwen/qwen3-30b-a3b-fp8. Do not silently switch to a Llama or a more expensive Kimi call without an explicit cost reason. Kimi K2.6 is reserved for one-time fingerprint extraction and (optional) hard-rewrite escalation.
- Slider is 1–5, not 1–10 and not 3 discrete modes. Keep it that way.
- Never ship rewrite output containing em-dashes (
—). Force a deterministic post-process replace step in the Worker after model output.
- CORS must allow
https://mail.google.com + chrome-extension://*. Preflight OPTIONS returns 204 with headers. Test before claiming an endpoint works.
- Shadow DOM panel only. No external stylesheets, no
<script> injection, no eval. Inline styles via constructable stylesheets. Gmail CSP will silently break anything else.
- Selection rewrite + full-draft rewrite are both day-one. Never ship one without the other.
- Per-hunk accept/reject diff via
diff-match-patch is non-negotiable for the panel UX.
- Universal textbox coverage is day-one via a single non-invasive floating dot anchored to the focused
<textarea> / text <input> / plain [contenteditable]. Never stamp a button onto every input. One dot, page-wide, re-anchors on focusin.
- Framework-rich editors (ProseMirror, Lexical, Slate, Monaco, CodeMirror) get the dot + a read-only preview + copy button in the panel. Full write-back is v1.1 — do not ship fragile write code to MVP for these.
- Do not add Quillbot action modes (paraphrase, shorten, expand, simplify, formalize) to MVP. That is v1.2.
Stack quick reference
| Concern | Tool |
|---|
| Backend framework | Hono (bun create hono → cloudflare-workers template) |
| AI orchestration | Vercel AI SDK (ai + workers-ai-provider + @openrouter/ai-sdk-provider) |
| Per-user state | Durable Object class UserState |
| Rewrite cache | KV namespace REWRITE_CACHE, key = sha256(text + slider + useMe + profileVersion + voicePrompt) |
| Sample storage | R2 bucket SAMPLES, key = samples/{anonId}/{sampleId}.txt |
| Diff library | diff-match-patch (client-side only, ~20KB) |
| Extension framework | WXT (Vite-based, MV3, Bun-friendly) |
| Local runtime | Bun |
Workflow when adding a backend endpoint
- Define the route in
src/routes/ (one file per resource).
- Validate input with
zod.
- Apply CORS via shared middleware — never per-route.
- If the endpoint touches per-user state, route through the user's DO via
env.USER.idFromName(anonId).
- If the endpoint streams, return a
Response wrapping streamText().toDataStreamResponse() (AI SDK helper).
- Add KV cache check before model call when the operation is deterministic on inputs.
- Update
PLAN.md §2.3 route table.
Workflow when changing the rewrite prompt
- Edit the prompt template in one place only (a
lib/prompt.ts).
- Bump an internal
PROMPT_VERSION constant; include it in the KV cache key so old cached results are invalidated.
- Re-test all 5 slider levels with a fixed input fixture in
tests/.
- Run a manual em-dash regression — output must contain zero
—.
- Update §4.2 / §4.3 of
PLAN.md if behavior changed.
Workflow when changing the fingerprint pipeline
- The structured stats live in
lib/fingerprint/stats.ts — pure deterministic functions, easy to unit test.
- The LLM summary call lives in
lib/fingerprint/summary.ts — uses Kimi K2.6.
- Any change to stat shape or summary format must bump
profileVersion for all existing users on read (lazy migration in DO).
- Always merge new samples by token-weighted average; never overwrite.
- Update §3 of
PLAN.md.
Workflow when changing the extension panel
- The panel is a React tree mounted inside a Shadow DOM root attached to
document.body.
- All styles must come from constructable stylesheets — no external CSS.
- Test on Gmail compose and plain
<textarea> before claiming done.
- Verify selector cascade still hits at least one Gmail selector — log via the telemetry beacon.
- Re-verify the diff renderer with a known fixture (input → expected hunks).
Cost discipline
- Each rewrite at default model: ~$0.000087 (400 in / 200 out).
- Each fingerprint extraction: ~$0.0016 (one-time per sample).
- Parallel prefetch of 3 slider anchors: ~$0.00026 per first-click.
- Stay under $10/month at 100 DAU. If a change inflates per-call cost, flag it explicitly in the PR description.
Verification checklist before declaring work done