@kiwidata/grimoire 0.1.2 → 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.
- package/AGENTS.md +56 -4
- package/README.md +29 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/check.js +3 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/configure.d.ts +3 -0
- package/dist/commands/configure.d.ts.map +1 -0
- package/dist/commands/configure.js +19 -0
- package/dist/commands/configure.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +2 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/map.d.ts.map +1 -1
- package/dist/commands/map.js +10 -11
- package/dist/commands/map.js.map +1 -1
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +32 -43
- package/dist/core/archive.js.map +1 -1
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/check.js +115 -104
- package/dist/core/check.js.map +1 -1
- package/dist/core/ci.d.ts.map +1 -1
- package/dist/core/ci.js +50 -69
- package/dist/core/ci.js.map +1 -1
- package/dist/core/configure.d.ts +14 -0
- package/dist/core/configure.d.ts.map +1 -0
- package/dist/core/configure.js +434 -0
- package/dist/core/configure.js.map +1 -0
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +153 -26
- package/dist/core/detect.js.map +1 -1
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +62 -93
- package/dist/core/diff.js.map +1 -1
- package/dist/core/doc-style.d.ts +0 -4
- package/dist/core/doc-style.d.ts.map +1 -1
- package/dist/core/doc-style.js +28 -23
- package/dist/core/doc-style.js.map +1 -1
- package/dist/core/docs.js +106 -100
- package/dist/core/docs.js.map +1 -1
- package/dist/core/health.js +55 -77
- package/dist/core/health.js.map +1 -1
- package/dist/core/hooks.d.ts +0 -3
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/hooks.js +0 -11
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +230 -406
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +55 -65
- package/dist/core/list.js.map +1 -1
- package/dist/core/log.d.ts.map +1 -1
- package/dist/core/log.js +23 -33
- package/dist/core/log.js.map +1 -1
- package/dist/core/map.d.ts +15 -2
- package/dist/core/map.d.ts.map +1 -1
- package/dist/core/map.js +257 -194
- package/dist/core/map.js.map +1 -1
- package/dist/core/shared-setup.d.ts +0 -40
- package/dist/core/shared-setup.d.ts.map +1 -1
- package/dist/core/shared-setup.js +87 -52
- package/dist/core/shared-setup.js.map +1 -1
- package/dist/core/status.d.ts.map +1 -1
- package/dist/core/status.js +42 -52
- package/dist/core/status.js.map +1 -1
- package/dist/core/test-quality.d.ts +0 -8
- package/dist/core/test-quality.d.ts.map +1 -1
- package/dist/core/test-quality.js +24 -30
- package/dist/core/test-quality.js.map +1 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/trace.js +31 -41
- package/dist/core/trace.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +61 -11
- package/dist/core/update.js.map +1 -1
- package/dist/core/validate.d.ts +1 -4
- package/dist/core/validate.d.ts.map +1 -1
- package/dist/core/validate.js +126 -148
- package/dist/core/validate.js.map +1 -1
- package/dist/utils/config.d.ts +15 -5
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +63 -42
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/fs.d.ts +0 -12
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +0 -12
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/paths.d.ts +0 -6
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +0 -6
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/spawn.d.ts +0 -3
- package/dist/utils/spawn.d.ts.map +1 -1
- package/dist/utils/spawn.js +0 -3
- package/dist/utils/spawn.js.map +1 -1
- package/package.json +1 -1
- package/skills/grimoire-apply/SKILL.md +84 -16
- package/skills/grimoire-audit/SKILL.md +21 -1
- package/skills/grimoire-bug/SKILL.md +48 -9
- package/skills/grimoire-commit/SKILL.md +2 -1
- package/skills/grimoire-design/SKILL.md +259 -0
- package/skills/grimoire-design-consult/SKILL.md +200 -0
- package/skills/grimoire-discover/SKILL.md +65 -2
- package/skills/grimoire-draft/SKILL.md +85 -2
- package/skills/grimoire-plan/SKILL.md +61 -18
- package/skills/grimoire-pr/SKILL.md +4 -6
- package/skills/grimoire-pr-review/SKILL.md +45 -114
- package/skills/grimoire-precommit-review/SKILL.md +205 -0
- package/skills/grimoire-refactor/SKILL.md +5 -5
- package/skills/grimoire-review/SKILL.md +74 -147
- package/skills/grimoire-verify/SKILL.md +33 -0
- package/skills/references/adversarial-personas.md +225 -0
- package/skills/references/brand-tokens-format.md +186 -0
- package/skills/references/code-quality.md +140 -0
- package/skills/references/design-heuristics.md +138 -0
- package/skills/references/design-input-formats.md +190 -0
- package/skills/references/pattern-guard.md +180 -0
- package/skills/references/refactor-scan-categories.md +152 -0
- package/skills/references/review-personas.md +405 -0
- package/skills/references/security-compliance.md +22 -1
- package/skills/references/visual-fidelity.md +206 -0
- package/templates/brand-tokens-example.json +13 -0
- package/templates/brand-voice-example.md +22 -0
- 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.
|