---
name: audit
description: Audit one area of Modbux against the eight criteria and write the findings to tmp/AUDIT-.md, changing no code. Use when the user asks to audit or assess an area, or to re-check one after changes. Do NOT use to review a branch or your own diff — that is /code-review; and do NOT use to execute a finished audit, which is a separate session with fresh context.
---
# Audit one area
**Read-only with respect to code.** The only file this writes is
`tmp/AUDIT-.md`, which is gitignored. Every improvement you spot becomes a
finding, including the one-line obvious ones.
## The areas
Split by what the code shares, not by directory size. One area per run.
| area | what it is |
| ----------- | ------------------------------------------------------------------------------------ |
| `modbus` | `main/modules/modbusClient.ts`, `modbusServer.ts` and `modbusServer/` |
| `boundary` | `main/ipc.ts`, `preload/`, `shared/types/ipc.ts`, `main/state.ts`, `main/windows.ts` |
| `shared` | `shared/` minus `types/ipc.ts`: schemas, migrations, pure helpers |
| `stores` | `renderer/src/context/` |
| `client-ui` | `renderer/src/components/client/` |
| `server-ui` | `renderer/src/components/server/`, `components/shared/`, `containers/` |
## Before you start
Read `CLAUDE.md`, `CONTRIBUTING.md` and `src/__tests__/conformance.test.ts`.
The suite already asserts thirteen conventions. **Do not report what it asserts** —
it is green, so those are closed. Audit what a test cannot see.
## The eight criteria
Apply all eight. Do not merge them and do not skip the cosmetic ones.
1. **What the conformance suite cannot see.** The store-versus-component IPC
rule, the folder-per-component judgement, and anything else CONTRIBUTING
states as prose under _The rules no test can see_.
2. **Duplication.** Search for the shape, not the name. Two functions doing one
job often share no word, so a search for what one is called returns neither.
Report identical copies too, because they diverge later. A duplication claim
carries its own burden of proof, under _Verification_.
→ [WHY: a meter is a claim too](./references/a-meter-is-a-claim.md)
3. **Dead code.** Unused exports, unreachable branches, config entries pointing
at deleted paths, comments naming files that are gone.
4. **Deferred comments.** Every `TODO`, `FIXME`, `for now`, `later`, `until we`,
with its exact location and text.
5. **Test coverage.** What is _not_ covered. Think like someone with a field
device, not like the author: a unit id of 0 and of 248, an address at 65535
with a data type needing four registers, a serial port that disappears
mid-read, a config file from two versions ago.
6. **File size and module shape.** Four files stand clear of the rest, and the
distribution is the argument rather than any number you pick:
```sh
find src -name '*.ts' -o -name '*.tsx' | grep -v __tests__ | xargs wc -l | sort -rn | head
```
Propose a split only along a real responsibility boundary, never on length
alone. Count code lines and test lines separately: a file that looks like the
worst offender is sometimes a thin one with a large test block bolted on, and
the two call for opposite conclusions.
7. **Architecture fit.** Does this block RTU over TCP, a second client, the
gateway idea, or anything `CHANGELOG.md` says is coming?
8. **What modbus-serial actually does.** For `modbus`, `boundary` and `shared`:
every assumption this code makes about the library is checked against
`node_modules/modbus-serial/`, not against its README. Two of this project's
sharper findings came from that gap.
→ [WHY: read the library, not its README](./references/read-the-library.md)
## Reproduce, do not reason
**Build first.** A reproduction against a stale bundle looks like a measurement
and is not one:
```sh
npx electron-vite build
```
Then drive it. The e2e fixtures already stand up a server, a client and a socat
serial pair:
```sh
npx playwright test e2e/specs/01-main/-.spec.ts
```
For a claim about the wire, a scratch spec beats reading. For a claim about a
schema or a pure helper, `npx vitest run ` in a scratch test is faster than
either.
**A reproduced defect beats ten lines of reading.** Delete your scratch files
when you are done.
## Recording a finding
Every finding is a heading in this exact form, and then the fields under it:
```
### · ` ` · criterion <1-8> · · size · reproduced
```
The ID is the area's initial and a number: `F1`, `CU-01`, `B-04`. Drop
`reproduced` when you did not run it.
**The heading is the form, not a suggestion.** Six agents were given the field
list and five wrote the same heading; the sixth used numbered titles with the
fields as bullets, and a grep for severity across the six documents found
nothing in that one. The document was fine and the count was wrong.
Under the heading: `claim` (one sentence) · `evidence` (the code, or the command
and its output) · `proposal` · `recipe` if reproduced.
**severity — what the app does to a user decides it, not how much it annoys
you.**
| | |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **blocking** | A user gets a wrong answer, a crash, or lost configuration. A malformed frame reaching the socket. A register read as the wrong type. Also: dead code that makes something look covered when it is not. |
| **annoying** | Right behaviour, wrong construction. Duplication, a rule enforced in one of two places, untested logic. Nothing a user sees today; the next change here is where it bites. |
| **cosmetic** | Neither. A stale comment, an unused export, a name that misleads. |
**size — how much work the proposal is, not how large the defect is.** **S** is
one file and no decision. **M** touches several call sites or needs a small
decision you can make from the area alone. **L** needs a decision that is not
yours: a protocol question, a persisted shape, or anything crossing two areas.
**Give the exact recipe if you reproduced it** — the file contents and the
command, complete enough to paste. A recipe that does not work as written is
worse than none, because the next person dismisses a real defect.
**Anchor on the symbol, not the line.** `modbusClient.ts readRegisters`, not
`modbusClient.ts:412`. A line number is right for one commit; the symbol keeps
working. The exception is inside a finding's evidence, where the line is what
makes it checkable.
**Do not record a file's size as a number.** A size is worth writing only as an
argument: past the threshold, and here is why it is still one file.
## Also record what you checked and found sound
A document listing only defects leaves the reader unable to tell "examined and
correct" from "not looked at". Say what you read and what held, briefly.
A claim you could not settle is an **open question**, labelled as one. Not a
finding.
## Verification
Every finding goes to a second agent whose job is to **break** it. That is not a
reviewer looking for problems; it is handed a claim and asked to demonstrate it
wrong.
- **refuted** — you can demonstrate it is wrong. Give the demonstration.
- **unconfirmed** — neither proved nor disproved. **Keep it.** Say what you
checked and what would settle it.
- **confirmed** — independently verified.
**The bar for rejection is high.** A wrong finding costs one look; a dropped
correct one costs a defect in a released build. When in doubt: unconfirmed.
**No verdict returned counts as kept, never as rejected.**
Two exceptions, where the burden runs the other way:
- **A duplication claim must be actively substantiated.** If you cannot show the
two are equivalent in behaviour, refute it and say how they differ.
- **A claim marked reproduced must actually reproduce.** Finding one that does
not is the most valuable single outcome available to you.
## What a finding is worth once it is written
| part | trust |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| the file, the symbol, the evidence | high, and verifiable |
| the claim, if reproduced | high |
| the claim, if only read | good |
| **the recipe** | **low** — the most common defect in an audit is a recipe describing an input that does not trigger the behaviour |
| **the proposal** | **low** — a guess by someone who did not read the rest of the file |
| any summary or state | **low** — a compression, and compressions interpret |
Open the file, reproduce, then decide. Being written down is not evidence.
## When more than one area is done
**Group the blocking findings by cause before anyone fixes them.** Six areas
audited in parallel produced 28 blocking findings that turned out to be thirteen
causes, and three of those thirteen were invisible from any single document.
→ [WHY: the same bug from three directions](./references/one-bug-three-reports.md)
Write `tmp/AUDIT-clusters.md`: one section per cause, naming the findings it
holds, what they share, and what a fix has to settle. Keep the per-finding
evidence where it is; the cluster document points, it does not copy.
Check every blocking finding reaches the document:
```sh
for a in ; do
grep -E '^#{3,4} .*· blocking' tmp/AUDIT-$a.md | sed 's/^#* *//;s/ ·.*//' | while read id; do
grep -q "$a/$id" tmp/AUDIT-clusters.md || echo "missing: $a/$id"
done
done
```
**A wrong grouping is a new way to be wrong.** A cluster that is really two
causes gets fixed as one and half of it survives, so the refutation reviewer is
asked to break the grouping as well as the claims.
**A cluster's size is not the largest size inside it.** Each finding was sized
inside one area by someone who could not see the others. Four of the thirteen
here needed a decision that fits in no single area, which is size L whatever the
findings said.
## Finish by reporting
The document path, then one line per finding: severity, symbol, claim. Then
stop. Proposing is the whole job, and the session that proved a defect is the
worst one to fix it: it is invested in the finding and has read past everything
it already dismissed.