CLAUDE.md · diff
git:20260828.c1e536a to git:20260828.04ade38
5 added, 3 removed. Audit A to A.
# CLAUDE.md
Notes for an AI assistant working on this repository. Human contributors want
[`CONTRIBUTING.md`](CONTRIBUTING.md), which this does not duplicate.
## What this is
An MCP server over Stockbit's private JSON API — the Indonesian exchange. 139 tools in 17 families
(40 of them registered by default — `STOCKBIT_TOOLS` defaults to `core`),
three token domains, and, behind switches the account owner turns on themselves, order entry against
a real brokerage account.
## Commands
```bash
npm run typecheck # src/, bin/, scripts/
npm test # the whole suite, offline, no skips
npm run build # cleans dist/ first
npm run smoke # starts the built binary over stdio and asks it what it registered
npm run check:pack # asserts npm publish would ship the build and nothing else
npm run docs:tools # regenerates docs/TOOLS.md — run it after touching ANY tool
```
The full gate before a commit is all six. CI runs them on three operating systems and two Node
versions.
## The map
| Path | |
|---|---|
| `src/http/routes/` | The closed route table — 153 request shapes. The security boundary. |
| `src/http/transport.ts` | What enforces it. |
| `src/auth/` | Login capture, three token stores, refresh with a cross-process lock. |
| `src/core/` | One module per Stockbit domain. The readers everything else stands on. |
| `src/analysis/` | Indicators, patterns, strategies, backtests, scans, position sizing. |
| `src/render/` | Pure SVG. No browser. |
| `src/tools/` | MCP registration, one module per family. `_define.ts` is the door. |
| `src/trading/`, `src/eipo/` | Tickets, previews, submission, the paper ledger. |
| `src/alerts/` | Rules, the daemon, delivery. |
| `docs/adr/` | Every decision that changed what this server may do. |
## Three invariants. Do not break them; each has a test.
1. **Nothing reaches a Stockbit host outside `src/http/routes/`.** Adding a **non-GET** route
requires an ADR *before* the code lands. `test/transport.test.ts`.
2. **`define.write` never adds to the handler map** `workflow_run` reads. A saved recipe is data,
and data must not be able to place an order. `test/tools.test.ts`.
3. **No module under `src/tools/`, `src/trading/` or `src/eipo/` may import `saveSettings`.** A
server that can widen its own permissions has no permissions. `test/settings.test.ts`.
## Rules that are easy to get wrong
- **Run `npm run docs:tools` after touching a tool.** `docs/TOOLS.md` is generated and a test fails
if it is stale.
- **Add a new write tool's name to `WRITES` in `test/tools.test.ts`.** That list is deliberately
hand-written; deriving it would make the test agree with the code.
- **Never invent a number.** If a field could not be read, it is absent — not zero, not a default.
`readFrom` names the wire key a value came from; `unmappedKeys` names what was not recognised, and
on account data the *values* are dropped because an unmapped field there may be an account number.
- - **Never widen an evidence claim.** A tool whose description says the route has never been observed
- is `projected`, and saying otherwise throws at registration. Settling one takes a live call, not
- an edit.
+ - **Never widen an evidence claim.** Every tool DECLARES its evidence — `{ evidence }` on the tool
+ or on `define.family()` — and one that declares nothing fails to register rather than defaulting
+ to `observed`. A description that says the route has never been observed while the declaration
+ claims more throws at registration. The full map is hand-written in `test/tools.test.ts` beside
+ `WRITES`, and for the same reason. Settling one takes a live call, not an edit.
- **Never write a secret anywhere.** Tokens, PINs and bot tokens are matched by shape as well as by
key. When adding an error path, ask what the message could contain: a `fetch` failure quotes the
URL, and a Telegram URL contains the bot token.
- **The tests are offline.** `fetch` is stubbed; every test file sets `STOCKBIT_FORCE_FILE_STORE=1`
and a temp `STOCKBIT_STORE_DIR` *before* its imports, because module state is captured at import.
- **Fixtures carry no real data.** No real account numbers, names or watchlists.
- **No AI co-author trailers in commits.** Commit on `marvel-testing`, never on `main`.
## Vocabulary
[`CONTEXT.md`](CONTEXT.md) is the glossary — one meaning per word, and the code uses those words.
The evidence ladder (**Observed / Read-back / Projected**) is load-bearing, not decoration.
## Where the money is
`src/trading/` and `src/eipo/`. If a change touches the ticket protocol, the confirmation gates, the
outcome classes or the settings file, read [ADR-0004](docs/adr/0004-order-entry.md),
[ADR-0008](docs/adr/0008-paper-trading.md) and
[ADR-0010](docs/adr/0010-elicitation-is-decisive.md) first. The rule those encode: a user must never
be able to place an order they did not read, and the server must never say "placed" when it does not
know.
**One gate, in `src/trading/confirmation.ts`.** Exchange orders and e-IPO both call it, and the
duplicate that used to live in `src/eipo/order.ts` had already drifted in three places before anyone
noticed. Its branch order is the security property, not an implementation detail: the human is asked
**before** `confirm` is looked at, and behind no `via` test. A change that moves the ask later, or
puts any condition in front of it, is reintroducing the defect ADR-0010 closed.