@kiwidata/grimoire 0.1.4 → 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/README.md +79 -58
- package/dist/cli/index.js +5 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/check.js +54 -11
- package/dist/core/check.js.map +1 -1
- package/dist/core/doc-style.d.ts.map +1 -1
- package/dist/core/doc-style.js +76 -0
- package/dist/core/doc-style.js.map +1 -1
- package/dist/core/docs.js +96 -70
- 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 +78 -19
- package/dist/core/health.js.map +1 -1
- package/dist/core/hooks.js +11 -5
- package/dist/core/hooks.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.map +1 -1
- package/dist/core/shared-setup.js +5 -4
- package/dist/core/shared-setup.js.map +1 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/trace.js +37 -35
- package/dist/core/trace.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/package.json +1 -1
- package/skills/grimoire-apply/SKILL.md +35 -39
- package/skills/grimoire-commit/SKILL.md +1 -1
- package/skills/grimoire-design/SKILL.md +3 -3
- package/skills/grimoire-discover/SKILL.md +77 -110
- package/skills/grimoire-draft/SKILL.md +51 -18
- package/skills/grimoire-plan/SKILL.md +62 -32
- package/skills/grimoire-pr/SKILL.md +7 -8
- package/skills/grimoire-pr-review/SKILL.md +1 -1
- package/skills/grimoire-refactor/SKILL.md +2 -2
- package/skills/grimoire-review/SKILL.md +12 -0
- package/skills/grimoire-verify/SKILL.md +7 -7
- package/skills/grimoire-vuln-remediate/SKILL.md +107 -0
- package/skills/grimoire-vuln-triage/SKILL.md +109 -0
- package/skills/references/code-quality.md +41 -9
- package/skills/references/container-scan-triage.md +102 -0
- package/skills/references/dependency-vuln-triage.md +236 -0
- package/skills/references/principles.md +82 -0
- package/skills/references/refactor-scan-categories.md +2 -2
- package/skills/references/review-personas.md +4 -3
- package/skills/references/testing-contracts.md +1 -1
- package/templates/accepted-risks.yml +47 -0
- package/templates/constraints.md +25 -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 -16
- 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 -81
- 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 -140
- package/dist/core/log.js.map +0 -1
- package/dist/core/map.d.ts +0 -22
- package/dist/core/map.d.ts.map +0 -1
- package/dist/core/map.js +0 -365
- package/dist/core/map.js.map +0 -1
- package/templates/dupignore +0 -93
- package/templates/mapignore +0 -58
- package/templates/mapkeys +0 -65
|
@@ -38,7 +38,7 @@ Derive implementation tasks from approved Gherkin features and MADR decisions. T
|
|
|
38
38
|
- "See if there's an existing utility for Z"
|
|
39
39
|
- "TODO: check if this conflicts with…"
|
|
40
40
|
|
|
41
|
-
Resolve each one yourself before writing the task. Tools: codebase-memory-mcp (`search_graph`, `trace_path`, `get_code_snippet`), `.grimoire/docs/<area>.md`
|
|
41
|
+
Resolve each one yourself before writing the task. Tools: codebase-memory-mcp (`search_graph`, `trace_path`, `get_code_snippet`) for symbols and reusable code, `.grimoire/docs/<area>.md` for conventions/boundaries, `Grep`, neighbor files. The task should state the *answer* ("Reuse `parse_invoice` in `src/billing/parsing.py:42`" or "No existing utility — write new"), never the *question*.
|
|
42
42
|
|
|
43
43
|
**2. Clarify or propose, never assume.** When the spec is ambiguous or silent on something you need to plan:
|
|
44
44
|
|
|
@@ -48,6 +48,14 @@ Resolve each one yourself before writing the task. Tools: codebase-memory-mcp (`
|
|
|
48
48
|
|
|
49
49
|
The plan implements what's approved. It does not expand scope to hit a checklist.
|
|
50
50
|
|
|
51
|
+
**3. Plan to the principles.** Every task is gated by the four principles in `../references/principles.md` — **one right way, DRY, don't reinvent the wheel, keep it simple.** Concretely, before writing each task:
|
|
52
|
+
- **One right way:** name the single sanctioned approach. If the spec leaves two ways open, pick one (record why in the task) — never plan both.
|
|
53
|
+
- **DRY:** reuse before write (search the graph); don't plan a task that stores a fact already derivable from code/mcp or already homed elsewhere.
|
|
54
|
+
- **Don't reinvent:** prefer an existing tool/library/proven pattern over a bespoke mechanism. git for change process, standard libs for crypto/auth/parsing.
|
|
55
|
+
- **Keep it simple:** choose the least-code option inside the non-goals. Flag any task that adds an abstraction, a dependency, or a second mechanism — it needs an explicit reason.
|
|
56
|
+
|
|
57
|
+
These are gates, not aspirations — a task that adds a duplicate home or a reinvented wheel is rejected, not refined.
|
|
58
|
+
|
|
51
59
|
### 1. Select Change
|
|
52
60
|
- List active changes in `.grimoire/changes/`
|
|
53
61
|
- If multiple, ask user which one to plan
|
|
@@ -59,9 +67,10 @@ The plan implements what's approved. It does not expand scope to hit a checklist
|
|
|
59
67
|
|
|
60
68
|
**Always read:**
|
|
61
69
|
- `manifest.md` for the change summary, **including complexity level, Assumptions, Pre-Mortem, and Prior Art sections**
|
|
62
|
-
- All
|
|
63
|
-
- All
|
|
64
|
-
-
|
|
70
|
+
- All `.feature` files for this change (edited live in `features/` on the branch)
|
|
71
|
+
- All decision records for this change (edited live in `.grimoire/decisions/`), **including Cost of Ownership sections**
|
|
72
|
+
- `.grimoire/docs/constraints.md` — any constraints (security/NFR/observability) this change adds or touches. These produce `unit-invariant` tasks, not scenarios.
|
|
73
|
+
- The current baseline (`features/`, `.grimoire/decisions/`) via `git diff main` to see exactly what this change adds vs. what already existed
|
|
65
74
|
|
|
66
75
|
**Validate the build-vs-buy decision:**
|
|
67
76
|
- Check that `manifest.md` has a **Prior Art** section documenting what existing solutions were researched. If it's missing or empty, **stop and tell the user** — planning without a build-vs-buy analysis produces plans that ignore cheaper alternatives.
|
|
@@ -70,21 +79,21 @@ The plan implements what's approved. It does not expand scope to hit a checklist
|
|
|
70
79
|
- If the decision was **hybrid** (adopt for part, build for part), ensure the boundary between adopted and custom code is clear in the tasks.
|
|
71
80
|
|
|
72
81
|
**Read from grimoire docs (these replace codebase exploration):**
|
|
73
|
-
- **`.grimoire/docs/<area>.md`** for each area the change touches — these contain
|
|
82
|
+
- **`.grimoire/docs/<area>.md`** for each area the change touches — these contain Purpose, Boundaries, Conventions (naming/structure), and "Where New Code Goes" guidance. For key files, exact symbols, reusable utilities (function names, file paths, line numbers), and call graphs, **query the graph** — `search_graph` / `get_code_snippet` / `get_architecture`. Area docs give you intent and placement; the graph gives you the live structure to write exact file paths and reuse existing code.
|
|
74
83
|
- **`.grimoire/docs/data/schema.yml`** — the full data model: every table/collection, field types, relationships, indexes, and external API contracts with `source:` pointers to ORM code. Read this instead of reading individual model files.
|
|
75
84
|
- **`.grimoire/docs/context.yml`** — the project's deployment environment, related services, infrastructure dependencies, CI/CD pipelines, and observability setup. Read this to understand deployment constraints (e.g., Lambda means no long-running processes, Kubernetes means you may need health check endpoints), cross-service boundaries (e.g., auth is handled by a sibling service, not this project), and infrastructure available at runtime (e.g., Redis is available for caching, RabbitMQ for async tasks).
|
|
76
|
-
-
|
|
85
|
+
- Existing duplication in areas you're touching — query codebase-memory-mcp (`search_graph` for similar functions) or run `grimoire health` (its config-driven `duplicates` metric) so tasks consolidate rather than add more clones.
|
|
77
86
|
|
|
78
87
|
**Read proposed data changes:**
|
|
79
88
|
- **`data.yml`** if present — proposed schema changes need migration and model tasks
|
|
80
89
|
|
|
81
90
|
**Staleness gate:** For each area doc loaded, check its `last_updated` date against `git log -1 --format=%ci <directory>`. If any doc is older than the most recent commit to its directory, it's stale — the file paths, utility names, and patterns it describes may no longer be accurate.
|
|
82
91
|
|
|
83
|
-
- **Level 1-2:** Warn ("Area doc for `<area>` is behind recent commits —
|
|
92
|
+
- **Level 1-2:** Warn ("Area doc for `<area>` is behind recent commits — its boundaries/conventions may be wrong; rely on the graph for structure") and proceed. Note inferred paths with `<!-- inferred: area doc may be stale -->`.
|
|
84
93
|
- **Level 3-4:** Treat as a blocker. Do not generate tasks until the user refreshes stale docs via `grimoire-discover` targeted refresh. Planning with stale docs at this complexity produces wrong file paths and misses recent utilities — the cost of re-planning outweighs the cost of refreshing first.
|
|
85
94
|
|
|
86
95
|
**Read specific source files only when:**
|
|
87
|
-
- Area docs don't exist yet (tell the user to run
|
|
96
|
+
- Area docs don't exist yet (tell the user to run `/grimoire:discover` first — planning without area docs produces worse tasks)
|
|
88
97
|
- Area docs exist but you need to verify a specific implementation detail (e.g., exact function signature, exact import path)
|
|
89
98
|
- You need to read existing step definitions to understand the test setup
|
|
90
99
|
|
|
@@ -133,11 +142,22 @@ Level 1-2 changes with minor gaps may proceed; level 3-4 with multiple gaps shou
|
|
|
133
142
|
**If no real gaps**, proceed directly to task generation.
|
|
134
143
|
|
|
135
144
|
### 4. Generate Tasks
|
|
136
|
-
Create `.grimoire/changes/<change-id>/tasks.md`. **Every
|
|
145
|
+
Create `.grimoire/changes/<change-id>/tasks.md`. **Every task produces both production code AND a test — but the test level matches the artifact the task derives from.** Tasks are structured as pairs: the failing test first, then the production code.
|
|
146
|
+
|
|
147
|
+
**Tag every implementation task with a `verify:` level** — this tells `grimoire-apply` which test vehicle to use. Match the artifact:
|
|
148
|
+
|
|
149
|
+
| Task derives from | `verify:` | Test vehicle |
|
|
150
|
+
|-------------------|-----------|--------------|
|
|
151
|
+
| a `.feature` scenario (actor-observable behavior) | `scenario` | step definitions + Gherkin |
|
|
152
|
+
| a constraint in `constraints.md` (security/NFR/observability) | `unit-invariant` | unit/integration test asserting the invariant |
|
|
153
|
+
| an ADR consequence, refactor, or internal change with no spec | `characterization` | unit / characterization test |
|
|
154
|
+
|
|
155
|
+
**Do not plan a `.feature` scenario task for a constraint or an internal change.** Constraints get `unit-invariant` unit tests; internal changes get `characterization` tests. Forcing Gherkin onto a non-behavioral concern is the antipattern that fills feature files with slop (one right way: behavior → scenario, everything else → unit test).
|
|
137
156
|
|
|
138
157
|
**THE PLAN'S SCOPE IS WHAT WAS APPROVED.** Tasks may only derive from:
|
|
139
|
-
-
|
|
140
|
-
-
|
|
158
|
+
- `.feature` scenarios in this change → `verify: scenario`
|
|
159
|
+
- Constraints added/touched in `.grimoire/docs/constraints.md` → `verify: unit-invariant`
|
|
160
|
+
- ADRs in this change (and their Confirmation sections) → `verify: unit-invariant` or `characterization`
|
|
141
161
|
- `data.yml` entries in this change
|
|
142
162
|
- The manifest's Assumptions, Pre-Mortem mitigations, and Prior Art borrowings
|
|
143
163
|
- Verification tasks (run feature suite, run project suite, validate ADR confirmation)
|
|
@@ -259,8 +279,8 @@ Follow the rules in `../references/testing-contracts.md`. Key points: mock at HT
|
|
|
259
279
|
- If a library was rejected for a specific reason (e.g., doesn't support X), add a comment to the relevant task noting this so future developers don't re-evaluate the same option
|
|
260
280
|
|
|
261
281
|
**Existing code to reuse:**
|
|
262
|
-
-
|
|
263
|
-
- If
|
|
282
|
+
- Query the graph (`search_graph` by concept/name) for existing utilities that apply to this change; area docs give conventions, the graph gives the reusable symbols
|
|
283
|
+
- If `grimoire health`/mcp shows existing clones in the area you're touching, tasks should consolidate rather than add more
|
|
264
284
|
- Add a "Reuse" section at the top of tasks.md listing specific functions/classes to import instead of rewriting
|
|
265
285
|
|
|
266
286
|
**Verification (always last):**
|
|
@@ -282,12 +302,12 @@ The tasks file starts with a context block so any LLM can orient without re-read
|
|
|
282
302
|
|
|
283
303
|
## 1. <Capability/Area>
|
|
284
304
|
<!-- context:
|
|
285
|
-
-
|
|
305
|
+
- features/<name>.feature
|
|
286
306
|
- .grimoire/docs/<area>.md
|
|
287
307
|
- src/<area>/<file-to-edit>.ts
|
|
288
308
|
- tests/<area>/<test-file>.ts
|
|
289
309
|
-->
|
|
290
|
-
- [ ] 1.1 Write step defs in `<exact path>` for scenario: "<scenario name>" in
|
|
310
|
+
- [ ] 1.1 (verify: scenario) Write step defs in `<exact path>` for scenario: "<scenario name>" in `features/<file>`
|
|
291
311
|
- Given: <what the step does, what it calls>
|
|
292
312
|
- When: <what the step does, what it calls>
|
|
293
313
|
- Then: <what to assert — specific expected values/states>
|
|
@@ -295,32 +315,40 @@ The tasks file starts with a context block so any LLM can orient without re-read
|
|
|
295
315
|
- <specific function/class/view to create or modify>
|
|
296
316
|
- <specific behavior to implement>
|
|
297
317
|
- <edge cases to handle>
|
|
298
|
-
- [ ] 1.3 Write step defs in `<exact path>` for scenario: "<next scenario>"
|
|
299
|
-
...
|
|
300
|
-
- [ ] 1.4 Implement in `<exact path>`:
|
|
301
|
-
...
|
|
302
318
|
|
|
303
|
-
## 2.
|
|
319
|
+
## 2. Constraints
|
|
320
|
+
<!-- context:
|
|
321
|
+
- .grimoire/docs/constraints.md
|
|
322
|
+
- src/<area>/<file-to-edit>.ts
|
|
323
|
+
- tests/<area>/<unit-test-file>.ts
|
|
324
|
+
-->
|
|
325
|
+
- [ ] 2.1 (verify: unit-invariant) Write unit test in `<exact path>` asserting constraint: "<assertion from constraints.md>"
|
|
326
|
+
- Arrange: <setup>
|
|
327
|
+
- Assert: <the invariant — exact expected behavior, no Gherkin>
|
|
328
|
+
- [ ] 2.2 Implement in `<exact path>`:
|
|
329
|
+
- <specific change that satisfies the invariant>
|
|
330
|
+
|
|
331
|
+
## 3. Shared Steps
|
|
304
332
|
<!-- context:
|
|
305
333
|
- tests/step_defs/common.py
|
|
306
|
-
-
|
|
334
|
+
- features/<all relevant .feature files>
|
|
307
335
|
-->
|
|
308
|
-
- [ ]
|
|
336
|
+
- [ ] 3.1 Add to `<exact path>`:
|
|
309
337
|
- Given "<step text>": <what it does>
|
|
310
338
|
- Given "<step text>": <what it does>
|
|
311
339
|
|
|
312
|
-
##
|
|
340
|
+
## 4. Architecture
|
|
313
341
|
<!-- context:
|
|
314
|
-
- .grimoire/
|
|
342
|
+
- .grimoire/decisions/<nnnn-title>.md
|
|
315
343
|
- src/<files affected by decision>
|
|
316
344
|
-->
|
|
317
|
-
- [ ]
|
|
318
|
-
- [ ]
|
|
345
|
+
- [ ] 4.1 (verify: characterization) In `<exact path>`: <specific change from ADR>
|
|
346
|
+
- [ ] 4.2 Add test in `<exact path>`: <ADR confirmation check — what to assert>
|
|
319
347
|
|
|
320
|
-
##
|
|
321
|
-
- [ ]
|
|
322
|
-
- [ ]
|
|
323
|
-
- [ ]
|
|
348
|
+
## 5. Verification
|
|
349
|
+
- [ ] 5.1 Run `<exact test command>` — all new scenarios green
|
|
350
|
+
- [ ] 5.2 Run `<exact test command>` — no regressions
|
|
351
|
+
- [ ] 5.3 Run `<exact test command>` — full project suite
|
|
324
352
|
```
|
|
325
353
|
|
|
326
354
|
**Context blocks are mandatory.** Every task section (except Verification) must have a `<!-- context: ... -->` listing the files needed. This serves two purposes:
|
|
@@ -330,11 +358,13 @@ The tasks file starts with a context block so any LLM can orient without re-read
|
|
|
330
358
|
### 6. Quality Check
|
|
331
359
|
Before presenting to the user, verify the plan:
|
|
332
360
|
- [ ] Every task references a specific file path (no "implement the feature")
|
|
333
|
-
- [ ] Every
|
|
361
|
+
- [ ] Every implementation task carries a `verify:` tag matching its source artifact — `scenario` only for `.feature` behavior; `unit-invariant` for constraints; `characterization` for internal/refactor. No `.feature` scenario task for a constraint or internal change.
|
|
362
|
+
- [ ] Every test task describes what to assert (no "write a test")
|
|
334
363
|
- [ ] Every implementation task describes what to create/modify (no "add the code")
|
|
335
364
|
- [ ] The verification section has the exact commands to run
|
|
336
|
-
- [ ] Tasks are ordered: shared steps →
|
|
365
|
+
- [ ] Tasks are ordered: shared steps → test → production code → verification
|
|
337
366
|
- [ ] No task requires the LLM to make architectural decisions — those should already be in the ADR
|
|
367
|
+
- [ ] **Principles gate** (`../references/principles.md`): no task introduces a duplicate home for an existing fact (DRY), a second way to do an existing thing (one right way), a reinvented wheel where a tool/library/proven pattern exists (don't reinvent), or an abstraction/dependency justified only by a hypothetical (KISS). Any that does has a stated reason.
|
|
338
368
|
|
|
339
369
|
If any task is too vague, make it more specific before presenting. Read more codebase if needed.
|
|
340
370
|
|
|
@@ -17,14 +17,14 @@ Generate a pull request description from grimoire change artifacts and optionall
|
|
|
17
17
|
- Loose match: "PR", "pull request", "ready to merge", "create PR"
|
|
18
18
|
|
|
19
19
|
## Routing
|
|
20
|
-
- Tasks incomplete or finalize not done → `grimoire-apply` first. Do not create a PR before the change is finalized — PR
|
|
20
|
+
- Tasks incomplete or finalize not done → `grimoire-apply` first. Do not create a PR before the change is finalized — the PR reflects the finished branch state (decisions accepted, change folder removed).
|
|
21
21
|
- Haven't committed yet → `grimoire-commit` first
|
|
22
22
|
- Want a pre-merge design review → this skill includes optional post-implementation review
|
|
23
23
|
|
|
24
24
|
## Prerequisites
|
|
25
|
-
- Change has been finalized: `.grimoire/changes/<change-id>/` is removed
|
|
26
|
-
-
|
|
27
|
-
- The change is on a feature branch (created during apply)
|
|
25
|
+
- Change has been finalized: `.grimoire/changes/<change-id>/` is removed (manifest/tasks were ephemeral scaffolding)
|
|
26
|
+
- Decision records are live in `.grimoire/decisions/` with status `accepted`
|
|
27
|
+
- The change is on a feature branch (created during draft/apply); its diff vs. `main` is the change
|
|
28
28
|
|
|
29
29
|
## Workflow
|
|
30
30
|
|
|
@@ -117,9 +117,8 @@ Check that the branch is pushed to the remote before creating. If not, offer to
|
|
|
117
117
|
|
|
118
118
|
### 6. Link Back
|
|
119
119
|
After PR creation:
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- Suggest running `grimoire archive <change-id>` to complete the lifecycle
|
|
120
|
+
- The `Change: <change-id>` trailer on the commits links them to the change; the PR body + git log are the durable record (the change folder was already removed at finalize).
|
|
121
|
+
- Suggest merging the PR to complete the change. There is no archive step — git history is the history.
|
|
123
122
|
|
|
124
123
|
## Important
|
|
125
124
|
- The PR description must trace back to grimoire artifacts — this is what makes the audit trail work.
|
|
@@ -129,4 +128,4 @@ After PR creation:
|
|
|
129
128
|
- If tasks are incomplete, warn the user but don't block PR creation — they may want a draft PR.
|
|
130
129
|
|
|
131
130
|
## Done
|
|
132
|
-
When the PR is created (or description is presented for manual creation), the workflow is complete. Suggest
|
|
131
|
+
When the PR is created (or description is presented for manual creation), the workflow is complete. Suggest merging the PR to complete the change — git history + the `Change:` trailer are the record; there is no separate archive step.
|
|
@@ -64,7 +64,7 @@ git log <base>..<head> --format="%B" | grep -E "^Change:"
|
|
|
64
64
|
|
|
65
65
|
If present:
|
|
66
66
|
- Change ID = trailer value
|
|
67
|
-
- Load artifacts:
|
|
67
|
+
- Load artifacts: check `.grimoire/changes/<change-id>/` for an active change. If the change is already finalized/merged the change folder is gone — read the artifacts from the PR's head branch (`git diff main` shows the live `features/`, `.grimoire/decisions/`, `.grimoire/docs/constraints.md`) and use the `Change:` trailer to correlate commits.
|
|
68
68
|
- Read `manifest.md`, all `.feature` files in the change, decision records, `tasks.md`, `data.yml`
|
|
69
69
|
- Also grep for `Scenarios:` and `Decisions:` trailers to scope review to the named items
|
|
70
70
|
|
|
@@ -26,7 +26,7 @@ Systematically find, prioritize, and plan tech debt reduction. Combines automate
|
|
|
26
26
|
## Prerequisites
|
|
27
27
|
- A grimoire-initialized project (`.grimoire/` exists)
|
|
28
28
|
- Git history available (hotspot analysis needs `git log`)
|
|
29
|
-
- Ideally:
|
|
29
|
+
- Ideally: codebase-memory-mcp indexed (live structure/duplication intelligence) + `/grimoire:discover` run (area intent docs help contextualize findings)
|
|
30
30
|
|
|
31
31
|
## Debt Item Format
|
|
32
32
|
|
|
@@ -113,7 +113,7 @@ Run applicable scans from the categories in `../references/refactor-scan-categor
|
|
|
113
113
|
- **Circular dependencies** — tight coupling between modules
|
|
114
114
|
- **Dependency staleness** — uses `config.tools.dep_audit` or package manager outdated commands
|
|
115
115
|
- **Broken promises** — aged TODO/FIXME/HACK comments via `grep` + `git blame`
|
|
116
|
-
- **Duplication** — textual clones via
|
|
116
|
+
- **Duplication** — textual clones via `config.tools.duplicates` or `grimoire health` (config-driven duplicates metric); plus semantic duplicate detection via `search_graph(semantic_query=[...])` to find re-implementations under different names (requires `codebase-memory-mcp`)
|
|
117
117
|
- **Dead code** — uses `config.tools.dead_code` or `codebase-memory-mcp` graph queries
|
|
118
118
|
- **Test debt** — high complexity + low coverage
|
|
119
119
|
- **Pattern divergence** — code that contradicts established codebase patterns; uses `codebase-memory-mcp` peer group analysis + hallucinated reference detection (skip if graph not indexed)
|
|
@@ -95,6 +95,12 @@ Persona scope for design review:
|
|
|
95
95
|
- 4.6 Code Style Reviewer — **skip** (no code yet; runs only on diff reviews)
|
|
96
96
|
- 4.7 Adversarial User — engage per matrix; criteria in `../references/adversarial-personas.md`
|
|
97
97
|
- 4.8 Contrarian — runs last when any persona produced a blocker; calibrates other personas' findings post-hoc
|
|
98
|
+
- 4.9 Principles Auditor — **runs on every review (complexity ≥ 2), always.** Audits the design against the four principles in `../references/principles.md` and raises a finding for each violation lacking a stated reason:
|
|
99
|
+
- **One right way** — does any artifact introduce a second way to do something the codebase already does? Does a spec leave two approaches open ("X or Y")? → blocker until one is chosen.
|
|
100
|
+
- **DRY** — is any fact given a second home (a capability in feature + MADR + constraint; a constant/rule duplicated)? Does any task store something derivable from code/mcp? → blocker.
|
|
101
|
+
- **Don't reinvent the wheel** — does any task build a mechanism that an existing tool/library/proven pattern already provides (custom crypto/auth, a bespoke change-tracking/diff/staging process where git suffices)? → blocker.
|
|
102
|
+
- **Keep it simple** — any abstraction, indirection, new dependency, or new file justified only by a hypothetical, or scope reaching past a non-goal? → suggestion (blocker if it adds a maintained surface).
|
|
103
|
+
- Also enforce the **artifact-jurisdiction** rule: any `.feature` scenario that is really a constraint (security/NFR/observability), an internal technical detail, or a non-functional concern is a blocker — it belongs in `constraints.md` or a MADR, not Gherkin.
|
|
98
104
|
|
|
99
105
|
### 5.5 Visual Fidelity (cheap tier)
|
|
100
106
|
|
|
@@ -144,6 +150,12 @@ Compile into the standard report layout (§5 of the personas reference):
|
|
|
144
150
|
- **[suggestion]** [axe-color-contrast] `designs/preview.html` — Element has insufficient color contrast 3.8. Impact: serious.
|
|
145
151
|
(or: "axe-core: no violations." / "axe-core not installed — install `@axe-core/cli` for accessibility checks.")
|
|
146
152
|
|
|
153
|
+
## Principles Auditor
|
|
154
|
+
- **[blocker]** [DRY] PII-scrubbing behavior is specified in both `features/pii-log-scrubbing.feature` and ADR-0008 — one authoritative home. Move to `constraints.md`, link the MADR.
|
|
155
|
+
- **[blocker]** [jurisdiction] `features/logging-observability.feature` describes internal log structure (no external actor) — belongs in `constraints.md`, not a `.feature`.
|
|
156
|
+
- **[suggestion]** [KISS] Task 3.2 adds a `BaseExtractor` for a single caller — inline until a second extractor exists.
|
|
157
|
+
(or: "No principle violations found.")
|
|
158
|
+
|
|
147
159
|
## Contrarian <!-- omit when zero findings from all personas -->
|
|
148
160
|
- **[blocker upheld]** ...
|
|
149
161
|
- **[blocker → suggestion]** ...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grimoire-verify
|
|
3
|
-
description: Verify that implementation matches feature specs and decision records. Use after apply is complete, before
|
|
3
|
+
description: Verify that implementation matches feature specs and decision records. Use after apply is complete, before committing and opening a PR.
|
|
4
4
|
compatibility: Designed for Claude Code (or similar products)
|
|
5
5
|
metadata:
|
|
6
6
|
author: kiwi-data
|
|
@@ -9,11 +9,11 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
# grimoire-verify
|
|
11
11
|
|
|
12
|
-
Verify that implementation matches the feature specs and decision records. Run after apply, before
|
|
12
|
+
Verify that implementation matches the feature specs and decision records. Run after apply, before commit and PR.
|
|
13
13
|
|
|
14
14
|
## Triggers
|
|
15
15
|
- User wants to verify a grimoire change is correctly implemented
|
|
16
|
-
- User asks to check, verify, or review a change before
|
|
16
|
+
- User asks to check, verify, or review a change before committing
|
|
17
17
|
- Loose match: "verify", "check", "review" with a change reference
|
|
18
18
|
|
|
19
19
|
## Routing
|
|
@@ -236,8 +236,8 @@ Produce a structured report:
|
|
|
236
236
|
|
|
237
237
|
### 8. Recommend Next Steps
|
|
238
238
|
Based on the report:
|
|
239
|
-
- **All clear** → recommend
|
|
240
|
-
- **Critical issues** → must fix before
|
|
239
|
+
- **All clear** → recommend committing and opening a PR (git diff is the staging area, the PR is the changelog)
|
|
240
|
+
- **Critical issues** → must fix before committing
|
|
241
241
|
- **Warnings only** → user decides whether to fix or accept
|
|
242
242
|
- **Dead features found** → suggest a removal change or updating the features
|
|
243
243
|
|
|
@@ -251,6 +251,6 @@ Based on the report:
|
|
|
251
251
|
|
|
252
252
|
## Done
|
|
253
253
|
When the verification report is presented, the workflow is complete. Suggest next steps based on findings:
|
|
254
|
-
- **All clear** → `grimoire
|
|
255
|
-
- **Critical issues** → must fix before
|
|
254
|
+
- **All clear** → `grimoire-commit` then `grimoire-pr`
|
|
255
|
+
- **Critical issues** → must fix before committing
|
|
256
256
|
- **Warnings only** → user decides whether to fix or accept
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grimoire-vuln-remediate
|
|
3
|
+
description: File the dev work from a vulnerability triage — turn affected findings into tickets in the configured bug tracker, record risk-accepted items with expiry, and stub grimoire changes for non-trivial fixes. Consumes a grimoire-vuln-triage record. Use after triage, when you're ready to action the findings that actually matter.
|
|
4
|
+
compatibility: Designed for Claude Code (or similar products)
|
|
5
|
+
metadata:
|
|
6
|
+
author: kiwi-data
|
|
7
|
+
version: "0.1"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# grimoire-vuln-remediate
|
|
11
|
+
|
|
12
|
+
`grimoire-vuln-triage` decides *what matters*. This skill **files the work** for the findings that survived: it turns `affected` advisories into tickets in the team's configured bug reporting system, records `accept` items in a risk-acceptance register with an expiry, and stubs a grimoire change for fixes too big to be a one-line bump. It never invents urgency — it acts on the triage's verdicts.
|
|
13
|
+
|
|
14
|
+
The split is deliberate: triage classifies (read-only, repeatable), remediate commits to action (writes tickets, branches, registers). Run triage first.
|
|
15
|
+
|
|
16
|
+
## Triggers
|
|
17
|
+
- After triage: "file these", "create the tickets", "remediate the CVEs", "action the vuln triage"
|
|
18
|
+
- "open tickets for the affected vulnerabilities", "track the risk-accepted ones"
|
|
19
|
+
- User points at a triage record and asks to take it forward
|
|
20
|
+
- Loose match: "vuln remediate", "file vulnerabilities", "security tickets", "remediation plan"
|
|
21
|
+
|
|
22
|
+
## Routing
|
|
23
|
+
- No triage record yet → `grimoire-vuln-triage` first. Do not file tickets off a raw scanner dump — file only what triage marked `affected`.
|
|
24
|
+
- A fix that's a real code change (new abstraction, behavior change, schema) → stub a change and hand to `grimoire-draft` / `grimoire-plan` / `grimoire-apply`.
|
|
25
|
+
- A confirmed-exploitable code defect a developer will fix immediately → `grimoire-bug` (reproduction-first).
|
|
26
|
+
- Infra follow-ups (base-image bump, secrets-in-image, IaC misconfig from the triage's "Infra follow-ups") → infra board / `grimoire-draft`, not app remediation.
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
- A triage record at `.grimoire/security/vulns/<run-date>/triage.md` (from `grimoire-vuln-triage`). If the user points at a different path, use it.
|
|
30
|
+
- `.grimoire/config.yaml` — read `bug_trackers` (MCP) for where to file. If `none`, fall back to a local remediation doc.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
### 1. Read the triage record
|
|
35
|
+
|
|
36
|
+
Load the triage. Pull the actionable buckets — **ignore `fixed` and `not_affected`** (triage already dismissed them; they are the audit trail, not work):
|
|
37
|
+
- **hotfix-now** — expedited, confidential handling.
|
|
38
|
+
- **next-release** — normal release-cycle work.
|
|
39
|
+
- **accept** — no fix available / low risk → goes to the risk-acceptance register, not a fix ticket.
|
|
40
|
+
- **under_investigation** — file a time-boxed investigation task, not a fix. **Does not enter the risk register** — it isn't a decision yet. When the investigation resolves, re-run remediate so it routes to close / ticket / register.
|
|
41
|
+
- **Infra follow-ups** — route separately (Step 5).
|
|
42
|
+
|
|
43
|
+
Cross-check the record's totals against what you're about to file so nothing is dropped or invented.
|
|
44
|
+
|
|
45
|
+
### 2. Decide the fix type per affected item
|
|
46
|
+
|
|
47
|
+
For each `affected` advisory, classify the remediation so it routes correctly:
|
|
48
|
+
- **Trivial bump** — a patch/minor version with a fix exists (`upgrade X 1.2.3 → 1.2.4`), no API break. Can be a direct PR or a simple ticket.
|
|
49
|
+
- **Non-trivial fix** — major-version bump, code changes, base-image rebuild, or a transitive dep that needs a constraint/override. Needs design → stub a grimoire change (Step 4).
|
|
50
|
+
- **No fix available** — `will_not_fix` / no fixed version. Not a fix ticket → risk-acceptance register with an expiry (Step 3). Never file an "upgrade X" ticket when no fixed version exists.
|
|
51
|
+
|
|
52
|
+
### 3. File the work into the configured bug reporting system
|
|
53
|
+
|
|
54
|
+
Read `bug_trackers` in `.grimoire/config.yaml`.
|
|
55
|
+
|
|
56
|
+
**If a tracker MCP is configured (Jira / Linear / GitHub):**
|
|
57
|
+
- **Idempotency first** — search the tracker for the CVE/GHSA id before creating anything. If a ticket exists, update it (link the triage, refresh urgency); don't duplicate. Mirror both directions like `grimoire-bug-triage`.
|
|
58
|
+
- **One ticket per advisory** (or per package-upgrade when several CVEs share one bump — group by the fix, not the CVE). Include: id(s), component + version, fixed version / action, urgency, reachability + exposure summary, and a link back to `.grimoire/security/vulns/<run-date>/triage.md`. Set priority from urgency (hotfix-now → highest).
|
|
59
|
+
- **hotfix-now is confidential** — mirror `grimoire-bug-triage` §7. Do **not** post exploit details, reachability specifics, or a step-by-step in a public tracker. If the tracker is public, the ticket states impact + "fix in progress, details held privately"; the detail stays in the local triage record. Notify the security owner out of band. Recommend an expedited branch (non-descriptive name) + out-of-band release.
|
|
60
|
+
- **under_investigation** → a time-boxed task ("confirm reachability of <CVE> in <path> by <date>"), assigned, with the open question from triage.
|
|
61
|
+
|
|
62
|
+
**If `bug_trackers: none`:**
|
|
63
|
+
- Write `.grimoire/security/vulns/<run-date>/remediation.md` — a checklist: one entry per actionable item with `[ ]` checkbox, id(s), action (exact upgrade or change-stub link or accept-ref), urgency, suggested owner, and the triage link. This is the deliverable the team works from until a tracker exists. Tell the user where it is.
|
|
64
|
+
|
|
65
|
+
### 4. Stub a grimoire change for non-trivial fixes
|
|
66
|
+
|
|
67
|
+
For each non-trivial fix, create `.grimoire/changes/<change-id>/manifest.md` so the normal build workflow takes over:
|
|
68
|
+
- Frontmatter `status: proposed`, plus `source: vuln-triage`, the CVE id(s), and the triage path.
|
|
69
|
+
- **Why**: the vulnerability + why a one-line bump isn't enough (major break, code path affected, transitive constraint).
|
|
70
|
+
- **Scope**: the upgrade/change needed and the blast radius from triage (which code paths touch the vulnerable surface).
|
|
71
|
+
- Point the user at `grimoire-draft` / `grimoire-plan` to continue. Reference the change-id from the ticket so the trail is intact.
|
|
72
|
+
|
|
73
|
+
### 5. Record risk-accepted items (with expiry)
|
|
74
|
+
|
|
75
|
+
**Register invariant: only a settled `accept` verdict enters the register.** The register means "we triaged this, decided, and consciously accepted the residual risk." Two states must stay out of it:
|
|
76
|
+
- **`under_investigation`** — not decided yet. It gets an investigation task (Step 3), nothing in the register. The register's `vex_justification` must be a real VEX code, not "pending"; a finding you can't yet justify hasn't been accepted. When the investigation resolves, *then* it routes — to `not_affected` (close), `affected` (ticket/change), or `accept` (register).
|
|
77
|
+
- **`not_affected`** — already dismissed by triage, deterministically, every scan. It needs no register entry; adding one would bloat the register with the unreachable os-package noise the reachability verdict already suppresses.
|
|
78
|
+
|
|
79
|
+
For every settled `accept` item, append to `.grimoire/security/accepted-risks.yml` (create from the template if absent). Each entry: `cve`, `component`, `vex_justification` (a real code), `reason` (why it's acceptable — reachability/exposure/no-fix), `owner`, `accepted` date, and an **`expires`** date (when to re-triage — sooner for higher residual risk, default ~90 days). An accepted risk is not closed — it's scheduled for re-evaluation.
|
|
80
|
+
|
|
81
|
+
**This register is read back by `grimoire-vuln-triage` during reconciliation** — an unexpired entry auto-suppresses that CVE as a known-accepted, so the same finding doesn't re-flood the queue next scan. An **expired** entry is re-surfaced for re-triage. Don't accept the same CVE twice; update the existing entry.
|
|
82
|
+
|
|
83
|
+
A no-fix os-package finding that is genuinely **`affected` and accepted** (reachable, no patched base yet) belongs here — with `component_type: os-package` and the trace summary from `container-scan-triage.md`. One that is **`not_affected`** (unreachable, like a headless API's GPU/terminal libs) does **not**.
|
|
84
|
+
|
|
85
|
+
### 6. Optionally execute trivial bumps (skippable)
|
|
86
|
+
|
|
87
|
+
Offer — don't assume — to apply trivial bumps directly: edit the manifest pin, run the lockfile update (`uv lock` / `npm install` / etc.), and run the configured `dep_audit` to confirm the advisory clears. Keep it on a branch. This step is **opt-in**; if the user just wants tickets, file and stop. If you do apply, the supply-chain rules in `security-compliance.md` still hold (committed lockfile + integrity hashes).
|
|
88
|
+
|
|
89
|
+
### 7. Report and update the record
|
|
90
|
+
|
|
91
|
+
Update the triage record (or a sibling `remediation.md`) with what was filed: ticket refs, change-ids, register entries. Report the headline:
|
|
92
|
+
- N tickets filed (M hotfix-now expedited), K risk-accepted (next review dates), J change stubs, I investigations.
|
|
93
|
+
- Any hotfix-now: restate it's flagged for the security owner and expedited.
|
|
94
|
+
- Where the artifacts are (tracker links / local docs / register).
|
|
95
|
+
|
|
96
|
+
## Important
|
|
97
|
+
- **Act only on triage verdicts.** This skill files what triage marked `affected` / `accept` / `under_investigation`. It does not re-triage, re-rank, or escalate on its own. If a verdict looks wrong, send it back to `grimoire-vuln-triage`, don't override it here.
|
|
98
|
+
- **No fixed version → no upgrade ticket.** A `will_not_fix` / no-fix finding goes to the risk register with an expiry, or to infra for a base bump. Filing "upgrade X" when X has no fix wastes a developer's time.
|
|
99
|
+
- **Group by the fix, not the CVE.** One upgrade often clears several advisories — one ticket, listing all the CVEs it resolves.
|
|
100
|
+
- **hotfix-now is confidential.** No exploit detail in public trackers; impact-only, details local, security owner notified out of band, expedited branch.
|
|
101
|
+
- **Idempotent.** Search before filing; update existing tickets and register entries instead of duplicating. Sync local ↔ tracker both ways.
|
|
102
|
+
- **`accept` carries an expiry and feeds back into triage.** The register is the loop that stops accepted noise from re-flooding every scan — and re-surfaces it when the expiry passes.
|
|
103
|
+
- **The register holds only settled `accept` verdicts.** `under_investigation` gets an investigation task, never a register entry (it isn't decided); `not_affected` is already suppressed by triage's reachability verdict each run. Keep the invariant clean: register = decided + accepted, with a real VEX justification.
|
|
104
|
+
- **Steps are skippable.** Filing, change-stubbing, and direct bumps are independent — do what the user asked for and stop.
|
|
105
|
+
|
|
106
|
+
## Done
|
|
107
|
+
When every actionable triage finding has a home — a ticket (or remediation.md entry), a change stub, or a risk-register entry with an expiry — and the triage record reflects what was filed, remediation is complete. Hotfix-now items are flagged and confidential; accepted items are scheduled for re-triage.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grimoire-vuln-triage
|
|
3
|
+
description: Triage vulnerability scans from any source — npm audit, pip-audit, osv-scanner, Trivy, Grype, Snyk, Dependabot, SARIF, or a report a teammate forwards — against our actual deployment model and recorded mitigating controls. Reconciles stale scans against the current tree, then decides the one thing that matters per finding — drop-everything hotfix vs next release cycle — and suppresses non-actionable noise with VEX verdicts. Use when a scanner produces a flood of CVEs and you need to know which actually matter here.
|
|
4
|
+
compatibility: Designed for Claude Code (or similar products)
|
|
5
|
+
metadata:
|
|
6
|
+
author: kiwi-data
|
|
7
|
+
version: "0.2"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# grimoire-vuln-triage
|
|
11
|
+
|
|
12
|
+
Vulnerability scanners flag every CVE that *exists* in your tree or image, ranked by CVSS base score — which knows nothing about your deployment, and nothing about whether you already upgraded past it. Most findings are not exploitable as you actually run the code. This skill is **scanner-agnostic**: it normalizes whatever it's handed (npm audit, pip-audit, osv-scanner, Trivy, Grype, Snyk, Dependabot, SARIF, or a freeform CSV/markdown report) into one canonical model, reconciles it against the current tree, and triages each surviving advisory against **our** deployment and controls to answer the only question that drives action:
|
|
13
|
+
|
|
14
|
+
> **Drop everything and hotfix now, or let it ride the normal testing / release cycle?**
|
|
15
|
+
|
|
16
|
+
It produces VEX verdicts (`fixed` / `not_affected` / `affected` / `under_investigation`) so non-actionable findings are dismissed with an auditor-defensible justification, and an urgency (`hotfix-now` / `next-release` / `accept`) for the ones that survive. Covers application dependencies, OS packages from container scans, and runtime/build tooling alike.
|
|
17
|
+
|
|
18
|
+
This skill **classifies**. Filing the dev work (tickets in the configured bug reporting system) is the job of `grimoire-vuln-remediate`, which consumes this triage.
|
|
19
|
+
|
|
20
|
+
## Triggers
|
|
21
|
+
- A scanner produces a wall of findings: "npm audit found 40 vulnerabilities", "pip-audit is screaming", "trivy flagged 200 CVEs in the image", "triage these CVEs"
|
|
22
|
+
- "Is this CVE actually a problem for us?", "do we need to hotfix this or can it wait?"
|
|
23
|
+
- "which of these vulnerabilities actually matter", "filter out the noise from the scan"
|
|
24
|
+
- A teammate forwards a scan report (any tool, any format) and asks what's real
|
|
25
|
+
- Loose match: "vuln triage", "CVE triage", "security scan", "trivy/grype/snyk results", "audit results", "image scan"
|
|
26
|
+
|
|
27
|
+
## Routing
|
|
28
|
+
- A *reported* security bug (not a scanner finding) → `grimoire-bug-triage` (it has a security classification path)
|
|
29
|
+
- A dependency *add/upgrade* review (lockfile, floating ranges, supply chain) → review-time; see `../references/security-compliance.md` § Supply Chain Defense, enforced by `grimoire-review` / `grimoire-precommit-review`
|
|
30
|
+
- After this triage, to file dev work → `grimoire-vuln-remediate`
|
|
31
|
+
- Persistent IaC/container misconfig (root user, no limits, `:latest` base) → `grimoire-draft`/infra, not an app hotfix
|
|
32
|
+
- A control gap surfaced here (a mitigation assumed but never recorded) → `grimoire-draft` to write the MADR
|
|
33
|
+
|
|
34
|
+
## Prerequisites
|
|
35
|
+
- A scan to triage: output of `config.tools.dep_audit` / `config.tools.security`, a saved scan file (e.g. `reports/security/...`), or pasted text. Prefer machine-readable (`--json` / SARIF) over a human table.
|
|
36
|
+
- The repo's current lockfile/manifest (or deployed image tag) available for reconciliation.
|
|
37
|
+
- Network access for KEV + EPSS enrichment (degrades gracefully to CVSS-only if offline).
|
|
38
|
+
- Best results with `codebase-memory-mcp` for reachability; falls back to grep.
|
|
39
|
+
|
|
40
|
+
## Workflow
|
|
41
|
+
|
|
42
|
+
Read `../references/dependency-vuln-triage.md` now — it has the canonical model, the format adapters, the reconciliation rule, the enrichment feeds, the type-aware reachability rules, the VEX statuses, the urgency tree, and the record format. Follow it. The steps below are the spine.
|
|
43
|
+
|
|
44
|
+
### 1. Normalize the scan into the canonical model (any scanner)
|
|
45
|
+
|
|
46
|
+
Identify the source and map each finding to the canonical advisory (reference § Step 1): `id`, `aliases`, `cve`, `component`, `component_type` (`library`/`os-package`/`container`/`iac`/`runtime`), `installed_version`, `fixed_version`, `severity`/`cvss`, `target`, `scanner`. Use the format adapter for the tool you were handed — **never assume one tool's field names apply to another** (npm's `isDirect`/`via` ≠ pip-audit's `aliases`/`fix_versions` ≠ Trivy's `Results[].Vulnerabilities[]` with `Class`/`Type`/`Status`). For an unknown/freeform report, extract the minimum (`id`, `component`, version, fixed version) and mark the rest `unknown`. If you can't parse it, ask for `--json`/SARIF rather than guessing.
|
|
47
|
+
|
|
48
|
+
**Dedup + non-CVE results.** Collapse the same CVE listed across multiple packages (Trivy does this constantly) to unique `(id, component_type)`, keeping the package list — report `raw_findings → unique_advisories` so the noise reduction is visible. Don't discard `Class: secret` / `Class: config` results — they aren't package CVEs; route them (secrets-in-image, Dockerfile/k8s misconfig) to infra/`grimoire-draft`, not triage.
|
|
49
|
+
|
|
50
|
+
### 2. Reconcile against the current tree FIRST (mandatory)
|
|
51
|
+
|
|
52
|
+
Before any enrichment, compare each advisory's `installed_version` against what the repo resolves **right now** (reference § Step 2): read the live lockfile/manifest (`uv.lock`/`poetry.lock`/`package-lock.json`/`go.sum`/`Cargo.lock`/`Gemfile.lock`), or for container/OS findings check the currently deployed image tag / Dockerfile base. If the current version ≥ `fixed_version`, mark **`fixed`** and drop it before enrichment — record it under "Already fixed" as the audit trail. Honor manifest comments / prior triage that already dismiss a CVE. **Never file remediation for an advisory you haven't confirmed still exists.** On a stale scan this pass clears most of the queue.
|
|
53
|
+
|
|
54
|
+
**Honor the risk-acceptance register.** Read `.grimoire/security/accepted-risks.yml` (written by `grimoire-vuln-remediate`). An **unexpired** entry for a CVE means it was already triaged and consciously accepted → carry it as known-accepted, don't re-escalate (cite the register entry). An **expired** entry → re-triage it fresh (the acceptance lapsed). This is what stops accepted findings from re-flooding the queue every scan.
|
|
55
|
+
|
|
56
|
+
### 3. Enrich the survivors — KEV then EPSS
|
|
57
|
+
|
|
58
|
+
Per the reference: fetch the **CISA KEV** catalog once and match every `cve`/`aliases` (known-exploited = strongest hotfix signal); fetch **EPSS** for all CVE ids (batch, comma-separated) for exploit probability. Cache both in the run dir. If offline, record `kev-feed: offline` / `epss-fetched: false` and proceed on CVSS + reachability + exposure — say so. IaC/config findings skip threat-intel (no CVE).
|
|
59
|
+
|
|
60
|
+
### 4. Reachability — type-aware, the cheapest big filter
|
|
61
|
+
|
|
62
|
+
Judge reachability by `component_type` (reference § Reachability):
|
|
63
|
+
- **library** — dev/test-only (infer from lockfile groups, not a flag) → `not_affected` in prod; imported at all? (`search_graph`/`search_code`); vulnerable function actually called? (`trace_path`).
|
|
64
|
+
- **os-package** (container scan) — judge **two separate axes**: *reachability* (is the vulnerable code called by untrusted input? grep the consumer, not the C package name) and *removability* (how installed — explicit/transitive/base-image/builder-only — and what breaks). **Unreachable ≠ removable.** Never recommend removing a package (or "slim the base image") without tracing the install path and naming the post-change test; many base-OS/transitive libs aren't removable. No-fix / `will_not_fix` → accept or base bump, never an "upgrade X" ticket. Full discipline + maps + anti-patterns in `../references/container-scan-triage.md`.
|
|
65
|
+
- **runtime** (interpreter/build tool, e.g. pip) — invoked at runtime or only at build time? Build-only, not in the running container → `not_affected` at runtime (check entrypoint/CMD).
|
|
66
|
+
- **container/iac** — not a CVE; triage on whether the misconfig is reachable in our deployment; route persistent ones to infra.
|
|
67
|
+
|
|
68
|
+
Also check **advisory preconditions** against real config — many CVEs are conditional (a setting, ASGI-vs-WSGI, a middleware). Precondition met → raises urgency; absent → clean `not_affected`. Record reachability provenance (`graph-verified`/`grep-asserted`/`image-layer`/`unknown`).
|
|
69
|
+
|
|
70
|
+
**Resolve unknowns in the moment — don't default to `under_investigation`.** When reachability isn't settled by grep: trace deeper (`trace_path` from routes to the vulnerable binding), then **ask the human the one decisive question** (e.g. "does any endpoint parse user-supplied XML?") — a single yes/no usually collapses several findings to `not_affected` or `affected` on the spot, sparing both a register entry and a follow-up task. Reserve `under_investigation` for questions nobody in the session can answer (needs a runtime check / a teammate / an external dependency); time-box those and name what must be checked.
|
|
71
|
+
|
|
72
|
+
### 5. Exposure & controls — read, don't invent
|
|
73
|
+
|
|
74
|
+
Per reference § Exposure & Controls: `.grimoire/docs/context.yml` (internet-facing vs internal vs lambda/batch, infra, services) and MADR decisions (`Security (CIA)` rows, WAF/network-isolation/auth/tenancy decisions). A documented control that breaks the attack path is a legitimate damper / VEX `inline_mitigations_already_exist`. **Do not create a controls config file** — controls live in MADR + context.yml. A verdict-changing control that's recorded nowhere → log under "Control gaps", don't credit it silently.
|
|
75
|
+
|
|
76
|
+
### 6. Assign VEX verdict + urgency
|
|
77
|
+
|
|
78
|
+
Apply the decision tree (reference § VEX + § Urgency): `fixed` (Step 2) → `not_affected` (reachability/precondition) → `affected` with **hotfix-now** / **next-release** / **accept** (with expiry) → `under_investigation` (time-boxed). Fail safe on unknowns (KEV + public + unknown reachability → hotfix-now); don't manufacture emergencies (no KEV + low EPSS + unknown → under_investigation + next-release).
|
|
79
|
+
|
|
80
|
+
### 7. Contrarian pass — calibrate before you escalate
|
|
81
|
+
|
|
82
|
+
Run the **Contrarian calibration pass** (`../references/review-personas.md` §4.8) over every `hotfix-now` and `affected` verdict: steel-man "we are not affected", name the assumption, run the inversion test (does a rushed hotfix / base-image swap ship *new* risk?), check severity clears all three bars (reachable + exploitable-as-deployed + real blast radius). Emit `[hotfix upheld]` / `[hotfix → next-release]` / `[finding dropped]` per escalation with one line of evidence. Summary counts are **post-Contrarian**. Calibration, not veto.
|
|
83
|
+
|
|
84
|
+
### 8. Write the triage record
|
|
85
|
+
|
|
86
|
+
Write `.grimoire/security/vulns/<run-date>/triage.md` in the reference format: frontmatter totals, then sections — Hotfix now / Next release / Risk-accepted / **Already fixed** (the stale-scan audit trail) / Not affected / Under investigation / Control gaps. Cache the KEV snapshot and EPSS responses alongside for reproducibility.
|
|
87
|
+
|
|
88
|
+
### 9. Report and hand off
|
|
89
|
+
|
|
90
|
+
Headline: how many findings, how many already **fixed** (stale scan), how many **not_affected** (noise) with the dominant reason, how many real (`affected`), how many **hotfix-now** — and *why* the hotfixes are hotfixes (one line each).
|
|
91
|
+
- **Any hotfix-now** → flag immediately, notify the security owner, recommend expedited fix.
|
|
92
|
+
- **affected (any urgency)** → "Run `grimoire-vuln-remediate` to file these into the bug tracker."
|
|
93
|
+
- **Control gaps** → "Assumed but unrecorded — `grimoire-draft` to capture them."
|
|
94
|
+
|
|
95
|
+
## Important
|
|
96
|
+
- **Reconcile before you triage.** A scan is a snapshot; the tree moves. Confirm each finding still exists in the current lockfile/image before spending any effort on it — it's the single highest-leverage step and stops you filing dead tickets.
|
|
97
|
+
- **Scanner-agnostic by design.** Normalize any tool into the canonical model, then triage that. The verdict logic must never depend on npm's or pip's or Trivy's field names. New tool? Add an adapter, not a new triage path.
|
|
98
|
+
- **CVSS ranks the world; we triage our deployment.** A "critical" in a dev-only, unreachable, or base-OS-cruft component is noise; a "medium" KEV hit on a public endpoint is a hotfix.
|
|
99
|
+
- **Reachability is type-aware.** App import ≠ OS package ≠ build-time tool ≠ IaC misconfig. Judge each on its own terms; a flagged base-image lib the app never calls is not a prod emergency.
|
|
100
|
+
- **Check the precondition.** Conditional CVEs are common — read the actual setting. Met → escalate; absent → `not_affected`.
|
|
101
|
+
- **`not_affected` requires a justification code.** The code is what makes the dismissal defensible to an auditor.
|
|
102
|
+
- **Controls must be recorded to count.** Flag undocumented ones, don't assume them.
|
|
103
|
+
- **Fail safe, don't fearmonger.** Escalate on KEV + public + unknown reachability; don't turn a low-EPSS, non-KEV, internal-only finding into a fire drill.
|
|
104
|
+
- **The Contrarian is the noise filter, not a silencer.**
|
|
105
|
+
- **`accept` carries an expiry.** Re-triaged when the fix ships; never silently permanent.
|
|
106
|
+
- **This skill does not fix or file.** It classifies. Code changes, ticket filing, image rebuilds are downstream (`grimoire-vuln-remediate`, `grimoire-bug`/`grimoire-draft` for non-trivial fixes).
|
|
107
|
+
|
|
108
|
+
## Done
|
|
109
|
+
When `.grimoire/security/vulns/<run-date>/triage.md` exists with every finding normalized, reconciled, and assigned a VEX verdict (and for `affected`, an urgency), the Contrarian pass applied to escalations, and the headline reported, triage is complete. Hand off to `grimoire-vuln-remediate` to file the dev work.
|