@booklib/skills 1.8.0 → 1.10.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 +111 -53
- package/CONTRIBUTING.md +147 -0
- package/PLAN.md +28 -0
- package/README.md +102 -108
- package/bin/skills.js +218 -20
- package/hooks/hooks.json +12 -0
- package/hooks/suggest.js +153 -0
- package/package.json +1 -1
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
package/hooks/suggest.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* booklib-suggest.js
|
|
4
|
+
* Claude Code UserPromptSubmit hook — suggests a relevant @booklib/skills skill
|
|
5
|
+
* when the prompt contains a review intent AND a language/domain signal.
|
|
6
|
+
*
|
|
7
|
+
* Install: copy (or symlink) this file to ~/.claude/booklib-suggest.js
|
|
8
|
+
* Hook config (hooks.json):
|
|
9
|
+
* { "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "node \"$HOME/.claude/booklib-suggest.js\"" }] }] }
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
process.exitCode = 0;
|
|
15
|
+
|
|
16
|
+
const REVIEW_KEYWORDS = [
|
|
17
|
+
"review", "check", "improve", "refactor", "fix", "audit",
|
|
18
|
+
"analyse", "analyze", "critique", "lint",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const LANGUAGE_SIGNALS = {
|
|
22
|
+
python_async: [".py", "asyncio", "async def", "await "],
|
|
23
|
+
python_scraping: [".py", "python", "beautifulsoup", "scrapy", "requests.get", "web scraping"],
|
|
24
|
+
python: [".py", "python", "def ", "async def", "import ", "asyncio", "beautifulsoup", "scrapy"],
|
|
25
|
+
typescript: [".ts", ".tsx", ".js", "typescript", "interface ", "type ", "const ", "function "],
|
|
26
|
+
java: [".java", "java", "class ", "@override", "public static"],
|
|
27
|
+
kotlin: [".kt", "kotlin", "fun ", "val ", "var ", "data class"],
|
|
28
|
+
rust: [".rs", "rust", "fn ", "impl ", "struct ", "enum ", "let mut"],
|
|
29
|
+
ui_animation: [".css", ".scss", "animation", "transition", "@keyframes", "styled", "tailwind"],
|
|
30
|
+
ui: [".css", ".scss", "animation", "transition", "@keyframes", "styled", "tailwind"],
|
|
31
|
+
data: ["pipeline", "etl", "dataframe", "schema", "migration", "replication"],
|
|
32
|
+
architecture: ["microservice", "aggregate", "bounded context", "saga", "event sourcing"],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const SKILL_MAP = {
|
|
36
|
+
python_async: { skill: "/using-asyncio-python", reason: "Async patterns and asyncio best practices in Python" },
|
|
37
|
+
python_scraping: { skill: "/web-scraping-python", reason: "Web scraping techniques and patterns with Python" },
|
|
38
|
+
python: { skill: "/effective-python", reason: "Pythonic idioms and best practices" },
|
|
39
|
+
typescript: { skill: "/effective-typescript", reason: "TypeScript type safety and idiomatic patterns" },
|
|
40
|
+
java: { skill: "/effective-java", reason: "Effective Java patterns and API design" },
|
|
41
|
+
kotlin: { skill: "/effective-kotlin", reason: "Idiomatic Kotlin and best practices" },
|
|
42
|
+
rust: { skill: "/programming-with-rust", reason: "Rust ownership, safety, and idiomatic patterns" },
|
|
43
|
+
ui_animation: { skill: "/animation-at-work", reason: "Web animation principles and best practices" },
|
|
44
|
+
ui: { skill: "/refactoring-ui", reason: "UI design principles and visual hierarchy" },
|
|
45
|
+
data: { skill: "/data-pipelines", reason: "Data pipeline design and ETL best practices" },
|
|
46
|
+
architecture: { skill: "/skill-router", reason: "Routes to the right skill for architecture concerns" },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function extractPrompt(raw) {
|
|
50
|
+
if (!raw || raw.trim() === "") return "";
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
// Claude Code may send { prompt: "..." } or { message: "..." }
|
|
54
|
+
if (parsed && typeof parsed === "object") {
|
|
55
|
+
return String(parsed.prompt || parsed.message || parsed.text || "");
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {
|
|
58
|
+
// Not JSON — treat as raw prompt text
|
|
59
|
+
}
|
|
60
|
+
return raw;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasReviewIntent(text) {
|
|
64
|
+
const lower = text.toLowerCase();
|
|
65
|
+
return REVIEW_KEYWORDS.some((kw) => lower.includes(kw));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns the most specific language key that matches, or null.
|
|
70
|
+
* Order matters: more specific keys (python_async, python_scraping) are checked first.
|
|
71
|
+
*/
|
|
72
|
+
function detectLanguage(text) {
|
|
73
|
+
const lower = text.toLowerCase();
|
|
74
|
+
|
|
75
|
+
const orderedKeys = [
|
|
76
|
+
"python_async",
|
|
77
|
+
"python_scraping",
|
|
78
|
+
"python",
|
|
79
|
+
"typescript",
|
|
80
|
+
"java",
|
|
81
|
+
"kotlin",
|
|
82
|
+
"rust",
|
|
83
|
+
"ui_animation",
|
|
84
|
+
"ui",
|
|
85
|
+
"data",
|
|
86
|
+
"architecture",
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const key of orderedKeys) {
|
|
90
|
+
const signals = LANGUAGE_SIGNALS[key];
|
|
91
|
+
// For compound keys, require at least 2 signals to avoid false positives
|
|
92
|
+
// (e.g. python_async needs both a python marker AND an async marker)
|
|
93
|
+
if (key === "python_async") {
|
|
94
|
+
const hasPython = [".py", "python", "def ", "import "].some((s) => lower.includes(s));
|
|
95
|
+
const hasAsync = ["asyncio", "async def", "await "].some((s) => lower.includes(s));
|
|
96
|
+
if (hasPython && hasAsync) return key;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (key === "python_scraping") {
|
|
100
|
+
const hasPython = [".py", "python", "def ", "import "].some((s) => lower.includes(s));
|
|
101
|
+
const hasScraping = ["beautifulsoup", "scrapy", "web scraping", "requests.get"].some((s) => lower.includes(s));
|
|
102
|
+
if (hasPython && hasScraping) return key;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (signals.some((s) => lower.includes(s.toLowerCase()))) {
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function main() {
|
|
114
|
+
let raw = "";
|
|
115
|
+
try {
|
|
116
|
+
// Read all of stdin synchronously
|
|
117
|
+
const fd = require("fs").openSync("/dev/stdin", "r");
|
|
118
|
+
const chunks = [];
|
|
119
|
+
const buf = Buffer.alloc(4096);
|
|
120
|
+
let bytesRead;
|
|
121
|
+
while ((bytesRead = require("fs").readSync(fd, buf, 0, buf.length, null)) > 0) {
|
|
122
|
+
chunks.push(buf.slice(0, bytesRead));
|
|
123
|
+
}
|
|
124
|
+
require("fs").closeSync(fd);
|
|
125
|
+
raw = Buffer.concat(chunks).toString("utf8");
|
|
126
|
+
} catch (_) {
|
|
127
|
+
// stdin unavailable or empty — exit silently
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const prompt = extractPrompt(raw);
|
|
132
|
+
if (!prompt) process.exit(0);
|
|
133
|
+
|
|
134
|
+
if (!hasReviewIntent(prompt)) process.exit(0);
|
|
135
|
+
|
|
136
|
+
const langKey = detectLanguage(prompt);
|
|
137
|
+
|
|
138
|
+
if (!langKey) {
|
|
139
|
+
// Review intent but no specific language — suggest clean-code-reviewer
|
|
140
|
+
process.stdout.write(
|
|
141
|
+
"💡 booklib: try /clean-code-reviewer — General clean code review principles\n"
|
|
142
|
+
);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const match = SKILL_MAP[langKey];
|
|
147
|
+
if (!match) process.exit(0);
|
|
148
|
+
|
|
149
|
+
process.stdout.write(`💡 booklib: try ${match.skill} — ${match.reason}\n`);
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main();
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Clean Code standards from Robert C. Martin. Apply to all code regardless of language.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Clean Code Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Clean Code* (Robert C. Martin) to all code you write or review.
|
|
8
|
+
|
|
9
|
+
## Names
|
|
10
|
+
|
|
11
|
+
- Use intention-revealing names — if the name needs a comment to explain it, rename it
|
|
12
|
+
- Avoid abbreviations unless universally understood (`url`, `id`, `ctx` are fine; `mgr`, `proc` are not)
|
|
13
|
+
- Classes and types are nouns; methods and functions are verb phrases
|
|
14
|
+
- Avoid noise words that add no meaning: `Manager`, `Data`, `Info`, `Handler` in type names usually signal a missing concept
|
|
15
|
+
- Boolean variables and functions read as assertions: `isEnabled`, `hasPermission`, `canRetry`
|
|
16
|
+
|
|
17
|
+
## Functions
|
|
18
|
+
|
|
19
|
+
- Functions do one thing; if you can extract a meaningful sub-function with a non-trivial name, the function does too much
|
|
20
|
+
- Keep functions short — aim for under 20 lines; over 40 is a smell
|
|
21
|
+
- Max 3 parameters; group related parameters into a value object when you need more
|
|
22
|
+
- Avoid boolean flag parameters — they signal the function does two things; split it
|
|
23
|
+
- No side effects in functions that return values
|
|
24
|
+
|
|
25
|
+
## Comments
|
|
26
|
+
|
|
27
|
+
- Comments compensate for failure to express intent in code — prefer renaming over commenting
|
|
28
|
+
- Never commit commented-out code; use version control
|
|
29
|
+
- `// TODO:` is acceptable only when tracked in an issue; delete stale TODOs
|
|
30
|
+
- Document *why*, not *what* — the code shows what; the comment explains a non-obvious reason
|
|
31
|
+
|
|
32
|
+
## Structure
|
|
33
|
+
|
|
34
|
+
- Group related code together; put high-level concepts at the top, details below
|
|
35
|
+
- Functions in a file should be ordered so callers appear before callees
|
|
36
|
+
- Avoid deep nesting — if `if`/`else` chains exceed 3 levels, extract or invert conditions
|
|
37
|
+
|
|
38
|
+
## Error handling
|
|
39
|
+
|
|
40
|
+
- Prefer exceptions over error codes for exceptional conditions
|
|
41
|
+
- Handle errors at the appropriate abstraction level — don't catch and re-throw unless you add context
|
|
42
|
+
- Never swallow exceptions silently; at minimum log before ignoring
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Effective Java standards from Joshua Bloch. Apply when writing or reviewing Java code.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Effective Java Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Effective Java* (Joshua Bloch, 3rd edition) to all Java code.
|
|
8
|
+
|
|
9
|
+
## Object creation
|
|
10
|
+
|
|
11
|
+
- Prefer static factory methods over constructors — they have names, can return subtypes, and can cache instances
|
|
12
|
+
- Use a builder when a constructor or factory would have more than 3 parameters
|
|
13
|
+
- Never create unnecessary objects; reuse `String` literals, prefer `Boolean.valueOf(x)` over `new Boolean(x)`
|
|
14
|
+
|
|
15
|
+
## Classes and mutability
|
|
16
|
+
|
|
17
|
+
- Minimize mutability — all fields `private final` by default; add setters only when needed
|
|
18
|
+
- Favor composition over inheritance; explicitly document classes designed for extension or mark them `final`
|
|
19
|
+
- Override `@Override` on every method that overrides or implements; the annotation catches typos at compile time
|
|
20
|
+
|
|
21
|
+
## Methods
|
|
22
|
+
|
|
23
|
+
- Validate parameters at entry; throw `IllegalArgumentException`, `NullPointerException`, or `IndexOutOfBoundsException` with a message
|
|
24
|
+
- Return empty collections or `Optional`, never `null`, from methods with a non-primitive return type
|
|
25
|
+
- Use `Optional` for return values that may be absent; don't use it for fields or parameters
|
|
26
|
+
|
|
27
|
+
## Exceptions
|
|
28
|
+
|
|
29
|
+
- Use checked exceptions for recoverable conditions; unchecked (`RuntimeException`) for programming errors
|
|
30
|
+
- Prefer standard exceptions: `IllegalArgumentException`, `IllegalStateException`, `UnsupportedOperationException`, `NullPointerException`
|
|
31
|
+
- Don't swallow exceptions — at minimum log with context before ignoring; never `catch (Exception e) {}`
|
|
32
|
+
|
|
33
|
+
## Generics and collections
|
|
34
|
+
|
|
35
|
+
- Use generic types and methods; avoid raw types (`List` → `List<E>`)
|
|
36
|
+
- Use bounded wildcards (`? extends T` for producers, `? super T` for consumers — PECS)
|
|
37
|
+
- Prefer `List` over arrays for type safety; use arrays only for performance-sensitive low-level code
|
|
38
|
+
|
|
39
|
+
## Concurrency
|
|
40
|
+
|
|
41
|
+
- Synchronize all accesses to shared mutable state; prefer `java.util.concurrent` utilities over `synchronized`
|
|
42
|
+
- Prefer immutable objects and thread confinement over shared mutable state
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Effective Kotlin standards from Marcin Moskała. Apply when writing or reviewing Kotlin code.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Effective Kotlin Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Effective Kotlin* (Marcin Moskała, 2nd edition) to all Kotlin code.
|
|
8
|
+
|
|
9
|
+
## Safety
|
|
10
|
+
|
|
11
|
+
- Prefer `val` over `var`; use `var` only when mutation is genuinely required
|
|
12
|
+
- Use nullable types explicitly (`T?`); avoid `!!` — narrow with `?.`, `?:`, `let`, or `checkNotNull()`
|
|
13
|
+
- Use `require()` for argument preconditions and `check()` for state preconditions at function entry
|
|
14
|
+
|
|
15
|
+
## Functions
|
|
16
|
+
|
|
17
|
+
- Use named arguments when passing more than 2 parameters, especially when they share the same type
|
|
18
|
+
- Use default arguments instead of overloads for optional behavior
|
|
19
|
+
- Prefer extension functions over utility classes for domain operations on a type you own
|
|
20
|
+
|
|
21
|
+
## Classes and design
|
|
22
|
+
|
|
23
|
+
- Use data classes for value objects — they get `equals`, `hashCode`, `copy`, and `toString` for free
|
|
24
|
+
- Prefer sealed classes over open hierarchies when the set of subtypes is finite and known
|
|
25
|
+
- Use `object` for singletons, `companion object` for factory methods and class-level constants
|
|
26
|
+
|
|
27
|
+
## Collections
|
|
28
|
+
|
|
29
|
+
- Use functional operators (`map`, `filter`, `fold`, `groupBy`) over manual loops
|
|
30
|
+
- Prefer `Sequence` for large collections or multi-step pipelines — avoids intermediate lists
|
|
31
|
+
- Use `buildList { }` / `buildMap { }` instead of a mutable variable followed by `.toList()`
|
|
32
|
+
|
|
33
|
+
## Coroutines
|
|
34
|
+
|
|
35
|
+
- Launch coroutines in a structured `CoroutineScope`; never use `GlobalScope` in production
|
|
36
|
+
- Use `withContext(Dispatchers.IO)` for blocking I/O; never block the main/UI thread
|
|
37
|
+
- Prefer `Flow` over callbacks for asynchronous streams; use `StateFlow` for observable state
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Effective Python standards from Brett Slatkin. Apply when writing or reviewing Python code.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Effective Python Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Effective Python* (Brett Slatkin, 3rd edition) to all Python code.
|
|
8
|
+
|
|
9
|
+
## Pythonic style
|
|
10
|
+
|
|
11
|
+
- Use `enumerate()` over `range(len(...))` for indexed iteration
|
|
12
|
+
- Use f-strings for interpolation; avoid `%` formatting and `.format()`
|
|
13
|
+
- Prefer unpacking over indexing: `first, *rest = items` instead of `items[0]` and `items[1:]`
|
|
14
|
+
- Use `zip()` to iterate two sequences together; use `zip(strict=True)` when lengths must match
|
|
15
|
+
|
|
16
|
+
## Data structures
|
|
17
|
+
|
|
18
|
+
- Use `list` for ordered mutable sequences, `tuple` for immutable positional data, `set` for membership tests
|
|
19
|
+
- Use `collections.defaultdict` or `Counter` instead of manual dict initialization
|
|
20
|
+
- Prefer `dataclasses` over plain dicts or namedtuples for structured data with methods
|
|
21
|
+
|
|
22
|
+
## Functions
|
|
23
|
+
|
|
24
|
+
- Use keyword-only arguments (`def f(a, *, b)`) for optional parameters that benefit from names at the call site
|
|
25
|
+
- Never use mutable default arguments — use `None` and assign inside the function body
|
|
26
|
+
- Prefer generator expressions `(x for x in ...)` over list comprehensions when you don't need the full list in memory
|
|
27
|
+
|
|
28
|
+
## Type annotations
|
|
29
|
+
|
|
30
|
+
- Annotate all public functions and class attributes
|
|
31
|
+
- Use `X | None` (Python 3.10+) or `Optional[X]` for nullable types; never return `None` silently from a typed function
|
|
32
|
+
- Avoid `Any` except at system boundaries (external APIs, deserialized JSON)
|
|
33
|
+
|
|
34
|
+
## Error handling
|
|
35
|
+
|
|
36
|
+
- Catch specific exception types; never use bare `except:`
|
|
37
|
+
- Use `contextlib.suppress(ExceptionType)` for intentionally ignored exceptions — makes the intent explicit
|
|
38
|
+
- Use `__all__` in every module to declare its public API
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Rust standards from Programming with Rust and Rust in Action. Apply when writing or reviewing Rust code.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Rust Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Programming with Rust* (Donis Marshall) and *Rust in Action* (Tim McNamara) to all Rust code.
|
|
8
|
+
|
|
9
|
+
## Ownership and borrowing
|
|
10
|
+
|
|
11
|
+
- Use owned values (`String`, `Vec<T>`) for data you own; borrow (`&str`, `&[T]`) when you only need to read
|
|
12
|
+
- Prefer passing `&T` or `&mut T` over cloning; clone only when ownership transfer is required
|
|
13
|
+
- Use `Rc<T>` for single-threaded shared ownership, `Arc<T>` for multi-threaded; use `RefCell<T>` / `Mutex<T>` for interior mutability
|
|
14
|
+
|
|
15
|
+
## Error handling
|
|
16
|
+
|
|
17
|
+
- Return `Result<T, E>` from all fallible functions; propagate with `?`
|
|
18
|
+
- Use `thiserror` to define library errors with `#[derive(Error)]`; use `anyhow` for application-level error context
|
|
19
|
+
- Avoid `.unwrap()` in library code; use `.expect("clear message")` in application code where panicking is intentional
|
|
20
|
+
|
|
21
|
+
## Types and traits
|
|
22
|
+
|
|
23
|
+
- Use `struct` for data, `enum` for variants with payloads, `trait` for shared behaviour
|
|
24
|
+
- Implement standard traits where appropriate: `Debug` always, `Display` for user-facing types, `Clone`, `PartialEq`, `Hash` as needed
|
|
25
|
+
- Use `impl Trait` in argument position for static dispatch; `Box<dyn Trait>` only when you need runtime dispatch
|
|
26
|
+
|
|
27
|
+
## Idiomatic patterns
|
|
28
|
+
|
|
29
|
+
- Use `Iterator` adapters (`map`, `filter`, `flat_map`, `collect`) over manual loops — the compiler optimizes them equally
|
|
30
|
+
- Use `Option` methods (`map`, `unwrap_or`, `and_then`, `ok_or`) over `match` for simple transformations
|
|
31
|
+
- Use `if let` for single-variant matching; use `match` for exhaustive handling
|
|
32
|
+
|
|
33
|
+
## Naming and style
|
|
34
|
+
|
|
35
|
+
- Types: `PascalCase`; functions, variables, modules: `snake_case`; constants and statics: `SCREAMING_SNAKE_CASE`
|
|
36
|
+
- Lifetime names: `'a`, `'b` for simple cases; descriptive names (`'arena`, `'cx`) for complex lifetimes
|
|
37
|
+
- Mark all public items in a library crate with doc comments (`///`)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Always-on Effective TypeScript standards from Dan Vanderkam. Apply when writing or reviewing TypeScript or JavaScript code.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Effective TypeScript Standards
|
|
6
|
+
|
|
7
|
+
Apply these principles from *Effective TypeScript* (Dan Vanderkam, 2nd edition) to all TypeScript code.
|
|
8
|
+
|
|
9
|
+
## Types
|
|
10
|
+
|
|
11
|
+
- Prefer union types over enums for simple sets of values: `type Direction = 'N' | 'S' | 'E' | 'W'`
|
|
12
|
+
- Use `interface` for extensible object shapes that others may augment; use `type` for unions, intersections, and computed types
|
|
13
|
+
- Avoid `any`; use `unknown` when the type is genuinely unknown, then narrow with guards before use
|
|
14
|
+
- Avoid type assertions (`as T`) — prefer type narrowing, overloads, or generics
|
|
15
|
+
|
|
16
|
+
## Type inference
|
|
17
|
+
|
|
18
|
+
- Let TypeScript infer return types on internal functions; explicitly annotate public API return types
|
|
19
|
+
- Annotate a variable at declaration if it cannot be initialized immediately
|
|
20
|
+
- Use `as const` to preserve literal types; don't use it just to silence widening errors
|
|
21
|
+
|
|
22
|
+
## Null safety
|
|
23
|
+
|
|
24
|
+
- Enable `strict` mode (which includes `strictNullChecks`) — treat every `T | undefined` as requiring explicit handling
|
|
25
|
+
- Use optional chaining `?.` and nullish coalescing `??` over `&&` and `||` chains
|
|
26
|
+
- Never use non-null assertion (`!`) — narrow instead
|
|
27
|
+
|
|
28
|
+
## Structural typing
|
|
29
|
+
|
|
30
|
+
- TypeScript checks shapes, not nominal types — understand that duck typing applies
|
|
31
|
+
- Use discriminated unions with a `kind` or `type` literal field for exhaustive `switch` / narrowing
|
|
32
|
+
- Avoid class hierarchies for data shapes — prefer interfaces and composition
|
|
33
|
+
|
|
34
|
+
## Generics
|
|
35
|
+
|
|
36
|
+
- Constrain generics to the minimum required: `<T extends string>` not `<T>`
|
|
37
|
+
- Use descriptive generic names for complex types (`<TItem, TKey>`) and single letters for simple transforms (`<T>`, `<K, V>`)
|
|
38
|
+
|
|
39
|
+
## Functions
|
|
40
|
+
|
|
41
|
+
- Prefer function overloads over union parameter types to express the relationship between input and output
|
|
42
|
+
- Keep functions pure where possible; extract side effects to the call site
|