📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-01 16:02:23 +00:00
parent 843ff9620b
commit 8696cd9e52
26 changed files with 2314 additions and 44 deletions
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

+495
View File
@@ -0,0 +1,495 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gallery — brooks-lint</title>
<meta name="description" content="Real brooks-lint output across Python, TypeScript, Go, and Java — PR reviews, architecture audits with dependency graphs, tech debt assessments, and test quality reviews.">
<link rel="icon" type="image/svg+xml" href="logo.svg">
<meta property="og:title" content="brooks-lint Gallery">
<meta property="og:description" content="Real diagnostic output across four languages and four review modes.">
<meta property="og:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
<meta name="twitter:card" content="summary_large_image">
<style>
:root{
--paper:#f7f1e4; --paper-2:#fbf6ec; --card:#fffdf8; --ink:#2a2520; --ink-soft:#5c5347;
--rule:#e3d8c2; --rule-soft:#ece2cf; --blue:#2d4a7c; --blue-soft:#e7edf5;
--green:#2b8a3e; --gold:#b07d2b; --red:#b23b3b; --ink-dark:#211d18;
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Roboto,Helvetica,Arial,sans-serif;
--mono:"SF Mono","JetBrains Mono","Fira Code",Menlo,Consolas,monospace;
}
*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{font-family:var(--sans);color:var(--ink);background:var(--paper);line-height:1.65;-webkit-font-smoothing:antialiased;}
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
background-image:radial-gradient(rgba(120,90,40,.04) 1px,transparent 1px);background-size:4px 4px;opacity:.6;}
.wrap{max-width:1000px;margin:0 auto;padding:0 24px;position:relative;z-index:1;}
h1,h2,h3,.serif{font-family:var(--serif);font-weight:700;letter-spacing:-.01em;}
a{color:var(--blue);text-decoration:none;} a:hover{text-decoration:underline;}
nav{position:sticky;top:0;z-index:50;background:rgba(247,241,228,.85);backdrop-filter:saturate(140%) blur(10px);border-bottom:1px solid var(--rule);}
.nav-in{display:flex;align-items:center;gap:20px;height:64px;max-width:1000px;margin:0 auto;padding:0 24px;}
.brand{display:flex;align-items:center;gap:10px;font-family:var(--serif);font-weight:700;font-size:19px;color:var(--ink);}
.brand img{width:30px;height:30px;border-radius:7px;}
.nav-links{display:flex;gap:22px;margin-left:auto;align-items:center;}
.nav-links a{color:var(--ink-soft);font-size:14.5px;}
header.hero{padding:60px 0 30px;text-align:center;}
header.hero h1{font-size:clamp(30px,4.6vw,46px);margin-bottom:14px;}
header.hero p{color:var(--ink-soft);font-size:17px;max-width:60ch;margin:0 auto;}
.meta-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:22px;}
.meta-pill{background:var(--card);border:1px solid var(--rule);border-radius:999px;padding:6px 14px;font-size:13px;color:var(--ink-soft);}
.meta-pill b{color:var(--ink);font-family:var(--serif);}
.filters{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:34px 0 8px;position:sticky;top:64px;z-index:40;
background:var(--paper);padding:12px 0;}
.filters button{font-family:var(--sans);font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--rule);
background:var(--card);color:var(--ink-soft);padding:8px 16px;border-radius:999px;}
.filters button.active{background:var(--ink);color:var(--paper-2);border-color:var(--ink);}
section.mode-group{padding:24px 0;}
.mode-title{font-family:var(--serif);font-size:24px;margin:18px 0 4px;display:flex;align-items:center;gap:10px;}
.mode-title .badge{font-family:var(--mono);font-size:12px;color:var(--gold);border:1px solid var(--rule);background:var(--card);padding:3px 10px;border-radius:999px;font-weight:400;}
.mode-sub{color:var(--ink-soft);font-size:14.5px;margin-bottom:18px;}
.case{background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:24px 26px;margin-bottom:20px;
box-shadow:0 6px 20px rgba(80,55,15,.05);}
.case-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px;}
.lang{font-family:var(--mono);font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
padding:3px 9px;border-radius:6px;color:#fff;}
.lang.ts{background:#2d4a7c;} .lang.py{background:#2b6a8a;} .lang.go{background:#1f8a9c;} .lang.java{background:#9c6b1f;}
.case-head h3{font-size:19px;flex:1;min-width:200px;}
.hs{font-family:var(--serif);font-weight:700;font-size:15px;padding:4px 12px;border-radius:999px;white-space:nowrap;}
.hs.bad{background:#f6dede;color:var(--red);} .hs.mid{background:#f6ecd4;color:var(--gold);} .hs.good{background:#def0e1;color:var(--green);}
.case-summary{font-style:italic;color:var(--ink-soft);font-size:14.5px;margin:8px 0 16px;}
details{margin:0 0 18px;border:1px solid var(--rule-soft);border-radius:10px;background:var(--paper-2);}
details summary{cursor:pointer;padding:11px 16px;font-size:13.5px;color:var(--ink-soft);font-weight:600;user-select:none;}
details[open] summary{border-bottom:1px solid var(--rule-soft);}
pre.code{margin:0;padding:18px;background:var(--ink-dark);color:#cfc7b6;border-radius:0 0 10px 10px;overflow-x:auto;
font-family:var(--mono);font-size:12.8px;line-height:1.6;}
pre.struct{background:var(--ink-dark);color:#cfc7b6;}
.finding{border-top:1px solid var(--rule-soft);padding-top:14px;margin-top:14px;}
.finding:first-of-type{border-top:none;padding-top:0;margin-top:0;}
.finding h4{font-family:var(--serif);font-size:16px;margin-bottom:8px;display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;}
.finding h4 .pri{font-family:var(--mono);font-size:11px;color:var(--ink-soft);font-weight:400;}
.ln{font-size:13.5px;line-height:1.55;margin-bottom:5px;color:var(--ink-soft);}
.ln b{font-weight:700;}
.ln.sy b{color:var(--ink);} .ln.sr b{color:var(--blue);} .ln.cq b{color:var(--gold);} .ln.rx b{color:var(--green);}
.mermaid{background:var(--paper-2);border:1px solid var(--rule-soft);border-radius:10px;padding:16px;margin:4px 0 18px;text-align:center;}
table.dbt{width:100%;border-collapse:collapse;font-size:13.5px;margin:4px 0 16px;border:1px solid var(--rule-soft);border-radius:8px;overflow:hidden;}
table.dbt th,table.dbt td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--rule-soft);}
table.dbt th{font-family:var(--serif);background:var(--paper-2);}
table.dbt tr:last-child td{border-bottom:none;}
.note{background:var(--blue-soft);border-left:3px solid var(--blue);border-radius:8px;padding:11px 15px;font-size:13.5px;margin-top:6px;}
footer{border-top:1px solid var(--rule);padding:36px 0 56px;background:var(--paper-2);margin-top:30px;}
.foot-in{display:flex;flex-wrap:wrap;gap:18px;align-items:center;justify-content:space-between;max-width:1000px;margin:0 auto;padding:0 24px;}
.foot-links{display:flex;gap:20px;flex-wrap:wrap;} .foot-links a{color:var(--ink-soft);font-size:14px;}
.foot-note{font-size:13px;color:var(--ink-soft);}
.hidden{display:none !important;}
</style>
</head>
<body>
<nav>
<div class="nav-in">
<a class="brand" href="index.html"><img src="logo.svg" alt="">brooks-lint</a>
<div class="nav-links">
<a href="index.html">← Home</a>
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub ↗</a>
</div>
</div>
</nav>
<header class="hero">
<div class="wrap">
<h1>The Gallery</h1>
<p>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.</p>
<div class="meta-row">
<span class="meta-pill"><b>8</b> worked examples</span>
<span class="meta-pill"><b>4</b> review modes</span>
<span class="meta-pill"><b>4</b> languages</span>
<span class="meta-pill">Python · TypeScript · Go · Java</span>
</div>
</div>
</header>
<div class="wrap">
<div class="filters" id="filters">
<button data-filter="all" class="active">All</button>
<button data-filter="review">PR Review</button>
<button data-filter="audit">Architecture</button>
<button data-filter="debt">Tech Debt</button>
<button data-filter="test">Test Quality</button>
</div>
</div>
<main class="wrap">
<!-- ============ PR REVIEW ============ -->
<section class="mode-group" data-mode="review">
<h2 class="mode-title">PR Review <span class="badge">Mode 1</span></h2>
<p class="mode-sub">Diagnoses a diff against the six production decay risks (R1R6).</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3>Seven-service payment processor</h3>
<span class="hs mid">55 / 100</span>
</div>
<p class="case-summary">A single method orchestrates seven services, creating a structural coupling trap where any change to payment, fraud, inventory, or notification touches the same method.</p>
<details>
<summary>▸ Input code</summary>
<pre class="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) &lt; 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 };
}
}</pre>
</details>
<div class="finding">
<h4>🔴 Change Propagation — Seven-service constructor signals a God Class</h4>
<div class="ln sy"><b>Symptom:</b> One class injects seven dependencies; one method orchestrates all of them.</div>
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Divergent Change; Martin — Clean Architecture — SRP</div>
<div class="ln rx"><b>Remedy:</b> Decompose into FraudCheckService, InventoryDeductionService, PaymentNotifier — inject 3, not 7.</div>
</div>
<div class="finding">
<h4>🔴 Change Propagation — Inventory loop embeds warehouse notification policy</h4>
<div class="ln sy"><b>Symptom:</b> A low-stock check (<code>&lt; 10</code>) fires a warehouse email with hardcoded recipient inside the payment loop.</div>
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality</div>
<div class="ln rx"><b>Remedy:</b> Publish a <code>StockLevelChanged</code> domain event a separate notifier subscribes to.</div>
</div>
<div class="finding">
<h4>🟡 Knowledge Duplication — <code>order.total + tax</code> computed three times</h4>
<div class="ln sy"><b>Symptom:</b> The same expression appears on three lines with no shared name.</div>
<div class="ln sr"><b>Source:</b> Hunt &amp; Thomas — DRY; Fowler — Refactoring — Duplicate Code</div>
<div class="ln rx"><b>Remedy:</b> Expose a computed <code>order.grandTotal</code>.</div>
</div>
<div class="finding">
<h4>🟡 Domain Model Distortion — <code>Order</code> is a mutable data bag</h4>
<div class="ln sy"><b>Symptom:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code> are all set externally; the object holds no behaviour.</div>
<div class="ln rx"><b>Remedy:</b> Give <code>Order</code> state-transition methods: <code>order.recordPayment(chargeId)</code>.</div>
</div>
</article>
</section>
<!-- ============ ARCHITECTURE ============ -->
<section class="mode-group" data-mode="audit">
<h2 class="mode-title">Architecture Audit <span class="badge">Mode 2</span></h2>
<p class="mode-sub">Maps module dependencies, renders a colour-coded graph, and flags layering and cycle violations.</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3>Dependency Inversion violation</h3>
<span class="hs mid">50 / 100</span>
</div>
<p class="case-summary">Domain entities import infrastructure directly, and a near-cycle forms between Product and PricingService.</p>
<pre class="mermaid">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</pre>
<div class="finding">
<h4>🔴 Dependency Disorder — Domain layer directly imports infrastructure</h4>
<div class="ln sy"><b>Symptom:</b> <code>Order.ts</code> imports <code>PostgresClient</code>; <code>User.ts</code> imports <code>RedisCache</code>.</div>
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Dependency Inversion Principle</div>
<div class="ln rx"><b>Remedy:</b> Define <code>IOrderRepository</code>/<code>IUserRepository</code> in the domain; move infra refs to <code>infra/</code>.</div>
</div>
<div class="finding">
<h4>🔴 Dependency Disorder — Product → PricingService (upward dependency)</h4>
<div class="ln sy"><b>Symptom:</b> Near-cycle <code>PricingService → Product → PricingService</code>.</div>
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
<div class="ln rx"><b>Remedy:</b> Pass pricing as a value object or define <code>IPricingPolicy</code> in the domain.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang go">Go</span>
<h3>Circular dependency across packages</h3>
<span class="hs bad">45 / 100</span>
</div>
<p class="case-summary"><code>auth → user → notification → auth</code> forms a strongly connected component — Go refuses to compile it.</p>
<pre class="mermaid">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</pre>
<div class="finding">
<h4>🔴 Dependency Disorder — Circular dependency auth → user → notification → auth</h4>
<div class="ln sy"><b>Symptom:</b> Three packages form a cycle; none compile, test, or deploy independently.</div>
<div class="ln sr"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
<div class="ln rx"><b>Remedy:</b> Extract interfaces into <code>pkg/contracts</code>; each package implements the interface its consumer defines.</div>
</div>
<div class="finding">
<h4>🟡 Domain Model Distortion — Bounded contexts crossed with no anti-corruption layer</h4>
<div class="ln sy"><b>Symptom:</b> Identity, profile, and notification contexts import each other with no translation layer.</div>
<div class="ln sr"><b>Source:</b> Evans — DDD — Bounded Context; Anti-Corruption Layer</div>
<div class="ln rx"><b>Remedy:</b> Define thin adapters at each context boundary.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang java">Java</span>
<h3>Textbook Clean Architecture</h3>
<span class="hs good">98 / 100</span>
</div>
<p class="case-summary">Dependencies flow inward, infra implements domain ports, no cycles. brooks-lint reports clean code as clean — and still offers one forward-looking suggestion.</p>
<pre class="mermaid">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</pre>
<div class="finding">
<h4>🟢 Suggestion — Monitor application service growth</h4>
<div class="ln sy"><b>Symptom:</b> <code>OrderService</code> and <code>UserService</code> are symmetric siblings that may accrue responsibilities without a split policy.</div>
<div class="ln sr"><b>Source:</b> Brooks — The Mythical Man-Month — Conceptual Integrity</div>
<div class="ln rx"><b>Remedy:</b> Document a "one service per use-case cluster" rule now, before the pattern calcifies.</div>
</div>
</article>
</section>
<!-- ============ TECH DEBT ============ -->
<section class="mode-group" data-mode="debt">
<h2 class="mode-title">Tech Debt Assessment <span class="badge">Mode 3</span></h2>
<p class="mode-sub">Classifies debt across the decay risks and scores each finding by Pain × Spread priority.</p>
<article class="case">
<div class="case-head">
<span class="lang java">Java</span>
<h3>Shotgun Surgery across six files</h3>
<span class="hs mid">56 / 100</span>
</div>
<p class="case-summary">Adding a currency means editing six files in six unrelated layers — three Critical findings that share one root cause.</p>
<table class="dbt">
<thead><tr><th>Risk</th><th>Findings</th><th>Avg Priority</th><th>Classification</th></tr></thead>
<tbody>
<tr><td>Change Propagation</td><td>2</td><td>6.5</td><td>Mixed (1 Critical + 1 Scheduled)</td></tr>
<tr><td>Knowledge Duplication</td><td>1</td><td>9.0</td><td>Critical</td></tr>
<tr><td>Domain Model Distortion</td><td>1</td><td>9.0</td><td>Critical</td></tr>
<tr><td>Cognitive Overload</td><td>1</td><td>6.0</td><td>Scheduled</td></tr>
</tbody>
</table>
<div class="finding">
<h4>🔴 Change Propagation — Shotgun Surgery across six modules <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy"><b>Symptom:</b> Adding EUR requires editing 6 files in 6 distinct layers with no architectural relationship.</div>
<div class="ln sr"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality</div>
<div class="ln rx"><b>Remedy:</b> Introduce a <code>Money</code> value object and a <code>MoneyFormatter</code> service.</div>
</div>
<div class="finding">
<h4>🔴 Knowledge Duplication — <code>$</code> as a magic literal in five files <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy"><b>Symptom:</b> The string <code>"$"</code> appears in 5 independent locations with no shared constant.</div>
<div class="ln sr"><b>Source:</b> Hunt &amp; Thomas — DRY; McConnell — Code Complete — Ch. 12</div>
<div class="ln rx"><b>Remedy:</b> Use <code>Currency.getSymbol(Locale)</code> in <code>MoneyFormatter</code>; remove all <code>"$"</code> literals.</div>
</div>
<div class="finding">
<h4>🔴 Domain Model Distortion — No <code>Money</code> type exists <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy"><b>Symptom:</b> All price/amount fields are raw <code>double</code>.</div>
<div class="ln sr"><b>Source:</b> Evans — DDD — Domain Model; Fowler — Refactoring — Data Class</div>
<div class="ln rx"><b>Remedy:</b> Introduce <code>record Money(BigDecimal amount, Currency currency)</code>.</div>
</div>
<div class="note"><b>Recommended focus:</b> All three Critical findings share one root cause — the absence of a <code>Money</code> value object. One intervention collapses three findings.</div>
</article>
</section>
<!-- ============ TEST QUALITY ============ -->
<section class="mode-group" data-mode="test">
<h2 class="mode-title">Test Quality Review <span class="badge">Mode 4</span></h2>
<p class="mode-sub">Audits an existing suite against six test-space decay risks (T1T6).</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3>Mock abuse</h3>
<span class="hs mid">60 / 100</span>
</div>
<p class="case-summary">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.</p>
<details>
<summary>▸ Input test</summary>
<pre class="code">it('should place an order successfully', () => {
const mockDb = mock&lt;Database&gt;();
const mockPayment = mock&lt;PaymentGateway&gt;();
const mockInventory = mock&lt;InventoryService&gt;();
const mockMailer = mock&lt;MailService&gt;();
const mockAudit = mock&lt;AuditLogger&gt;();
const mockCache = mock&lt;CacheService&gt;();
const mockMetrics = mock&lt;MetricsCollector&gt;();
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');
});</pre>
</details>
<div class="finding">
<h4>🔴 Mock Abuse — Seven mocks per test; setup dominates logic</h4>
<div class="ln sy"><b>Symptom:</b> 7 mock objects; 14 lines of setup vs 6 of assertions. No real collaborator is ever exercised.</div>
<div class="ln sr"><b>Source:</b> Osherove — The Art of Unit Testing (mock count > 3); Meszaros — xUnit Test Patterns</div>
<div class="ln rx"><b>Remedy:</b> Reduce mocks to ≤ 3, use in-memory fakes, assert on <code>result</code> first.</div>
</div>
<div class="finding">
<h4>🔴 Mock Abuse — All six assertions verify mock calls, not behaviour</h4>
<div class="ln sy"><b>Symptom:</b> Every assertion is <code>toHaveBeenCalledWith</code>; <code>result</code> is captured but never asserted.</div>
<div class="ln cq"><b>Consequence:</b> A <code>placeOrder</code> that calls every mock yet returns <code>null</code> or double-charges still passes.</div>
<div class="ln rx"><b>Remedy:</b> Assert observable output: <code>expect(result.status).toBe('confirmed')</code>.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang py">Python</span>
<h3>Inverted test pyramid</h3>
<span class="hs mid">55 / 100</span>
</div>
<p class="case-summary">Only 16% of tests are unit tests; an E2E-heavy suite takes ~9 minutes and blocks fast CI feedback.</p>
<details>
<summary>▸ Suite overview</summary>
<pre class="code">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%</pre>
</details>
<div class="finding">
<h4>🔴 Architecture Mismatch — Fully inverted test pyramid</h4>
<div class="ln sy"><b>Symptom:</b> Only 24 of 154 tests (16%) are unit tests; E2E + integration = 84%.</div>
<div class="ln sr"><b>Source:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns</div>
<div class="ln rx"><b>Remedy:</b> Target 70% unit; reduce E2E to 58 critical smoke tests.</div>
</div>
<div class="finding">
<h4>🔴 Architecture Mismatch — 9-minute suite blocks CI fast-feedback</h4>
<div class="ln sy"><b>Symptom:</b> Full suite ~542s, dominated by 8s E2E tests.</div>
<div class="ln rx"><b>Remedy:</b> Split CI: (1) unit only, &lt; 60s, blocks merge; (2) integration + E2E async, non-blocking.</div>
</div>
<div class="finding">
<h4>🟡 Coverage Illusion — Core domain untested at unit level</h4>
<div class="ln sy"><b>Symptom:</b> <code>tests/unit/</code> covers only validators and formatters — no checkout, login, order, or payment.</div>
<div class="ln sr"><b>Source:</b> Feathers — Working Effectively with Legacy Code</div>
<div class="ln rx"><b>Remedy:</b> Start with <code>test_checkout_flow.py</code> and <code>test_payment_api.py</code>.</div>
</div>
</article>
</section>
</main>
<footer>
<div class="foot-in">
<div class="foot-links">
<a href="index.html">Home</a>
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/docs/gallery.md" target="_blank" rel="noopener">Gallery source (Markdown)</a>
</div>
<div class="foot-note">MIT License — Built by hyhmrright</div>
</div>
</footer>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: true,
theme: 'base',
themeVariables: {
fontFamily: 'ui-monospace, Menlo, monospace',
primaryColor: '#fffdf8', primaryBorderColor: '#e3d8c2', primaryTextColor: '#2a2520',
lineColor: '#9c8f74', clusterBkg: '#f3ead8', clusterBorder: '#e3d8c2'
}
});
</script>
<script>
(function () {
var btns = document.querySelectorAll('#filters button');
var groups = document.querySelectorAll('.mode-group');
btns.forEach(function (b) {
b.addEventListener('click', function () {
btns.forEach(function (x) { x.classList.remove('active'); });
b.classList.add('active');
var f = b.getAttribute('data-filter');
groups.forEach(function (g) {
g.classList.toggle('hidden', f !== 'all' && g.getAttribute('data-mode') !== f);
});
});
});
})();
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

+549
View File
@@ -0,0 +1,549 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="google-site-verification" content="899Jw4jb5ux928e2-cYHEoRy70zaMqWIXL-7YK1Nd2U" />
<title>brooks-lint — AI code reviews grounded in twelve classic engineering books</title>
<meta name="description" content="brooks-lint diagnoses your code against twelve classic software engineering books, producing structured Symptom → Source → Consequence → Remedy findings every time.">
<link rel="icon" type="image/svg+xml" href="logo.svg">
<!-- Open Graph -->
<meta property="og:title" content="brooks-lint">
<meta property="og:description" content="AI code reviews grounded in twelve classic engineering books. Consistent. Traceable. Actionable.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://hyhmrright.github.io/brooks-lint/">
<meta property="og:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="brooks-lint — AI code reviews grounded in twelve engineering classics">
<meta name="twitter:description" content="Decay-risk diagnostics with book citations, severity labels, and a 0100 Health Score. Consistent. Traceable. Actionable.">
<meta name="twitter:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
<link rel="canonical" href="https://hyhmrright.github.io/brooks-lint/">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "brooks-lint",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Claude Code, Gemini CLI, Codex CLI, GitHub Actions",
"description": "AI code reviews grounded in twelve classic software engineering books. Decay-risk diagnostics with book citations, severity labels, and six analysis modes including full-sweep auto-fix.",
"url": "https://hyhmrright.github.io/brooks-lint/",
"image": "https://hyhmrright.github.io/brooks-lint/hero.png",
"softwareVersion": "1.3.0",
"license": "https://github.com/hyhmrright/brooks-lint/blob/main/LICENSE",
"codeRepository": "https://github.com/hyhmrright/brooks-lint",
"keywords": "AI code review, code quality, tech debt, architecture audit, test quality, Claude Code plugin, refactoring, clean architecture",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
}
</script>
<style>
:root {
--paper: #f7f1e4;
--paper-2: #fbf6ec;
--card: #fffdf8;
--ink: #2a2520;
--ink-soft: #5c5347;
--rule: #e3d8c2;
--rule-soft: #ece2cf;
--blue: #2d4a7c;
--blue-soft: #e7edf5;
--green: #2b8a3e;
--gold: #b07d2b;
--red: #b23b3b;
--ink-dark: #211d18;
--serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
--mono: "SF Mono", "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
color: var(--ink);
background: var(--paper);
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
h1, h2, h3, .serif { font-family: var(--serif); font-weight: 700; letter-spacing: -0.01em; }
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
/* paper grain overlay */
body::before {
content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
background-image: radial-gradient(rgba(120,90,40,0.04) 1px, transparent 1px);
background-size: 4px 4px; opacity: .6;
}
main, nav, footer { position: relative; z-index: 1; }
/* ---- Nav ---- */
nav {
position: sticky; top: 0; z-index: 50;
background: rgba(247,241,228,0.85);
backdrop-filter: saturate(140%) blur(10px);
border-bottom: 1px solid var(--rule);
}
.nav-in { display: flex; align-items: center; gap: 20px; height: 64px; }
.brand { display: flex; align-items: center; gap: 10px; font-family: var(--serif); font-weight: 700; font-size: 19px; color: var(--ink); }
.brand img { width: 30px; height: 30px; border-radius: 7px; }
.nav-links { display: flex; gap: 22px; margin-left: auto; align-items: center; }
.nav-links a { color: var(--ink-soft); font-size: 14.5px; }
.nav-links a:hover { color: var(--ink); text-decoration: none; }
.lang-toggle {
display: inline-flex; border: 1px solid var(--rule); border-radius: 999px; overflow: hidden;
font-size: 13px; background: var(--card);
}
.lang-toggle button {
border: none; background: transparent; padding: 5px 12px; cursor: pointer;
color: var(--ink-soft); font-family: var(--sans); font-weight: 600;
}
.lang-toggle button.active { background: var(--ink); color: var(--paper-2); }
.btn {
display: inline-block; padding: 10px 20px; border-radius: 8px; font-weight: 600; font-size: 15px;
border: 1px solid transparent; cursor: pointer; transition: transform .08s ease, box-shadow .15s ease;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn-primary { background: var(--ink); color: var(--paper-2); box-shadow: 0 2px 0 var(--gold); }
.btn-ghost { background: var(--card); color: var(--ink); border-color: var(--rule); }
@media (max-width: 720px) { .nav-links a.hide-sm { display: none; } }
/* ---- Hero ---- */
.hero { padding: 76px 0 56px; text-align: center; }
.eyebrow {
display: inline-block; font-family: var(--mono); font-size: 12.5px; letter-spacing: .12em;
text-transform: uppercase; color: var(--gold); border: 1px solid var(--rule);
padding: 5px 12px; border-radius: 999px; margin-bottom: 22px; background: var(--card);
}
.hero h1 { font-size: clamp(34px, 5.4vw, 56px); line-height: 1.08; max-width: 18ch; margin: 0 auto 18px; }
.hero .lede { font-size: clamp(17px, 2.2vw, 20px); color: var(--ink-soft); max-width: 60ch; margin: 0 auto 14px; }
.quote {
max-width: 56ch; margin: 26px auto 8px; font-family: var(--serif); font-style: italic;
font-size: 18px; color: var(--ink); border-left: 3px solid var(--gold); padding-left: 18px; text-align: left;
}
.quote cite { display: block; font-style: normal; font-size: 14px; color: var(--ink-soft); margin-top: 6px; }
.cta-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
.hero-shot { margin: 46px auto 0; max-width: 900px; }
.hero-shot img { width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--rule);
box-shadow: 0 18px 50px rgba(80,55,15,.16); display: block; }
/* ---- Stat strip ---- */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--rule);
border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; margin-top: 52px; }
.stat { background: var(--card); padding: 22px 14px; text-align: center; }
.stat b { display: block; font-family: var(--serif); font-size: clamp(26px, 4vw, 36px); color: var(--blue); line-height: 1; }
.stat span { font-size: 13px; color: var(--ink-soft); display: block; margin-top: 8px; }
@media (max-width: 640px) { .stats { grid-template-columns: repeat(2, 1fr); } }
/* ---- Section scaffolding ---- */
section { padding: 64px 0; }
.section-head { text-align: center; max-width: 64ch; margin: 0 auto 44px; }
.section-head h2 { font-size: clamp(26px, 3.6vw, 38px); margin-bottom: 12px; }
.section-head p { color: var(--ink-soft); font-size: 17px; }
.alt { background: var(--paper-2); border-top: 1px solid var(--rule-soft); border-bottom: 1px solid var(--rule-soft); }
/* ---- Feature grid ---- */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
@media (max-width: 900px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .grid-4 { grid-template-columns: 1fr; } }
.feature { background: var(--card); border: 1px solid var(--rule); border-radius: 12px; padding: 24px; }
.feature .ic { font-size: 26px; margin-bottom: 12px; display: block; }
.feature h3 { font-size: 18px; margin-bottom: 8px; }
.feature p { font-size: 14.5px; color: var(--ink-soft); }
/* ---- Decay risks ---- */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
@media (max-width: 860px) { .grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .grid-3 { grid-template-columns: 1fr; } }
.risk { background: var(--card); border: 1px solid var(--rule); border-left: 4px solid var(--blue);
border-radius: 10px; padding: 20px; }
.risk .code { font-family: var(--mono); font-size: 12px; color: var(--gold); font-weight: 700; letter-spacing: .06em; }
.risk h3 { font-size: 17px; margin: 4px 0 8px; }
.risk p { font-size: 14px; color: var(--ink-soft); }
.risk .src { font-size: 12px; color: var(--ink-soft); margin-top: 12px; font-style: italic; opacity: .85; }
/* ---- Sample finding ---- */
.sample { background: var(--ink-dark); border-radius: 14px; padding: 30px; color: #e9e2d3; font-size: 15px; }
.sample .score { font-family: var(--serif); font-size: 22px; color: #fff; margin-bottom: 6px; }
.sample .score b { color: var(--red); }
.sample .summary { color: #b9b0a0; font-style: italic; font-size: 14.5px; margin-bottom: 22px; }
.finding { border-top: 1px solid #3a342c; padding-top: 18px; margin-top: 18px; }
.finding h4 { font-family: var(--serif); font-size: 16px; color: #fff; margin-bottom: 10px; }
.finding .dot { color: var(--red); }
.finding .row { font-size: 14px; margin-bottom: 7px; line-height: 1.55; }
.finding .row b { color: var(--gold); font-weight: 700; }
.finding .row.src b { color: #7fa8e0; }
.finding .row.fix b { color: #6fcf83; }
/* ---- Benchmark table ---- */
.table-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: 12px; background: var(--card); }
table { width: 100%; border-collapse: collapse; font-size: 15px; min-width: 460px; }
th, td { padding: 13px 18px; text-align: left; border-bottom: 1px solid var(--rule-soft); }
th { font-family: var(--serif); font-size: 15px; }
td.c, th.c { text-align: center; }
tr:last-child td { border-bottom: none; }
tbody tr:last-child { font-weight: 700; background: var(--blue-soft); }
.yes { color: var(--green); font-weight: 700; }
.no { color: var(--red); font-weight: 700; }
/* ---- Books ---- */
.books { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; max-width: 880px; margin: 0 auto; }
.book-chip { background: var(--card); border: 1px solid var(--rule); border-radius: 8px;
padding: 9px 14px; font-size: 14px; }
.book-chip b { font-family: var(--serif); }
.book-chip span { color: var(--ink-soft); font-size: 12.5px; }
/* ---- Install ---- */
.code-block { position: relative; background: var(--ink-dark); border-radius: 12px; padding: 22px 22px;
font-family: var(--mono); font-size: 14px; color: #e9e2d3; overflow-x: auto; margin-bottom: 16px; }
.code-block .cmt { color: #8a8170; }
.code-block .tok { color: #6fcf83; }
.copy-btn { position: absolute; top: 12px; right: 12px; background: #3a342c; color: #cfc7b6;
border: none; border-radius: 6px; padding: 5px 11px; font-size: 12px; cursor: pointer; font-family: var(--sans); }
.copy-btn:hover { background: #4a4338; }
.install-grid { display: grid; grid-template-columns: 1fr; gap: 0; max-width: 760px; margin: 0 auto; }
.install-label { font-family: var(--serif); font-size: 18px; margin: 18px 0 10px; }
/* ---- Footer ---- */
footer { border-top: 1px solid var(--rule); padding: 40px 0 56px; background: var(--paper-2); }
.foot-in { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; justify-content: space-between; }
.foot-links { display: flex; gap: 20px; flex-wrap: wrap; }
.foot-links a { color: var(--ink-soft); font-size: 14px; }
.foot-note { font-size: 13px; color: var(--ink-soft); }
[data-lang-hide] { display: none !important; }
</style>
</head>
<body>
<nav>
<div class="wrap nav-in">
<a class="brand" href="#top"><img src="logo.svg" alt="brooks-lint logo">brooks-lint</a>
<div class="nav-links">
<a class="hide-sm" href="#risks" data-en="Decay Risks" data-zh="腐化风险">Decay Risks</a>
<a class="hide-sm" href="#sample" data-en="Example" data-zh="示例">Example</a>
<a class="hide-sm" href="#benchmark" data-en="Benchmark" data-zh="基准">Benchmark</a>
<a class="hide-sm" href="gallery.html" data-en="Gallery" data-zh="画廊">Gallery</a>
<a class="hide-sm" href="#install" data-en="Install" data-zh="安装">Install</a>
<span class="lang-toggle">
<button data-set-lang="en">EN</button>
<button data-set-lang="zh">中文</button>
</span>
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub ↗</a>
</div>
</div>
</nav>
<main id="top">
<!-- HERO -->
<header class="hero">
<div class="wrap">
<span class="eyebrow" data-en="Claude Code · Codex · Gemini plugin" data-zh="Claude Code · Codex · Gemini 插件">Claude Code · Codex · Gemini plugin</span>
<h1 data-en="AI code reviews grounded in twelve classic engineering books"
data-zh="扎根于十二本经典工程著作的 AI 代码审查">AI code reviews grounded in twelve classic engineering books</h1>
<p class="lede"
data-en="Most tools count lines and complexity. brooks-lint diagnoses your code against twelve decay risks synthesized from the classics — every finding cited, scored, and remedied."
data-zh="多数工具只统计行数和复杂度。brooks-lint 把你的代码对照从十二本经典提炼的腐化风险逐一诊断——每条结论都有出处、有评分、有修复方案。">
Most tools count lines and complexity. brooks-lint diagnoses your code against twelve decay risks synthesized from the classics — every finding cited, scored, and remedied.
</p>
<blockquote class="quote"
data-en="“The bearing of a child takes nine months, no matter how many women are assigned.”"
data-zh="“一个孩子的孕育需要九个月,无论你派多少人去。”">
“The bearing of a child takes nine months, no matter how many women are assigned.”
<cite data-en="— Frederick Brooks, The Mythical Man-Month (1975)" data-zh="—— 弗雷德里克·布鲁克斯,《人月神话》(1975)">— Frederick Brooks, The Mythical Man-Month (1975)</cite>
</blockquote>
<div class="cta-row">
<a class="btn btn-primary" href="#install" data-en="Get Started" data-zh="开始使用">Get Started</a>
<a class="btn btn-ghost" href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener" data-en="View on GitHub" data-zh="GitHub 查看">View on GitHub</a>
</div>
<div class="hero-shot">
<img src="demo.gif" alt="brooks-lint reviewing code: a /brooks-review command produces a 28/100 health score and cited Symptom → Source → Consequence → Remedy findings" loading="eager">
</div>
<div class="stats">
<div class="stat"><b>12</b><span data-en="classic books" data-zh="本经典著作">classic books</span></div>
<div class="stat"><b>12</b><span data-en="decay risks (R1R6 · T1T6)" data-zh="腐化风险 (R1R6 · T1T6)">decay risks (R1R6 · T1T6)</span></div>
<div class="stat"><b>94%</b><span data-en="benchmark pass rate" data-zh="基准通过率">benchmark pass rate</span></div>
<div class="stat"><b>6</b><span data-en="independent skills" data-zh="个独立技能">independent skills</span></div>
</div>
</div>
</header>
<!-- FEATURES -->
<section>
<div class="wrap">
<div class="section-head">
<h2 data-en="Not a linter. A second opinion from the canon." data-zh="不是 linter,是来自经典的第二意见。">Not a linter. A second opinion from the canon.</h2>
<p data-en="Linters catch syntax. brooks-lint catches architectural drift, knowledge silos, and domain distortion — the slow problems that cost teams months."
data-zh="linter 抓语法。brooks-lint 抓架构漂移、知识孤岛和领域失真——那些拖垮团队数月却无人察觉的慢性病。">Linters catch syntax. brooks-lint catches architectural drift, knowledge silos, and domain distortion — the slow problems that cost teams months.</p>
</div>
<div class="grid-4">
<div class="feature">
<span class="ic">⚖️</span>
<h3 data-en="The Iron Law" data-zh="铁律格式">The Iron Law</h3>
<p data-en="Every finding follows one shape: Symptom → Source → Consequence → Remedy. No vague vibes, ever."
data-zh="每条结论遵循同一形态:症状 → 根源 → 后果 → 修复。绝不含糊其辞。">Every finding follows one shape: Symptom → Source → Consequence → Remedy. No vague vibes, ever.</p>
</div>
<div class="feature">
<span class="ic">📚</span>
<h3 data-en="Cited to the books" data-zh="书有出处">Cited to the books</h3>
<p data-en="Brooks, Fowler, Martin, Ousterhout, Evans, Feathers, Meszaros and more — each finding names the author and principle."
data-zh="布鲁克斯、Fowler、Martin、Ousterhout、Evans、Feathers、Meszaros 等——每条结论都点名作者与原则。">Brooks, Fowler, Martin, Ousterhout, Evans, Feathers, Meszaros and more — each finding names the author and principle.</p>
</div>
<div class="feature">
<span class="ic">🎯</span>
<h3 data-en="Six focused skills" data-zh="六个专注技能">Six focused skills</h3>
<p data-en="PR Review, Architecture Audit, Tech Debt, Test Quality, Health Dashboard, and a Full Sweep that auto-fixes."
data-zh="PR 审查、架构审计、技术债、测试质量、健康仪表盘,以及会自动修复的全面扫描。">PR Review, Architecture Audit, Tech Debt, Test Quality, Health Dashboard, and a Full Sweep that auto-fixes.</p>
</div>
<div class="feature">
<span class="ic">🔌</span>
<h3 data-en="Zero config, any language" data-zh="零配置·任意语言">Zero config, any language</h3>
<p data-en="Works in Claude Code, Codex CLI, and Gemini CLI. No plugins to wire up, no language limits."
data-zh="支持 Claude Code、Codex CLI、Gemini CLI。无需接线,不限语言。">Works in Claude Code, Codex CLI, and Gemini CLI. No plugins to wire up, no language limits.</p>
</div>
</div>
</div>
</section>
<!-- DECAY RISKS -->
<section id="risks" class="alt">
<div class="wrap">
<div class="section-head">
<h2 data-en="The Six Production Decay Risks" data-zh="六大生产代码腐化风险">The Six Production Decay Risks</h2>
<p data-en="Synthesized from the twelve books. Six more (T1T6) cover test-suite decay."
data-zh="提炼自十二本经典。另有六项(T1–T6)覆盖测试套件的腐化。">Synthesized from the twelve books. Six more (T1T6) cover test-suite decay.</p>
</div>
<div class="grid-3">
<div class="risk">
<span class="code">R1</span>
<h3>🧠 <span data-en="Cognitive Overload" data-zh="认知过载">Cognitive Overload</span></h3>
<p data-en="How much mental effort does it take to understand this?" data-zh="理解这段代码要耗费多少脑力?">How much mental effort does it take to understand this?</p>
<div class="src">Code Complete · Refactoring · DDD · Philosophy of SD</div>
</div>
<div class="risk">
<span class="code">R2</span>
<h3>🔗 <span data-en="Change Propagation" data-zh="变更扩散">Change Propagation</span></h3>
<p data-en="How many unrelated things break on one change?" data-zh="改一处会牵连多少无关之物崩坏?">How many unrelated things break on one change?</p>
<div class="src">Refactoring · Clean Architecture · Pragmatic · SE@Google</div>
</div>
<div class="risk">
<span class="code">R3</span>
<h3>📋 <span data-en="Knowledge Duplication" data-zh="知识重复">Knowledge Duplication</span></h3>
<p data-en="Is the same decision expressed in multiple places?" data-zh="同一个决策是否散落在多处表达?">Is the same decision expressed in multiple places?</p>
<div class="src">Pragmatic · Refactoring · DDD</div>
</div>
<div class="risk">
<span class="code">R4</span>
<h3>🌀 <span data-en="Accidental Complexity" data-zh="偶发复杂度">Accidental Complexity</span></h3>
<p data-en="Is the code more complex than the problem itself?" data-zh="代码是否比问题本身更复杂?">Is the code more complex than the problem itself?</p>
<div class="src">Refactoring · Code Complete · Brooks · Philosophy of SD</div>
</div>
<div class="risk">
<span class="code">R5</span>
<h3>🏗️ <span data-en="Dependency Disorder" data-zh="依赖失序">Dependency Disorder</span></h3>
<p data-en="Do dependencies flow in a consistent direction?" data-zh="依赖是否朝一致的方向流动?">Do dependencies flow in a consistent direction?</p>
<div class="src">Clean Architecture · Brooks · Pragmatic · SE@Google</div>
</div>
<div class="risk">
<span class="code">R6</span>
<h3>🗺️ <span data-en="Domain Model Distortion" data-zh="领域模型失真">Domain Model Distortion</span></h3>
<p data-en="Does the code faithfully represent the domain?" data-zh="代码是否忠实地表达了业务领域?">Does the code faithfully represent the domain?</p>
<div class="src">DDD · Refactoring</div>
</div>
</div>
</div>
</section>
<!-- SAMPLE -->
<section id="sample">
<div class="wrap">
<div class="section-head">
<h2 data-en="What a finding looks like" data-zh="一条结论长什么样">What a finding looks like</h2>
<p data-en="Same messy method, two of the eight findings brooks-lint produces — each one cited and actionable."
data-zh="同一段混乱方法,brooks-lint 给出的八条结论中的两条——每条都有出处、可执行。">Same messy method, two of the eight findings brooks-lint produces — each one cited and actionable.</p>
</div>
<div class="sample">
<div class="score" data-en="Health Score: <b>28/100</b>" data-zh="健康分:<b>28/100</b>">Health Score: <b>28/100</b></div>
<div class="summary"
data-en="This method concentrates four unrelated business responsibilities, hides a logic bug that silently suppresses email notifications, and is wide open to SQL injection."
data-zh="这个方法把四项互不相关的业务职责堆在一起,藏着一个让邮件通知静默失效的逻辑 bug,并且对 SQL 注入门户大开。">
This method concentrates four unrelated business responsibilities, hides a logic bug that silently suppresses email notifications, and is wide open to SQL injection.
</div>
<div class="finding">
<h4><span class="dot">🔴</span> R2 — <span data-en="One method changes for four unrelated reasons" data-zh="一个方法因四个无关原因而改动">One method changes for four unrelated reasons</span></h4>
<div class="row"><b data-en="Symptom:" data-zh="症状:">Symptom:</b>
<span data-en="update_profile does field updates, email notifications, loyalty recalculation, and cache invalidation in one body."
data-zh="update_profile 在一个方法体里同时做字段更新、邮件通知、积分重算和缓存失效。">update_profile does field updates, email notifications, loyalty recalculation, and cache invalidation in one body.</span></div>
<div class="row src"><b data-en="Source:" data-zh="根源:">Source:</b>
<span data-en="Fowler — Refactoring — Divergent Change; Hunt &amp; Thomas — Orthogonality"
data-zh="Fowler《重构》——发散式变化;Hunt &amp; Thomas——正交性">Fowler — Refactoring — Divergent Change; Hunt &amp; Thomas — Orthogonality</span></div>
<div class="row"><b data-en="Consequence:" data-zh="后果:">Consequence:</b>
<span data-en="A change to the loyalty formula risks breaking email notifications. Every edit carries regression risk across four domains."
data-zh="改一下积分公式就可能弄坏邮件通知。每次编辑都在四个领域同时承担回归风险。">A change to the loyalty formula risks breaking email notifications. Every edit carries regression risk across four domains.</span></div>
<div class="row fix"><b data-en="Remedy:" data-zh="修复:">Remedy:</b>
<span data-en="Extract NotificationService, LoyaltyService, UserCacheInvalidator. update_profile should orchestrate, not implement."
data-zh="抽出 NotificationService、LoyaltyService、UserCacheInvalidator。update_profile 应当编排而非实现。">Extract NotificationService, LoyaltyService, UserCacheInvalidator. update_profile should orchestrate, not implement.</span></div>
</div>
<div class="finding">
<h4><span class="dot">🔴</span> R6 — <span data-en="Silent logic bug: notification never fires" data-zh="静默逻辑 bug:通知永不触发">Silent logic bug: notification never fires</span></h4>
<div class="row"><b data-en="Symptom:" data-zh="症状:">Symptom:</b>
<span data-en="user['email'] = email runs before if user['email'] != email — the condition is always False, the code is dead."
data-zh="user['email'] = email 在 if user['email'] != email 之前执行——条件永远为 False,该分支是死代码。">user['email'] = email runs before if user['email'] != email — the condition is always False, the code is dead.</span></div>
<div class="row src"><b data-en="Source:" data-zh="根源:">Source:</b>
<span data-en="McConnell — Code Complete — Ch. 17: Unusual Control Structures"
data-zh="McConnell《代码大全》——第 17 章:非常规控制结构">McConnell — Code Complete — Ch. 17: Unusual Control Structures</span></div>
<div class="row"><b data-en="Consequence:" data-zh="后果:">Consequence:</b>
<span data-en="Users are never notified when their email changes. A business rule is silently violated while the system looks fine."
data-zh="用户改邮箱时永远收不到通知。系统看似正常,业务规则却被悄悄违反。">Users are never notified when their email changes. A business rule is silently violated while the system looks fine.</span></div>
<div class="row fix"><b data-en="Remedy:" data-zh="修复:">Remedy:</b>
<span data-en="Capture old_email before any mutation. Compare against old_email, not the already-overwritten value."
data-zh="在任何修改前先捕获 old_email,与 old_email 比较,而非已被覆盖的值。">Capture old_email before any mutation. Compare against old_email, not the already-overwritten value.</span></div>
</div>
</div>
</div>
</section>
<!-- BENCHMARK -->
<section id="benchmark" class="alt">
<div class="wrap">
<div class="section-head">
<h2 data-en="Consistency is the point" data-zh="一致性才是重点">Consistency is the point</h2>
<p data-en="Tested across PR review, architecture audit, and tech debt scenarios. The gap isn't what Claude can find — it's what it finds every single time, with evidence."
data-zh="在 PR 审查、架构审计、技术债三类场景上测试。差距不在于 Claude 能找到什么,而在于它能否每一次都带着证据找到。">Tested across PR review, architecture audit, and tech debt scenarios. The gap isn't what Claude can find — it's what it finds every single time, with evidence.</p>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th data-en="Criterion" data-zh="评判标准">Criterion</th>
<th class="c">brooks-lint</th>
<th class="c" data-en="Claude alone" data-zh="纯 Claude">Claude alone</th>
</tr>
</thead>
<tbody>
<tr><td data-en="Structured Symptom→Source→Consequence→Remedy" data-zh="结构化 症状→根源→后果→修复">Structured Symptom→Source→Consequence→Remedy</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
<tr><td data-en="Book citation per finding" data-zh="每条结论附书目出处">Book citation per finding</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
<tr><td data-en="Consistent severity labels 🔴🟡🟢" data-zh="一致的严重度标签 🔴🟡🟢">Consistent severity labels 🔴🟡🟢</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
<tr><td data-en="Health Score (0100)" data-zh="健康分(0100">Health Score (0100)</td><td class="c yes">100%</td><td class="c no">0%</td></tr>
<tr><td data-en="Overall pass rate" data-zh="总体通过率">Overall pass rate</td><td class="c">94%</td><td class="c">16%</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- BOOKS -->
<section>
<div class="wrap">
<div class="section-head">
<h2 data-en="Standing on twelve giants" data-zh="站在十二位巨人的肩上">Standing on twelve giants</h2>
<p data-en="The decay risks are our synthesis of their ideas, applied to modern code quality."
data-zh="这些腐化风险是我们对他们思想的综合,应用于现代代码质量评估。">The decay risks are our synthesis of their ideas, applied to modern code quality.</p>
</div>
<div class="books">
<span class="book-chip"><b>The Mythical Man-Month</b> · <span>Brooks</span></span>
<span class="book-chip"><b>Code Complete</b> · <span>McConnell</span></span>
<span class="book-chip"><b>Refactoring</b> · <span>Fowler</span></span>
<span class="book-chip"><b>Clean Architecture</b> · <span>Martin</span></span>
<span class="book-chip"><b>The Pragmatic Programmer</b> · <span>Hunt &amp; Thomas</span></span>
<span class="book-chip"><b>Domain-Driven Design</b> · <span>Evans</span></span>
<span class="book-chip"><b>A Philosophy of Software Design</b> · <span>Ousterhout</span></span>
<span class="book-chip"><b>Software Engineering at Google</b> · <span>Winters et al.</span></span>
<span class="book-chip"><b>The Art of Unit Testing</b> · <span>Osherove</span></span>
<span class="book-chip"><b>How Google Tests Software</b> · <span>Whittaker et al.</span></span>
<span class="book-chip"><b>Working Effectively with Legacy Code</b> · <span>Feathers</span></span>
<span class="book-chip"><b>xUnit Test Patterns</b> · <span>Meszaros</span></span>
</div>
</div>
</section>
<!-- INSTALL -->
<section id="install" class="alt">
<div class="wrap">
<div class="section-head">
<h2 data-en="Get started in seconds" data-zh="几秒钟即可上手">Get started in seconds</h2>
<p data-en="Pick your tool. Then just ask it to review your code, audit your architecture, or assess tech debt."
data-zh="选择你的工具,然后让它审查代码、审计架构或评估技术债即可。">Pick your tool. Then just ask it to review your code, audit your architecture, or assess tech debt.</p>
</div>
<div class="install-grid">
<div class="install-label">Claude Code</div>
<div class="code-block">
<button class="copy-btn" data-copy="/plugin marketplace add hyhmrright/brooks-lint
/plugin install brooks-lint@brooks-lint-marketplace">Copy</button><span class="cmt"># add the marketplace, then install</span>
<span class="tok">/plugin</span> marketplace add hyhmrright/brooks-lint
<span class="tok">/plugin</span> install brooks-lint@brooks-lint-marketplace</div>
<div class="install-label">Gemini CLI</div>
<div class="code-block">
<button class="copy-btn" data-copy="/extensions install https://github.com/hyhmrright/brooks-lint">Copy</button><span class="tok">/extensions</span> install https://github.com/hyhmrright/brooks-lint</div>
<div class="install-label">Codex CLI</div>
<div class="code-block">
<button class="copy-btn" data-copy="Install the brooks-lint skill from hyhmrright/brooks-lint">Copy</button><span class="cmt"># just say this in a Codex session</span>
Install the brooks-lint skill from hyhmrright/brooks-lint</div>
<div class="install-label" data-en="Then, in any session:" data-zh="然后,在任意会话里:">Then, in any session:</div>
<div class="code-block">
<button class="copy-btn" data-copy="/brooks-review">Copy</button><span class="tok">/brooks-review</span> <span class="cmt"># or /brooks-audit · /brooks-debt · /brooks-test · /brooks-health · /brooks-sweep</span></div>
</div>
<div style="text-align:center; margin-top:34px;">
<a class="btn btn-primary" href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener" data-en="Star on GitHub ⭐" data-zh="去 GitHub 点个 Star ⭐">Star on GitHub ⭐</a>
</div>
</div>
</section>
</main>
<footer>
<div class="wrap foot-in">
<div class="foot-links">
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub</a>
<a href="gallery.html" data-en="Gallery" data-zh="示例画廊">Gallery</a>
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener" data-en="Contributing" data-zh="参与贡献">Contributing</a>
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/CHANGELOG.md" target="_blank" rel="noopener" data-en="Changelog" data-zh="更新日志">Changelog</a>
</div>
<div class="foot-note" data-en="MIT License — Built by hyhmrright" data-zh="MIT 许可证 — 由 hyhmrright 构建">MIT License — Built by hyhmrright</div>
</div>
</footer>
<script>
(function () {
var STORE = "brooks-lint-lang";
function apply(lang) {
document.documentElement.lang = (lang === "zh") ? "zh-CN" : "en";
document.querySelectorAll("[data-en]").forEach(function (el) {
var v = el.getAttribute("data-" + lang);
if (v != null) el.innerHTML = v;
});
document.querySelectorAll(".lang-toggle button").forEach(function (b) {
b.classList.toggle("active", b.getAttribute("data-set-lang") === lang);
});
try { localStorage.setItem(STORE, lang); } catch (e) {}
}
document.querySelectorAll("[data-set-lang]").forEach(function (b) {
b.addEventListener("click", function () { apply(b.getAttribute("data-set-lang")); });
});
var saved;
try { saved = localStorage.getItem(STORE); } catch (e) {}
if (!saved) saved = (navigator.language || "").toLowerCase().indexOf("zh") === 0 ? "zh" : "en";
apply(saved);
// copy buttons
document.querySelectorAll(".copy-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
var text = btn.getAttribute("data-copy");
navigator.clipboard.writeText(text).then(function () {
var prev = btn.textContent;
btn.textContent = "✓";
setTimeout(function () { btn.textContent = prev; }, 1200);
});
});
});
})();
</script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<rect width="512" height="512" rx="80" fill="#1e293b"/>
<!-- Book spine -->
<rect x="108" y="96" width="48" height="320" rx="8" fill="#3b82f6"/>
<!-- Book pages -->
<rect x="152" y="112" width="252" height="288" rx="4" fill="#f8fafc"/>
<!-- Code lines -->
<rect x="180" y="160" width="120" height="12" rx="4" fill="#6366f1"/>
<rect x="180" y="188" width="80" height="12" rx="4" fill="#94a3b8"/>
<rect x="196" y="216" width="100" height="12" rx="4" fill="#94a3b8"/>
<rect x="196" y="244" width="60" height="12" rx="4" fill="#94a3b8"/>
<rect x="180" y="272" width="90" height="12" rx="4" fill="#6366f1"/>
<rect x="180" y="300" width="140" height="12" rx="4" fill="#94a3b8"/>
<!-- Check badge -->
<circle cx="360" cy="340" r="52" fill="#1e293b"/>
<circle cx="360" cy="340" r="44" fill="#22c55e"/>
<polyline points="337,340 353,356 385,322" fill="none" stroke="#fff" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://hyhmrright.github.io/brooks-lint/sitemap.xml
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://hyhmrright.github.io/brooks-lint/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://hyhmrright.github.io/brooks-lint/gallery.html</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>