diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..aaa0a20 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,25 @@ +{ + "permissions": { + "allow": [ + "Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", "Bash(git show:*)", + "Bash(git branch --list:*)", "Bash(git rev-parse:*)", "Bash(git add:*)", "Bash(git commit:*)", + "Bash(bun install --frozen-lockfile:*)", "Bash(bun test:*)", "Bash(bun run build:*)", + "Bash(bun run icons:*)", "Bash(bun dev:*)" + ], + "ask": [ + "Bash(git push:*)", "Bash(git merge:*)", "Bash(git rebase:*)", "Bash(git switch:*)", + "Bash(git restore:*)", "Bash(git commit --amend:*)", "Bash(rm:*)", + "Bash(bun add:*)", "Bash(bun remove:*)", "Bash(bun update:*)", "Bash(bun upgrade:*)", + "Bash(bun patch:*)", "Bash(bunx:*)", "Bash(bun x:*)", + "Bash(docker:*)", "Bash(pip install:*)", "Bash(python3 -m pip install:*)" + ], + "deny": [ + "Bash(git push --force:*)", "Bash(git push -f:*)", "Bash(git push --force-with-lease:*)", + "Bash(git reset --hard:*)", "Bash(git clean:*)", "Bash(git add -f:*)", "Bash(git add --force:*)", + "Bash(git commit --no-verify:*)", "Bash(git commit -n:*)", + "Bash(rm -r:*)", "Bash(rm -R:*)", "Bash(rm -rf:*)", "Bash(rm -fr:*)", "Bash(rm -Rf:*)", + "Bash(rm -fR:*)", "Bash(rm --recursive:*)", "Bash(sudo:*)", + "Read(./.env)", "Read(./.env.*)", "Read(./data/**)" + ] + } +} diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..5542eff --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,2 @@ +sandbox_mode = "workspace-write" +approval_policy = "on-request" diff --git a/.codex/rules/project.rules b/.codex/rules/project.rules new file mode 100644 index 0000000..2cae5c1 --- /dev/null +++ b/.codex/rules/project.rules @@ -0,0 +1,23 @@ +# Rendered from docs/PERMISSIONS.md — change both in one commit. +# No allow rules on purpose: in Codex `allow` means "run outside the sandbox". + +# Deny +prefix_rule(pattern = ["git", "push", ["--force", "-f", "--force-with-lease"]], decision = "forbidden", + justification = "Force-push rewrites shared history.") +prefix_rule(pattern = ["git", "reset", "--hard"], decision = "forbidden", justification = "Discards work.") +prefix_rule(pattern = ["git", "clean"], decision = "forbidden", justification = "Deletes untracked files.") +prefix_rule(pattern = ["git", "add", ["-f", "--force"]], decision = "forbidden", + justification = "Stages ignored files, e.g. secrets.") +prefix_rule(pattern = ["git", "commit", ["--no-verify", "-n"]], decision = "forbidden", justification = "Skips hooks.") +prefix_rule(pattern = ["rm", ["-r", "-R", "-rf", "-fr", "-Rf", "-fR", "--recursive"]], decision = "forbidden", + justification = "Recursive delete.") +prefix_rule(pattern = ["sudo"], decision = "forbidden", justification = "No privilege escalation.") +# Ask +prefix_rule(pattern = ["git", ["push", "merge", "rebase", "switch", "restore"]], decision = "prompt") +prefix_rule(pattern = ["git", "commit", "--amend"], decision = "prompt") +prefix_rule(pattern = ["rm"], decision = "prompt") +prefix_rule(pattern = ["bun", ["add", "remove", "update", "upgrade", "patch", "x"]], decision = "prompt") +prefix_rule(pattern = ["bunx"], decision = "prompt") +prefix_rule(pattern = ["docker"], decision = "prompt") +prefix_rule(pattern = ["pip", "install"], decision = "prompt") +prefix_rule(pattern = ["python3", "-m", "pip", "install"], decision = "prompt") diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..c676954 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,20 @@ +{ + "tools": { + "allowed": [ + "run_shell_command(git status)", "run_shell_command(git diff)", "run_shell_command(git log)", + "run_shell_command(git show)", "run_shell_command(git branch --list)", + "run_shell_command(git rev-parse)", "run_shell_command(git add)", "run_shell_command(git commit)", + "run_shell_command(bun install --frozen-lockfile)", "run_shell_command(bun test)", + "run_shell_command(bun run build)", "run_shell_command(bun run icons)", "run_shell_command(bun dev)" + ], + "exclude": [ + "run_shell_command(git push --force)", "run_shell_command(git push -f)", + "run_shell_command(git push --force-with-lease)", "run_shell_command(git reset --hard)", + "run_shell_command(git clean)", "run_shell_command(git add -f)", "run_shell_command(git add --force)", + "run_shell_command(git commit --no-verify)", "run_shell_command(git commit -n)", + "run_shell_command(rm -r)", "run_shell_command(rm -R)", "run_shell_command(rm -rf)", + "run_shell_command(rm -fr)", "run_shell_command(rm -Rf)", "run_shell_command(rm -fR)", + "run_shell_command(rm --recursive)", "run_shell_command(sudo)" + ] + } +} diff --git a/.gitignore b/.gitignore index f12457f..3afd280 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,8 @@ __pycache__/ # Finder (MacOS) folder config .DS_Store + +# agent-local files (D-036) +CLAUDE.local.md +.claude/settings.local.json +.claude/worktrees/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..13abf3f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,109 @@ +# whisper_web + +Browser-first Whisper speech-to-text PWA. Bun 1.3 (runtime, bundler, test runner), React 19, TypeScript, +`bun:sqlite`, Passport.js; optional Python faster-whisper sidecar (`server-py/`) for the server engine. + +`AGENTS.md` is the single source of truth for agents here. `CLAUDE.md`, `GEMINI.md`, and `docs/CLAUDE.md` +only point here. Deeper context on demand: `docs/Architecture.md`, `docs/Decisions.md` (`D-###` ids +cited throughout the code), `docs/WorkBreakdown.md` (milestones + backlog), `PRODUCT.md`, `DESIGN.md`. + +## Commands + +Install: `bun install --frozen-lockfile` +Dev: `bun dev` +Test one file: `bun test tests/export.test.ts` | One case: `bun test tests/models.test.ts -t "dtype"` +Test (fast, ~6 s): `bun test --path-ignore-patterns='tests/live-real-model.test.ts'` +Test all: `bun test` — ~17 min: `tests/live-real-model.test.ts` downloads and runs real Whisper +weights whenever huggingface.co is reachable. Run it only when touching the live pipeline or decoding. +Currently 1 known failure there (`en_alien` timeout — see WorkBreakdown "Noticed, not fixed"). +Build: `bun run build` (rewrites `dist/`) +Opt-in real sidecar test (needs `pip install -r server-py/requirements.txt`): `FW_INTEGRATION=1 bun test tests/transcribe.test.ts` +Lint / typecheck: none configured (`typescript` is not installed). TODO(maintainer): confirm whether one is wanted. + +Browser-only behaviour (offline PWA, mic, WebGPU) has no automated coverage and there is no browser in +the dev environment: follow `tests/offline.md`, `tests/live.md`, `tests/server-engine.md` and say +plainly when a manual check is still pending — never claim it was verified. + +CI (`.gitlab-ci.yml`) only builds Docker images — nothing else runs your tests. + +## Rules + +- **Bun only.** No node/npm/npx/yarn/pnpm, vite/webpack, jest/vitest, express, `ws`, better-sqlite3, + dotenv. Use `Bun.serve()` routes + HTML imports, `bun:sqlite`, built-in `WebSocket`, `Bun.file`, + `Bun.$`. Bun API docs: `node_modules/bun-types/docs/`. +- **Two engines, two libraries (D-013).** Browser inference = Transformers.js, inside + `src/worker/inference.worker.ts` only — never on the main thread. Server inference = faster-whisper + via `server-py/sidecar.py` — never Transformers.js on the server. +- **`@huggingface/transformers` is patched and a devDependency on purpose.** It is pinned by + `patches/@huggingface%2Ftransformers@4.2.0.patch` (D-027/D-028) and kept out of runtime deps so the + Docker image stays small (D-031). Upgrading or moving it means redoing the patch: ask first. +- **No cross-origin subresources.** Every response carries COOP/COEP `require-corp` (D-023, + `src/index.ts`); a CDN script/style/font will be blocked. Self-host it (onnxruntime is served from `/ort/`). +- **Selecting a model never downloads it (D-020).** Only a transcription start or the explicit Download + button posts `load` to the worker. +- **Server modules take dependencies by injection** (`createTranscribeHandlers(deps)` pattern) so tests + run against `tests/fixtures/fake_sidecar.py`; validate `language`/`task` with + `parseLanguageAndTask` before anything reaches the sidecar (D-032). Every stateful server module has a test file. +- **Errors go through `classifyError` in `src/lib/errors.ts`** (pattern-matched, D-030) — don't add + error class hierarchies. +- **Configuration is runtime env only (D-015)** — no build-time flags baked into the image. New + variables go in README's env table. +- Anonymous users must keep every feature except the server engine; browser-engine audio never leaves + the device (Architecture §5 invariants). + +## Workflow + +Required skills: `superpowers`, `impeccable`. Check at session start; install if missing. +Specs → `docs/specs/NNN-slug.md` (permanent). Plans → `docs/plans/NNN-slug.md` while in progress; +`git rm` them before merging — the spec is the record. (Overrides the superpowers default paths.) + +Anything beyond a trivial fix: brainstorm → spec (sign-off) → plan (sign-off) → TDD → review. +TDD means failing test first, watch it fail, minimal code, watch it pass, commit. +Tick plan checkboxes as you go so an interrupted session is resumable. +Trivial changes (typo, version bump, one-line config): say "trivial, skipping spec" and proceed. + +UI work: use impeccable — `craft` for new, `audit`/`polish` on existing, `harden` before shipping. +Design context: `PRODUCT.md`, `DESIGN.md`. + +## Architecture + +`src/index.ts` -> `Bun.serve` entry: routes, `/api/*`, isolation headers, dev worker bundling, prod `dist/` serving +`src/App.tsx`, `src/components/` -> React UI +`src/lib/` -> pure client logic (audio, live segmenter, model catalog, engines, errors) — most unit tests target this +`src/worker/` -> inference worker + typed postMessage protocol (shared with UI) +`src/pwa/` -> hand-rolled service worker (D-007); `src/server/` -> auth, db, sidecar supervisor, live WS +`server-py/` -> faster-whisper sidecar (JSON-lines over stdio) + +Full picture: `docs/Architecture.md` — read it before your first change in a session. + +## Boundaries + +Never touch: `dist/` (build output), `src/pwa/icons/` (generated by `bun run icons`), `data/` (local SQLite). + +Ask first: new runtime dependencies · changing or regenerating the transformers patch · auth/session +code (`src/server/auth.ts`, `src/server/db.ts`) · `Dockerfile` / `.gitlab-ci.yml` · anything +destructive · work the approved plan does not cover. + +If the task turns out much larger than the plan assumed: stop, report, re-plan. + +## Docs duties + +Same commit as the code, not "later": +- `docs/Architecture.md` — when a module, boundary, or data flow changes. +- `docs/Decisions.md` — append at the **bottom** with the next `D-###` id; never rewrite an Accepted + entry, supersede it. Cite the id in code comments where the decision bites. +- `docs/WorkBreakdown.md` — tick items as they land; anything noticed and deliberately not fixed goes + under "Noticed, not fixed". No `TODO` comments in source. +- `docs/PERMISSIONS.md` — together with every tool permission file, in one commit. + +## Git + +Commit subjects: plain imperative sentence describing the change (e.g. "Show \"Loading model from +cache\" instead of \"Downloading\" when weights are already cached"). Small, each leaving tests green. +TODO(maintainer): confirm branch naming and whether work lands directly on `main`. + +## Done means verified + +Before saying done: relevant tests run *in this session* and seen passing · `bun run build` succeeds +if `src/` or `scripts/` changed · matches the approved spec · docs duties complete · `git status` clean +and the diff is only what you intended. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..20275b8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,9 @@ +# CLAUDE.md + +**`AGENTS.md` is the single source of truth for this repository.** Read it now and follow it exactly. + +@AGENTS.md + +This file exists only so Claude Code finds the rules, and for Claude-specific configuration that +AGENTS.md cannot express. It contains no project rules of its own — add those to `AGENTS.md`. +If anything here ever conflicts with `AGENTS.md`, `AGENTS.md` wins. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..b820a2d --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,32 @@ +# Design + +Derived from the shipped `src/index.css` (single stylesheet, plain CSS, no framework). Describes what +exists, not an aspiration. Update in the same commit as visual changes. + +## Foundations + +- **Theme:** dark only. `:root` sets `color: #e8e8e8; background-color: #16181d` (also the PWA + `theme_color`). `color-scheme: light dark` is declared but there is no light palette. +- **Tokens (`:root`):** `--panel #1f232b` · `--border #343a46` · `--accent #7aa2f7` · `--muted #9aa3b2`. + Everything else is a hard-coded hex. +- **Semantic colours in use (hard-coded):** success/ready text `#9ad6ae` (bg `#1d2b23`, border `#3d6b4f`); + error text `#f2b8b5` (bg `#2a1c1e`, border `#8b3a3a`); live/recording `#e0605e`; raised surfaces + `#262b34` / `#232a38`. +- **Type:** `Inter, system-ui, …` stack — Inter is not loaded, so it renders in the system font unless + installed locally. Body line-height 1.5; `h1` 2.2rem / 1.15. Any webfont must be self-hosted (COEP, + see AGENTS.md). +- **Layout:** single centred column, `.app { max-width: 720px; padding: 2.5rem 1.25rem 3rem; gap: 1.5rem }`, + `body { min-width: 320px }`. +- **Radius:** 8px for controls (most common); 10–14px for panels/cards; 999px for pills/badges. +- **Motion:** `0.15s` colour/border transitions; `pulse` (live dot) and `blink` (cursor) animations, + disabled under `prefers-reduced-motion`. + +## Components + +One BEM-ish class block per React component in `src/components/` (`.model-picker`, `.dropzone`, +`.live-controls`, `.engine-toggle`, `.auth-panel`, `.update-toast`, …), with modifier classes like +`-selected`, `-active`, `-disabled`. Interactive states use `:hover:enabled` and `:focus-visible`. +`.visually-hidden` exists for screen-reader-only text. + +TODO(maintainer): confirm whether the dark-only theme is intentional, and whether hard-coded semantic +colours should become tokens. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..a97a85e --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,7 @@ +# GEMINI.md + +**`AGENTS.md` is the single source of truth for this repository.** Read `./AGENTS.md` before doing +anything else and follow it exactly. + +This file exists only so Gemini CLI finds the rules. It contains no project rules of its own — add +those to `AGENTS.md`. If anything here ever conflicts with `AGENTS.md`, `AGENTS.md` wins. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..141ecd4 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,40 @@ +# Product + +Whisper Web — on-device speech-to-text. A browser-first transcription app powered by OpenAI Whisper, +running through [Transformers.js](https://github.com/huggingface/transformers) (`@huggingface/transformers`). +(Moved here from the original `docs/CLAUDE.md` §1.) + +## Users and what they get + +**Anonymous users (no login) can:** +- Run Whisper models fully in the browser — no audio or transcript leaves the device. +- Choose which Whisper model to use (tiny → large, English-only and multilingual, quantized variants). +- Keep working when the network — or our own server — is offline (installable PWA + cached model weights). +- Accelerate inference with WebNN or WebGPU (auto-selected in that order, D-017; never WebGPU on + Firefox, D-026), falling back to WASM automatically. +- Transcribe an uploaded audio/video file, or live from the microphone (D-016). +- Export transcripts as `.txt` / `.srt` and manage cached model storage. + +**Logged-in users (Passport.js, when `AUTH_ENABLED`) get all of the above, plus:** +- A toggle to choose where inference runs: **their own device** (browser engine) or **the server** + (server engine). +- The server engine MUST use [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (CTranslate2) — + not Transformers.js (D-013). faster-whisper cannot run in a browser, so the browser engine stays on + Transformers.js. + +## Product principles (from `docs/Architecture.md` §5) + +- **Privacy:** browser-engine audio never touches the network. +- **Anonymous-first:** login only unlocks the server engine; nothing else is gated. +- **Offline-first:** app shell + previously downloaded models work with no network. +- **Honest status:** the UI must not claim a state it isn't in — e.g. "Downloading" vs "Loading model + from cache" (D-033), no "Listening" before the model is ready (D-034). +- **User-initiated downloads:** selecting a model never downloads it (D-020); large models on mobile + get a warn/confirm gate (D-027–D-029). + +## Out of scope (from `docs/WorkBreakdown.md`) + +Arbitrary Hub model ids, OAuth providers, multi-user transcript storage, whisper.cpp server backend, +i18n of the UI. + +TODO(maintainer): confirm target audience and tone of voice for UI copy — not recorded anywhere. diff --git a/README.md b/README.md index d1abfd2..bda05a6 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,11 @@ users can optionally run inference on the **server** instead, backed by [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (CTranslate2) through a small Python sidecar (`server-py/`, Decisions.md D-013). -See `Architecture.md` for the design, `Decisions.md` for the decision log, and -`CLAUDE.md` for the toolchain rules (Bun everywhere — no npm/node). +See `docs/Architecture.md` for the design, `docs/Decisions.md` for the decision +log, and `PRODUCT.md` for the product spec. + +**AI coding agents:** read [`AGENTS.md`](AGENTS.md) — the single source of truth +for commands, conventions and boundaries (Bun everywhere — no npm/node). ## Development diff --git a/docs/Architecture.md b/docs/Architecture.md index c539ff0..49a45b2 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -1,7 +1,7 @@ # Architecture -> Companion to `CLAUDE.md` (product spec & tooling rules) and `Decisions.md` (why things -> are the way they are). This file describes **how the system is structured**. Update it +> Companion to `../PRODUCT.md` (product spec), `../AGENTS.md` (agent & tooling rules) and +> `Decisions.md` (why things are the way they are). This file describes **how the system is structured**. Update it > whenever the structure changes. ## 1. System overview diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 8c6e422..b8480a8 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -1,129 +1,12 @@ -# CLAUDE.md — Read this first, every session +# docs/CLAUDE.md -This is the single source of truth for any agent working in this repo. If anything -you're told conflicts with this file, this file wins. +**`AGENTS.md` (repo root) is the single source of truth for agents in this repository.** Read +`../AGENTS.md` and follow it exactly. This file contains no rules of its own. -## 1. What we are building +This file used to hold the product spec and the Bun toolchain rules (it was the original `bun init` +CLAUDE.md, extended). Its content now lives in: +- Product spec ("what we are building") → `PRODUCT.md` +- Bun toolchain and API rules → `AGENTS.md` § Rules +- Bun API reference → `node_modules/bun-types/docs/` -A browser-first speech-to-text web app powered by OpenAI Whisper, running through -[Transformers.js](https://github.com/huggingface/transformers) (`@huggingface/transformers`). - -**Anonymous users (no login) can:** -- Run Whisper models fully in the browser (no data leaves the device). -- Choose which Whisper model to use (tiny → large, English-only and multilingual, quantized variants). -- Keep working when the network — or our own server — is offline (installable PWA + cached model weights). -- Accelerate inference with WebNN or WebGPU (auto-selected in that order, D-017), falling back to WASM automatically. -- Transcribe from an uploaded audio/video file. - -**Logged-in users (Passport.js) get all of the above, plus:** -- A toggle to choose where inference runs: **their own device** (browser engine) or **the server** the app is deployed on (server engine). -- The server engine MUST use [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (CTranslate2) for inference — not Transformers.js (see Decisions.md D-013). faster-whisper cannot run in a browser (no WASM build of CTranslate2), so the browser engine stays on Transformers.js. - -## 2 How to run things - - -Default to using Bun instead of Node.js. - -- Use `bun ` instead of `node ` or `ts-node ` -- Use `bun test` instead of `jest` or `vitest` -- Use `bun build ` instead of `webpack` or `esbuild` -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` -- Use `bun run - - -``` - -With the following `frontend.tsx`: - -```tsx#frontend.tsx -import React from "react"; -import { createRoot } from "react-dom/client"; - -// import .css files directly and it works -import './index.css'; - -const root = createRoot(document.body); - -export default function Frontend() { - return

Hello, world!

; -} - -root.render(); -``` - -Then, run index.ts - -```sh -bun --hot ./index.ts -``` - -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. +See Decisions.md D-036. diff --git a/docs/Decisions.md b/docs/Decisions.md index 76e2ffa..12c23cb 100644 --- a/docs/Decisions.md +++ b/docs/Decisions.md @@ -1126,3 +1126,28 @@ change needs its own accuracy verification against `tests/live-accuracy.test.ts` and `tests/live-real-model.test.ts` (a repetition guard can change correct-transcript wording too) before landing, which is out of scope for this investigation. + +## D-036 — Root `AGENTS.md` is the single source of truth for agents +**Status:** Accepted (2026-10-29) +`docs/CLAUDE.md` called itself the single source of truth, but Claude Code +only auto-loads it when working inside `docs/`, other agents (Codex, Gemini, +Cursor) never read it, and most of it was the generic `bun init` boilerplate +(sample `Bun.serve`/HTML snippets) rather than repo-specific rules. +**Decision:** a root `AGENTS.md` (read natively by Codex/Gemini, imported by +the root `CLAUDE.md` via `@AGENTS.md`) holds the rules: verified commands, the +conventions agents would otherwise break (Bun only, D-013 engine split, the +transformers patch, COEP, D-020, D-030, D-032), boundaries, and docs duties. +`CLAUDE.md`, `GEMINI.md`, and `docs/CLAUDE.md` are pointers only. The product +spec from `docs/CLAUDE.md` §1 moved to `PRODUCT.md`; `DESIGN.md` records the +shipped visual system (both read by the impeccable skill). Specs from the +superpowers workflow live permanently in `docs/specs/`; plans live in +`docs/plans/` only while work is in progress and are `git rm`'d before +merging. Agent tool permissions are defined in `docs/PERMISSIONS.md` and +rendered to `.claude/settings.json`, `.codex/`, `.gemini/settings.json`. +**Kept as-is:** `docs/Architecture.md`, `docs/Decisions.md` (same filenames — +renaming would break every existing reference) and `docs/WorkBreakdown.md` +as the one backlog (its own rule: no separate backlog), rather than adding a +`docs/TODO.md`. +**Trade-off:** `AGENTS.md` is kept short (<150 lines) on purpose — rationale +goes here, not there. Changes to it redirect every agent session, so review +them like CI config. diff --git a/docs/PERMISSIONS.md b/docs/PERMISSIONS.md new file mode 100644 index 0000000..4364505 --- /dev/null +++ b/docs/PERMISSIONS.md @@ -0,0 +1,56 @@ +# Agent permissions + +Source of truth for what AI coding agents may run here. Tool files are renderings of this page: +`.claude/settings.json`, `.codex/config.toml` + `.codex/rules/project.rules`, `.gemini/settings.json`. +Change this page and every tool file in the same commit. (No Cursor/Copilot files: no sign the team uses them.) + +## Verified commands + +Run on 2026-10-29 (Bun 1.3.14). Only `worked` rows may appear in a tier, a tool file, or `AGENTS.md` Commands. + +| Command | Result | +|---|---| +| `bun install --frozen-lockfile` | worked | +| `bun test tests/export.test.ts` | worked — 28 pass | +| `bun test tests/models.test.ts -t "dtype"` | worked — 5 pass, 14 filtered | +| `bun test --path-ignore-patterns='tests/live-real-model.test.ts'` | worked — 239 pass, 1 skip, ~6 s | +| `bun test` | ran — 242 pass, 1 skip, 1 fail in ~17 min. The fail is pre-existing: `tests/live-real-model.test.ts` › `en_alien` hits its 900 s timeout (reproduced in isolation). Allowed anyway: the command itself is the verified dev loop. | +| `bun run build` | worked | +| `bun run icons` | worked | +| `bun dev` | worked — HTTP 200 on `/` | +| `FW_INTEGRATION=1 bun test tests/transcribe.test.ts` | not run — faster-whisper not installed here | +| `bun start` | not run — documented in README | +| Typecheck / lint | not run — `typescript` not installed, no linter configured | +| `docker build .` | not run — CI-only; in Ask tier | + +TODO(maintainer): confirm every row above that is not worked. + +## Tiers + +| Tier | Commands | Why | +|---|---|---| +| Allow | `git status`, `git diff`, `git log`, `git show`, `git branch --list`, `git rev-parse`, `git add`, `git commit`; `bun install --frozen-lockfile`, `bun test`, `bun run build`, `bun run icons`, `bun dev` | Inspection and the verified dev loop; no prompt needed. | +| Ask | `git push`, `git merge`, `git rebase`, `git switch`, `git restore`, `git commit --amend`, `rm`; `bun add`/`remove`/`update`/`upgrade`/`patch`, `bunx`, `bun x`; `docker`; `pip install`, `python3 -m pip install` | Leaves the machine, rewrites work, changes dependencies or the transformers patch, or touches deployment — a human decides. | +| Deny | `git push --force`/`-f`/`--force-with-lease`, `git reset --hard`, `git clean`, `git add -f`/`--force`, `git commit --no-verify`/`-n`, `rm -r`/`-R`/`-rf`/`-fr`/`-Rf`/`-fR`/`--recursive`, `sudo`; reading `.env`, `.env.*`, `data/**` (local SQLite users + sessions) | Destroys work, skips checks, or exposes secrets/user data. | + +## Known gaps +- Allowlists save prompts; they are not a security boundary. Use the tool's sandbox for isolation. +- Prefix rules are bypassable: `git -C dir push`, flags placed later (`git push origin main --force`), + `+refspec` pushes. Claude Code's `:*` matches at a word boundary, so each flag cluster must be + listed — which is why `git clean` is denied whole and `rm` always asks. +- `bun install ` adds a dependency but cannot be put in Ask without also catching the allowed + `bun install --frozen-lockfile` (Ask beats Allow in Claude Code). Treat it as Ask by convention. +- `bun run build` deletes and rewrites `dist/` itself (`scripts/build.ts`), outside the `rm` rules. +- Allowed test, build, and dev commands run code from the repo; an agent that can edit + `package.json`, `scripts/`, or the tests can run anything through them. `bun test` also downloads + model weights from huggingface.co (`tests/live-real-model.test.ts`). +- Denying reads of `.env` does not stop a script that opens it itself (Bun auto-loads `.env`), and + git can print staged or committed secrets. `git add -f` is denied so an agent cannot stage an + ignored secret; keep real secrets out of the working tree. +- Codex: loaded only once the project is trusted; `approval_policy = "on-request"` here can loosen a + stricter user setting. No allow rules (Codex `allow` = run outside the sandbox). In + `workspace-write`, `.git/` is read-only, so `git add`/`commit` ask for escalation; `bun install` + needs network and asks too. `--force-with-lease=` and late flags only match the Ask rule. + Rules checked with `codex execpolicy check`. +- Gemini: ignored until the folder is trusted; `tools.exclude` is the only project-level deny; no + Ask tier (anything not allowed already asks); cannot deny file reads. diff --git a/docs/WorkBreakdown.md b/docs/WorkBreakdown.md index 0be4a50..0199edd 100644 --- a/docs/WorkBreakdown.md +++ b/docs/WorkBreakdown.md @@ -345,3 +345,35 @@ cached. Arbitrary Hub model ids, OAuth providers, multi-user transcript storage, whisper.cpp server backend, i18n of the UI. Promote to a milestone via a new entry in `Decisions.md` first. + +--- + +## Noticed, not fixed +Found while bootstrapping agent docs (D-036); docs-only pass, so no code changed. +- [ ] `bun test tests/live-real-model.test.ts` fails: the `en_alien (en)` case + times out at its 900 s limit (2026-10-29, Linux CPU/WASM, reproduced in + isolation; `da_festuge` and `en_aiproblem` pass). +- [ ] `isolate_wer2.ts` (repo root) is marked "TEMP (delete after use)" and + writes to a stale `/tmp/claude-1000/...` scratchpad path. +- [ ] `docs/Architecture.md` §3 and `scripts/build.ts` say `src/pwa/icons/` + is "not checked in", but `icon-192.png`/`icon-512.png` are tracked. +- [ ] No typecheck or lint: `tsconfig.json` is strict but `typescript` is not + installed, so nothing checks types (`bun` strips them). +- [ ] `.gitlab-ci.yml` only builds Docker images; `bun test` never runs in CI. +- [ ] README "Graceful degradation" says server-engine weights download into + the data volume; the env table, Dockerfile and D-031 say they are baked + into the image at `/opt/faster-whisper/models`. +- [ ] `src/index.css` declares `color-scheme: light dark` but only has a dark + palette; `Inter` is listed first in the font stack but never loaded. + +## Recurring +- [ ] Maintain `AGENTS.md`: + - **Add a rule only in response to a real failure.** When an agent repeatedly + gets something wrong, add one line, ideally with a code example. Do not + pre-empt hypothetical mistakes. + - **Update in the same commit as the convention change.** A stale rules file + actively misleads. + - **Prune quarterly.** Delete rules whose convention changed or whose mistake + no longer occurs. Keep it under 150 lines. + - **Optional validation:** run the same task twice in clean sessions, with and + without the file, and compare steps and token use. diff --git a/docs/plans/README.md b/docs/plans/README.md new file mode 100644 index 0000000..e9c8527 --- /dev/null +++ b/docs/plans/README.md @@ -0,0 +1,11 @@ +# Plans + +Implementation plans from the superpowers writing-plans workflow. **Transient:** a plan lives only while +its work is in progress and is removed with `git rm` before merging. This README stays. + +- Naming: `NNN-short-slug.md`, matching its spec number. +- Links back to its spec. Bite-sized checkbox tasks with exact file paths, test commands, and + commit checkpoints. +- Written for an engineer with zero context for this codebase — spell out the obvious. +- Tick checkboxes as you go so another agent can resume an interrupted session. +- Front matter: `status: draft | approved | in-progress`. There is no `done` — a finished plan is deleted. diff --git a/docs/specs/README.md b/docs/specs/README.md new file mode 100644 index 0000000..7b491ac --- /dev/null +++ b/docs/specs/README.md @@ -0,0 +1,11 @@ +# Specs + +Approved specifications from the superpowers brainstorming workflow. Nothing non-trivial gets +implemented without one. + +- Naming: `NNN-short-slug.md`, zero-padded, sequential. +- A spec states **what** and **why**, plus acceptance criteria. Not **how**. +- Front matter: `status: draft | approved | implemented | superseded`. No link to a plan — plans are + removed before merge. +- Never delete an implemented spec; it is the record of intent. +- A spec that makes an architectural choice also gets a `D-###` entry in `docs/Decisions.md`.