@booklib/skills 1.8.0 → 1.9.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/PLAN.md +100 -0
- package/README.md +102 -108
- package/bin/skills.js +120 -13
- package/hooks/hooks.json +12 -0
- package/hooks/suggest.js +153 -0
- package/package.json +1 -1
package/PLAN.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Implementation Plan
|
|
2
|
+
|
|
3
|
+
Current version: **1.8.0**
|
|
4
|
+
Next release: **1.9.0**
|
|
5
|
+
|
|
6
|
+
## Remaining features (priority order)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
### 1. `skills agents` list command
|
|
11
|
+
**File:** `bin/skills.js`
|
|
12
|
+
**What:** New `agents` command that lists all agents from `agents/` with name + description (parsed from frontmatter). Mirrors `skills list`.
|
|
13
|
+
**Usage:**
|
|
14
|
+
```bash
|
|
15
|
+
skills agents
|
|
16
|
+
skills agents --info booklib-reviewer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### 2. Cursor support (`--target`)
|
|
22
|
+
**File:** `bin/skills.js`
|
|
23
|
+
**What:** `--target` flag on `add` that writes skills to `.cursor/rules/` (Cursor's rule system). Skills install as rule files; agents not applicable to Cursor (no native agent system).
|
|
24
|
+
**Usage:**
|
|
25
|
+
```bash
|
|
26
|
+
skills add --profile=ts --target cursor # writes to .cursor/rules/
|
|
27
|
+
skills add --profile=ts --target all # writes to both .claude/ and .cursor/rules/
|
|
28
|
+
skills add effective-python --target cursor
|
|
29
|
+
```
|
|
30
|
+
**Cursor paths:**
|
|
31
|
+
- Skills → `.cursor/rules/<skill-name>.md` (copy of SKILL.md)
|
|
32
|
+
- Commands → not applicable
|
|
33
|
+
- Agents → not applicable
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 3. `hooks/` — UserPromptSubmit skill suggestion
|
|
38
|
+
**Files:** `hooks/hooks.json`, `hooks/suggest.js`
|
|
39
|
+
**What:** A single `UserPromptSubmit` hook. Reads the user's prompt, detects language + "review/check/improve/refactor" intent, outputs a one-line skill suggestion. Only fires when both intent AND a language signal are present — not on every message.
|
|
40
|
+
|
|
41
|
+
**Hook config:**
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"UserPromptSubmit": [{
|
|
45
|
+
"matcher": ".*",
|
|
46
|
+
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/booklib-suggest.js" }]
|
|
47
|
+
}]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**suggest.js logic:**
|
|
52
|
+
- Read prompt from stdin (JSON event from Claude Code)
|
|
53
|
+
- Check for review intent: `review|check|improve|refactor|fix|audit`
|
|
54
|
+
- Check for language signals: `.py`, `.ts`, `.tsx`, `.java`, `.kt`, `.rs`, etc.
|
|
55
|
+
- Output one-line suggestion or nothing
|
|
56
|
+
|
|
57
|
+
**Install path:** `hooks/suggest.js` in repo → installed to `.claude/` root as `booklib-suggest.js` via `skills add --all` or `skills add --hooks`.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 4. README overhaul
|
|
62
|
+
**File:** `README.md`
|
|
63
|
+
**What:** Rewrite to document the full three-tier architecture. Current README only describes skills. Needs: profiles section, agents section, commands section, architecture diagram.
|
|
64
|
+
|
|
65
|
+
**New structure:**
|
|
66
|
+
```
|
|
67
|
+
1. Tagline + install
|
|
68
|
+
2. Three-tier architecture diagram (skills → commands → agents → profiles)
|
|
69
|
+
3. Quick start (pick a profile)
|
|
70
|
+
4. Skills list (existing)
|
|
71
|
+
5. Agents (new section)
|
|
72
|
+
6. Profiles (new section)
|
|
73
|
+
7. Commands (new section — brief, link to commands/)
|
|
74
|
+
8. Quality / evals (existing, improved)
|
|
75
|
+
9. Contributing
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Parallel implementation tracks
|
|
81
|
+
|
|
82
|
+
| Track | Files touched | Depends on |
|
|
83
|
+
|-------|--------------|------------|
|
|
84
|
+
| A | `bin/skills.js` | — |
|
|
85
|
+
| B | `hooks/hooks.json`, `hooks/suggest.js` | — |
|
|
86
|
+
| C | `README.md` | — |
|
|
87
|
+
|
|
88
|
+
Tracks B and C are independent and can be implemented simultaneously.
|
|
89
|
+
Track A (`bin/skills.js`) has no file conflicts with B or C.
|
|
90
|
+
All three can be implemented in parallel.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Release checklist
|
|
95
|
+
|
|
96
|
+
- [ ] Track A: `skills agents` command + Cursor support in bin/skills.js
|
|
97
|
+
- [ ] Track B: hooks/hooks.json + hooks/suggest.js
|
|
98
|
+
- [ ] Track C: README overhaul
|
|
99
|
+
- [ ] Bump version to 1.9.0
|
|
100
|
+
- [ ] Commit + tag v1.9.0 + push
|
package/README.md
CHANGED
|
@@ -12,100 +12,100 @@
|
|
|
12
12
|
|
|
13
13
|
Book-grounded AI agent skills — each skill packages expert practices from a canonical programming book into reusable instructions that Claude and other AI agents can apply to code generation, code review, and design decisions.
|
|
14
14
|
|
|
15
|
-
```bash
|
|
16
|
-
npx skills add booklib-ai/skills
|
|
17
|
-
```
|
|
18
|
-
|
|
19
15
|

|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Structure
|
|
17
|
+
## Architecture
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
booklib-ai/skills (repo root)
|
|
27
|
-
├── skills/
|
|
28
|
-
│ ├── clean-code-reviewer/
|
|
29
|
-
│ │ ├── SKILL.md # Required
|
|
30
|
-
│ │ ├── examples/
|
|
31
|
-
│ │ ├── references/
|
|
32
|
-
│ │ ├── scripts/
|
|
33
|
-
│ │ └── evals/
|
|
34
|
-
│ └── [skill-name]/ # One folder per book
|
|
35
|
-
│ └── ...
|
|
36
|
-
├── README.md
|
|
37
|
-
├── LICENSE
|
|
38
|
-
└── package.json
|
|
39
|
-
```
|
|
19
|
+
The library is organized into three tiers that work together:
|
|
40
20
|
|
|
41
|
-
|
|
21
|
+
| Tier | Count | What it does |
|
|
22
|
+
|------|-------|--------------|
|
|
23
|
+
| **Skills** | 22 | Passive context loaded from `.claude/skills/` — triggered automatically when the AI detects a matching file or task |
|
|
24
|
+
| **Commands** | 22 | Explicit slash commands — `/effective-python`, `/design-patterns`, etc. — one per skill |
|
|
25
|
+
| **Agents** | 8 | Autonomous reviewers that combine multiple skills and run end-to-end reviews |
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
**Profiles** bundle all three tiers by language or domain so you install everything you need in one command.
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
skill-name/
|
|
47
|
-
├── SKILL.md # Required — YAML frontmatter + markdown instructions
|
|
48
|
-
├── scripts/ # Optional — deterministic code for repeated tasks
|
|
49
|
-
├── references/ # Optional — docs loaded into context as needed
|
|
50
|
-
├── assets/ # Optional — templates, fonts, images
|
|
51
|
-
└── evals/ # Optional — test cases for skill evaluation
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### SKILL.md Structure
|
|
55
|
-
|
|
56
|
-
```markdown
|
|
57
|
-
---
|
|
58
|
-
name: skill-name
|
|
59
|
-
description: When to trigger this skill and what it does
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
# Skill Title
|
|
63
|
-
|
|
64
|
-
Instructions for the AI agent...
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Installation
|
|
68
|
-
|
|
69
|
-
### via skills CLI (recommended)
|
|
29
|
+
## Quick Start
|
|
70
30
|
|
|
71
31
|
```bash
|
|
72
|
-
#
|
|
73
|
-
npx skills add
|
|
32
|
+
# Pick your language or domain
|
|
33
|
+
npx @booklib/skills add --profile=python # Python skills + commands + python-reviewer agent
|
|
34
|
+
npx @booklib/skills add --profile=ts # TypeScript skills + commands + ts-reviewer agent
|
|
35
|
+
npx @booklib/skills add --profile=rust # Rust skills + commands + rust-reviewer agent
|
|
36
|
+
npx @booklib/skills add --profile=jvm # Java/Kotlin skills + commands + jvm-reviewer agent
|
|
37
|
+
npx @booklib/skills add --profile=architecture # DDD/microservices/system design
|
|
38
|
+
npx @booklib/skills add --profile=data # Data pipelines + DDIA
|
|
39
|
+
npx @booklib/skills add --profile=ui # Refactoring UI + animations + data viz
|
|
40
|
+
npx @booklib/skills add --profile=lean # Lean Startup practices
|
|
41
|
+
|
|
42
|
+
# Or install everything
|
|
43
|
+
npx @booklib/skills add --all
|
|
44
|
+
```
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
npx skills add booklib-ai/skills --skill effective-kotlin
|
|
46
|
+
Skills are installed to `.claude/skills/` in your project, or `~/.claude/skills/` with `--global`.
|
|
77
47
|
|
|
78
|
-
|
|
79
|
-
npx skills add booklib-ai/skills --list
|
|
80
|
-
```
|
|
48
|
+
## Agents
|
|
81
49
|
|
|
82
|
-
|
|
50
|
+
Eight autonomous reviewers that run end-to-end reviews combining the most relevant skills for each domain:
|
|
83
51
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
52
|
+
| Agent | Skills used | Use when |
|
|
53
|
+
|-------|-------------|----------|
|
|
54
|
+
| `booklib-reviewer` | skill-router (auto-selects) | Unsure which skill applies — routes automatically |
|
|
55
|
+
| `python-reviewer` | effective-python · asyncio · web-scraping | Reviewing any Python code |
|
|
56
|
+
| `jvm-reviewer` | effective-java · effective-kotlin · kotlin-in-action · spring-boot | Java or Kotlin code reviews |
|
|
57
|
+
| `rust-reviewer` | programming-with-rust · rust-in-action | Rust ownership, safety, and systems code |
|
|
58
|
+
| `ts-reviewer` | effective-typescript · clean-code-reviewer | TypeScript and TSX reviews |
|
|
59
|
+
| `architecture-reviewer` | domain-driven-design · microservices-patterns · system-design · data-intensive | System design, domain models, service boundaries |
|
|
60
|
+
| `data-reviewer` | data-intensive-patterns · data-pipelines | Schemas, ETL pipelines, stream processing |
|
|
61
|
+
| `ui-reviewer` | refactoring-ui · storytelling-with-data · animation-at-work | UI components, dashboards, data visualizations |
|
|
87
62
|
|
|
88
|
-
|
|
89
|
-
npx @booklib/skills add effective-kotlin
|
|
63
|
+
Invoke an agent in Claude Code with `@booklib-reviewer` or the specific agent name.
|
|
90
64
|
|
|
91
|
-
|
|
92
|
-
npx @booklib/skills list
|
|
93
|
-
```
|
|
65
|
+
## Profiles
|
|
94
66
|
|
|
95
|
-
|
|
67
|
+
| Profile | Skills + agents included |
|
|
68
|
+
|---------|--------------------------|
|
|
69
|
+
| `python` | effective-python · using-asyncio-python · web-scraping-python · python-reviewer |
|
|
70
|
+
| `ts` | effective-typescript · clean-code-reviewer · ts-reviewer |
|
|
71
|
+
| `jvm` | effective-java · effective-kotlin · kotlin-in-action · spring-boot-in-action · jvm-reviewer |
|
|
72
|
+
| `rust` | programming-with-rust · rust-in-action · rust-reviewer |
|
|
73
|
+
| `architecture` | domain-driven-design · microservices-patterns · system-design-interview · data-intensive-patterns · architecture-reviewer |
|
|
74
|
+
| `data` | data-intensive-patterns · data-pipelines · data-reviewer |
|
|
75
|
+
| `ui` | refactoring-ui · storytelling-with-data · animation-at-work · ui-reviewer |
|
|
76
|
+
| `lean` | lean-startup · design-patterns · clean-code-reviewer |
|
|
77
|
+
| `core` | clean-code-reviewer · design-patterns · skill-router · booklib-reviewer |
|
|
96
78
|
|
|
97
|
-
|
|
79
|
+
## Skills
|
|
98
80
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
| Skill | Book |
|
|
82
|
+
|-------|------|
|
|
83
|
+
| [animation-at-work](./skills/animation-at-work/) | *Animation at Work* — Rachel Nabors |
|
|
84
|
+
| [clean-code-reviewer](./skills/clean-code-reviewer/) | *Clean Code* — Robert C. Martin |
|
|
85
|
+
| [data-intensive-patterns](./skills/data-intensive-patterns/) | *Designing Data-Intensive Applications* — Martin Kleppmann |
|
|
86
|
+
| [data-pipelines](./skills/data-pipelines/) | *Data Pipelines Pocket Reference* — James Densmore |
|
|
87
|
+
| [design-patterns](./skills/design-patterns/) | *Head First Design Patterns* |
|
|
88
|
+
| [domain-driven-design](./skills/domain-driven-design/) | *Domain-Driven Design* — Eric Evans |
|
|
89
|
+
| [effective-java](./skills/effective-java/) | *Effective Java* (3rd ed) — Joshua Bloch |
|
|
90
|
+
| [effective-kotlin](./skills/effective-kotlin/) | *Effective Kotlin* (2nd ed) — Marcin Moskała |
|
|
91
|
+
| [effective-python](./skills/effective-python/) | *Effective Python* (2nd ed) — Brett Slatkin |
|
|
92
|
+
| [effective-typescript](./skills/effective-typescript/) | *Effective TypeScript* — Dan Vanderkam |
|
|
93
|
+
| [kotlin-in-action](./skills/kotlin-in-action/) | *Kotlin in Action* (2nd ed) |
|
|
94
|
+
| [lean-startup](./skills/lean-startup/) | *The Lean Startup* — Eric Ries |
|
|
95
|
+
| [microservices-patterns](./skills/microservices-patterns/) | *Microservices Patterns* — Chris Richardson |
|
|
96
|
+
| [programming-with-rust](./skills/programming-with-rust/) | *Programming with Rust* — Donis Marshall |
|
|
97
|
+
| [refactoring-ui](./skills/refactoring-ui/) | *Refactoring UI* — Adam Wathan & Steve Schoger |
|
|
98
|
+
| [rust-in-action](./skills/rust-in-action/) | *Rust in Action* — Tim McNamara |
|
|
99
|
+
| [skill-router](./skills/skill-router/) | Meta-skill — routes to the right skill automatically |
|
|
100
|
+
| [spring-boot-in-action](./skills/spring-boot-in-action/) | *Spring Boot in Action* — Craig Walls |
|
|
101
|
+
| [storytelling-with-data](./skills/storytelling-with-data/) | *Storytelling with Data* — Cole Nussbaumer Knaflic |
|
|
102
|
+
| [system-design-interview](./skills/system-design-interview/) | *System Design Interview* — Alex Xu |
|
|
103
|
+
| [using-asyncio-python](./skills/using-asyncio-python/) | *Using Asyncio in Python* — Caleb Hattingh |
|
|
104
|
+
| [web-scraping-python](./skills/web-scraping-python/) | *Web Scraping with Python* — Ryan Mitchell |
|
|
103
105
|
|
|
104
106
|
## Automatic Skill Routing
|
|
105
107
|
|
|
106
|
-
You don't need to know which skill to apply — the **[skill-router](./skills/skill-router/)** meta-skill does it for you.
|
|
107
|
-
|
|
108
|
-
When an AI agent receives a task, it can invoke `skill-router` first to identify the 1–2 most relevant skills based on the file, language, domain, and work type. The router then returns a ranked recommendation with rationale, so the right expertise is applied automatically.
|
|
108
|
+
You don't need to know which skill to apply — the **[skill-router](./skills/skill-router/)** meta-skill does it for you. When invoked, it reads the file, language, domain, and task type, then returns a ranked recommendation with rationale.
|
|
109
109
|
|
|
110
110
|
```
|
|
111
111
|
User: "Review my order processing service"
|
|
@@ -113,39 +113,11 @@ User: "Review my order processing service"
|
|
|
113
113
|
→ skill-router selects:
|
|
114
114
|
Primary: domain-driven-design — domain model design (Aggregates, Value Objects)
|
|
115
115
|
Secondary: microservices-patterns — service boundaries and inter-service communication
|
|
116
|
-
Skip: clean-code-reviewer — premature at design stage; apply later on implementation code
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
**See it in action:** The [`benchmark/`](./benchmark/) folder contains a head-to-head comparison — same buggy Node.js file reviewed by the native PR toolkit vs. `skill-router` routing to `clean-code-reviewer` + `design-patterns`. The skill-router pipeline finds ~47% more unique issues and adds a full refactor roadmap with pattern sequence.
|
|
118
|
+
The `booklib-reviewer` agent wraps this logic end-to-end — invoke it and it handles selection and review automatically.
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Skill | Description |
|
|
126
|
-
|-------|-------------|
|
|
127
|
-
| 🎬 [animation-at-work](./skills/animation-at-work/) | Apply web animation principles from Rachel Nabors' *Animation at Work* — human perception of motion, 12 principles of animation, and performance |
|
|
128
|
-
| 🧹 [clean-code-reviewer](./skills/clean-code-reviewer/) | Reviews code against Robert C. Martin's *Clean Code* principles with heuristic codes (C1–C5, G1–G36, N1–N7, T1–T9) |
|
|
129
|
-
| 🗄️ [data-intensive-patterns](./skills/data-intensive-patterns/) | Patterns for reliable, scalable, and maintainable systems from Martin Kleppmann's *Designing Data-Intensive Applications* — storage engines, replication, partitioning, and transactions |
|
|
130
|
-
| 🔀 [data-pipelines](./skills/data-pipelines/) | Data pipeline practices from James Densmore's *Data Pipelines Pocket Reference* — ingestion, streaming, transformation, and orchestration |
|
|
131
|
-
| 🏗️ [design-patterns](./skills/design-patterns/) | Apply and review GoF design patterns from *Head First Design Patterns* — creational, structural, and behavioral patterns |
|
|
132
|
-
| 🧩 [domain-driven-design](./skills/domain-driven-design/) | Design and review software using patterns from Eric Evans' *Domain-Driven Design* — tactical and strategic patterns, and Ubiquitous Language |
|
|
133
|
-
| ☕ [effective-java](./skills/effective-java/) | Java best practices from Joshua Bloch's *Effective Java* (3rd Edition) — object creation, generics, enums, lambdas, and concurrency |
|
|
134
|
-
| 🛡️ [effective-kotlin](./skills/effective-kotlin/) | Best practices from Marcin Moskała's *Effective Kotlin* (2nd Ed) — safety, readability, reusability, and abstraction |
|
|
135
|
-
| 🐍 [effective-python](./skills/effective-python/) | Python best practices from Brett Slatkin's *Effective Python* (2nd Edition) — Pythonic thinking, functions, classes, concurrency, and testing |
|
|
136
|
-
| 🔷 [effective-typescript](./skills/effective-typescript/) | TypeScript best practices from Dan Vanderkam's *Effective TypeScript* — type system, type design, avoiding any, type declarations, and migration |
|
|
137
|
-
| 🦀 [programming-with-rust](./skills/programming-with-rust/) | Rust practices from Donis Marshall's *Programming with Rust* — ownership, borrowing, lifetimes, error handling, traits, and fearless concurrency |
|
|
138
|
-
| ⚙️ [rust-in-action](./skills/rust-in-action/) | Systems programming from Tim McNamara's *Rust in Action* — smart pointers, endianness, memory, file formats, TCP networking, concurrency, and OS fundamentals |
|
|
139
|
-
| 🌱 [spring-boot-in-action](./skills/spring-boot-in-action/) | Spring Boot best practices from Craig Walls' *Spring Boot in Action* — auto-configuration, starters, externalized config, profiles, testing with MockMvc, Actuator, and deployment |
|
|
140
|
-
| ⚡ [kotlin-in-action](./skills/kotlin-in-action/) | Practices from *Kotlin in Action* (2nd Ed) — functions, classes, lambdas, nullability, and coroutines |
|
|
141
|
-
| 🚀 [lean-startup](./skills/lean-startup/) | Practices from Eric Ries' *The Lean Startup* — MVP testing, validated learning, Build-Measure-Learn loop, and pivots |
|
|
142
|
-
| 🔧 [microservices-patterns](./skills/microservices-patterns/) | Expert guidance on microservices patterns from Chris Richardson's *Microservices Patterns* — decomposition, sagas, API gateways, event sourcing, CQRS, and service mesh |
|
|
143
|
-
| 🎨 [refactoring-ui](./skills/refactoring-ui/) | UI design principles from *Refactoring UI* by Adam Wathan & Steve Schoger — visual hierarchy, layout, typography, and color |
|
|
144
|
-
| 🗺️ [skill-router](./skills/skill-router/) | **Meta-skill.** Automatically selects the 1–2 most relevant skills for a given file, PR, or task — routes by language, domain, and work type with conflict resolution. Use this when the right skill isn't obvious, or let the AI invoke it automatically before applying any skill |
|
|
145
|
-
| 📊 [storytelling-with-data](./skills/storytelling-with-data/) | Data visualization and storytelling from Cole Nussbaumer Knaflic's *Storytelling with Data* — effective visuals, decluttering, and narrative structure |
|
|
146
|
-
| 🏛️ [system-design-interview](./skills/system-design-interview/) | System design principles from Alex Xu's *System Design Interview* — scaling, estimation, and real-world system designs |
|
|
147
|
-
| 🔄 [using-asyncio-python](./skills/using-asyncio-python/) | Asyncio practices from Caleb Hattingh's *Using Asyncio in Python* — coroutines, event loop, tasks, and signal handling |
|
|
148
|
-
| 🕷️ [web-scraping-python](./skills/web-scraping-python/) | Web scraping practices from Ryan Mitchell's *Web Scraping with Python* — BeautifulSoup, Scrapy, and data storage |
|
|
120
|
+
**Benchmark:** The [`benchmark/`](./benchmark/) folder contains a head-to-head comparison of a native PR review vs. `skill-router` routing to `clean-code-reviewer` + `design-patterns`. The skill-router pipeline finds ~47% more unique issues and produces a full refactor roadmap.
|
|
149
121
|
|
|
150
122
|
## Quality
|
|
151
123
|
|
|
@@ -157,7 +129,7 @@ Skills are evaluated against 6–15 test cases each, run both **with** and **wit
|
|
|
157
129
|
| Skill | Pass Rate | Baseline | Delta | Evals | Last Run |
|
|
158
130
|
|-------|-----------|----------|-------|-------|----------|
|
|
159
131
|
| animation-at-work | — | — | — | — | — |
|
|
160
|
-
| clean-code-reviewer |
|
|
132
|
+
| clean-code-reviewer | 74% ⚠ | 55% | +19pp | 15 | 2026-03-28 |
|
|
161
133
|
| data-intensive-patterns | — | — | — | — | — |
|
|
162
134
|
| data-pipelines | — | — | — | — | — |
|
|
163
135
|
| design-patterns | — | — | — | — | — |
|
|
@@ -182,9 +154,31 @@ Skills are evaluated against 6–15 test cases each, run both **with** and **wit
|
|
|
182
154
|
|
|
183
155
|
Results are stored in each skill's `evals/results.json` and updated by running `npx @booklib/skills eval <name>`.
|
|
184
156
|
|
|
185
|
-
##
|
|
157
|
+
## Structure
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
booklib-ai/skills/
|
|
161
|
+
├── skills/ 22 book-grounded skills (SKILL.md + examples + evals)
|
|
162
|
+
├── commands/ 22 slash commands, one per skill
|
|
163
|
+
├── agents/ 8 autonomous reviewer agents
|
|
164
|
+
├── hooks/ Claude Code hooks (skill suggestion on UserPromptSubmit)
|
|
165
|
+
└── bin/skills.js CLI
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Each skill folder follows the [Agent Skills standard](https://agentskills.io):
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
skill-name/
|
|
172
|
+
├── SKILL.md # Required — YAML frontmatter + instructions
|
|
173
|
+
├── examples/ # before.md and after.md
|
|
174
|
+
├── references/ # Deep reference material loaded on demand
|
|
175
|
+
├── scripts/ # Deterministic helper scripts
|
|
176
|
+
└── evals/ # Test cases for skill evaluation
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
186
180
|
|
|
187
|
-
If you've read a book that belongs here, you can add it
|
|
181
|
+
If you've read a book that belongs here, you can add it:
|
|
188
182
|
|
|
189
183
|
```bash
|
|
190
184
|
# 1. Copy an existing skill as a template
|
|
@@ -196,7 +190,7 @@ cp -r skills/clean-code-reviewer skills/your-book-name
|
|
|
196
190
|
npx @booklib/skills check your-book-name
|
|
197
191
|
```
|
|
198
192
|
|
|
199
|
-
The `check` command runs all evals and reports what passes and fails
|
|
193
|
+
The `check` command runs all evals and reports what passes and fails. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
|
|
200
194
|
|
|
201
195
|
**Books with open issues** (tagged `good first issue`): [The Pragmatic Programmer](https://github.com/booklib-ai/skills/issues/2) · [Clean Architecture](https://github.com/booklib-ai/skills/issues/3) · [A Philosophy of Software Design](https://github.com/booklib-ai/skills/issues/4) · [Accelerate](https://github.com/booklib-ai/skills/issues/8) · [and more →](https://github.com/booklib-ai/skills/issues?q=is%3Aopen+label%3A%22good+first+issue%22)
|
|
202
196
|
|
package/bin/skills.js
CHANGED
|
@@ -167,6 +167,30 @@ function getAvailableAgents() {
|
|
|
167
167
|
.sort();
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
function parseAgentFrontmatter(agentName) {
|
|
171
|
+
const agentMdPath = path.join(agentsRoot, `${agentName}.md`);
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(agentMdPath, 'utf8');
|
|
174
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
175
|
+
if (!fmMatch) return { name: agentName, description: '', model: '' };
|
|
176
|
+
const fm = fmMatch[1];
|
|
177
|
+
|
|
178
|
+
const blockMatch = fm.match(/^description:\s*>\s*\n((?:[ \t]+.+\n?)+)/m);
|
|
179
|
+
const quotedMatch = fm.match(/^description:\s*["'](.+?)["']\s*$/m);
|
|
180
|
+
const plainMatch = fm.match(/^description:\s*(?!>)(.+)$/m);
|
|
181
|
+
const modelMatch = fm.match(/^model:\s*(\S+)/m);
|
|
182
|
+
|
|
183
|
+
let description = '';
|
|
184
|
+
if (blockMatch) description = blockMatch[1].split('\n').map(l => l.trim()).filter(Boolean).join(' ');
|
|
185
|
+
else if (quotedMatch) description = quotedMatch[1];
|
|
186
|
+
else if (plainMatch) description = plainMatch[1].trim();
|
|
187
|
+
|
|
188
|
+
return { name: agentName, description, model: modelMatch?.[1] ?? '' };
|
|
189
|
+
} catch {
|
|
190
|
+
return { name: agentName, description: '', model: '' };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
170
194
|
function copyAgent(agentName) {
|
|
171
195
|
const src = path.join(agentsRoot, `${agentName}.md`);
|
|
172
196
|
if (!fs.existsSync(src)) return;
|
|
@@ -176,6 +200,36 @@ function copyAgent(agentName) {
|
|
|
176
200
|
console.log(c.green('✓') + ` @${agentName} agent → ${c.dim(dest)}`);
|
|
177
201
|
}
|
|
178
202
|
|
|
203
|
+
// ─── Cursor support ───────────────────────────────────────────────────────────
|
|
204
|
+
function getCursorRulesDir() {
|
|
205
|
+
return isGlobal
|
|
206
|
+
? path.join(os.homedir(), '.cursor', 'rules')
|
|
207
|
+
: path.join(process.cwd(), '.cursor', 'rules');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function copyHooks() {
|
|
211
|
+
const hooksDir = path.join(__dirname, '..', 'hooks');
|
|
212
|
+
if (!fs.existsSync(hooksDir)) return;
|
|
213
|
+
// Copy suggest.js to the .claude/ root as booklib-suggest.js
|
|
214
|
+
const suggestSrc = path.join(hooksDir, 'suggest.js');
|
|
215
|
+
if (fs.existsSync(suggestSrc)) {
|
|
216
|
+
const claudeDir = isGlobal ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
|
|
217
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
218
|
+
const dest = path.join(claudeDir, 'booklib-suggest.js');
|
|
219
|
+
fs.copyFileSync(suggestSrc, dest);
|
|
220
|
+
console.log(c.green('✓') + ` booklib-suggest.js hook → ${c.dim(dest)}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function copySkillToCursor(skillName) {
|
|
225
|
+
const src = path.join(skillsRoot, skillName, 'SKILL.md');
|
|
226
|
+
if (!fs.existsSync(src)) return;
|
|
227
|
+
const dest = path.join(getCursorRulesDir(), `${skillName}.md`);
|
|
228
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
229
|
+
fs.copyFileSync(src, dest);
|
|
230
|
+
console.log(c.green('✓') + ` ${c.bold(skillName)} → ${c.dim(dest)}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
179
233
|
// ─── CHECK command ────────────────────────────────────────────────────────────
|
|
180
234
|
function checkSkill(skillName) {
|
|
181
235
|
const skillDir = path.join(skillsRoot, skillName);
|
|
@@ -776,23 +830,35 @@ async function main() {
|
|
|
776
830
|
const noAgents = args.includes('--no-agents');
|
|
777
831
|
const agentArg = args.find(a => a.startsWith('--agent='))?.split('=')[1];
|
|
778
832
|
const profileArg = args.find(a => a.startsWith('--profile='))?.split('=')[1];
|
|
833
|
+
const targetArg = (args.find(a => a.startsWith('--target='))?.split('=')[1] ?? 'claude').toLowerCase();
|
|
834
|
+
const toClaude = targetArg === 'claude' || targetArg === 'all';
|
|
835
|
+
const toCursor = targetArg === 'cursor' || targetArg === 'all';
|
|
779
836
|
const skillName = args.find(a => !a.startsWith('--') && a !== 'add');
|
|
780
837
|
|
|
838
|
+
const installSkills = (list) => {
|
|
839
|
+
if (toClaude) list.forEach(s => copySkill(s, targetDir));
|
|
840
|
+
if (toCursor) list.forEach(s => copySkillToCursor(s));
|
|
841
|
+
if (toClaude && !noCommands) list.forEach(s => copyCommand(s));
|
|
842
|
+
};
|
|
843
|
+
const installAgents = (list) => {
|
|
844
|
+
if (toClaude && !noAgents) list.forEach(a => copyAgent(a));
|
|
845
|
+
// agents not applicable to Cursor
|
|
846
|
+
};
|
|
847
|
+
|
|
781
848
|
if (profileArg) {
|
|
782
849
|
const profile = PROFILES[profileArg];
|
|
783
850
|
if (!profile) {
|
|
784
851
|
console.error(c.red(`✗ Profile "${profileArg}" not found.`) + ' Run ' + c.cyan('skills profiles') + ' to see available profiles.');
|
|
785
852
|
process.exit(1);
|
|
786
853
|
}
|
|
787
|
-
profile.skills
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const agentStr = profile.agents.length
|
|
854
|
+
installSkills(profile.skills);
|
|
855
|
+
installAgents(profile.agents);
|
|
856
|
+
const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
|
|
857
|
+
const agentStr = (!noAgents && toClaude && profile.agents.length)
|
|
791
858
|
? `, ${profile.agents.length} agent${profile.agents.length > 1 ? 's' : ''}`
|
|
792
859
|
: '';
|
|
793
|
-
console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr}`));
|
|
860
|
+
console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr} → ${targets}`));
|
|
794
861
|
} else if (agentArg) {
|
|
795
|
-
// explicit: skills add --agent=booklib-reviewer
|
|
796
862
|
const agents = getAvailableAgents();
|
|
797
863
|
if (!agents.includes(agentArg)) {
|
|
798
864
|
console.error(c.red(`✗ Agent "${agentArg}" not found.`) + ' Available: ' + c.dim(agents.join(', ')));
|
|
@@ -802,14 +868,14 @@ async function main() {
|
|
|
802
868
|
console.log(c.dim(`\nInstalled to ${agentsTargetDir}`));
|
|
803
869
|
} else if (addAll) {
|
|
804
870
|
const skills = getAvailableSkills();
|
|
805
|
-
skills
|
|
806
|
-
|
|
807
|
-
if (
|
|
808
|
-
const agentCount = noAgents
|
|
809
|
-
|
|
871
|
+
installSkills(skills);
|
|
872
|
+
installAgents(getAvailableAgents());
|
|
873
|
+
if (toClaude) copyHooks();
|
|
874
|
+
const agentCount = (!noAgents && toClaude) ? getAvailableAgents().length : 0;
|
|
875
|
+
const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
|
|
876
|
+
console.log(c.dim(`\nInstalled ${skills.length} skills, ${agentCount} agents → ${targets}`));
|
|
810
877
|
} else if (skillName) {
|
|
811
|
-
|
|
812
|
-
if (!noCommands) copyCommand(skillName);
|
|
878
|
+
installSkills([skillName]);
|
|
813
879
|
console.log(c.dim(`\nInstalled to ${targetDir}`));
|
|
814
880
|
} else {
|
|
815
881
|
console.error(c.red('Usage: skills add <skill-name> | skills add --all | skills add --agent=<name>'));
|
|
@@ -916,6 +982,43 @@ async function main() {
|
|
|
916
982
|
break;
|
|
917
983
|
}
|
|
918
984
|
|
|
985
|
+
case 'agents': {
|
|
986
|
+
const infoArg = args.find(a => a.startsWith('--info='))?.split('=')[1]
|
|
987
|
+
|| args.find(a => !a.startsWith('--') && a !== 'agents');
|
|
988
|
+
const available = getAvailableAgents();
|
|
989
|
+
|
|
990
|
+
if (infoArg) {
|
|
991
|
+
if (!available.includes(infoArg)) {
|
|
992
|
+
console.error(c.red(`✗ Agent "${infoArg}" not found.`) + ' Run ' + c.cyan('skills agents') + ' to see available agents.');
|
|
993
|
+
process.exit(1);
|
|
994
|
+
}
|
|
995
|
+
const { description, model } = parseAgentFrontmatter(infoArg);
|
|
996
|
+
console.log('');
|
|
997
|
+
console.log(c.bold(` ${infoArg}`) + c.dim(model ? ` [${model}]` : ''));
|
|
998
|
+
console.log(' ' + c.line(55));
|
|
999
|
+
console.log(' ' + description);
|
|
1000
|
+
console.log('');
|
|
1001
|
+
console.log(c.dim(` Install: skills add --agent=${infoArg}`));
|
|
1002
|
+
console.log('');
|
|
1003
|
+
} else {
|
|
1004
|
+
const nameW = Math.max(...available.map(n => n.length)) + 2;
|
|
1005
|
+
console.log('');
|
|
1006
|
+
console.log(c.bold(` @booklib/skills — agents`) + c.dim(` (${available.length})`));
|
|
1007
|
+
console.log(' ' + c.line(60));
|
|
1008
|
+
for (const name of available) {
|
|
1009
|
+
const { description, model } = parseAgentFrontmatter(name);
|
|
1010
|
+
const modelTag = model ? c.dim(` [${model}]`) : '';
|
|
1011
|
+
console.log(` ${c.cyan(name.padEnd(nameW))}${modelTag}`);
|
|
1012
|
+
if (description) console.log(` ${' '.repeat(nameW)}${firstSentence(description, 72)}`);
|
|
1013
|
+
}
|
|
1014
|
+
console.log('');
|
|
1015
|
+
console.log(c.dim(` skills add --agent=<name> install one agent`));
|
|
1016
|
+
console.log(c.dim(` skills agents --info=<name> full description`));
|
|
1017
|
+
console.log('');
|
|
1018
|
+
}
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
919
1022
|
case 'profiles': {
|
|
920
1023
|
const nameW = Math.max(...Object.keys(PROFILES).map(k => k.length)) + 2;
|
|
921
1024
|
console.log('');
|
|
@@ -939,6 +1042,8 @@ ${c.bold(' @booklib/skills')} — book knowledge distilled into AI agent skills
|
|
|
939
1042
|
|
|
940
1043
|
${c.bold(' Usage:')}
|
|
941
1044
|
${c.cyan('skills list')} list all available skills
|
|
1045
|
+
${c.cyan('skills agents')} list all available agents
|
|
1046
|
+
${c.cyan('skills agents')} ${c.dim('--info=<name>')} full description of an agent
|
|
942
1047
|
${c.cyan('skills profiles')} list available profiles
|
|
943
1048
|
${c.cyan('skills info')} ${c.dim('<name>')} full description of a skill
|
|
944
1049
|
${c.cyan('skills demo')} ${c.dim('<name>')} before/after example
|
|
@@ -947,6 +1052,8 @@ ${c.bold(' Usage:')}
|
|
|
947
1052
|
${c.cyan('skills add --all')} install everything (skills + commands + agents)
|
|
948
1053
|
${c.cyan('skills add')} ${c.dim('<name> --global')} install globally (~/.claude/)
|
|
949
1054
|
${c.cyan('skills add')} ${c.dim('--agent=<name>')} install a single agent to .claude/agents/
|
|
1055
|
+
${c.cyan('skills add')} ${c.dim('--target=cursor')} install to .cursor/rules/ (Cursor IDE)
|
|
1056
|
+
${c.cyan('skills add')} ${c.dim('--target=all')} install to both .claude/ and .cursor/
|
|
950
1057
|
${c.cyan('skills add')} ${c.dim('--no-commands')} skip /command installation
|
|
951
1058
|
${c.cyan('skills add')} ${c.dim('--no-agents')} skip agent installation
|
|
952
1059
|
${c.cyan('skills check')} ${c.dim('<name>')} quality check (Bronze/Silver/Gold/Platinum)
|
package/hooks/hooks.json
ADDED
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();
|