@lvlup-sw/axiom 0.2.2 → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiom",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Backend code quality skills for Claude Code. Six skills that audit, critique, harden, and simplify your architecture — the backend half of impeccable.",
5
5
  "author": {
6
6
  "name": "LevelUp Software"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvlup-sw/axiom",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
4
4
  "description": "Backend code quality skills for Claude Code",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -108,6 +108,30 @@ Each check has: ID, pattern, what it detects, severity, and false-positive guida
108
108
 
109
109
  ## DIM-6: Architecture
110
110
 
111
+ ### T-6.1a: Deep nesting
112
+ - **Pattern:** Count indentation levels per function; flag functions with >3 levels of nesting
113
+ - **Severity:** MEDIUM
114
+ - **Detects:** Functions where business logic is buried under layers of conditional checks
115
+ - **False positives:** Nesting from resource scoping (`try`/`with` blocks) or framework-required patterns
116
+
117
+ ### T-6.1b: Long functions
118
+ - **Pattern:** Functions exceeding 50 lines (measured from signature to closing brace)
119
+ - **Severity:** MEDIUM
120
+ - **Detects:** Functions doing too much — multiple concerns in a single body
121
+ - **False positives:** Functions with long but simple data declarations (mapping tables, configuration objects)
122
+
123
+ ### T-6.1c: Long parameter lists
124
+ - **Pattern:** Functions with more than 4 parameters in the signature
125
+ - **Severity:** MEDIUM
126
+ - **Detects:** Functions with too many inputs, suggesting they handle multiple concerns or need an options object
127
+ - **False positives:** Constructor functions in DI-heavy codebases where parameters are injected dependencies; callback-heavy APIs where signature is dictated by framework
128
+
129
+ ### T-6.1d: Deep inheritance
130
+ - **Pattern:** `class\s+\w+\s+extends\s+` — flag when inheritance chain exceeds 2 levels
131
+ - **Severity:** MEDIUM
132
+ - **Detects:** Class hierarchies that would be simpler as composition
133
+ - **False positives:** Framework-required inheritance (e.g., extending base controller classes); sealed hierarchies with exhaustive matching
134
+
111
135
  ### T-6.1: Circular imports
112
136
  - **Pattern:** Build import graph; detect cycles
113
137
  - **Severity:** HIGH
@@ -100,7 +100,17 @@ Identify modules with too many responsibilities:
100
100
  - Classes or modules that are modified in every feature branch (shotgun surgery indicator)
101
101
  - Modules that import from many unrelated domains
102
102
 
103
- #### 3e. Circular Dependency Identification
103
+ #### 3e. Readability Patterns
104
+
105
+ Evaluate code-level readability and structural clarity:
106
+
107
+ - **DRY violations:** Duplicated logic across multiple locations that should be extracted
108
+ - **Deep nesting:** Functions with excessive indentation that could use guard clauses / early returns
109
+ - **Composition over inheritance:** Class hierarchies that would be simpler as composed components
110
+ - **Parameter discipline:** Long parameter lists and boolean flags that obscure call-site meaning
111
+ - For definitions, violation signals, and refactoring approaches, see `@skills/critique/references/readability-patterns.md`
112
+
113
+ #### 3f. Circular Dependency Identification
104
114
 
105
115
  Detect import cycles between modules:
106
116
 
@@ -130,3 +140,4 @@ Format all findings per `@skills/backend-quality/references/findings-format.md`:
130
140
  - `@skills/backend-quality/references/findings-format.md` — Standard output format for findings
131
141
  - `@skills/critique/references/solid-principles.md` — SOLID principle definitions, violation signals, severity guide, and detection heuristics
132
142
  - `@skills/critique/references/dependency-patterns.md` — Dependency pattern catalog, coupling metrics, circular dependency detection, and layered architecture guidance
143
+ - `@skills/critique/references/readability-patterns.md` — DRY, early returns, composition over inheritance, and parameter discipline
@@ -0,0 +1,277 @@
1
+ # Readability Patterns Reference
2
+
3
+ Detection heuristics and severity guidance for code readability concerns beyond SOLID. These patterns address structural clarity at the function and expression level.
4
+
5
+ ---
6
+
7
+ ## DRY — Don't Repeat Yourself
8
+
9
+ ### Definition
10
+
11
+ Every piece of knowledge should have a single, authoritative representation in the system. When the same logic appears in multiple places, a change to that logic requires finding and updating every copy — and missing one creates a bug.
12
+
13
+ ### Violation Signals
14
+
15
+ 1. Two or more functions with near-identical bodies differing only in variable names or literals
16
+ 2. Copy-pasted conditional chains (same `if/else` structure with different field names)
17
+ 3. Parallel data transformations that follow the same pattern but aren't abstracted
18
+ 4. Test setup code duplicated across multiple test files without extraction into a shared fixture
19
+ 5. Configuration or mapping objects repeated in multiple modules instead of imported from one source
20
+
21
+ ### When Duplication Is Acceptable
22
+
23
+ Not all duplication violates DRY. Apply the **three uses rule** (see `@skills/distill/references/simplification-guide.md`):
24
+
25
+ - **First occurrence:** Write it inline. Do not extract.
26
+ - **Second occurrence:** Note the duplication but tolerate it. The pattern may be coincidental.
27
+ - **Third occurrence:** Extract. You now have enough examples to see the true shape of the abstraction.
28
+
29
+ **Also acceptable:**
30
+ - Test assertions that happen to look similar but test different behaviors — readability in tests often outweighs DRY
31
+ - Protocol handlers or route definitions that share structure but represent distinct endpoints — premature abstraction here creates "magic" dispatchers that are harder to understand
32
+ - Two-line utility patterns (e.g., null-check-and-return) — the abstraction would be longer than the duplication
33
+
34
+ ### Severity Guide
35
+
36
+ | Severity | When to assign |
37
+ |----------|---------------|
38
+ | **HIGH** | Duplicated business logic where a bug fix applied to one copy but not the other would cause incorrect behavior. |
39
+ | **MEDIUM** | Duplicated structural patterns (>10 lines) across 3+ locations. The abstraction is clear but hasn't been extracted. |
40
+ | **LOW** | Minor duplication (<10 lines) in 2 locations. Extraction would add more complexity than it removes. |
41
+
42
+ ### Detection Heuristics
43
+
44
+ - Look for functions with the same control flow structure but different variable names
45
+ - Search for identical error handling blocks across different catch clauses
46
+ - Check for repeated object construction patterns (same keys, different values)
47
+ - Compare test files for duplicated setup/teardown logic
48
+
49
+ ---
50
+
51
+ ## Early Returns and Guard Clauses
52
+
53
+ ### Definition
54
+
55
+ Guard clauses handle edge cases and preconditions at the top of a function, returning or throwing early. This eliminates nesting and keeps the main logic path at the lowest indentation level.
56
+
57
+ ### The Problem with Deep Nesting
58
+
59
+ ```typescript
60
+ // Deep nesting — hard to follow
61
+ function processOrder(order: Order): Result {
62
+ if (order) {
63
+ if (order.items.length > 0) {
64
+ if (order.customer) {
65
+ if (order.customer.isActive) {
66
+ // ... actual business logic buried at 4 levels deep
67
+ const total = calculateTotal(order.items)
68
+ return { success: true, total }
69
+ } else {
70
+ return { success: false, error: 'Inactive customer' }
71
+ }
72
+ } else {
73
+ return { success: false, error: 'No customer' }
74
+ }
75
+ } else {
76
+ return { success: false, error: 'Empty order' }
77
+ }
78
+ } else {
79
+ return { success: false, error: 'No order' }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ```typescript
85
+ // Guard clauses — preconditions handled upfront, main path is flat
86
+ function processOrder(order: Order): Result {
87
+ if (!order) return { success: false, error: 'No order' }
88
+ if (order.items.length === 0) return { success: false, error: 'Empty order' }
89
+ if (!order.customer) return { success: false, error: 'No customer' }
90
+ if (!order.customer.isActive) return { success: false, error: 'Inactive customer' }
91
+
92
+ const total = calculateTotal(order.items)
93
+ return { success: true, total }
94
+ }
95
+ ```
96
+
97
+ ### Violation Signals
98
+
99
+ 1. Functions with more than 3 levels of nesting
100
+ 2. `else` branches that contain only a return or throw (invert the condition and return early)
101
+ 3. Long `if` blocks followed by a short `else { return }` — the return should come first
102
+ 4. Nested `if` chains where each level checks a single condition (collapse into sequential guards)
103
+ 5. Functions where the main logic is indented 3+ levels deep
104
+
105
+ ### When to Keep Nesting
106
+
107
+ - **Mutually exclusive branches with equal weight:** When both branches contain substantial logic (not just a return), `if/else` is clearer than early return
108
+ - **Loop bodies with `continue`:** Using `continue` as a guard clause inside loops is the same pattern and equally valid, but excessive `continue` statements can fragment loop logic
109
+ - **Transaction scoping:** When nesting represents a resource scope (e.g., `try` blocks wrapping database transactions), the nesting communicates the scope boundary
110
+
111
+ ### Severity Guide
112
+
113
+ | Severity | When to assign |
114
+ |----------|---------------|
115
+ | **HIGH** | Function with 5+ levels of nesting. Main business logic is buried and hard to trace. |
116
+ | **MEDIUM** | Function with 3-4 levels of nesting that could be flattened with guard clauses. |
117
+ | **LOW** | Occasional unnecessary `else` after a return statement. Cosmetic but worth noting. |
118
+
119
+ ### Detection Heuristics
120
+
121
+ - Measure maximum indentation depth per function — flag functions exceeding 3 levels
122
+ - Search for `else { return` or `else { throw` patterns — these can almost always be inverted
123
+ - Look for `if (condition) { ... long block ... } else { return }` — invert the condition
124
+ - Count the ratio of guard-eligible checks to actual guards in functions with preconditions
125
+
126
+ ---
127
+
128
+ ## Composition Over Inheritance
129
+
130
+ ### Definition
131
+
132
+ Favor assembling behavior from small, focused components rather than building deep class hierarchies. Inheritance creates tight coupling between parent and child classes; composition allows flexible recombination.
133
+
134
+ ### Why Inheritance Creates Problems
135
+
136
+ - **Fragile base class:** Changes to the parent class ripple into all children, even when irrelevant to their specific behavior
137
+ - **Forced inheritance of unwanted behavior:** Subclasses inherit all parent methods, even ones that don't apply (ISP violation in class form)
138
+ - **Diamond problem:** Multiple inheritance paths create ambiguity about which parent's behavior to use
139
+ - **Deep hierarchies obscure behavior:** Understanding what a class does requires reading the entire chain from root to leaf
140
+
141
+ ### Violation Signals
142
+
143
+ 1. Class hierarchies deeper than 2 levels (grandchild classes)
144
+ 2. Subclasses that override >50% of parent methods (the "is-a" relationship is wrong)
145
+ 3. Abstract base classes with only one concrete implementation (premature abstraction)
146
+ 4. Classes that extend a base class only to access a utility method (use composition or a standalone function)
147
+ 5. `super` calls that require understanding the parent's implementation details (leaky inheritance)
148
+ 6. "Template method" patterns where the base class controls flow and subclasses fill in hooks — often better expressed as a function accepting strategy callbacks
149
+
150
+ ### Composition Alternatives
151
+
152
+ **Instead of inheritance for shared behavior:**
153
+ ```typescript
154
+ // Inheritance approach — tight coupling
155
+ class BaseRepository {
156
+ async findById(id: string) { /* shared query logic */ }
157
+ async save(entity: unknown) { /* shared persistence logic */ }
158
+ }
159
+ class UserRepository extends BaseRepository {
160
+ async findByEmail(email: string) { /* user-specific */ }
161
+ }
162
+ ```
163
+
164
+ ```typescript
165
+ // Composition approach — flexible, testable
166
+ function createRepository(tableName: string) {
167
+ return {
168
+ findById: (id: string) => query(tableName, { id }),
169
+ save: (entity: unknown) => upsert(tableName, entity),
170
+ }
171
+ }
172
+
173
+ function createUserRepository() {
174
+ const base = createRepository('users')
175
+ return {
176
+ ...base,
177
+ findByEmail: (email: string) => query('users', { email }),
178
+ }
179
+ }
180
+ ```
181
+
182
+ **Instead of inheritance for variation:**
183
+ ```typescript
184
+ // Strategy pattern via composition
185
+ interface PricingStrategy {
186
+ calculate(items: Item[]): number
187
+ }
188
+
189
+ const standardPricing: PricingStrategy = {
190
+ calculate: (items) => items.reduce((sum, i) => sum + i.price, 0)
191
+ }
192
+
193
+ const discountPricing: PricingStrategy = {
194
+ calculate: (items) => items.reduce((sum, i) => sum + i.price * 0.9, 0)
195
+ }
196
+
197
+ // No inheritance needed — strategies are interchangeable values
198
+ function checkout(items: Item[], pricing: PricingStrategy) {
199
+ return pricing.calculate(items)
200
+ }
201
+ ```
202
+
203
+ ### When Inheritance Is Appropriate
204
+
205
+ - **Framework requirements:** When a framework requires extending a base class (e.g., React class components, some ORM patterns). Use inheritance because the framework demands it, not by choice.
206
+ - **True "is-a" relationships with shared state:** When the parent class manages state that all subclasses genuinely need, and subclasses extend rather than override behavior.
207
+ - **Sealed hierarchies:** When the set of subclasses is closed and known (e.g., AST node types, event types). Inheritance + exhaustive pattern matching is a valid design.
208
+
209
+ ### Severity Guide
210
+
211
+ | Severity | When to assign |
212
+ |----------|---------------|
213
+ | **HIGH** | Class hierarchy 3+ levels deep where subclasses override most parent behavior. The hierarchy is fighting against itself. |
214
+ | **MEDIUM** | Abstract base class with one implementation, or base class used primarily as a bag of utility methods. Composition would be simpler. |
215
+ | **LOW** | Shallow inheritance (1 level) that works but could be expressed as composition. Not causing active problems. |
216
+
217
+ ### Detection Heuristics
218
+
219
+ - Count `extends` depth — flag hierarchies deeper than 2 levels
220
+ - Look for abstract classes with exactly one concrete subclass
221
+ - Search for `super.` calls in methods that also override the parent method — sign of fragile coupling
222
+ - Check if subclasses use <50% of inherited methods — the inheritance may be for convenience, not design
223
+ - Look for classes that extend a base class and immediately override the constructor to do something unrelated
224
+
225
+ ---
226
+
227
+ ## Parameter Discipline
228
+
229
+ ### Long Parameter Lists
230
+
231
+ Functions with many parameters are hard to call correctly. The caller must remember the order, and boolean flags in the middle of a parameter list are especially error-prone.
232
+
233
+ **Violation signals:**
234
+ - Functions with more than 4 parameters
235
+ - Boolean parameters that aren't self-documenting at the call site
236
+ - Parameters that are always passed together (should be grouped into an object)
237
+
238
+ **Refactoring approaches:**
239
+ ```typescript
240
+ // Before: positional parameters, unclear at call site
241
+ function createUser(name: string, email: string, isAdmin: boolean,
242
+ sendWelcome: boolean, teamId: string | null) { ... }
243
+
244
+ createUser('Alice', 'a@b.com', true, false, 'team-1') // What do true, false mean?
245
+ ```
246
+
247
+ ```typescript
248
+ // After: options object, self-documenting
249
+ interface CreateUserOptions {
250
+ name: string
251
+ email: string
252
+ isAdmin?: boolean
253
+ sendWelcome?: boolean
254
+ teamId?: string
255
+ }
256
+
257
+ function createUser(options: CreateUserOptions) { ... }
258
+
259
+ createUser({ name: 'Alice', email: 'a@b.com', isAdmin: true, teamId: 'team-1' })
260
+ ```
261
+
262
+ ### Boolean Parameters
263
+
264
+ Boolean parameters are a special case of poor readability. At the call site, `true` and `false` carry no meaning without reading the function signature.
265
+
266
+ **Alternatives:**
267
+ - Use an options object (as above)
268
+ - Use separate functions: `enableFeature()` / `disableFeature()` instead of `setFeature(enabled: boolean)`
269
+ - Use string literals or enums: `format('compact')` instead of `format(true)`
270
+
271
+ ### Severity Guide
272
+
273
+ | Severity | When to assign |
274
+ |----------|---------------|
275
+ | **HIGH** | Function with 6+ parameters, especially if multiple are booleans. Call sites are unreadable. |
276
+ | **MEDIUM** | Function with 4-5 parameters where grouping into an object would improve clarity. |
277
+ | **LOW** | Function with a boolean parameter that could be more expressive but is only called in 1-2 places. |
@@ -36,10 +36,54 @@ Reference guide for reducing code complexity, identifying vestigial patterns, an
36
36
  ### Reducing Conditional Complexity
37
37
 
38
38
  - **Collapse nested conditionals:** Replace `if (a) { if (b) { ... } }` with `if (a && b) { ... }` when the nesting adds no clarity
39
- - **Use early returns:** Convert deep nesting into guard clauses that return/throw early
40
39
  - **Replace flag variables:** When a boolean flag is set and then checked once, inline the condition
41
40
  - **Simplify boolean expressions:** `if (x === true)` becomes `if (x)`; `if (!x === false)` becomes `if (x)`
42
41
 
42
+ #### Early Returns and Guard Clauses
43
+
44
+ Convert deep nesting into guard clauses that return or throw early. This is one of the highest-impact readability improvements available.
45
+
46
+ **The principle:** Handle preconditions and edge cases at the top of the function, then let the main logic flow at the lowest indentation level.
47
+
48
+ **Before — nested conditionals:**
49
+ ```typescript
50
+ function getDiscount(customer: Customer): number {
51
+ if (customer) {
52
+ if (customer.isActive) {
53
+ if (customer.orders > 10) {
54
+ return 0.15
55
+ } else {
56
+ return 0.05
57
+ }
58
+ } else {
59
+ return 0
60
+ }
61
+ } else {
62
+ throw new Error('Customer required')
63
+ }
64
+ }
65
+ ```
66
+
67
+ **After — guard clauses:**
68
+ ```typescript
69
+ function getDiscount(customer: Customer): number {
70
+ if (!customer) throw new Error('Customer required')
71
+ if (!customer.isActive) return 0
72
+
73
+ return customer.orders > 10 ? 0.15 : 0.05
74
+ }
75
+ ```
76
+
77
+ **Patterns to look for:**
78
+ - `if (x) { ... long block ... } else { return y }` — invert: `if (!x) return y; ... long block ...`
79
+ - `if (x) { if (y) { if (z) { ... } } }` — flatten: `if (!x) return; if (!y) return; if (!z) return; ...`
80
+ - `else` after a `return` or `throw` — the `else` is unnecessary; remove it and dedent
81
+
82
+ **When not to flatten:**
83
+ - Both branches have substantial logic (not just returns) — `if/else` is clearer
84
+ - The nesting represents resource scoping (e.g., transaction boundaries)
85
+ - The conditions are not preconditions but genuinely branching logic paths
86
+
43
87
  ## Vestigial Pattern Identification
44
88
 
45
89
  ### Code Archaeology Approach