# Agent Guidelines — QMK RGB Tool ## Read README.md First `README.md` is the single source of truth for this project's behaviour: the command surface, the effect catalog, zone semantics, the protocol, the keyboard list and platform setup. **Read it in full before you work on this repository.** This file deliberately does not repeat any of it. A second copy is a second thing that can go stale, and that has happened: a command prefix that had not existed for many commits survived in eleven lines of prose, and a `keyboards.json` field was documented here as load-bearing while the code never read it. So the split is: - `README.md` — what the software does. Reference. - `AGENTS.md` — how to work on it. Discipline. - `.claude/skills/qmk-rgb/SKILL.md` — the live-query workflow for the tool. For the effect catalog specifically, read it from no document at all. Run `qmk-rgb-tool effect --list` and use the output. Logo and Side accept fewer effects than Backlight, and only the tool knows what the current firmware supports. ## Objective Cross-platform Go CLI library for programmatic/agent-friendly control of QMK keyboard RGB lighting. ## Architecture ``` cmd/ # Cobra-based CLI entrypoints internal/device/ # QMK Raw HID discovery and keyboard numbering internal/hid/ # Cross-platform HID access internal/via/ # VIA protocol implementation + LED subsystem internal/rgb/ # Effects, values, color definitions keyboards.json # Optional name/metadata lookup for known keyboards ``` ## Device Selection Discovery matches every connected keyboard exposing the QMK Raw HID signature (Usage Page `0xFF60`, Usage `0x61`) — the same check `qmk/qmk_udev` performs. `keyboards.json` is only consulted to attach a name to a recognized model; an absent or malformed file costs names, not the ability to drive the keyboard. `keyboard info` numbers the connected keyboards from 1 in a stable order (vendor ID, product ID, path). `--device` accepts that number, never a HID path. Without `--device`, commands proceed only when exactly one keyboard is connected; otherwise they fail and list the selectable numbers, so a command never targets an unintended keyboard. Device numbers are stable for the current session only — HID paths are reassigned on reboot and most keyboards report no serial number. `--zone` accepts `logo`, `backlight`, or `side` and may be given on any subcommand. It is a persistent flag in cobra's sense only — accepted on the root and inherited by subcommands — and nothing is remembered between runs, so it must be repeated on every invocation. Without `--zone`, commands target Logo, Backlight, and Side in that order. With `--zone`, commands target exactly one zone. Unsupported default targets are skipped with a stderr warning; unsupported explicit-zone effects and unknown names fail before the device is opened. ## Stack - Go 1.26+ (cross-platform: Linux, macOS, Windows) - `github.com/sstallion/go-hid` for HID access - `github.com/spf13/cobra` for CLI framework ## Constraints - CLI-first, no GUI — designed for automation and agent consumption - Output should be machine-parseable (JSON when possible) - Cross-platform from day one ## Consistency Documentation describes real, verifiable behavior. Names are only the start — behavior counts too. Before any edit: 1. **Binary name** — the `go build -o` target (usually `$GOBIN/$module_name`) must match every example in README, AGENTS.md, comments, and docs. 2. **CLI command** — the root command name (e.g. `qmk-rgb-tool`) is the binary name; every usage example, CLI reference table, and doc must use the same string. 3. **Module path** — the `go.mod` module path is the source of truth for `go install` targets. 4. **Zone names** — `logo`, `backlight`, `side` must be identical in code, docs, and examples. Documented behavior must also match the code: 5. **Flags and accepted values** — what a flag accepts and rejects, its type, its default, and whether it persists across commands. A doc promising `--device 1` must not accept a path. 6. **Error messages and exit codes** — the exact strings a user is told to look for, which error takes precedence when several apply, and when JSON is still printed before a non-zero exit. 7. **JSON output shape** — field names, types, and whether a field is always present, omitted, or `null`. Agents and scripts parse this. 8. **Ranges, defaults, and stability** — accepted ranges, default values, boundary behavior, and any ordering or stability guarantee together with the scope it holds over (a device number is stable for one session, not across reboots). ## Firmware Transforms Values The Impact 80 firmware rescales or clamps brightness and speed per channel, so an accepted 0–255 request is not the value the keyboard holds. `logo` and `side` cap brightness at 160 and collapse any speed above 0 to 4, while `backlight` scales brightness up to 255 and applies speed as given. README.md tabulates the per-channel behaviour; the rule that follows from it is the part that matters when you write code here: `brightness` and `speed` read every selected zone back and print what was actually applied; where all zones match they print `Brightness set to N` or `Speed set to N`, and where any zone differs they print one summary line naming each zone's real value and the request. Never let a command report success for a value the keyboard did not accept. Both read back through the shared `setValueVerified`, so a future parameter needs no new read-back path. When you see a name or a behavior in one file, grep for it across the whole repo before deciding if a change is consistent. ## Code vs Documentation All documentation (README.md, comments, AGENTS.md) must stay in sync with the code. When code and documentation conflict, **ask the user** before deciding which one to change. Do not silently pick a winner — explicitly state the conflict and get direction. ## Go Development ### Tooling - `gopls` — Go Language Server, installed separately with `go install golang.org/x/tools/gopls@latest` (LSP, diagnostics, go-to-def, rename, references) - `gofmt` / `goimports` — formatting. `goimports` adds/removes imports automatically. Always run before committing. - `go test` — built-in test framework. Tests live in `*_test.go` files alongside source. - `go vet` — static analysis. Run before committing. - `go build` — compiles without installing. Fast, cached. - `go mod tidy` — adds missing deps, removes unused ones. Run after every import change. ### Style (Effective Go + Code Review Comments) - **Formatting**: always `gofmt` or `goimports`. Tabs for indentation, no line-length limit but avoid uncomfortably long lines. - **Names**: `MixedCaps` / `mixedCaps`, no underscores. Short local variables (`i`, `c`, `r`). Descriptive for globals. - **Initialisms**: consistent case — `URL`, `ID`, `HTTP` → `appID`, `urlPony`, `ServeHTTP`. - **Comments**: doc comments start with the name, end with period. `// Package rgb provides RGB effect handling.` - **Imports**: standard library first, blank line, then third-party. Grouped. ### Error Handling - Return errors, never `panic` for normal control flow. - Indent error flow early — happy path at minimal indentation: ```go if err != nil { return err } // normal code ``` - Error strings: lowercase, no period — `fmt.Errorf("device not found")`. - Do not discard errors with `_`. ### Interfaces & Types - Define interfaces in the **consumer** package, not the producer. - Use pointer receivers when in doubt (mutation, large structs, sync fields). - Return concrete types from constructors — let consumers mock if needed. - Empty slices: `var t []string` (nil), not `t := []string{}` (non-nil), unless JSON encoding requires `[]`. ### Concurrency - Pass `context.Context` as **first parameter**. Never store in structs. - Clear goroutine lifetimes — document when/why they exit. Avoid leaks via channels. - Prefer synchronous functions — callers can add concurrency, not remove it. ### Testing - Table-driven tests preferred for multiple cases. - Failure messages: `t.Errorf("Effect(%q) = %d, want %d", input, got, want)`. - Use `*_test.go` files. Test package should match source package (internal tests). - Example functions (`func Example...`) double as docs and tests. ### Modules & Packages - Module path: `github.com//` (when published). - Package names: short, single-word, lowercase. No `util`, `common`, `api`, `types`. - Zero values should be useful (e.g. `bytes.Buffer`, `sync.Mutex`). ## graphify This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: - For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).