@kiwidata/grimoire 0.1.3 → 0.1.5
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 +107 -59
- package/dist/cli/index.js +7 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/check.js +1 -1
- 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/core/check.d.ts.map +1 -1
- package/dist/core/check.js +165 -111
- 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 +103 -22
- package/dist/core/doc-style.js.map +1 -1
- package/dist/core/docs.js +202 -170
- package/dist/core/docs.js.map +1 -1
- package/dist/core/health.d.ts +6 -0
- package/dist/core/health.d.ts.map +1 -1
- package/dist/core/health.js +133 -96
- 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 +11 -16
- 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/risk-register.d.ts +17 -0
- package/dist/core/risk-register.d.ts.map +1 -0
- package/dist/core/risk-register.js +73 -0
- package/dist/core/risk-register.js.map +1 -0
- 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 +92 -56
- 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 +67 -75
- 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/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.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 +89 -25
- package/skills/grimoire-audit/SKILL.md +21 -1
- package/skills/grimoire-bug/SKILL.md +48 -9
- package/skills/grimoire-commit/SKILL.md +3 -2
- package/skills/grimoire-design/SKILL.md +259 -0
- package/skills/grimoire-design-consult/SKILL.md +200 -0
- package/skills/grimoire-discover/SKILL.md +139 -109
- package/skills/grimoire-draft/SKILL.md +131 -15
- package/skills/grimoire-plan/SKILL.md +119 -46
- package/skills/grimoire-pr/SKILL.md +7 -10
- package/skills/grimoire-pr-review/SKILL.md +46 -115
- package/skills/grimoire-precommit-review/SKILL.md +205 -0
- package/skills/grimoire-refactor/SKILL.md +6 -6
- package/skills/grimoire-review/SKILL.md +95 -156
- package/skills/grimoire-verify/SKILL.md +40 -7
- package/skills/grimoire-vuln-remediate/SKILL.md +107 -0
- package/skills/grimoire-vuln-triage/SKILL.md +109 -0
- package/skills/references/adversarial-personas.md +225 -0
- package/skills/references/brand-tokens-format.md +186 -0
- package/skills/references/code-quality.md +172 -0
- package/skills/references/container-scan-triage.md +102 -0
- package/skills/references/dependency-vuln-triage.md +236 -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/principles.md +82 -0
- package/skills/references/refactor-scan-categories.md +154 -2
- package/skills/references/review-personas.md +406 -0
- package/skills/references/security-compliance.md +22 -1
- package/skills/references/testing-contracts.md +1 -1
- package/skills/references/visual-fidelity.md +206 -0
- package/templates/accepted-risks.yml +47 -0
- package/templates/brand-tokens-example.json +13 -0
- package/templates/brand-voice-example.md +22 -0
- package/templates/constraints.md +25 -0
- package/templates/design-tool-setup-stub.md +59 -0
- package/dist/commands/archive.d.ts +0 -3
- package/dist/commands/archive.d.ts.map +0 -1
- package/dist/commands/archive.js +0 -22
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/log.d.ts +0 -3
- package/dist/commands/log.d.ts.map +0 -1
- package/dist/commands/log.js +0 -15
- package/dist/commands/log.js.map +0 -1
- package/dist/commands/map.d.ts +0 -3
- package/dist/commands/map.d.ts.map +0 -1
- package/dist/commands/map.js +0 -17
- package/dist/commands/map.js.map +0 -1
- package/dist/core/archive.d.ts +0 -9
- package/dist/core/archive.d.ts.map +0 -1
- package/dist/core/archive.js +0 -92
- package/dist/core/archive.js.map +0 -1
- package/dist/core/log.d.ts +0 -8
- package/dist/core/log.d.ts.map +0 -1
- package/dist/core/log.js +0 -150
- package/dist/core/log.js.map +0 -1
- package/dist/core/map.d.ts +0 -9
- package/dist/core/map.d.ts.map +0 -1
- package/dist/core/map.js +0 -302
- package/dist/core/map.js.map +0 -1
- package/templates/dupignore +0 -93
- package/templates/mapignore +0 -58
- package/templates/mapkeys +0 -65
|
@@ -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,172 @@
|
|
|
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: query the graph (`search_graph` by concept and by name) for an existing one. Then grep, and 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 — terse, self-contained, no essays
|
|
87
|
+
|
|
88
|
+
Write comments like a senior engineer with no time: dense, professional, zero filler.
|
|
89
|
+
|
|
90
|
+
**Voice: terse.** "Resolve model by id; raises on unknown provider." — not "This function is responsible for resolving the model by its id, and it will raise an exception if the provider is not known." Drop "this function", "we", hedging, and restated types. Fragments are fine; full prose grammar is not required.
|
|
91
|
+
|
|
92
|
+
**Self-contained.** A comment describes the function/class on its own terms only. It must NOT name an external artifact that changes independently — feature flags / `.feature` files / scenario names, unit or integration test names, MADR/ADR numbers, change-ids, issue/PR numbers, tag codes (`LOG-OBS-003`). Those orphan the moment the artifact moves, and rot silently. Describe the *behavior*, not where it's specced.
|
|
93
|
+
- OK: `# skip third-party sinks (e.g. behave capture)` — generic, about the code.
|
|
94
|
+
- Not OK: `# implements scenario LOG-OBS-003 from logging.feature` — points at an artifact that will move.
|
|
95
|
+
|
|
96
|
+
**No paragraphs.** Summary is one line, two at most. No prose block explaining the whole design before the params. If the rationale needs a paragraph, it belongs in a decision record — not the code.
|
|
97
|
+
|
|
98
|
+
**Params per `comment_style` are fine.** If the project's style (sphinx/google/jsdoc/…) calls for `:param`/`Args:`/`@param`, keep them — but describe a param only when its name + type don't already say it, and don't precede them with prose.
|
|
99
|
+
|
|
100
|
+
Drop:
|
|
101
|
+
- Comments that restate the code (`# loop over users`).
|
|
102
|
+
- Any reference to a task / PR / ticket / feature / scenario / ADR / specific test (`# added for issue #123`, `# covers scenario X`, `# see test_foo`). Self-contained or gone.
|
|
103
|
+
- Multi-line prose docstrings on private functions whose name + signature already say everything.
|
|
104
|
+
- Commented-out code. Delete it; git remembers.
|
|
105
|
+
|
|
106
|
+
Keep:
|
|
107
|
+
- One terse line of *why* when non-obvious — a hidden constraint, a workaround, a surprising invariant — stated in terms of the code itself.
|
|
108
|
+
- The structured `comment_style` param/return section, terse.
|
|
109
|
+
|
|
110
|
+
Fail: any comment that (a) wouldn't confuse a future reader if removed, (b) names an external artifact, or (c) runs to a prose paragraph.
|
|
111
|
+
|
|
112
|
+
**Before / after** (the offender this rule targets):
|
|
113
|
+
```python
|
|
114
|
+
# BEFORE — orphan-prone essay
|
|
115
|
+
def build_chat(model_id):
|
|
116
|
+
"""
|
|
117
|
+
Build and return a chat model for the given model id. This is the primary
|
|
118
|
+
entry point used by every agent and team in the system, as specified by
|
|
119
|
+
scenario LOG-OBS-003 in logging.feature and decided in ADR-0001. See
|
|
120
|
+
test_build_chat for the expected behavior. Added as part of add-2fa-login.
|
|
121
|
+
|
|
122
|
+
:param model_id: the id of the model to build
|
|
123
|
+
:return: the chat model
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
# AFTER — terse, self-contained
|
|
127
|
+
def build_chat(model_id):
|
|
128
|
+
"""Resolve a chat model by id. Raises on an unknown provider.
|
|
129
|
+
|
|
130
|
+
:param model_id: provider-prefixed model id (e.g. "gpt-4.1-mini")
|
|
131
|
+
"""
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Quick self-check (paste into the task loop)
|
|
137
|
+
|
|
138
|
+
Before marking a task `[x]`:
|
|
139
|
+
|
|
140
|
+
- [ ] Searched for existing utilities before writing new ones (§1)
|
|
141
|
+
- [ ] No function with more than ~7 branches or ~30 lines without a reason (§2, §3)
|
|
142
|
+
- [ ] No guards / try-except / type-checks inside the trust boundary (§4)
|
|
143
|
+
- [ ] No locals named `data`, `result`, `temp`, `info`, `obj` — names reveal intent (§5)
|
|
144
|
+
- [ ] No new abstractions, interfaces, or wrappers with a single caller (§6)
|
|
145
|
+
- [ ] Comments are terse, self-contained, ≤2 lines of prose — no *what*, no external-artifact refs (feature/scenario/ADR/test/ticket) (§7)
|
|
146
|
+
- [ ] Diff stays inside the task's scope — no "while I'm here" refactors
|
|
147
|
+
|
|
148
|
+
If any box can't be ticked, fix the code (not the checklist) and re-run tests.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Anti-patterns by symptom
|
|
153
|
+
|
|
154
|
+
| Symptom | Likely cause | Fix |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| Function is 80 lines with 5 levels of nesting | Too many jobs, defensive guards | Split + drop dead guards (§2, §3, §4) |
|
|
157
|
+
| Three helper files for one feature | Premature abstraction | Inline back, extract once a real second caller exists (§6) |
|
|
158
|
+
| `data`, `result`, `obj` everywhere | Generic names from training data | Rename to the actual concept (§5) |
|
|
159
|
+
| Every function starts with `if x is None: return` | Defensive habit | Trust the caller; validate once at the edge (§4) |
|
|
160
|
+
| Comments restate variable names | Filler | Delete (§7) |
|
|
161
|
+
| `try: ... except Exception: pass` | Hiding bugs | Remove or handle the specific exception with a real recovery (§4) |
|
|
162
|
+
| Wrapper `def get_user(id): return db.get_user(id)` | Pointless indirection | Inline (§1) |
|
|
163
|
+
| 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) |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Notes for the reviewer / self
|
|
168
|
+
|
|
169
|
+
- The gate is **per file, per task**. Not a separate review pass.
|
|
170
|
+
- "I'll clean it up later" is the failure mode. Clean it before the test goes green stays green.
|
|
171
|
+
- 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.
|
|
172
|
+
- 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.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Container & OS-Package Scan Triage Reference
|
|
2
|
+
|
|
3
|
+
The deep-dive for `os-package` / `container` / `iac` findings from image scanners (Trivy, Grype). Loaded by `grimoire-vuln-triage` when a scan carries container/OS-package results. The general rubric — normalize, reconcile, KEV/EPSS, VEX, urgency, Contrarian — lives in `./dependency-vuln-triage.md`; this file is the discipline for the part that goes wrong most: deciding what to *do* about a base-OS CVE.
|
|
4
|
+
|
|
5
|
+
Written after a review recommended removing `Mesa`, `ncurses`, and `krb5` from a headless Django API with the rationale "no business in a headless API." Two of the three could not be removed without breaking the image. This guide exists so that mistake is not repeated.
|
|
6
|
+
|
|
7
|
+
## Separate the two axes — they are not the same question
|
|
8
|
+
|
|
9
|
+
- **Reachability** — *is the vulnerable code path actually called by untrusted input in this service?* Decides **urgency** (hotfix / next-release / accept). A library present but never invoked is low risk even at CVSS CRITICAL.
|
|
10
|
+
- **Removability** — *can we delete the package, and what breaks if we do?* Decides **remediation** (Dockerfile edit / base bump / accept+document).
|
|
11
|
+
|
|
12
|
+
Conflating them produces the classic error: "this lib is unreachable, so remove it." Unreachable ≠ removable. A headless API genuinely cannot reach Mesa's OpenGL code — **and also cannot remove Mesa** if it arrived transitively behind a package it needs. Judge both, separately.
|
|
13
|
+
|
|
14
|
+
## Core rule: trace before you recommend
|
|
15
|
+
|
|
16
|
+
A CVE scanner reports *what is present*, not *why* or *whether it is removable*. Never recommend removing a package until you have answered all three:
|
|
17
|
+
|
|
18
|
+
1. **How does it get in?** Directly installed, transitive, base image, or builder-stage-only?
|
|
19
|
+
2. **What depends on it?** Application code, a required runtime lib, or the base OS?
|
|
20
|
+
3. **What breaks if it's gone?** Build, runtime, or nothing — and what's the test that proves it?
|
|
21
|
+
|
|
22
|
+
"This doesn't belong in a headless API" is an assumption, not an analysis.
|
|
23
|
+
|
|
24
|
+
## Step A — How is it installed?
|
|
25
|
+
|
|
26
|
+
Search the Dockerfile for an explicit install line first.
|
|
27
|
+
|
|
28
|
+
- **Explicitly installed** (named in `apt-get install` / `pip install`): a real removal candidate — continue to Step B.
|
|
29
|
+
- **Not named anywhere**: it's transitive — pulled by another package or shipped in the base image. You cannot `apt-get remove` it without breaking its parent. Identify the parent before saying anything.
|
|
30
|
+
|
|
31
|
+
Common transitive sources — map these before flagging:
|
|
32
|
+
|
|
33
|
+
| Flagged lib | Usually pulled in by | Notes |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| krb5 / libgssapi-krb5 | `libpq5`, `postgresql-client`, `curl` | Postgres GSSAPI/Kerberos auth |
|
|
36
|
+
| ncurses / libtinfo | base image | bash, apt, dpkg, python readline link it |
|
|
37
|
+
| Mesa / libgl1 / libgbm | `libgl1`, `libglib2.0-0` | OpenCV / docling / easyocr deps |
|
|
38
|
+
| OpenSSL / libssl | base image + most TLS clients | almost never removable |
|
|
39
|
+
| libexpat1 | base image + python (`pyexpat`) | stdlib XML |
|
|
40
|
+
|
|
41
|
+
## Step B — What actually depends on it?
|
|
42
|
+
|
|
43
|
+
Do not assume. Check the repo:
|
|
44
|
+
|
|
45
|
+
- **App imports** — grep for the consuming module (`import cv2`, `import magic`, `import pyodbc`, `gssapi`, `lxml`).
|
|
46
|
+
- **System tools used at runtime** — grep code, scripts, and the entrypoint for the binary (`psql`, `pg_dump`, `pg_isready`).
|
|
47
|
+
- **Driver bundling** — many Python wheels bundle their native lib, making the system package redundant. `psycopg-binary` bundles libpq → system `libpq5` not needed for the driver; `pylibmagic` bundles libmagic → system `libmagic1` may be redundant. When a binary wheel is present, the matching system runtime package is often dead weight — **verify, then say so**.
|
|
48
|
+
- **Cross-service config trap** — env vars or paths in this repo may configure a *different* container. `EASYOCR_MODULE_PATH` / `DOCLING_ARTIFACTS_PATH` in bake are passed by `job_runner.py` to the **ricky** pipeline container — they do **not** mean bake runs easyocr/docling. Never justify or condemn a package using a string that belongs to another service.
|
|
49
|
+
|
|
50
|
+
## Step C — Know what is not removable
|
|
51
|
+
|
|
52
|
+
Some findings are not actionable by editing an install line:
|
|
53
|
+
|
|
54
|
+
- **Base-image packages** (ncurses, OpenSSL, glibc, zlib, expat): part of the OS. Removing breaks bash/apt/dpkg or the Python runtime. The only real mitigations are: **switch to a smaller/distroless base**, **bump the base image** for patched versions, or **accept and document** the risk. State which — do not tell the user to "remove" it.
|
|
55
|
+
- **Transitive deps of required libs** (e.g. krb5 behind `libpq5`): removable only by also removing the parent, and only if the parent is itself unneeded.
|
|
56
|
+
|
|
57
|
+
## Step D — Multi-stage builds: target the right stage
|
|
58
|
+
|
|
59
|
+
In a multi-stage Dockerfile only the final stage ships. Packages in a `builder` stage (compilers, `-dev` headers) do **not** appear in the runtime image if only the artifact (`/opt/venv` or equivalent) is copied forward. They are not runtime attack surface. Don't flag builder-stage packages as runtime risk; if you mention them, label them **build-only**.
|
|
60
|
+
|
|
61
|
+
## Step E — Assess real risk, not just presence (reachability)
|
|
62
|
+
|
|
63
|
+
Maps to `./dependency-vuln-triage.md` § Reachability. A CVE in a library never reached by untrusted input is lower priority than its score. For each finding note:
|
|
64
|
+
|
|
65
|
+
- Is the vulnerable code path reachable in *this* service? (use the consumer map below — grep the **consumer**, not the C package name; the app never `import`s `libexpat1`)
|
|
66
|
+
- Network/user-input exposed, or internal-only?
|
|
67
|
+
- Headless API context: no display, no user shell, no interactive TTY → GUI/terminal libs (Mesa, ncurses) are usually unreachable even when present.
|
|
68
|
+
|
|
69
|
+
| OS package | Reached only if the app… | Grep for |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| libexpat1 | parses XML via stdlib | `xml.etree`, `xml.sax`, `pyexpat`, `minidom` |
|
|
72
|
+
| libxml2 / libxslt | parses XML/XSLT via lxml | `import lxml`, `etree`, `XSLT` |
|
|
73
|
+
| krb5 / libgssapi | does Kerberos/GSSAPI auth | `gssapi`, `kerberos`, `requests_kerberos` |
|
|
74
|
+
| mesa / libGL / libgbm | does GPU/OpenGL rendering | `OpenGL`, `moderngl`, `cv2` (headless API: none) |
|
|
75
|
+
| ncurses / libtinfo | drives an interactive terminal | `curses`, `pty`, `readline` (web process: none) |
|
|
76
|
+
| libssl / openssl | does TLS | usually reachable — judge on impact |
|
|
77
|
+
| imagemagick / libvips | processes user-uploaded images | the upload/convert path |
|
|
78
|
+
|
|
79
|
+
**Grep can lie — verify the binding.** `Price.fromstring()` (price-parser) is not `etree.fromstring()` (XML). Confirm the match is the real vulnerable call site before asserting `not_affected` or `affected`; if you can't, mark `under_investigation` and name what a human must check. Prefer reachability-based prioritization over raw CVSS.
|
|
80
|
+
|
|
81
|
+
## Honor the scanner's fix-state
|
|
82
|
+
|
|
83
|
+
Trivy `Status` (and Grype `fix.state`): `fixed` → a patched package exists; upgrade/rebuild is the lever. `affected` / `will_not_fix` / `end_of_life` → **no fix available**; the lever is *accept with expiry* or *rebuild on a newer/slimmer base when one ships* — **not** an "upgrade X" ticket. `under_investigation` → distro hasn't ruled; mirror it. Never file an upgrade task for a no-fixed-version finding.
|
|
84
|
+
|
|
85
|
+
## Output per flagged package
|
|
86
|
+
|
|
87
|
+
1. **Package + CVE(s)** — what the scanner said (and the dedup count if one CVE spans many packages).
|
|
88
|
+
2. **How it's installed** — explicit line N / transitive via `<parent>` / base image / builder-only.
|
|
89
|
+
3. **What depends on it** — app module / runtime tool / OS, with grep evidence.
|
|
90
|
+
4. **Reachable?** — vulnerable path called by untrusted input? (provenance: graph / grep / image-layer / unknown)
|
|
91
|
+
5. **Removable?** — Yes (safe) / Yes (after removing `<parent>`, test X) / No (base OS) / No (required by Y).
|
|
92
|
+
6. **Recommendation** — exact Dockerfile edit, or "patch/bump base image", or "accept + document", **plus the post-change test** (build, import, DB connect). Route image-structure changes to infra / `grimoire-draft`, not app remediation.
|
|
93
|
+
|
|
94
|
+
## Anti-patterns — do not do these
|
|
95
|
+
|
|
96
|
+
- ❌ "X has no business in a headless API" with no trace of how X got installed.
|
|
97
|
+
- ❌ Recommending `apt-get remove` of a base-image or transitive package.
|
|
98
|
+
- ❌ Treating scanner presence as equal to exploitable risk.
|
|
99
|
+
- ❌ Justifying or condemning a package using config that targets another service.
|
|
100
|
+
- ❌ Flagging builder-stage packages as runtime attack surface.
|
|
101
|
+
- ❌ Recommending removal without naming the post-change test.
|
|
102
|
+
- ❌ Filing an "upgrade" ticket for a `will_not_fix` / no-fixed-version finding.
|