Ver Fonte

make README the single source of truth for documented behaviour

AGENTS.md duplicated six of its sections from README.md: the command table,
the effect catalog, the known VID/PID list, the architecture, the protocol
essentials and the firmware transform table. That is 90 of 235 lines, and
duplication is what went stale here. A `rgb <subcommand>` prefix that stopped
existing in e7dd8f6 survived in eleven lines of prose across both files, and
keyboards.json's ledLayout was documented in AGENTS.md as load-bearing while
the code never read it.

AGENTS.md now opens with a directive to read README.md in full and explains why
it carries no second copy. The reference sections are gone; what remains is
discipline with no other home: the consistency rules, the rule that documented
behaviour must match the code, the rule to ask before resolving a doc/code
conflict, and the Go craft guidance.

Kept the two things that were discipline rather than reference. The device
selection rules still say what --device accepts and why numbers are session
scoped, because a command must never hit an unintended keyboard. The firmware
transform rule still says never report success for a value the keyboard did not
accept, and points at setValueVerified; the per-channel numbers moved to README
where they are now maintained in one place.

The effect catalog is documented in no file at all. AGENTS.md tells agents to
run `qmk-rgb-tool effect --list` and use the output, since only the tool knows
what the current firmware accepts per zone.

Two statements existed only in AGENTS.md and would have been lost: that the
keyboard list is matched by VID/PID name lookup only, and that enable and
disable use no separate handshake. Both moved into README, which is now the
reference the file points at.

Three tests keep the split from collapsing. Two read AGENTS.md and fail if an
effect name, a per-zone ID range or an invocation reappears in it; the third
fails if the instruction to read README.md is dropped. Verified they bite by
re-adding catalog entries to AGENTS.md.
Paul Klumpp há 2 semanas atrás
pai
commit
fd34b1d03f
3 ficheiros alterados com 121 adições e 85 exclusões
  1. 29 85
      AGENTS.md
  2. 3 0
      README.md
  3. 89 0
      cmd/qmk-rgb-tool/agents_doc_test.go

+ 29 - 85
AGENTS.md

@@ -1,5 +1,26 @@
 # 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.
 
@@ -14,30 +35,6 @@ internal/rgb/           # Effects, values, color definitions
 keyboards.json          # Optional name/metadata lookup for known keyboards
 ```
 
-## CLI Interface
-
-```
-qmk-rgb-tool keyboard info
-qmk-rgb-tool effect breathing
-qmk-rgb-tool effect rainbow_moving_chevron
-qmk-rgb-tool effect rainbow_moving_chevron --zone backlight
-qmk-rgb-tool effect --list            # Every effect per zone (JSON)
-qmk-rgb-tool brightness <val>       # 0-255, verified by read-back
-qmk-rgb-tool speed <val>            # 0-255, verified by read-back
-qmk-rgb-tool color <hex>            # Six hexadecimal digits
-qmk-rgb-tool mode <index>           # Raw zone-specific effect ID
-qmk-rgb-tool enable
-qmk-rgb-tool disable
-qmk-rgb-tool info
-qmk-rgb-tool save [name]        # Save current RGB state to a profile
-qmk-rgb-tool load [name]        # Load a profile and apply it to the keyboard
-qmk-rgb-tool delete [name]      # Delete a saved profile
-qmk-rgb-tool list               # List saved profiles
-qmk-rgb-tool --device <n> ...   # Target keyboard number from `keyboard info`
-```
-
-`save`, `load` and `delete` take an optional name and default to `default`.
-
 ## Device Selection
 
 Discovery matches every connected keyboard exposing the QMK Raw HID signature
@@ -61,61 +58,6 @@ 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.
 
-### Impact 80 Effect Families
-
-Logo and Side use IDs 0–6: `none`, `wave`, `fixed_wave`, `spectrum`,
-`breathing`, `light`, and `shutdown`.
-
-Backlight uses the complete ID 0–45 family:
-
-```text
-none, solid_color, alphas_mods, gradient_up_down, gradient_left_right,
-breathing, band_sat, band_val, band_pinwheel_sat, band_pinwheel_val,
-band_spiral_sat, band_spiral_val, cycle_all, cycle_left_right,
-cycle_up_down, cycle_out_in, cycle_out_in_dual, rainbow_moving_chevron,
-cycle_pinwheel, cycle_spiral, dual_beacon, rainbow_beacon,
-rainbow_pinwheels, flower_blooming, raindrops, jellybean_raindrops,
-hue_breathing, hue_pendulum, hue_wave, pixel_flow, digital_rain,
-solid_reactive, solid_reactive_wide, solid_reactive_multiwide,
-solid_reactive_cross, solid_reactive_multicross, solid_reactive_nexus,
-solid_reactive_multinexus, splash, multisplash, solid_splash,
-solid_multisplash, starlight, starlight_dual_hue, starlight_dual_sat,
-riverflow
-```
-
-ID 39 is `multisplash`; ID 41 is `solid_multisplash`. Supported aliases are
-`off` → `none`, `breathe` → `breathing`, `rainbow` (zone-dependent),
-`rainbow_wave` (Logo/Side), `solid` (zone-dependent), and legacy `static`.
-Do not assume a numeric effect ID is valid on every zone; use the zone-aware
-name resolver or deliberately use `mode` as a raw escape hatch.
-
-`info` emits a `zones` array containing each selected zone's channel,
-enabled state, effect name and ID, brightness, speed, and color. The
-top-level summary comes from the first selected zone. A failed zone includes
-an `error` field while other zone results remain available; the command
-prints JSON before returning non-zero.
-
-## VIA Protocol Essentials
-
-- Transport: QMK Raw HID over a 32-byte hidraw report, report number `0`
-- Message types:
-  - `0x07` — Custom set value
-  - `0x08` — Custom get value
-- Impact 80 lighting channels: `0x02` logo, `0x03` backlight, `0x04` side
-- RGB value IDs: brightness `0x01`, effect `0x02`, speed `0x03`, color `0x04`
-- No separate enable handshake is used
-
-## Keyboards (Known VID/PID)
-
-| Keyboard         | VID    | PID    |
-|------------------|--------|--------|
-| Wobkey Rainy 75  | 0x6666 | 0x0001 |
-| Wobkey Impact 80 | 0x36B0 | 0x309F |
-
-`keyboards.json` maps VID+PID → display name. Only `name` is read by the
-code; unknown fields in the file are ignored, so entries may carry extra keys
-without affecting discovery.
-
 ## Stack
 
 - Go 1.26+ (cross-platform: Linux, macOS, Windows)
@@ -148,12 +90,14 @@ Documented behavior must also match the code:
 ## 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.
-
-`brightness` and `speed` therefore read every selected zone back and print what
-was actually applied; where all zones match they print `Brightness set to N` or
+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

+ 3 - 0
README.md

@@ -336,3 +336,6 @@ Communicates via the QMK Raw HID interface (`Usage Page 0xFF60`, `Usage 0x61`) w
 - `0x08` — Custom get value
 - Lighting channels: `0x02` logo, `0x03` backlight, `0x04` side lighting
 - RGB values: brightness `0x01`, effect `0x02`, speed `0x03`, color `0x04`
+
+`enable` and `disable` use no separate handshake: they set the effect ID and
+brightness, and nothing else.

+ 89 - 0
cmd/qmk-rgb-tool/agents_doc_test.go

@@ -0,0 +1,89 @@
+package main
+
+import (
+	"os"
+	"strings"
+	"testing"
+)
+
+// README.md is the single source of truth for documented behaviour; agents are
+// told to read it whole. AGENTS.md therefore carries discipline, not a second
+// copy of the reference. Duplicated reference is what went stale: the
+// `rgb <subcommand>` prefix survived in eleven lines of prose, and ledLayout was
+// promised in AGENTS.md while the code never read it.
+//
+// These tests fail if the duplication creeps back.
+func TestAgentsDocDoesNotDuplicateTheEffectCatalog(t *testing.T) {
+	data, err := os.ReadFile("../../AGENTS.md")
+	if err != nil {
+		t.Fatalf("read AGENTS.md: %v", err)
+	}
+	body := string(data)
+
+	// Effect names that exist only as catalog entries. The Discipline chapters
+	// legitimately mention a few by name, so match a representative set of
+	// obscure ones plus the literal range statements.
+	for _, name := range []string{
+		"jellybean_raindrops", "band_pinwheel_sat", "hue_pendulum",
+		"starlight_dual_sat", "solid_reactive_multinexus", "riverflow",
+		"multisplash",
+	} {
+		if strings.Contains(body, name) {
+			t.Errorf("AGENTS.md lists the effect %q; the catalog comes live from `qmk-rgb-tool effect --list`", name)
+		}
+	}
+
+	for _, rangeStmt := range []string{"ID 0–45 family", "ID 0-45 family", "IDs 0–45", "IDs 0-45"} {
+		if strings.Contains(body, rangeStmt) {
+			t.Errorf("AGENTS.md states %q; the per-zone catalog comes from `qmk-rgb-tool effect --list`", rangeStmt)
+		}
+	}
+}
+
+func TestAgentsDocDoesNotDuplicateTheCommandTable(t *testing.T) {
+	data, err := os.ReadFile("../../AGENTS.md")
+	if err != nil {
+		t.Fatalf("read AGENTS.md: %v", err)
+	}
+	body := string(data)
+
+	// A fenced block enumerating invocations is the duplicated CLI table.
+	commands := []string{
+		"qmk-rgb-tool effect breathing",
+		"qmk-rgb-tool brightness <val>",
+		"qmk-rgb-tool color <hex>",
+		"qmk-rgb-tool speed <val>",
+		"qmk-rgb-tool mode <index>",
+	}
+	for _, cmd := range commands {
+		if strings.Contains(body, cmd) {
+			t.Errorf("AGENTS.md lists the invocation %q; the command surface lives in README.md", cmd)
+		}
+	}
+}
+
+func TestAgentsDocTellsAgentsToReadTheReadme(t *testing.T) {
+	data, err := os.ReadFile("../../AGENTS.md")
+	if err != nil {
+		t.Fatalf("read AGENTS.md: %v", err)
+	}
+	body := string(data)
+
+	if !strings.Contains(body, "README.md") {
+		t.Fatal("AGENTS.md never points at README.md, so nothing tells an agent to read the real reference")
+	}
+
+	// The instruction must be a directive, not a passing mention.
+	lowered := strings.ToLower(body)
+	directives := []string{"read it in full", "read it whole", "read it entirely"}
+	found := false
+	for _, directive := range directives {
+		if strings.Contains(lowered, directive) {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Errorf("AGENTS.md does not instruct the agent to read README.md in full; wanted one of %v", directives)
+	}
+}