@booklib/skills 1.3.2 → 1.4.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.
Files changed (31) hide show
  1. package/AGENTS.md +108 -0
  2. package/CLAUDE.md +57 -0
  3. package/CODE_OF_CONDUCT.md +31 -0
  4. package/CONTRIBUTING.md +13 -0
  5. package/README.md +68 -45
  6. package/SECURITY.md +9 -0
  7. package/assets/logo.svg +36 -0
  8. package/demo.gif +0 -0
  9. package/demo.tape +40 -0
  10. package/docs/index.html +187 -0
  11. package/package.json +2 -2
  12. package/skills/effective-typescript/SKILL.md +166 -0
  13. package/skills/effective-typescript/evals/evals.json +36 -0
  14. package/skills/effective-typescript/examples/after.md +70 -0
  15. package/skills/effective-typescript/examples/before.md +47 -0
  16. package/skills/effective-typescript/references/api_reference.md +118 -0
  17. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  18. package/skills/programming-with-rust/SKILL.md +194 -0
  19. package/skills/programming-with-rust/evals/evals.json +37 -0
  20. package/skills/programming-with-rust/examples/after.md +107 -0
  21. package/skills/programming-with-rust/examples/before.md +59 -0
  22. package/skills/programming-with-rust/references/api_reference.md +152 -0
  23. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  24. package/skills/rust-in-action/SKILL.md +290 -0
  25. package/skills/rust-in-action/evals/evals.json +38 -0
  26. package/skills/rust-in-action/examples/after.md +156 -0
  27. package/skills/rust-in-action/examples/before.md +56 -0
  28. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  29. package/skills/rust-in-action/scripts/review.py +147 -0
  30. package/skills/skill-router/SKILL.md +16 -13
  31. package/skills/skill-router/references/skill-catalog.md +19 -1
@@ -0,0 +1,346 @@
1
+ # Rust in Action — Practices Catalog
2
+
3
+ Systems-focused before/after examples from each chapter group.
4
+
5
+ ---
6
+
7
+ ## Ownership: Use References, Not Moves (Ch 4)
8
+
9
+ **Before:**
10
+ ```rust
11
+ fn print_log(data: Vec<u8>) { // consumes — caller loses data
12
+ println!("{} bytes", data.len());
13
+ }
14
+ let log = vec![1u8, 2, 3];
15
+ print_log(log);
16
+ // log is gone — can't use it again
17
+ ```
18
+ **After:**
19
+ ```rust
20
+ fn print_log(data: &[u8]) { // borrows a slice — Vec<u8> derefs to &[u8]
21
+ println!("{} bytes", data.len());
22
+ }
23
+ let log = vec![1u8, 2, 3];
24
+ print_log(&log);
25
+ println!("{log:?}"); // still valid
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Ownership: Resolving Lifetime Issues (Ch 4)
31
+
32
+ **Before:**
33
+ ```rust
34
+ struct Config {
35
+ name: String, // owned — always heap-allocates
36
+ }
37
+ ```
38
+ **After (borrow when caller controls data):**
39
+ ```rust
40
+ struct Config<'a> {
41
+ name: &'a str, // borrows — zero allocation if caller has the string
42
+ }
43
+ // Use when Config doesn't outlive the string it references
44
+ ```
45
+ **Or (own when Config must be independent):**
46
+ ```rust
47
+ struct Config {
48
+ name: String, // owned — correct when Config outlives the source string
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Smart Pointers: Choosing the Right Type (Ch 6)
55
+
56
+ ```rust
57
+ // Box<T>: heap allocation, single owner
58
+ let boxed: Box<[u8]> = vec![1, 2, 3].into_boxed_slice();
59
+
60
+ // Rc<T>: shared ownership, single thread only
61
+ use std::rc::Rc;
62
+ let shared = Rc::new(vec![1, 2, 3]);
63
+ let clone1 = Rc::clone(&shared); // cheap — increments refcount
64
+ // let _ = thread::spawn(move || { clone1; }); // COMPILE ERROR: Rc is not Send
65
+
66
+ // Arc<T>: shared ownership, multi-thread safe
67
+ use std::sync::Arc;
68
+ let arc = Arc::new(vec![1, 2, 3]);
69
+ let arc2 = Arc::clone(&arc); // idiomatic — explicit about cheapness
70
+ thread::spawn(move || println!("{arc2:?}")).join().unwrap();
71
+
72
+ // RefCell<T>: interior mutability, single thread, runtime checks
73
+ use std::cell::RefCell;
74
+ let cache: RefCell<Option<String>> = RefCell::new(None);
75
+ *cache.borrow_mut() = Some("computed".into());
76
+
77
+ // Cow<T>: clone-on-write — avoids allocation when only reading
78
+ use std::borrow::Cow;
79
+ fn process(input: &str) -> Cow<str> {
80
+ if input.contains("bad") {
81
+ Cow::Owned(input.replace("bad", "good")) // allocates only when needed
82
+ } else {
83
+ Cow::Borrowed(input) // zero allocation
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Data: Explicit Endianness (Ch 5, 7)
91
+
92
+ **Before:**
93
+ ```rust
94
+ fn parse_id(bytes: &[u8]) -> u32 {
95
+ u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) // native — wrong on BE hosts
96
+ }
97
+ fn write_id(id: u32) -> [u8; 4] {
98
+ id.to_ne_bytes() // corrupts protocol on big-endian
99
+ }
100
+ ```
101
+ **After:**
102
+ ```rust
103
+ // Protocol says: little-endian. Be explicit regardless of host.
104
+ fn parse_id(bytes: &[u8], offset: usize) -> Result<u32, &'static str> {
105
+ bytes.get(offset..offset + 4)
106
+ .ok_or("buffer too short")
107
+ .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
108
+ }
109
+
110
+ fn write_id(id: u32) -> [u8; 4] {
111
+ id.to_le_bytes() // always little-endian — deterministic on all hosts
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Data: Bit Manipulation (Ch 5)
118
+
119
+ ```rust
120
+ // Named constants for masks — self-documenting (Ch 5)
121
+ const SIGN_BIT: u32 = 0x8000_0000;
122
+ const EXPONENT_MASK: u32 = 0x7F80_0000;
123
+ const MANTISSA_MASK: u32 = 0x007F_FFFF;
124
+
125
+ fn dissect_f32(n: f32) -> (u32, u32, u32) {
126
+ let bits = n.to_bits();
127
+ let sign = (bits & SIGN_BIT) >> 31;
128
+ let exponent = (bits & EXPONENT_MASK) >> 23;
129
+ let mantissa = bits & MANTISSA_MASK;
130
+ (sign, exponent, mantissa)
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Files: Buffered I/O + serde (Ch 7)
137
+
138
+ **Before:**
139
+ ```rust
140
+ use std::fs::File;
141
+ use std::io::Read;
142
+ let mut f = File::open("data.bin").unwrap();
143
+ let mut buf = vec![];
144
+ f.read_to_end(&mut buf).unwrap(); // unbuffered + panics
145
+ ```
146
+ **After:**
147
+ ```rust
148
+ use std::fs::File;
149
+ use std::io::{BufReader, Read};
150
+ use std::path::Path;
151
+
152
+ fn read_file(path: &Path) -> Result<Vec<u8>, std::io::Error> {
153
+ let f = File::open(path)?;
154
+ let mut reader = BufReader::new(f); // batched syscalls
155
+ let mut buf = Vec::new();
156
+ reader.read_to_end(&mut buf)?;
157
+ Ok(buf)
158
+ }
159
+
160
+ // Structured serialization with serde + bincode (Ch 7)
161
+ use serde::{Deserialize, Serialize};
162
+
163
+ #[derive(Serialize, Deserialize, Debug)]
164
+ struct Record {
165
+ id: u64,
166
+ payload: Vec<u8>,
167
+ }
168
+
169
+ fn write_record(rec: &Record, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
170
+ let f = File::create(path)?;
171
+ let writer = std::io::BufWriter::new(f);
172
+ bincode::serialize_into(writer, rec)?;
173
+ Ok(())
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Error Handling: Library Error Wrapping (Ch 8)
180
+
181
+ ```rust
182
+ // Wrapping multiple downstream error types in one domain error (Ch 8)
183
+ #[derive(Debug)]
184
+ pub enum NetworkError {
185
+ Io(std::io::Error),
186
+ AddrParse(std::net::AddrParseError),
187
+ Timeout,
188
+ }
189
+
190
+ impl std::fmt::Display for NetworkError {
191
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192
+ match self {
193
+ NetworkError::Io(e) => write!(f, "I/O: {e}"),
194
+ NetworkError::AddrParse(e) => write!(f, "address parse: {e}"),
195
+ NetworkError::Timeout => write!(f, "connection timed out"),
196
+ }
197
+ }
198
+ }
199
+
200
+ impl std::error::Error for NetworkError {}
201
+
202
+ impl From<std::io::Error> for NetworkError {
203
+ fn from(e: std::io::Error) -> Self { NetworkError::Io(e) }
204
+ }
205
+ impl From<std::net::AddrParseError> for NetworkError {
206
+ fn from(e: std::net::AddrParseError) -> Self { NetworkError::AddrParse(e) }
207
+ }
208
+
209
+ fn connect(addr: &str) -> Result<std::net::TcpStream, NetworkError> {
210
+ let addr: std::net::SocketAddr = addr.parse()?; // AddrParseError → NetworkError
211
+ let stream = std::net::TcpStream::connect(addr)?; // io::Error → NetworkError
212
+ Ok(stream)
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Networking: State Machines with Enums (Ch 8)
219
+
220
+ ```rust
221
+ #[derive(Debug, Clone, PartialEq)]
222
+ enum TcpState {
223
+ Closed,
224
+ Listen,
225
+ SynReceived,
226
+ Established,
227
+ FinWait1,
228
+ Closed_,
229
+ }
230
+
231
+ impl TcpState {
232
+ fn on_syn(self) -> Result<Self, &'static str> {
233
+ match self {
234
+ TcpState::Listen => Ok(TcpState::SynReceived),
235
+ other => Err("SYN received in invalid state"),
236
+ }
237
+ }
238
+
239
+ fn on_ack(self) -> Result<Self, &'static str> {
240
+ match self {
241
+ TcpState::SynReceived => Ok(TcpState::Established),
242
+ other => Err("ACK received in invalid state"),
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Concurrency: Thread Pool via Channels (Ch 10)
251
+
252
+ ```rust
253
+ use std::sync::{Arc, Mutex, mpsc};
254
+ use std::thread;
255
+
256
+ // move closure required — captures must be 'static (Ch 10)
257
+ fn spawn_worker(id: usize, rx: Arc<Mutex<mpsc::Receiver<String>>>) {
258
+ thread::spawn(move || loop { // move transfers rx into thread
259
+ let msg = rx.lock().expect("mutex poisoned").recv();
260
+ match msg {
261
+ Ok(s) => println!("worker {id}: {s}"),
262
+ Err(_) => break,
263
+ }
264
+ });
265
+ }
266
+
267
+ fn main() {
268
+ let (tx, rx) = mpsc::channel::<String>();
269
+ let rx = Arc::new(Mutex::new(rx));
270
+
271
+ for i in 0..4 {
272
+ spawn_worker(i, Arc::clone(&rx));
273
+ }
274
+
275
+ tx.send("hello".into()).unwrap();
276
+ tx.send("world".into()).unwrap();
277
+ drop(tx); // close channel — workers will exit their loops
278
+ thread::sleep(std::time::Duration::from_millis(10));
279
+ }
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Time: Instant vs SystemTime + NTP Offset (Ch 9)
285
+
286
+ ```rust
287
+ use std::time::{Instant, SystemTime, UNIX_EPOCH};
288
+
289
+ // Instant for elapsed — monotonic, cannot go backwards (Ch 9)
290
+ let start = Instant::now();
291
+ do_work();
292
+ println!("elapsed: {:?}", start.elapsed());
293
+
294
+ // SystemTime for wall clock (can go backwards — don't use for elapsed)
295
+ let unix_ts = SystemTime::now()
296
+ .duration_since(UNIX_EPOCH)
297
+ .expect("system clock before Unix epoch")
298
+ .as_secs();
299
+
300
+ // NTP epoch conversion (Ch 9)
301
+ // NTP counts from 1900-01-01; Unix counts from 1970-01-01
302
+ const NTP_UNIX_OFFSET: u64 = 2_208_988_800;
303
+
304
+ fn ntp_to_unix(ntp_seconds: u64) -> u64 {
305
+ ntp_seconds.saturating_sub(NTP_UNIX_OFFSET)
306
+ }
307
+
308
+ fn unix_to_ntp(unix_seconds: u64) -> u64 {
309
+ unix_seconds + NTP_UNIX_OFFSET
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Unsafe: Safe Abstraction Pattern (Ch 6)
316
+
317
+ ```rust
318
+ // Wrap unsafe in a safe API — callers need not use unsafe (Ch 6)
319
+ pub struct AlignedBuffer {
320
+ ptr: *mut u8,
321
+ len: usize,
322
+ }
323
+
324
+ impl AlignedBuffer {
325
+ pub fn new(len: usize) -> Self {
326
+ // SAFETY: len > 0, alignment is a power of 2, ptr checked for null
327
+ let layout = std::alloc::Layout::from_size_align(len, 64).unwrap();
328
+ let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
329
+ assert!(!ptr.is_null(), "allocation failed");
330
+ AlignedBuffer { ptr, len }
331
+ }
332
+
333
+ pub fn as_slice(&self) -> &[u8] {
334
+ // SAFETY: ptr valid, len accurate, no mutable alias exists
335
+ unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
336
+ }
337
+ }
338
+
339
+ impl Drop for AlignedBuffer {
340
+ fn drop(&mut self) {
341
+ // SAFETY: same layout as alloc, ptr not yet freed
342
+ let layout = std::alloc::Layout::from_size_align(self.len, 64).unwrap();
343
+ unsafe { std::alloc::dealloc(self.ptr, layout) }
344
+ }
345
+ }
346
+ ```
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ review.py — Pre-analysis script for Rust in Action reviews.
4
+ Usage: python review.py <file.rs>
5
+
6
+ Scans a Rust source file for systems-programming anti-patterns from the book:
7
+ endianness issues, unsafe shared state, missing buffered I/O, unwrap misuse,
8
+ unbounded thread spawning, and incorrect smart pointer choices.
9
+ """
10
+
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ CHECKS = [
17
+ (
18
+ r"from_ne_bytes|to_ne_bytes",
19
+ "Ch 5/7: Native endianness",
20
+ "use from_le_bytes/to_le_bytes or from_be_bytes/to_be_bytes to match the protocol spec",
21
+ ),
22
+ (
23
+ r"static\s+mut\s+\w+",
24
+ "Ch 6/10: static mut",
25
+ "data race risk — replace with Arc<Mutex<T>> or std::sync::atomic",
26
+ ),
27
+ (
28
+ r"\.unwrap\(\)",
29
+ "Ch 3: .unwrap()",
30
+ "panics on failure — use ?, .expect(\"reason\"), or match in production paths",
31
+ ),
32
+ (
33
+ r"unsafe\s*\{",
34
+ "Ch 6: unsafe block",
35
+ "ensure a safe abstraction wraps this; add a // SAFETY: comment explaining invariants",
36
+ ),
37
+ (
38
+ r"File::open|File::create",
39
+ "Ch 7: Unbuffered file I/O",
40
+ "wrap in BufReader::new()/BufWriter::new() to batch syscalls",
41
+ ),
42
+ (
43
+ r"thread::spawn",
44
+ "Ch 10: thread::spawn",
45
+ "if inside a loop, consider a thread pool (channel + Arc<Mutex<Receiver>>) instead of one thread per task",
46
+ ),
47
+ (
48
+ r"\bRc\s*::\s*(new|clone)\b",
49
+ "Ch 6: Rc usage",
50
+ "Rc is not Send — if shared across threads, replace with Arc",
51
+ ),
52
+ (
53
+ r"Box::new\(Vec\b|Box::new\(vec!",
54
+ "Ch 6: Box<Vec<T>>",
55
+ "Vec already heap-allocates — Box<Vec<T>> is double-indirection with no benefit; return Vec directly",
56
+ ),
57
+ (
58
+ r"\bexpect\s*\(\s*\)",
59
+ "Ch 3: .expect() with empty string",
60
+ "add a meaningful reason: .expect(\"what invariant was violated\")",
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 # skip comments
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 group_by_label(findings: list[dict]) -> dict:
84
+ groups: dict[str, list] = {}
85
+ for f in findings:
86
+ groups.setdefault(f["label"], []).append(f)
87
+ return groups
88
+
89
+
90
+ def sep(char="-", width=70) -> str:
91
+ return char * width
92
+
93
+
94
+ def main() -> None:
95
+ if len(sys.argv) < 2:
96
+ print("Usage: python review.py <file.rs>")
97
+ sys.exit(1)
98
+
99
+ path = Path(sys.argv[1])
100
+ if not path.exists():
101
+ print(f"Error: file not found: {path}")
102
+ sys.exit(1)
103
+
104
+ if path.suffix.lower() != ".rs":
105
+ print(f"Warning: expected a .rs file, got '{path.suffix}' — continuing anyway")
106
+
107
+ source = path.read_text(encoding="utf-8", errors="replace")
108
+ findings = scan(source)
109
+ groups = group_by_label(findings)
110
+
111
+ print(sep("="))
112
+ print("RUST IN ACTION — PRE-REVIEW REPORT")
113
+ print(sep("="))
114
+ print(f"File : {path}")
115
+ print(f"Lines : {len(source.splitlines())}")
116
+ print(f"Issues : {len(findings)} potential anti-patterns across {len(groups)} categories")
117
+ print()
118
+
119
+ if not findings:
120
+ print(" [OK] No common anti-patterns detected.")
121
+ print()
122
+ else:
123
+ for label, items in groups.items():
124
+ print(sep())
125
+ print(f" {label} ({len(items)} occurrence{'s' if len(items) != 1 else ''})")
126
+ print(sep())
127
+ print(f" Advice: {items[0]['advice']}")
128
+ print()
129
+ for item in items[:5]: # cap display at 5 per category
130
+ print(f" line {item['line']:>4}: {item['text'][:100]}")
131
+ if len(items) > 5:
132
+ print(f" ... and {len(items) - 5} more occurrence(s)")
133
+ print()
134
+
135
+ severity = (
136
+ "HIGH" if len(findings) >= 5
137
+ else "MEDIUM" if len(findings) >= 2
138
+ else "LOW" if findings
139
+ else "NONE"
140
+ )
141
+ print(sep("="))
142
+ print(f"SEVERITY: {severity} | Review chapters: Ch 3 (errors), Ch 5-7 (data/files), Ch 6 (pointers), Ch 10 (concurrency)")
143
+ print(sep("="))
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
@@ -10,7 +10,7 @@ description: >
10
10
 
11
11
  # Skill Router
12
12
 
13
- You are a skill selector for the `@booklib/skills` library — a collection of 17 book-based AI skills covering code quality, architecture, language best practices, and design. Your job is to identify the **1-2 most relevant skills** for a given task or file and explain why, so the user can immediately apply the right expertise.
13
+ You are a skill selector for the `@booklib/skills` library — a collection of 19 book-based AI skills covering code quality, architecture, language best practices, and design. Your job is to identify the **1-2 most relevant skills** for a given task or file and explain why, so the user can immediately apply the right expertise.
14
14
 
15
15
  ## When You're Triggered
16
16
 
@@ -41,7 +41,7 @@ Identify what the user is trying to do:
41
41
 
42
42
  From the file extension, imports, description, or code provided:
43
43
 
44
- - **Language signals:** `.py` → Python skills; `.java` → `effective-java` or `clean-code-reviewer`; `.kt` → `effective-kotlin` or `kotlin-in-action`; `.js`/`.ts` → `clean-code-reviewer` or `design-patterns`
44
+ - **Language signals:** `.py` → Python skills; `.java` → `effective-java` or `clean-code-reviewer`; `.kt` → `effective-kotlin` or `kotlin-in-action`; `.ts`/`.tsx` → `effective-typescript`; `.rs` → `programming-with-rust`; `.js` → `clean-code-reviewer` or `design-patterns`
45
45
  - **Domain signals:** "microservice", "saga" → microservices-patterns; "bounded context", "aggregate" → domain-driven-design; "chart", "visualization" → storytelling-with-data; "UI", "layout", "typography" → refactoring-ui; "web scraping", "BeautifulSoup" → web-scraping-python; "asyncio", "coroutine" → using-asyncio-python; "data pipeline", "ETL" → data-pipelines; "replication", "partitioning", "database internals" → data-intensive-patterns
46
46
  - **Architecture signals:** "monolith decomposition", "distributed systems" → microservices-patterns or system-design-interview
47
47
 
@@ -57,17 +57,19 @@ Apply these primary routing rules:
57
57
  4. **Python best practices** → `effective-python`
58
58
  5. **Python asyncio/concurrency** → `using-asyncio-python` (overrides effective-python for async topics)
59
59
  6. **Python web scraping** → `web-scraping-python`
60
- 7. **OO design patterns (GoF)** → `design-patterns`
61
- 8. **Domain modeling, DDD** → `domain-driven-design`
62
- 9. **Microservices, sagas, decomposition** → `microservices-patterns`
63
- 10. **System scalability, estimation** → `system-design-interview`
64
- 11. **Data storage internals, replication** → `data-intensive-patterns`
65
- 12. **Data pipelines, ETL** → `data-pipelines`
66
- 13. **UI design, visual hierarchy** → `refactoring-ui`
67
- 14. **Charts, data visualization** → `storytelling-with-data`
68
- 15. **Web animation** → `animation-at-work`
69
- 16. **Startup strategy, MVP** → `lean-startup`
70
- 17. **Routing help** → `skill-router` (this skill)
60
+ 7. **TypeScript best practices, type design, any, migration** → `effective-typescript`
61
+ 8. **Rust, ownership, borrowing, lifetimes, traits, concurrency** → `programming-with-rust`
62
+ 9. **OO design patterns (GoF)** → `design-patterns`
63
+ 10. **Domain modeling, DDD** → `domain-driven-design`
64
+ 11. **Microservices, sagas, decomposition** → `microservices-patterns`
65
+ 12. **System scalability, estimation** → `system-design-interview`
66
+ 13. **Data storage internals, replication** → `data-intensive-patterns`
67
+ 14. **Data pipelines, ETL** → `data-pipelines`
68
+ 15. **UI design, visual hierarchy** → `refactoring-ui`
69
+ 16. **Charts, data visualization** → `storytelling-with-data`
70
+ 17. **Web animation** → `animation-at-work`
71
+ 18. **Startup strategy, MVP** → `lean-startup`
72
+ 19. **Routing help** → `skill-router` (this skill)
71
73
 
72
74
  Read `references/routing-heuristics.md` for detailed decision rules and conflict resolution.
73
75
 
@@ -77,6 +79,7 @@ Some skill pairs can conflict. Resolve using these rules:
77
79
 
78
80
  | Conflict | Resolution |
79
81
  |----------|------------|
82
+ | `effective-typescript` vs `clean-code-reviewer` | Use `effective-typescript` for TypeScript-specific concerns (type system, any, type design); use `clean-code-reviewer` for naming/functions/readability which applies cross-language |
80
83
  | `clean-code-reviewer` vs `effective-java` | Use `effective-java` for Java-specific idioms (generics, enums, builders); use `clean-code-reviewer` for naming/functions/readability which applies cross-language |
81
84
  | `effective-kotlin` vs `kotlin-in-action` | `effective-kotlin` for best practices and pitfall avoidance; `kotlin-in-action` for learning Kotlin language features |
82
85
  | `domain-driven-design` vs `microservices-patterns` | `domain-driven-design` for domain model design; `microservices-patterns` for service decomposition and inter-service communication. Apply both if designing a new microservice with rich domain model |
@@ -1,6 +1,6 @@
1
1
  # Skill Catalog
2
2
 
3
- All 17 skills in the `@booklib/skills` library with routing metadata.
3
+ All 19 skills in the `@booklib/skills` library with routing metadata.
4
4
 
5
5
  ## animation-at-work
6
6
  - **Source:** *Animation at Work* by Rachel Nabors
@@ -74,6 +74,24 @@ All 17 skills in the `@booklib/skills` library with routing metadata.
74
74
  - **Works well with:** kotlin-in-action (best practices + language features), clean-code-reviewer (Kotlin idioms + readability)
75
75
  - **Conflicts with:** kotlin-in-action (effective-kotlin for best practices; kotlin-in-action for language learning — if user knows Kotlin and wants advice, use effective-kotlin)
76
76
 
77
+ ## effective-typescript
78
+ - **Source:** *Effective TypeScript* by Dan Vanderkam
79
+ - **Domain:** TypeScript best practices, type system, type design, any, migration
80
+ - **Language:** TypeScript only
81
+ - **Trigger keywords:** "effective typescript", "typescript best practices", "type safety", "any type", "type assertions", "type design", "strict mode", "typescript review", "migrate to typescript", "unknown vs any", "type declarations", "tagged union", "branded types", "typescript item"
82
+ - **Anti-triggers:** Non-TypeScript code, plain JavaScript without types, architecture concerns
83
+ - **Works well with:** clean-code-reviewer (TypeScript idioms + readability), design-patterns (type-safe pattern implementations)
84
+ - **Conflicts with:** clean-code-reviewer (effective-typescript wins for TypeScript-specific type system concerns; clean-code-reviewer for general readability)
85
+
86
+ ## programming-with-rust
87
+ - **Source:** *Programming with Rust* by Donis Marshall
88
+ - **Domain:** Rust language, ownership, borrowing, lifetimes, error handling, traits, concurrency
89
+ - **Language:** Rust only
90
+ - **Trigger keywords:** "rust", "ownership", "borrow checker", "lifetimes", "Result", "Option", "traits", "fearless concurrency", "cargo", "crate", "unwrap", "move semantics", "Arc", "Mutex", "pattern matching rust", ".rs"
91
+ - **Anti-triggers:** Non-Rust code, architecture concerns, domain modeling
92
+ - **Works well with:** clean-code-reviewer (Rust idioms + readability), design-patterns (Rust implementation of patterns)
93
+ - **Conflicts with:** clean-code-reviewer (programming-with-rust wins for Rust-specific concerns like ownership, lifetimes, and the borrow checker)
94
+
77
95
  ## effective-python
78
96
  - **Source:** *Effective Python* (2nd Edition) by Brett Slatkin
79
97
  - **Domain:** Python best practices, Pythonic idioms