@malamute/ai-rules 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -117
- package/configs/_shared/{CLAUDE.md → rules/conventions/core.md} +5 -0
- package/configs/_shared/rules/conventions/git.md +2 -0
- package/configs/_shared/rules/lang/python/python.md +4 -6
- package/configs/angular/{CLAUDE.md → rules/core.md} +5 -2
- package/configs/dotnet/{CLAUDE.md → rules/core.md} +5 -2
- package/configs/fastapi/{CLAUDE.md → rules/core.md} +5 -2
- package/configs/flask/{CLAUDE.md → rules/core.md} +5 -2
- package/configs/flask/rules/flask.md +4 -6
- package/configs/flask/rules/marshmallow.md +4 -6
- package/configs/nestjs/{CLAUDE.md → rules/core.md} +5 -2
- package/configs/nextjs/{CLAUDE.md → rules/core.md} +6 -3
- package/package.json +7 -1
- package/src/cli.js +93 -59
- package/src/installer.js +40 -63
- package/src/merge.js +0 -23
- package/src/tech-config.json +1 -6
package/README.md
CHANGED
|
@@ -10,12 +10,12 @@ AI Rules installs curated configuration boilerplates that teach Claude Code your
|
|
|
10
10
|
|
|
11
11
|
## Why Use This?
|
|
12
12
|
|
|
13
|
-
| Without AI Rules
|
|
14
|
-
|
|
15
|
-
| Claude uses generic patterns
|
|
16
|
-
| You repeat "use signals, not decorators" | Angular 21 patterns are built-in
|
|
17
|
-
| Security issues slip through
|
|
18
|
-
| Inconsistent code style
|
|
13
|
+
| Without AI Rules | With AI Rules |
|
|
14
|
+
| ---------------------------------------- | ---------------------------------------- |
|
|
15
|
+
| Claude uses generic patterns | Claude follows your framework's idioms |
|
|
16
|
+
| You repeat "use signals, not decorators" | Angular 21 patterns are built-in |
|
|
17
|
+
| Security issues slip through | OWASP Top 10 rules catch vulnerabilities |
|
|
18
|
+
| Inconsistent code style | Consistent conventions across the team |
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
@@ -24,7 +24,7 @@ AI Rules installs curated configuration boilerplates that teach Claude Code your
|
|
|
24
24
|
npx @malamute/ai-rules init
|
|
25
25
|
|
|
26
26
|
# Or specify your stack directly
|
|
27
|
-
npx @malamute/ai-rules init angular nestjs
|
|
27
|
+
npx @malamute/ai-rules init angular nestjs
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
That's it. Claude Code now understands your stack.
|
|
@@ -41,19 +41,20 @@ npx @malamute/ai-rules <command>
|
|
|
41
41
|
|
|
42
42
|
## Supported Technologies
|
|
43
43
|
|
|
44
|
-
| Technology
|
|
45
|
-
|
|
46
|
-
| **Angular** | Nx + NgRx + Signals + Vitest
|
|
47
|
-
| **Next.js** | App Router + React 19 + Server Components | 15+
|
|
48
|
-
| **NestJS**
|
|
49
|
-
| **.NET**
|
|
50
|
-
| **FastAPI** | Pydantic v2 + SQLAlchemy 2.0 + pytest
|
|
51
|
-
| **Flask**
|
|
44
|
+
| Technology | Stack | Version |
|
|
45
|
+
| ----------- | ----------------------------------------- | ------- |
|
|
46
|
+
| **Angular** | Nx + NgRx + Signals + Vitest | 21+ |
|
|
47
|
+
| **Next.js** | App Router + React 19 + Server Components | 15+ |
|
|
48
|
+
| **NestJS** | Prisma/TypeORM + Passport + Vitest | 11+ |
|
|
49
|
+
| **.NET** | Clean Architecture + MediatR + EF Core | 9+ |
|
|
50
|
+
| **FastAPI** | Pydantic v2 + SQLAlchemy 2.0 + pytest | 0.115+ |
|
|
51
|
+
| **Flask** | Marshmallow + SQLAlchemy 2.0 + pytest | 3.0+ |
|
|
52
52
|
|
|
53
53
|
## Commands
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
ai-rules init [tech...] # Install configs (interactive if no tech)
|
|
57
|
+
ai-rules add <tech> # Add technology to existing installation
|
|
57
58
|
ai-rules update # Update to latest rules
|
|
58
59
|
ai-rules status # Show installation info
|
|
59
60
|
ai-rules list # List available technologies
|
|
@@ -61,39 +62,37 @@ ai-rules list # List available technologies
|
|
|
61
62
|
|
|
62
63
|
### Options
|
|
63
64
|
|
|
64
|
-
| Option
|
|
65
|
-
|
|
66
|
-
| `--
|
|
67
|
-
| `--
|
|
68
|
-
| `--
|
|
69
|
-
| `--
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
| Option | Description |
|
|
66
|
+
| ---------------- | --------------------------------------------------------- |
|
|
67
|
+
| `--minimal` | Skip skills and shared rules (only tech rules + settings) |
|
|
68
|
+
| `--dry-run` | Preview changes without writing files |
|
|
69
|
+
| `--target <dir>` | Install to a specific directory |
|
|
70
|
+
| `--force` | Overwrite without creating backups |
|
|
71
|
+
|
|
72
|
+
By default, `init` installs everything (skills + shared rules). Use `--minimal` to skip extras.
|
|
72
73
|
|
|
73
74
|
## What Gets Installed
|
|
74
75
|
|
|
75
76
|
```
|
|
76
77
|
your-project/
|
|
77
|
-
├── CLAUDE.md #
|
|
78
|
+
├── CLAUDE.md # Your project-specific info (not touched)
|
|
78
79
|
└── .claude/
|
|
79
80
|
├── settings.json # Allowed/denied commands
|
|
80
81
|
├── rules/ # Framework-specific patterns
|
|
81
|
-
│ ├──
|
|
82
|
-
│ ├──
|
|
83
|
-
│
|
|
82
|
+
│ ├── nextjs/
|
|
83
|
+
│ │ ├── core.md # Stack, architecture, conventions
|
|
84
|
+
│ │ ├── components.md
|
|
85
|
+
│ │ └── ...
|
|
86
|
+
│ ├── conventions/ # Shared conventions
|
|
87
|
+
│ │ └── core.md
|
|
88
|
+
│ └── security/
|
|
84
89
|
└── skills/ # Optional workflows
|
|
85
90
|
├── learning/
|
|
86
91
|
├── review/
|
|
87
92
|
└── debug/
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
The main instruction file. Contains:
|
|
93
|
-
- Project architecture overview
|
|
94
|
-
- Technology stack and versions
|
|
95
|
-
- Coding conventions (naming, structure, patterns)
|
|
96
|
-
- Commands to run (build, test, lint)
|
|
95
|
+
> **Note:** Your project's `CLAUDE.md` is never modified. Use it for project-specific context (business domain, team conventions, etc.).
|
|
97
96
|
|
|
98
97
|
### Rules
|
|
99
98
|
|
|
@@ -102,9 +101,11 @@ Context-aware rules that activate based on file paths:
|
|
|
102
101
|
```markdown
|
|
103
102
|
---
|
|
104
103
|
paths:
|
|
105
|
-
-
|
|
104
|
+
- '**/*.component.ts'
|
|
106
105
|
---
|
|
106
|
+
|
|
107
107
|
# Angular Component Rules
|
|
108
|
+
|
|
108
109
|
- Use `ChangeDetectionStrategy.OnPush`
|
|
109
110
|
- Use `input()`, `output()`, not decorators
|
|
110
111
|
- Template in separate `.html` file
|
|
@@ -114,33 +115,33 @@ paths:
|
|
|
114
115
|
|
|
115
116
|
Interactive workflows invoked with `/skill-name`:
|
|
116
117
|
|
|
117
|
-
| Skill
|
|
118
|
-
|
|
119
|
-
| `/learning`
|
|
120
|
-
| `/review`
|
|
121
|
-
| `/debug`
|
|
122
|
-
| `/spec`
|
|
123
|
-
| `/fix-issue`
|
|
124
|
-
| `/generate-tests` | Generate comprehensive tests
|
|
118
|
+
| Skill | Description |
|
|
119
|
+
| ----------------- | ----------------------------------------- |
|
|
120
|
+
| `/learning` | Pedagogical mode — explains before coding |
|
|
121
|
+
| `/review` | Code review with security/perf checklist |
|
|
122
|
+
| `/debug` | Structured debugging workflow |
|
|
123
|
+
| `/spec` | Write technical spec before implementing |
|
|
124
|
+
| `/fix-issue` | Analyze GitHub issue and implement fix |
|
|
125
|
+
| `/generate-tests` | Generate comprehensive tests |
|
|
125
126
|
|
|
126
127
|
<details>
|
|
127
128
|
<summary><strong>See all 13 skills</strong></summary>
|
|
128
129
|
|
|
129
|
-
| Skill
|
|
130
|
-
|
|
131
|
-
| `/learning`
|
|
132
|
-
| `/review`
|
|
133
|
-
| `/spec`
|
|
134
|
-
| `/debug`
|
|
135
|
-
| `/fix-issue`
|
|
136
|
-
| `/review-pr`
|
|
137
|
-
| `/generate-tests` | `/generate-tests src/user.ts` | Generate tests
|
|
138
|
-
| `/api-endpoint`
|
|
139
|
-
| `/migration`
|
|
140
|
-
| `/security-audit` | `/security-audit`
|
|
141
|
-
| `/docker`
|
|
142
|
-
| `/deploy`
|
|
143
|
-
| `/explore`
|
|
130
|
+
| Skill | Usage | Description |
|
|
131
|
+
| ----------------- | ----------------------------- | ------------------------------------- |
|
|
132
|
+
| `/learning` | `/learning nextjs` | Explains concepts before implementing |
|
|
133
|
+
| `/review` | `/review src/users/` | Code review with checklist |
|
|
134
|
+
| `/spec` | `/spec add auth` | Technical specification |
|
|
135
|
+
| `/debug` | `/debug TypeError...` | Systematic debugging |
|
|
136
|
+
| `/fix-issue` | `/fix-issue 123` | Fix GitHub issue |
|
|
137
|
+
| `/review-pr` | `/review-pr 456` | Review pull request |
|
|
138
|
+
| `/generate-tests` | `/generate-tests src/user.ts` | Generate tests |
|
|
139
|
+
| `/api-endpoint` | `/api-endpoint POST /users` | Generate API endpoint |
|
|
140
|
+
| `/migration` | `/migration add users` | Database migration |
|
|
141
|
+
| `/security-audit` | `/security-audit` | Security analysis |
|
|
142
|
+
| `/docker` | `/docker` | Dockerfile generation |
|
|
143
|
+
| `/deploy` | `/deploy` | Deployment config |
|
|
144
|
+
| `/explore` | `/explore` | Repository analysis |
|
|
144
145
|
|
|
145
146
|
</details>
|
|
146
147
|
|
|
@@ -148,15 +149,15 @@ Interactive workflows invoked with `/skill-name`:
|
|
|
148
149
|
|
|
149
150
|
Cross-framework rules included with `--with-rules`:
|
|
150
151
|
|
|
151
|
-
| Rule
|
|
152
|
-
|
|
153
|
-
| **security.md**
|
|
154
|
-
| **performance.md**
|
|
155
|
-
| **accessibility.md**
|
|
156
|
-
| **testing-patterns.md** | AAA pattern, mocking, coverage
|
|
157
|
-
| **error-handling.md**
|
|
158
|
-
| **git.md**
|
|
159
|
-
| **observability.md**
|
|
152
|
+
| Rule | What It Covers |
|
|
153
|
+
| ----------------------- | ------------------------------------------- |
|
|
154
|
+
| **security.md** | OWASP Top 10: injection, XSS, CSRF, secrets |
|
|
155
|
+
| **performance.md** | N+1 queries, caching, lazy loading |
|
|
156
|
+
| **accessibility.md** | WCAG 2.1, semantic HTML, ARIA |
|
|
157
|
+
| **testing-patterns.md** | AAA pattern, mocking, coverage |
|
|
158
|
+
| **error-handling.md** | Error categories, response formats |
|
|
159
|
+
| **git.md** | Conventional commits, branching, PRs |
|
|
160
|
+
| **observability.md** | Logging, metrics, tracing |
|
|
160
161
|
|
|
161
162
|
## Examples
|
|
162
163
|
|
|
@@ -164,10 +165,16 @@ Cross-framework rules included with `--with-rules`:
|
|
|
164
165
|
|
|
165
166
|
```bash
|
|
166
167
|
# Angular frontend + NestJS backend
|
|
167
|
-
ai-rules init angular nestjs
|
|
168
|
+
ai-rules init angular nestjs
|
|
168
169
|
|
|
169
170
|
# Next.js frontend + FastAPI backend
|
|
170
|
-
ai-rules init nextjs fastapi
|
|
171
|
+
ai-rules init nextjs fastapi
|
|
172
|
+
|
|
173
|
+
# Add a technology to existing installation
|
|
174
|
+
ai-rules add nestjs
|
|
175
|
+
|
|
176
|
+
# Minimal install (no skills/shared rules)
|
|
177
|
+
ai-rules init angular --minimal
|
|
171
178
|
```
|
|
172
179
|
|
|
173
180
|
### Preview Before Installing
|
|
@@ -177,15 +184,15 @@ ai-rules init angular --dry-run
|
|
|
177
184
|
```
|
|
178
185
|
|
|
179
186
|
Output:
|
|
187
|
+
|
|
180
188
|
```
|
|
181
189
|
DRY RUN - No files will be modified
|
|
182
190
|
|
|
183
191
|
ℹ Would install to: /your/project
|
|
184
192
|
|
|
185
193
|
ℹ Would install angular...
|
|
186
|
-
○ CLAUDE.md (create)
|
|
187
194
|
○ settings.json (create)
|
|
188
|
-
○ rules/ (
|
|
195
|
+
○ rules/angular/ (9 files)
|
|
189
196
|
|
|
190
197
|
Summary:
|
|
191
198
|
10 file(s) would be created
|
|
@@ -210,89 +217,89 @@ ai-rules update
|
|
|
210
217
|
<details>
|
|
211
218
|
<summary><strong>Angular</strong></summary>
|
|
212
219
|
|
|
213
|
-
| Aspect
|
|
214
|
-
|
|
215
|
-
| Components | Standalone, OnPush change detection
|
|
216
|
-
| Signals
|
|
217
|
-
| State
|
|
218
|
-
| Structure
|
|
219
|
-
| Tests
|
|
220
|
+
| Aspect | Convention |
|
|
221
|
+
| ---------- | --------------------------------------------- |
|
|
222
|
+
| Components | Standalone, OnPush change detection |
|
|
223
|
+
| Signals | `input()`, `output()`, `model()` functions |
|
|
224
|
+
| State | NgRx with Entity Adapter + Functional Effects |
|
|
225
|
+
| Structure | Nx monorepo with feature/ui/data-access libs |
|
|
226
|
+
| Tests | Vitest + Marble testing |
|
|
220
227
|
|
|
221
228
|
</details>
|
|
222
229
|
|
|
223
230
|
<details>
|
|
224
231
|
<summary><strong>Next.js</strong></summary>
|
|
225
232
|
|
|
226
|
-
| Aspect
|
|
227
|
-
|
|
228
|
-
| Components | Server Components by default
|
|
229
|
-
| Client
|
|
230
|
-
| Data
|
|
231
|
-
| State
|
|
232
|
-
| Structure
|
|
233
|
+
| Aspect | Convention |
|
|
234
|
+
| ---------- | ------------------------------------------ |
|
|
235
|
+
| Components | Server Components by default |
|
|
236
|
+
| Client | `'use client'` directive for interactivity |
|
|
237
|
+
| Data | Server Components + fetch, Server Actions |
|
|
238
|
+
| State | Zustand (simple) / Redux Toolkit (complex) |
|
|
239
|
+
| Structure | App Router with route groups |
|
|
233
240
|
|
|
234
241
|
</details>
|
|
235
242
|
|
|
236
243
|
<details>
|
|
237
244
|
<summary><strong>NestJS</strong></summary>
|
|
238
245
|
|
|
239
|
-
| Aspect
|
|
240
|
-
|
|
241
|
-
| Architecture | Modular Monolith
|
|
242
|
-
| Validation
|
|
243
|
-
| Database
|
|
244
|
-
| Auth
|
|
245
|
-
| Tests
|
|
246
|
+
| Aspect | Convention |
|
|
247
|
+
| ------------ | -------------------------------------- |
|
|
248
|
+
| Architecture | Modular Monolith |
|
|
249
|
+
| Validation | class-validator + class-transformer |
|
|
250
|
+
| Database | Prisma (modern) / TypeORM (decorators) |
|
|
251
|
+
| Auth | Passport + JWT |
|
|
252
|
+
| Tests | Vitest + Supertest |
|
|
246
253
|
|
|
247
254
|
</details>
|
|
248
255
|
|
|
249
256
|
<details>
|
|
250
257
|
<summary><strong>.NET</strong></summary>
|
|
251
258
|
|
|
252
|
-
| Aspect
|
|
253
|
-
|
|
259
|
+
| Aspect | Convention |
|
|
260
|
+
| ------------ | ----------------------------------------- |
|
|
254
261
|
| Architecture | Clean Architecture (Domain → App → Infra) |
|
|
255
|
-
| API
|
|
256
|
-
| CQRS
|
|
257
|
-
| ORM
|
|
258
|
-
| Tests
|
|
262
|
+
| API | Minimal APIs (preferred) or Controllers |
|
|
263
|
+
| CQRS | MediatR for Commands/Queries |
|
|
264
|
+
| ORM | Entity Framework Core |
|
|
265
|
+
| Tests | xUnit + NSubstitute + FluentAssertions |
|
|
259
266
|
|
|
260
267
|
</details>
|
|
261
268
|
|
|
262
269
|
<details>
|
|
263
270
|
<summary><strong>FastAPI</strong></summary>
|
|
264
271
|
|
|
265
|
-
| Aspect
|
|
266
|
-
|
|
267
|
-
| Framework
|
|
268
|
-
| Validation | Pydantic v2
|
|
269
|
-
| ORM
|
|
270
|
-
| Tests
|
|
271
|
-
| Migrations | Alembic
|
|
272
|
+
| Aspect | Convention |
|
|
273
|
+
| ---------- | --------------------------------- |
|
|
274
|
+
| Framework | FastAPI with async/await |
|
|
275
|
+
| Validation | Pydantic v2 |
|
|
276
|
+
| ORM | SQLAlchemy 2.0 with async support |
|
|
277
|
+
| Tests | pytest + httpx |
|
|
278
|
+
| Migrations | Alembic |
|
|
272
279
|
|
|
273
280
|
</details>
|
|
274
281
|
|
|
275
282
|
<details>
|
|
276
283
|
<summary><strong>Flask</strong></summary>
|
|
277
284
|
|
|
278
|
-
| Aspect
|
|
279
|
-
|
|
280
|
-
| Framework
|
|
281
|
-
| Validation | Marshmallow schemas
|
|
282
|
-
| ORM
|
|
283
|
-
| Tests
|
|
285
|
+
| Aspect | Convention |
|
|
286
|
+
| ---------- | --------------------------------------------------- |
|
|
287
|
+
| Framework | Flask 3.0 with Application Factory |
|
|
288
|
+
| Validation | Marshmallow schemas |
|
|
289
|
+
| ORM | SQLAlchemy 2.0 |
|
|
290
|
+
| Tests | pytest |
|
|
284
291
|
| Extensions | Flask-SQLAlchemy, Flask-Migrate, Flask-JWT-Extended |
|
|
285
292
|
|
|
286
293
|
</details>
|
|
287
294
|
|
|
288
295
|
## How It Works
|
|
289
296
|
|
|
290
|
-
1. **
|
|
291
|
-
2.
|
|
297
|
+
1. **Rules** are loaded by Claude Code based on file paths you're editing
|
|
298
|
+
2. **`rules/core.md`** with `alwaysApply: true` provides framework conventions
|
|
292
299
|
3. **Skills** are invoked on-demand with `/skill-name`
|
|
293
300
|
4. **Settings** define what commands Claude can run
|
|
294
301
|
|
|
295
|
-
|
|
302
|
+
Your project's `CLAUDE.md` stays clean for project-specific context, while framework conventions live in rules.
|
|
296
303
|
|
|
297
304
|
## Contributing
|
|
298
305
|
|
|
@@ -306,13 +313,13 @@ npm install
|
|
|
306
313
|
npm test
|
|
307
314
|
|
|
308
315
|
# Add a new technology
|
|
309
|
-
mkdir configs/your-tech
|
|
310
|
-
# Add
|
|
316
|
+
mkdir -p configs/your-tech/rules
|
|
317
|
+
# Add rules/core.md and other rules
|
|
311
318
|
```
|
|
312
319
|
|
|
313
320
|
### Adding a Technology
|
|
314
321
|
|
|
315
|
-
1. Create `configs/[tech]/
|
|
322
|
+
1. Create `configs/[tech]/rules/core.md` with framework conventions
|
|
316
323
|
2. Add rules in `configs/[tech]/rules/`
|
|
317
324
|
3. Add `configs/[tech]/settings.json` for permissions
|
|
318
325
|
4. Add tests
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Next.js 15+ project conventions and architecture"
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# Next.js Project Guidelines
|
|
4
7
|
|
|
5
8
|
## Stack
|
|
6
9
|
|
|
@@ -31,7 +34,7 @@ libs/[domain]/
|
|
|
31
34
|
### Folder Conventions
|
|
32
35
|
|
|
33
36
|
| Pattern | Meaning |
|
|
34
|
-
|
|
37
|
+
|---------|---------|
|
|
35
38
|
| `_folder/` | Private - co-located, not a route |
|
|
36
39
|
| `(folder)/` | Route group - organizational only |
|
|
37
40
|
| `[param]/` | Dynamic segment |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malamute/ai-rules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Claude Code configuration boilerplates for Angular, Next.js, NestJS, .NET, Python and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"test:coverage": "vitest run --coverage",
|
|
12
12
|
"lint": "eslint src bin",
|
|
13
13
|
"lint:rules": "node scripts/lint-rules.js",
|
|
14
|
+
"format": "prettier --write .",
|
|
15
|
+
"format:check": "prettier --check .",
|
|
14
16
|
"validate": "npm test && npm run lint:rules"
|
|
15
17
|
},
|
|
16
18
|
"keywords": [
|
|
@@ -46,10 +48,14 @@
|
|
|
46
48
|
"engines": {
|
|
47
49
|
"node": ">=18.0.0"
|
|
48
50
|
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@inquirer/prompts": "^7.5.3"
|
|
53
|
+
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"@eslint/js": "^9.39.2",
|
|
51
56
|
"eslint": "^9.39.2",
|
|
52
57
|
"husky": "^9.1.7",
|
|
58
|
+
"prettier": "^3.5.3",
|
|
53
59
|
"vitest": "^2.0.0"
|
|
54
60
|
},
|
|
55
61
|
"packageManager": "yarn@4.12.0"
|
package/src/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { checkbox, input } from '@inquirer/prompts';
|
|
2
2
|
import { colors, log } from './utils.js';
|
|
3
3
|
import { VERSION, AVAILABLE_TECHS } from './config.js';
|
|
4
4
|
import { init, update, status, listTechnologies } from './installer.js';
|
|
5
|
+
import { readManifest } from './merge.js';
|
|
5
6
|
|
|
6
7
|
function printUsage() {
|
|
7
8
|
console.log(`
|
|
@@ -9,12 +10,14 @@ ${colors.bold('AI Rules')} v${VERSION} - Claude Code configuration boilerplates
|
|
|
9
10
|
|
|
10
11
|
${colors.bold('Usage:')}
|
|
11
12
|
ai-rules init [tech] [tech2] [options]
|
|
13
|
+
ai-rules add <tech> [options]
|
|
12
14
|
ai-rules update [options]
|
|
13
15
|
ai-rules status
|
|
14
16
|
ai-rules list
|
|
15
17
|
|
|
16
18
|
${colors.bold('Commands:')}
|
|
17
19
|
init Install configuration (interactive if no tech specified)
|
|
20
|
+
add Add a technology to existing installation
|
|
18
21
|
update Update installed configs to latest version
|
|
19
22
|
status Show current installation status
|
|
20
23
|
list List available technologies
|
|
@@ -28,7 +31,7 @@ ${colors.bold('Technologies:')}
|
|
|
28
31
|
flask Flask + SQLAlchemy + Marshmallow
|
|
29
32
|
|
|
30
33
|
${colors.bold('Options:')}
|
|
31
|
-
--minimal Only install
|
|
34
|
+
--minimal Only install settings.json and tech rules (no shared skills/rules)
|
|
32
35
|
--target <dir> Target directory (default: current directory)
|
|
33
36
|
--dry-run Preview changes without writing files
|
|
34
37
|
--force Overwrite files without backup (update command)
|
|
@@ -37,78 +40,54 @@ ${colors.bold('Examples:')}
|
|
|
37
40
|
ai-rules init # Interactive mode
|
|
38
41
|
ai-rules init angular # Full install (skills + rules)
|
|
39
42
|
ai-rules init angular --minimal # Minimal install
|
|
40
|
-
ai-rules
|
|
43
|
+
ai-rules add nestjs # Add NestJS to existing install
|
|
41
44
|
ai-rules update
|
|
42
|
-
ai-rules update --force
|
|
43
45
|
ai-rules status
|
|
44
46
|
`);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
async function prompt(question) {
|
|
48
|
-
const rl = readline.createInterface({
|
|
49
|
-
input: process.stdin,
|
|
50
|
-
output: process.stdout,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return new Promise((resolve) => {
|
|
54
|
-
rl.question(question, (answer) => {
|
|
55
|
-
rl.close();
|
|
56
|
-
resolve(answer.trim());
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function multiSelect(message, choices) {
|
|
62
|
-
console.log(`\n${colors.bold(message)}`);
|
|
63
|
-
console.log(colors.dim('(enter numbers separated by spaces, or "all")'));
|
|
64
|
-
console.log('');
|
|
65
|
-
|
|
66
|
-
choices.forEach((choice, i) => {
|
|
67
|
-
console.log(` ${colors.cyan(i + 1)}. ${choice.name} ${colors.dim(`- ${choice.description}`)}`);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
console.log('');
|
|
71
|
-
const answer = await prompt('Your selection: ');
|
|
72
|
-
|
|
73
|
-
if (answer.toLowerCase() === 'all') {
|
|
74
|
-
return choices.map((c) => c.value);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const indices = answer
|
|
78
|
-
.split(/[\s,]+/)
|
|
79
|
-
.map((s) => parseInt(s, 10) - 1)
|
|
80
|
-
.filter((i) => i >= 0 && i < choices.length);
|
|
81
|
-
|
|
82
|
-
return indices.map((i) => choices[i].value);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
49
|
async function interactiveInit() {
|
|
86
50
|
console.log(`\n${colors.bold('AI Rules')} - Interactive Setup\n`);
|
|
87
51
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
52
|
+
const techs = await checkbox({
|
|
53
|
+
message: 'Select technologies:',
|
|
54
|
+
instructions: '(Space to select, Enter to confirm)',
|
|
55
|
+
choices: [
|
|
56
|
+
{ name: 'Angular - Angular 21 + Nx + NgRx + Signals', value: 'angular' },
|
|
57
|
+
{ name: 'Next.js - Next.js 15 + React 19 + App Router', value: 'nextjs' },
|
|
58
|
+
{ name: 'NestJS - NestJS 11 + Prisma/TypeORM + Passport', value: 'nestjs' },
|
|
59
|
+
{ name: '.NET - .NET 9 + ASP.NET Core + EF Core', value: 'dotnet' },
|
|
60
|
+
{ name: 'FastAPI - FastAPI + SQLAlchemy 2.0 + Pydantic v2', value: 'fastapi' },
|
|
61
|
+
{ name: 'Flask - Flask + SQLAlchemy 2.0 + Marshmallow', value: 'flask' },
|
|
62
|
+
],
|
|
63
|
+
});
|
|
98
64
|
|
|
99
65
|
if (techs.length === 0) {
|
|
100
66
|
log.error('No technology selected');
|
|
101
67
|
process.exit(1);
|
|
102
68
|
}
|
|
103
69
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
70
|
+
const extras = await checkbox({
|
|
71
|
+
message: 'Include extras:',
|
|
72
|
+
instructions: '(Space to toggle, Enter to confirm)',
|
|
73
|
+
choices: [
|
|
74
|
+
{
|
|
75
|
+
name: 'Skills - /learning, /review, /spec, /debug, etc.',
|
|
76
|
+
value: 'skills',
|
|
77
|
+
checked: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Shared Rules - security, performance, accessibility',
|
|
81
|
+
value: 'rules',
|
|
82
|
+
checked: true,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
});
|
|
110
86
|
|
|
111
|
-
const targetDir = await
|
|
87
|
+
const targetDir = await input({
|
|
88
|
+
message: 'Target directory:',
|
|
89
|
+
default: '.',
|
|
90
|
+
});
|
|
112
91
|
|
|
113
92
|
const options = {
|
|
114
93
|
target: targetDir === '.' ? null : targetDir,
|
|
@@ -164,6 +143,61 @@ export async function run(args) {
|
|
|
164
143
|
return;
|
|
165
144
|
}
|
|
166
145
|
|
|
146
|
+
if (command === 'add') {
|
|
147
|
+
const targetIndex = args.indexOf('--target');
|
|
148
|
+
const targetDir = targetIndex !== -1 ? args[targetIndex + 1] : process.cwd();
|
|
149
|
+
|
|
150
|
+
const manifest = readManifest(targetDir);
|
|
151
|
+
if (!manifest) {
|
|
152
|
+
log.error('No ai-rules installation found.');
|
|
153
|
+
console.log(`Run ${colors.cyan('ai-rules init')} first.`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const newTechs = [];
|
|
158
|
+
for (let i = 1; i < args.length; i++) {
|
|
159
|
+
const arg = args[i];
|
|
160
|
+
if (arg === '--target') {
|
|
161
|
+
i++; // Skip next arg
|
|
162
|
+
} else if (arg === '--dry-run' || arg === '--force') {
|
|
163
|
+
// Handled below
|
|
164
|
+
} else if (!arg.startsWith('-')) {
|
|
165
|
+
if (AVAILABLE_TECHS.includes(arg)) {
|
|
166
|
+
if (manifest.technologies.includes(arg)) {
|
|
167
|
+
log.warning(`${arg} is already installed, skipping`);
|
|
168
|
+
} else {
|
|
169
|
+
newTechs.push(arg);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
log.error(`Unknown technology: ${arg}`);
|
|
173
|
+
console.log(`Available: ${AVAILABLE_TECHS.join(', ')}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (newTechs.length === 0) {
|
|
180
|
+
log.error('No new technology to add');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const allTechs = [...manifest.technologies, ...newTechs];
|
|
185
|
+
|
|
186
|
+
const options = {
|
|
187
|
+
target: targetDir === process.cwd() ? null : targetDir,
|
|
188
|
+
withSkills: manifest.options?.withSkills ?? true,
|
|
189
|
+
withRules: manifest.options?.withRules ?? true,
|
|
190
|
+
dryRun: args.includes('--dry-run'),
|
|
191
|
+
force: args.includes('--force'),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
console.log('');
|
|
195
|
+
log.info(`Adding ${newTechs.join(', ')} to existing installation`);
|
|
196
|
+
|
|
197
|
+
init(allTechs, options);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
167
201
|
if (command === 'init') {
|
|
168
202
|
const minimal = args.includes('--minimal');
|
|
169
203
|
const options = {
|
package/src/installer.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { colors, log, getFilesRecursive, copyDirRecursive, backupFile } from './utils.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CONFIGS_DIR,
|
|
6
|
+
AVAILABLE_TECHS,
|
|
7
|
+
VERSION,
|
|
8
|
+
getRulePathsToInclude,
|
|
9
|
+
shouldIncludeRule,
|
|
10
|
+
} from './config.js';
|
|
11
|
+
import { mergeSettingsJson, readManifest, writeManifest } from './merge.js';
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Copy skills to target directory with flat structure.
|
|
@@ -114,8 +120,12 @@ export function listTechnologies() {
|
|
|
114
120
|
const skills = fs.existsSync(path.join(sharedPath, 'skills'));
|
|
115
121
|
const rules = fs.existsSync(path.join(sharedPath, 'rules'));
|
|
116
122
|
|
|
117
|
-
console.log(
|
|
118
|
-
|
|
123
|
+
console.log(
|
|
124
|
+
` ${skills ? colors.green('✓') : colors.red('✗')} skills /learning, /review, /spec, /debug, and more`
|
|
125
|
+
);
|
|
126
|
+
console.log(
|
|
127
|
+
` ${rules ? colors.green('✓') : colors.red('✗')} rules security, performance, accessibility`
|
|
128
|
+
);
|
|
119
129
|
console.log('');
|
|
120
130
|
}
|
|
121
131
|
|
|
@@ -136,7 +146,9 @@ export function status(targetDir) {
|
|
|
136
146
|
|
|
137
147
|
console.log(` ${colors.bold('Installed version:')} ${manifest.version}`);
|
|
138
148
|
console.log(` ${colors.bold('Latest version:')} ${VERSION}`);
|
|
139
|
-
console.log(
|
|
149
|
+
console.log(
|
|
150
|
+
` ${colors.bold('Installed at:')} ${new Date(manifest.installedAt).toLocaleString()}`
|
|
151
|
+
);
|
|
140
152
|
console.log('');
|
|
141
153
|
|
|
142
154
|
if (manifest.technologies?.length) {
|
|
@@ -155,7 +167,9 @@ export function status(targetDir) {
|
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
if (manifest.version !== VERSION) {
|
|
158
|
-
console.log(
|
|
170
|
+
console.log(
|
|
171
|
+
` ${colors.yellow('⚠')} Update available! Run ${colors.cyan('ai-rules update')} to update.`
|
|
172
|
+
);
|
|
159
173
|
console.log('');
|
|
160
174
|
}
|
|
161
175
|
|
|
@@ -187,8 +201,6 @@ export function init(techs, options) {
|
|
|
187
201
|
fs.mkdirSync(path.join(targetDir, '.claude', 'rules'), { recursive: true });
|
|
188
202
|
}
|
|
189
203
|
|
|
190
|
-
let isFirstClaudeMd = true;
|
|
191
|
-
|
|
192
204
|
for (const tech of techs) {
|
|
193
205
|
log.info(`${dryRun ? 'Would install' : 'Installing'} ${tech}...`);
|
|
194
206
|
|
|
@@ -199,31 +211,13 @@ export function init(techs, options) {
|
|
|
199
211
|
process.exit(1);
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
const claudeMdPath = path.join(techDir, 'CLAUDE.md');
|
|
203
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
204
|
-
const op = mergeClaudeMd(
|
|
205
|
-
path.join(targetDir, 'CLAUDE.md'),
|
|
206
|
-
claudeMdPath,
|
|
207
|
-
isFirstClaudeMd,
|
|
208
|
-
{ dryRun, backup, targetDir }
|
|
209
|
-
);
|
|
210
|
-
operations.push(op);
|
|
211
|
-
isFirstClaudeMd = false;
|
|
212
|
-
|
|
213
|
-
if (dryRun) {
|
|
214
|
-
log.dry(` CLAUDE.md (${op.type})`);
|
|
215
|
-
} else {
|
|
216
|
-
log.success(` CLAUDE.md`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
214
|
const settingsPath = path.join(techDir, 'settings.json');
|
|
221
215
|
if (fs.existsSync(settingsPath)) {
|
|
222
|
-
const op = mergeSettingsJson(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
216
|
+
const op = mergeSettingsJson(path.join(targetDir, '.claude', 'settings.json'), settingsPath, {
|
|
217
|
+
dryRun,
|
|
218
|
+
backup,
|
|
219
|
+
targetDir,
|
|
220
|
+
});
|
|
227
221
|
operations.push(op);
|
|
228
222
|
|
|
229
223
|
if (dryRun) {
|
|
@@ -235,11 +229,11 @@ export function init(techs, options) {
|
|
|
235
229
|
|
|
236
230
|
const rulesDir = path.join(techDir, 'rules');
|
|
237
231
|
if (fs.existsSync(rulesDir)) {
|
|
238
|
-
const ops = copyDirRecursive(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
232
|
+
const ops = copyDirRecursive(rulesDir, path.join(targetDir, '.claude', 'rules', tech), {
|
|
233
|
+
dryRun,
|
|
234
|
+
backup,
|
|
235
|
+
targetDir,
|
|
236
|
+
});
|
|
243
237
|
operations.push(...ops);
|
|
244
238
|
|
|
245
239
|
if (dryRun) {
|
|
@@ -252,11 +246,11 @@ export function init(techs, options) {
|
|
|
252
246
|
if (options.withSkills) {
|
|
253
247
|
const techSkillsDir = path.join(techDir, 'skills');
|
|
254
248
|
if (fs.existsSync(techSkillsDir)) {
|
|
255
|
-
const ops = copySkillsToTarget(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
);
|
|
249
|
+
const ops = copySkillsToTarget(techSkillsDir, path.join(targetDir, '.claude', 'skills'), {
|
|
250
|
+
dryRun,
|
|
251
|
+
backup,
|
|
252
|
+
targetDir,
|
|
253
|
+
});
|
|
260
254
|
operations.push(...ops);
|
|
261
255
|
}
|
|
262
256
|
}
|
|
@@ -268,11 +262,11 @@ export function init(techs, options) {
|
|
|
268
262
|
log.info(`${dryRun ? 'Would install' : 'Installing'} skills...`);
|
|
269
263
|
const skillsDir = path.join(sharedDir, 'skills');
|
|
270
264
|
if (fs.existsSync(skillsDir)) {
|
|
271
|
-
const ops = copySkillsToTarget(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
);
|
|
265
|
+
const ops = copySkillsToTarget(skillsDir, path.join(targetDir, '.claude', 'skills'), {
|
|
266
|
+
dryRun,
|
|
267
|
+
backup,
|
|
268
|
+
targetDir,
|
|
269
|
+
});
|
|
276
270
|
operations.push(...ops);
|
|
277
271
|
|
|
278
272
|
if (dryRun) {
|
|
@@ -312,23 +306,6 @@ export function init(techs, options) {
|
|
|
312
306
|
}
|
|
313
307
|
}
|
|
314
308
|
|
|
315
|
-
// Resolve @../_shared/CLAUDE.md imports
|
|
316
|
-
const targetClaudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
317
|
-
if (!dryRun && fs.existsSync(targetClaudeMd)) {
|
|
318
|
-
let content = fs.readFileSync(targetClaudeMd, 'utf8');
|
|
319
|
-
|
|
320
|
-
if (content.includes('@../_shared/CLAUDE.md')) {
|
|
321
|
-
const sharedClaudeMd = path.join(sharedDir, 'CLAUDE.md');
|
|
322
|
-
if (fs.existsSync(sharedClaudeMd)) {
|
|
323
|
-
const sharedContent = fs.readFileSync(sharedClaudeMd, 'utf8');
|
|
324
|
-
content = content.replace(/@..\/_shared\/CLAUDE\.md/g, '');
|
|
325
|
-
content = sharedContent + '\n\n' + content;
|
|
326
|
-
fs.writeFileSync(targetClaudeMd, content);
|
|
327
|
-
log.success('Merged shared conventions into CLAUDE.md');
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
309
|
writeManifest(
|
|
333
310
|
targetDir,
|
|
334
311
|
{
|
package/src/merge.js
CHANGED
|
@@ -3,29 +3,6 @@ import path from 'path';
|
|
|
3
3
|
import { log, backupFile } from './utils.js';
|
|
4
4
|
import { VERSION } from './config.js';
|
|
5
5
|
|
|
6
|
-
export function mergeClaudeMd(targetPath, sourcePath, isFirst, options = {}) {
|
|
7
|
-
const { dryRun = false, backup = false, targetDir } = options;
|
|
8
|
-
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
9
|
-
const exists = fs.existsSync(targetPath);
|
|
10
|
-
|
|
11
|
-
if (dryRun) {
|
|
12
|
-
return { type: exists ? 'merge' : 'create', path: 'CLAUDE.md' };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (exists && backup && isFirst) {
|
|
16
|
-
backupFile(targetPath, targetDir);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (isFirst) {
|
|
20
|
-
fs.writeFileSync(targetPath, content);
|
|
21
|
-
} else {
|
|
22
|
-
const existing = fs.readFileSync(targetPath, 'utf8');
|
|
23
|
-
fs.writeFileSync(targetPath, `${existing}\n\n---\n\n${content}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return { type: exists ? 'merge' : 'create', path: 'CLAUDE.md' };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
6
|
export function mergeSettingsJson(targetPath, sourcePath, options = {}) {
|
|
30
7
|
const { dryRun = false, backup = false, targetDir } = options;
|
|
31
8
|
const exists = fs.existsSync(targetPath);
|