@malamute/ai-rules 1.3.2 → 1.4.1

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,14 +62,14 @@ 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
 
@@ -100,9 +101,11 @@ Context-aware rules that activate based on file paths:
100
101
  ```markdown
101
102
  ---
102
103
  paths:
103
- - "**/*.component.ts"
104
+ - '**/*.component.ts'
104
105
  ---
106
+
105
107
  # Angular Component Rules
108
+
106
109
  - Use `ChangeDetectionStrategy.OnPush`
107
110
  - Use `input()`, `output()`, not decorators
108
111
  - Template in separate `.html` file
@@ -112,33 +115,35 @@ paths:
112
115
 
113
116
  Interactive workflows invoked with `/skill-name`:
114
117
 
115
- | Skill | Description |
116
- |-------|-------------|
117
- | `/learning` | Pedagogical mode — explains before coding |
118
- | `/review` | Code review with security/perf checklist |
119
- | `/debug` | Structured debugging workflow |
120
- | `/spec` | Write technical spec before implementing |
121
- | `/fix-issue` | Analyze GitHub issue and implement fix |
122
- | `/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
+ | `/sudden-death` | Kill indecision with rapid-fire questions |
125
+ | `/fix-issue` | Analyze GitHub issue and implement fix |
126
+ | `/generate-tests` | Generate comprehensive tests |
123
127
 
124
128
  <details>
125
- <summary><strong>See all 13 skills</strong></summary>
126
-
127
- | Skill | Usage | Description |
128
- |-------|-------|-------------|
129
- | `/learning` | `/learning nextjs` | Explains concepts before implementing |
130
- | `/review` | `/review src/users/` | Code review with checklist |
131
- | `/spec` | `/spec add auth` | Technical specification |
132
- | `/debug` | `/debug TypeError...` | Systematic debugging |
133
- | `/fix-issue` | `/fix-issue 123` | Fix GitHub issue |
134
- | `/review-pr` | `/review-pr 456` | Review pull request |
135
- | `/generate-tests` | `/generate-tests src/user.ts` | Generate tests |
136
- | `/api-endpoint` | `/api-endpoint POST /users` | Generate API endpoint |
137
- | `/migration` | `/migration add users` | Database migration |
138
- | `/security-audit` | `/security-audit` | Security analysis |
139
- | `/docker` | `/docker` | Dockerfile generation |
140
- | `/deploy` | `/deploy` | Deployment config |
141
- | `/explore` | `/explore` | Repository analysis |
129
+ <summary><strong>See all 14 skills</strong></summary>
130
+
131
+ | Skill | Usage | Description |
132
+ | ----------------- | ----------------------------- | ------------------------------------- |
133
+ | `/learning` | `/learning nextjs` | Explains concepts before implementing |
134
+ | `/review` | `/review src/users/` | Code review with checklist |
135
+ | `/spec` | `/spec add auth` | Technical specification |
136
+ | `/sudden-death` | `/sudden-death backend` | Kill indecision, get a verdict |
137
+ | `/debug` | `/debug TypeError...` | Systematic debugging |
138
+ | `/fix-issue` | `/fix-issue 123` | Fix GitHub issue |
139
+ | `/review-pr` | `/review-pr 456` | Review pull request |
140
+ | `/generate-tests` | `/generate-tests src/user.ts` | Generate tests |
141
+ | `/api-endpoint` | `/api-endpoint POST /users` | Generate API endpoint |
142
+ | `/migration` | `/migration add users` | Database migration |
143
+ | `/security-audit` | `/security-audit` | Security analysis |
144
+ | `/docker` | `/docker` | Dockerfile generation |
145
+ | `/deploy` | `/deploy` | Deployment config |
146
+ | `/explore` | `/explore` | Repository analysis |
142
147
 
143
148
  </details>
144
149
 
@@ -146,15 +151,15 @@ Interactive workflows invoked with `/skill-name`:
146
151
 
147
152
  Cross-framework rules included with `--with-rules`:
148
153
 
149
- | Rule | What It Covers |
150
- |------|----------------|
151
- | **security.md** | OWASP Top 10: injection, XSS, CSRF, secrets |
152
- | **performance.md** | N+1 queries, caching, lazy loading |
153
- | **accessibility.md** | WCAG 2.1, semantic HTML, ARIA |
154
- | **testing-patterns.md** | AAA pattern, mocking, coverage |
155
- | **error-handling.md** | Error categories, response formats |
156
- | **git.md** | Conventional commits, branching, PRs |
157
- | **observability.md** | Logging, metrics, tracing |
154
+ | Rule | What It Covers |
155
+ | ----------------------- | ------------------------------------------- |
156
+ | **security.md** | OWASP Top 10: injection, XSS, CSRF, secrets |
157
+ | **performance.md** | N+1 queries, caching, lazy loading |
158
+ | **accessibility.md** | WCAG 2.1, semantic HTML, ARIA |
159
+ | **testing-patterns.md** | AAA pattern, mocking, coverage |
160
+ | **error-handling.md** | Error categories, response formats |
161
+ | **git.md** | Conventional commits, branching, PRs |
162
+ | **observability.md** | Logging, metrics, tracing |
158
163
 
159
164
  ## Examples
160
165
 
@@ -162,10 +167,16 @@ Cross-framework rules included with `--with-rules`:
162
167
 
163
168
  ```bash
164
169
  # Angular frontend + NestJS backend
165
- ai-rules init angular nestjs --all
170
+ ai-rules init angular nestjs
166
171
 
167
172
  # Next.js frontend + FastAPI backend
168
- ai-rules init nextjs fastapi --all
173
+ ai-rules init nextjs fastapi
174
+
175
+ # Add a technology to existing installation
176
+ ai-rules add nestjs
177
+
178
+ # Minimal install (no skills/shared rules)
179
+ ai-rules init angular --minimal
169
180
  ```
170
181
 
171
182
  ### Preview Before Installing
@@ -175,6 +186,7 @@ ai-rules init angular --dry-run
175
186
  ```
176
187
 
177
188
  Output:
189
+
178
190
  ```
179
191
  DRY RUN - No files will be modified
180
192
 
@@ -207,77 +219,77 @@ ai-rules update
207
219
  <details>
208
220
  <summary><strong>Angular</strong></summary>
209
221
 
210
- | Aspect | Convention |
211
- |--------|------------|
212
- | Components | Standalone, OnPush change detection |
213
- | Signals | `input()`, `output()`, `model()` functions |
214
- | State | NgRx with Entity Adapter + Functional Effects |
215
- | Structure | Nx monorepo with feature/ui/data-access libs |
216
- | Tests | Vitest + Marble testing |
222
+ | Aspect | Convention |
223
+ | ---------- | --------------------------------------------- |
224
+ | Components | Standalone, OnPush change detection |
225
+ | Signals | `input()`, `output()`, `model()` functions |
226
+ | State | NgRx with Entity Adapter + Functional Effects |
227
+ | Structure | Nx monorepo with feature/ui/data-access libs |
228
+ | Tests | Vitest + Marble testing |
217
229
 
218
230
  </details>
219
231
 
220
232
  <details>
221
233
  <summary><strong>Next.js</strong></summary>
222
234
 
223
- | Aspect | Convention |
224
- |--------|------------|
225
- | Components | Server Components by default |
226
- | Client | `'use client'` directive for interactivity |
227
- | Data | Server Components + fetch, Server Actions |
228
- | State | Zustand (simple) / Redux Toolkit (complex) |
229
- | Structure | App Router with route groups |
235
+ | Aspect | Convention |
236
+ | ---------- | ------------------------------------------ |
237
+ | Components | Server Components by default |
238
+ | Client | `'use client'` directive for interactivity |
239
+ | Data | Server Components + fetch, Server Actions |
240
+ | State | Zustand (simple) / Redux Toolkit (complex) |
241
+ | Structure | App Router with route groups |
230
242
 
231
243
  </details>
232
244
 
233
245
  <details>
234
246
  <summary><strong>NestJS</strong></summary>
235
247
 
236
- | Aspect | Convention |
237
- |--------|------------|
238
- | Architecture | Modular Monolith |
239
- | Validation | class-validator + class-transformer |
240
- | Database | Prisma (modern) / TypeORM (decorators) |
241
- | Auth | Passport + JWT |
242
- | Tests | Vitest + Supertest |
248
+ | Aspect | Convention |
249
+ | ------------ | -------------------------------------- |
250
+ | Architecture | Modular Monolith |
251
+ | Validation | class-validator + class-transformer |
252
+ | Database | Prisma (modern) / TypeORM (decorators) |
253
+ | Auth | Passport + JWT |
254
+ | Tests | Vitest + Supertest |
243
255
 
244
256
  </details>
245
257
 
246
258
  <details>
247
259
  <summary><strong>.NET</strong></summary>
248
260
 
249
- | Aspect | Convention |
250
- |--------|------------|
261
+ | Aspect | Convention |
262
+ | ------------ | ----------------------------------------- |
251
263
  | Architecture | Clean Architecture (Domain → App → Infra) |
252
- | API | Minimal APIs (preferred) or Controllers |
253
- | CQRS | MediatR for Commands/Queries |
254
- | ORM | Entity Framework Core |
255
- | Tests | xUnit + NSubstitute + FluentAssertions |
264
+ | API | Minimal APIs (preferred) or Controllers |
265
+ | CQRS | MediatR for Commands/Queries |
266
+ | ORM | Entity Framework Core |
267
+ | Tests | xUnit + NSubstitute + FluentAssertions |
256
268
 
257
269
  </details>
258
270
 
259
271
  <details>
260
272
  <summary><strong>FastAPI</strong></summary>
261
273
 
262
- | Aspect | Convention |
263
- |--------|------------|
264
- | Framework | FastAPI with async/await |
265
- | Validation | Pydantic v2 |
266
- | ORM | SQLAlchemy 2.0 with async support |
267
- | Tests | pytest + httpx |
268
- | Migrations | Alembic |
274
+ | Aspect | Convention |
275
+ | ---------- | --------------------------------- |
276
+ | Framework | FastAPI with async/await |
277
+ | Validation | Pydantic v2 |
278
+ | ORM | SQLAlchemy 2.0 with async support |
279
+ | Tests | pytest + httpx |
280
+ | Migrations | Alembic |
269
281
 
270
282
  </details>
271
283
 
272
284
  <details>
273
285
  <summary><strong>Flask</strong></summary>
274
286
 
275
- | Aspect | Convention |
276
- |--------|------------|
277
- | Framework | Flask 3.0 with Application Factory |
278
- | Validation | Marshmallow schemas |
279
- | ORM | SQLAlchemy 2.0 |
280
- | Tests | pytest |
287
+ | Aspect | Convention |
288
+ | ---------- | --------------------------------------------------- |
289
+ | Framework | Flask 3.0 with Application Factory |
290
+ | Validation | Marshmallow schemas |
291
+ | ORM | SQLAlchemy 2.0 |
292
+ | Tests | pytest |
281
293
  | Extensions | Flask-SQLAlchemy, Flask-Migrate, Flask-JWT-Extended |
282
294
 
283
295
  </details>
@@ -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
 
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: sudden-death
3
+ description: Kill indecision with rapid-fire questionnaires
4
+ argument-hint: [decision-topic]
5
+ ---
6
+
7
+ # Sudden Death Mode
8
+
9
+ You are now in **sudden death mode**. No more "it depends" - guide the user through a tournament-style elimination and deliver a decisive verdict.
10
+
11
+ **IMPORTANT: Always respond in the user's language.** If they write in French, respond in French. Polish? Polish. The examples below are in French for flavor, but adapt to the user.
12
+
13
+ ## Input
14
+
15
+ Decision topic: `$ARGUMENTS`
16
+
17
+ If no argument provided, ask: "What's on the chopping block? (e.g., backend stack, database, UI library, hosting)"
18
+
19
+ ## The Game
20
+
21
+ ### Phase 1: Candidates
22
+
23
+ List all reasonable options for the domain. Example for backend:
24
+
25
+ ```
26
+ Candidats: NestJS, Hono, Fastify, Elysia, AdonisJS, .NET, FastAPI, Go
27
+
28
+ En garde. Première question...
29
+ ```
30
+
31
+ ### Phase 2: Elimination Tournament
32
+
33
+ Ask **5-8 killer questions**. Each question should potentially eliminate candidates.
34
+
35
+ Format:
36
+ ```
37
+ ### Q1: [Short punchy question]
38
+ [Context if needed]
39
+
40
+ → User answers
41
+ → **Eliminated: [X, Y]** or **Advantage: [Z]** or **Point: [Z]**
42
+ ```
43
+
44
+ Example questions (backend stack):
45
+ - "Full TypeScript (front + back) or ok to switch languages?"
46
+ - "Structured framework (modules, DI, conventions) or minimal?"
47
+ - "Decorators (@Controller, @Get) or simple functions?"
48
+ - "Batteries included or pick your own libs?"
49
+ - "Big community or cutting-edge?"
50
+
51
+ **Elimination rules:**
52
+ - Strong preference → Eliminate mismatches immediately
53
+ - Slight preference → Note advantage, keep in race
54
+ - "Tie" / "Both good" → No elimination, move on
55
+
56
+ ### Phase 3: Final Showdown
57
+
58
+ When down to 2-3 candidates:
59
+
60
+ ```
61
+ ### Finale: [A] vs [B]
62
+
63
+ [A]: [2-3 key traits]
64
+ [B]: [2-3 key traits]
65
+ ```
66
+
67
+ If clear winner → Declare it
68
+ If tie → Go to tiebreaker
69
+
70
+ ### Phase 4: Tiebreaker (when needed)
71
+
72
+ Frame it as a **character choice**, not just technical:
73
+
74
+ ```
75
+ Score: [A] 2 - [B] 2
76
+
77
+ Tu as choisi [previous bold choice] pour sortir de ta zone.
78
+ [A] = full send, nouvelle expérience
79
+ [B] = un pied dans le connu
80
+
81
+ On est des fous ou pas ?
82
+ ```
83
+
84
+ ### Phase 5: Verdict
85
+
86
+ ```
87
+ ### Winner: **[Option]**
88
+
89
+ [One-liner why it fits THEIR specific answers]
90
+ ```
91
+
92
+ ## Between Decisions
93
+
94
+ After each major decision, recap and offer options:
95
+
96
+ ```
97
+ Stack actuelle:
98
+ - Frontend: Next.js 15
99
+ - Backend: AdonisJS
100
+ - ORM: Lucid
101
+
102
+ On continue ? Il reste:
103
+ - UI lib (shadcn, autre ?)
104
+ - State management
105
+ - Hosting
106
+
107
+ **Sudden death** ou **tu tranches direct** ?
108
+ ```
109
+
110
+ - **Sudden death** = Full questionnaire
111
+ - **Tu tranches direct** = User is confident, give quick recommendation
112
+
113
+ ## Tone
114
+
115
+ - **Playful combat** - "En garde", "Eliminated", "Survivor"
116
+ - **Call out bold choices** - "On est des fous !", "Allez on y va !"
117
+ - **No corporate speak** - Skip the "it depends on your requirements"
118
+ - **Quick and punchy** - Short questions, fast eliminations
119
+ - **Celebrate decisions** - Each choice is a win, not a compromise
120
+
121
+ ## Quick Verdict Mode
122
+
123
+ If user says "tu tranches" or wants fast advice:
124
+
125
+ ```
126
+ Pour [context], je dirais **[Option]**.
127
+
128
+ [One sentence why]
129
+
130
+ Sold ? Ou on fait un sudden death pour être sûr ?
131
+ ```
132
+
133
+ ## Adapt to Domain
134
+
135
+ Common sudden death topics:
136
+
137
+ | Domain | Typical Candidates |
138
+ |--------|-------------------|
139
+ | Backend | NestJS, Fastify, Hono, AdonisJS, .NET, FastAPI, Go |
140
+ | Frontend | Next.js, Nuxt, SvelteKit, Remix, Angular |
141
+ | Database | PostgreSQL, MySQL, MongoDB, SQLite, Supabase, PlanetScale |
142
+ | ORM | Prisma, Drizzle, TypeORM, Lucid, SQLAlchemy |
143
+ | UI | shadcn/ui, Radix, Chakra, MUI, Mantine |
144
+ | Hosting | Vercel, Railway, Render, Fly.io, AWS, Coolify |
145
+ | State | Zustand, Jotai, Redux Toolkit, Signals, TanStack Query |
146
+ | Auth | Auth.js, Lucia, Clerk, Supabase Auth, custom JWT |
147
+
148
+ For unknown domains, identify the key trade-offs and build questions on the fly.
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malamute/ai-rules",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
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,7 +1,13 @@
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';
4
+ import {
5
+ CONFIGS_DIR,
6
+ AVAILABLE_TECHS,
7
+ VERSION,
8
+ getRulePathsToInclude,
9
+ shouldIncludeRule,
10
+ } from './config.js';
5
11
  import { mergeSettingsJson, readManifest, writeManifest } from './merge.js';
6
12
 
7
13
  /**
@@ -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
 
@@ -199,11 +213,11 @@ export function init(techs, options) {
199
213
 
200
214
  const settingsPath = path.join(techDir, 'settings.json');
201
215
  if (fs.existsSync(settingsPath)) {
202
- const op = mergeSettingsJson(
203
- path.join(targetDir, '.claude', 'settings.json'),
204
- settingsPath,
205
- { dryRun, backup, targetDir }
206
- );
216
+ const op = mergeSettingsJson(path.join(targetDir, '.claude', 'settings.json'), settingsPath, {
217
+ dryRun,
218
+ backup,
219
+ targetDir,
220
+ });
207
221
  operations.push(op);
208
222
 
209
223
  if (dryRun) {
@@ -215,11 +229,11 @@ export function init(techs, options) {
215
229
 
216
230
  const rulesDir = path.join(techDir, 'rules');
217
231
  if (fs.existsSync(rulesDir)) {
218
- const ops = copyDirRecursive(
219
- rulesDir,
220
- path.join(targetDir, '.claude', 'rules', tech),
221
- { dryRun, backup, targetDir }
222
- );
232
+ const ops = copyDirRecursive(rulesDir, path.join(targetDir, '.claude', 'rules', tech), {
233
+ dryRun,
234
+ backup,
235
+ targetDir,
236
+ });
223
237
  operations.push(...ops);
224
238
 
225
239
  if (dryRun) {
@@ -232,11 +246,11 @@ export function init(techs, options) {
232
246
  if (options.withSkills) {
233
247
  const techSkillsDir = path.join(techDir, 'skills');
234
248
  if (fs.existsSync(techSkillsDir)) {
235
- const ops = copySkillsToTarget(
236
- techSkillsDir,
237
- path.join(targetDir, '.claude', 'skills'),
238
- { dryRun, backup, targetDir }
239
- );
249
+ const ops = copySkillsToTarget(techSkillsDir, path.join(targetDir, '.claude', 'skills'), {
250
+ dryRun,
251
+ backup,
252
+ targetDir,
253
+ });
240
254
  operations.push(...ops);
241
255
  }
242
256
  }
@@ -248,11 +262,11 @@ export function init(techs, options) {
248
262
  log.info(`${dryRun ? 'Would install' : 'Installing'} skills...`);
249
263
  const skillsDir = path.join(sharedDir, 'skills');
250
264
  if (fs.existsSync(skillsDir)) {
251
- const ops = copySkillsToTarget(
252
- skillsDir,
253
- path.join(targetDir, '.claude', 'skills'),
254
- { dryRun, backup, targetDir }
255
- );
265
+ const ops = copySkillsToTarget(skillsDir, path.join(targetDir, '.claude', 'skills'), {
266
+ dryRun,
267
+ backup,
268
+ targetDir,
269
+ });
256
270
  operations.push(...ops);
257
271
 
258
272
  if (dryRun) {
@@ -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
  }