@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.
- package/AGENTS.md +108 -0
- package/CLAUDE.md +57 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +13 -0
- package/README.md +68 -45
- package/SECURITY.md +9 -0
- package/assets/logo.svg +36 -0
- package/demo.gif +0 -0
- package/demo.tape +40 -0
- package/docs/index.html +187 -0
- package/package.json +2 -2
- package/skills/effective-typescript/SKILL.md +166 -0
- package/skills/effective-typescript/evals/evals.json +36 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/programming-with-rust/SKILL.md +194 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/rust-in-action/SKILL.md +290 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +16 -13
- 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
|
|
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`; `.
|
|
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. **
|
|
61
|
-
8. **
|
|
62
|
-
9. **
|
|
63
|
-
10. **
|
|
64
|
-
11. **
|
|
65
|
-
12. **
|
|
66
|
-
13. **
|
|
67
|
-
14. **
|
|
68
|
-
15. **
|
|
69
|
-
16. **
|
|
70
|
-
17. **
|
|
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
|
|
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
|