@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/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
  ![Demo](demo.gif)
20
16
 
21
- Each skill is a self-contained folder with a `SKILL.md` file containing instructions and metadata that AI agents use to perform specialized tasks.
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
- ## Skill Format
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
- Each skill follows the [Agent Skills standard](https://agentskills.io):
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
- # Install all skills globally
73
- npx skills add booklib-ai/skills --all -g
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
- # Install a specific skill
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
- # List available skills without installing
79
- npx skills add booklib-ai/skills --list
80
- ```
48
+ ## Agents
81
49
 
82
- ### via npm
50
+ Eight autonomous reviewers that run end-to-end reviews combining the most relevant skills for each domain:
83
51
 
84
- ```bash
85
- # Install all skills globally
86
- npx @booklib/skills add --all --global
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
- # Install a single skill
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
- # List available skills
92
- npx @booklib/skills list
93
- ```
65
+ ## Profiles
94
66
 
95
- Skills are installed to `.claude/skills/` in your project (or `~/.claude/skills/` with `--global`).
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
- ### Manual
79
+ ## Skills
98
80
 
99
- ```bash
100
- git clone https://github.com/booklib-ai/skills.git
101
- cp -r booklib-ai-skills/skills/effective-kotlin /path/to/project/.claude/skills/
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
- This means skills compose: `skill-router` acts as an orchestrator that picks the right specialist skills for the context, without requiring the user to know the library upfront.
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
- ## Skills
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
- ## Contributing a skill
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. The bar is lower than you think:
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 — you get a quality signal before anyone else sees the PR. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
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
@@ -12,6 +12,7 @@ const command = args[0];
12
12
  const skillsRoot = path.join(__dirname, '..', 'skills');
13
13
  const commandsRoot = path.join(__dirname, '..', 'commands');
14
14
  const agentsRoot = path.join(__dirname, '..', 'agents');
15
+ const rulesRoot = path.join(__dirname, '..', 'rules');
15
16
 
16
17
  // ─── Installation profiles ────────────────────────────────────────────────────
17
18
  const PROFILES = {
@@ -149,6 +150,9 @@ const commandsTargetDir = isGlobal
149
150
  const agentsTargetDir = isGlobal
150
151
  ? path.join(os.homedir(), '.claude', 'agents')
151
152
  : path.join(process.cwd(), '.claude', 'agents');
153
+ const rulesTargetDir = isGlobal
154
+ ? path.join(os.homedir(), '.claude', 'rules')
155
+ : path.join(process.cwd(), '.claude', 'rules');
152
156
 
153
157
  function copyCommand(skillName) {
154
158
  const src = path.join(commandsRoot, `${skillName}.md`);
@@ -167,6 +171,30 @@ function getAvailableAgents() {
167
171
  .sort();
168
172
  }
169
173
 
174
+ function parseAgentFrontmatter(agentName) {
175
+ const agentMdPath = path.join(agentsRoot, `${agentName}.md`);
176
+ try {
177
+ const content = fs.readFileSync(agentMdPath, 'utf8');
178
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
179
+ if (!fmMatch) return { name: agentName, description: '', model: '' };
180
+ const fm = fmMatch[1];
181
+
182
+ const blockMatch = fm.match(/^description:\s*>\s*\n((?:[ \t]+.+\n?)+)/m);
183
+ const quotedMatch = fm.match(/^description:\s*["'](.+?)["']\s*$/m);
184
+ const plainMatch = fm.match(/^description:\s*(?!>)(.+)$/m);
185
+ const modelMatch = fm.match(/^model:\s*(\S+)/m);
186
+
187
+ let description = '';
188
+ if (blockMatch) description = blockMatch[1].split('\n').map(l => l.trim()).filter(Boolean).join(' ');
189
+ else if (quotedMatch) description = quotedMatch[1];
190
+ else if (plainMatch) description = plainMatch[1].trim();
191
+
192
+ return { name: agentName, description, model: modelMatch?.[1] ?? '' };
193
+ } catch {
194
+ return { name: agentName, description: '', model: '' };
195
+ }
196
+ }
197
+
170
198
  function copyAgent(agentName) {
171
199
  const src = path.join(agentsRoot, `${agentName}.md`);
172
200
  if (!fs.existsSync(src)) return;
@@ -176,6 +204,72 @@ function copyAgent(agentName) {
176
204
  console.log(c.green('✓') + ` @${agentName} agent → ${c.dim(dest)}`);
177
205
  }
178
206
 
207
+ // ─── Cursor support ───────────────────────────────────────────────────────────
208
+ function getCursorRulesDir() {
209
+ return isGlobal
210
+ ? path.join(os.homedir(), '.cursor', 'rules')
211
+ : path.join(process.cwd(), '.cursor', 'rules');
212
+ }
213
+
214
+ function copyHooks() {
215
+ const hooksDir = path.join(__dirname, '..', 'hooks');
216
+ if (!fs.existsSync(hooksDir)) return;
217
+ // Copy suggest.js to the .claude/ root as booklib-suggest.js
218
+ const suggestSrc = path.join(hooksDir, 'suggest.js');
219
+ if (fs.existsSync(suggestSrc)) {
220
+ const claudeDir = isGlobal ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
221
+ fs.mkdirSync(claudeDir, { recursive: true });
222
+ const dest = path.join(claudeDir, 'booklib-suggest.js');
223
+ fs.copyFileSync(suggestSrc, dest);
224
+ console.log(c.green('✓') + ` booklib-suggest.js hook → ${c.dim(dest)}`);
225
+ }
226
+ }
227
+
228
+ function getAvailableRules() {
229
+ // Returns [{language, name, file}] for each rule file found under rules/
230
+ if (!fs.existsSync(rulesRoot)) return [];
231
+ const result = [];
232
+ for (const lang of fs.readdirSync(rulesRoot).sort()) {
233
+ const langDir = path.join(rulesRoot, lang);
234
+ if (!fs.statSync(langDir).isDirectory()) continue;
235
+ for (const file of fs.readdirSync(langDir).filter(f => f.endsWith('.md')).sort()) {
236
+ result.push({ language: lang, name: file.replace(/\.md$/, ''), file: path.join(langDir, file) });
237
+ }
238
+ }
239
+ return result;
240
+ }
241
+
242
+ function copyRules(language) {
243
+ // Copies all rule files for a given language (or 'common') to rulesTargetDir
244
+ const langDir = path.join(rulesRoot, language);
245
+ if (!fs.existsSync(langDir)) {
246
+ console.error(c.red(`✗ No rules for language "${language}".`) + ' Run ' + c.cyan('skills rules') + ' to see available rules.');
247
+ process.exit(1);
248
+ }
249
+ const destDir = path.join(rulesTargetDir, language);
250
+ fs.mkdirSync(destDir, { recursive: true });
251
+ for (const file of fs.readdirSync(langDir).filter(f => f.endsWith('.md'))) {
252
+ const dest = path.join(destDir, file);
253
+ fs.copyFileSync(path.join(langDir, file), dest);
254
+ console.log(c.green('✓') + ` ${c.bold(language + '/' + file.replace(/\.md$/, ''))} rule → ${c.dim(dest)}`);
255
+ }
256
+ }
257
+
258
+ function copyAllRules() {
259
+ const rules = getAvailableRules();
260
+ const languages = [...new Set(rules.map(r => r.language))];
261
+ for (const lang of languages) copyRules(lang);
262
+ }
263
+
264
+ function copySkillToCursor(skillName) {
265
+ const src = path.join(skillsRoot, skillName, 'SKILL.md');
266
+ if (!fs.existsSync(src)) return;
267
+ const dest = path.join(getCursorRulesDir(), `${skillName}.md`);
268
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
269
+ fs.copyFileSync(src, dest);
270
+ console.log(c.green('✓') + ` ${c.bold(skillName)} → ${c.dim(dest)}`);
271
+ }
272
+
179
273
  // ─── CHECK command ────────────────────────────────────────────────────────────
180
274
  function checkSkill(skillName) {
181
275
  const skillDir = path.join(skillsRoot, skillName);
@@ -771,12 +865,28 @@ async function main() {
771
865
  }
772
866
 
773
867
  case 'add': {
774
- const addAll = args.includes('--all');
775
- const noCommands = args.includes('--no-commands');
776
- const noAgents = args.includes('--no-agents');
777
- const agentArg = args.find(a => a.startsWith('--agent='))?.split('=')[1];
778
- const profileArg = args.find(a => a.startsWith('--profile='))?.split('=')[1];
779
- const skillName = args.find(a => !a.startsWith('--') && a !== 'add');
868
+ const addAll = args.includes('--all');
869
+ const addHooks = args.includes('--hooks');
870
+ const noCommands = args.includes('--no-commands');
871
+ const noAgents = args.includes('--no-agents');
872
+ const agentArg = args.find(a => a.startsWith('--agent='))?.split('=')[1];
873
+ const profileArg = args.find(a => a.startsWith('--profile='))?.split('=')[1];
874
+ const rulesArg = args.find(a => a === '--rules' || a.startsWith('--rules='));
875
+ const rulesLang = rulesArg?.includes('=') ? rulesArg.split('=')[1] : null;
876
+ const targetArg = (args.find(a => a.startsWith('--target='))?.split('=')[1] ?? 'claude').toLowerCase();
877
+ const toClaude = targetArg === 'claude' || targetArg === 'all';
878
+ const toCursor = targetArg === 'cursor' || targetArg === 'all';
879
+ const skillName = args.find(a => !a.startsWith('--') && a !== 'add');
880
+
881
+ const installSkills = (list) => {
882
+ if (toClaude) list.forEach(s => copySkill(s, targetDir));
883
+ if (toCursor) list.forEach(s => copySkillToCursor(s));
884
+ if (toClaude && !noCommands) list.forEach(s => copyCommand(s));
885
+ };
886
+ const installAgents = (list) => {
887
+ if (toClaude && !noAgents) list.forEach(a => copyAgent(a));
888
+ // agents not applicable to Cursor
889
+ };
780
890
 
781
891
  if (profileArg) {
782
892
  const profile = PROFILES[profileArg];
@@ -784,15 +894,14 @@ async function main() {
784
894
  console.error(c.red(`✗ Profile "${profileArg}" not found.`) + ' Run ' + c.cyan('skills profiles') + ' to see available profiles.');
785
895
  process.exit(1);
786
896
  }
787
- profile.skills.forEach(s => copySkill(s, targetDir));
788
- if (!noCommands) profile.skills.forEach(s => copyCommand(s));
789
- if (!noAgents) profile.agents.forEach(a => copyAgent(a));
790
- const agentStr = profile.agents.length
897
+ installSkills(profile.skills);
898
+ installAgents(profile.agents);
899
+ const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
900
+ const agentStr = (!noAgents && toClaude && profile.agents.length)
791
901
  ? `, ${profile.agents.length} agent${profile.agents.length > 1 ? 's' : ''}`
792
902
  : '';
793
- console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr}`));
903
+ console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr} → ${targets}`));
794
904
  } else if (agentArg) {
795
- // explicit: skills add --agent=booklib-reviewer
796
905
  const agents = getAvailableAgents();
797
906
  if (!agents.includes(agentArg)) {
798
907
  console.error(c.red(`✗ Agent "${agentArg}" not found.`) + ' Available: ' + c.dim(agents.join(', ')));
@@ -802,14 +911,27 @@ async function main() {
802
911
  console.log(c.dim(`\nInstalled to ${agentsTargetDir}`));
803
912
  } else if (addAll) {
804
913
  const skills = getAvailableSkills();
805
- skills.forEach(s => copySkill(s, targetDir));
806
- if (!noCommands) skills.forEach(s => copyCommand(s));
807
- if (!noAgents) getAvailableAgents().forEach(a => copyAgent(a));
808
- const agentCount = noAgents ? 0 : getAvailableAgents().length;
809
- console.log(c.dim(`\nInstalled ${skills.length} skills, ${agentCount} agents to .claude/`));
914
+ const agents = getAvailableAgents();
915
+ installSkills(skills);
916
+ installAgents(agents);
917
+ if (toClaude) { copyHooks(); copyAllRules(); }
918
+ const agentCount = (!noAgents && toClaude) ? agents.length : 0;
919
+ const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
920
+ console.log(c.dim(`\nInstalled ${skills.length} skills, ${agentCount} agents, ${getAvailableRules().length} rules → ${targets}`));
921
+ } else if (addHooks) {
922
+ if (toClaude) copyHooks();
923
+ else console.log(c.yellow(' --hooks only applies to Claude targets. Use without --target=cursor.'));
924
+ break;
925
+ } else if (rulesArg) {
926
+ if (!toClaude) {
927
+ console.log(c.yellow(' --rules only applies to Claude targets (.claude/rules/).'));
928
+ break;
929
+ }
930
+ if (rulesLang) copyRules(rulesLang);
931
+ else copyAllRules();
932
+ console.log(c.dim(`\nInstalled rules → ${rulesTargetDir}`));
810
933
  } else if (skillName) {
811
- copySkill(skillName, targetDir);
812
- if (!noCommands) copyCommand(skillName);
934
+ installSkills([skillName]);
813
935
  console.log(c.dim(`\nInstalled to ${targetDir}`));
814
936
  } else {
815
937
  console.error(c.red('Usage: skills add <skill-name> | skills add --all | skills add --agent=<name>'));
@@ -916,6 +1038,74 @@ async function main() {
916
1038
  break;
917
1039
  }
918
1040
 
1041
+ case 'agents': {
1042
+ const infoArg = args.find(a => a.startsWith('--info='))?.split('=')[1]
1043
+ || args.find(a => !a.startsWith('--') && a !== 'agents');
1044
+ const available = getAvailableAgents();
1045
+
1046
+ if (infoArg) {
1047
+ if (!available.includes(infoArg)) {
1048
+ console.error(c.red(`✗ Agent "${infoArg}" not found.`) + ' Run ' + c.cyan('skills agents') + ' to see available agents.');
1049
+ process.exit(1);
1050
+ }
1051
+ const { description, model } = parseAgentFrontmatter(infoArg);
1052
+ console.log('');
1053
+ console.log(c.bold(` ${infoArg}`) + c.dim(model ? ` [${model}]` : ''));
1054
+ console.log(' ' + c.line(55));
1055
+ console.log(' ' + description);
1056
+ console.log('');
1057
+ console.log(c.dim(` Install: skills add --agent=${infoArg}`));
1058
+ console.log('');
1059
+ } else {
1060
+ const nameW = Math.max(...available.map(n => n.length)) + 2;
1061
+ console.log('');
1062
+ console.log(c.bold(` @booklib/skills — agents`) + c.dim(` (${available.length})`));
1063
+ console.log(' ' + c.line(60));
1064
+ for (const name of available) {
1065
+ const { description, model } = parseAgentFrontmatter(name);
1066
+ const modelTag = model ? c.dim(` [${model}]`) : '';
1067
+ console.log(` ${c.cyan(name.padEnd(nameW))}${modelTag}`);
1068
+ if (description) console.log(` ${' '.repeat(nameW)}${firstSentence(description, 72)}`);
1069
+ }
1070
+ console.log('');
1071
+ console.log(c.dim(` skills add --agent=<name> install one agent`));
1072
+ console.log(c.dim(` skills agents --info=<name> full description`));
1073
+ console.log('');
1074
+ }
1075
+ break;
1076
+ }
1077
+
1078
+ case 'rules': {
1079
+ const available = getAvailableRules();
1080
+ if (!available.length) {
1081
+ console.log(c.yellow(' No rules found.'));
1082
+ break;
1083
+ }
1084
+ // Group by language
1085
+ const byLang = {};
1086
+ for (const r of available) {
1087
+ if (!byLang[r.language]) byLang[r.language] = [];
1088
+ byLang[r.language].push(r);
1089
+ }
1090
+ console.log('');
1091
+ console.log(c.bold(' @booklib/skills — rules') + c.dim(` (${available.length} always-on)`));
1092
+ console.log(' ' + c.line(60));
1093
+ for (const [lang, rules] of Object.entries(byLang)) {
1094
+ console.log(` ${c.bold(lang)}`);
1095
+ for (const r of rules) {
1096
+ const content = fs.readFileSync(r.file, 'utf8');
1097
+ const descMatch = content.match(/^description:\s*(.+)$/m);
1098
+ const desc = descMatch ? descMatch[1].trim().replace(/^>$/, '') : '';
1099
+ console.log(` ${c.cyan(r.name.padEnd(28))}${c.dim(firstSentence(desc, 55))}`);
1100
+ }
1101
+ console.log('');
1102
+ }
1103
+ console.log(c.dim(` skills add --rules install all rules → .claude/rules/`));
1104
+ console.log(c.dim(` skills add --rules=<language> install rules for one language`));
1105
+ console.log('');
1106
+ break;
1107
+ }
1108
+
919
1109
  case 'profiles': {
920
1110
  const nameW = Math.max(...Object.keys(PROFILES).map(k => k.length)) + 2;
921
1111
  console.log('');
@@ -939,14 +1129,22 @@ ${c.bold(' @booklib/skills')} — book knowledge distilled into AI agent skills
939
1129
 
940
1130
  ${c.bold(' Usage:')}
941
1131
  ${c.cyan('skills list')} list all available skills
1132
+ ${c.cyan('skills agents')} list all available agents
1133
+ ${c.cyan('skills agents')} ${c.dim('--info=<name>')} full description of an agent
942
1134
  ${c.cyan('skills profiles')} list available profiles
1135
+ ${c.cyan('skills rules')} list always-on rule files
943
1136
  ${c.cyan('skills info')} ${c.dim('<name>')} full description of a skill
944
1137
  ${c.cyan('skills demo')} ${c.dim('<name>')} before/after example
945
1138
  ${c.cyan('skills add')} ${c.dim('--profile=<name>')} install a profile (skills + commands + agent)
946
1139
  ${c.cyan('skills add')} ${c.dim('<name>')} install a single skill + /command
947
- ${c.cyan('skills add --all')} install everything (skills + commands + agents)
1140
+ ${c.cyan('skills add --all')} install everything (skills + agents + rules + hooks)
948
1141
  ${c.cyan('skills add')} ${c.dim('<name> --global')} install globally (~/.claude/)
949
1142
  ${c.cyan('skills add')} ${c.dim('--agent=<name>')} install a single agent to .claude/agents/
1143
+ ${c.cyan('skills add --rules')} install always-on rules to .claude/rules/
1144
+ ${c.cyan('skills add')} ${c.dim('--rules=<language>')} install rules for one language
1145
+ ${c.cyan('skills add --hooks')} install the UserPromptSubmit suggestion hook
1146
+ ${c.cyan('skills add')} ${c.dim('--target=cursor')} install to .cursor/rules/ (Cursor IDE)
1147
+ ${c.cyan('skills add')} ${c.dim('--target=all')} install to both .claude/ and .cursor/
950
1148
  ${c.cyan('skills add')} ${c.dim('--no-commands')} skip /command installation
951
1149
  ${c.cyan('skills add')} ${c.dim('--no-agents')} skip agent installation
952
1150
  ${c.cyan('skills check')} ${c.dim('<name>')} quality check (Bronze/Silver/Gold/Platinum)
@@ -0,0 +1,12 @@
1
+ {
2
+ "UserPromptSubmit": [
3
+ {
4
+ "hooks": [
5
+ {
6
+ "type": "command",
7
+ "command": "node \"$HOME/.claude/booklib-suggest.js\""
8
+ }
9
+ ]
10
+ }
11
+ ]
12
+ }