The Gallery

Real diagnostic output from brooks-lint — generated by running the skill, then lightly abridged. Four languages, four review modes, every finding in the Iron Law form.

8 worked examples 4 review modes 4 languages Python · TypeScript · Go · Java

PR Review Mode 1

Diagnoses a diff against the six production decay risks (R1–R6).

TypeScript

Seven-service payment processor

55 / 100

A single method orchestrates seven services, creating a structural coupling trap where any change to payment, fraud, inventory, or notification touches the same method.

▸ Input code
class PaymentProcessor {
  constructor(
    private db, private stripe, private mailer, private inventory,
    private analytics, private taxCalc, private fraudDetection
  ) {}

  async processPayment(orderId, cardToken) {
    const order = await this.db.orders.findById(orderId);
    const tax = this.taxCalc.calculate(order.items, order.shippingAddress.state);
    order.tax = tax;
    const fraudScore = await this.fraudDetection.evaluate({ amount: order.total + tax, card: cardToken, ... });
    if (fraudScore > 0.8) { await this.mailer.send(...); await this.analytics.track('fraud_hold', ...); return {...}; }
    const charge = await this.stripe.charges.create({ amount: Math.round((order.total + tax) * 100), ... });
    for (const item of order.items) {
      await this.inventory.decrement(item.sku, item.quantity);
      if (await this.inventory.getStock(item.sku) < 10) { await this.mailer.send('warehouse@company.com', ...); }
    }
    order.status = 'paid'; order.chargeId = charge.id; await this.db.orders.save(order);
    await this.mailer.send(order.customerEmail, 'Payment Received', `Charge: $${order.total + tax}`);
    await this.analytics.track('payment_success', { orderId, amount: order.total + tax });
    return { status: 'paid', chargeId: charge.id };
  }
}

🔴 Change Propagation — Seven-service constructor signals a God Class

Symptom: One class injects seven dependencies; one method orchestrates all of them.
Source: Fowler — Refactoring — Divergent Change; Martin — Clean Architecture — SRP
Remedy: Decompose into FraudCheckService, InventoryDeductionService, PaymentNotifier — inject 3, not 7.

🔴 Change Propagation — Inventory loop embeds warehouse notification policy

Symptom: A low-stock check (< 10) fires a warehouse email with hardcoded recipient inside the payment loop.
Source: Fowler — Refactoring — Shotgun Surgery; Hunt & Thomas — Orthogonality
Remedy: Publish a StockLevelChanged domain event a separate notifier subscribes to.

🟡 Knowledge Duplication — order.total + tax computed three times

Symptom: The same expression appears on three lines with no shared name.
Source: Hunt & Thomas — DRY; Fowler — Refactoring — Duplicate Code
Remedy: Expose a computed order.grandTotal.

🟡 Domain Model Distortion — Order is a mutable data bag

Symptom: order.tax, order.status, order.chargeId are all set externally; the object holds no behaviour.
Remedy: Give Order state-transition methods: order.recordPayment(chargeId).

Architecture Audit Mode 2

Maps module dependencies, renders a colour-coded graph, and flags layering and cycle violations.

TypeScript

Dependency Inversion violation

50 / 100

Domain entities import infrastructure directly, and a near-cycle forms between Product and PricingService.

graph TD
  subgraph API["API Layer"]
    OrderController; UserController
  end
  subgraph Services["Service Layer"]
    OrderService; PricingService; UserService
  end
  subgraph Domain["Domain Layer"]
    Order; User; Product
  end
  subgraph Infra["Infrastructure Layer"]
    PostgresClient; RedisCache; StripeClient
  end
  OrderController --> OrderService
  UserController --> UserService
  UserService --> OrderService
  OrderService --> Order
  OrderService --> PostgresClient
  PricingService --> Product
  PricingService --> StripeClient
  Order --> PostgresClient
  User --> RedisCache
  Product --> PricingService
  classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
  classDef warning fill:#ffd43b,stroke:#e67700
  classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
  class Order,User,Product critical
  class OrderService,PricingService,UserService warning
  class OrderController,UserController,PostgresClient,RedisCache,StripeClient clean

🔴 Dependency Disorder — Domain layer directly imports infrastructure

Symptom: Order.ts imports PostgresClient; User.ts imports RedisCache.
Source: Martin — Clean Architecture — Dependency Inversion Principle
Remedy: Define IOrderRepository/IUserRepository in the domain; move infra refs to infra/.

🔴 Dependency Disorder — Product → PricingService (upward dependency)

Symptom: Near-cycle PricingService → Product → PricingService.
Source: Martin — Clean Architecture — Acyclic Dependencies Principle
Remedy: Pass pricing as a value object or define IPricingPolicy in the domain.
Go

Circular dependency across packages

45 / 100

auth → user → notification → auth forms a strongly connected component — Go refuses to compile it.

graph TD
  subgraph pkg["pkg/"]
    auth["auth"]; user["user"]; notification["notification"]; billing["billing"]
  end
  auth --> user
  user --> notification
  notification -.->|circular| auth
  billing --> user
  classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
  classDef warning fill:#ffd43b,stroke:#e67700
  class auth,user,notification critical
  class billing warning

🔴 Dependency Disorder — Circular dependency auth → user → notification → auth

Symptom: Three packages form a cycle; none compile, test, or deploy independently.
Source: Martin — Clean Architecture — Acyclic Dependencies Principle
Remedy: Extract interfaces into pkg/contracts; each package implements the interface its consumer defines.

🟡 Domain Model Distortion — Bounded contexts crossed with no anti-corruption layer

Symptom: Identity, profile, and notification contexts import each other with no translation layer.
Source: Evans — DDD — Bounded Context; Anti-Corruption Layer
Remedy: Define thin adapters at each context boundary.
Java

Textbook Clean Architecture

98 / 100

Dependencies flow inward, infra implements domain ports, no cycles. brooks-lint reports clean code as clean — and still offers one forward-looking suggestion.

graph TD
  subgraph API["API Layer"]
    OrderController; UserController
  end
  subgraph Application["Application Layer"]
    OrderService; UserService
  end
  subgraph Domain["Domain Layer"]
    OrderModel["Order"]; UserModel["User"]; OrderRepository["OrderRepository (interface)"]; UserRepository["UserRepository (interface)"]
  end
  subgraph Infra["Infrastructure Layer"]
    JpaOrderRepository; JpaUserRepository
  end
  OrderController --> OrderService
  UserController --> UserService
  OrderService --> OrderModel
  OrderService --> OrderRepository
  UserService --> UserModel
  UserService --> UserRepository
  JpaOrderRepository --> OrderRepository
  JpaUserRepository --> UserRepository
  classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
  class OrderController,UserController,OrderService,UserService,OrderModel,UserModel,OrderRepository,UserRepository,JpaOrderRepository,JpaUserRepository clean

🟢 Suggestion — Monitor application service growth

Symptom: OrderService and UserService are symmetric siblings that may accrue responsibilities without a split policy.
Source: Brooks — The Mythical Man-Month — Conceptual Integrity
Remedy: Document a "one service per use-case cluster" rule now, before the pattern calcifies.

Tech Debt Assessment Mode 3

Classifies debt across the decay risks and scores each finding by Pain × Spread priority.

Java

Shotgun Surgery across six files

56 / 100

Adding a currency means editing six files in six unrelated layers — three Critical findings that share one root cause.

RiskFindingsAvg PriorityClassification
Change Propagation26.5Mixed (1 Critical + 1 Scheduled)
Knowledge Duplication19.0Critical
Domain Model Distortion19.0Critical
Cognitive Overload16.0Scheduled

🔴 Change Propagation — Shotgun Surgery across six modules Pain × Spread: 9

Symptom: Adding EUR requires editing 6 files in 6 distinct layers with no architectural relationship.
Source: Fowler — Refactoring — Shotgun Surgery; Hunt & Thomas — Orthogonality
Remedy: Introduce a Money value object and a MoneyFormatter service.

🔴 Knowledge Duplication — $ as a magic literal in five files Pain × Spread: 9

Symptom: The string "$" appears in 5 independent locations with no shared constant.
Source: Hunt & Thomas — DRY; McConnell — Code Complete — Ch. 12
Remedy: Use Currency.getSymbol(Locale) in MoneyFormatter; remove all "$" literals.

🔴 Domain Model Distortion — No Money type exists Pain × Spread: 9

Symptom: All price/amount fields are raw double.
Source: Evans — DDD — Domain Model; Fowler — Refactoring — Data Class
Remedy: Introduce record Money(BigDecimal amount, Currency currency).
Recommended focus: All three Critical findings share one root cause — the absence of a Money value object. One intervention collapses three findings.

Test Quality Review Mode 4

Audits an existing suite against six test-space decay risks (T1–T6).

TypeScript

Mock abuse

60 / 100

Seven mocks per test, 14 lines of setup vs 6 of assertions — the service is never tested against a real collaborator, and the return value is never checked.

▸ Input test
it('should place an order successfully', () => {
  const mockDb = mock<Database>();
  const mockPayment = mock<PaymentGateway>();
  const mockInventory = mock<InventoryService>();
  const mockMailer = mock<MailService>();
  const mockAudit = mock<AuditLogger>();
  const mockCache = mock<CacheService>();
  const mockMetrics = mock<MetricsCollector>();
  const service = new OrderService(mockDb, mockPayment, mockInventory, mockMailer, mockAudit, mockCache, mockMetrics);
  const result = await service.placeOrder('1', 'item-1', 2);

  expect(mockPayment.charge).toHaveBeenCalledWith('ch_1', 2000);
  expect(mockInventory.check).toHaveBeenCalledWith('item-1', 2);
  expect(mockMailer.send).toHaveBeenCalled();
  expect(mockAudit.log).toHaveBeenCalledWith('ORDER_PLACED', expect.anything());
  expect(mockCache.invalidate).toHaveBeenCalledWith('orders:1');
  expect(mockMetrics.increment).toHaveBeenCalledWith('orders.placed');
});

🔴 Mock Abuse — Seven mocks per test; setup dominates logic

Symptom: 7 mock objects; 14 lines of setup vs 6 of assertions. No real collaborator is ever exercised.
Source: Osherove — The Art of Unit Testing (mock count > 3); Meszaros — xUnit Test Patterns
Remedy: Reduce mocks to ≤ 3, use in-memory fakes, assert on result first.

🔴 Mock Abuse — All six assertions verify mock calls, not behaviour

Symptom: Every assertion is toHaveBeenCalledWith; result is captured but never asserted.
Consequence: A placeOrder that calls every mock yet returns null or double-charges still passes.
Remedy: Assert observable output: expect(result.status).toBe('confirmed').
Python

Inverted test pyramid

55 / 100

Only 16% of tests are unit tests; an E2E-heavy suite takes ~9 minutes and blocks fast CI feedback.

▸ Suite overview
tests/
├── e2e/           47 tests, avg 8s each   (~6 min)
├── integration/   83 tests, avg 2s each   (~3 min)
└── unit/          24 tests, avg 10ms each (~0.2s)

Total: 154 tests, ~9 min
Actual  ratio:  Unit 16% : Integration 54% : E2E 30%
Target  ratio:  Unit 70% : Integration 20% : E2E 10%

🔴 Architecture Mismatch — Fully inverted test pyramid

Symptom: Only 24 of 154 tests (16%) are unit tests; E2E + integration = 84%.
Source: Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns
Remedy: Target 70% unit; reduce E2E to 5–8 critical smoke tests.

🔴 Architecture Mismatch — 9-minute suite blocks CI fast-feedback

Symptom: Full suite ~542s, dominated by 8s E2E tests.
Remedy: Split CI: (1) unit only, < 60s, blocks merge; (2) integration + E2E async, non-blocking.

🟡 Coverage Illusion — Core domain untested at unit level

Symptom: tests/unit/ covers only validators and formatters — no checkout, login, order, or payment.
Source: Feathers — Working Effectively with Legacy Code
Remedy: Start with test_checkout_flow.py and test_payment_api.py.