Bootstrap agent-ready docs and permissions (D-036)

Root AGENTS.md is now the single source of truth for coding agents;
CLAUDE.md, GEMINI.md and docs/CLAUDE.md are pointers. The product spec
moves from docs/CLAUDE.md to PRODUCT.md, DESIGN.md records the shipped
visual system, and docs/PERMISSIONS.md defines agent permission tiers
rendered to .claude/, .codex/ and .gemini/. Adds docs/specs/ and
docs/plans/, logs issues noticed (not fixed) in WorkBreakdown.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 19:51:30 +01:00
co-authored by Claude Opus 5.5
parent c1b710cb23
commit 0ece4b3a2a
18 changed files with 423 additions and 130 deletions
+25
View File
@@ -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/**)"
]
}
}
+2
View File
@@ -0,0 +1,2 @@
sandbox_mode = "workspace-write"
approval_policy = "on-request"
+23
View File
@@ -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")
+20
View File
@@ -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)"
]
}
}
+5
View File
@@ -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/
+109
View File
@@ -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%[email protected]` (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.
+9
View File
@@ -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.
+32
View File
@@ -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.
+7
View File
@@ -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.
+40
View File
@@ -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.
+5 -2
View File
@@ -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
+2 -2
View File
@@ -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
+9 -126
View File
@@ -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 <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.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
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 <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
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.
+25
View File
@@ -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.
+56
View File
@@ -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 <pkg>` 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=<ref>` 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.
+32
View File
@@ -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.
+11
View File
@@ -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.
+11
View File
@@ -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`.