@kiwidata/grimoire 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/AGENTS.md +56 -4
  2. package/README.md +107 -59
  3. package/dist/cli/index.js +7 -7
  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/core/check.d.ts.map +1 -1
  15. package/dist/core/check.js +165 -111
  16. package/dist/core/check.js.map +1 -1
  17. package/dist/core/ci.d.ts.map +1 -1
  18. package/dist/core/ci.js +50 -69
  19. package/dist/core/ci.js.map +1 -1
  20. package/dist/core/configure.d.ts +14 -0
  21. package/dist/core/configure.d.ts.map +1 -0
  22. package/dist/core/configure.js +434 -0
  23. package/dist/core/configure.js.map +1 -0
  24. package/dist/core/detect.d.ts.map +1 -1
  25. package/dist/core/detect.js +153 -26
  26. package/dist/core/detect.js.map +1 -1
  27. package/dist/core/diff.d.ts.map +1 -1
  28. package/dist/core/diff.js +62 -93
  29. package/dist/core/diff.js.map +1 -1
  30. package/dist/core/doc-style.d.ts +0 -4
  31. package/dist/core/doc-style.d.ts.map +1 -1
  32. package/dist/core/doc-style.js +103 -22
  33. package/dist/core/doc-style.js.map +1 -1
  34. package/dist/core/docs.js +202 -170
  35. package/dist/core/docs.js.map +1 -1
  36. package/dist/core/health.d.ts +6 -0
  37. package/dist/core/health.d.ts.map +1 -1
  38. package/dist/core/health.js +133 -96
  39. package/dist/core/health.js.map +1 -1
  40. package/dist/core/hooks.d.ts +0 -3
  41. package/dist/core/hooks.d.ts.map +1 -1
  42. package/dist/core/hooks.js +11 -16
  43. package/dist/core/hooks.js.map +1 -1
  44. package/dist/core/init.d.ts +2 -0
  45. package/dist/core/init.d.ts.map +1 -1
  46. package/dist/core/init.js +230 -406
  47. package/dist/core/init.js.map +1 -1
  48. package/dist/core/list.d.ts.map +1 -1
  49. package/dist/core/list.js +55 -65
  50. package/dist/core/list.js.map +1 -1
  51. package/dist/core/risk-register.d.ts +17 -0
  52. package/dist/core/risk-register.d.ts.map +1 -0
  53. package/dist/core/risk-register.js +73 -0
  54. package/dist/core/risk-register.js.map +1 -0
  55. package/dist/core/shared-setup.d.ts +0 -40
  56. package/dist/core/shared-setup.d.ts.map +1 -1
  57. package/dist/core/shared-setup.js +92 -56
  58. package/dist/core/shared-setup.js.map +1 -1
  59. package/dist/core/status.d.ts.map +1 -1
  60. package/dist/core/status.js +42 -52
  61. package/dist/core/status.js.map +1 -1
  62. package/dist/core/test-quality.d.ts +0 -8
  63. package/dist/core/test-quality.d.ts.map +1 -1
  64. package/dist/core/test-quality.js +24 -30
  65. package/dist/core/test-quality.js.map +1 -1
  66. package/dist/core/trace.d.ts.map +1 -1
  67. package/dist/core/trace.js +67 -75
  68. package/dist/core/trace.js.map +1 -1
  69. package/dist/core/update.d.ts.map +1 -1
  70. package/dist/core/update.js +61 -11
  71. package/dist/core/update.js.map +1 -1
  72. package/dist/core/validate.d.ts +1 -4
  73. package/dist/core/validate.d.ts.map +1 -1
  74. package/dist/core/validate.js +126 -148
  75. package/dist/core/validate.js.map +1 -1
  76. package/dist/index.d.ts +0 -3
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +0 -3
  79. package/dist/index.js.map +1 -1
  80. package/dist/utils/config.d.ts +15 -5
  81. package/dist/utils/config.d.ts.map +1 -1
  82. package/dist/utils/config.js +63 -42
  83. package/dist/utils/config.js.map +1 -1
  84. package/dist/utils/fs.d.ts +0 -12
  85. package/dist/utils/fs.d.ts.map +1 -1
  86. package/dist/utils/fs.js +0 -12
  87. package/dist/utils/fs.js.map +1 -1
  88. package/dist/utils/paths.d.ts +0 -6
  89. package/dist/utils/paths.d.ts.map +1 -1
  90. package/dist/utils/paths.js +0 -6
  91. package/dist/utils/paths.js.map +1 -1
  92. package/dist/utils/spawn.d.ts +0 -3
  93. package/dist/utils/spawn.d.ts.map +1 -1
  94. package/dist/utils/spawn.js +0 -3
  95. package/dist/utils/spawn.js.map +1 -1
  96. package/package.json +1 -1
  97. package/skills/grimoire-apply/SKILL.md +89 -25
  98. package/skills/grimoire-audit/SKILL.md +21 -1
  99. package/skills/grimoire-bug/SKILL.md +48 -9
  100. package/skills/grimoire-commit/SKILL.md +3 -2
  101. package/skills/grimoire-design/SKILL.md +259 -0
  102. package/skills/grimoire-design-consult/SKILL.md +200 -0
  103. package/skills/grimoire-discover/SKILL.md +139 -109
  104. package/skills/grimoire-draft/SKILL.md +131 -15
  105. package/skills/grimoire-plan/SKILL.md +119 -46
  106. package/skills/grimoire-pr/SKILL.md +7 -10
  107. package/skills/grimoire-pr-review/SKILL.md +46 -115
  108. package/skills/grimoire-precommit-review/SKILL.md +205 -0
  109. package/skills/grimoire-refactor/SKILL.md +6 -6
  110. package/skills/grimoire-review/SKILL.md +95 -156
  111. package/skills/grimoire-verify/SKILL.md +40 -7
  112. package/skills/grimoire-vuln-remediate/SKILL.md +107 -0
  113. package/skills/grimoire-vuln-triage/SKILL.md +109 -0
  114. package/skills/references/adversarial-personas.md +225 -0
  115. package/skills/references/brand-tokens-format.md +186 -0
  116. package/skills/references/code-quality.md +172 -0
  117. package/skills/references/container-scan-triage.md +102 -0
  118. package/skills/references/dependency-vuln-triage.md +236 -0
  119. package/skills/references/design-heuristics.md +138 -0
  120. package/skills/references/design-input-formats.md +190 -0
  121. package/skills/references/pattern-guard.md +180 -0
  122. package/skills/references/principles.md +82 -0
  123. package/skills/references/refactor-scan-categories.md +154 -2
  124. package/skills/references/review-personas.md +406 -0
  125. package/skills/references/security-compliance.md +22 -1
  126. package/skills/references/testing-contracts.md +1 -1
  127. package/skills/references/visual-fidelity.md +206 -0
  128. package/templates/accepted-risks.yml +47 -0
  129. package/templates/brand-tokens-example.json +13 -0
  130. package/templates/brand-voice-example.md +22 -0
  131. package/templates/constraints.md +25 -0
  132. package/templates/design-tool-setup-stub.md +59 -0
  133. package/dist/commands/archive.d.ts +0 -3
  134. package/dist/commands/archive.d.ts.map +0 -1
  135. package/dist/commands/archive.js +0 -22
  136. package/dist/commands/archive.js.map +0 -1
  137. package/dist/commands/log.d.ts +0 -3
  138. package/dist/commands/log.d.ts.map +0 -1
  139. package/dist/commands/log.js +0 -15
  140. package/dist/commands/log.js.map +0 -1
  141. package/dist/commands/map.d.ts +0 -3
  142. package/dist/commands/map.d.ts.map +0 -1
  143. package/dist/commands/map.js +0 -17
  144. package/dist/commands/map.js.map +0 -1
  145. package/dist/core/archive.d.ts +0 -9
  146. package/dist/core/archive.d.ts.map +0 -1
  147. package/dist/core/archive.js +0 -92
  148. package/dist/core/archive.js.map +0 -1
  149. package/dist/core/log.d.ts +0 -8
  150. package/dist/core/log.d.ts.map +0 -1
  151. package/dist/core/log.js +0 -150
  152. package/dist/core/log.js.map +0 -1
  153. package/dist/core/map.d.ts +0 -9
  154. package/dist/core/map.d.ts.map +0 -1
  155. package/dist/core/map.js +0 -302
  156. package/dist/core/map.js.map +0 -1
  157. package/templates/dupignore +0 -93
  158. package/templates/mapignore +0 -58
  159. package/templates/mapkeys +0 -65
@@ -0,0 +1,180 @@
1
+ # Pattern Guard Reference
2
+
3
+ Loaded by `grimoire-apply` and `grimoire-bug`. Run **before writing the test** for each task — not after, not as a review pass. The goal is to write code that matches the codebase's established conventions the first time, rather than writing to generic patterns and fixing divergence later.
4
+
5
+ This is not a quality checklist. It is a reconnaissance step: find out how this codebase already solves this class of problem, then write to that pattern.
6
+
7
+ Requires `codebase-memory-mcp` indexed. If the graph is not available, skip this reference entirely and rely on `code-quality.md` alone.
8
+
9
+ ---
10
+
11
+ ## Run Before Each Task
12
+
13
+ ### Step 1 — Classify the code being written
14
+
15
+ From the task description and feature file, identify what category of code this task produces:
16
+
17
+ | Code type | Examples |
18
+ |---|---|
19
+ | `api_handler` | Route handler, view, controller, endpoint function |
20
+ | `service` | Use case, interactor, domain service, business logic function |
21
+ | `repository` | Data access, ORM query, store method, DAO |
22
+ | `model` | ORM model, dataclass, schema, type definition |
23
+ | `utility` | Helper, formatter, validator, parser |
24
+ | `test` | Step definition, unit test, fixture, factory |
25
+ | `middleware` | Auth, logging, rate-limiting, request transform |
26
+ | `integration` | External API client, webhook handler, adapter |
27
+
28
+ A task may touch multiple types — classify the primary one.
29
+
30
+ ### Step 1b — Reuse discovery
31
+
32
+ Before finding peer patterns, ask: **does what I'm about to write already exist?**
33
+
34
+ These are two different questions. Step 2 finds code to *pattern-match against*. This step finds code to *call instead of writing*.
35
+
36
+ For each function, helper, or class the task requires, run both searches:
37
+
38
+ **Semantic search** — find it by concept, not by name:
39
+ ```
40
+ search_graph(semantic_query=["<primary_concept>", "<action_verb>", "<domain_noun>"])
41
+ ```
42
+ Example: about to write something that formats a currency amount →
43
+ ```
44
+ search_graph(semantic_query=["format", "currency", "amount"])
45
+ ```
46
+ This finds `render_price`, `display_amount`, `format_currency` — whatever name the codebase already uses.
47
+
48
+ **Name-pattern search** — find it by likely prefix or suffix:
49
+ ```
50
+ search_graph(name_pattern="(format_|_format|currency|amount|price)")
51
+ ```
52
+
53
+ **Decision rules:**
54
+ - Result does the job → **call it**. Do not re-implement.
55
+ - Result almost fits → **use it directly**. Do not generalize it for a second case that doesn't exist yet.
56
+ - Both searches return nothing usable → write new code and proceed to Step 2.
57
+
58
+ **Log the outcome in the pattern brief** (Step 4): note which searches ran and what they found. If calling an existing function instead of writing new code, note it explicitly: `Reused format_currency from billing/utils.py — no new function needed.`
59
+
60
+ Do not skip this step. Writing new code without a reuse search is the primary source of duplication in LLM-generated codebases. The semantic_query mode bridges vocabulary gaps — it finds "publish" when you search "send".
61
+
62
+ ### Step 2 — Find peer examples
63
+
64
+ Use `search_graph` to find 3–5 existing functions/classes of the same type. Prefer the most established (oldest, least recently changed) — these are the modal pattern, not the recent drift.
65
+
66
+ **Queries by code type:**
67
+
68
+ ```
69
+ api_handler: search_graph(label="Function", name_pattern="(handle|view|endpoint|get_|post_|put_|delete_|patch_)")
70
+ service: search_graph(label="Function", name_pattern="(service|use_case|create_|update_|delete_|process_)")
71
+ repository: search_graph(label="Function", name_pattern="(repo|get_by|find_by|list_|save_|delete_)")
72
+ model: search_graph(label="Class", name_pattern="(Model|Schema|DTO|Type|Entity)")
73
+ utility: search_graph(label="Function", name_pattern="(parse_|format_|validate_|convert_|build_)")
74
+ test: search_graph(label="Module", name_pattern="(test_|_test|spec|_spec|conftest|fixture)")
75
+ middleware: search_graph(label="Function", name_pattern="(middleware|guard|interceptor|filter|auth)")
76
+ integration: search_graph(label="Class", name_pattern="(Client|Adapter|Gateway|Connector|Webhook)")
77
+ ```
78
+
79
+ If the query returns > 10 results, filter to the same area as the task's target file (check area docs or directory).
80
+
81
+ Exclude files changed in the last 60 days from your sample — those may already be drifted. Use `git log --since="60 days ago" --name-only --format=` to get the recent list.
82
+
83
+ If < 3 peers exist in the graph, skip the pattern brief — there's no established pattern yet. Write to `code-quality.md` rules and the feature spec only.
84
+
85
+ ### Step 3 — Extract the modal pattern
86
+
87
+ `get_code_snippet(qualified_name)` for each peer. Read across all samples and identify:
88
+
89
+ **Four critical seams** (these are the ones that cause architectural drift if broken):
90
+
91
+ 1. **Error handling** — Does this codebase raise exceptions or return result/error values at this layer? Do handlers catch specific exception types? Is there a central error handler or per-function handling?
92
+
93
+ 2. **Dependency access** — Are dependencies injected (constructor, function arg) or imported directly? Is there an established pattern (DI container, FastAPI `Depends`, Django `self.repository`, etc.)?
94
+
95
+ 3. **Abstraction depth** — Does this code type contain business logic, or does it delegate? (e.g., handlers should be thin, services should be thick — but check what *this* codebase actually does)
96
+
97
+ 4. **Return shape** — Dict? Typed dataclass/schema? Model instance? Tuple `(result, error)`? Pydantic model? Match exactly.
98
+
99
+ **Three secondary seams** (style drift, not architecture):
100
+
101
+ 5. **Naming** — snake_case vs camelCase beyond language default, verb-first vs noun-first for functions, consistent abbreviation patterns
102
+
103
+ 6. **Test structure** — `pytest` fixtures vs factories vs inline setup? `unittest.mock` vs `pytest-mock`? Arrange/Act/Assert comments or no?
104
+
105
+ 7. **Import order / grouping** — stdlib → third-party → local? Relative vs absolute imports?
106
+
107
+ ### Step 4 — Write the pattern brief
108
+
109
+ Produce a short, concrete brief of 5–8 rules derived from the samples. Not generic rules — rules for *this task in this codebase*. Example:
110
+
111
+ ```
112
+ Pattern brief for: POST /invoices handler (api_handler)
113
+
114
+ From 4 peers (billing/views.py, orders/views.py, customers/views.py, auth/views.py):
115
+
116
+ 1. Error handling: raise ValidationError / NotFound — do NOT return {"error": ...}.
117
+ Central handler in middleware/errors.py converts exceptions to HTTP responses.
118
+ 2. Dependency: inject service via constructor arg — `def __init__(self, invoice_service: InvoiceService)`
119
+ Do NOT call InvoiceService() inline.
120
+ 3. Abstraction: handler validates request, calls one service method, serializes response.
121
+ No business logic in the handler.
122
+ 4. Return shape: return InvoiceSerializer(result).data with DRF Response — not a raw dict.
123
+ 5. Naming: method names are HTTP verb — `def post(self, request)` not `def create_invoice`.
124
+ ```
125
+
126
+ This brief is your constraint set for this task. Apply it while writing — not as a review after.
127
+
128
+ ### Step 5 — Write to the brief
129
+
130
+ When writing the test and production code for this task:
131
+
132
+ - Apply the brief's rules as hard constraints, not suggestions
133
+ - If the task spec conflicts with the brief (e.g., feature file implies a return shape the codebase doesn't use), flag it to the user before writing — don't silently choose one
134
+ - If you must deviate from the brief (e.g., the brief's pattern won't work for this specific case), note the deviation inline with a comment explaining why, and add it to the handoff note
135
+
136
+ ### Step 6 — Verify called functions exist
137
+
138
+ After writing production code, before running tests:
139
+
140
+ Extract every external function/method call your new code makes (exclude stdlib and known third-party packages). For each:
141
+
142
+ ```
143
+ search_graph(name_pattern="<function_name>")
144
+ ```
145
+
146
+ If a called function is not found in the graph:
147
+ - Check the import — is it an alias or renamed import?
148
+ - Check for typos against similar names in the graph
149
+ - If genuinely missing: **stop**. Do not call a function that doesn't exist. Either find the correct function via `search_graph` or flag to the user.
150
+
151
+ This catches hallucinated API calls before they become broken tests.
152
+
153
+ ---
154
+
155
+ ## Pattern Brief Template
156
+
157
+ ```
158
+ Pattern brief for: <task title> (<code_type>)
159
+
160
+ From <N> peers (<file1>, <file2>, ...):
161
+
162
+ 1. Error handling: <what the peers do>
163
+ 2. Dependency: <injection pattern used>
164
+ 3. Abstraction: <what this layer does vs delegates>
165
+ 4. Return shape: <concrete type/shape>
166
+ 5. Naming: <any non-obvious conventions>
167
+ [6. Test structure: <if this is a test task>]
168
+ [7. Deviation noted: <if you must deviate, why>]
169
+ ```
170
+
171
+ Write the brief into the task's handoff note in `tasks.md` so future sessions have it.
172
+
173
+ ---
174
+
175
+ ## When to Skip
176
+
177
+ - Graph not indexed → skip entirely, use `code-quality.md` only
178
+ - < 3 peers found → skip the brief, note "no established pattern yet"
179
+ - Task is adding a new code type with no prior examples → skip the brief, note "first of this type"
180
+ - Hotfix / bug task in `grimoire-bug` → run only Step 6 (hallucination check); skip the full brief to avoid over-constraining the fix
@@ -0,0 +1,82 @@
1
+ # Grimoire Design Principles
2
+
3
+ Four principles govern every grimoire artifact and every change. `grimoire-draft`,
4
+ `grimoire-plan`, and `grimoire-review` each enforce them at their own stage. They
5
+ are not style preferences — they are gates. A draft, plan, or design that violates
6
+ one without a stated reason is rejected, not merged.
7
+
8
+ This file is the single home for the principles (it practices what it preaches —
9
+ the skills cite it rather than restating it).
10
+
11
+ ---
12
+
13
+ ## 1. One right way to do a thing
14
+
15
+ There is exactly **one** sanctioned way to do each thing in the codebase, and one
16
+ authoritative home for each fact. Two ways to do the same thing is a defect, even
17
+ if both work.
18
+
19
+ - One capability → one feature spec. One decision → one MADR. One constraint → one
20
+ register entry. One fact → one home. No capability described in three places.
21
+ - When a second mechanism appears for an existing job, the right move is to delete
22
+ one and converge — never to keep both "for flexibility."
23
+ - **Tell:** "we could do it this way *or* that way" in a spec/plan. Pick one. Record
24
+ why in a MADR if the choice is non-obvious; don't leave both paths in the code.
25
+
26
+ ## 2. DRY — don't repeat yourself
27
+
28
+ Every piece of knowledge has a single, unambiguous representation.
29
+
30
+ - Don't store what's derivable. Code structure comes from codebase-memory-mcp on
31
+ demand — never freeze it into a doc that drifts. Generated overviews regenerate;
32
+ they are not hand-edited.
33
+ - Reuse before write: search the graph for an existing function/utility before
34
+ writing a new one. Three near-identical copies is the trigger to converge — but
35
+ do not abstract before the third (see KISS).
36
+ - Duplication of *content* (the same rule in three skill files, the same constant in
37
+ three modules, the same scenario in feature + MADR) is the target. Eliminate it.
38
+
39
+ ## 3. Don't reinvent the wheel — use existing tools
40
+
41
+ If an established tool already does a job well, use it. Do not build a parallel
42
+ grimoire mechanism that duplicates it.
43
+
44
+ - **git** is the wheel for change processes: branches = isolation, `git diff` =
45
+ staging, `git log` + PR + commit trailers = history and change identity. Do not
46
+ build change-folder copies, promote/sync steps, or bespoke archive/changelog trees.
47
+ - For auth, crypto, parsing, HTTP, queues, etc. — adopt the battle-tested library.
48
+ Never roll custom crypto, custom session management, custom auth tokens.
49
+ - Before building any tracking/versioning/state/diff mechanism, ask: does a standard
50
+ tool already in the stack do this? If yes, wire to it.
51
+ - **Exception that proves the rule:** when no single standard tool exists (e.g. issue
52
+ tracking is a fractured landscape), don't force-adopt one *and* don't build a
53
+ general-purpose clone. Keep any local mechanism narrow and purpose-scoped.
54
+
55
+ ## 4. Keep it simple (KISS)
56
+
57
+ The simplest thing that fully solves the *stated* problem wins.
58
+
59
+ - Least code, fewest new files, smallest surface area. A few lines in an existing
60
+ file beats a new module. A standard-library call beats a new dependency. Inline
61
+ beats a one-line wrapper.
62
+ - No premature abstraction. No `BaseX`/factory/strategy/config-object for a single
63
+ caller. No speculative generality "for a future second caller" that doesn't exist.
64
+ - Solve the problem in front of you, not the imagined one. Non-goals are real scope
65
+ boundaries — do not plan or build past them.
66
+ - **Tell:** an abstraction, indirection, or dependency whose only justification is a
67
+ hypothetical. Cut it.
68
+
69
+ ---
70
+
71
+ ## How the stages apply these
72
+
73
+ - **draft** — admission-test every artifact: does this fact already have a home
74
+ (one-right-way/DRY)? Is it behavior (→ feature) or a constraint/decision/structure
75
+ (→ its own home, not a feature)? Is there an existing tool/library for it
76
+ (don't-reinvent)? Is the scope the stated problem only (KISS)?
77
+ - **plan** — every task names the single approach (one-right-way), reuses before
78
+ writing (DRY), follows a proven pattern / existing tool rather than a bespoke one
79
+ (don't-reinvent), and chooses the least-code option within non-goals (KISS). Flag
80
+ any task that adds an abstraction, dependency, or second mechanism.
81
+ - **review** — a dedicated principles pass: hunt for duplicate homes, derivable-but-
82
+ stored facts, reinvented wheels, and speculative complexity. Each is a finding.
@@ -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 |
@@ -76,12 +91,28 @@ TODO/FIXME/HACK/XXX comments that have aged.
76
91
  ## 2g. Duplication
77
92
 
78
93
  **How to scan:**
79
- - Read `.grimoire/docs/.snapshot.json` `duplicates` section if present
80
- - Or run `config.tools.duplicates` if configured (e.g., jscpd)
94
+ - Run `config.tools.duplicates` if configured (e.g., jscpd), or `grimoire health` (config-driven `duplicates` metric)
95
+ - Plus semantic clones via `search_graph(semantic_query=[...])` (requires codebase-memory-mcp) to catch re-implementations under different names
81
96
  - Group by area — within-area dupes are easy to consolidate
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.