# `keyboard` — keyboard adjacency matcher _(no submodules)_ ## Purpose Detects spatial walks across a physical keyboard layout (QWERTY, DVORAK, AZERTY, or any caller-supplied adjacency graph) per Wheeler 2016 section 3.5. A password like `qwerty` traces a straight horizontal walk along the top row; the matcher reports its length, the number of times the walker changed direction, and how many shift-required presses it contained — all inputs to the keyboard guess estimate in section 4.5. ## Features - Pluggable graph map via the `graphs` option - Counts both `turns` (direction changes) and `shiftedCount` (capital / symbol presses) - Default graph is QWERTY; alternative layouts ship under `graphs/{dvorak,azerty}` subpaths - Rejects malformed graphs (self-adjacency or asymmetric edges) via `validateGraph` → `PWSTRENGTH.GRAPH_INVALID` ## Usage: Basic Example ```ts import { keyboardMatch } from "@suaveplan/password-strength/matchers/keyboard"; const matches = keyboardMatch("qwerty"); console.log(matches[0]); // { graph: "qwerty", turns: 0, shiftedCount: 0, … } ``` ## Usage: Advanced Example ```ts import { keyboardMatch } from "@suaveplan/password-strength/matchers/keyboard"; import { DVORAK_GRAPH } from "@suaveplan/password-strength/graphs/dvorak"; const matches = keyboardMatch("aoeuhtns", { graphs: { dvorak: DVORAK_GRAPH } }); for (const m of matches) { console.log(m.token, m.graph, m.turns, m.shiftedCount); } ``` ## API Reference - `keyboardMatch(password: string, opts?: KeyboardMatchOptions): KeyboardMatch[]` - `opts.graphs`: `Record` — override the default QWERTY-only graph set. Each graph is validated before use. - `KeyboardMatchOptions`: `{ graphs?: KeyboardGraphMap }` ## Implementation Notes Each walk advances character by character; a transition is accepted iff the target character appears in the current key's adjacency slots (six slots per key, encoding the six surrounding keys on a staggered layout). A direction change increments `turns`; a press whose target matches the shifted half of the slot increments `shiftedCount`. The walk continues greedily until the chain breaks; the matcher then restarts at the following index. Graph validation rejects malformed graphs upfront so the matcher never produces self-loops or asymmetric walks.