@malamute/ai-rules 1.4.0 → 1.5.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.
@@ -0,0 +1,170 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.ts"
4
+ ---
5
+
6
+ # AdonisJS Testing (Japa)
7
+
8
+ ## Test Structure
9
+
10
+ ```
11
+ tests/
12
+ ├── bootstrap.ts # Test setup
13
+ ├── unit/
14
+ │ └── services/
15
+ ├── functional/
16
+ │ └── controllers/
17
+ └── integration/
18
+ ```
19
+
20
+ ## Unit Test
21
+
22
+ ```typescript
23
+ import { test } from '@japa/runner'
24
+ import UserService from '#services/user_service'
25
+
26
+ test.group('UserService', () => {
27
+ test('creates a user with valid data', async ({ assert }) => {
28
+ const service = new UserService()
29
+ const user = await service.create({
30
+ email: 'test@example.com',
31
+ password: 'password123',
32
+ })
33
+
34
+ assert.exists(user.id)
35
+ assert.equal(user.email, 'test@example.com')
36
+ })
37
+ })
38
+ ```
39
+
40
+ ## Functional Test (HTTP)
41
+
42
+ ```typescript
43
+ import { test } from '@japa/runner'
44
+ import { UserFactory } from '#database/factories/user_factory'
45
+
46
+ test.group('Users Controller', (group) => {
47
+ group.each.setup(async () => {
48
+ // Runs before each test
49
+ await Database.beginGlobalTransaction()
50
+ return () => Database.rollbackGlobalTransaction()
51
+ })
52
+
53
+ test('list all users', async ({ client, assert }) => {
54
+ await UserFactory.createMany(3)
55
+
56
+ const response = await client.get('/users')
57
+
58
+ response.assertStatus(200)
59
+ assert.lengthOf(response.body(), 3)
60
+ })
61
+
62
+ test('create a user', async ({ client }) => {
63
+ const response = await client.post('/users').json({
64
+ email: 'new@example.com',
65
+ password: 'password123',
66
+ name: 'New User',
67
+ })
68
+
69
+ response.assertStatus(201)
70
+ response.assertBodyContains({ email: 'new@example.com' })
71
+ })
72
+
73
+ test('requires authentication', async ({ client }) => {
74
+ const response = await client.get('/profile')
75
+ response.assertStatus(401)
76
+ })
77
+ })
78
+ ```
79
+
80
+ ## Authenticated Requests
81
+
82
+ ```typescript
83
+ test('authenticated user can view profile', async ({ client }) => {
84
+ const user = await UserFactory.create()
85
+
86
+ const response = await client
87
+ .get('/profile')
88
+ .loginAs(user)
89
+
90
+ response.assertStatus(200)
91
+ response.assertBodyContains({ id: user.id })
92
+ })
93
+ ```
94
+
95
+ ## Database Helpers
96
+
97
+ ```typescript
98
+ import Database from '@adonisjs/lucid/services/db'
99
+ import { test } from '@japa/runner'
100
+
101
+ test.group('Database tests', (group) => {
102
+ // Transaction per test (auto rollback)
103
+ group.each.setup(async () => {
104
+ await Database.beginGlobalTransaction()
105
+ return () => Database.rollbackGlobalTransaction()
106
+ })
107
+
108
+ // Or truncate tables
109
+ group.each.setup(async () => {
110
+ await Database.truncate('users')
111
+ })
112
+ })
113
+ ```
114
+
115
+ ## Assertions
116
+
117
+ ```typescript
118
+ test('response assertions', async ({ client, assert }) => {
119
+ const response = await client.get('/users/1')
120
+
121
+ // Status
122
+ response.assertStatus(200)
123
+ response.assertStatus(201)
124
+ response.assertStatus(404)
125
+
126
+ // Body
127
+ response.assertBody({ id: 1, name: 'John' })
128
+ response.assertBodyContains({ name: 'John' })
129
+
130
+ // Headers
131
+ response.assertHeader('content-type', 'application/json')
132
+
133
+ // Custom assertions
134
+ assert.equal(response.body().email, 'test@example.com')
135
+ assert.lengthOf(response.body().users, 3)
136
+ assert.isTrue(response.body().isActive)
137
+ })
138
+ ```
139
+
140
+ ## Mocking
141
+
142
+ ```typescript
143
+ import { test } from '@japa/runner'
144
+ import mail from '@adonisjs/mail/services/main'
145
+
146
+ test('sends welcome email on registration', async ({ client, assert }) => {
147
+ const { mails } = mail.fake()
148
+
149
+ await client.post('/auth/register').json({
150
+ email: 'test@example.com',
151
+ password: 'password123',
152
+ })
153
+
154
+ assert.lengthOf(mails.sent, 1)
155
+ mails.assertSent(WelcomeMail, (mail) => {
156
+ return mail.to[0].address === 'test@example.com'
157
+ })
158
+
159
+ mail.restore()
160
+ })
161
+ ```
162
+
163
+ ## Running Tests
164
+
165
+ ```bash
166
+ node ace test # All tests
167
+ node ace test --files="tests/functional/**"
168
+ node ace test --tags="@slow"
169
+ node ace test --watch # Watch mode
170
+ ```
@@ -0,0 +1,130 @@
1
+ ---
2
+ paths:
3
+ - "app/validators/**/*.ts"
4
+ ---
5
+
6
+ # VineJS Validation
7
+
8
+ ## Basic Validator
9
+
10
+ ```typescript
11
+ import vine from '@vinejs/vine'
12
+
13
+ export const createUserValidator = vine.compile(
14
+ vine.object({
15
+ email: vine.string().email().normalizeEmail(),
16
+ password: vine.string().minLength(8),
17
+ name: vine.string().minLength(2).maxLength(100),
18
+ })
19
+ )
20
+
21
+ export const updateUserValidator = vine.compile(
22
+ vine.object({
23
+ email: vine.string().email().normalizeEmail().optional(),
24
+ name: vine.string().minLength(2).maxLength(100).optional(),
25
+ })
26
+ )
27
+ ```
28
+
29
+ ## Common Rules
30
+
31
+ ```typescript
32
+ vine.object({
33
+ // Strings
34
+ name: vine.string().minLength(2).maxLength(100),
35
+ email: vine.string().email(),
36
+ url: vine.string().url(),
37
+ slug: vine.string().regex(/^[a-z0-9-]+$/),
38
+
39
+ // Numbers
40
+ age: vine.number().min(0).max(150),
41
+ price: vine.number().positive().decimal(2),
42
+
43
+ // Booleans
44
+ isActive: vine.boolean(),
45
+
46
+ // Dates
47
+ birthDate: vine.date({ formats: ['YYYY-MM-DD'] }),
48
+
49
+ // Arrays
50
+ tags: vine.array(vine.string()).minLength(1).maxLength(10),
51
+
52
+ // Enums
53
+ status: vine.enum(['draft', 'published', 'archived']),
54
+
55
+ // Optional & Nullable
56
+ bio: vine.string().optional(),
57
+ deletedAt: vine.date().nullable(),
58
+ })
59
+ ```
60
+
61
+ ## Custom Error Messages
62
+
63
+ ```typescript
64
+ export const createUserValidator = vine.compile(
65
+ vine.object({
66
+ email: vine.string().email(),
67
+ password: vine.string().minLength(8),
68
+ })
69
+ )
70
+
71
+ createUserValidator.messagesProvider = new SimpleMessagesProvider({
72
+ 'email.required': 'Email is required',
73
+ 'email.email': 'Invalid email format',
74
+ 'password.minLength': 'Password must be at least 8 characters',
75
+ })
76
+ ```
77
+
78
+ ## Unique Validation
79
+
80
+ ```typescript
81
+ import vine from '@vinejs/vine'
82
+ import { FieldContext } from '@vinejs/vine/types'
83
+ import User from '#models/user'
84
+
85
+ const uniqueEmail = vine.createRule(async (value: unknown, _options: undefined, field: FieldContext) => {
86
+ if (typeof value !== 'string') return
87
+
88
+ const user = await User.findBy('email', value)
89
+ if (user) {
90
+ field.report('Email already exists', 'unique', field)
91
+ }
92
+ })
93
+
94
+ export const createUserValidator = vine.compile(
95
+ vine.object({
96
+ email: vine.string().email().use(uniqueEmail()),
97
+ })
98
+ )
99
+ ```
100
+
101
+ ## Conditional Validation
102
+
103
+ ```typescript
104
+ vine.object({
105
+ accountType: vine.enum(['personal', 'business']),
106
+ companyName: vine
107
+ .string()
108
+ .minLength(2)
109
+ .requiredWhen('accountType', '=', 'business'),
110
+ taxId: vine
111
+ .string()
112
+ .optional()
113
+ .requiredWhen('accountType', '=', 'business'),
114
+ })
115
+ ```
116
+
117
+ ## Usage in Controller
118
+
119
+ ```typescript
120
+ import { createUserValidator } from '#validators/user'
121
+
122
+ export default class UsersController {
123
+ async store({ request, response }: HttpContext) {
124
+ const payload = await request.validateUsing(createUserValidator)
125
+ // payload is fully typed: { email: string, password: string, name: string }
126
+ const user = await User.create(payload)
127
+ return response.created(user)
128
+ }
129
+ }
130
+ ```
@@ -0,0 +1,34 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node ace serve --watch)",
5
+ "Bash(node ace build)",
6
+ "Bash(node ace test*)",
7
+ "Bash(node ace make:*)",
8
+ "Bash(node ace migration:*)",
9
+ "Bash(node ace db:*)",
10
+ "Bash(node ace generate:*)",
11
+ "Bash(node ace list)",
12
+ "Bash(npm run dev)",
13
+ "Bash(npm run build)",
14
+ "Bash(npm run test*)",
15
+ "Bash(npm run lint*)",
16
+ "Bash(npm install *)",
17
+ "Bash(npm ci)",
18
+ "Read",
19
+ "Edit",
20
+ "Write"
21
+ ],
22
+ "deny": [
23
+ "Bash(git push *)",
24
+ "Bash(git push)",
25
+ "Bash(rm -rf *)",
26
+ "Read(.env)",
27
+ "Read(.env.*)",
28
+ "Read(**/secrets/**)"
29
+ ]
30
+ },
31
+ "env": {
32
+ "NODE_ENV": "development"
33
+ }
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malamute/ai-rules",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",
@@ -23,6 +23,10 @@
23
23
  "flask": {
24
24
  "type": "backend",
25
25
  "language": "python"
26
+ },
27
+ "adonisjs": {
28
+ "type": "backend",
29
+ "language": "typescript"
26
30
  }
27
31
  },
28
32
  "ruleMapping": {