@kiwidata/grimoire 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/AGENTS.md +56 -4
  2. package/README.md +28 -1
  3. package/dist/cli/index.js +2 -0
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/check.js +1 -1
  6. package/dist/commands/check.js.map +1 -1
  7. package/dist/commands/configure.d.ts +3 -0
  8. package/dist/commands/configure.d.ts.map +1 -0
  9. package/dist/commands/configure.js +19 -0
  10. package/dist/commands/configure.js.map +1 -0
  11. package/dist/commands/init.d.ts.map +1 -1
  12. package/dist/commands/init.js +2 -0
  13. package/dist/commands/init.js.map +1 -1
  14. package/dist/commands/map.d.ts.map +1 -1
  15. package/dist/commands/map.js +10 -11
  16. package/dist/commands/map.js.map +1 -1
  17. package/dist/core/archive.d.ts.map +1 -1
  18. package/dist/core/archive.js +32 -43
  19. package/dist/core/archive.js.map +1 -1
  20. package/dist/core/check.d.ts.map +1 -1
  21. package/dist/core/check.js +115 -104
  22. package/dist/core/check.js.map +1 -1
  23. package/dist/core/ci.d.ts.map +1 -1
  24. package/dist/core/ci.js +50 -69
  25. package/dist/core/ci.js.map +1 -1
  26. package/dist/core/configure.d.ts +14 -0
  27. package/dist/core/configure.d.ts.map +1 -0
  28. package/dist/core/configure.js +434 -0
  29. package/dist/core/configure.js.map +1 -0
  30. package/dist/core/detect.d.ts.map +1 -1
  31. package/dist/core/detect.js +153 -26
  32. package/dist/core/detect.js.map +1 -1
  33. package/dist/core/diff.d.ts.map +1 -1
  34. package/dist/core/diff.js +62 -93
  35. package/dist/core/diff.js.map +1 -1
  36. package/dist/core/doc-style.d.ts +0 -4
  37. package/dist/core/doc-style.d.ts.map +1 -1
  38. package/dist/core/doc-style.js +28 -23
  39. package/dist/core/doc-style.js.map +1 -1
  40. package/dist/core/docs.js +106 -100
  41. package/dist/core/docs.js.map +1 -1
  42. package/dist/core/health.js +55 -77
  43. package/dist/core/health.js.map +1 -1
  44. package/dist/core/hooks.d.ts +0 -3
  45. package/dist/core/hooks.d.ts.map +1 -1
  46. package/dist/core/hooks.js +0 -11
  47. package/dist/core/hooks.js.map +1 -1
  48. package/dist/core/init.d.ts +2 -0
  49. package/dist/core/init.d.ts.map +1 -1
  50. package/dist/core/init.js +230 -406
  51. package/dist/core/init.js.map +1 -1
  52. package/dist/core/list.d.ts.map +1 -1
  53. package/dist/core/list.js +55 -65
  54. package/dist/core/list.js.map +1 -1
  55. package/dist/core/log.d.ts.map +1 -1
  56. package/dist/core/log.js +23 -33
  57. package/dist/core/log.js.map +1 -1
  58. package/dist/core/map.d.ts +15 -2
  59. package/dist/core/map.d.ts.map +1 -1
  60. package/dist/core/map.js +257 -194
  61. package/dist/core/map.js.map +1 -1
  62. package/dist/core/shared-setup.d.ts +0 -40
  63. package/dist/core/shared-setup.d.ts.map +1 -1
  64. package/dist/core/shared-setup.js +87 -52
  65. package/dist/core/shared-setup.js.map +1 -1
  66. package/dist/core/status.d.ts.map +1 -1
  67. package/dist/core/status.js +42 -52
  68. package/dist/core/status.js.map +1 -1
  69. package/dist/core/test-quality.d.ts +0 -8
  70. package/dist/core/test-quality.d.ts.map +1 -1
  71. package/dist/core/test-quality.js +24 -30
  72. package/dist/core/test-quality.js.map +1 -1
  73. package/dist/core/trace.d.ts.map +1 -1
  74. package/dist/core/trace.js +31 -41
  75. package/dist/core/trace.js.map +1 -1
  76. package/dist/core/update.d.ts.map +1 -1
  77. package/dist/core/update.js +61 -11
  78. package/dist/core/update.js.map +1 -1
  79. package/dist/core/validate.d.ts +1 -4
  80. package/dist/core/validate.d.ts.map +1 -1
  81. package/dist/core/validate.js +126 -148
  82. package/dist/core/validate.js.map +1 -1
  83. package/dist/utils/config.d.ts +15 -5
  84. package/dist/utils/config.d.ts.map +1 -1
  85. package/dist/utils/config.js +63 -42
  86. package/dist/utils/config.js.map +1 -1
  87. package/dist/utils/fs.d.ts +0 -12
  88. package/dist/utils/fs.d.ts.map +1 -1
  89. package/dist/utils/fs.js +0 -12
  90. package/dist/utils/fs.js.map +1 -1
  91. package/dist/utils/paths.d.ts +0 -6
  92. package/dist/utils/paths.d.ts.map +1 -1
  93. package/dist/utils/paths.js +0 -6
  94. package/dist/utils/paths.js.map +1 -1
  95. package/dist/utils/spawn.d.ts +0 -3
  96. package/dist/utils/spawn.d.ts.map +1 -1
  97. package/dist/utils/spawn.js +0 -3
  98. package/dist/utils/spawn.js.map +1 -1
  99. package/package.json +1 -1
  100. package/skills/grimoire-apply/SKILL.md +84 -16
  101. package/skills/grimoire-audit/SKILL.md +21 -1
  102. package/skills/grimoire-bug/SKILL.md +48 -9
  103. package/skills/grimoire-commit/SKILL.md +2 -1
  104. package/skills/grimoire-design/SKILL.md +259 -0
  105. package/skills/grimoire-design-consult/SKILL.md +200 -0
  106. package/skills/grimoire-discover/SKILL.md +65 -2
  107. package/skills/grimoire-draft/SKILL.md +85 -2
  108. package/skills/grimoire-plan/SKILL.md +61 -18
  109. package/skills/grimoire-pr/SKILL.md +4 -6
  110. package/skills/grimoire-pr-review/SKILL.md +45 -114
  111. package/skills/grimoire-precommit-review/SKILL.md +205 -0
  112. package/skills/grimoire-refactor/SKILL.md +5 -5
  113. package/skills/grimoire-review/SKILL.md +74 -147
  114. package/skills/grimoire-verify/SKILL.md +33 -0
  115. package/skills/references/adversarial-personas.md +225 -0
  116. package/skills/references/brand-tokens-format.md +186 -0
  117. package/skills/references/code-quality.md +140 -0
  118. package/skills/references/design-heuristics.md +138 -0
  119. package/skills/references/design-input-formats.md +190 -0
  120. package/skills/references/pattern-guard.md +180 -0
  121. package/skills/references/refactor-scan-categories.md +152 -0
  122. package/skills/references/review-personas.md +405 -0
  123. package/skills/references/security-compliance.md +22 -1
  124. package/skills/references/visual-fidelity.md +206 -0
  125. package/templates/brand-tokens-example.json +13 -0
  126. package/templates/brand-voice-example.md +22 -0
  127. package/templates/design-tool-setup-stub.md +59 -0
@@ -0,0 +1,225 @@
1
+ # Adversarial Personas Reference
2
+
3
+ Loaded by `grimoire-review`, `grimoire-pr-review`, and `grimoire-precommit-review` when the change touches a user-facing surface. Defines the surface-conditional adversarial personas that complement the engineering / product / security personas in `./review-personas.md`.
4
+
5
+ Each persona inhabits a user the design might fail. Their job is to find the failure paths a happy-path-focused reviewer would miss: the keyboard-only user who can't tab to the submit button, the screen-reader user lost in an unlabelled icon grid, the user on a 3G connection waiting for a 4 MB hero image.
6
+
7
+ The set engaged on a given review is filtered by `project.surface` (see §Activation Matrix). All personas inherit the project briefing (§1 of `./review-personas.md`), the materiality gate (§2), and the steel-man requirement (§2a).
8
+
9
+ ---
10
+
11
+ ## Persona Catalog
12
+
13
+ Each persona below names: **identity** (whose constraints they hold), **evaluation criteria** (what they look at), **what triggers a finding** (the concrete observation that becomes feedback).
14
+
15
+ ### Keyboard-Only User
16
+
17
+ - **Identity**: Power user navigating exclusively via keyboard (Tab, Shift-Tab, arrow keys, Enter, Esc). Mouse unavailable due to RSI, motor impairment, terminal-only environment, or preference for speed.
18
+ - **Evaluation criteria**:
19
+ - Every interactive element reachable via Tab in a sensible order
20
+ - Focus indicator visible on every focusable element
21
+ - No keyboard traps (except modal dialogs, which must escape on Esc)
22
+ - Shortcuts documented and consistent (don't shadow OS / browser defaults)
23
+ - Custom controls (combobox, datepicker, drag-drop) implement full keyboard interaction patterns per WAI-ARIA Authoring Practices
24
+ - **Triggers a finding**:
25
+ - Click-only handler without keyboard equivalent
26
+ - Focus indicator suppressed (`outline: none` without replacement)
27
+ - Tab order jumps visually unrelated regions
28
+ - Modal dialog cannot be dismissed with Esc
29
+ - Custom dropdown reachable but not operable from keyboard
30
+
31
+ ### Screen-Reader User
32
+
33
+ - **Identity**: User of VoiceOver (macOS/iOS), NVDA (Windows), JAWS (Windows), or TalkBack (Android). Navigates by reading aloud announcements; relies on semantic markup and ARIA.
34
+ - **Evaluation criteria**:
35
+ - Semantic HTML used in preference to ARIA (`<button>` not `<div role="button">`)
36
+ - Every form input has a programmatically-associated label
37
+ - Icons that convey meaning have accessible names (`aria-label` or visually-hidden text)
38
+ - Dynamic content changes are announced (live regions or focus shifts)
39
+ - Heading hierarchy is meaningful (H1 once, H2 sections, no skipped levels)
40
+ - Image alt text describes meaning, not appearance (decorative images: `alt=""`)
41
+ - **Triggers a finding**:
42
+ - Icon-only button without accessible name
43
+ - Form field with placeholder used as the only label
44
+ - Live error announcement missing (form submit → silent failure)
45
+ - Decorative SVG read aloud as a long filename
46
+ - `<div onclick>` instead of `<button>`
47
+
48
+ ### Low-Vision / Color-Blind User
49
+
50
+ - **Identity**: User with low vision (uses browser zoom 200%+, OS magnifier), or color vision deficiency (deuteranopia, protanopia, tritanopia, monochromacy — affects ~8% of men, ~0.5% of women).
51
+ - **Evaluation criteria**:
52
+ - Body text contrast ≥ 4.5:1 (WCAG AA)
53
+ - UI component contrast ≥ 3:1 vs adjacent
54
+ - Information not conveyed by color alone (status badges have icons or text; error fields have text labels not just red borders)
55
+ - Page reflows cleanly at 200% zoom (no horizontal scroll, no overlapping content)
56
+ - Text scales when user adjusts browser font size (no fixed `px` font sizes on body text where `rem` would work)
57
+ - **Triggers a finding**:
58
+ - Gray text on white below 4.5:1 (the perennial offender)
59
+ - Red/green status indicator with no icon or text differentiator
60
+ - Layout breaks at 200% zoom (content cut off, controls overlap)
61
+ - Required-field marker conveyed only by red asterisk color (no actual asterisk character)
62
+
63
+ ### Touch-Target User
64
+
65
+ - **Identity**: Mobile user, often one-handed, often with imprecise thumb input (in motion, accessibility-impaired motor control, or just normal use on a small screen).
66
+ - **Evaluation criteria**:
67
+ - Interactive targets ≥ 24×24 CSS pixels (WCAG 2.2 AA minimum); ≥ 44×44 preferred (iOS HIG)
68
+ - 8px+ spacing between adjacent targets
69
+ - Primary actions reachable in the thumb zone (bottom half of phone screen)
70
+ - No hover-only affordances (hover doesn't exist on touch)
71
+ - Long-press, swipe, pinch interactions have a tap-only equivalent
72
+ - **Triggers a finding**:
73
+ - Buttons sized below 24×24 in the actual rendered design
74
+ - Adjacent links in a list with no spacing (mis-tap risk)
75
+ - Primary CTA in the top-right corner (unreachable for one-handed use on large phones)
76
+ - Tooltip is the only source of help text on a touch surface
77
+
78
+ ### Responsive-Breakpoint User
79
+
80
+ - **Identity**: User on a viewport the designer didn't focus-test — 320px-wide phones, foldables in their open state, 5K desktops, browser windows the user resized.
81
+ - **Evaluation criteria**:
82
+ - Layout works from 320px viewport width upward (smallest common phone)
83
+ - No horizontal scroll except for content where scrolling is intentional (tables, code blocks)
84
+ - Breakpoints handle landscape phone orientation (short, wide viewports)
85
+ - Text remains readable line length (45-75 characters) across breakpoints — does not stretch to full ultra-wide width
86
+ - Touch targets remain ≥ 24×24 at the smallest breakpoint
87
+ - **Triggers a finding**:
88
+ - Card layout breaks below 360px (content overflows or overlaps)
89
+ - Sidebar that collapses to a hamburger but the hamburger button is itself 12×12
90
+ - Line length unconstrained on ultra-wide displays (text spans 2000px)
91
+ - Modal dialog wider than the viewport, requiring horizontal scroll inside the modal
92
+
93
+ ### RTL / i18n User
94
+
95
+ - **Identity**: User of a right-to-left script (Arabic, Hebrew, Persian, Urdu) or a language with different text expansion (German, Finnish — typically 30% longer; Chinese, Japanese, Korean — often shorter but with line-break rules).
96
+ - **Evaluation criteria**:
97
+ - Layout mirrors correctly under `dir="rtl"` (logical properties used: `margin-inline-start` not `margin-left`)
98
+ - Icons that imply direction (arrows, chevrons) flip; icons that don't (clock, magnifying glass) don't
99
+ - Strings interpolated with placeholders use named/positional substitution, not concatenation
100
+ - Hardcoded strings flagged; user-visible text comes from a translation catalog
101
+ - Date, number, currency formats use the user's locale
102
+ - **Triggers a finding**:
103
+ - User-visible string hardcoded in the markup (not extracted for translation)
104
+ - Layout breaks visually under `dir="rtl"` (margins on the wrong side, icons not mirrored)
105
+ - Date displayed as `MM/DD/YYYY` without locale awareness (ambiguous globally)
106
+ - String concatenation that won't translate cleanly: `"You have " + count + " items"` → use a translation function with placeholders
107
+
108
+ ### Low-Bandwidth / Offline User
109
+
110
+ - **Identity**: User on slow or intermittent network (3G, congested wifi, satellite, train tunnel). Frequent in markets the project may not actively target but where users still exist.
111
+ - **Evaluation criteria**:
112
+ - Total payload weight for first meaningful render reasonable for the surface (web < 200 KB JS gzipped is a starting bar; mobile native should function offline-first for read paths)
113
+ - Images sized to actual display dimensions; modern formats (AVIF, WebP) with fallbacks
114
+ - Network failures fail gracefully (cached state, queued writes, retry with backoff)
115
+ - Loading states address slow connections, not just fast ones (skeleton at 100ms, escalated message at 5s)
116
+ - **Triggers a finding**:
117
+ - Hero image > 1 MB without responsive `srcset`
118
+ - Form submit hangs indefinitely when network is dropped (no timeout, no queue)
119
+ - Critical content loaded via blocking JS bundle > 500 KB
120
+ - No offline state — app crashes or shows blank screen when network is lost
121
+
122
+ ### Hostile Actor
123
+
124
+ - **Identity**: User actively trying to misuse the design — abuse a flow, exfiltrate data, deceive other users via the system, or weaponize the UI as part of a social-engineering attack. Adversarial UX, not just adversarial security.
125
+ - **Evaluation criteria**:
126
+ - User-generated content is escaped and rendered safely (no XSS, no markdown that smuggles HTML)
127
+ - Rate limits visible to legitimate users (so they understand why an action was blocked) but unbypassable
128
+ - Sharing / impersonation features have abuse mitigation (report button, mute, block)
129
+ - Display-name fields don't accept Unicode lookalikes that impersonate other users (homograph attack)
130
+ - Email / SMS templates can't be hijacked for phishing-by-proxy (user-supplied URL appearing in an authority-branded message)
131
+ - Sensitive actions require recent re-authentication, not just an open session
132
+ - **Triggers a finding**:
133
+ - Comment field renders user-supplied HTML
134
+ - Search field reflects input back into the page without escaping
135
+ - Profile URL accepts `javascript:` scheme
136
+ - "Invite via email" feature sends arbitrary user-supplied text from the project's domain
137
+
138
+ ### API Conventions Reviewer
139
+
140
+ - **Identity**: API consumer (internal team, partner, third-party developer). Evaluates the *design* of public APIs the same way users evaluate UI — for consistency, predictability, and minimum surprise.
141
+ - **Evaluation criteria**:
142
+ - REST: resource-oriented URLs, correct HTTP verbs and status codes, consistent error envelope, predictable pagination
143
+ - GraphQL: schema follows naming conventions (camelCase fields, PascalCase types), no over-fetching defaults, deprecation flow uses `@deprecated` not removal
144
+ - Idempotency: PUT and DELETE are idempotent; POST that should be idempotent uses an `Idempotency-Key` header
145
+ - Versioning: breaking change has a versioning plan (URL path, header, or media type — but consistent across the API)
146
+ - Discoverability: spec is published (OpenAPI, GraphQL introspection); errors point to docs
147
+ - **Triggers a finding**:
148
+ - New `POST /deleteUser` endpoint (verb in URL — should be `DELETE /users/:id`)
149
+ - Inconsistent error envelopes across endpoints (some `{ error: "..." }`, some `{ message: "..." }`)
150
+ - Breaking change to a documented field with no version bump or deprecation notice
151
+ - 200 OK returned for an operation that failed (status code masks the error)
152
+
153
+ ---
154
+
155
+ ## Activation Matrix
156
+
157
+ Engagement of each persona is determined by `project.surface`. The matrix below comes from ADR-0019.
158
+
159
+ | Persona | TUI | Web | Mobile | API | Mixed |
160
+ |---|---|---|---|---|---|
161
+ | Keyboard-only | required | required | n/a | n/a | required |
162
+ | Screen-reader | n/a | required | required | n/a | required |
163
+ | Low-vision / color-blind | n/a | required | required | n/a | required |
164
+ | Touch-target | n/a | n/a | required | n/a | required |
165
+ | Responsive-breakpoint | n/a | required | n/a | n/a | required |
166
+ | RTL / i18n | conditional | conditional | conditional | n/a | conditional |
167
+ | Low-bandwidth / offline | n/a | required | required | conditional | required |
168
+ | Hostile actor | required | required | required | required | required |
169
+ | API conventions | n/a | n/a | n/a | required | required |
170
+
171
+ **Legend**:
172
+ - **required** — persona engages by default on this surface
173
+ - **conditional** — persona engages only when a project briefing axis flags it (e.g., `i18n` tag has count > 0 in the feature inventory; project compliance includes a region requiring RTL)
174
+ - **n/a** — persona does not engage by default; user can still invoke via `--personas=...`
175
+
176
+ **User override always available** via `--personas=keyboard,low-vision` or "skip <persona>". Surface is the default; the user is the override.
177
+
178
+ **Mixed surface** runs the union of all surface-specific personas. Projects that genuinely span TUI + web + mobile should set `surface: mixed`; that is the cost of breadth.
179
+
180
+ **Unknown surface** (config field absent) falls back to `mixed`. Better to over-engage than to silently skip critical personas.
181
+
182
+ ---
183
+
184
+ ## Materiality Gate Cross-Reference
185
+
186
+ Every adversarial finding must cite an axis from the Project Briefing (§1 of `./review-personas.md`). The adversarial set extends the briefing axes the original engine considered:
187
+
188
+ - **Surface axis** (this engine adds it) — TUI vs web vs mobile vs API determines which personas engage at all
189
+ - **Brand axis** — if `.grimoire/brand/tokens.json` defines a contrast ratio target, the low-vision persona uses it; otherwise WCAG AA default
190
+ - **Compliance axis** — `project.compliance` mentions WCAG, ADA, EAA (European Accessibility Act), Section 508 → screen-reader and low-vision findings escalate from suggestion to blocker by default
191
+ - **Threat-surface tags** (existing) — `i18n=N` count > 0 promotes RTL persona from conditional to required; `mobile=N` count > 0 promotes touch-target persona
192
+
193
+ A finding from any persona below that lacks a briefing anchor is dropped. The full materiality rules from `./review-personas.md` §2 apply unchanged.
194
+
195
+ ---
196
+
197
+ ## Steel-Man Requirement
198
+
199
+ Per `./review-personas.md` §2a, every adversarial finding must include a steel-man before submission:
200
+
201
+ - **Steel-man**: "The designer likely chose this because <strongest plausible reason — convention, performance, scope, stage>."
202
+ - **Why it still fails**: "Despite that, <concrete harm path tied to a briefing axis — named user impacted, named state, named consequence>."
203
+
204
+ If either line cannot be completed with substance, the finding is dropped. "An accessibility issue" is not a finding; "screen-reader user submitting the login form receives no audible feedback on validation error — they cannot recover without sighted assistance" is a finding.
205
+
206
+ Vague harm paths fail this gate. The adversarial personas exist precisely to name specific harms; generic ones are noise.
207
+
208
+ ---
209
+
210
+ ## Severity Calibration
211
+
212
+ The default for an adversarial finding is **suggestion**. A finding is a **blocker** when *all three* hold (mirroring `./review-personas.md` §2b):
213
+
214
+ 1. **Concrete harm path** — named user (the persona), named trigger, named consequence
215
+ 2. **Briefing-anchored** — the consequence threatens a briefing axis (surface, compliance, brand contrast target, threat-surface tag)
216
+ 3. **Not already mitigated** — neighbor code, design-system component, or sibling feature does not already handle it
217
+
218
+ Adversarial-specific severity rules:
219
+
220
+ - WCAG AA violations on a project that lists WCAG / ADA / EAA in `project.compliance` → blocker by default (regulator anchor)
221
+ - Deceptive patterns (see `./design-heuristics.md` §3) → blocker by default on customer-facing stage; suggestion on internal-tools stage
222
+ - Hostile-actor findings with a working harm path → blocker, regardless of stage (security overlap)
223
+ - Touch-target findings on a mobile surface where the project ships → blocker if a user couldn't complete a primary task; suggestion if it's a secondary control
224
+
225
+ Zero findings is a valid outcome. A persona that returns "no material findings under the briefing" is doing its job. The Contrarian pass (§4.8 of `./review-personas.md`) calibrates inflated findings post-hoc.
@@ -0,0 +1,186 @@
1
+ # Brand Tokens Format Reference
2
+
3
+ Loaded by `grimoire-design` (variant generation, lint mode) and any review skill running visual-fidelity checks. Defines the on-disk shape of `.grimoire/brand/tokens.json` and the voice/tone file beside it.
4
+
5
+ The format is **W3C Design Tokens (DTCG) JSON** — an emerging standard supported by Tokens Studio (Figma plugin), Style Dictionary (compiler), and design-extract (URL scraper). See ADR-0016 for the format decision.
6
+
7
+ ---
8
+
9
+ ## File Layout
10
+
11
+ ```
12
+ .grimoire/brand/
13
+ tokens.json # DTCG-format design tokens (colors, typography, spacing, etc.)
14
+ voice.md # Markdown voice/tone — DTCG does not cover prose
15
+ ```
16
+
17
+ `brand_dir` in `.grimoire/config.yaml` can override the location; default is `.grimoire/brand`.
18
+
19
+ ---
20
+
21
+ ## DTCG Basics
22
+
23
+ Every leaf token is an object with `$value` and `$type`. Optional `$description` documents intent. Nesting creates groups.
24
+
25
+ ```json
26
+ {
27
+ "color": {
28
+ "primary": {
29
+ "$value": "#0066ff",
30
+ "$type": "color",
31
+ "$description": "Brand primary — links, primary buttons, focus rings"
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Group nodes (objects without a `$value`) carry no metadata themselves — they organize children. Names use kebab-case or camelCase consistently across a file; do not mix.
38
+
39
+ ### Token types used by grimoire-design
40
+
41
+ | `$type` | Example `$value` | Used for |
42
+ |---|---|---|
43
+ | `color` | `"#0066ff"`, `"rgb(0,102,255)"`, `"{color.primary}"` | Backgrounds, text, borders |
44
+ | `dimension` | `"8px"`, `"1rem"` | Spacing, sizing, border-radius |
45
+ | `fontFamily` | `"Inter, sans-serif"`, `["Inter", "system-ui"]` | Typography stacks |
46
+ | `fontWeight` | `400`, `"bold"` | Weight tokens |
47
+ | `number` | `1.5`, `1.2` | Line-height ratios, opacity |
48
+ | `duration` | `"200ms"` | Motion timing |
49
+ | `cubicBezier` | `[0.4, 0, 0.2, 1]` | Motion easing |
50
+ | `shadow` | `{ "color": "...", "offsetX": "...", ... }` | Elevation |
51
+ | `asset` *(grimoire extension)* | `"./assets/logo.svg"` (path relative to repo root) | Logo and favicon paths |
52
+
53
+ References use `{group.subgroup.name}` syntax: `"$value": "{color.primary}"` resolves to the token at `color.primary`.
54
+
55
+ ---
56
+
57
+ ## Required Groups (for grimoire-design consumption)
58
+
59
+ A `tokens.json` that grimoire-design treats as "captured" must include at least one token in each:
60
+
61
+ - `color.*` — at minimum `color.primary`. Strongly recommended: `color.secondary`, `color.accent`, `color.background`, `color.text`
62
+ - `font.family.*` — at minimum `font.family.base`. Often also `font.family.heading`, `font.family.mono`
63
+ - `font.size.*` — at minimum `font.size.base`. Scale tokens (`xs`, `sm`, `md`, `lg`, `xl`) are conventional
64
+ - `spacing.*` — at minimum `spacing.base` (the unit step, e.g. `8px`). Scale tokens preferred
65
+
66
+ Missing required groups → grimoire-design generates a warning and falls back to neutral defaults for that group.
67
+
68
+ ## Optional Groups
69
+
70
+ - `motion.*` — `duration`, `easing` tokens
71
+ - `elevation.*` or `shadow.*` — shadow stacks for cards, modals, etc.
72
+ - `border-radius.*` — corner radii (e.g. `sm: 4px`, `md: 8px`, `pill: 9999px`)
73
+ - `breakpoint.*` — responsive breakpoints (web surface only)
74
+ - `asset.logo`, `asset.favicon` — see Logo / Favicon Convention below
75
+
76
+ ---
77
+
78
+ ## Complete Minimal Example
79
+
80
+ ```json
81
+ {
82
+ "color": {
83
+ "primary": { "$value": "#0066ff", "$type": "color" },
84
+ "secondary": { "$value": "#6b7280", "$type": "color" },
85
+ "accent": { "$value": "#f59e0b", "$type": "color" },
86
+ "background": { "$value": "#ffffff", "$type": "color" },
87
+ "text": { "$value": "#111827", "$type": "color" }
88
+ },
89
+ "font": {
90
+ "family": {
91
+ "base": { "$value": "Inter, sans-serif", "$type": "fontFamily" },
92
+ "heading": { "$value": "Inter, sans-serif", "$type": "fontFamily" },
93
+ "mono": { "$value": "JetBrains Mono, monospace", "$type": "fontFamily" }
94
+ },
95
+ "size": {
96
+ "base": { "$value": "16px", "$type": "dimension" },
97
+ "sm": { "$value": "14px", "$type": "dimension" },
98
+ "lg": { "$value": "20px", "$type": "dimension" }
99
+ }
100
+ },
101
+ "spacing": {
102
+ "base": { "$value": "8px", "$type": "dimension" },
103
+ "sm": { "$value": "4px", "$type": "dimension" },
104
+ "lg": { "$value": "16px", "$type": "dimension" }
105
+ },
106
+ "asset": {
107
+ "logo": { "$value": "./brand/logo.svg", "$type": "asset" },
108
+ "favicon": { "$value": "./brand/favicon.ico", "$type": "asset" }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Logo / Favicon Convention
116
+
117
+ DTCG does not standardize asset references. Grimoire stores them under `asset.*` with `$type: "asset"` and a path **relative to repo root**. Tools that don't understand `$type: "asset"` ignore the node — safe to round-trip.
118
+
119
+ ```json
120
+ "asset": {
121
+ "logo": { "$value": "./brand/logo.svg", "$type": "asset" },
122
+ "logo-dark": { "$value": "./brand/logo-dark.svg", "$type": "asset" },
123
+ "favicon": { "$value": "./brand/favicon.ico", "$type": "asset" },
124
+ "favicon-svg": { "$value": "./brand/favicon.svg", "$type": "asset" }
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Voice / Tone (`voice.md`)
131
+
132
+ Voice and tone are prose — DTCG does not cover them. Store as markdown alongside `tokens.json`:
133
+
134
+ ```markdown
135
+ # Voice & Tone
136
+
137
+ ## Voice (always)
138
+ - Direct, plain language. No marketing fluff.
139
+ - Active voice. Short sentences.
140
+ - Confident, not boastful.
141
+
142
+ ## Tone (varies by context)
143
+ - Onboarding: warm, inviting
144
+ - Errors: calm, blameless, with a concrete next step
145
+ - Success: brief acknowledgement, no celebration confetti
146
+
147
+ ## Do
148
+ - "Save changes" → action verb, present tense
149
+ - "Couldn't reach the server. Retry?" → blameless, actionable
150
+
151
+ ## Don't
152
+ - "Awesome! You're crushing it!" → over-celebratory
153
+ - "An error has occurred." → vague, no recovery path
154
+ ```
155
+
156
+ Sections `## Do` and `## Don't` are required; everything else is suggested structure. The Anthropic `brand-guidelines` skill convention is the source.
157
+
158
+ ---
159
+
160
+ ## Round-Trip with External Tools
161
+
162
+ The format is designed to flow between tools without modification:
163
+
164
+ - **Tokens Studio (Figma plugin)** — exports Figma Variables to DTCG JSON. Drop the export at `.grimoire/brand/tokens.json` and grimoire-design reads it.
165
+ - **Style Dictionary** — compiles DTCG JSON to CSS custom properties, iOS/Android constants, JS modules. Point its source to `.grimoire/brand/tokens.json`; targets go wherever the project builds.
166
+ - **design-extract** — scrapes a live URL and emits DTCG JSON. Pipe its output to `.grimoire/brand/tokens.json` to bootstrap from an existing site.
167
+
168
+ Round-trip rule: external tools may rewrite the file; grimoire reads only `$value`, `$type`, `$description`. Unknown keys (e.g. `$extensions` from Tokens Studio) are preserved on read but not consumed.
169
+
170
+ ---
171
+
172
+ ## Validation Tips (for AI agents)
173
+
174
+ When reading `tokens.json`:
175
+
176
+ 1. **Parse error** → emit one-line "tokens.json malformed at `<path>` — `<parse-error>`" and continue without brand grounding. Do not crash the workflow.
177
+ 2. **Missing `$value`** on a leaf node → log "skipping token `<dotted.path>` — missing `$value`". The leaf is invalid; siblings still load.
178
+ 3. **Malformed hex** (`$type: "color"` with `$value` not matching `/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/` or `rgb(...)` / `hsl(...)`) → log and skip.
179
+ 4. **Unresolved reference** (`{color.primary}` where `color.primary` does not exist) → log and treat as literal string; do not infinite-loop.
180
+ 5. **Circular reference** (A → B → A) → break at first revisit; log "circular token reference at `<path>`".
181
+
182
+ When writing `tokens.json`:
183
+
184
+ - Always include `$type` on every leaf. Tools that compile to typed targets need it.
185
+ - Normalize hex to lowercase, 6-digit form. `#0066FF` → `#0066ff`. Keeps diffs clean.
186
+ - Sort keys alphabetically within each group. Keeps round-trips with Tokens Studio stable.
@@ -0,0 +1,140 @@
1
+ # Code Quality Reference
2
+
3
+ Loaded by skills that write production code (`grimoire-apply`, `grimoire-bug`). Run as a checklist **after the test goes green, before marking the task done**. Same shape as the test-quality check — short, concrete, and gated.
4
+
5
+ LLM-generated code drifts toward predictable failure modes: too many branches, too many guards, too many helpers wrapping single calls, too many names that mean nothing. This reference exists to make those drifts visible while the code is fresh.
6
+
7
+ Most rules already live in `AGENTS.md` "Engineering Principles" and in the touched area's `.grimoire/docs/<area>.md`. Those win. This file is the concrete checklist; the principles are the source of truth.
8
+
9
+ ---
10
+
11
+ ## Quality Gate (run before marking task `[x]`)
12
+
13
+ For each production file you wrote or edited, walk the seven checks below. Any failure → fix the code, re-run tests, then re-check. The gate is not a code review — it's a self-check that catches the cheap mistakes before the human reviewer sees them.
14
+
15
+ ### 1. Reuse before write
16
+
17
+ Before adding a function, helper, type, or constant: grep for it. Check the area doc's reusable-code table if one exists. Check neighbors in the same directory.
18
+
19
+ - If a function with the same job already exists → call it. Don't re-implement.
20
+ - If something *almost* fits → use it directly first, refactor it once a second caller actually needs the change. Don't generalize on speculation.
21
+ - If you wrote a helper used by exactly one caller → inline it. Helpers earn their name through reuse.
22
+
23
+ Fail: two near-identical functions, parallel utility files, a private helper next to a public one that already does the same thing.
24
+
25
+ ### 2. Branching budget
26
+
27
+ Count branches per function: every `if`, `else if`, `case`, `&&`/`||` in a condition, ternary, `try/except` arm, early-return guard. Aim for ≤ 7. Above that, the function does too much.
28
+
29
+ Fixes (in order of preference):
30
+ 1. Delete branches that guard impossible states (see §4).
31
+ 2. Replace nested `if`/`else` ladders with early returns or a lookup table / dict.
32
+ 3. Split the function — one job per function. If the function name needs "and", split it.
33
+
34
+ Fail: a 60-line function with four levels of nesting, or any function whose flow can't be described in one sentence.
35
+
36
+ ### 3. Function and file size
37
+
38
+ - Function body > ~30 lines → look for a split. A long function is usually two functions in a trenchcoat.
39
+ - File > ~300 lines → look for a split, unless the project's neighbors are larger.
40
+ - One function should do one thing. If naming it forces "and" / "or" / "_helper", split first.
41
+
42
+ Don't split just to hit a number. A 40-line function that reads top-to-bottom and does one thing is better than four 10-line functions that bounce around.
43
+
44
+ ### 4. No defensive code inside the trust boundary
45
+
46
+ Validate at the edges (user input, external API responses, file/network reads). Inside, trust your callers and your types.
47
+
48
+ Drop:
49
+ - `if x is None` guards on values the type system says can't be None.
50
+ - `try/except` that catches an exception you have no plan for and re-raises or logs-and-continues.
51
+ - "Just in case" type checks (`isinstance`), length checks, key-exists checks on dicts you just built.
52
+ - Fallback values for branches that can't be reached.
53
+
54
+ Keep:
55
+ - Validation of payload from a request/response/file.
56
+ - Error handling at a real boundary with a real recovery path (retry, user message, fallback service).
57
+
58
+ Fail: a private function with three guard clauses before its one real line.
59
+
60
+ ### 5. Names reveal intent
61
+
62
+ - Locals: name the thing, not its type. `users`, not `user_list`. `unpaid_invoices`, not `data` / `result` / `temp` / `info`.
63
+ - Booleans: read as a yes/no question. `is_expired`, `has_admin_role`, `should_retry` — not `flag`, `check`, `status`.
64
+ - Functions: verb phrase that says what it does. `parse_invoice(raw)` not `process_data(d)`.
65
+ - No `_v2`, `_new`, `_helper`, `_util`, `_handler` suffixes unless the project's neighbors use them.
66
+ - Single-letter names only for indices in tight loops and well-known math conventions (`i`, `j`, `x`, `y`).
67
+
68
+ Fail: any local named `data`, `result`, `temp`, `obj`, `item`, or `value` when a more specific name fits.
69
+
70
+ ### 6. No premature abstraction
71
+
72
+ Three near-identical copies is acceptable. Extract on the fourth, or when the shared shape is stable and named. Wrong abstractions are harder to undo than duplication.
73
+
74
+ Drop:
75
+ - Generic interfaces with one implementation.
76
+ - Config objects with one caller and one shape.
77
+ - "Strategy" / "factory" / "registry" patterns added without a second case actually needing them.
78
+ - Wrapper functions that only rename arguments.
79
+
80
+ Keep:
81
+ - Abstractions the codebase already uses for this kind of thing — follow the neighbor.
82
+ - Boundaries called out in an accepted ADR.
83
+
84
+ Fail: a new `BaseFoo` / `FooStrategy` / `FooFactory` introduced for a single caller.
85
+
86
+ ### 7. Comments earn their place
87
+
88
+ Default: no comments. Add a comment **only** when the *why* is non-obvious — a hidden constraint, a workaround for a specific bug, an invariant that would surprise a future reader.
89
+
90
+ Drop:
91
+ - Comments that restate the code (`# loop over users`).
92
+ - Comments referencing the current task / PR / ticket (`# added for issue #123`, `# used by the new flow`). These rot.
93
+ - Multi-line docstrings on private functions whose name and signature already say everything.
94
+ - Commented-out code. Delete it; git remembers.
95
+
96
+ Keep:
97
+ - One-line "why": the constraint, the gotcha, the link to the spec / ADR.
98
+ - Docstrings the project's `comment_style` requires (check `.grimoire/config.yaml`).
99
+
100
+ Fail: any comment whose removal would not confuse a future reader.
101
+
102
+ ---
103
+
104
+ ## Quick self-check (paste into the task loop)
105
+
106
+ Before marking a task `[x]`:
107
+
108
+ - [ ] Searched for existing utilities before writing new ones (§1)
109
+ - [ ] No function with more than ~7 branches or ~30 lines without a reason (§2, §3)
110
+ - [ ] No guards / try-except / type-checks inside the trust boundary (§4)
111
+ - [ ] No locals named `data`, `result`, `temp`, `info`, `obj` — names reveal intent (§5)
112
+ - [ ] No new abstractions, interfaces, or wrappers with a single caller (§6)
113
+ - [ ] No comments describing *what* the code does — only *why*, and only when non-obvious (§7)
114
+ - [ ] Diff stays inside the task's scope — no "while I'm here" refactors
115
+
116
+ If any box can't be ticked, fix the code (not the checklist) and re-run tests.
117
+
118
+ ---
119
+
120
+ ## Anti-patterns by symptom
121
+
122
+ | Symptom | Likely cause | Fix |
123
+ |---|---|---|
124
+ | Function is 80 lines with 5 levels of nesting | Too many jobs, defensive guards | Split + drop dead guards (§2, §3, §4) |
125
+ | Three helper files for one feature | Premature abstraction | Inline back, extract once a real second caller exists (§6) |
126
+ | `data`, `result`, `obj` everywhere | Generic names from training data | Rename to the actual concept (§5) |
127
+ | Every function starts with `if x is None: return` | Defensive habit | Trust the caller; validate once at the edge (§4) |
128
+ | Comments restate variable names | Filler | Delete (§7) |
129
+ | `try: ... except Exception: pass` | Hiding bugs | Remove or handle the specific exception with a real recovery (§4) |
130
+ | Wrapper `def get_user(id): return db.get_user(id)` | Pointless indirection | Inline (§1) |
131
+ | Two near-identical functions differing by one constant | Copy-paste instead of reuse | Pass the constant as a parameter, or call the existing one (§1) |
132
+
133
+ ---
134
+
135
+ ## Notes for the reviewer / self
136
+
137
+ - The gate is **per file, per task**. Not a separate review pass.
138
+ - "I'll clean it up later" is the failure mode. Clean it before the test goes green stays green.
139
+ - If a check seems wrong for *this* codebase, the project's `AGENTS.md` / area doc / neighbor patterns win. Cite the override; don't ignore silently.
140
+ - The full review-stage Senior Engineer + Code Style personas (`./review-personas.md`) catch what slipped through. This gate exists so they have less to catch.