@booklib/skills 1.5.0 → 1.5.2
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/docs/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8"/>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
6
6
|
<title>booklib-ai/skills — Book-grounded AI agent skills</title>
|
|
7
|
-
<meta name="description" content="
|
|
7
|
+
<meta name="description" content="22 AI agent skills grounded in canonical programming books. Give your AI coding assistant expert knowledge from Clean Code, Effective Java, DDD, and more."/>
|
|
8
8
|
<meta property="og:title" content="booklib-ai/skills"/>
|
|
9
9
|
<meta property="og:description" content="Book-grounded AI agent skills for Claude Code, Cursor, Copilot, and Windsurf."/>
|
|
10
10
|
<meta property="og:image" content="https://booklib-ai.github.io/skills/logo.png"/>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
max-width: 720px;
|
|
29
29
|
margin: 0 auto;
|
|
30
30
|
}
|
|
31
|
-
.hero img { width: 96px; height: 96px; margin-bottom: 24px; }
|
|
31
|
+
.hero img.logo { width: 96px; height: 96px; margin-bottom: 24px; }
|
|
32
32
|
.hero h1 { font-size: 2.5rem; font-weight: 700; color: #f1f5f9; letter-spacing: -0.03em; }
|
|
33
33
|
.hero p {
|
|
34
34
|
font-size: 1.125rem;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/* Install */
|
|
43
|
-
|
|
43
|
+
#install {
|
|
44
44
|
display: flex;
|
|
45
45
|
align-items: center;
|
|
46
46
|
justify-content: center;
|
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
color: #a5b4fc;
|
|
59
59
|
cursor: pointer;
|
|
60
60
|
user-select: all;
|
|
61
|
+
transition: border-color 0.2s;
|
|
62
|
+
}
|
|
63
|
+
.install-box.highlighted {
|
|
64
|
+
border-color: #6366f1;
|
|
65
|
+
color: #c7d2fe;
|
|
61
66
|
}
|
|
62
67
|
.copy-btn {
|
|
63
68
|
background: #6366f1;
|
|
@@ -84,7 +89,7 @@
|
|
|
84
89
|
|
|
85
90
|
/* Skills grid */
|
|
86
91
|
.skills-section {
|
|
87
|
-
max-width:
|
|
92
|
+
max-width: 1100px;
|
|
88
93
|
margin: 64px auto;
|
|
89
94
|
padding: 0 24px;
|
|
90
95
|
}
|
|
@@ -92,28 +97,117 @@
|
|
|
92
97
|
font-size: 1.5rem;
|
|
93
98
|
font-weight: 700;
|
|
94
99
|
color: #f1f5f9;
|
|
95
|
-
margin-bottom:
|
|
100
|
+
margin-bottom: 8px;
|
|
96
101
|
text-align: center;
|
|
97
102
|
}
|
|
103
|
+
.skills-section .subtitle {
|
|
104
|
+
text-align: center;
|
|
105
|
+
color: #64748b;
|
|
106
|
+
font-size: 0.9rem;
|
|
107
|
+
margin-bottom: 32px;
|
|
108
|
+
}
|
|
98
109
|
.skills-grid {
|
|
99
110
|
display: grid;
|
|
100
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
101
|
-
gap:
|
|
111
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
112
|
+
gap: 20px;
|
|
102
113
|
}
|
|
114
|
+
|
|
115
|
+
/* Card */
|
|
103
116
|
.skill-card {
|
|
104
117
|
background: #11111f;
|
|
105
118
|
border: 1px solid #1e1e35;
|
|
106
119
|
border-radius: 12px;
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
text-decoration: none;
|
|
126
|
+
color: inherit;
|
|
109
127
|
}
|
|
110
128
|
.skill-card:hover {
|
|
111
129
|
border-color: #6366f1;
|
|
112
|
-
transform: translateY(-
|
|
130
|
+
transform: translateY(-3px);
|
|
131
|
+
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15);
|
|
132
|
+
text-decoration: none;
|
|
133
|
+
}
|
|
134
|
+
.skill-card.active {
|
|
135
|
+
border-color: #818cf8;
|
|
136
|
+
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Book cover area */
|
|
140
|
+
.cover-wrap {
|
|
141
|
+
width: 100%;
|
|
142
|
+
aspect-ratio: 2 / 3;
|
|
143
|
+
background: #161625;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
position: relative;
|
|
149
|
+
}
|
|
150
|
+
.cover-wrap img {
|
|
151
|
+
width: 100%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
object-fit: cover;
|
|
154
|
+
display: block;
|
|
155
|
+
}
|
|
156
|
+
.cover-fallback {
|
|
157
|
+
width: 100%;
|
|
158
|
+
height: 100%;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
padding: 16px;
|
|
164
|
+
text-align: center;
|
|
165
|
+
gap: 8px;
|
|
166
|
+
}
|
|
167
|
+
.cover-fallback .initials {
|
|
168
|
+
font-size: 2rem;
|
|
169
|
+
font-weight: 700;
|
|
170
|
+
color: #6366f1;
|
|
171
|
+
line-height: 1;
|
|
172
|
+
}
|
|
173
|
+
.cover-fallback .fallback-title {
|
|
174
|
+
font-size: 0.75rem;
|
|
175
|
+
color: #64748b;
|
|
176
|
+
line-height: 1.3;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Card body */
|
|
180
|
+
.card-body {
|
|
181
|
+
padding: 14px 16px 16px;
|
|
182
|
+
flex: 1;
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: 4px;
|
|
186
|
+
}
|
|
187
|
+
.card-body .skill-name {
|
|
188
|
+
font-size: 0.8rem;
|
|
189
|
+
font-weight: 700;
|
|
190
|
+
color: #a5b4fc;
|
|
191
|
+
font-family: "SF Mono", "Fira Code", monospace;
|
|
192
|
+
word-break: break-all;
|
|
193
|
+
}
|
|
194
|
+
.card-body .book-title {
|
|
195
|
+
font-size: 0.82rem;
|
|
196
|
+
font-weight: 600;
|
|
197
|
+
color: #e2e8f0;
|
|
198
|
+
line-height: 1.3;
|
|
199
|
+
margin-top: 2px;
|
|
200
|
+
}
|
|
201
|
+
.card-body .book-author {
|
|
202
|
+
font-size: 0.75rem;
|
|
203
|
+
color: #64748b;
|
|
204
|
+
}
|
|
205
|
+
.card-body .card-desc {
|
|
206
|
+
font-size: 0.775rem;
|
|
207
|
+
color: #475569;
|
|
208
|
+
line-height: 1.45;
|
|
209
|
+
margin-top: 6px;
|
|
113
210
|
}
|
|
114
|
-
.skill-card .icon { font-size: 1.5rem; margin-bottom: 10px; }
|
|
115
|
-
.skill-card h3 { font-size: 0.95rem; font-weight: 600; color: #e2e8f0; margin-bottom: 6px; }
|
|
116
|
-
.skill-card p { font-size: 0.825rem; color: #64748b; line-height: 1.5; }
|
|
117
211
|
|
|
118
212
|
/* Footer */
|
|
119
213
|
footer {
|
|
@@ -129,13 +223,13 @@
|
|
|
129
223
|
<body>
|
|
130
224
|
|
|
131
225
|
<section class="hero">
|
|
132
|
-
<img src="https://raw.githubusercontent.com/booklib-ai/skills/main/assets/logo.svg" alt="booklib-ai skills logo"/>
|
|
226
|
+
<img class="logo" src="https://raw.githubusercontent.com/booklib-ai/skills/main/assets/logo.svg" alt="booklib-ai skills logo"/>
|
|
133
227
|
<h1>Skills</h1>
|
|
134
228
|
<p>Book-grounded AI agent skills — expert knowledge from canonical programming books, packaged for Claude Code, Cursor, Copilot, and Windsurf.</p>
|
|
135
229
|
|
|
136
|
-
<div
|
|
230
|
+
<div id="install">
|
|
137
231
|
<div class="install-box" id="cmd">npx skills add booklib-ai/skills --all -g</div>
|
|
138
|
-
<button class="copy-btn" onclick="
|
|
232
|
+
<button class="copy-btn" onclick="copyCmd()">Copy</button>
|
|
139
233
|
</div>
|
|
140
234
|
|
|
141
235
|
<div class="badges">
|
|
@@ -147,40 +241,121 @@
|
|
|
147
241
|
</section>
|
|
148
242
|
|
|
149
243
|
<section class="skills-section">
|
|
150
|
-
<h2>
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
<div class="skill-card"><div class="icon">🧹</div><h3>clean-code-reviewer</h3><p>Code review against Robert C. Martin's Clean Code — naming, functions, comments, classes.</p></div>
|
|
154
|
-
<div class="skill-card"><div class="icon">🗄️</div><h3>data-intensive-patterns</h3><p>Reliable, scalable systems from Martin Kleppmann — storage engines, replication, transactions.</p></div>
|
|
155
|
-
<div class="skill-card"><div class="icon">🔀</div><h3>data-pipelines</h3><p>Pipeline practices from James Densmore — ingestion, streaming, transformation, orchestration.</p></div>
|
|
156
|
-
<div class="skill-card"><div class="icon">🏗️</div><h3>design-patterns</h3><p>GoF design patterns from Head First Design Patterns — creational, structural, behavioral.</p></div>
|
|
157
|
-
<div class="skill-card"><div class="icon">🧩</div><h3>domain-driven-design</h3><p>Eric Evans' DDD — Aggregates, Value Objects, Bounded Contexts, Ubiquitous Language.</p></div>
|
|
158
|
-
<div class="skill-card"><div class="icon">☕</div><h3>effective-java</h3><p>Joshua Bloch's Effective Java — object creation, generics, enums, lambdas, concurrency.</p></div>
|
|
159
|
-
<div class="skill-card"><div class="icon">🛡️</div><h3>effective-kotlin</h3><p>Marcin Moskała's Effective Kotlin — safety, readability, reusability, abstraction.</p></div>
|
|
160
|
-
<div class="skill-card"><div class="icon">🐍</div><h3>effective-python</h3><p>Brett Slatkin's Effective Python — Pythonic thinking, functions, classes, concurrency.</p></div>
|
|
161
|
-
<div class="skill-card"><div class="icon">⚡</div><h3>kotlin-in-action</h3><p>Kotlin in Action — functions, classes, lambdas, nullability, coroutines, flows.</p></div>
|
|
162
|
-
<div class="skill-card"><div class="icon">🚀</div><h3>lean-startup</h3><p>Eric Ries' The Lean Startup — MVP testing, validated learning, Build-Measure-Learn.</p></div>
|
|
163
|
-
<div class="skill-card"><div class="icon">🔧</div><h3>microservices-patterns</h3><p>Chris Richardson — decomposition, sagas, API gateways, event sourcing, CQRS.</p></div>
|
|
164
|
-
<div class="skill-card"><div class="icon">🎨</div><h3>refactoring-ui</h3><p>Refactoring UI by Wathan & Schoger — visual hierarchy, layout, typography, color.</p></div>
|
|
165
|
-
<div class="skill-card"><div class="icon">🗺️</div><h3>skill-router</h3><p><strong>Meta-skill.</strong> Automatically selects the 1–2 most relevant skills for any task.</p></div>
|
|
166
|
-
<div class="skill-card"><div class="icon">📊</div><h3>storytelling-with-data</h3><p>Cole Nussbaumer Knaflic — effective visuals, decluttering, narrative structure.</p></div>
|
|
167
|
-
<div class="skill-card"><div class="icon">🏛️</div><h3>system-design-interview</h3><p>Alex Xu — scaling, estimation, load balancing, caching, sharding, real-world designs.</p></div>
|
|
168
|
-
<div class="skill-card"><div class="icon">🔄</div><h3>using-asyncio-python</h3><p>Caleb Hattingh — coroutines, event loop, tasks, signal handling, aiohttp.</p></div>
|
|
169
|
-
<div class="skill-card"><div class="icon">🕷️</div><h3>web-scraping-python</h3><p>Ryan Mitchell — BeautifulSoup, Scrapy, Selenium, data storage, anti-detection.</p></div>
|
|
170
|
-
</div>
|
|
244
|
+
<h2>22 skills, 22 books</h2>
|
|
245
|
+
<p class="subtitle">Click any skill to see its install command</p>
|
|
246
|
+
<div class="skills-grid" id="grid"></div>
|
|
171
247
|
</section>
|
|
172
248
|
|
|
173
249
|
<footer>
|
|
174
|
-
<p>MIT License
|
|
250
|
+
<p>MIT License · <a href="https://github.com/booklib-ai/skills">GitHub</a> · <a href="https://github.com/booklib-ai/skills/blob/main/CONTRIBUTING.md">Contributing</a> · <a href="https://github.com/booklib-ai/skills/blob/main/AGENTS.md">Agent Setup</a></p>
|
|
175
251
|
</footer>
|
|
176
252
|
|
|
177
253
|
<script>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
254
|
+
const SKILLS = [
|
|
255
|
+
{ name: "animation-at-work", book: "Animation at Work", author: "Rachel Nabors", isbn: "9781937557942", desc: "Motion perception, 12 animation principles, CSS transitions, and performance." },
|
|
256
|
+
{ name: "clean-code-reviewer", book: "Clean Code", author: "Robert C. Martin", isbn: "9780132350884", desc: "Naming, functions, comments, classes, and the Boy Scout Rule." },
|
|
257
|
+
{ name: "data-intensive-patterns",book: "Designing Data-Intensive Applications",author: "Martin Kleppmann", isbn: "9781449373320", desc: "Storage engines, replication, transactions, and distributed systems." },
|
|
258
|
+
{ name: "data-pipelines", book: "Data Pipelines Pocket Reference", author: "James Densmore", isbn: "9781492087601", desc: "Ingestion, streaming, transformation, and orchestration." },
|
|
259
|
+
{ name: "design-patterns", book: "Head First Design Patterns", author: "Freeman & Robson", isbn: "9781492078005", desc: "GoF creational, structural, and behavioral patterns in depth." },
|
|
260
|
+
{ name: "domain-driven-design", book: "Domain-Driven Design", author: "Eric Evans", isbn: "9780321125217", desc: "Aggregates, Value Objects, Bounded Contexts, Ubiquitous Language." },
|
|
261
|
+
{ name: "effective-java", book: "Effective Java", author: "Joshua Bloch", isbn: "9780134685991", desc: "Object creation, generics, enums, lambdas, and concurrency." },
|
|
262
|
+
{ name: "effective-kotlin", book: "Effective Kotlin", author: "Marcin Moskala", isbn: "9781803248776", desc: "Safety, readability, reusability, and abstraction." },
|
|
263
|
+
{ name: "effective-python", book: "Effective Python", author: "Brett Slatkin", isbn: "9780134853987", desc: "Pythonic thinking, functions, classes, concurrency, and testing." },
|
|
264
|
+
{ name: "effective-typescript", book: "Effective TypeScript", author: "Dan Vanderkam", isbn: "9781492053743", desc: "Type system, type design, avoiding any, declarations, and migration." },
|
|
265
|
+
{ name: "kotlin-in-action", book: "Kotlin in Action", author: "Elizarov & Isakova", isbn: "9781617293290", desc: "Functions, classes, lambdas, nullability, coroutines, and flows." },
|
|
266
|
+
{ name: "lean-startup", book: "The Lean Startup", author: "Eric Ries", isbn: "9780307887894", desc: "MVP testing, validated learning, Build-Measure-Learn loop." },
|
|
267
|
+
{ name: "microservices-patterns", book: "Microservices Patterns", author: "Chris Richardson", isbn: "9781617294549", desc: "Decomposition, sagas, API gateways, event sourcing, and CQRS." },
|
|
268
|
+
{ name: "programming-with-rust", book: "Programming with Rust", author: "Donis Marshall", isbn: "9780137889655", desc: "Ownership, borrowing, lifetimes, error handling, traits, concurrency." },
|
|
269
|
+
{ name: "refactoring-ui", book: "Refactoring UI", author: "Wathan & Schoger", isbn: null, desc: "Visual hierarchy, layout, typography, color, and spacing." },
|
|
270
|
+
{ name: "rust-in-action", book: "Rust in Action", author: "Tim McNamara", isbn: "9781617294556", desc: "Systems programming — smart pointers, memory, networking, OS." },
|
|
271
|
+
{ name: "skill-router", book: "Meta-skill", author: "booklib-ai", isbn: null, desc: "Automatically routes to the most relevant skill for any task." },
|
|
272
|
+
{ name: "spring-boot-in-action", book: "Spring Boot in Action", author: "Craig Walls", isbn: "9781617292545", desc: "Auto-configuration, starters, testing, Actuator, and deployment." },
|
|
273
|
+
{ name: "storytelling-with-data", book: "Storytelling with Data", author: "Cole Nussbaumer Knaflic",isbn: "9781119002253", desc: "Effective visuals, decluttering, and narrative structure." },
|
|
274
|
+
{ name: "system-design-interview",book: "System Design Interview", author: "Alex Xu", isbn: "9798664653403", desc: "Scaling, estimation, load balancing, caching, and sharding." },
|
|
275
|
+
{ name: "using-asyncio-python", book: "Using Asyncio in Python", author: "Caleb Hattingh", isbn: "9781492075325", desc: "Coroutines, event loop, tasks, and signal handling." },
|
|
276
|
+
{ name: "web-scraping-python", book: "Web Scraping with Python", author: "Ryan Mitchell", isbn: "9781491985564", desc: "BeautifulSoup, Scrapy, Selenium, and data storage." },
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const ALL_CMD = "npx skills add booklib-ai/skills --all -g";
|
|
280
|
+
let activeCard = null;
|
|
281
|
+
|
|
282
|
+
function initials(name) {
|
|
283
|
+
return name.split(/[\s-]+/).map(w => w[0]).join("").toUpperCase().slice(0, 3);
|
|
183
284
|
}
|
|
285
|
+
|
|
286
|
+
function buildCard(skill) {
|
|
287
|
+
const card = document.createElement("a");
|
|
288
|
+
card.className = "skill-card";
|
|
289
|
+
card.href = `https://github.com/booklib-ai/skills/tree/main/skills/${skill.name}`;
|
|
290
|
+
card.target = "_blank";
|
|
291
|
+
card.rel = "noopener";
|
|
292
|
+
|
|
293
|
+
const coverHtml = skill.isbn
|
|
294
|
+
? `<img
|
|
295
|
+
src="https://covers.openlibrary.org/b/isbn/${skill.isbn}-M.jpg"
|
|
296
|
+
alt="${skill.book} cover"
|
|
297
|
+
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"
|
|
298
|
+
/>
|
|
299
|
+
<div class="cover-fallback" style="display:none">
|
|
300
|
+
<div class="initials">${initials(skill.book)}</div>
|
|
301
|
+
<div class="fallback-title">${skill.book}</div>
|
|
302
|
+
</div>`
|
|
303
|
+
: `<div class="cover-fallback">
|
|
304
|
+
<div class="initials">${initials(skill.book)}</div>
|
|
305
|
+
<div class="fallback-title">${skill.book}</div>
|
|
306
|
+
</div>`;
|
|
307
|
+
|
|
308
|
+
card.innerHTML = `
|
|
309
|
+
<div class="cover-wrap">${coverHtml}</div>
|
|
310
|
+
<div class="card-body">
|
|
311
|
+
<div class="skill-name">${skill.name}</div>
|
|
312
|
+
<div class="book-title">${skill.book}</div>
|
|
313
|
+
<div class="book-author">${skill.author}</div>
|
|
314
|
+
<div class="card-desc">${skill.desc}</div>
|
|
315
|
+
</div>
|
|
316
|
+
`;
|
|
317
|
+
|
|
318
|
+
card.addEventListener("click", (e) => {
|
|
319
|
+
e.preventDefault();
|
|
320
|
+
selectSkill(skill, card);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return card;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function selectSkill(skill, card) {
|
|
327
|
+
// Deactivate previous
|
|
328
|
+
if (activeCard && activeCard !== card) {
|
|
329
|
+
activeCard.classList.remove("active");
|
|
330
|
+
}
|
|
331
|
+
const cmdEl = document.getElementById("cmd");
|
|
332
|
+
|
|
333
|
+
if (activeCard === card) {
|
|
334
|
+
// Deselect — back to install all
|
|
335
|
+
activeCard.classList.remove("active");
|
|
336
|
+
activeCard = null;
|
|
337
|
+
cmdEl.textContent = ALL_CMD;
|
|
338
|
+
cmdEl.classList.remove("highlighted");
|
|
339
|
+
} else {
|
|
340
|
+
activeCard = card;
|
|
341
|
+
card.classList.add("active");
|
|
342
|
+
cmdEl.textContent = `npx skills add booklib-ai/skills ${skill.name} -g`;
|
|
343
|
+
cmdEl.classList.add("highlighted");
|
|
344
|
+
document.getElementById("install").scrollIntoView({ behavior: "smooth", block: "center" });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function copyCmd() {
|
|
349
|
+
const text = document.getElementById("cmd").textContent;
|
|
350
|
+
navigator.clipboard.writeText(text);
|
|
351
|
+
const btn = document.querySelector(".copy-btn");
|
|
352
|
+
btn.textContent = "Copied!";
|
|
353
|
+
setTimeout(() => btn.textContent = "Copy", 2000);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Build grid
|
|
357
|
+
const grid = document.getElementById("grid");
|
|
358
|
+
SKILLS.forEach(skill => grid.appendChild(buildCard(skill)));
|
|
184
359
|
</script>
|
|
185
360
|
|
|
186
361
|
</body>
|
package/package.json
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"Recognize that this code is already applying Effective TypeScript principles correctly",
|
|
30
30
|
"Acknowledge Item 37 (branded OrderId), Item 33 (OrderStatus literal union), Item 28/32 (tagged union OrderResult), Item 17 (readonly fields), Item 42 (unknown from JSON), Item 40 (assertion inside well-typed function), Item 25 (async/await), Item 48 (TSDoc)",
|
|
31
31
|
"Do NOT manufacture issues — the code is well-typed",
|
|
32
|
+
"At most note: raw as Order on the last line is a narrowly scoped assertion (Item 40) — acceptable inside a well-typed wrapper, but mention that a runtime validator (e.g. zod) would catch malformed API responses that TypeScript cannot",
|
|
32
33
|
"At most offer minor suggestions, clearly marked as optional polish"
|
|
33
34
|
]
|
|
34
35
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
review.py — Pre-analysis script for Effective TypeScript reviews.
|
|
4
|
+
Usage: python review.py <file.ts|file.tsx>
|
|
5
|
+
|
|
6
|
+
Scans a TypeScript source file for anti-patterns from the book's 62 items:
|
|
7
|
+
any usage, type assertions, object wrapper types, non-null assertions,
|
|
8
|
+
missing strict mode, interface-of-unions, plain string types, and more.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CHECKS = [
|
|
17
|
+
(
|
|
18
|
+
r":\s*any\b",
|
|
19
|
+
"Item 5/38: any type annotation",
|
|
20
|
+
"replace with a specific type, generic parameter, or unknown for truly unknown values",
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
r"\bas\s+any\b",
|
|
24
|
+
"Item 38/40: 'as any' assertion",
|
|
25
|
+
"scope 'as any' as narrowly as possible — hide inside a well-typed wrapper function; prefer 'as unknown as T' for safer double assertion",
|
|
26
|
+
),
|
|
27
|
+
(
|
|
28
|
+
r"\bString\b|\bNumber\b|\bBoolean\b|\bObject\b|\bSymbol\b",
|
|
29
|
+
"Item 10: Object wrapper type (String/Number/Boolean)",
|
|
30
|
+
"use primitive types: string, number, boolean — never the wrapper class types",
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
r"!\.",
|
|
34
|
+
"Item 28/31: Non-null assertion (!).",
|
|
35
|
+
"non-null assertions are usually a symptom of an imprecise type — fix the type instead; consider optional chaining (?.) or a type guard",
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
r"@ts-ignore|@ts-nocheck",
|
|
39
|
+
"Item 38: @ts-ignore suppresses type errors",
|
|
40
|
+
"fix the underlying type issue; if unavoidable use @ts-expect-error with a comment explaining why",
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
r"function\s+\w+[^{]*\{[^}]{0,20}\}",
|
|
44
|
+
None, # skip — too noisy
|
|
45
|
+
None,
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
r"interface\s+\w+\s*\{[^}]*\?[^}]*\?[^}]*\}",
|
|
49
|
+
"Item 32: Interface with multiple optional fields",
|
|
50
|
+
"multiple optional fields that have implicit relationships suggest an interface-of-unions — convert to a tagged discriminated union",
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
r"param(?:eter)?\s*:\s*string(?!\s*[|&])",
|
|
54
|
+
"Item 33: Plain string parameter",
|
|
55
|
+
"consider a string literal union if the parameter has a finite set of valid values (e.g. 'asc' | 'desc')",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"\.json\(\)\s*as\s+\w",
|
|
59
|
+
"Item 9/40: Direct type assertion on .json()",
|
|
60
|
+
"assign to unknown first, then narrow: 'const raw: unknown = await res.json()' — assertion inside a well-typed wrapper is acceptable (Item 40)",
|
|
61
|
+
),
|
|
62
|
+
(
|
|
63
|
+
r"Promise<any>",
|
|
64
|
+
"Item 38: Promise<any> return type",
|
|
65
|
+
"replace with Promise<unknown> or a concrete type — Promise<any> disables type checking on the resolved value",
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def scan(source: str) -> list[dict]:
|
|
71
|
+
findings = []
|
|
72
|
+
lines = source.splitlines()
|
|
73
|
+
for lineno, line in enumerate(lines, start=1):
|
|
74
|
+
stripped = line.strip()
|
|
75
|
+
if stripped.startswith("//") or stripped.startswith("*"):
|
|
76
|
+
continue
|
|
77
|
+
for pattern, label, advice in CHECKS:
|
|
78
|
+
if label is None:
|
|
79
|
+
continue
|
|
80
|
+
if re.search(pattern, line):
|
|
81
|
+
findings.append({
|
|
82
|
+
"line": lineno,
|
|
83
|
+
"text": line.rstrip(),
|
|
84
|
+
"label": label,
|
|
85
|
+
"advice": advice,
|
|
86
|
+
})
|
|
87
|
+
return findings
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def check_strict(source: str) -> bool:
|
|
91
|
+
"""Returns True if this looks like a tsconfig with strict mode enabled."""
|
|
92
|
+
return bool(re.search(r'"strict"\s*:\s*true', source))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def sep(char="-", width=70) -> str:
|
|
96
|
+
return char * width
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main() -> None:
|
|
100
|
+
if len(sys.argv) < 2:
|
|
101
|
+
print("Usage: python review.py <file.ts|file.tsx>")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
path = Path(sys.argv[1])
|
|
105
|
+
if not path.exists():
|
|
106
|
+
print(f"Error: file not found: {path}")
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
if path.suffix.lower() not in (".ts", ".tsx", ".json"):
|
|
110
|
+
print(f"Warning: expected .ts/.tsx, got '{path.suffix}' — continuing anyway")
|
|
111
|
+
|
|
112
|
+
source = path.read_text(encoding="utf-8", errors="replace")
|
|
113
|
+
|
|
114
|
+
# Special case: tsconfig.json
|
|
115
|
+
if path.name == "tsconfig.json":
|
|
116
|
+
print(sep("="))
|
|
117
|
+
print("EFFECTIVE TYPESCRIPT — TSCONFIG CHECK")
|
|
118
|
+
print(sep("="))
|
|
119
|
+
if check_strict(source):
|
|
120
|
+
print(" [OK] strict: true is enabled (Item 2)")
|
|
121
|
+
else:
|
|
122
|
+
print(" [!] strict: true is NOT enabled — Item 2: always enable strict mode")
|
|
123
|
+
print(" Add: \"strict\": true to compilerOptions")
|
|
124
|
+
print()
|
|
125
|
+
print(sep("="))
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
findings = scan(source)
|
|
129
|
+
groups: dict[str, list] = {}
|
|
130
|
+
for f in findings:
|
|
131
|
+
groups.setdefault(f["label"], []).append(f)
|
|
132
|
+
|
|
133
|
+
print(sep("="))
|
|
134
|
+
print("EFFECTIVE TYPESCRIPT — PRE-REVIEW REPORT")
|
|
135
|
+
print(sep("="))
|
|
136
|
+
print(f"File : {path}")
|
|
137
|
+
print(f"Lines : {len(source.splitlines())}")
|
|
138
|
+
print(f"Issues : {len(findings)} potential violations across {len(groups)} categories")
|
|
139
|
+
print()
|
|
140
|
+
|
|
141
|
+
if not findings:
|
|
142
|
+
print(" [OK] No common Effective TypeScript anti-patterns detected.")
|
|
143
|
+
print()
|
|
144
|
+
else:
|
|
145
|
+
for label, items in groups.items():
|
|
146
|
+
print(sep())
|
|
147
|
+
print(f" {label} ({len(items)} occurrence{'s' if len(items) != 1 else ''})")
|
|
148
|
+
print(sep())
|
|
149
|
+
print(f" Advice: {items[0]['advice']}")
|
|
150
|
+
print()
|
|
151
|
+
for item in items[:5]:
|
|
152
|
+
print(f" line {item['line']:>4}: {item['text'][:100]}")
|
|
153
|
+
if len(items) > 5:
|
|
154
|
+
print(f" ... and {len(items) - 5} more")
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
severity = (
|
|
158
|
+
"HIGH" if len(findings) >= 5
|
|
159
|
+
else "MEDIUM" if len(findings) >= 2
|
|
160
|
+
else "LOW" if findings
|
|
161
|
+
else "NONE"
|
|
162
|
+
)
|
|
163
|
+
print(sep("="))
|
|
164
|
+
print(f"SEVERITY: {severity} | Key items: Item 2 (strict), Item 5/38 (any/unknown), Item 9 (assertions), Item 28/32 (tagged unions), Item 33 (literal types)")
|
|
165
|
+
print(sep("="))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
main()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
review.py — Pre-analysis script for Programming with Rust reviews.
|
|
4
|
+
Usage: python review.py <file.rs>
|
|
5
|
+
|
|
6
|
+
Scans a Rust source file for anti-patterns from the book:
|
|
7
|
+
unwrap misuse, unnecessary cloning, unsafe shared state, manual index loops,
|
|
8
|
+
missing Result return types, static mut, and more.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CHECKS = [
|
|
17
|
+
(
|
|
18
|
+
r"\.unwrap\(\)",
|
|
19
|
+
"Ch 12: .unwrap()",
|
|
20
|
+
"panics on failure in production paths — use ?, .expect(\"reason\"), or match",
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
r"\.clone\(\)",
|
|
24
|
+
"Ch 8: .clone()",
|
|
25
|
+
"verify cloning is necessary — prefer borrowing (&T or &mut T) to avoid heap allocation",
|
|
26
|
+
),
|
|
27
|
+
(
|
|
28
|
+
r"static\s+mut\s+\w+",
|
|
29
|
+
"Ch 19: static mut",
|
|
30
|
+
"data race risk — replace with Arc<Mutex<T>> or std::sync::atomic types",
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
r"unsafe\s*\{",
|
|
34
|
+
"Ch 20: unsafe block",
|
|
35
|
+
"minimize unsafe scope; add a // SAFETY: comment explaining the invariant being upheld",
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
r"for\s+\w+\s+in\s+0\s*\.\.\s*\w+\.len\(\)",
|
|
39
|
+
"Ch 6: Manual index loop",
|
|
40
|
+
"use iterator adapters: for item in &collection, or .iter().enumerate() if index is needed",
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
r"\bpanic!\s*\(",
|
|
44
|
+
"Ch 12: panic!()",
|
|
45
|
+
"panics should be reserved for unrecoverable programmer errors — use Result<T, E> for recoverable failures",
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
r"Box<dyn\s+\w+>",
|
|
49
|
+
"Ch 17: dyn Trait (dynamic dispatch)",
|
|
50
|
+
"prefer impl Trait for static dispatch (zero-cost) unless you need a heterogeneous collection",
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
r"Rc\s*::\s*(new|clone)\b",
|
|
54
|
+
"Ch 19: Rc usage",
|
|
55
|
+
"Rc is not Send — if shared across threads, use Arc instead",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"\.expect\s*\(\s*\)",
|
|
59
|
+
"Ch 12: .expect() with empty string",
|
|
60
|
+
"add a meaningful reason: .expect(\"invariant: config is always loaded before this point\")",
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def scan(source: str) -> list[dict]:
|
|
66
|
+
findings = []
|
|
67
|
+
lines = source.splitlines()
|
|
68
|
+
for lineno, line in enumerate(lines, start=1):
|
|
69
|
+
stripped = line.strip()
|
|
70
|
+
if stripped.startswith("//"):
|
|
71
|
+
continue
|
|
72
|
+
for pattern, label, advice in CHECKS:
|
|
73
|
+
if re.search(pattern, line):
|
|
74
|
+
findings.append({
|
|
75
|
+
"line": lineno,
|
|
76
|
+
"text": line.rstrip(),
|
|
77
|
+
"label": label,
|
|
78
|
+
"advice": advice,
|
|
79
|
+
})
|
|
80
|
+
return findings
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def sep(char="-", width=70) -> str:
|
|
84
|
+
return char * width
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> None:
|
|
88
|
+
if len(sys.argv) < 2:
|
|
89
|
+
print("Usage: python review.py <file.rs>")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
path = Path(sys.argv[1])
|
|
93
|
+
if not path.exists():
|
|
94
|
+
print(f"Error: file not found: {path}")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
if path.suffix.lower() != ".rs":
|
|
98
|
+
print(f"Warning: expected a .rs file, got '{path.suffix}' — continuing anyway")
|
|
99
|
+
|
|
100
|
+
source = path.read_text(encoding="utf-8", errors="replace")
|
|
101
|
+
findings = scan(source)
|
|
102
|
+
groups: dict[str, list] = {}
|
|
103
|
+
for f in findings:
|
|
104
|
+
groups.setdefault(f["label"], []).append(f)
|
|
105
|
+
|
|
106
|
+
print(sep("="))
|
|
107
|
+
print("PROGRAMMING WITH RUST — PRE-REVIEW REPORT")
|
|
108
|
+
print(sep("="))
|
|
109
|
+
print(f"File : {path}")
|
|
110
|
+
print(f"Lines : {len(source.splitlines())}")
|
|
111
|
+
print(f"Issues : {len(findings)} potential anti-patterns across {len(groups)} categories")
|
|
112
|
+
print()
|
|
113
|
+
|
|
114
|
+
if not findings:
|
|
115
|
+
print(" [OK] No common Rust anti-patterns detected.")
|
|
116
|
+
print()
|
|
117
|
+
else:
|
|
118
|
+
for label, items in groups.items():
|
|
119
|
+
print(sep())
|
|
120
|
+
print(f" {label} ({len(items)} occurrence{'s' if len(items) != 1 else ''})")
|
|
121
|
+
print(sep())
|
|
122
|
+
print(f" Advice: {items[0]['advice']}")
|
|
123
|
+
print()
|
|
124
|
+
for item in items[:5]:
|
|
125
|
+
print(f" line {item['line']:>4}: {item['text'][:100]}")
|
|
126
|
+
if len(items) > 5:
|
|
127
|
+
print(f" ... and {len(items) - 5} more")
|
|
128
|
+
print()
|
|
129
|
+
|
|
130
|
+
severity = (
|
|
131
|
+
"HIGH" if len(findings) >= 5
|
|
132
|
+
else "MEDIUM" if len(findings) >= 2
|
|
133
|
+
else "LOW" if findings
|
|
134
|
+
else "NONE"
|
|
135
|
+
)
|
|
136
|
+
print(sep("="))
|
|
137
|
+
print(f"SEVERITY: {severity} | Key chapters: Ch 8 (ownership), Ch 12 (errors), Ch 17 (traits), Ch 19 (concurrency), Ch 20 (memory)")
|
|
138
|
+
print(sep("="))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
main()
|