@booklib/skills 1.5.0 → 1.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@booklib/skills",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Book knowledge distilled into structured AI skills for Claude Code and other AI assistants",
5
5
  "bin": {
6
6
  "skills": "bin/skills.js"
@@ -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()