📦 deps(skills): sync thirdparty skills
This commit is contained in:
Vendored
+240
@@ -0,0 +1,240 @@
|
||||
# Brooks-Lint — Shared Framework
|
||||
|
||||
Code and test quality diagnosis using principles from twelve classic software engineering books.
|
||||
Use `source-coverage.md` to keep those sources grounded in real evidence, exceptions, and tradeoffs.
|
||||
|
||||
## The Iron Law
|
||||
|
||||
```
|
||||
NEVER suggest fixes before completing risk diagnosis.
|
||||
EVERY finding must follow: Symptom → Source → Consequence → Remedy.
|
||||
```
|
||||
|
||||
Violating this law produces reviews that list rule violations without explaining why they
|
||||
matter. A finding without a consequence and a remedy is not a finding — it is noise.
|
||||
|
||||
> **On-demand sections (skip unless the condition applies):**
|
||||
> - "Remedy Mode" — only when user passes `--fix` or asks to fix findings
|
||||
> - "Post-Report Triage" — only in interactive sessions after the report is output
|
||||
> - "History Tracking" — only after the Health Score is computed
|
||||
|
||||
## Project Config
|
||||
|
||||
Before executing the review, attempt to read `.brooks-lint.yaml` from the project root.
|
||||
If the file exists, parse and apply its settings before proceeding.
|
||||
If the file does not exist, continue with defaults (all risks enabled, no ignores).
|
||||
|
||||
In a multi-mode session, re-read only if the user says the config has changed.
|
||||
|
||||
### Supported settings
|
||||
|
||||
**`disable`** — list of risk codes to skip entirely. Findings for disabled risks are
|
||||
silently omitted from the report and do not affect the Health Score.
|
||||
Valid codes: `R1` `R2` `R3` `R4` `R5` `R6` `T1` `T2` `T3` `T4` `T5` `T6`
|
||||
|
||||
**`severity`** — override the severity of a specific risk for this project.
|
||||
Valid values: `critical` `warning` `suggestion`
|
||||
Example: `R1: suggestion` means every R1 finding is downgraded to Suggestion regardless
|
||||
of what the guide says.
|
||||
|
||||
**`ignore`** — list of glob patterns. Files matching any pattern are excluded from
|
||||
analysis. Findings that arise solely from ignored files are omitted.
|
||||
Common entries: `**/*.generated.*`, `**/vendor/**`, `**/migrations/**`
|
||||
|
||||
**`focus`** — non-empty list of risk codes to evaluate; all others are skipped.
|
||||
Omit this key (or leave it empty) to evaluate all non-disabled risks.
|
||||
Cannot be combined with a non-empty `disable` list.
|
||||
|
||||
**Minimal example:**
|
||||
```yaml
|
||||
version: 1
|
||||
disable:
|
||||
- T5
|
||||
severity:
|
||||
R1: suggestion
|
||||
ignore:
|
||||
- "**/*.generated.*"
|
||||
```
|
||||
|
||||
If `.brooks-lint.yaml` contains a `custom_risks` map, read `custom-risks-guide.md`
|
||||
from the `_shared/` directory for loading and scanning instructions.
|
||||
|
||||
### Config Validation
|
||||
|
||||
Before applying, check for errors and mention each in the report:
|
||||
- Invalid risk code (not R1–R6, T1–T6, or a defined `Cx` code): skip it, note `"Config warning: X is not a valid risk code"`
|
||||
- Invalid severity value (not `critical`/`warning`/`suggestion`): skip it, note the error
|
||||
- Both `disable` and `focus` are non-empty: treat as a config error, ignore both, note it
|
||||
|
||||
If the YAML fails to parse entirely, skip config loading and proceed with defaults.
|
||||
|
||||
### Config Reporting
|
||||
|
||||
If a config file was found and applied, add this line immediately after the **Scope** line
|
||||
in the report:
|
||||
`Config: .brooks-lint.yaml applied (N risks disabled, M paths ignored)`
|
||||
|
||||
Include N and M even if zero. Omit this line if no config file was found.
|
||||
|
||||
---
|
||||
|
||||
## Auto Scope Detection
|
||||
|
||||
When no files or code are specified, detect scope automatically:
|
||||
|
||||
**PR Review:** `git diff --cached` → `git diff` → `git diff main...HEAD` → ask user.
|
||||
|
||||
**Architecture Audit / Tech Debt:** Entire project by default. `--since=<ref>`: run `git diff <ref>...HEAD --name-only`, analyze only modules containing changed files; note "Incremental audit — modules touched since <ref>".
|
||||
|
||||
**Test Quality:** All test files by default. If a diff exists, prioritize test files co-located with changed production files (`src/foo.ts` → `src/foo.test.ts`).
|
||||
|
||||
**Health Dashboard:** Entire project by default. If user provides a path, scope all dimension sub-scans to that path.
|
||||
|
||||
**Scope line:** Always state what was detected — e.g., `Scope: staged changes (3 files)` or `Scope: branch changes vs main (12 files)`.
|
||||
|
||||
---
|
||||
|
||||
## The Six Decay Risks
|
||||
|
||||
Navigation index only — canonical definitions (symptoms, severity guides, sources, "What Not
|
||||
to Flag" guards) live in `decay-risks.md`. Do not duplicate or edit diagnostic questions here;
|
||||
update `decay-risks.md` directly. Book-level coverage, exceptions, and tradeoffs are in
|
||||
`source-coverage.md`.
|
||||
|
||||
| Risk | Diagnostic Question |
|
||||
|------|---------------------|
|
||||
| Cognitive Overload | How much mental effort to understand this? |
|
||||
| Change Propagation | How many unrelated things break on one change? |
|
||||
| Knowledge Duplication | Is the same decision expressed in multiple places? |
|
||||
| Accidental Complexity | Is the code more complex than the problem? |
|
||||
| Dependency Disorder | Do dependencies flow in a consistent direction? |
|
||||
| Domain Model Distortion | Does the code faithfully represent the domain? |
|
||||
|
||||
---
|
||||
|
||||
## Report Template
|
||||
|
||||
**Language rule:** Output the report in the same language the user is using. Translate the
|
||||
per-finding content and the one-sentence verdict to match the user's language. Keep the
|
||||
following in English: Iron Law field labels (Symptom / Source / Consequence / Remedy),
|
||||
book titles, principle and smell names (e.g. "Shotgun Surgery", "Divergent Change"),
|
||||
and fixed structural headers from the template below (`Findings`, `Summary`,
|
||||
`Module Dependency Graph`, `Critical`, `Warning`, `Suggestion`).
|
||||
|
||||
````
|
||||
# Brooks-Lint Review
|
||||
|
||||
**Mode:** [PR Review / Architecture Audit / Tech Debt Assessment / Test Quality Review]
|
||||
**Scope:** [file(s), directory, or description of what was reviewed]
|
||||
**Health Score:** XX/100
|
||||
|
||||
[One sentence overall verdict]
|
||||
|
||||
---
|
||||
|
||||
## Module Dependency Graph
|
||||
|
||||
<!-- Mode 2 (Architecture Audit) ONLY — omit this section for other modes -->
|
||||
<!-- classDef colors: see architecture-guide.md Step 1 Rule 6 -->
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
<!-- Sort all findings by severity: Critical first, then Warning, then Suggestion -->
|
||||
<!-- If no findings in a severity tier, omit that tier's heading -->
|
||||
|
||||
### 🔴 Critical
|
||||
|
||||
**[Risk Name] — [Short descriptive title]**
|
||||
Symptom: [exactly what was observed in the code]
|
||||
Source: [Book title — Principle or Smell name]
|
||||
Consequence: [what breaks or gets worse if this is not fixed]
|
||||
Remedy: [concrete, specific action]
|
||||
|
||||
### 🟡 Warning
|
||||
|
||||
**[Risk Name] — [Short descriptive title]**
|
||||
Symptom: ...
|
||||
Source: ...
|
||||
Consequence: ...
|
||||
Remedy: ...
|
||||
|
||||
### 🟢 Suggestion
|
||||
|
||||
**[Risk Name] — [Short descriptive title]**
|
||||
Symptom: ...
|
||||
Source: ...
|
||||
Consequence: ...
|
||||
Remedy: ...
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
[2–3 sentences: what is the most important action, and what is the overall trend]
|
||||
````
|
||||
|
||||
## Remedy Mode
|
||||
|
||||
When the user passes `--fix` or asks to "fix the findings", read
|
||||
`remedy-guide.md` from the `_shared/` directory before writing the report.
|
||||
|
||||
## Health Score Calculation
|
||||
|
||||
Base score: 100
|
||||
Deductions:
|
||||
- Each 🔴 Critical finding: −15
|
||||
- Each 🟡 Warning finding: −5
|
||||
- Each 🟢 Suggestion finding: −1
|
||||
Floor: 0 (score cannot go below 0)
|
||||
|
||||
## History Tracking
|
||||
|
||||
After generating the Health Score, attempt to append a record to `.brooks-lint-history.json`
|
||||
in the project root.
|
||||
|
||||
**Append logic:**
|
||||
1. Read the file (or start with empty array if it doesn't exist)
|
||||
2. Append: `{ date, mode, score, findings: { critical, warning, suggestion }, scope }`
|
||||
3. Write the file back
|
||||
|
||||
**Trend display:** If the history file exists and contains at least one prior record for
|
||||
the same mode, add a Trend line after the Health Score in the report:
|
||||
|
||||
**Trend:** 85 → 82 (−3) over last 3 runs
|
||||
|
||||
Show the most recent prior score and the delta. If delta is 0: "Stable at 82".
|
||||
If this is the first run for this mode: "First run — no trend data".
|
||||
|
||||
## Post-Report Triage (Optional)
|
||||
|
||||
**Guard:** Interactive sessions only — skip in CI/headless mode.
|
||||
|
||||
After reporting Warning or Suggestion findings, offer:
|
||||
> Would you like to triage these findings? (accept / dismiss / defer / skip)
|
||||
|
||||
For each finding one at a time (lowest severity first): show title, ask `[a]ccept / [d]ismiss / [f]defer / [s]kip`; wait for reply before moving to the next.
|
||||
|
||||
**Dismiss:** ask one-line reason → append to `.brooks-lint.yaml` under `suppress:` → downgraded to info in future runs.
|
||||
|
||||
**Defer:** same as dismiss, add `expires: YYYY-MM-DD` (default 90 days) → resurfaces at original severity after expiry.
|
||||
|
||||
**Suppress matching at scan time:** for each `suppress:` entry, match `risk` code and file `pattern` against findings.
|
||||
- Both match → downgrade to info (not counted in Health Score, shown under collapsed "Suppressed" section).
|
||||
- `expires` is past → ignore entry, finding resurfaces. Note in Summary: "N suppressed findings have expired and are now active again."
|
||||
|
||||
## Reference Files
|
||||
|
||||
Read on demand:
|
||||
|
||||
| File | When to Read |
|
||||
|------|-------------|
|
||||
| `source-coverage.md` | At the start of every review, before writing findings |
|
||||
| `decay-risks.md` | Before any production-code review or architecture/debt assessment |
|
||||
| `test-decay-risks.md` | Before any test review and before the PR Review "Quick Test Check" step |
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Custom Risk Loading Guide
|
||||
|
||||
When `.brooks-lint.yaml` contains a `custom_risks` map, this guide governs how those
|
||||
risks are loaded and scanned. Custom risks use `Cx` codes (C1, C2, …) — no conflict with
|
||||
the standard R1–R6 and T1–T6 namespaces.
|
||||
|
||||
---
|
||||
|
||||
## Loading
|
||||
|
||||
1. For each entry in `custom_risks`, validate that it has:
|
||||
- `name` — non-empty string
|
||||
- `question` — the diagnostic question to ask
|
||||
- `symptoms` — non-empty list of symptom patterns
|
||||
- `severity` — map with at least one of: `critical`, `warning`, `suggestion`
|
||||
|
||||
2. Register each valid entry as a `Cx` code alongside R1–R6 / T1–T6. Once loaded,
|
||||
`Cx` codes become valid targets for `disable`, `focus`, and `severity` fields in
|
||||
the same config file.
|
||||
|
||||
3. Report any validation errors as config warnings (do not abort the review):
|
||||
- Missing required field: `"Config warning: C1 missing 'symptoms'"`
|
||||
- Invalid code format (must be `C` followed by digits): skip, note error
|
||||
- Code conflicts with R/T namespace: skip, note error
|
||||
|
||||
---
|
||||
|
||||
## Scanning
|
||||
|
||||
During the analysis, treat each custom risk as an additional step after the standard
|
||||
process:
|
||||
|
||||
- Use `question` as the diagnostic question
|
||||
- Use `symptoms` as the symptom lookup list
|
||||
- Use the `severity` map for tier classification
|
||||
- Apply the Iron Law: `Source` field should be `"[Project-defined risk] — <risk name>"`
|
||||
- Include custom risk findings in the Health Score (same deduction rules as R/T codes)
|
||||
- In the report, custom findings appear after standard findings under a
|
||||
**### Project-Specific Risks** sub-heading
|
||||
|
||||
---
|
||||
|
||||
## Config Validation additions
|
||||
|
||||
The following codes are valid in `disable`, `focus`, and `severity`:
|
||||
- Standard: `R1`–`R6`, `T1`–`T6`
|
||||
- Custom: any `Cx` code defined in `custom_risks`
|
||||
- Any other code: skip it and emit `"Config warning: X is not a valid risk code"`
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
# Decay Risk Reference
|
||||
|
||||
Six patterns that cause software to degrade. Apply the Iron Law to each finding.
|
||||
|
||||
---
|
||||
|
||||
## Risk 1: Cognitive Overload
|
||||
|
||||
**Diagnostic question:** How much mental effort does a human need to understand this?
|
||||
|
||||
Cognitive load beyond working memory causes mistakes, avoidance, and blocks the refactoring that would fix it.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Function longer than 20 lines where multiple levels of abstraction are mixed together
|
||||
- Nesting depth greater than 3 levels
|
||||
- Parameter list with more than 4 parameters
|
||||
- Magic numbers or unexplained constants
|
||||
- Variable names that require reading the implementation to understand (e.g., `d`, `tmp2`, `flag`)
|
||||
- Boolean expressions with 3 or more conditions combined
|
||||
- Train-wreck chains: `a.getB().getC().doD()`
|
||||
- Code names that do not match what the business calls the same concept
|
||||
- Flag Arguments: a boolean parameter that makes the function do two fundamentally different
|
||||
things depending on its value — a sign the function has two responsibilities
|
||||
- Primitive Obsession: domain concepts represented as primitive types (`String email`,
|
||||
`int orderId`, `double money`) rather than purpose-built value types — forces callers to know
|
||||
which string is an email and which is a name
|
||||
- Shallow module: the interface or documentation of a component is more complex relative to
|
||||
the functionality it provides
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Long Method | Fowler — Refactoring | Long Method |
|
||||
| Long Parameter List | Fowler — Refactoring | Long Parameter List |
|
||||
| Message Chains | Fowler — Refactoring | Message Chains |
|
||||
| Flag Arguments | Fowler — Refactoring | Flag Arguments |
|
||||
| Primitive Obsession | Fowler — Refactoring | Primitive Obsession |
|
||||
| Function length and nesting | McConnell — Code Complete | Ch. 7: High-Quality Routines |
|
||||
| Variable naming | McConnell — Code Complete | Ch. 11: The Power of Variable Names |
|
||||
| Magic numbers | McConnell — Code Complete | Ch. 12: Fundamental Data Types |
|
||||
| Domain name mismatch | Evans — Domain-Driven Design | Ubiquitous Language |
|
||||
| Shallow Module | Ousterhout — A Philosophy of Software Design | Ch. 4: Modules Should Be Deep |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: function > 50 lines, nesting > 5, or virtually no meaningful names
|
||||
- 🟡 Warning: function 20–50 lines, nesting 4–5, some unclear names
|
||||
- 🟢 Suggestion: minor naming issues, 1–2 magic numbers, isolated train-wreck chains
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- Linear code with clear names and guard clauses is not automatically high cognitive load
|
||||
- Internal implementation detail hidden behind a deep, simple module boundary is not a shallow-module problem
|
||||
- Domain-specific terminology should not be flagged if it matches how experts actually speak
|
||||
|
||||
---
|
||||
|
||||
## Risk 2: Change Propagation
|
||||
|
||||
**Diagnostic question:** How many unrelated things break when you change one thing?
|
||||
|
||||
Each change ripples to unrelated modules, slowing velocity and multiplying regression risk.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Modifying one feature requires touching more than 3 files in unrelated modules
|
||||
- One class changes for multiple different business reasons (e.g., `UserService` changes for
|
||||
billing logic AND notification logic AND profile logic)
|
||||
- A method uses more data from another class than from its own class
|
||||
- Two classes know each other's internal state directly
|
||||
- Changing one module requires recompiling or retesting many unrelated modules
|
||||
- **Hyrum's Law**: with sufficient callers, every observable behavior — including
|
||||
implementation details, error message text, coincidental call ordering, and undocumented
|
||||
side effects — becomes an implicit contract that callers depend on, even though it was
|
||||
never guaranteed by the declared API
|
||||
- **Orthogonality violation**: changing one dimension of a feature forces edits in
|
||||
unrelated dimensions — adding a new payment type should not require touching logging,
|
||||
caching, or notification code, but in a non-orthogonal design it does
|
||||
- Information Leakage: a design decision (e.g., a file format, protocol detail, or data
|
||||
shape) is encoded in more than one module, so changing it requires coordinated edits
|
||||
in multiple places even though only one module "owns" the concept
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Shotgun Surgery | Fowler — Refactoring | Shotgun Surgery |
|
||||
| Divergent Change | Fowler — Refactoring | Divergent Change |
|
||||
| Feature Envy | Fowler — Refactoring | Feature Envy |
|
||||
| Inappropriate Intimacy | Fowler — Refactoring | Inappropriate Intimacy |
|
||||
| Orthogonality violation | Hunt & Thomas — The Pragmatic Programmer | Ch. 2: Orthogonality |
|
||||
| DIP violation | Martin — Clean Architecture | Dependency Inversion Principle |
|
||||
| High change propagation radius | Brooks — The Mythical Man-Month | Ch. 2: Brooks's Law (communication overhead) |
|
||||
| Hyrum's Law | Winters et al. — Software Engineering at Google | Ch. 1: Hyrum's Law |
|
||||
| Information Leakage | Ousterhout — A Philosophy of Software Design | Ch. 5: Information Hiding and Leakage |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: one change touches > 5 files, or there is a structural dependency inversion (domain depends on infrastructure)
|
||||
- 🟡 Warning: one change touches 3–5 files, mild coupling between modules
|
||||
- 🟢 Suggestion: minor coupling, easily isolatable
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- A composition root wiring concrete dependencies is not a DIP violation by itself
|
||||
- A stable public API with intentionally supported behavior is not automatically Hyrum's Law debt
|
||||
- Similar edits inside one bounded context may be normal coordinated change, not shotgun surgery
|
||||
|
||||
---
|
||||
|
||||
## Risk 3: Knowledge Duplication
|
||||
|
||||
**Diagnostic question:** Is the same decision expressed in more than one place?
|
||||
|
||||
Multiple copies drift apart silently. DRY is about decisions, not code lines.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Same logic copy-pasted across multiple files or functions
|
||||
- Same concept named differently in different parts of the codebase
|
||||
(e.g., `user`, `account`, `member`, `customer` all referring to the same domain entity)
|
||||
- Parallel class hierarchies that must change in sync
|
||||
(e.g., adding a new payment type requires adding a class in 3 different hierarchies)
|
||||
- Configuration values repeated as literals in multiple places
|
||||
- Two modules that implement the same algorithm independently
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Code duplication | Fowler — Refactoring | Duplicate Code |
|
||||
| Parallel Inheritance | Fowler — Refactoring | Parallel Inheritance Hierarchies |
|
||||
| DRY violation | Hunt & Thomas — The Pragmatic Programmer | DRY: Don't Repeat Yourself |
|
||||
| Inconsistent naming | Evans — Domain-Driven Design | Ubiquitous Language |
|
||||
| Alternative Classes | Fowler — Refactoring | Alternative Classes with Different Interfaces |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: core business logic duplicated across modules, or same domain concept named 3+ different ways
|
||||
- 🟡 Warning: utility code duplicated, naming inconsistent within a subsystem
|
||||
- 🟢 Suggestion: minor literal duplication, single naming inconsistency
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- Repetition across separate bounded contexts is not automatically duplicate knowledge
|
||||
- Temporary duplication during an active extraction or migration is not necessarily debt
|
||||
- Shared protocol constants repeated at explicit boundaries may be acceptable when local ownership is clearer
|
||||
|
||||
---
|
||||
|
||||
## Risk 4: Accidental Complexity
|
||||
|
||||
**Diagnostic question:** Is the code more complex than the problem it solves?
|
||||
|
||||
Accidental complexity accumulates addition by addition until developers fight scaffolding more than solving the problem.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Abstractions built "for future use" with no current consumer
|
||||
(e.g., a plugin system for a use case that has only one known implementation)
|
||||
- Classes that barely justify their existence (wrap a single method call)
|
||||
- Classes that only delegate to another class without adding behavior (pure middle-men)
|
||||
- Second attempt at a system that is significantly more elaborate than the first,
|
||||
adding generality for requirements that do not yet exist
|
||||
- Switch statements that signal missing polymorphism
|
||||
- Configuration options that have never been changed from their defaults
|
||||
- Framework code larger than the application it powers
|
||||
- Code grown under sustained tactical shortcuts: each workaround seemed small, but
|
||||
accumulated shortcuts mean every new feature requires fighting the existing structure
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Speculative Generality | Fowler — Refactoring | Speculative Generality |
|
||||
| Lazy Class | Fowler — Refactoring | Lazy Class |
|
||||
| Middle Man | Fowler — Refactoring | Middle Man |
|
||||
| Switch Statements | Fowler — Refactoring | Switch Statements |
|
||||
| Second System Effect | Brooks — The Mythical Man-Month | Ch. 5: The Second-System Effect |
|
||||
| YAGNI violations | McConnell — Code Complete | Ch. 5: Design in Construction |
|
||||
| Over-engineering | Hunt & Thomas — The Pragmatic Programmer | Topic 4: Good-Enough Software |
|
||||
| Tactical programming debt | Ousterhout — A Philosophy of Software Design | Ch. 3: Strategic vs. Tactical Programming |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: an entire subsystem built around a speculative requirement, or framework overhead dominates domain logic
|
||||
- 🟡 Warning: several unnecessary abstractions or wrapper classes, unused configuration systems
|
||||
- 🟢 Suggestion: one or two lazy classes or middle-man patterns in non-critical paths
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- A switch over an external protocol, wire format, or closed enum is not automatically missing polymorphism
|
||||
- Thin wrappers that absorb vendor churn or hide instability may be justified
|
||||
- A larger second version is not second-system effect unless the added generality exceeds present needs
|
||||
|
||||
---
|
||||
|
||||
## Risk 5: Dependency Disorder
|
||||
|
||||
**Diagnostic question:** Do dependencies flow in a consistent, predictable direction?
|
||||
|
||||
When business logic depends on infrastructure, infrastructure changes cascade into domain changes. Cycles prevent isolation.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Circular dependencies between modules or packages
|
||||
- High-level business logic directly imports from low-level infrastructure
|
||||
(e.g., a domain service imports from a specific database driver)
|
||||
- Stable, widely-used components depend on unstable, frequently-changing ones
|
||||
- Abstract components depending on concrete implementations
|
||||
- Law of Demeter violations: `order.getCustomer().getAddress().getCity()`
|
||||
- Module fan-out greater than 5 (imports from more than 5 other modules)
|
||||
- A module implements an interface but only uses a subset of its methods, or must
|
||||
provide stub implementations for methods it does not need (ISP violation: fat interface
|
||||
forces unwanted dependencies on callers)
|
||||
- The system feels like "one mind did not design this" — different modules use
|
||||
incompatible architectural patterns with no clear rule for which to use where
|
||||
- Direct version-pinned dependencies on transitive packages (diamond dependency risk);
|
||||
upgrading one library requires coordinating multiple unrelated teams or repositories
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Dependency cycles | Martin — Clean Architecture | Acyclic Dependencies Principle (ADP) |
|
||||
| DIP violation | Martin — Clean Architecture | Dependency Inversion Principle (DIP) |
|
||||
| Instability direction | Martin — Clean Architecture | Stable Dependencies Principle (SDP) |
|
||||
| Abstraction mismatch | Martin — Clean Architecture | Stable Abstractions Principle (SAP) |
|
||||
| ISP violation | Martin — Clean Architecture | Interface Segregation Principle (ISP) |
|
||||
| Conceptual integrity | Brooks — The Mythical Man-Month | Ch. 4: Conceptual Integrity |
|
||||
| Law of Demeter | Hunt & Thomas — The Pragmatic Programmer | Ch. 5: Decoupling and the Law of Demeter |
|
||||
| SOLID violations | Martin — Clean Architecture | Single Responsibility, Open/Closed Principles |
|
||||
| Diamond dependency / upgrade blockage | Winters et al. — Software Engineering at Google | Ch. 21: Dependency Management |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: dependency cycles present, or domain layer directly depends on infrastructure layer
|
||||
- 🟡 Warning: several SDP or DIP violations but no cycles; conceptual inconsistency across modules
|
||||
- 🟢 Suggestion: minor Demeter violations, slightly elevated fan-out in isolated modules
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- High fan-out in an orchestration layer or composition root is not automatically disorder
|
||||
- Adapter modules may depend on both domain and infrastructure when they explicitly translate across the boundary
|
||||
- A stable facade over many leaf dependencies can be healthy if dependency policy is clear
|
||||
|
||||
---
|
||||
|
||||
## Risk 6: Domain Model Distortion
|
||||
|
||||
**Diagnostic question:** Does the code faithfully represent the problem it is solving?
|
||||
|
||||
Code that mismatches business language forces mental translation. Over time it models schemas instead of the domain, with logic bleeding into service layers.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Business logic scattered across service layers while domain objects have only getters and setters
|
||||
(anemic domain model)
|
||||
- Code variable, class, or method names that do not match what business stakeholders call the concept
|
||||
- A class whose only purpose is to hold data with no behavior (pure data bag)
|
||||
- A subclass that ignores or overrides most of its parent's behavior (refuses the inheritance)
|
||||
- Bounded context boundaries crossed without any translation or anti-corruption layer
|
||||
- Methods that are more interested in the data of another class than their own
|
||||
(domain logic in the wrong place)
|
||||
- A subclass overrides most parent methods with incompatible behavior or throws exceptions
|
||||
where the parent contract guarantees success (LSP violation: substitution breaks callers)
|
||||
- Value Objects treated as Entities: a concept defined entirely by its attributes (e.g., Money,
|
||||
Email, Address) is given a mutable ID and lifecycle instead of being replaced when changed
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Anemic Domain Model | Evans — Domain-Driven Design | Domain Model pattern |
|
||||
| Ubiquitous Language drift | Evans — Domain-Driven Design | Ubiquitous Language |
|
||||
| Bounded context violation | Evans — Domain-Driven Design | Bounded Context |
|
||||
| Data Class | Fowler — Refactoring | Data Class |
|
||||
| Refused Bequest | Fowler — Refactoring | Refused Bequest |
|
||||
| Feature Envy | Fowler — Refactoring | Feature Envy |
|
||||
| LSP violation | Martin — Clean Architecture | Liskov Substitution Principle (LSP) |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: domain logic entirely in service layer, domain objects are pure data bags with no behavior
|
||||
- 🟡 Warning: partial anemia, some naming inconsistency between code and domain language
|
||||
- 🟢 Suggestion: minor naming drift in non-core areas, isolated cases of Feature Envy
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- CRUD-heavy workflows may legitimately use transaction scripts instead of rich domain objects
|
||||
- DTOs, persistence records, and API payload models are allowed to be data-only
|
||||
- Shared infrastructure language should not be mistaken for domain drift if the business model itself is simple
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Remedy Guide — Actionable Fix Mode
|
||||
|
||||
When `--fix` is active, enhance every finding's Remedy field to be directly actionable:
|
||||
|
||||
## Remedy Enhancement Rules
|
||||
|
||||
For each finding, the Remedy must include:
|
||||
1. **Target**: exact file path and function/class name
|
||||
2. **Action**: specific refactoring operation (e.g., "Extract lines 45-67 into a new
|
||||
function `calculateShippingCost(items, config)`")
|
||||
3. **Rationale**: one sentence explaining why this specific fix (not just "refactor")
|
||||
|
||||
## Fixability Classification
|
||||
|
||||
Classify each finding after writing the enhanced Remedy:
|
||||
|
||||
| Tier | Criteria | Report label |
|
||||
|------|---------|-------------|
|
||||
| Quick fix | Single-file, mechanical: rename, extract constant, reorder imports | `[quick-fix]` |
|
||||
| Guided fix | Requires a design choice: where to split, what interface shape | `[guided]` |
|
||||
| Manual | Cross-module, needs domain knowledge or team discussion | `[manual]` |
|
||||
|
||||
Append the label to the finding title: `**R1 — Long function in OrderService [quick-fix]**`
|
||||
|
||||
## Output Addition
|
||||
|
||||
After the standard report, add a **Fix Summary** section:
|
||||
|
||||
| Finding | Tier | Target File | Action |
|
||||
|---------|------|------------|--------|
|
||||
| R1 — Long function | quick-fix | src/order.ts:45 | Extract `calculateTotal()` |
|
||||
| R5 — Circular dep | manual | src/models/ ↔ src/services/ | Introduce interface boundary |
|
||||
|
||||
## What NOT to do
|
||||
- Do NOT modify any files. Phase 1 is diagnosis + actionable plan only.
|
||||
- Do NOT generate diffs or code blocks. The Remedy text IS the deliverable.
|
||||
- Do NOT re-score. The Health Score reflects current state, not projected state.
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
---
|
||||
books:
|
||||
- The Mythical Man-Month
|
||||
- Code Complete
|
||||
- Refactoring
|
||||
- Clean Architecture
|
||||
- The Pragmatic Programmer
|
||||
- Domain-Driven Design
|
||||
- A Philosophy of Software Design
|
||||
- Software Engineering at Google
|
||||
- xUnit Test Patterns
|
||||
- The Art of Unit Testing
|
||||
- Working Effectively with Legacy Code
|
||||
- How Google Tests Software
|
||||
---
|
||||
|
||||
# Source Coverage Matrix
|
||||
|
||||
Use this file after selecting a mode and before writing findings.
|
||||
It exists to prevent shallow "book-name citation" reviews.
|
||||
|
||||
## Review Discipline
|
||||
|
||||
- Cite a book only when the observed symptom actually matches that book's principle.
|
||||
- A threshold crossing is a hint, not a verdict. Check context, intent, and blast radius.
|
||||
- Look for justified tradeoffs before flagging a smell as debt.
|
||||
- Prefer concrete architectural or domain consequences over abstract style complaints.
|
||||
- If two books pull in different directions, state the tradeoff instead of pretending there is no tension.
|
||||
|
||||
---
|
||||
|
||||
## Frederick Brooks — *The Mythical Man-Month*
|
||||
|
||||
**Encoded today**
|
||||
- Change propagation as communication overhead
|
||||
- Second-System Effect
|
||||
- Conceptual Integrity
|
||||
|
||||
**Do not ignore**
|
||||
- Whether the design shows a single coherent idea or competing local optimizations
|
||||
- Whether cross-team coordination cost is becoming part of feature cost
|
||||
|
||||
**Do not over-flag**
|
||||
- Large systems are not automatically second systems
|
||||
- Multi-module designs are acceptable when they preserve conceptual integrity
|
||||
|
||||
---
|
||||
|
||||
## Steve McConnell — *Code Complete*
|
||||
|
||||
**Encoded today**
|
||||
- Routine length, nesting, naming, and magic numbers
|
||||
- Construction-phase YAGNI checks
|
||||
- Defensive programming and error-handling discipline (guard clauses, input validation,
|
||||
explicit error paths, assertions for invariants)
|
||||
|
||||
**Do not ignore**
|
||||
- Whether low-level readability choices compound into operational risk
|
||||
- Whether missing error handling makes failure modes invisible to maintainers
|
||||
|
||||
**Do not over-flag**
|
||||
- Small, explicit guard clauses are not cognitive overload
|
||||
- A long routine may be acceptable when it is linear, well-named, and single-purpose
|
||||
|
||||
---
|
||||
|
||||
## Martin Fowler — *Refactoring*
|
||||
|
||||
**Encoded today**
|
||||
- Long Method, Long Parameter List, Message Chains
|
||||
- Shotgun Surgery, Divergent Change, Feature Envy, Inappropriate Intimacy
|
||||
- Duplicate Code, Speculative Generality, Lazy Class, Middle Man, Data Class
|
||||
- Flag Arguments: boolean parameters that split a function into two behaviors
|
||||
- Primitive Obsession: domain concepts expressed as raw primitive types instead of value types
|
||||
|
||||
**Do not ignore**
|
||||
- Whether the code smell is local or systemic
|
||||
- Whether a refactoring target has a natural home in the model
|
||||
|
||||
**Do not over-flag**
|
||||
- Temporary duplication during an active extraction is not always debt
|
||||
- A data-focused structure is acceptable when it is intentionally a DTO or boundary record
|
||||
|
||||
---
|
||||
|
||||
## Robert C. Martin — *Clean Architecture*
|
||||
|
||||
**Encoded today**
|
||||
- DIP, ADP, SDP, SAP, and layering direction
|
||||
- ISP: fat interfaces that force callers to depend on methods they do not use
|
||||
- LSP: subclasses that break the behavioral contract of their parent type
|
||||
- SRP and OCP: classes with multiple reasons to change; modules closed to modification
|
||||
but open to extension via abstraction
|
||||
|
||||
**Do not ignore**
|
||||
- Policy vs detail boundaries
|
||||
- Whether dependency arrows preserve replaceability and testability
|
||||
|
||||
**Do not over-flag**
|
||||
- Composition roots may depend on concrete infrastructure by design
|
||||
- Thin adapter layers can import both directions when they are explicitly boundary glue
|
||||
|
||||
---
|
||||
|
||||
## Andrew Hunt & David Thomas — *The Pragmatic Programmer*
|
||||
|
||||
**Encoded today**
|
||||
- Orthogonality
|
||||
- DRY
|
||||
- Law of Demeter
|
||||
|
||||
**Do not ignore**
|
||||
- Whether knowledge duplication is really duplicated decision-making
|
||||
- Whether coupling is accidental or a deliberate local simplification
|
||||
|
||||
**Do not over-flag**
|
||||
- Similar code in different bounded contexts is not automatically a DRY violation
|
||||
- Direct object access inside a cohesive aggregate is not always a Demeter problem
|
||||
|
||||
---
|
||||
|
||||
## Eric Evans — *Domain-Driven Design*
|
||||
|
||||
**Encoded today**
|
||||
- Ubiquitous Language
|
||||
- Bounded Context
|
||||
- Anemic Domain Model
|
||||
- Entity vs Value Object: objects with identity and lifecycle vs. objects defined solely by
|
||||
their attributes (Money, Email, Address should be immutable value types, not mutable entities)
|
||||
- Aggregate Roots: who owns the invariant boundary; cross-aggregate access only through the root
|
||||
|
||||
**Do not ignore**
|
||||
- Aggregate boundaries, invariant ownership, and anti-corruption layers
|
||||
- Whether names match the business language used by experts
|
||||
|
||||
**Do not over-flag**
|
||||
- CRUD-heavy workflows may legitimately use transaction scripts
|
||||
- Thin entities are acceptable when the domain itself is simple
|
||||
|
||||
---
|
||||
|
||||
## John Ousterhout — *A Philosophy of Software Design*
|
||||
|
||||
**Encoded today**
|
||||
- Deep vs shallow modules
|
||||
- Strategic vs tactical programming
|
||||
- Information Leakage: a design decision encoded in more than one module, creating
|
||||
change coupling even when no explicit import exists between the modules
|
||||
|
||||
**Do not ignore**
|
||||
- Interface complexity relative to hidden complexity
|
||||
- Whether repeated tactical patches are raising long-term cognitive load
|
||||
- Whether a "helper" exposes internal design decisions that callers should not know
|
||||
|
||||
**Do not over-flag**
|
||||
- Internal implementation complexity is fine when the interface stays simple
|
||||
- A small wrapper is acceptable when it meaningfully absorbs volatility
|
||||
|
||||
---
|
||||
|
||||
## Titus Winters, Tom Manshreck, Hyrum Wright — *Software Engineering at Google*
|
||||
|
||||
**Encoded today**
|
||||
- Hyrum's Law
|
||||
- Dependency management and upgrade blockage
|
||||
- Code sustainability: whether code as written can be maintained, migrated, and upgraded
|
||||
over a multi-year horizon without heroic effort
|
||||
- Backward compatibility: whether API changes preserve existing callers or force
|
||||
coordinated upgrades across the organization
|
||||
|
||||
**Do not ignore**
|
||||
- De facto APIs created by observable behavior
|
||||
- The maintenance cost of exposing too much surface area
|
||||
- Whether the dependency graph will allow independent upgrades over time
|
||||
|
||||
**Do not over-flag**
|
||||
- A stable public API is not a liability if it is intentionally supported
|
||||
- Fan-out alone is not disorder when dependency policy is explicit and governed
|
||||
|
||||
---
|
||||
|
||||
## Gerard Meszaros — *xUnit Test Patterns*
|
||||
|
||||
**Encoded today**
|
||||
- Assertion Roulette, Mystery Guest, General Fixture
|
||||
- Eager Test, Lazy Test, Test Code Duplication, Behavior Verification
|
||||
- Erratic Test: tests that produce non-deterministic results due to shared state,
|
||||
time dependence, or ordering assumptions between tests
|
||||
|
||||
**Do not ignore**
|
||||
- Whether test failures are diagnosable
|
||||
- Whether the suite shape amplifies maintenance cost
|
||||
|
||||
**Do not over-flag**
|
||||
- Multiple assertions are acceptable when they express one behavior with one failure story
|
||||
- Shared fixtures are acceptable when every field is relevant to the scenario
|
||||
|
||||
---
|
||||
|
||||
## Roy Osherove — *The Art of Unit Testing*
|
||||
|
||||
**Encoded today**
|
||||
- Test naming discipline
|
||||
- Test isolation
|
||||
- Mock usage guidelines
|
||||
- Completeness of edge-path tests
|
||||
|
||||
**Do not ignore**
|
||||
- Whether tests verify behavior rather than wiring
|
||||
- Whether seams are used to simplify tests, or production code is being contorted for testability
|
||||
|
||||
**Do not over-flag**
|
||||
- A mock is acceptable when the dependency is nondeterministic and the assertion still verifies behavior
|
||||
- Naming conventions are guidance; clarity is the goal
|
||||
|
||||
---
|
||||
|
||||
## Michael Feathers — *Working Effectively with Legacy Code*
|
||||
|
||||
**Encoded today**
|
||||
- Legacy code as code without tests
|
||||
- Sensing and Separation
|
||||
- Seams
|
||||
- Characterization Tests
|
||||
|
||||
**Do not ignore**
|
||||
- Whether the team can change a risky area safely today
|
||||
- Whether the code offers any seam for isolating behavior under change
|
||||
|
||||
**Do not over-flag**
|
||||
- Untested code is not automatically legacy if it is stable and not under active change
|
||||
- Characterization tests are most important before modifying unclear existing behavior
|
||||
|
||||
---
|
||||
|
||||
## Google Engineering — *How Google Tests Software*
|
||||
|
||||
**Encoded today**
|
||||
- Change coverage vs line coverage
|
||||
- Pyramid shape and suite portfolio economics
|
||||
|
||||
**Do not ignore**
|
||||
- Whether the suite reflects business risk, not just percentages
|
||||
- Whether expensive tests dominate feedback loops
|
||||
|
||||
**Do not over-flag**
|
||||
- A non-70:20:10 ratio can be healthy when justified by platform constraints or product risk
|
||||
- High coverage is useful when paired with meaningful branch and change protection
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
# Test Decay Risk Reference
|
||||
|
||||
Six patterns that cause test suites to degrade. Apply the Iron Law to each finding.
|
||||
|
||||
---
|
||||
|
||||
## Risk T1: Test Obscurity
|
||||
|
||||
**Diagnostic question:** How much effort does it take to understand what this test verifies?
|
||||
|
||||
Unclear test intent breeds distrust, missed failures, and duplicates — one step from an abandoned suite.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Assertion Roulette: multiple assertions with no message string — when one fails, it is
|
||||
impossible to determine which behavior broke without reading every assertion
|
||||
- Mystery Guest: test depends on external state (files, database rows, shared fixtures)
|
||||
that is not visible in the test body
|
||||
- Test names that do not express the scenario and expected outcome
|
||||
(e.g., `test1`, `shouldWork`, `testLogin`, `testUserService`)
|
||||
- General Fixture: an oversized setUp or beforeEach shared by unrelated tests, making
|
||||
each test's preconditions invisible
|
||||
- Test body requires reading production code to understand what is being verified
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Assertion Roulette | Meszaros — xUnit Test Patterns | Assertion Roulette (p.224) |
|
||||
| Mystery Guest | Meszaros — xUnit Test Patterns | Mystery Guest (p.411) |
|
||||
| General Fixture | Meszaros — xUnit Test Patterns | General Fixture (p.316) |
|
||||
| Test naming | Osherove — The Art of Unit Testing | method_scenario_expected naming convention |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: no test name in the file describes the behavior being tested; all assertions lack messages
|
||||
- 🟡 Warning: multiple Mystery Guests; several ambiguous test names
|
||||
- 🟢 Suggestion: minor naming issues; isolated General Fixture
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- Multiple assertions are acceptable when they describe one coherent behavior and fail with a clear story
|
||||
- Shared setup is fine when every initialized value is relevant to nearly every test
|
||||
- Concise test names are acceptable if scenario and expected outcome are still obvious
|
||||
|
||||
---
|
||||
|
||||
## Risk T2: Test Brittleness
|
||||
|
||||
**Diagnostic question:** Do tests break when you refactor without changing behavior?
|
||||
|
||||
Brittle tests punish refactoring — eventually developers stop refactoring and the codebase stagnates to protect the suite.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Tests assert on private method results, internal state, or implementation details
|
||||
rather than observable behavior
|
||||
- Eager Test: one test method verifies multiple unrelated behaviors; any single change
|
||||
causes it to fail regardless of which behavior was touched
|
||||
- Over-specified: assertions enforce mock call order or exact parameter values that are
|
||||
irrelevant to the behavior being tested
|
||||
- Renaming or extracting a method causes 5 or more tests to fail even though no behavior changed
|
||||
- Erratic Test: a test produces different results across runs without any change to
|
||||
production code — caused by race conditions, time-dependent logic, random data, or
|
||||
shared mutable state between tests
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Eager Test | Meszaros — xUnit Test Patterns | Eager Test (p.228) |
|
||||
| Erratic Test | Meszaros — xUnit Test Patterns | Erratic Test |
|
||||
| Implementation coupling | Osherove — The Art of Unit Testing | Test isolation principle |
|
||||
| Orthogonality violation | Hunt & Thomas — The Pragmatic Programmer | Ch. 2: Orthogonality |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: refactoring with no behavior change causes test failures; > 5 tests coupled to a single implementation detail
|
||||
- 🟡 Warning: Eager Tests common across the suite; moderate implementation-detail assertions
|
||||
- 🟢 Suggestion: isolated over-specification in non-critical tests
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- Verifying an externally observable event or emitted command is not implementation coupling
|
||||
- One test with several assertions is acceptable when all assertions support one behavior claim
|
||||
- A fake or in-memory adapter is not brittleness if the test still asserts behavior, not wiring
|
||||
|
||||
---
|
||||
|
||||
## Risk T3: Test Duplication
|
||||
|
||||
**Diagnostic question:** Is the same test scenario expressed in more than one place?
|
||||
|
||||
Duplicated tests must change in multiple places and create false confidence without testing distinct behavior.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Test Code Duplication: same setup or assertion logic copy-pasted across multiple tests
|
||||
without extraction into a shared helper
|
||||
- Lazy Test: multiple tests verifying identical behavior with no differentiation in input,
|
||||
state, or expected output
|
||||
- Same boundary condition tested identically at unit, integration, and E2E level —
|
||||
three copies with no layer differentiation
|
||||
- Test helper functions or fixtures duplicated across test files instead of shared
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Test Code Duplication | Meszaros — xUnit Test Patterns | Test Code Duplication (p.213) |
|
||||
| Lazy Test | Meszaros — xUnit Test Patterns | Lazy Test (p.232) |
|
||||
| DRY violation in tests | Hunt & Thomas — The Pragmatic Programmer | DRY: Don't Repeat Yourself |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: core business scenario fully duplicated across all three test layers with no differentiation
|
||||
- 🟡 Warning: common scenario setup repeated in 5 or more tests without extraction
|
||||
- 🟢 Suggestion: minor helper duplication; isolated Lazy Tests
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- The same scenario may appear at unit and integration level when each layer verifies a distinct risk
|
||||
- Small local setup duplication can be clearer than an over-abstracted fixture maze
|
||||
- Similar assertions against different domain rules are not Lazy Tests if the business intent differs
|
||||
|
||||
---
|
||||
|
||||
## Risk T4: Mock Abuse
|
||||
|
||||
**Diagnostic question:** Is the test more complex than the behavior it tests?
|
||||
|
||||
Mock abuse produces tests that pass while verifying nothing — production code can be fully broken as long as the mocks are wired up.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Mock setup code is longer than the test logic itself
|
||||
- Primary assertion is `expect(mock).toHaveBeenCalledWith(...)` — the test verifies
|
||||
that a mock was called, not that any real behavior occurred
|
||||
- Test-only methods added to production classes for lifecycle management in tests
|
||||
- Single unit test uses more than 3 mocks
|
||||
- Incomplete Mock: mock object missing fields that downstream code will access,
|
||||
causing silent failures only visible in integration
|
||||
- Hard-Coded Test Data: test data has no resemblance to real data shapes or constraints
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Mock count > 3 | Osherove — The Art of Unit Testing | Mock usage guidelines |
|
||||
| Testing mock behavior | Meszaros — xUnit Test Patterns | Behavior Verification (p.544) |
|
||||
| Test-only production methods | Feathers — Working Effectively with Legacy Code | Ch. 3: Sensing and Separation |
|
||||
| Hard-Coded Test Data | Meszaros — xUnit Test Patterns | Hard-Coded Test Data (p.534) |
|
||||
| Incomplete Mock | Osherove — The Art of Unit Testing | Mock completeness requirement |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: mock setup > 50% of test code; production class has methods only called from tests
|
||||
- 🟡 Warning: mocks consistently > 3 per test; primary assertions are mock call verifications
|
||||
- 🟢 Suggestion: isolated Incomplete Mocks; minor Hard-Coded Test Data
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- A small number of mocks around nondeterministic dependencies is acceptable when assertions still verify behavior
|
||||
- Fakes and spies used to observe state transitions are not mock abuse by default
|
||||
- One interaction assertion may be appropriate when the interaction itself is the behavior under test
|
||||
|
||||
---
|
||||
|
||||
## Risk T5: Coverage Illusion
|
||||
|
||||
**Diagnostic question:** Does the test suite actually protect against the failures that matter?
|
||||
|
||||
Coverage measures execution, not verification. 90% line coverage can still miss every critical failure mode — teams stop looking because the number says "covered."
|
||||
|
||||
### Symptoms
|
||||
|
||||
- High line coverage but error-handling branches, boundary conditions, and exception paths
|
||||
have no corresponding tests
|
||||
- Happy-path only: no sad paths, no null/empty/zero inputs, no concurrency edge cases
|
||||
- Legacy code areas are being actively modified with no tests present
|
||||
(Feathers: "legacy code is code without tests")
|
||||
- Coverage percentage treated as a sign-off criterion; critical change paths remain untested
|
||||
- Tests assert on return values but not on important side effects such as database writes,
|
||||
event publications, or state transitions
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Legacy code = no tests | Feathers — Working Effectively with Legacy Code | Ch. 1: "Legacy code is code without tests" |
|
||||
| Change coverage vs line coverage | Google — How Google Tests Software | Ch. 11: Testing at Google Scale |
|
||||
| Happy-path only | Osherove — The Art of Unit Testing | Test completeness principle |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: legacy code area actively being modified with no tests; error-handling paths entirely absent
|
||||
- 🟡 Warning: coverage > 80% but edge and exception paths are systematically absent
|
||||
- 🟢 Suggestion: a few non-critical paths missing sad-path tests
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- High line coverage is useful when paired with branch, boundary, and change-path coverage
|
||||
- A new module may have limited coverage early if it is still private and low-risk
|
||||
- Side-effect assertions may live in integration tests rather than unit tests without implying a gap
|
||||
|
||||
---
|
||||
|
||||
## Risk T6: Architecture Mismatch
|
||||
|
||||
**Diagnostic question:** Does the test suite structure reflect the system's actual risk profile?
|
||||
|
||||
Wrong suite shape is slow and expensive — not from bad tests, but from using the wrong type at the wrong layer.
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Inverted test pyramid: E2E or integration test count exceeds unit test count,
|
||||
causing a slow and fragile suite
|
||||
- Legacy code with no seam points: no interfaces, dependency injection, or seams exist,
|
||||
making it impossible to test in isolation without modifying production code
|
||||
- Legacy areas being modified have no Characterization Tests to capture current behavior
|
||||
before changes are made
|
||||
- Full suite execution time exceeds 10 minutes (indicates architectural problem,
|
||||
not a performance problem — too many slow tests)
|
||||
- High-risk and low-risk paths are tested at identical density;
|
||||
no risk-based prioritization in test distribution
|
||||
|
||||
### Sources
|
||||
|
||||
| Symptom | Book | Principle / Smell |
|
||||
|---------|------|-------------------|
|
||||
| Inverted pyramid | Google — How Google Tests Software | 70:20:10 unit:integration:E2E ratio |
|
||||
| No seam points | Feathers — Working Effectively with Legacy Code | Ch. 4: Seam Model |
|
||||
| Missing Characterization Tests | Feathers — Working Effectively with Legacy Code | Ch. 13: Characterization Tests |
|
||||
| Suite execution time | Meszaros — xUnit Test Patterns | Slow Tests (p. 253) |
|
||||
|
||||
### Severity Guide
|
||||
|
||||
- 🔴 Critical: legacy code being modified has no seams and no characterization tests; pyramid fully inverted
|
||||
- 🟡 Warning: suite execution > 10 minutes; integration/E2E count exceeds unit tests
|
||||
- 🟢 Suggestion: localized pyramid ratio deviation; a few legacy areas missing characterization tests
|
||||
|
||||
### What Not to Flag
|
||||
|
||||
- Deviating from 70:20:10 can be justified by platform constraints or product risk
|
||||
- A suite heavy on integration tests can still be healthy if feedback is fast and purposefully layered
|
||||
- A small number of critical-path E2E tests is desirable, not a smell
|
||||
Reference in New Issue
Block a user