@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.
Files changed (81) hide show
  1. package/README.md +79 -58
  2. package/dist/cli/index.js +5 -7
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/check.d.ts.map +1 -1
  5. package/dist/core/check.js +54 -11
  6. package/dist/core/check.js.map +1 -1
  7. package/dist/core/doc-style.d.ts.map +1 -1
  8. package/dist/core/doc-style.js +76 -0
  9. package/dist/core/doc-style.js.map +1 -1
  10. package/dist/core/docs.js +96 -70
  11. package/dist/core/docs.js.map +1 -1
  12. package/dist/core/health.d.ts +6 -0
  13. package/dist/core/health.d.ts.map +1 -1
  14. package/dist/core/health.js +78 -19
  15. package/dist/core/health.js.map +1 -1
  16. package/dist/core/hooks.js +11 -5
  17. package/dist/core/hooks.js.map +1 -1
  18. package/dist/core/risk-register.d.ts +17 -0
  19. package/dist/core/risk-register.d.ts.map +1 -0
  20. package/dist/core/risk-register.js +73 -0
  21. package/dist/core/risk-register.js.map +1 -0
  22. package/dist/core/shared-setup.d.ts.map +1 -1
  23. package/dist/core/shared-setup.js +5 -4
  24. package/dist/core/shared-setup.js.map +1 -1
  25. package/dist/core/trace.d.ts.map +1 -1
  26. package/dist/core/trace.js +37 -35
  27. package/dist/core/trace.js.map +1 -1
  28. package/dist/index.d.ts +0 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +0 -3
  31. package/dist/index.js.map +1 -1
  32. package/package.json +1 -1
  33. package/skills/grimoire-apply/SKILL.md +35 -39
  34. package/skills/grimoire-commit/SKILL.md +1 -1
  35. package/skills/grimoire-design/SKILL.md +3 -3
  36. package/skills/grimoire-discover/SKILL.md +77 -110
  37. package/skills/grimoire-draft/SKILL.md +51 -18
  38. package/skills/grimoire-plan/SKILL.md +62 -32
  39. package/skills/grimoire-pr/SKILL.md +7 -8
  40. package/skills/grimoire-pr-review/SKILL.md +1 -1
  41. package/skills/grimoire-refactor/SKILL.md +2 -2
  42. package/skills/grimoire-review/SKILL.md +12 -0
  43. package/skills/grimoire-verify/SKILL.md +7 -7
  44. package/skills/grimoire-vuln-remediate/SKILL.md +107 -0
  45. package/skills/grimoire-vuln-triage/SKILL.md +109 -0
  46. package/skills/references/code-quality.md +41 -9
  47. package/skills/references/container-scan-triage.md +102 -0
  48. package/skills/references/dependency-vuln-triage.md +236 -0
  49. package/skills/references/principles.md +82 -0
  50. package/skills/references/refactor-scan-categories.md +2 -2
  51. package/skills/references/review-personas.md +4 -3
  52. package/skills/references/testing-contracts.md +1 -1
  53. package/templates/accepted-risks.yml +47 -0
  54. package/templates/constraints.md +25 -0
  55. package/dist/commands/archive.d.ts +0 -3
  56. package/dist/commands/archive.d.ts.map +0 -1
  57. package/dist/commands/archive.js +0 -22
  58. package/dist/commands/archive.js.map +0 -1
  59. package/dist/commands/log.d.ts +0 -3
  60. package/dist/commands/log.d.ts.map +0 -1
  61. package/dist/commands/log.js +0 -15
  62. package/dist/commands/log.js.map +0 -1
  63. package/dist/commands/map.d.ts +0 -3
  64. package/dist/commands/map.d.ts.map +0 -1
  65. package/dist/commands/map.js +0 -16
  66. package/dist/commands/map.js.map +0 -1
  67. package/dist/core/archive.d.ts +0 -9
  68. package/dist/core/archive.d.ts.map +0 -1
  69. package/dist/core/archive.js +0 -81
  70. package/dist/core/archive.js.map +0 -1
  71. package/dist/core/log.d.ts +0 -8
  72. package/dist/core/log.d.ts.map +0 -1
  73. package/dist/core/log.js +0 -140
  74. package/dist/core/log.js.map +0 -1
  75. package/dist/core/map.d.ts +0 -22
  76. package/dist/core/map.d.ts.map +0 -1
  77. package/dist/core/map.js +0 -365
  78. package/dist/core/map.js.map +0 -1
  79. package/templates/dupignore +0 -93
  80. package/templates/mapignore +0 -58
  81. 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` reusable-code tables, `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*.
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 proposed `.feature` files
63
- - All proposed decision records, **including Cost of Ownership sections**
64
- - The current baseline (`features/`, `.grimoire/decisions/`) for context on what's changing
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: key files with responsibilities, reusable utilities (exact function names, file paths, line numbers), naming conventions, structural patterns, and "Where New Code Goes" guidance. This is the information that lets you write tasks with exact file paths without reading every source file.
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
- - **`.grimoire/docs/.snapshot.json`** `duplicates` section if present existing clones in areas you're touching, so tasks consolidate rather than add more.
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 — reusable utilities and file paths may be wrong") and proceed. Note inferred paths with `<!-- inferred: area doc may be stale -->`.
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 `grimoire map` + `/grimoire:discover` first — planning without area docs produces worse tasks)
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 scenario must produce both production code AND tests.** Tasks are structured as pairs: step definitions first, then production code.
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
- - Approved `.feature` scenarios in this change
140
- - Approved ADRs in this change (and their Confirmation sections)
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
- - If `.grimoire/docs/` has area docs, check the Reusable Code tables for utilities that apply to this change
263
- - If the snapshot has duplicate data, check whether the area you're touching already has clones — tasks should consolidate rather than add more
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
- - .grimoire/changes/<change-id>/features/<capability>/<name>.feature
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 `<file>`
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. Shared Steps
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
- - .grimoire/changes/<change-id>/features/<all relevant .feature files>
334
+ - features/<all relevant .feature files>
307
335
  -->
308
- - [ ] 2.1 Add to `<exact path>`:
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
- ## 3. Architecture
340
+ ## 4. Architecture
313
341
  <!-- context:
314
- - .grimoire/changes/<change-id>/decisions/<nnnn-title>.md
342
+ - .grimoire/decisions/<nnnn-title>.md
315
343
  - src/<files affected by decision>
316
344
  -->
317
- - [ ] 3.1 In `<exact path>`: <specific change from ADR>
318
- - [ ] 3.2 Add test in `<exact path>`: <ADR confirmation check — what to assert>
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
- ## 4. Verification
321
- - [ ] 4.1 Run `<exact test command>` — all new scenarios green
322
- - [ ] 4.2 Run `<exact test command>` — no regressions
323
- - [ ] 4.3 Run `<exact test command>` — full project suite
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 step definition task describes what to assert (no "write a test")
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 → step defs → production code → verification
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 must reflect the archived state (decisions promoted, manifest archived, change directory removed).
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, manifest is in `.grimoire/archive/`
26
- - All decisions promoted to `.grimoire/decisions/`
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
- - Update manifest's status to `complete` if not already
121
- - Add the PR URL to the manifest as a comment or field
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 `grimoire archive <change-id>` to complete the lifecycle.
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: first check `.grimoire/changes/<change-id>/` (in-progress), then `.grimoire/archive/*<change-id>*/` (archived). Try the PR's head branch checked out locally if needed.
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: `grimoire map` + `/grimoire:discover` already run (area docs help contextualize findings)
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 `.snapshot.json` or `config.tools.duplicates`; plus semantic duplicate detection via `search_graph(semantic_query=[...])` to find re-implementations under different names (requires `codebase-memory-mcp`)
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 archiving the change.
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 archive.
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 archiving
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 archiving the change
240
- - **Critical issues** → must fix before archiving
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 archive <change-id>` or `grimoire-pr`
255
- - **Critical issues** → must fix before archiving
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.