# AGENTS.md These are the conventions this codebase holds itself to. Apply them to new code, and apply them to code you touch wherever that does not break consistency with its surroundings. ## Core Principles 1. **Readability is the highest priority** - code should be self-explanatory 2. **Functional style** - prefer pure functions, avoid side effects 3. **Efficiency** - no redundant operations 4. **Simplicity** - minimal code that solves the problem ## Single Source of Truth - A shared vocabulary or contract (emoji names, enum options, capability names, component lists) consumed by more than one role, package, or service is defined exactly once; every consumer derives from that definition. Hand-maintained parallel copies are a defect: they drift (one list gains a value the other never sees) and every addition requires touching N places. - When a consumer lives in another language and cannot import the definition, add a conformance test that reads the canonical source and fails on drift, instead of keeping a second hand-edited copy (`chatd/tests/buzz-adapter.test.ts` reads `.dependency/blueprotocol/agentcontract/reaction_emoji.go`). - On discovering duplicated sources of truth, merge them as part of the change that touched them; do not extend a duplicate. ## Working on this repository Nothing lands on `main` by direct push. Branch, open a pull request, let the check run, merge. ### Branch names `/` — the type is the same word the commit will carry, and the subject says what changes, not what you did to it. ``` feat/acp-context-injection fix/approval-resume-after-restart docs/tui-screenshots refactor/turn-runner-split test/completion-gate-evidence chore/gofmt ci/skip-docs-only-runs ``` Branch off `main`, rebase rather than merge when `main` moves under you, and delete the branch once the pull request is merged. ### Commit messages ``` : ``` `type` is one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`. A scope is allowed when it disambiguates (`fix(acp): …`) and omitted when it does not. The subject line is a claim about the code, not a description of the work: `fix: stop reading "the agent stopped talking" as "the task is done"` rather than `fix: fixed the status bug`. The body exists to answer *why*; a commit whose body only repeats the subject should not have one. ### Prose in documents The README and the docs are read by people deciding whether to trust this thing. Prose that reads as machine-written costs that trust, and it drifts back in every time someone lets a model write a paragraph. Grep for it before committing. - **One negative-parallel construction per 500 words.** `X, not Y` · `rather than` · `not merely … but`. Keep the ones where the alternative is what a reader would actually assume; cut the rest. They stop registering when they repeat, which wastes the ones that matter. - **Under three em dashes per 500 words.** An em dash that bolts an appositive onto a finished sentence should be a period. - **No sentence praising the document's own honesty.** "worth stating plainly", "to be clear", "honest list". Be plain and say nothing about it. - **No section-closing restatement.** If the last sentence of a section adds no fact, delete it. - **Three or more `A X is a Y that …` in a row is a definition list.** - **A fact stated in a table is not restated in prose.** - **Bold whole blocks, never words inside a sentence.** Check with: ```bash python3 - <<'EOF' import re, pathlib text = pathlib.Path("README.md").read_text() words = len(text.split()) negations = len(re.findall(r", not |rather than |not merely", text)) print(f"{words} words · {negations} negations (1 per {words // max(negations, 1)}) · {text.count(chr(8212))} em dashes") EOF ``` ### Screenshots The terminal user interface images the README embeds are generated, never hand-shot. Re-shoot them from the working tree after any change to `internal/tui`: ```bash ./tools/shoot-tui-screenshots # every frame, into assets/screenshots/ ./tools/shoot-tui-screenshots --only tui-tasks ``` It builds `cmd/blueclaw-cli`, serves a seeded admin API on 127.0.0.1:8099, drives the client through a pseudo terminal, and renders each screen with headless Chrome. The demo task runs, ledger, and harness answers live in `tools/tui_screenshots/fixture_api.py`; change them there so every frame stays consistent. ### Pull requests One reviewable change per pull request. The description says what the reader should look at and what evidence exists that it works — the test that fails without the change, the scenario that was run, the screenshot. A pull request that touches unrelated files should be split. Branch names, commit messages, pull request titles and pull request descriptions are written in English. Discussion in review can be in whatever language the reviewers share; the repository's permanent record is English. ## Testing - `go test ./...` covers the unit suites beside their sources and the integration suite under `tests/`. Nothing needs an external service; the standalone boot check skips unless `BLUECLAW_TEST_POSTGRES_URL` names a database, which a developer supplies locally. - TypeScript suites are per package: `bun run test` at the repository root runs every package suite in its own process, and `cd && bun run test` runs one. Never run bare `bun test` at the repository root — it blends isolated package processes into one and reports false failures. - A guard is proven by watching it fail. Write the test, break the thing it claims to protect, watch it go red, put the thing back. A test that was never seen to fail is an assumption with a green checkmark on it, and the ones that matter most here are exactly the shape that can pass for the wrong reason. `TestOverheardMessageNeverBecomesTheInstruction` guards the behaviour that once had the bot reposting somebody else's announcement as its own instruction, and it is worth nothing unless it actually goes red when the overheard text reaches the prompt again. - The pull request says what was broken to prove it. One line, naming the edit or the revision, is enough. Anyone reading the guard later can redo it from that line, which is the point. ## Which revision a binary is `blueclaw --version` answers it and `GET /admin/api/harness` carries it, so a deploy can read back what landed instead of trusting a green exit code. The answer comes from `-ldflags`, and it has to, because this repository is usually built as a submodule of the host: its `.git` is a gitdir file pointing outside the tree, so the Go toolchain records no VCS information at all and `debug.ReadBuildInfo` has nothing to report. A build that wants an answer passes ``` -ldflags "-X github.com/yeomyeonggeori/blueclaw/internal/buildrevision.injected=$(git rev-parse HEAD)" ``` A build that passes nothing reports `unknown`, which is the honest answer and the one a deploy check should refuse. ## LLM-First Runtime Policy - User-facing answers, failure explanations, approval wording, and recovery direction must go through the LLM. - Deterministic runtime code may validate, normalize, enforce schemas, orchestrate retries, and record diagnostics, but must not compose fallback sentences for users. - Exact control acknowledgements for slash commands, such as stop/stop-all, may use deterministic system responses; do not expand that exception to task judgment, failure explanation, recovery direction, or confirmation wording. - When a failure requires a judgment, request structured output first, then use that structured decision as input to an LLM-generated user reply. - Deterministic helpers may prepare safe facts for the model, such as failure stage, error code, known context, and attempted actions. - For real task failures, do not fully suppress the user reply. Try local LLM failure wording first, then send a compact raw error summary if no LLM path can produce a usable notice. - Full suppression is only for intentionally ignored control/runtime cases such as duplicate delivery, cancelled task output, or self/bot messages. ## Code Style ### No Comments Code self-documents through descriptive names and small functions; write zero comments. The only comment that survives names a concrete external fact — an upstream bug with its number, a spec with its clause, a provider behavior with its documented source. Rationale is never that: a sentence explaining why the change was made, what the code is for, or what would break without it belongs in the commit message, never in the file. ### No Abbreviations Use full names: `response` not `res`, `error` not `err`, `configuration` not `config` ### Initialism Casing (camelCase) - **Leading**: lowercase (`idToken`, `urlParams`, `apiKey`) - **Trailing**: UPPERCASE (`userID`, `callbackURL`, `oauthAPI`) ### Naming Conventions - **Functions**: Clear verbs (`calculateTotalPrice`, `validateUserInput`) - **Variables**: Descriptive nouns (`userAccountBalance`, `authenticationToken`) - **Booleans**: is/has/can prefixes (`isAuthenticated`, `hasPermission`) ### Function Design - Each function does ONE thing - 10-20 lines maximum when possible - Use early returns and guard clauses - Same level of abstraction within a function ```js // BAD - mixed abstraction async function processOrder(order) { const user = await database.query(`SELECT * FROM users WHERE id = ${order.userID}`); if (!user.isActive) throw new Error('Inactive user'); await sendEmail(user.email, 'Order confirmed'); return { success: true }; } // GOOD - consistent abstraction async function processOrder(order) { const user = await fetchUser(order.userID); validateUserIsActive(user); await notifyOrderConfirmation(user); return createSuccessResponse(); } ``` ```js // BAD - nested conditionals function processUser(user) { if (user) { if (user.isActive) { if (user.hasPermission) { return doWork(user); } } } return null; } // GOOD - guard clauses function processUser(user) { if (!user) return null; if (!user.isActive) return null; if (!user.hasPermission) return null; return doWork(user); } ``` ### Functional Style - Prefer pure functions (same inputs → same outputs) - Avoid side effects and mutations - But readability wins over functional purity ```js // GOOD - functional and readable const activeUserEmails = users .filter(user => user.isActive) .map(user => user.email); // Also GOOD - imperative but clear const result = {}; for (const item of items) { if (item.isValid) { result[item.id] = item.value; } } ``` ### TypeScript Types - Define meaningful domain types (User, Order, Product) - Avoid: `any`, `as` assertions, non-null assertions (!) - Use `unknown` at boundaries before validation, then narrow to a proper type - Validate at boundaries, trust internal code ```ts // BAD function processData(data: any) { return data.map((item: any) => item.value); } // GOOD function processData(data: unknown): string[] { const validatedData: DataItem[] = validateAndParseData(data); return validatedData.map(item => item.value); } ``` ## Error Handling **Throw errors only for real errors:** - External API failures - Network errors - Resource exhaustion (not enough credits, disk full) - Authentication/authorization failures - Database connection issues **Be specific and accurate:** ```ts // BAD - vague throw new Error('Something went wrong'); // GOOD - specific throw new Error('Stripe API returned 402: insufficient funds for charge'); ``` **Don't wrap everything in try-catch:** - Only catch errors you expect and can handle - Let unexpected errors bubble up naturally - Catching everything hides bugs ```ts // BAD - catching everything try { const user = await fetchUser(id); const orders = await fetchOrders(user.id); return processOrders(orders); } catch (error) { return null; // Hides all problems } // GOOD - catch specific expected errors const user = await fetchUser(id); const orders = await fetchOrders(user.id); return processOrders(orders); // Let errors bubble up - they indicate real problems ``` **Handle edge cases without throwing:** ```ts // BAD - throwing for non-errors function findUser(users: User[], id: string): User { const user = users.find(u => u.id === id); if (!user) throw new Error('User not found'); return user; } // GOOD - handle expected cases gracefully function findUser(users: User[], id: string): User | undefined { return users.find(user => user.id === id); } ``` **Validate at boundaries:** - Validate user input at entry points - Validate external API responses - Trust internal code once validated ## Quality Checklist Before considering implementation complete: - [ ] Code is readable without comments - [ ] Functions are small and focused - [ ] No abbreviations in names - [ ] No redundant operations - [ ] No dead code - [ ] Edge cases handled - [ ] Follows existing codebase patterns - [ ] Efficient - no unnecessary work - [ ] Proper types defined (no any/unknown cheating)