@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 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 | 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 |
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 --all
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 | 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+ |
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 | Description |
65
- |--------|-------------|
66
- | `--with-skills` | Add skills: `/learning`, `/review`, `/debug`, etc. |
67
- | `--with-rules` | Add shared rules: security, performance, accessibility |
68
- | `--all` | Include both skills and rules |
69
- | `--dry-run` | Preview changes without writing files |
70
- | `--target <dir>` | Install to a specific directory |
71
- | `--force` | Overwrite without creating backups |
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 # Architecture & conventions
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
- │ ├── components.md
82
- │ ├── state.md
83
- └── testing.md
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
- ### CLAUDE.md
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
- - "**/*.component.ts"
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 | Description |
118
- |-------|-------------|
119
- | `/learning` | Pedagogical mode — explains before coding |
120
- | `/review` | Code review with security/perf checklist |
121
- | `/debug` | Structured debugging workflow |
122
- | `/spec` | Write technical spec before implementing |
123
- | `/fix-issue` | Analyze GitHub issue and implement fix |
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 | Usage | Description |
130
- |-------|-------|-------------|
131
- | `/learning` | `/learning nextjs` | Explains concepts before implementing |
132
- | `/review` | `/review src/users/` | Code review with checklist |
133
- | `/spec` | `/spec add auth` | Technical specification |
134
- | `/debug` | `/debug TypeError...` | Systematic debugging |
135
- | `/fix-issue` | `/fix-issue 123` | Fix GitHub issue |
136
- | `/review-pr` | `/review-pr 456` | Review pull request |
137
- | `/generate-tests` | `/generate-tests src/user.ts` | Generate tests |
138
- | `/api-endpoint` | `/api-endpoint POST /users` | Generate API endpoint |
139
- | `/migration` | `/migration add users` | Database migration |
140
- | `/security-audit` | `/security-audit` | Security analysis |
141
- | `/docker` | `/docker` | Dockerfile generation |
142
- | `/deploy` | `/deploy` | Deployment config |
143
- | `/explore` | `/explore` | Repository analysis |
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 | What It Covers |
152
- |------|----------------|
153
- | **security.md** | OWASP Top 10: injection, XSS, CSRF, secrets |
154
- | **performance.md** | N+1 queries, caching, lazy loading |
155
- | **accessibility.md** | WCAG 2.1, semantic HTML, ARIA |
156
- | **testing-patterns.md** | AAA pattern, mocking, coverage |
157
- | **error-handling.md** | Error categories, response formats |
158
- | **git.md** | Conventional commits, branching, PRs |
159
- | **observability.md** | Logging, metrics, tracing |
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 --all
168
+ ai-rules init angular nestjs
168
169
 
169
170
  # Next.js frontend + FastAPI backend
170
- ai-rules init nextjs fastapi --all
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/ (8 files)
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 | Convention |
214
- |--------|------------|
215
- | Components | Standalone, OnPush change detection |
216
- | Signals | `input()`, `output()`, `model()` functions |
217
- | State | NgRx with Entity Adapter + Functional Effects |
218
- | Structure | Nx monorepo with feature/ui/data-access libs |
219
- | Tests | Vitest + Marble testing |
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 | Convention |
227
- |--------|------------|
228
- | Components | Server Components by default |
229
- | Client | `'use client'` directive for interactivity |
230
- | Data | Server Components + fetch, Server Actions |
231
- | State | Zustand (simple) / Redux Toolkit (complex) |
232
- | Structure | App Router with route groups |
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 | Convention |
240
- |--------|------------|
241
- | Architecture | Modular Monolith |
242
- | Validation | class-validator + class-transformer |
243
- | Database | Prisma (modern) / TypeORM (decorators) |
244
- | Auth | Passport + JWT |
245
- | Tests | Vitest + Supertest |
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 | Convention |
253
- |--------|------------|
259
+ | Aspect | Convention |
260
+ | ------------ | ----------------------------------------- |
254
261
  | Architecture | Clean Architecture (Domain → App → Infra) |
255
- | API | Minimal APIs (preferred) or Controllers |
256
- | CQRS | MediatR for Commands/Queries |
257
- | ORM | Entity Framework Core |
258
- | Tests | xUnit + NSubstitute + FluentAssertions |
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 | Convention |
266
- |--------|------------|
267
- | Framework | FastAPI with async/await |
268
- | Validation | Pydantic v2 |
269
- | ORM | SQLAlchemy 2.0 with async support |
270
- | Tests | pytest + httpx |
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 | Convention |
279
- |--------|------------|
280
- | Framework | Flask 3.0 with Application Factory |
281
- | Validation | Marshmallow schemas |
282
- | ORM | SQLAlchemy 2.0 |
283
- | Tests | pytest |
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. **CLAUDE.md** is read by Claude Code at session start
291
- 2. **Rules** activate based on file paths you're editing
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
- Claude Code sees your conventions as first-class instructions, not just suggestions in the chat.
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 CLAUDE.md and .claude/rules/
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]/CLAUDE.md` start with `@../_shared/CLAUDE.md`
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,3 +1,8 @@
1
+ ---
2
+ description: "Shared coding conventions across all technologies"
3
+ alwaysApply: true
4
+ ---
5
+
1
6
  # Shared Conventions
2
7
 
3
8
  ## Code Quality
@@ -9,6 +9,8 @@ paths:
9
9
 
10
10
  Format: `type(scope): description`
11
11
 
12
+ **No `Co-Authored-By`** - Do not add Co-Authored-By trailers to commits.
13
+
12
14
  ```bash
13
15
  # Types
14
16
  feat # New feature
@@ -1,13 +1,11 @@
1
- # Python Conventions
2
-
3
- ## Activation
4
-
5
- ```yaml
1
+ ---
6
2
  paths:
7
3
  - "**/*.py"
8
4
  - "**/pyproject.toml"
9
5
  - "**/requirements*.txt"
10
- ```
6
+ ---
7
+
8
+ # Python Conventions
11
9
 
12
10
  ## Type Hints (Required)
13
11
 
@@ -1,6 +1,9 @@
1
- # Angular Project Guidelines
1
+ ---
2
+ description: "Angular 21+ project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
6
+ # Angular Project Guidelines
4
7
 
5
8
  ## Stack
6
9
 
@@ -1,6 +1,9 @@
1
- # .NET Project Guidelines
1
+ ---
2
+ description: ".NET 9 project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
6
+ # .NET Project Guidelines
4
7
 
5
8
  ## Stack
6
9
 
@@ -1,6 +1,9 @@
1
- # FastAPI Project Guidelines
1
+ ---
2
+ description: "FastAPI project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
6
+ # FastAPI Project Guidelines
4
7
 
5
8
  ## Stack
6
9
 
@@ -1,6 +1,9 @@
1
- # Flask Project Guidelines
1
+ ---
2
+ description: "Flask 3.0+ project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
6
+ # Flask Project Guidelines
4
7
 
5
8
  ## Stack
6
9
 
@@ -1,11 +1,9 @@
1
- # Flask Rules
2
-
3
- ## Activation
4
-
5
- ```yaml
1
+ ---
6
2
  paths:
7
3
  - "**/*.py"
8
- ```
4
+ ---
5
+
6
+ # Flask Rules
9
7
 
10
8
  ## Application Factory
11
9
 
@@ -1,11 +1,9 @@
1
- # Marshmallow Validation Rules
2
-
3
- ## Activation
4
-
5
- ```yaml
1
+ ---
6
2
  paths:
7
3
  - "**/*.py"
8
- ```
4
+ ---
5
+
6
+ # Marshmallow Validation Rules
9
7
 
10
8
  ## Schema Definition
11
9
 
@@ -1,6 +1,9 @@
1
- # NestJS Project Guidelines
1
+ ---
2
+ description: "NestJS 11+ project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
6
+ # NestJS Project Guidelines
4
7
 
5
8
  ## Stack
6
9
 
@@ -1,6 +1,9 @@
1
- # Next.js Project Guidelines
1
+ ---
2
+ description: "Next.js 15+ project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
2
5
 
3
- @../_shared/CLAUDE.md
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.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 readline from 'readline';
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 CLAUDE.md, settings.json, and tech rules (no shared skills/rules)
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 init nextjs --dry-run
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 techChoices = [
89
- { name: 'Angular', value: 'angular', description: 'Angular 21 + Nx + NgRx + Signals' },
90
- { name: 'Next.js', value: 'nextjs', description: 'Next.js 15 + React 19 + App Router' },
91
- { name: 'NestJS', value: 'nestjs', description: 'NestJS 11 + Prisma/TypeORM + Passport' },
92
- { name: '.NET', value: 'dotnet', description: '.NET 9 + ASP.NET Core + EF Core' },
93
- { name: 'FastAPI', value: 'fastapi', description: 'FastAPI + SQLAlchemy 2.0 + Pydantic v2' },
94
- { name: 'Flask', value: 'flask', description: 'Flask + SQLAlchemy 2.0 + Marshmallow' },
95
- ];
96
-
97
- const techs = await multiSelect('Select technologies:', techChoices);
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 extraChoices = [
105
- { name: 'Skills', value: 'skills', description: '/learning, /review, /spec, /debug, etc.' },
106
- { name: 'Shared Rules', value: 'rules', description: 'security, performance, accessibility' },
107
- ];
108
-
109
- const extras = await multiSelect('Include extras:', extraChoices);
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 prompt(`Target directory ${colors.dim('(. for current)')}: `) || '.';
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 { CONFIGS_DIR, AVAILABLE_TECHS, VERSION, getRulePathsToInclude, shouldIncludeRule } from './config.js';
5
- import { mergeClaudeMd, mergeSettingsJson, readManifest, writeManifest } from './merge.js';
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(` ${skills ? colors.green('✓') : colors.red('✗')} skills /learning, /review, /spec, /debug, and more`);
118
- console.log(` ${rules ? colors.green('✓') : colors.red('✗')} rules security, performance, accessibility`);
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(` ${colors.bold('Installed at:')} ${new Date(manifest.installedAt).toLocaleString()}`);
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(` ${colors.yellow('⚠')} Update available! Run ${colors.cyan('ai-rules update')} to update.`);
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
- path.join(targetDir, '.claude', 'settings.json'),
224
- settingsPath,
225
- { dryRun, backup, targetDir }
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
- rulesDir,
240
- path.join(targetDir, '.claude', 'rules', tech),
241
- { dryRun, backup, targetDir }
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
- techSkillsDir,
257
- path.join(targetDir, '.claude', 'skills'),
258
- { dryRun, backup, targetDir }
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
- skillsDir,
273
- path.join(targetDir, '.claude', 'skills'),
274
- { dryRun, backup, targetDir }
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);
@@ -36,10 +36,5 @@
36
36
  "backend": "domain/backend"
37
37
  }
38
38
  },
39
- "alwaysInclude": [
40
- "conventions",
41
- "quality",
42
- "security",
43
- "devops"
44
- ]
39
+ "alwaysInclude": ["conventions", "quality", "security", "devops"]
45
40
  }