From fea5897015f89568edfe1fd8185dbd254f001d43 Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Sun, 30 Aug 2026 05:41:13 -0500 Subject: [PATCH 1/4] chore: align the superpowers ignore block with the ticket's canonical text TRA-1128 AC#1 requires identical text across repos so no repo is subtly different again. Platform's block carried a trailing "Named to match ble-mcp-test." clause that a previous session added while relaying and then described as adopted verbatim. It is not in the ticket. Dropping it makes platform byte-identical with docs and infra, verified by hash over the 14 lines rather than by eye: sed -n '187,200p' .gitignore | sha256sum cfc7cf430d18af4d3e788cfc66b1a0ab5a0e509c06e996b4d285362088a6d225 Refs: TRA-1128 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W7p2hFrE6ZFWS91XvyFQcV --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae97dcc5..7a741773 100644 --- a/.gitignore +++ b/.gitignore @@ -196,7 +196,7 @@ docs/superpowers/ # being built and reviewed, then go stale fast (PIDs, bench state, host facts) # while still reading as authoritative because they are checked in. Durable # conclusions belong in CLAUDE.md, an ADR or a code comment; the narrative and -# evidence belong on the ticket. Named to match ble-mcp-test. +# evidence belong on the ticket. docs/notes/ docs/investigations/ From e9d95e8fd26ca32ad93bed4ec8c11e505f09c97d Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Sun, 30 Aug 2026 05:41:13 -0500 Subject: [PATCH 2/4] docs: retarget references into the deleted superpowers tree at their tickets PR #586 untracked docs/superpowers/ but left five pointers into it across four files, so the repo has been documenting paths that do not exist. The sweep's blast radius is not something the ticket specifies; infra hit the same thing and flagged it. Test applied per file: does the surrounding prose still mean what it says without the link? Ticket numbers outlive paths, so where the reference carried real information it is retargeted rather than dropped. accumulator.ts behaviours are already described in full above; cite TRA-821 instead of the spec path auth_error.go "a short canonical set" is undefined without its pointer, so cite TRA-407 to keep it findable deploy/edge/README already carried TRA-988 beside the two paths load-tests/ingest finding is stated inline above; pointer was pure redundancy Nothing promoted out of the deleted tree, per the owner's "no case-by-case triage, no promoting files just in case." Refs: TRA-1128 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W7p2hFrE6ZFWS91XvyFQcV --- backend/internal/util/httputil/auth_error.go | 3 +-- deploy/edge/README.md | 3 +-- frontend/src/worker/cs108/barcode/accumulator.ts | 4 ++-- load-tests/ingest/README.md | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/internal/util/httputil/auth_error.go b/backend/internal/util/httputil/auth_error.go index 44f7e001..51306a1b 100644 --- a/backend/internal/util/httputil/auth_error.go +++ b/backend/internal/util/httputil/auth_error.go @@ -13,8 +13,7 @@ const AuthRealm = "trakrf-api" // in public and internal handlers should route through this helper so the // envelope, WWW-Authenticate header, and title are consistent. // -// detail is caller-supplied from a short canonical set — see -// docs/superpowers/specs/2026-04-20-tra-407-contract-bugs-design.md. +// detail is caller-supplied from a short canonical set — see TRA-407. func Respond401(w http.ResponseWriter, r *http.Request, detail, requestID string) { w.Header().Set("WWW-Authenticate", `Bearer realm="`+AuthRealm+`"`) WriteJSONError(w, r, http.StatusUnauthorized, apierrors.ErrUnauthorized, diff --git a/deploy/edge/README.md b/deploy/edge/README.md index 98ea38c6..0d6bc12b 100644 --- a/deploy/edge/README.md +++ b/deploy/edge/README.md @@ -2,8 +2,7 @@ Rootless Podman quadlets for the offline demo box (`trakrf-demo`). Hosts the backend + Timescale + Mosquitto + a Traefik TLS edge, all systemd-managed. -Design specs: `docs/superpowers/specs/2026-06-07-deploy-edge-design.md`, -`docs/superpowers/specs/2026-06-13-srv-trakrf-runtime-layout-design.md` (TRA-988). +Design context: TRA-988. Tim drives the demo from his laptop at **`https://app.demo.trakrf.id`** over the Slate WiFi. Break-glass = a shell over the tailnet (`systemctl --user`, diff --git a/frontend/src/worker/cs108/barcode/accumulator.ts b/frontend/src/worker/cs108/barcode/accumulator.ts index 8bea3c94..78ab9339 100644 --- a/frontend/src/worker/cs108/barcode/accumulator.ts +++ b/frontend/src/worker/cs108/barcode/accumulator.ts @@ -6,8 +6,8 @@ * terminated by 0x0D (CR). A single payload may contain multiple complete * records, or a record may span multiple payloads. * - * See `docs/superpowers/specs/2026-05-23-tra-821-barcode-fragment-reassembly-design.md` - * for the empirical firmware behaviors this class handles. + * The empirical firmware behaviors this class handles were characterised in + * TRA-821. */ const RECORD_TERMINATOR = 0x0D; diff --git a/load-tests/ingest/README.md b/load-tests/ingest/README.md index aebf0d24..cc90c65e 100644 --- a/load-tests/ingest/README.md +++ b/load-tests/ingest/README.md @@ -8,8 +8,7 @@ stack to characterize the ingestion path and tune the reader dedup window. The **reader's MQTT publish path is the bottleneck**, not the backend. At `dedup=0` a CS463 wedges its own publish in ~4 min (publish-buffer exhaustion); the single-replica backend consumer handled everything one reader could emit -(~270 reads/s) with zero drops. See -`docs/superpowers/specs/2026-06-16-cs463-ingest-load-and-dedup-design.md`. +(~270 reads/s) with zero drops. ## Golden config (validated, live on cs463-212 → preview) From b6d1fc39fe988b9531b2a9bfd9304d2687c639d1 Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Sun, 30 Aug 2026 05:51:25 -0500 Subject: [PATCH 3/4] docs: split shared working agreements into AGENTS.md TRA-1128. Shared process becomes a root AGENTS.md, byte-identical across docs, platform and infra, imported by CLAUDE.md via a bare @AGENTS.md. Root-level and conventionally named, so a human finds it and non-Claude tooling reads it. File pulled from trakrf/docs#218 rather than retyped: sha256 f01ea43d7154e3abe3096bf0edc3ead069ba9ac7064c00bc606fcfcb2fcb48d3 CLAUDE.md keeps only what is specific to this repo, stated rather than explained: 496 words to 209. Worktree conventions are out entirely -- that is csw.json configuration, not prose, and not a workflow to impose. CONTRIBUTING.md is the single source for branch, commit and merge rules, which required fixing three things it got wrong: - branch naming said feature/add-xyz, dead letter in every repo. Now /, with /- named for maintainers. An outside contributor has no ticket number and could not follow it. - conventional commits were marked "Optional ... no tool depends on it", contradicting CLAUDE.md, which stated them as a rule. They are required; enforcement is manual, which is a different claim. - no merge policy was stated at all. Silence in a contributor guide reads as no policy. Now: every change via PR, --merge, never squash or rebase. Import verified syntactically -- exactly one bare @ in the repo's instruction files, the intended one. Import parsing skips backticks, so a backticked path is silently not imported and a stray bare @ silently is. Not yet observed loading; /context is user-facing and confirms it. Refs: TRA-1128 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W7p2hFrE6ZFWS91XvyFQcV --- AGENTS.md | 28 +++++++++++++++++++++ CLAUDE.md | 67 ++++++++++++++++--------------------------------- CONTRIBUTING.md | 20 ++++++++++----- 3 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..1df5de96 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# AGENTS.md — trakrf working agreements + +Byte-identical in `docs`, `platform` and `infra`; each repo's `CLAUDE.md` imports +it and adds its own specifics. Public repos — write for a world reader. + +Only what a fresh session gets wrong otherwise. Branch, commit and merge rules +are in `CONTRIBUTING.md`, tooling choices in `.claude/csw.json`; neither is +restated here. `ble-mcp-test` is not in this family and keeps its own. + +## Specs, plans and notes are never tracked + +Per-session artifacts, disposable by decision. Ignored: `superpowers/`, +`docs/superpowers/`, `.superpowers/`, `docs/notes/` — each repo's `CLAUDE.md` +names which to write to. A plan worth a reviewer's time goes in the PR body. + +**Re-used, or merely re-read?** Re-used lives in the repo under a _behaviour_ +name — `describe` blocks, log prefixes and artifact dirs included. Merely +re-read belongs on the ticket, with the narrative and evidence. Durable +conclusions go to `CLAUDE.md`, an ADR or a code comment. Keep ticket ids out of +published prose. + +## Working rules + +- Ask when requirements are unclear; never delete code without instruction +- Verify before claiming done: run it, read the output, report what it said +- Verify the premise, not just the citation +- Search wide, then filter — absence from a narrowed search is not absence +- No invented imports; keep files under 500 lines diff --git a/CLAUDE.md b/CLAUDE.md index f6f0c12c..283283b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,51 +1,26 @@ # CLAUDE.md -## Package Managers -- **Backend**: `go mod`, `go get` -- **Frontend**: `pnpm` exclusively (`pnpm dlx` instead of `npx`) +@AGENTS.md -## Task Runner (Just) -- **Run everything from the project root** — delegate with `just ` rather than `cd`-ing in -- Workspaces: `frontend`, `backend`, `cli`, `database` (aliases `fe`, `be`, `db`) -- Combined across all four: `just lint`, `just test`, `just build`, `just validate` -- Every workspace justfile sets `fallback := true`, so root recipes still resolve from inside one - -## Cluster Ops -- `just ops [args]` forwards to the trakrf/infra justfile; bare `just ops` lists what's available -- Shortcuts: `just gcp-auth`, `just psql preview`, `just logs prod 1h` -- Cluster/namespace/pod knowledge stays in infra — never reimplement a kubectl incantation here -- Infra checkout is `TRAKRF_INFRA_DIR`, else a sibling `infra/`; set it in `.env.local` if yours is elsewhere - -## Git Workflow -- **Never push directly to main** — all changes via PR -- **Never squash merge** — `gh pr merge --merge` -- Branch naming: `/tra-NNNN-slug`, e.g. `feat/tra-1065-kitting-capability`, `fix/broken-xyz`, `chore/...`, `docs/...` -- Conventional commits: `feat:`, `fix:`, `docs:`, `chore:` -- Prefer incremental commits over amending - -## Migrations -- Adding a migration **requires** `just backend migrate-checksums` — CI fails without it, and applied migrations are immutable - -## Testing -- Playwright e2e **never runs in CI** — green CI does not mean e2e passes; run it against preview yourself -- **Name specs and tooling for what they do, never for a ticket** — `locate-mask-length-variants.spec.ts`, not `tra-1120-…`. Cite the ticket in the file header. Applies to `describe` blocks, log prefixes and artifact dirs, since those appear in output -- **Re-used vs merely re-read** — reusable things live in the repo under a behaviour name; point-in-time records go on the ticket, and working notes in `docs/notes/` stay untracked (see `.gitignore`) - -## Hardware access -- **`ble-mcp-test` is test tooling only, never the product path** — the app reaches a CS108 solely via browser `navigator.bluetooth` -- **One connection at a time**: a *connected client* blocks preview/prod hand-testing — a running bridge daemon does not. It holds the port, not the radio -- **An idle bridge port does *not* mean the reader is free** — a browser tab holding it appears in no process list. `get_connection_state` answers it in one call; see `docs/ble-hardware-access.md` -- The bridge is a supervised `systemctl --user` unit, so `pkill` is the wrong tool — it comes back in 5s - -## Preview Deployments -- Opening/updating a PR auto-deploys to `https://app.preview.trakrf.id` -- See `.github/workflows/sync-preview.yml` for details +Platform specifics only. Session artifacts go to `docs/superpowers/` and `docs/notes/`. ## Stack -- Go backend, React/TypeScript frontend, TimescaleDB, Go CLI, MQTT ingestion (`mqtt-rpc/`) -- Architecture context: `README.md`, `docs/architecture-decisions.md`, `docs/adr/`, `docs/logical-schema.md` - -## Worktrees -- Git worktrees live in `.claude/worktrees/` (gitignored) — where the native `EnterWorktree` tool writes -- **`just bootstrap` first, backgrounded** — generates the gitignored `go:embed` targets; without it `just validate` fails with `pattern frontend/dist: no matching files found`. Idempotent, near-instant warm -- **An unbootstrapped tree invalidates verifications** — everything fails, so a deliberate-break check proves nothing. Confirm bootstrap ran before believing a failure +Go backend · React/TypeScript frontend · TimescaleDB · Go CLI · MQTT ingestion (`mqtt-rpc/`). +Architecture: `README.md`, `docs/architecture-decisions.md`, `docs/adr/`, `docs/logical-schema.md`. + +## Tooling +- Backend `go mod`; frontend `pnpm` only — `pnpm dlx`, never `npx` +- Run `just` from the project root: `just `, workspaces `frontend` `backend` `cli` `database` (`fe` `be` `db`) +- `just ops ` forwards to trakrf/infra and bare `just ops` lists them; cluster knowledge stays there. Checkout via `TRAKRF_INFRA_DIR`, else sibling `infra/` + +## Gotchas +- A new migration requires `just backend migrate-checksums`; applied migrations are immutable +- Playwright e2e never runs in CI — run it against preview yourself +- `just bootstrap` a fresh worktree before validating, or the `go:embed` targets are missing and every check fails for that reason alone +- Opening a PR auto-deploys to `https://app.preview.trakrf.id` (`.github/workflows/sync-preview.yml`) + +## Hardware +- `ble-mcp-test` is test tooling only; the app reaches a CS108 via browser `navigator.bluetooth` +- One connection at a time: a connected client blocks hand-testing, a running daemon does not +- An idle bridge port does not mean the reader is free — `get_connection_state` answers it; see `docs/ble-hardware-access.md` +- The bridge is a supervised `systemctl --user` unit; `pkill` returns in 5s diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e363a931..24364bc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,12 +42,16 @@ cd ../frontend && pnpm test ### 1. Create a Branch ```bash -# Branch naming: -# - feature/add-xyz (new features) +# Branch naming: / +# - feat/add-xyz (new features) # - fix/broken-xyz (bug fixes) # - docs/update-xyz (documentation) +# - chore/tidy-xyz (everything else) +# +# Maintainers working a tracked issue use /-, +# e.g. feat/tra-1065-kitting-capability. -git checkout -b feature/add-asset-history +git checkout -b feat/add-asset-history ``` ### 2. Write Your Code @@ -144,7 +148,7 @@ cd tests/api && pnpm test 1. **Push to your fork:** ```bash - git push origin feature/add-asset-history + git push origin feat/add-asset-history ``` 2. **Open a Pull Request:** @@ -160,6 +164,10 @@ cd tests/api && pnpm test - [ ] API documentation updated - [ ] Commit messages use conventional format +4. **How changes land:** + - Every change goes through a PR. Nothing is pushed directly to `main`. + - PRs are merged with `gh pr merge --merge` — never squash, never rebase. + ## Common Tasks ### Adding a New API Endpoint @@ -243,8 +251,8 @@ release. Do not couple them. ### Conventional Commits -Optional. The git-log readability convention (`feat:`, `fix:`, `chore:`, -`docs:`) is encouraged but no tool depends on it — bumps are manual. +Required. Use `feat:`, `fix:`, `docs:` or `chore:`. No tool enforces it and +version bumps are manual, but the convention is not optional. ## Getting Help From 37b895659b9d834b99fdfaecae4d9d2e899c4da6 Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Mon, 31 Aug 2026 06:25:04 -0500 Subject: [PATCH 4/4] docs: cut the AGENTS.md preamble to the clauses that constrain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TRA-1128. The shared file carried six lines of preamble explaining its own mechanism, which violated the state-do-not-explain bar the file itself sets. Owner's catch. Removed as description: how CLAUDE.md imports it (the reader already has the file), why the pointers exist, and the note that ble-mcp-test is out of the family (no session in these three repos asks). Kept as constraint: identical across three repos, the repos are public, the scope bar, and both pointers as routing. Six lines to three, 212 words to 187, no rule lost. Pulled from trakrf/docs#218, not retyped: sha256 8aae612ddf7bea89358c13df03ae395aef041533afb019406c15d7c9bd9ac3c1 Supersedes f01ea43d…, which is now dead and which two open PRs still carry. A superseded hash is indistinguishable from a current one, so the target is the branch, not any hash quoted in a message. Refs: TRA-1128 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W7p2hFrE6ZFWS91XvyFQcV --- AGENTS.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1df5de96..bd94ac2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,11 +1,8 @@ # AGENTS.md — trakrf working agreements -Byte-identical in `docs`, `platform` and `infra`; each repo's `CLAUDE.md` imports -it and adds its own specifics. Public repos — write for a world reader. - -Only what a fresh session gets wrong otherwise. Branch, commit and merge rules -are in `CONTRIBUTING.md`, tooling choices in `.claude/csw.json`; neither is -restated here. `ble-mcp-test` is not in this family and keeps its own. +Identical in `docs`, `platform` and `infra` — change it in all three or none. +These repos are public. Add only what a fresh session gets wrong otherwise. +Branches, commits, merges: `CONTRIBUTING.md`. Tooling: `.claude/csw.json`. ## Specs, plans and notes are never tracked