@booklib/skills 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +23 -1
- package/README.md +55 -0
- package/benchmark/devto-post.md +178 -0
- package/benchmark/order-processing.original.js +158 -0
- package/benchmark/order-processing.pr-toolkit.js +181 -0
- package/benchmark/order-processing.skill-router.js +271 -0
- package/benchmark/review-report.md +129 -0
- package/bin/skills.js +327 -69
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/docs/index.html +62 -13
- package/package.json +4 -1
- package/scripts/gen-og.mjs +142 -0
- package/skills/skill-router/SKILL.md +23 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// code-after-custom.js
|
|
2
|
+
// Rewritten applying: clean-code-reviewer + design-patterns (via skill-router)
|
|
3
|
+
//
|
|
4
|
+
// Patterns applied (design-patterns skill):
|
|
5
|
+
// Strategy — payment methods (GoF: encapsulate interchangeable algorithms)
|
|
6
|
+
// Strategy — discount tiers (GoF: data-driven, open for extension)
|
|
7
|
+
// State — order lifecycle (GoF: each status owns its valid transitions)
|
|
8
|
+
// Singleton — stats (GoF: deliberate, clean, no accidental shared state)
|
|
9
|
+
// Observer — side effects (GoF: email/stats decoupled from business logic)
|
|
10
|
+
// Facade — module API (GoF: single clean surface, internals hidden)
|
|
11
|
+
// Template Method — validation (GoF: fixed skeleton, variable steps)
|
|
12
|
+
//
|
|
13
|
+
// Principles applied (clean-code-reviewer skill):
|
|
14
|
+
// N1/N2 — intention-revealing names at the right abstraction level
|
|
15
|
+
// F1/F2 — small functions, one responsibility each
|
|
16
|
+
// G5 — DRY: no duplicated confirmation block
|
|
17
|
+
// G25 — named constants, no magic numbers
|
|
18
|
+
// G23 — data-driven lookup, not if-chain
|
|
19
|
+
// G28 — encapsulated predicates
|
|
20
|
+
// G30 — functions do one thing
|
|
21
|
+
// Ch.7 — throw with context, never silent return false
|
|
22
|
+
// G4 — parameterised queries, no eval, no safety overrides
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const db = require('./db');
|
|
27
|
+
const mailer = require('./mailer');
|
|
28
|
+
|
|
29
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const TAX_RATE = 0.23;
|
|
32
|
+
|
|
33
|
+
// Strategy: discount tiers — add a new tier here, nothing else changes (G23, G25)
|
|
34
|
+
const DISCOUNT_BY_TIER = Object.freeze({
|
|
35
|
+
standard: 0,
|
|
36
|
+
premium: 0.10,
|
|
37
|
+
vip: 0.20,
|
|
38
|
+
staff: 0.50,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Strategy: payment processors — add a new method here, nothing else changes
|
|
42
|
+
const PAYMENT_PROCESSORS = Object.freeze({
|
|
43
|
+
card: {
|
|
44
|
+
charge(user, amount) {
|
|
45
|
+
// TODO: integrate real payment gateway
|
|
46
|
+
console.log(`Charging card (...${String(user.card).slice(-4)}) for ${formatMoney(amount)}`);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
paypal: {
|
|
51
|
+
charge(user, amount) {
|
|
52
|
+
// TODO: integrate PayPal SDK
|
|
53
|
+
console.log(`Charging PayPal ${user.paypal} for ${formatMoney(amount)}`);
|
|
54
|
+
return true;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
crypto: {
|
|
58
|
+
charge() {
|
|
59
|
+
throw new Error('Crypto payments are not yet implemented');
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ─── State: order lifecycle ───────────────────────────────────────────────────
|
|
65
|
+
// Each state owns its own transition rules.
|
|
66
|
+
// Adding a new status = adding one object. No existing guards change.
|
|
67
|
+
|
|
68
|
+
const ORDER_STATES = Object.freeze({
|
|
69
|
+
pending: {
|
|
70
|
+
canCancel: () => true,
|
|
71
|
+
canRefund: () => false,
|
|
72
|
+
},
|
|
73
|
+
paid: {
|
|
74
|
+
canCancel: () => true,
|
|
75
|
+
canRefund: () => true,
|
|
76
|
+
},
|
|
77
|
+
shipped: {
|
|
78
|
+
canCancel: () => false,
|
|
79
|
+
canRefund: () => false,
|
|
80
|
+
},
|
|
81
|
+
delivered: {
|
|
82
|
+
canCancel: () => false,
|
|
83
|
+
canRefund: () => true,
|
|
84
|
+
},
|
|
85
|
+
cancelled: {
|
|
86
|
+
canCancel: () => false,
|
|
87
|
+
canRefund: () => false,
|
|
88
|
+
},
|
|
89
|
+
refunded: {
|
|
90
|
+
canCancel: () => false,
|
|
91
|
+
canRefund: () => false,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function getOrderState(status) {
|
|
96
|
+
const state = ORDER_STATES[status];
|
|
97
|
+
if (!state) throw new Error(`Unknown order status: ${status}`);
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Singleton: stats ─────────────────────────────────────────────────────────
|
|
102
|
+
// Deliberate, clean singleton. Not an accidental module-scope variable.
|
|
103
|
+
|
|
104
|
+
const Stats = (() => {
|
|
105
|
+
const data = { orders: 0, revenue: 0, cancelled: 0 };
|
|
106
|
+
return {
|
|
107
|
+
recordPlaced(total) { data.orders++; data.revenue += total; },
|
|
108
|
+
recordCancelled(total) { data.cancelled++; data.revenue -= total; },
|
|
109
|
+
recordRefunded(total) { data.cancelled++; data.revenue -= total; },
|
|
110
|
+
snapshot() { return { ...data }; },
|
|
111
|
+
};
|
|
112
|
+
})();
|
|
113
|
+
|
|
114
|
+
// ─── Observer: event bus ──────────────────────────────────────────────────────
|
|
115
|
+
// Business logic emits events. Side effects (email, stats) register as listeners.
|
|
116
|
+
// Adding SMS or audit log = one new listener. Core functions unchanged.
|
|
117
|
+
|
|
118
|
+
const EventBus = (() => {
|
|
119
|
+
const listeners = {};
|
|
120
|
+
return {
|
|
121
|
+
on(event, fn) { (listeners[event] = listeners[event] || []).push(fn); },
|
|
122
|
+
emit(event, payload) { (listeners[event] || []).forEach(fn => fn(payload)); },
|
|
123
|
+
};
|
|
124
|
+
})();
|
|
125
|
+
|
|
126
|
+
// Register observers at startup — not inside business functions
|
|
127
|
+
EventBus.on('order.placed', ({ user, total }) => {
|
|
128
|
+
mailer.send(user.email, 'Order confirmed', `Your order total is ${formatMoney(total)}.`);
|
|
129
|
+
Stats.recordPlaced(total);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
EventBus.on('order.cancelled', ({ order, total }) => {
|
|
133
|
+
mailer.send(order.user_email, 'Order cancelled', 'Your order has been cancelled.');
|
|
134
|
+
Stats.recordCancelled(total);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
EventBus.on('order.refunded', ({ order, total }) => {
|
|
138
|
+
mailer.send(order.user_email, 'Refund processed', `Your refund of ${formatMoney(total)} is on its way.`);
|
|
139
|
+
Stats.recordRefunded(total);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ─── Validation (Template Method skeleton) ────────────────────────────────────
|
|
143
|
+
// Fixed skeleton: check preconditions, then run action. (G28, Ch.7)
|
|
144
|
+
|
|
145
|
+
function assertActiveUser(user) {
|
|
146
|
+
if (!user) throw new Error('user is required');
|
|
147
|
+
if (!user.active) throw new Error(`User ${user.id} is not active`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function assertValidOrder(order) {
|
|
151
|
+
if (!order?.items?.length) throw new Error('order must contain at least one item');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function assertSupportedPayment(method) {
|
|
155
|
+
if (!PAYMENT_PROCESSORS[method]) throw new Error(`Unsupported payment method: ${method}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Calculation (F1: one responsibility each) ────────────────────────────────
|
|
159
|
+
|
|
160
|
+
function calculateSubtotal(items) {
|
|
161
|
+
return items
|
|
162
|
+
.filter(item => item.qty > 0 && item.price > 0)
|
|
163
|
+
.reduce((sum, item) => sum + item.qty * item.price, 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function applyTierDiscount(amount, userTier) {
|
|
167
|
+
const rate = DISCOUNT_BY_TIER[userTier] ?? 0; // G25: named table, not magic number
|
|
168
|
+
return amount * (1 - rate);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function applyTax(amount) {
|
|
172
|
+
return amount * (1 + TAX_RATE);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Persistence ──────────────────────────────────────────────────────────────
|
|
176
|
+
// Each function does one thing. Parameterised queries throughout (G4).
|
|
177
|
+
|
|
178
|
+
async function persistOrder(order, user, total) {
|
|
179
|
+
await db.query(
|
|
180
|
+
'INSERT INTO orders (id, user_id, total, status) VALUES (?, ?, ?, ?)',
|
|
181
|
+
[order.id, user.id, total, 'paid'],
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function fetchOrder(orderId) {
|
|
186
|
+
const order = await db.query('SELECT * FROM orders WHERE id = ?', [orderId]);
|
|
187
|
+
if (!order) throw new Error(`Order not found: ${orderId}`);
|
|
188
|
+
return order;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function persistCancellation(orderId, reason) {
|
|
192
|
+
await db.query(
|
|
193
|
+
'UPDATE orders SET status = ?, reason = ? WHERE id = ?',
|
|
194
|
+
['cancelled', reason, orderId],
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function persistRefund(orderId) {
|
|
199
|
+
await db.query('UPDATE orders SET status = ? WHERE id = ?', ['refunded', orderId]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── Formatting ───────────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
function formatMoney(amount) {
|
|
205
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── Facade: public API ───────────────────────────────────────────────────────
|
|
209
|
+
// Callers import one clean surface. All internals (strategies, state,
|
|
210
|
+
// observer, singleton) are hidden and can evolve independently.
|
|
211
|
+
|
|
212
|
+
async function processOrder(order, user, paymentMethod) {
|
|
213
|
+
// Template Method: validate → calculate → charge → persist → notify (via Observer)
|
|
214
|
+
assertActiveUser(user);
|
|
215
|
+
assertValidOrder(order);
|
|
216
|
+
assertSupportedPayment(paymentMethod);
|
|
217
|
+
|
|
218
|
+
const subtotal = calculateSubtotal(order.items);
|
|
219
|
+
const discounted = applyTierDiscount(subtotal, user.type);
|
|
220
|
+
const total = applyTax(discounted);
|
|
221
|
+
|
|
222
|
+
// Strategy pattern: dispatch to the right processor, one confirmation path
|
|
223
|
+
PAYMENT_PROCESSORS[paymentMethod].charge(user, total);
|
|
224
|
+
await persistOrder(order, user, total);
|
|
225
|
+
|
|
226
|
+
// Observer: emit event — email and stats handled by listeners, not here
|
|
227
|
+
EventBus.emit('order.placed', { user, order, total });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function getUserOrders(userId) {
|
|
231
|
+
return db.query('SELECT * FROM orders WHERE user_id = ?', [userId]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function cancelOrder(orderId, userId, reason) {
|
|
235
|
+
const order = await fetchOrder(orderId);
|
|
236
|
+
|
|
237
|
+
if (order.userId !== userId) throw new Error('Order does not belong to this user');
|
|
238
|
+
|
|
239
|
+
// State pattern: the status object decides whether cancellation is legal
|
|
240
|
+
if (!getOrderState(order.status).canCancel()) {
|
|
241
|
+
throw new Error(`Order ${orderId} cannot be cancelled (status: ${order.status})`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await persistCancellation(orderId, reason);
|
|
245
|
+
EventBus.emit('order.cancelled', { order, total: order.total });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function refundOrder(orderId) {
|
|
249
|
+
const order = await fetchOrder(orderId);
|
|
250
|
+
|
|
251
|
+
// State pattern: the status object decides whether refund is legal
|
|
252
|
+
if (!getOrderState(order.status).canRefund()) {
|
|
253
|
+
throw new Error(`Order ${orderId} cannot be refunded (status: ${order.status})`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await persistRefund(orderId);
|
|
257
|
+
EventBus.emit('order.refunded', { order, total: order.total });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getStats() {
|
|
261
|
+
return Stats.snapshot(); // Singleton: clean access, defensive copy
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Facade export: one coherent interface
|
|
265
|
+
module.exports = {
|
|
266
|
+
processOrder,
|
|
267
|
+
getUserOrders,
|
|
268
|
+
cancelOrder,
|
|
269
|
+
refundOrder,
|
|
270
|
+
getStats,
|
|
271
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# skill-router benchmark: book-based review vs. native PR toolkit
|
|
2
|
+
|
|
3
|
+
**skill-router** analyzes your code and picks the right book-based skill for the job — so instead of one AI trying to apply four books superficially, you get a deep, focused review from the correct authority. This benchmark runs the same intentionally broken JavaScript file through two parallel pipelines to compare what each finds.
|
|
4
|
+
|
|
5
|
+
| | Native | skill-router |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| **Pipeline** | `pr-review-toolkit:code-reviewer` | `skill-router` → `clean-code-reviewer` + `design-patterns` |
|
|
8
|
+
| **Output file** | `order-processing.pr-toolkit.js` | `order-processing.skill-router.js` |
|
|
9
|
+
|
|
10
|
+
**Bottom line:** Native wins on security depth (catches PCI violation, highest confidence scores). skill-router wins on architectural insight and principle education — finds ~47% more unique issues when both pipelines are combined.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## The code under test
|
|
15
|
+
|
|
16
|
+
`order-processing.original.js` — 157-line Node.js order processing module with: one god function (`process`), no error handling, SQL injection on every query, global mutable state shared across all requests, and `eval()` used to return a plain variable.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## How skill-router picks its skills
|
|
21
|
+
|
|
22
|
+
The router commits to a routing decision before any review begins:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Primary: clean-code-reviewer — god function, cryptic names, magic numbers (Ch.2/3/7)
|
|
26
|
+
Secondary: design-patterns — duplicated payment blocks = Strategy pattern
|
|
27
|
+
Don't use: domain-driven-design — implementation-level code, not a model design problem
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This is the core value: rather than spreading one review thin across four books, the router picks the right lens and goes deep. The `Don't use` line matters — it avoids premature or irrelevant advice.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Issues found
|
|
35
|
+
|
|
36
|
+
> **Notation:** `C1`, `I9`, `S4` are clean-code-reviewer's severity tiers (Critical / Important / Suggestion). `G30`, `N1`, `Ch.7` etc. are chapter/guideline references from *Clean Code* by Robert C. Martin. `Strategy(1)`, `Singleton(4)` etc. are pattern references from *Head First Design Patterns*.
|
|
37
|
+
|
|
38
|
+
### Critical / High severity
|
|
39
|
+
|
|
40
|
+
| Issue | Native | clean-code | design-patterns |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| SQL injection — every query (7 locations) | ✅ Confidence 100 | ✅ C1 — G30 | — |
|
|
43
|
+
| `eval("stats")` — unnecessary, disables JIT | ✅ Confidence 100 | ✅ C3 — G30 | ✅ Singleton(4) |
|
|
44
|
+
| Global mutable `usr`/`items`/`total` — cross-request leak | ✅ Confidence 98 | ✅ C2 — G18 | ✅ Singleton(4) |
|
|
45
|
+
| `items` unbounded memory leak | ✅ Confidence 95 | ⚠️ implied | — |
|
|
46
|
+
| `refund()` null dereference crash | ✅ Confidence 95 | ✅ C4 — Ch.7 | — |
|
|
47
|
+
| PCI violation — card data logged to stdout | ✅ Confidence 92 | ❌ missed | — |
|
|
48
|
+
| Payment fall-through returns `undefined` not `false` | ✅ Confidence 91 | ✅ I9 | ✅ Strategy(1) |
|
|
49
|
+
| Duplicated card/paypal confirmation block | ✅ Confidence 85 | ✅ C5 — G5 | ✅ Strategy(1) |
|
|
50
|
+
| Wrong-recipient email via global `usr` in cancel/refund | ✅ Confidence 89 | ✅ C4/I7 | — |
|
|
51
|
+
|
|
52
|
+
### Important / Improvement
|
|
53
|
+
|
|
54
|
+
| Issue | Native | clean-code | design-patterns |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `==` instead of `===` throughout | ✅ Confidence 82 | ✅ I5 — G15 | — |
|
|
57
|
+
| `var` instead of `const`/`let` | ✅ Confidence 80 | ✅ I6 — G29 | — |
|
|
58
|
+
| Magic discount numbers (0.1, 0.2, 0.5) | ✅ | ✅ I3 — G25 | ✅ Strategy(3) |
|
|
59
|
+
| `discount` variable declared, never used | ✅ Confidence 88 | ✅ I3 | — |
|
|
60
|
+
| Discount should be data-driven, not if-chain | — | ✅ I4 — G23 | ✅ Strategy(3) |
|
|
61
|
+
| God function — 10 responsibilities | ⚠️ via nesting | ✅ I1 — F1/F2 | ✅ Template(5) |
|
|
62
|
+
| Six-level arrow-head nesting | ✅ Confidence 82 | ✅ I2 | — |
|
|
63
|
+
| No error handling on db/mailer calls | ✅ Confidence 85 | ✅ I8 — Ch.7 | — |
|
|
64
|
+
| `cancel()` never updates stats | ✅ Confidence 80 | — | — |
|
|
65
|
+
| Stats inconsistency — two separate objects | ✅ Confidence 82 | ✅ S4 | ✅ Singleton(4) |
|
|
66
|
+
| Cryptic names (`o`, `u`, `pay`, `s`, `rsn`) | ✅ Confidence 80 | ✅ S1 — N1/N2 | — |
|
|
67
|
+
| Noise/lying comments | — | ✅ S2 — C2/C3 | — |
|
|
68
|
+
| `formatMoney` floating-point rounding bug | — | ✅ S3 | — |
|
|
69
|
+
| Stubs always return `true` — lying | — | ✅ S5 — C3 | — |
|
|
70
|
+
| State machine needed for order lifecycle | — | — | ✅ State(2) |
|
|
71
|
+
| Side effects hardcoded — use Observer | — | — | ✅ Observer(6) |
|
|
72
|
+
| Flat module export — use Facade | — | — | ✅ Facade(7) |
|
|
73
|
+
|
|
74
|
+
### Totals
|
|
75
|
+
|
|
76
|
+
| | Native | clean-code | design-patterns | Combined |
|
|
77
|
+
|---|---|---|---|---|
|
|
78
|
+
| Critical/High | 9 | 5 | 4 | **9 unique** |
|
|
79
|
+
| Important/Improvement | 10 | 9 | 3 | **14 unique** |
|
|
80
|
+
| Suggestion/Low | 0 | 5 | 0 | **5** |
|
|
81
|
+
| **Total** | **19** | **19** | **7 patterns** | **~28 unique** |
|
|
82
|
+
|
|
83
|
+
Overlap: ~89% of native issues were also found by skill-router.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Pattern opportunities identified (skill-router only)
|
|
88
|
+
|
|
89
|
+
The native pipeline finds bugs. The skill-router pipeline additionally surfaces structural opportunities — places where a known pattern *could* reduce complexity — and explains the problem each one solves. Whether to apply a pattern is a judgment call: the skill flags the opportunity and the reasoning; you decide if the trade-off is worth it in your context.
|
|
90
|
+
|
|
91
|
+
| # | Pattern | Impact | Problem it would solve |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| 1 | Strategy — payments | HIGH | Copy-pasted if-block per payment method |
|
|
94
|
+
| 2 | State — order lifecycle | HIGH | Status strings checked in every function |
|
|
95
|
+
| 3 | Strategy — discounts | HIGH | Magic-number if-chain, no extensibility |
|
|
96
|
+
| 4 | Singleton (broken) | HIGH | Module-scope mutable state crossing requests |
|
|
97
|
+
| 5 | Template Method | MEDIUM | Arrow pyramid duplicated in process+cancel |
|
|
98
|
+
| 6 | Observer | MEDIUM | Email/stats hardcoded into business logic |
|
|
99
|
+
| 7 | Facade | MEDIUM | Unrelated concerns in one flat export |
|
|
100
|
+
|
|
101
|
+
**Suggested refactor sequence if you choose to act:** 4 → 1 → 3 → 2 → 5 → 6 → 7
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Updates since this benchmark
|
|
107
|
+
|
|
108
|
+
**skill-router now outputs severity tiers.** One gap this benchmark exposed was that native's confidence scores (≥80 threshold) act as a noise filter — skill-router had no equivalent. The router has since been updated to instruct selected skills to classify every finding as **HIGH** (correctness/security/data loss), **MEDIUM** (design/maintainability), or **LOW** (style/naming), and to skip LOW findings for standard code reviews. This closes the signal-to-noise gap without re-running the benchmark — detection quality hasn't changed, output actionability has.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## When to use each
|
|
113
|
+
|
|
114
|
+
| Situation | Use |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Pre-merge PR review, security audit | **Native** — pre-merge gate: fast, confidence-filtered, adapts to CLAUDE.md project conventions |
|
|
117
|
+
| Larger refactor, architecture planning | **skill-router** — patterns, principles, refactor roadmap |
|
|
118
|
+
| Both together | ~95% total issue coverage vs ~80% for either alone |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Files in this benchmark
|
|
123
|
+
|
|
124
|
+
| File | Description |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `order-processing.original.js` | Original bad code — unchanged input |
|
|
127
|
+
| `order-processing.pr-toolkit.js` | Rewritten applying all `pr-review-toolkit` findings |
|
|
128
|
+
| `order-processing.skill-router.js` | Rewritten applying `clean-code-reviewer` + `design-patterns` |
|
|
129
|
+
| `review-report.md` | This file |
|