@kiwidata/grimoire 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/AGENTS.md +56 -4
  2. package/README.md +28 -1
  3. package/dist/cli/index.js +2 -0
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/check.js +1 -1
  6. package/dist/commands/check.js.map +1 -1
  7. package/dist/commands/configure.d.ts +3 -0
  8. package/dist/commands/configure.d.ts.map +1 -0
  9. package/dist/commands/configure.js +19 -0
  10. package/dist/commands/configure.js.map +1 -0
  11. package/dist/commands/init.d.ts.map +1 -1
  12. package/dist/commands/init.js +2 -0
  13. package/dist/commands/init.js.map +1 -1
  14. package/dist/commands/map.d.ts.map +1 -1
  15. package/dist/commands/map.js +10 -11
  16. package/dist/commands/map.js.map +1 -1
  17. package/dist/core/archive.d.ts.map +1 -1
  18. package/dist/core/archive.js +32 -43
  19. package/dist/core/archive.js.map +1 -1
  20. package/dist/core/check.d.ts.map +1 -1
  21. package/dist/core/check.js +115 -104
  22. package/dist/core/check.js.map +1 -1
  23. package/dist/core/ci.d.ts.map +1 -1
  24. package/dist/core/ci.js +50 -69
  25. package/dist/core/ci.js.map +1 -1
  26. package/dist/core/configure.d.ts +14 -0
  27. package/dist/core/configure.d.ts.map +1 -0
  28. package/dist/core/configure.js +434 -0
  29. package/dist/core/configure.js.map +1 -0
  30. package/dist/core/detect.d.ts.map +1 -1
  31. package/dist/core/detect.js +153 -26
  32. package/dist/core/detect.js.map +1 -1
  33. package/dist/core/diff.d.ts.map +1 -1
  34. package/dist/core/diff.js +62 -93
  35. package/dist/core/diff.js.map +1 -1
  36. package/dist/core/doc-style.d.ts +0 -4
  37. package/dist/core/doc-style.d.ts.map +1 -1
  38. package/dist/core/doc-style.js +28 -23
  39. package/dist/core/doc-style.js.map +1 -1
  40. package/dist/core/docs.js +106 -100
  41. package/dist/core/docs.js.map +1 -1
  42. package/dist/core/health.js +55 -77
  43. package/dist/core/health.js.map +1 -1
  44. package/dist/core/hooks.d.ts +0 -3
  45. package/dist/core/hooks.d.ts.map +1 -1
  46. package/dist/core/hooks.js +0 -11
  47. package/dist/core/hooks.js.map +1 -1
  48. package/dist/core/init.d.ts +2 -0
  49. package/dist/core/init.d.ts.map +1 -1
  50. package/dist/core/init.js +230 -406
  51. package/dist/core/init.js.map +1 -1
  52. package/dist/core/list.d.ts.map +1 -1
  53. package/dist/core/list.js +55 -65
  54. package/dist/core/list.js.map +1 -1
  55. package/dist/core/log.d.ts.map +1 -1
  56. package/dist/core/log.js +23 -33
  57. package/dist/core/log.js.map +1 -1
  58. package/dist/core/map.d.ts +15 -2
  59. package/dist/core/map.d.ts.map +1 -1
  60. package/dist/core/map.js +257 -194
  61. package/dist/core/map.js.map +1 -1
  62. package/dist/core/shared-setup.d.ts +0 -40
  63. package/dist/core/shared-setup.d.ts.map +1 -1
  64. package/dist/core/shared-setup.js +87 -52
  65. package/dist/core/shared-setup.js.map +1 -1
  66. package/dist/core/status.d.ts.map +1 -1
  67. package/dist/core/status.js +42 -52
  68. package/dist/core/status.js.map +1 -1
  69. package/dist/core/test-quality.d.ts +0 -8
  70. package/dist/core/test-quality.d.ts.map +1 -1
  71. package/dist/core/test-quality.js +24 -30
  72. package/dist/core/test-quality.js.map +1 -1
  73. package/dist/core/trace.d.ts.map +1 -1
  74. package/dist/core/trace.js +31 -41
  75. package/dist/core/trace.js.map +1 -1
  76. package/dist/core/update.d.ts.map +1 -1
  77. package/dist/core/update.js +61 -11
  78. package/dist/core/update.js.map +1 -1
  79. package/dist/core/validate.d.ts +1 -4
  80. package/dist/core/validate.d.ts.map +1 -1
  81. package/dist/core/validate.js +126 -148
  82. package/dist/core/validate.js.map +1 -1
  83. package/dist/utils/config.d.ts +15 -5
  84. package/dist/utils/config.d.ts.map +1 -1
  85. package/dist/utils/config.js +63 -42
  86. package/dist/utils/config.js.map +1 -1
  87. package/dist/utils/fs.d.ts +0 -12
  88. package/dist/utils/fs.d.ts.map +1 -1
  89. package/dist/utils/fs.js +0 -12
  90. package/dist/utils/fs.js.map +1 -1
  91. package/dist/utils/paths.d.ts +0 -6
  92. package/dist/utils/paths.d.ts.map +1 -1
  93. package/dist/utils/paths.js +0 -6
  94. package/dist/utils/paths.js.map +1 -1
  95. package/dist/utils/spawn.d.ts +0 -3
  96. package/dist/utils/spawn.d.ts.map +1 -1
  97. package/dist/utils/spawn.js +0 -3
  98. package/dist/utils/spawn.js.map +1 -1
  99. package/package.json +1 -1
  100. package/skills/grimoire-apply/SKILL.md +84 -16
  101. package/skills/grimoire-audit/SKILL.md +21 -1
  102. package/skills/grimoire-bug/SKILL.md +48 -9
  103. package/skills/grimoire-commit/SKILL.md +2 -1
  104. package/skills/grimoire-design/SKILL.md +259 -0
  105. package/skills/grimoire-design-consult/SKILL.md +200 -0
  106. package/skills/grimoire-discover/SKILL.md +65 -2
  107. package/skills/grimoire-draft/SKILL.md +85 -2
  108. package/skills/grimoire-plan/SKILL.md +61 -18
  109. package/skills/grimoire-pr/SKILL.md +4 -6
  110. package/skills/grimoire-pr-review/SKILL.md +45 -114
  111. package/skills/grimoire-precommit-review/SKILL.md +205 -0
  112. package/skills/grimoire-refactor/SKILL.md +5 -5
  113. package/skills/grimoire-review/SKILL.md +74 -147
  114. package/skills/grimoire-verify/SKILL.md +33 -0
  115. package/skills/references/adversarial-personas.md +225 -0
  116. package/skills/references/brand-tokens-format.md +186 -0
  117. package/skills/references/code-quality.md +140 -0
  118. package/skills/references/design-heuristics.md +138 -0
  119. package/skills/references/design-input-formats.md +190 -0
  120. package/skills/references/pattern-guard.md +180 -0
  121. package/skills/references/refactor-scan-categories.md +152 -0
  122. package/skills/references/review-personas.md +405 -0
  123. package/skills/references/security-compliance.md +22 -1
  124. package/skills/references/visual-fidelity.md +206 -0
  125. package/templates/brand-tokens-example.json +13 -0
  126. package/templates/brand-voice-example.md +22 -0
  127. package/templates/design-tool-setup-stub.md +59 -0
@@ -28,6 +28,21 @@ Files that change frequently AND are hard to change. Highest-ROI refactoring tar
28
28
 
29
29
  **Severity:** high = 2x+ threshold, medium = 1-2x, low = marginally over
30
30
 
31
+ **Graph-powered LLM bloat checks** (requires `codebase-memory-mcp`; skip if not indexed):
32
+
33
+ These target patterns that static size checks miss — structurally valid code that adds indirection without value. Primary signal of LLM-generated over-engineering.
34
+
35
+ | Pattern | Query | Flag when |
36
+ |---|---|---|
37
+ | Single-subclass base class | `query_graph("MATCH (sub)-[:INHERITS]->(base:Class) WITH base, collect(sub) AS subs WHERE size(subs) = 1 RETURN base.qualified_name, base.file, subs[0].qualified_name AS only_subclass")` | Any result — a base with one child is premature abstraction |
38
+ | Single-caller wrapper | Step 1: `query_graph("MATCH (caller)-[:CALLS]->(fn) WITH fn, collect(caller) AS callers WHERE size(callers) = 1 RETURN fn.qualified_name, fn.file, callers[0].qualified_name AS only_caller")`. Step 2: for each result, `get_code_snippet(qualified_name)` and count body lines. | Wrapper with 1 caller and ≤7 body lines — inline candidate |
39
+ | Zero-caller export | `query_graph("MATCH (f:Function) WHERE f.exported = true AND NOT ()-[:CALLS]->(f) RETURN f.qualified_name, f.file")` — then filter out entry points manually: skip files named `index.ts`, `__init__.py`, `main.py`, `cli.py`, `app.py`, or in a `public/` directory | Exported, unreachable within repo, not an entry point — dead export |
40
+ | Single-implementation interface | `query_graph("MATCH (impl)-[:IMPLEMENTS]->(iface:Interface) WITH iface, collect(impl) AS impls WHERE size(impls) = 1 RETURN iface.qualified_name, iface.file, impls[0].qualified_name AS only_impl")` | Any result — interface with one implementor adds no polymorphism |
41
+
42
+ Note: the exact Cypher depends on the graph schema. If a query returns an error, adjust field names using `get_graph_schema()` to inspect available properties.
43
+
44
+ **Severity for graph findings:** high = single-implementation interface or zero-caller export, medium = single-subclass base or single-caller wrapper
45
+
31
46
  ## 2c. Data Structure Complexity
32
47
 
33
48
  | Signal | Meaning |
@@ -82,6 +97,22 @@ TODO/FIXME/HACK/XXX comments that have aged.
82
97
 
83
98
  **Severity:** high = >30 lines or >3 copies, medium = 10-30 lines or 2 copies, low = <10 lines
84
99
 
100
+ **Concept-based duplicate detection** (requires `codebase-memory-mcp`; supplements jscpd which only finds textual clones):
101
+
102
+ LLM-generated code frequently re-implements existing utilities under a different name. jscpd won't catch these — the code is structurally different even though it does the same thing.
103
+
104
+ **How to scan:**
105
+ 1. Find utility/helper functions: `search_graph(label="Function", name_pattern="(parse_|format_|validate_|convert_|build_|get_|find_|create_|check_|is_|has_)")`
106
+ 2. For each result, extract 2–3 concept words from the function name (e.g., `format_invoice_date` → `["format", "date", "invoice"]`)
107
+ 3. Run: `search_graph(semantic_query=["<concept1>", "<concept2>", "<concept3>"])` — if `semantic_query` is unsupported, fall back to `search_graph(name_pattern="(<concept1>|<concept2>)")`
108
+ 4. Compare: if the search returns a different function, read both with `get_code_snippet` and assess whether they do the same job
109
+
110
+ **Flag when:** two functions accept similar inputs, produce similar outputs, and operate on the same domain concept. Assessment is qualitative — the tool returns ranked results, not similarity scores.
111
+
112
+ **Focus on:** utility directories (`utils/`, `helpers/`, `lib/`, `common/`), validators, formatters, parsers. These are where re-implementations accumulate.
113
+
114
+ **Severity:** high = identical behavior under different names, medium = near-duplicate with minor variations that could be unified with a parameter, low = similar but distinct enough to keep
115
+
85
116
  ## 2h. Dead Code
86
117
 
87
118
  **How to scan:**
@@ -100,3 +131,124 @@ TODO/FIXME/HACK/XXX comments that have aged.
100
131
  - Check for over-mocked tests (testing mocks, not behavior)
101
132
 
102
133
  **Severity:** high = complex code (top quartile) with <30% coverage, medium = moderate complexity with <50%, low = simple code with low coverage
134
+
135
+ ## 2j. Pattern Divergence
136
+
137
+ Code that solves a problem in a way that contradicts how the codebase already solves the same class of problem. The primary AI slop signal — structurally valid code that ignores established conventions and accumulates architectural drift.
138
+
139
+ **Requires:** `codebase-memory-mcp` indexed. Skip this category if graph is not available.
140
+
141
+ **How to scan:**
142
+
143
+ **Step 1 — Identify peer groups**
144
+
145
+ A peer group is a set of nodes in the graph that share the same role. Use `search_graph` to find them:
146
+
147
+ | Peer group | Query |
148
+ |---|---|
149
+ | API/route handlers | `search_graph(label="Function", name_pattern="(handle|view|endpoint|route|controller)")` |
150
+ | Service methods | `search_graph(label="Function", name_pattern="(service|use_case|interactor)")` |
151
+ | Repository/data access | `search_graph(label="Function", name_pattern="(repo|repository|store|dao|query)")` |
152
+ | Test files | `search_graph(label="Module", name_pattern="(test_|_test|spec)")` |
153
+ | Error handlers | `search_graph(label="Function", name_pattern="(error|exception|fail|catch)")` |
154
+
155
+ Supplement with area docs if available — each area doc lists files by role.
156
+
157
+ **Step 2 — Extract modal pattern per peer group**
158
+
159
+ For each peer group with ≥3 members, sample 3-5 established members (oldest by `git log`, not recently changed):
160
+ - `get_code_snippet(qualified_name)` for each sample
161
+ - Identify the modal pattern across: error handling style, dependency access (injected vs imported), abstraction depth (business logic in handler vs delegated to service), naming convention, return type shape
162
+
163
+ This is the **baseline** — what the codebase already does.
164
+
165
+ **Step 3 — Compare recent code against baseline**
166
+
167
+ Scope: files changed in the last 60 days (`git log --since="60 days ago" --name-only --format=`). Cross-reference with the peer groups from step 1.
168
+
169
+ For each recently changed file that belongs to a peer group:
170
+ 1. `get_code_snippet` for the changed function/class
171
+ 2. Compare against the modal pattern from step 2
172
+ 3. Flag if it diverges on any of the four critical seams (see below)
173
+
174
+ **Step 4 — Flag divergences**
175
+
176
+ Only flag divergence on seams that matter architecturally. Cosmetic drift (whitespace, docstring style) is not a debt item.
177
+
178
+ | Seam | Divergence signal | Example |
179
+ |---|---|---|
180
+ | **Error handling** | Mix of exception-raise vs return-value-error in same layer | Most handlers raise `ValueError`; new one returns `{"error": ...}` |
181
+ | **Data access** | Bypass of established access layer | Most services call `repo.get()`; new one imports ORM model directly |
182
+ | **Abstraction depth** | Business logic at wrong layer | All handlers delegate; new handler contains domain logic inline |
183
+ | **Dependency wiring** | Injected vs hardcoded import for same dependency | All services receive `db` via constructor; new one calls `get_db()` directly |
184
+ | **Test structure** | Different test strategy in same area | All tests in area use factory fixtures; new tests use heavy mocks |
185
+
186
+ **Step 5 — Check for hallucinated or non-existent references**
187
+
188
+ Use `search_graph` to verify function calls in recently changed files:
189
+ - Extract all function calls in the diff using `search_code(pattern)` or `get_code_snippet`
190
+ - For each called function/method: `search_graph(name_pattern=<name>)` — does it exist?
191
+ - Missing = hallucinated API, deprecated method, or invented config option
192
+
193
+ Flag as `pattern_divergence` with detail: "Called `foo.bar()` — no matching node in graph."
194
+
195
+ **Severity:**
196
+ - high = divergence at a core architectural seam (data access, error handling, auth) OR hallucinated reference
197
+ - medium = wrong abstraction layer or dependency wiring inconsistency
198
+ - low = test strategy divergence or naming/convention drift
199
+
200
+ **Suggested action (per seam):**
201
+ - Error handling: align to codebase's exception or result pattern
202
+ - Data access: route through established repository/service layer
203
+ - Abstraction: extract domain logic to service, slim the handler
204
+ - Dependency: adopt constructor injection or established DI pattern
205
+ - Hallucinated ref: replace with actual existing function (use `search_graph` to find it)
206
+
207
+ ## 2k. Comment Noise
208
+
209
+ Comments that restate the code, reference stale context, or pad function bodies without conveying non-obvious intent. A secondary LLM bloat signal — LLMs are trained to produce documentation and carry that habit into code generation.
210
+
211
+ **How to scan:**
212
+
213
+ **Step 1 — High comment density files**
214
+ ```bash
215
+ grep -rcE "^\s*#|^\s*//" --include="*.py" --include="*.ts" --include="*.js" <src_dirs> | \
216
+ grep -v ":0$" | sort -t: -k2 -rn | head -20
217
+ ```
218
+ Flag files with >30 comment lines. Raw count, not ratio — a 30-comment file is a candidate regardless of size.
219
+
220
+ **Step 2 — Restatement pattern grep**
221
+ ```bash
222
+ grep -rni \
223
+ -e "# loop over" -e "# iterate over" -e "# return the" -e "# return result" \
224
+ -e "# loop through" -e "# now call" -e "# call the" -e "# increment" -e "# decrement" \
225
+ -e "// loop over" -e "// iterate over" -e "// return the" -e "// return result" \
226
+ -e "// loop through" -e "// now call" -e "// call the" -e "// increment" -e "// decrement" \
227
+ --include="*.py" --include="*.ts" --include="*.js" <src_dirs>
228
+ ```
229
+ Treat results as candidates — quick human scan to confirm before deleting.
230
+
231
+ **Step 3 — Task/PR reference comments**
232
+ ```bash
233
+ grep -rn \
234
+ -e "# added for" -e "# used by" -e "# see issue" -e "# handles the case" -e "# added in" \
235
+ -e "// added for" -e "// used by" -e "// see issue" -e "// handles the case" -e "// added in" \
236
+ --include="*.py" --include="*.ts" --include="*.js" <src_dirs>
237
+ ```
238
+ These belong in commit messages, not source. Treat results as candidates — review before flagging, as patterns like `# see issue` can appear in legitimate context.
239
+
240
+ **Step 4 — Docstrings on private/internal functions**
241
+ ```bash
242
+ # Python: single-underscore private functions (excludes dunders)
243
+ grep -rn "def _[^_]" --include="*.py" <src_dirs>
244
+ # TS/JS: JSDoc blocks
245
+ grep -rn "/\*\*" --include="*.ts" --include="*.js" <src_dirs>
246
+ ```
247
+ Manual triage: open each hit and check whether a multi-line docstring follows. Python `def _name` functions and TS/JS non-exported functions don't need docstrings. Delete multi-line blocks; a single-line doc is acceptable if `comment_style` requires it.
248
+
249
+ **Severity:**
250
+ - high = >20 restatement comments in a single file, or task/PR references in core business logic
251
+ - medium = 5–20 restatement comments, or any task/PR references found
252
+ - low = multi-line docstrings on private functions
253
+
254
+ **Suggested action:** Delete restatement comments. Move task/PR references to commit history. Trim private function docstrings to one line or remove entirely.
@@ -0,0 +1,405 @@
1
+ # Review Personas Reference
2
+
3
+ Shared persona evaluation engine used by `grimoire-review` (design review), `grimoire-pr-review` (remote PR diff), and `grimoire-precommit-review` (staged local diff).
4
+
5
+ The calling skill is responsible for:
6
+ - Resolving the **input** (specs only, PR diff, or staged diff)
7
+ - Loading project context (`.grimoire/config.yaml`, `.grimoire/docs/`)
8
+ - Building the **Project Briefing** (below) and injecting it into every persona
9
+ - Picking which personas run based on **complexity gating**
10
+ - Compiling persona output into the final report
11
+
12
+ This reference defines: project briefing, materiality gate, complexity gating, and the persona prompts themselves.
13
+
14
+ ---
15
+
16
+ ## 1. Project Briefing
17
+
18
+ Build once, inject as preface to every persona. Findings that don't threaten anything in the briefing are dropped (materiality gate, applied per-persona below).
19
+
20
+ ### Sources
21
+
22
+ - `README.md` — first 50 lines or up to first H2 (product framing, audience, stage signals)
23
+ - `.grimoire/config.yaml` — `project.compliance`, `project.language`, `project.comment_style`, `project.surface`, `dep_audit`
24
+ - `.grimoire/docs/context.yml` — deployment env, related services (if exists)
25
+ - `.grimoire/docs/components.md` — component-library inventory (if exists)
26
+ - `.grimoire/brand/tokens.json` and `.grimoire/brand/voice.md` — brand axis (if exist; see `./brand-tokens-format.md`)
27
+ - `.grimoire/changes/<id>/consult.md` — pre-design consult assumptions + givens (if exists)
28
+ - `.grimoire/changes/<id>/designs/problem.md` — design problem statement (if exists)
29
+ - Tag histogram across `.grimoire/changes/**/*.feature` + `.grimoire/archive/**/*.feature`
30
+ - All `.grimoire/decisions/*.md` with `status: accepted` — extract ID, title, top Decision Driver
31
+ - Linked manifest's `Why` and `Non-goals` (if a Change trailer / active manifest exists); else PR body or commit messages
32
+
33
+ ### Feature inventory
34
+
35
+ - Glob `.grimoire/changes/**/*.feature` + `.grimoire/archive/**/*.feature`
36
+ - Parse: `Feature:` line, first description line, `@tags`
37
+ - Bucket by path prefix (area)
38
+ - If total >80, emit area-level summary only (count + capability one-liner)
39
+
40
+ ### README fallback
41
+
42
+ If missing or <200 chars: design review prompts the user once; PR/pre-commit review notes `Product framing: unknown` and proceeds.
43
+
44
+ ### Briefing block
45
+
46
+ ```markdown
47
+ ## Project Briefing
48
+
49
+ **Product:** <one-line from README>
50
+ **Stage:** <prototype | internal | customer-facing | regulated — inferred from compliance config + README>
51
+ **Surface:** <tui | web | mobile | api | mixed | unknown — from `project.surface`; drives adversarial-persona filtering>
52
+ **Users:** <who, scale, trust level>
53
+ **Data sensitivity:** <none | pii | financial | phi — derived from tag histogram + compliance>
54
+ **Threat surface:** <only tags with count >0, e.g. auth=4, pii=3, payment=2>
55
+ **Brand:** <captured | none — one-line summary of `.grimoire/brand/tokens.json` presence + key tokens>
56
+ **Component library:** <name + path to `.grimoire/docs/components.md` | none documented>
57
+ **Problem statement:** <one-line from `designs/problem.md` | n/a>
58
+
59
+ **Active constraints (accepted decisions):**
60
+ - ADR-XXXX — <title>
61
+ - ...
62
+
63
+ **Feature inventory:**
64
+ <area>/ (N features)
65
+ - <Feature title> [@tags] — one-line capability
66
+ ...
67
+ Total: <N> features across <M> areas.
68
+
69
+ **Linked change non-goals (if any):**
70
+ - <bullets, or "n/a">
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 2. Materiality Gate
76
+
77
+ Apply to every persona. Every finding must cite either:
78
+ - A briefing axis it threatens (stage, data sensitivity, active constraint, threat-surface tag, surface), OR
79
+ - A concrete feature-inventory gap, OR
80
+ - A **brand axis** mismatch (e.g., design uses `#FF0000` not in `tokens.json`), OR
81
+ - A **component-inventory gap** (e.g., design introduces a new Button despite an existing variant in `components.md`), OR
82
+ - A **problem-statement mismatch** (e.g., scenario doesn't address the user problem articulated in `designs/problem.md`)
83
+
84
+ Rules:
85
+ - If the inventory shows the concern is already covered elsewhere, drop the finding or downgrade to a cross-feature integration note.
86
+ - Findings with no briefing anchor are dropped. Don't manufacture findings to hit a quota.
87
+ - Treat accepted ADRs as constraints, not suggestions. If a persona thinks one is wrong, name the ADR by ID and propose superseding it.
88
+ - Before flagging a missing capability (rate limit, audit log, etc.), check the feature inventory for a sibling feature that already covers it.
89
+
90
+ ## 2a. Steel-Man Before Flag
91
+
92
+ Before submitting any finding, write (mentally or in the finding itself) the strongest version of why the design / code is the way it is. If the steel-man holds, drop the finding. A finding that survives must explain why the steel-man is wrong given *this* project's briefing.
93
+
94
+ For each candidate finding, the persona must be able to complete:
95
+
96
+ - **Steel-man:** "The author likely chose this because <strongest plausible reason tied to briefing / constraints / convention>."
97
+ - **Why it still fails:** "Despite that, <concrete harm path tied to a briefing axis>."
98
+
99
+ If the persona can't complete both lines with substance, the finding is dropped. Vague harm paths ("could be exploited", "might fail", "is fragile") do not count — name the trigger and the consequence.
100
+
101
+ ## 2b. Severity Calibration
102
+
103
+ The default is **suggestion**. A finding is a **blocker** only when *all three* hold:
104
+
105
+ 1. **Concrete harm path** — name the trigger (the input, sequence, or state) and the consequence (data loss, auth bypass, regulator violation, broken acceptance criteria, regression).
106
+ 2. **Briefing-anchored** — the consequence threatens a briefing axis (stage, data sensitivity, threat-surface tag, active ADR, manifest non-goal).
107
+ 3. **Not already mitigated** — neighbor code, framework default, or sibling feature does not already handle it.
108
+
109
+ If any of the three is missing → suggestion, not blocker. If all three are weak → drop.
110
+
111
+ **Zero findings is a valid outcome.** Personas are not graded on volume. A persona that submits "no material findings under the briefing" is doing its job. Do not invent a blocker to hit a quota — reviewers who exaggerate severity get tuned out and the real blockers get lost.
112
+
113
+ Severity inflation patterns to avoid:
114
+ - "Could lead to" / "in theory" / "if an attacker" without the path → drop or downgrade.
115
+ - "Best practice says X" without a project anchor → suggestion at most, often drop.
116
+ - "Untested edge case" when no scenario in the briefing covers it → not a blocker.
117
+ - "Missing observability" on a level 1-2 change → suggestion, never blocker.
118
+
119
+ ---
120
+
121
+ ## 3. Complexity Gating
122
+
123
+ Read `complexity` from the linked manifest if available; otherwise infer from the change.
124
+
125
+ ### Design review (`grimoire-review`)
126
+
127
+ | Complexity | Depth |
128
+ |---|---|
129
+ | 1 (Trivial) | Skip review entirely — proceed to apply |
130
+ | 2 (Simple) | Senior Engineer only; skip others unless touching security or data |
131
+ | 3 (Moderate) | All relevant personas (skip Data if no data changes, skip QA if no user-facing change) |
132
+ | 4 (Complex) | All personas mandatory |
133
+
134
+ Note: When `project.surface` is set, adversarial personas auto-filter per the activation matrix in `./adversarial-personas.md`. Surface-irrelevant personas (e.g., touch-target on a TUI surface) are skipped by default; the user can still force-engage them via `--personas=...`.
135
+
136
+ ### Diff review (PR + pre-commit)
137
+
138
+ | Signal | Depth |
139
+ |---|---|
140
+ | Docs only, ≤50 lines | Senior engineer + Code Style skim |
141
+ | Linked complexity 1-2, diff <200 lines, no security tags | Senior engineer + Security quick scan + Code Style |
142
+ | Linked complexity 3, OR diff touches auth/data/API | All relevant personas (skip Data if no schema, skip QA if no user-facing) |
143
+ | Linked complexity 4, OR diff >500 lines, OR multi-domain | All personas mandatory |
144
+
145
+ User can override: "full review", "just security", "just code style", etc.
146
+
147
+ ### Contrarian pass
148
+
149
+ Runs after the chosen personas submit findings, whenever at least one blocker exists. Skipped only if every persona returned zero findings. Not configurable by complexity — the inflation problem hits all levels.
150
+
151
+ ---
152
+
153
+ ## 4. Personas
154
+
155
+ Each persona below names what it evaluates. The calling skill points the persona at the right input (specs for design review, diff hunks for PR / pre-commit). Apply the materiality gate to every finding. Flag as **blocker** (must fix before merge / commit / coding) or **suggestion**.
156
+
157
+ ### 4.1 Product Manager
158
+
159
+ Skip if the change is purely internal (no user-facing behavior).
160
+
161
+ Evaluate:
162
+ - **Outcome**: Manifest's Why states the problem and how success is measured? Mechanism vs outcome ("add an endpoint" vs "users can reset passwords")?
163
+ - **Coverage**: Do feature scenarios cover all user-facing behaviors? Missing edge cases, error states, alternate flows?
164
+ - **Diff vs scenarios** *(PR/pre-commit only)*: If a feature file exists in the change, does the diff implement every scenario? Any scenario with no matching code change?
165
+ - **Non-goals**: Does the design / diff touch anything the manifest's Non-goals excludes? Scope creep into non-goals = **blocker**.
166
+ - **Acceptance**: Could a PM validate this meets the feature's acceptance criteria from the artifact in front of them?
167
+ - **Clarity**: Are descriptions clear enough for a non-technical stakeholder? Does the PR/commit body make user-visible outcome clear?
168
+
169
+ ### 4.2 Senior Engineer
170
+
171
+ Treat accepted decisions as constraints — cite ADR ID before suggesting an override.
172
+
173
+ Evaluate:
174
+ - **Build vs Buy** *(design only)*: Was prior art research thorough? If a well-maintained library exists that the manifest doesn't mention, **blocker**.
175
+ - **Simplicity**: Simplest design that solves the problem? Unnecessary abstraction, indirection, premature generalization, config-driven where direct call would do?
176
+ - **Architecture**: Decisions sensible for this codebase? Will this paint us into a corner?
177
+ - **Conventions** *(PR/pre-commit)*: Does new code match file layout, naming, and patterns already in the touched areas? Check `.grimoire/docs/<area>.md` if present.
178
+ - **Reuse**: Existing utilities/functions that were re-implemented? `grep` for similar names; check area docs' reusable-code lists.
179
+ - **Dead code** *(PR/pre-commit)*: Functions added but not called, imports unused, commented-out code, stubs with no implementation.
180
+ - **Scope creep** *(PR/pre-commit)*: Files changed outside the scope implied by the change-id or manifest. Formatting-only changes to unrelated files = noise.
181
+ - **Error handling**: Errors handled at boundaries? Internal code shouldn't be littered with defensive checks; external inputs must be validated.
182
+ - **Tests**: New behaviors have tests? Tests make real assertions (not just `assert true` / mock everything)? Check `./testing-contracts.md` if framework matches.
183
+ - **Contract compatibility**: If `data.yml` / `schema.yml` exists, does the design / diff change request/response shape for a documented API? Contract change without updated contract test = **blocker**.
184
+ - **Dependencies**: New packages not mentioned in tasks? Version bumps not noted?
185
+ - **Task alignment** *(PR/pre-commit, if `tasks.md` exists)*: Does the diff complete the tasks as written? Any task marked done but no corresponding code?
186
+ - **Surface area**: New public APIs/exports/interfaces beyond what's needed? Fewer public functions with fewer parameters is better.
187
+ - **Quality attributes** *(design only)*: Decision records' Quality Attributes targets measurable and realistic? Blank targets on perf-sensitive change = **blocker**.
188
+
189
+ ### 4.3 Security Engineer
190
+
191
+ Calibrate severity to stage and data sensitivity from the briefing. Don't flag generic OWASP items that don't threaten the briefing's threat surface. Apply `./security-compliance.md`.
192
+
193
+ #### STRIDE
194
+
195
+ For every new entry point, data flow, or trust boundary:
196
+
197
+ | Threat | Question |
198
+ |---|---|
199
+ | **S**poofing | Auth check at every new route/handler? |
200
+ | **T**ampering | Input/message integrity validated? CSRF on state-changing requests? |
201
+ | **R**epudiation | Security-relevant actions logged? |
202
+ | **I**nfo disclosure | Errors, logs, stack traces leaking PII/tokens/secrets? |
203
+ | **D**oS | Unbounded loops, unlimited file uploads, expensive queries on user input, no rate limit? |
204
+ | **E**oP | Role/permission checks at the right layer? Bypass via missing middleware? |
205
+
206
+ Skip categories that don't apply.
207
+
208
+ #### Code-level scan *(PR/pre-commit only)*
209
+
210
+ - **Secrets**: Grep diff for hardcoded keys, tokens, passwords, cloud credentials, JWT secrets. Any hit = **blocker**.
211
+ - **Injection**: Raw SQL with string concatenation, shell-exec with user input, `eval`/`exec`, unsafe deserialization. Tag OWASP + CWE.
212
+ - **Input validation**: New endpoints without schema validation, file uploads without size/type limits, path params used directly in filesystem calls.
213
+ - **Auth**: New routes/handlers missing auth decorators / middleware. Compare against neighbors in same file.
214
+ - **Dependencies**: New packages — pinned to exact version (no `^`/`~`/`>=`/`*`), lockfile updated and committed with integrity hashes, name is real (typosquat risk), `dep_audit` output clean if committed. Flag packages with zero downloads, recent ownership transfer (~90 days), suspicious new maintainers, or post-install scripts. Unpinned dep or missing lockfile entry on a new package = **blocker** (see `./security-compliance.md` § Supply Chain Defense).
215
+ - **PII**: New logging that could emit PII; new storage of personal data without encryption.
216
+ - **Cross-service auth**: If `context.yml` lists related services, are service-to-service calls authenticated?
217
+
218
+ #### Compliance
219
+
220
+ If `project.compliance` configured, verify per `./security-compliance.md` (section "Compliance Framework Verification"). Security-tagged scenario in linked change with no corresponding verification = **blocker**.
221
+
222
+ #### Tagging
223
+
224
+ Every security finding gets OWASP 2021 + CWE tags. See CWE quick-reference in `./security-compliance.md`.
225
+
226
+ ### 4.4 QA Engineer
227
+
228
+ Skip if change is purely internal.
229
+
230
+ Evaluate:
231
+ - **Test presence**: Every new user-facing behavior has a test? Every scenario from linked feature file has step definitions?
232
+ - **Test quality**: Tests asserting outputs, or just that code "ran"? Over-mocked tests = red flag.
233
+ - **Negative paths**: For each happy path, is there a failure-path test?
234
+ - **Edge cases**: Empty states, concurrent users, interruptions, boundary values?
235
+ - **Observability**: New feature — how will it be debugged in prod? Structured logs / metrics / error surfaces?
236
+ - **Regression risk** *(PR/pre-commit)*: Which existing tests cover the touched code? Were any tests removed or weakened?
237
+ - **Accessibility**: New UI — keyboard nav, aria labels, contrast?
238
+
239
+ ### 4.5 Data Engineer
240
+
241
+ Skip unless change touches migrations, models, schema, or external API clients.
242
+
243
+ Read:
244
+ - `.grimoire/changes/<change-id>/data.yml` — proposed schema changes (design)
245
+ - `.grimoire/docs/data/schema.yml` — current baseline
246
+
247
+ Evaluate:
248
+ - **Migrations**: Safe on live DB? Adding NOT NULL without default on large table = **blocker**. Renames without two-step migration = **blocker**.
249
+ - **Indexes**: New foreign keys with no index? New query patterns against unindexed columns?
250
+ - **Naming**: New fields follow existing schema conventions?
251
+ - **Backwards compatibility**: Will schema change break existing API consumers, queries, or reports?
252
+ - **Breaking contract**: `data.yml` vs `schema.yml` — removed/renamed/retyped response fields or new required request fields = **blocker** unless migration path documented.
253
+ - **Transactions**: Multi-step writes wrapped in a transaction?
254
+ - **External APIs** *(design)*: New API dependency — `schema_ref` pointing to a stable spec? Fallback if API unavailable?
255
+
256
+ ### 4.6 Code Style Reviewer *(PR/pre-commit only — skip on design review)*
257
+
258
+ Verify the diff matches the project's code-style and comment standards. This is not "general taste" — every finding must cite a concrete project rule the change violates.
259
+
260
+ #### Sources to load (in order)
261
+
262
+ 1. `.grimoire/config.yaml` → `project.comment_style` and `project.language` (sets baseline expectations)
263
+ 2. `AGENTS.md` / `CLAUDE.md` at repo root — engineering principles, comment policy
264
+ 3. `.grimoire/docs/<area>.md` for each touched area — local conventions, reusable utilities
265
+ 4. Lint/format config in repo root: `.editorconfig`, `eslint.config.*`, `.prettierrc*`, `pyproject.toml` (ruff/black), `.rubocop.yml`, `rustfmt.toml`, `.golangci.yml`, etc.
266
+ 5. **Neighboring files** in the touched directories — derive convention from what already exists when no config exists
267
+
268
+ If none of the above pin a rule, **don't invent one**. Style preferences without a project anchor are dropped.
269
+
270
+ #### Evaluate
271
+
272
+ - **Naming**: Identifiers (functions, types, files) match the project's casing and naming patterns visible in neighbors? New file names follow the directory's existing pattern?
273
+ - **File layout**: New file lives where similar files live? Module boundaries respected?
274
+ - **Imports**: Order, grouping, and form match the project (relative vs absolute, `.js` extension policy, type-only imports)?
275
+ - **Formatting**: Diff respects `.editorconfig` and formatter rules (indentation, line endings, trailing newline, max line length)? Any formatter-noisy hunks unrelated to the change?
276
+ - **Comments — presence**: Is there a comment whose WHAT is already obvious from the code? Per most projects' comment policies (and grimoire's `AGENTS.md`), explanatory-of-what comments are noise — **suggestion** to remove.
277
+ - **Comments — content**: Do comments reference current task / fix / PR / caller ("added for X", "used by Y", "fix for issue #123")? These rot — **suggestion** to remove or rewrite as durable rationale.
278
+ - **Comments — style**: Match the project's comment form (`//` vs `/* */` vs `#`, JSDoc/TSDoc/docstring conventions)?
279
+ - **Docstrings**: New public functions/classes — does the project require docstrings? If yes (per `comment_style` or visible convention), missing docstring = **suggestion**. If no, added boilerplate docstrings = **suggestion** to remove.
280
+ - **Dead comments**: Commented-out code in the diff = **suggestion** to delete.
281
+ - **TODO/FIXME**: New TODOs added with no owner or ticket reference, when project convention requires them = **suggestion**.
282
+ - **Error messages / log strings**: Tone and format match neighbors (sentence case, periods, structured logging fields)?
283
+ - **Type annotations** *(typed languages)*: New code matches the project's typing strictness — no `any`/`unknown`/`Object` if neighbors are strict; explicit return types if convention requires?
284
+
285
+ Severity:
286
+ - **Blocker**: violates a configured lint/format rule that would fail CI, or violates an explicit rule in `AGENTS.md` / `CLAUDE.md` / area doc.
287
+ - **Suggestion**: deviates from neighbor convention without a config anchor, or comment-policy nits.
288
+
289
+ If the project has no committed style config and neighbors are inconsistent, say so once and move on — don't pick a side.
290
+
291
+ ### 4.7 Adversarial User *(engaged when `project.surface` matches the persona's activation row)*
292
+
293
+ Surface-conditional personas inhabit users the design might fail: keyboard-only, screen-reader, low-vision / color-blind, touch-target, responsive-breakpoint, RTL / i18n, low-bandwidth / offline, hostile-actor, API-conventions. Full criteria, persona catalog, activation matrix (persona × surface), severity calibration, and steel-man requirement live in `./adversarial-personas.md`. Engagement is gated by `project.surface` — see the activation matrix there. Findings inherit §1 briefing, §2 / §2a / §2b materiality and severity rules. The Contrarian pass (§4.8) calibrates adversarial findings post-hoc on the same terms as the other personas.
294
+
295
+ ### 4.8 Contrarian *(runs last, after all other personas submit findings)*
296
+
297
+ Inspired by ouroboros/contrarian — adapted for review use. The Contrarian does not submit its own findings against the code. Instead, it **challenges the other personas' findings**, especially blockers, and tunes them. Its goal is to kill the reviewer-overreach failure mode: manufactured blockers, missing steel-mans, finding-by-quota, severity inflation.
298
+
299
+ Always runs when at least one persona produced a blocker. May be skipped only when all personas produced zero findings.
300
+
301
+ #### Inputs
302
+
303
+ - The complete set of findings from §4.1-§4.7 (blockers and suggestions).
304
+ - The Project Briefing (§1).
305
+ - The diff or design under review.
306
+
307
+ #### For each blocker, ask:
308
+
309
+ 1. **What is the steel-man for the author's choice?** Write the strongest version of why the code / design is the way it is — drawing on briefing constraints, ADRs, neighbor conventions, performance trade-offs, simplicity, stage. If the finding doesn't already include a steel-man (§2a), the Contrarian writes one. If the steel-man holds, the finding is wrong.
310
+ 2. **What assumption is this finding making?** Name it. ("Assumes inputs are untrusted at this layer." / "Assumes high traffic." / "Assumes a regulator audits this surface.") If the assumption doesn't match the briefing, the finding is mis-calibrated.
311
+ 3. **What if the opposite were right?** What if the "obvious" fix is the wrong move for this codebase / stage / scale? Inversion test: if you removed the existing code and applied the finding's recommendation, what *new* problems would you create? List them.
312
+ 4. **What if doing nothing is the right call?** Is this a symptom or a root cause? Will it actually trigger? What's the cost of "fix now" vs. "fix when it actually hurts"?
313
+ 5. **Is the severity calibrated?** Does the finding meet all three blocker criteria (§2b)? If not, downgrade or drop.
314
+
315
+ #### For suggestions, ask:
316
+
317
+ - Is this a real preference of the project, or the reviewer's preference? If the reviewer can't cite a project anchor (AGENTS.md, ADR, area doc, neighbor pattern), drop it.
318
+
319
+ #### Output
320
+
321
+ For each blocker the Contrarian processes, emit one of:
322
+
323
+ - **Upheld** — `[blocker upheld]` with one line: "Steel-man considered; harm path holds because …"
324
+ - **Downgraded to suggestion** — `[blocker → suggestion]` with one line explaining what was missing (no harm path / no briefing anchor / mitigated by neighbor / steel-man held in part).
325
+ - **Dropped** — `[finding dropped]` with one line explaining why.
326
+
327
+ The Contrarian's report replaces or annotates the original findings. The Summary uses the post-Contrarian counts.
328
+
329
+ #### Contrarian is not a veto
330
+
331
+ The Contrarian is a calibration pass, not an authority. If a persona disagrees with a downgrade and can cite a concrete harm path tied to briefing that the Contrarian missed, the finding is re-upheld. The Contrarian's job is to make findings *honest about their evidence*, not to suppress signal.
332
+
333
+ #### What Contrarian does NOT do
334
+
335
+ - It does not add new findings.
336
+ - It does not soften the *content* of upheld blockers (no "perhaps consider possibly" hedging).
337
+ - It does not challenge findings that already pass §2a/§2b cleanly.
338
+ - It does not run on level 1 (no review) or when all personas returned zero findings.
339
+
340
+ ---
341
+
342
+ ## 5. Output Format
343
+
344
+ Each persona returns a short bulleted list. The calling skill compiles them into a single report. Standard structure:
345
+
346
+ ```markdown
347
+ # <Review Title> — <subject>
348
+
349
+ <header line: change-id / PR number / staged-diff scope, base/head, complexity, files/lines>
350
+
351
+ ## Project Briefing
352
+ <from §1>
353
+
354
+ ## Product Manager
355
+ - **[blocker]** ...
356
+ - **[suggestion]** ...
357
+ (or "Skipped — purely internal change.")
358
+
359
+ ## Senior Engineer
360
+ - ...
361
+
362
+ ## Security Engineer
363
+ ### STRIDE
364
+ - Spoofing: ...
365
+ - Tampering: ...
366
+ - ...
367
+
368
+ ### Findings
369
+ - **[blocker]** [A03:2021 / CWE-89] ...
370
+
371
+ ## QA Engineer
372
+ - ...
373
+
374
+ ## Data Engineer
375
+ - ...
376
+
377
+ ## Code Style <!-- omit on design review -->
378
+ - **[blocker]** `eslint.config.js` rule `no-unused-vars` violated at `src/foo.ts:42`
379
+ - **[suggestion]** Comment at `src/foo.ts:88` describes what the code does — remove (per `AGENTS.md` "Default to writing no comments").
380
+
381
+ ## Adversarial User <!-- omit when no surface-matched personas engaged -->
382
+ - **[blocker]** [keyboard-only] Submit button at `designs/variant-2.html:84` is `<div onclick>` — not focusable. Steel-man considered (custom styling); harm path holds (user cannot tab to submit).
383
+ - **[suggestion]** [low-vision] Body text contrast 4.2:1 at `designs/variant-2.html:120` — below WCAG AA 4.5:1.
384
+
385
+ ## Contrarian <!-- omit when zero findings from all personas -->
386
+ - **[blocker upheld]** Senior Engineer's auth-bypass finding at `src/api/users.ts:18`. Steel-man: middleware order may guarantee auth runs first. Inspected — the route is mounted outside the auth middleware. Harm path holds.
387
+ - **[blocker → suggestion]** Security Engineer's "missing rate limit on /reset-password". Briefing stage is internal-tools; threat surface tag count = 0. Cost-of-fix > realistic harm.
388
+ - **[finding dropped]** QA Engineer's "missing test for concurrent password reset". No scenario in the briefing references concurrency; no harm path stated; downgrade to suggestion would also fail. Dropped.
389
+
390
+ ## Summary <!-- counts are post-Contrarian -->
391
+ - **N blockers** — must be addressed
392
+ - **M suggestions** — consider addressing
393
+
394
+ Recommendation: <fix blockers / request changes / approve / proceed to apply>
395
+ ```
396
+
397
+ ---
398
+
399
+ ## 6. Style Rules for Findings
400
+
401
+ - Reference specific files and line numbers for every diff-based finding.
402
+ - Be direct. No padding with praise. Blockers stop the gate; suggestions are advisory.
403
+ - Findings describe the code, not the person. "This query is vulnerable to injection" not "you wrote an injection".
404
+ - Three findings that matter beat ten that don't.
405
+ - If the change is trivial, say so and don't manufacture issues.
@@ -75,7 +75,7 @@ For changed files, do a lightweight scan:
75
75
  | A03: Injection | String concatenation in SQL/commands/templates, `eval()`, `innerHTML` with user data |
76
76
  | A04: Insecure Design | Missing rate limiting on auth endpoints, no account lockout |
77
77
  | A05: Security Misconfiguration | Debug mode enabled, default credentials, overly permissive CORS |
78
- | A06: Vulnerable Components | New dependencies without version pins, known-vulnerable packages |
78
+ | A06: Vulnerable Components | Unpinned dependencies (ranges like `^`, `~`, `>=`, `*`), missing/uncommitted lockfile, known-vulnerable packages, recently-transferred or newly-maintained packages (supply-chain attack vector) |
79
79
  | A07: Auth Failures | Weak password requirements, session tokens in URLs |
80
80
  | A08: Data Integrity Failures | Insecure deserialization (`pickle`, `yaml.load`), missing integrity checks |
81
81
  | A09: Logging Failures | Security events not logged, PII/secrets in log output |
@@ -95,6 +95,27 @@ Tag each finding with OWASP category and CWE ID.
95
95
  | Hardcoded secrets | A07:2021 / CWE-798 |
96
96
  | SSRF | A10:2021 / CWE-918 |
97
97
  | Insecure deserialization | A08:2021 / CWE-502 |
98
+ | Unpinned / unverified dependency | A06:2021 / CWE-1357, CWE-829 |
99
+
100
+ ## Supply Chain Defense
101
+
102
+ Applies to any change that adds or upgrades a dependency, regardless of tags. Recent ecosystem incidents (npm, PyPI, RubyGems, Cargo) show attackers compromising maintainer accounts or transferring packages to push malicious patch releases. Floating version ranges let those releases auto-install on the next build.
103
+
104
+ **Scope:** these rules apply to **applications and services** (the thing that gets deployed). Libraries published to a registry should keep compatible ranges in their manifest so consumers can resolve — apply pinning only in the library's own dev/test lockfile, not its published manifest.
105
+
106
+ - **Pin resolved versions via a committed lockfile.** The build must always install the same exact versions and transitive tree that were reviewed.
107
+ - **npm / pnpm / yarn:** lockfile committed; no `^`, `~`, `>=`, `*`, or `latest` resolving wider than intended in `package.json` for an app.
108
+ - **Python (pip / uv / poetry):** committed lockfile with hashes (`uv.lock`, `poetry.lock`, or `requirements.txt` generated via `pip-compile --generate-hashes`). Avoid unpinned `requirements.txt`.
109
+ - **Ruby:** committed `Gemfile.lock`. `Gemfile` may use pessimistic operators; lockfile gates the resolution.
110
+ - **Cargo:** committed `Cargo.lock` (mandatory for binaries; opt in for libraries used as apps). Caret in `Cargo.toml` is idiomatic — the lockfile is the source of truth.
111
+ - **Go:** `go.mod` already records exact minimum versions (Minimum Version Selection — no range operators exist), and `go.sum` carries module hashes. Both must be committed.
112
+ - **Install from the lockfile in CI and prod.** `npm ci`, `pnpm install --frozen-lockfile`, `yarn install --frozen-lockfile` (Classic / v1) or `yarn install --immutable` (Berry / v2+), `uv sync --frozen`, `pip install --require-hashes -r requirements.txt`, `bundle install --deployment` (or `bundle config set --local frozen true && bundle install`), `cargo build --locked`, `go build` with `GOFLAGS=-mod=readonly`. Never run a resolver that can mutate the lockfile silently (`npm install`, `bundle install` without frozen, etc.).
113
+ - **Verify integrity hashes** are present in the lockfile (npm `integrity:`, pip `--hash` entries, Cargo `checksum`, Go `h1:`, `Gemfile.lock` `CHECKSUMS` section — opt-in via `bundle lock --add-checksums` on Bundler 2.5+). Reject lockfile entries without them on ecosystems that support hashing.
114
+ - **Vet new packages** — real name (typosquat check against the popular package), non-trivial download count, established maintainers, no ownership transfer in the last ~90 days, no sudden new-maintainer publish.
115
+ - **Run `dep_audit`** (if `config.tools.dep_audit` is configured) against the lockfile before merge. Vulnerable transitive dep = **blocker** unless an override is justified in the change manifest.
116
+ - **Avoid post-install / build scripts** from untrusted packages where possible. Use `--ignore-scripts` (npm) or equivalent in CI when feasible.
117
+
118
+ A change that adds or upgrades a dependency in an app/service without a committed lockfile (and, for ecosystems that support it, integrity hashes) is a **blocker**. Treat dependency additions as a change to the trust boundary — they execute with the same privileges as your code.
98
119
 
99
120
  ## Compliance Framework Verification
100
121