@jaypie/mcp 0.7.4 → 0.7.7

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,166 @@
1
+ ---
2
+ description: Initialize a Jaypie monorepo project
3
+ related: subpackage, cicd, style, tests
4
+ ---
5
+
6
+ # Jaypie Monorepo Setup
7
+
8
+ Initialize a new monorepo using Jaypie conventions and utilities.
9
+
10
+ ## Overview
11
+
12
+ - ESLint 9+ flat config with @jaypie/eslint
13
+ - NPM with Workspaces ("monorepo")
14
+ - TypeScript with ESM modules
15
+ - Vite for building, Vitest for testing
16
+ - Node.js 22, 24, 25 support
17
+
18
+ ## Process
19
+
20
+ 1. Create root configuration files
21
+ 2. Install dev dependencies
22
+ 3. Configure workspaces
23
+
24
+ ## Root Files
25
+
26
+ ### package.json
27
+
28
+ ```json
29
+ {
30
+ "name": "@project-org/monorepo",
31
+ "version": "0.0.1",
32
+ "private": true,
33
+ "type": "module",
34
+ "workspaces": [
35
+ "packages/*"
36
+ ],
37
+ "scripts": {
38
+ "build": "npm run build --workspaces --if-present",
39
+ "clean": "rimraf ./packages/*/dist",
40
+ "format": "npm run format:package && npm run format:lint",
41
+ "format:lint": "eslint --fix .",
42
+ "format:package": "sort-package-json ./package.json ./packages/*/package.json",
43
+ "lint": "eslint --quiet .",
44
+ "test": "vitest run",
45
+ "typecheck": "npm run typecheck --workspaces --if-present"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### eslint.config.mjs
51
+
52
+ ```javascript
53
+ export { default } from "@jaypie/eslint";
54
+ ```
55
+
56
+ For projects needing custom rules:
57
+
58
+ ```javascript
59
+ import jaypie from "@jaypie/eslint";
60
+
61
+ export default [
62
+ ...jaypie,
63
+ {
64
+ ignores: ["LOCAL/**"],
65
+ },
66
+ ];
67
+ ```
68
+
69
+ ### tsconfig.json (root)
70
+
71
+ ```json
72
+ {
73
+ "compilerOptions": {
74
+ "target": "ES2020",
75
+ "module": "ESNext",
76
+ "moduleResolution": "bundler",
77
+ "declaration": true,
78
+ "strict": true,
79
+ "esModuleInterop": true,
80
+ "skipLibCheck": true,
81
+ "forceConsistentCasingInFileNames": true
82
+ },
83
+ "exclude": ["node_modules", "dist"]
84
+ }
85
+ ```
86
+
87
+ ### vitest.workspace.ts
88
+
89
+ ```typescript
90
+ export default ["packages/*/vitest.config.{ts,js}"];
91
+ ```
92
+
93
+ ### .gitignore
94
+
95
+ ```
96
+ .DS_Store
97
+ node_modules
98
+ dist
99
+
100
+ # Local env files
101
+ .env
102
+ .env.local
103
+ .env.*.local
104
+
105
+ # Log files
106
+ npm-debug.log*
107
+
108
+ # Editor directories
109
+ .idea
110
+ *.sw?
111
+
112
+ # Build artifacts
113
+ *.tsbuildinfo
114
+ ```
115
+
116
+ ### .vscode/settings.json
117
+
118
+ ```json
119
+ {
120
+ "editor.formatOnSave": true,
121
+ "editor.codeActionsOnSave": {
122
+ "source.fixAll.eslint": "explicit"
123
+ },
124
+ "typescript.preferences.importModuleSpecifier": "relative"
125
+ }
126
+ ```
127
+
128
+ ## Installation
129
+
130
+ Install root dev dependencies:
131
+
132
+ ```bash
133
+ npm install --save-dev @jaypie/eslint @jaypie/testkit eslint rimraf sort-package-json tsx vite vite-plugin-dts vitest
134
+ ```
135
+
136
+ ## Workspace Conventions
137
+
138
+ | Directory | Purpose |
139
+ |-----------|---------|
140
+ | `packages/` | npm packages (default workspace) |
141
+ | `stacks/` | CDK-deployed infrastructure and sites |
142
+
143
+ ## Scripts Reference
144
+
145
+ | Script | Top-level | Package-level |
146
+ |--------|-----------|---------------|
147
+ | `build` | `npm run build --workspaces` | `vite build` |
148
+ | `clean` | `rimraf ./packages/*/dist` | `rimraf dist` |
149
+ | `format` | `eslint --fix .` | `eslint --fix` |
150
+ | `format:package` | `sort-package-json ./package.json ./packages/*/package.json` | `sort-package-json` |
151
+ | `lint` | `eslint --quiet .` | `eslint` |
152
+ | `test` | `vitest run` | `vitest run` |
153
+ | `typecheck` | `npm run typecheck --workspaces` | `tsc --noEmit` |
154
+
155
+ ## Guidelines
156
+
157
+ - Run `npm install` to generate package-lock.json (do not hard-code versions)
158
+ - Use `"version": "0.0.1"`, `"type": "module"`, and `"private": true` for new packages
159
+ - Do not include authors, keywords, or external links in package.json
160
+ - If this is the first commit, commit directly to main; otherwise create a branch
161
+
162
+ ## Next Steps
163
+
164
+ - `skill("subpackage")` - Create packages within the monorepo
165
+ - `skill("cicd")` - Add GitHub Actions workflows
166
+ - `skill("tests")` - Testing patterns with Vitest
package/skills/secrets.md CHANGED
@@ -5,190 +5,188 @@ related: aws, cdk, variables
5
5
 
6
6
  # Secret Management
7
7
 
8
- Jaypie uses AWS Secrets Manager for secure credential storage, with environment-aware resolution that works seamlessly in both local development and Lambda.
8
+ Jaypie uses AWS Secrets Manager for secure credential storage. The `JaypieEnvSecret` construct creates secrets at deploy time from environment variables, and `getEnvSecret` retrieves them at runtime.
9
9
 
10
- ## Basic Usage
10
+ ## The Pattern
11
11
 
12
- Use `getEnvSecret` for environment-aware secret resolution:
12
+ 1. **Deploy time**: Set environment variables in CI/CD (e.g., `MONGODB_URI=mongodb+srv://...`)
13
+ 2. **CDK**: `JaypieEnvSecret` reads the env var and creates/updates an AWS secret
14
+ 3. **Runtime**: `getEnvSecret("MONGODB_URI")` fetches from Secrets Manager
15
+
16
+ This keeps secrets out of code and config files while enabling environment-specific values.
17
+
18
+ ## CDK: Creating Secrets with JaypieEnvSecret
19
+
20
+ The simplest pattern uses the environment variable name as the construct ID:
13
21
 
14
22
  ```typescript
15
- import { getEnvSecret } from "jaypie";
23
+ import { JaypieEnvSecret, JaypieLambda } from "@jaypie/constructs";
16
24
 
17
- const apiKey = await getEnvSecret("ANTHROPIC_API_KEY");
18
- const dbUri = await getEnvSecret("MONGODB_URI");
25
+ // Creates secret from process.env.MONGODB_URI at deploy time
26
+ const mongoSecret = new JaypieEnvSecret(this, "MONGODB_URI");
27
+ const anthropicSecret = new JaypieEnvSecret(this, "ANTHROPIC_API_KEY");
28
+
29
+ // Lambda with secrets array (auto-creates JaypieEnvSecret instances)
30
+ new JaypieLambda(this, "Handler", {
31
+ code: "dist/lambda",
32
+ handler: "index.handler",
33
+ secrets: ["MONGODB_URI", "ANTHROPIC_API_KEY"],
34
+ });
19
35
  ```
20
36
 
21
- ### How getEnvSecret Works
37
+ When the construct ID matches an environment variable name, `JaypieEnvSecret` automatically:
38
+ - Uses that env var's value as the secret content
39
+ - Sets `envKey` to the ID for later reference
22
40
 
23
- `getEnvSecret` checks environment variables in order:
41
+ ### CI/CD Setup
24
42
 
25
- 1. `SECRET_{name}` - If found, fetches from AWS Secrets Manager
26
- 2. `{name}_SECRET` - If found, fetches from AWS Secrets Manager
27
- 3. `{name}` - Returns direct value without AWS call
43
+ Set secrets as environment variables in your deployment pipeline:
28
44
 
29
- This allows the same code to work locally (with direct env values) and in Lambda (with secret references).
45
+ ```yaml
46
+ # GitHub Actions example
47
+ jobs:
48
+ deploy:
49
+ env:
50
+ MONGODB_URI: ${{ secrets.MONGODB_URI }}
51
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
52
+ steps:
53
+ - run: npx cdk deploy
54
+ ```
30
55
 
31
- ## Loading Multiple Secrets
56
+ ## Runtime: Retrieving Secrets
32
57
 
33
- Use `loadEnvSecrets` during handler initialization:
58
+ Use `getEnvSecret` to fetch secrets in Lambda:
34
59
 
35
60
  ```typescript
36
- import { loadEnvSecrets } from "jaypie";
37
-
38
- // Load secrets and set in process.env
39
- await loadEnvSecrets("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "MONGODB_URI");
61
+ import { getEnvSecret } from "jaypie";
40
62
 
41
- // Now available as process.env.ANTHROPIC_API_KEY, etc.
63
+ const mongoUri = await getEnvSecret("MONGODB_URI");
64
+ const apiKey = await getEnvSecret("ANTHROPIC_API_KEY");
42
65
  ```
43
66
 
44
- ## CDK Configuration
67
+ ### Loading Multiple Secrets
45
68
 
46
- Reference secrets via environment variables in CDK:
69
+ Use `loadEnvSecrets` during handler initialization to populate `process.env`:
47
70
 
48
71
  ```typescript
49
- const handler = new JaypieLambda(this, "Handler", {
50
- environment: {
51
- // SECRET_ prefix triggers AWS Secrets Manager fetch
52
- SECRET_MONGODB_URI: "my-project/mongodb-uri",
53
- SECRET_API_KEY: "my-project/third-party-api-key",
54
- },
55
- });
56
- ```
72
+ import { loadEnvSecrets } from "jaypie";
57
73
 
58
- In code:
74
+ await loadEnvSecrets("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "MONGODB_URI");
59
75
 
60
- ```typescript
61
- // getEnvSecret sees SECRET_MONGODB_URI and fetches from Secrets Manager
62
- const mongoUri = await getEnvSecret("MONGODB_URI");
76
+ // Now available as process.env.ANTHROPIC_API_KEY, etc.
63
77
  ```
64
78
 
65
- ## Direct Secret Access
79
+ ## Provider/Consumer Pattern
66
80
 
67
- Use `getSecret` when you need to fetch by exact AWS secret name:
81
+ For shared secrets across environments (e.g., sandbox providing to personal builds):
68
82
 
69
83
  ```typescript
70
- import { getSecret } from "jaypie";
84
+ // Sandbox stack (provider) - exports the secret name
85
+ new JaypieEnvSecret(this, "SHARED_API_KEY", { provider: true });
71
86
 
72
- // Fetch by exact AWS secret name
73
- const secret = await getSecret("my-project/production/api-key");
87
+ // Personal build (consumer) - imports from sandbox
88
+ new JaypieEnvSecret(this, "SHARED_API_KEY"); // consumer auto-detected
74
89
  ```
75
90
 
76
- Note: `getSecret` requires `AWS_SESSION_TOKEN` and always calls Secrets Manager. Prefer `getEnvSecret` for typical use cases.
91
+ The construct auto-detects consumer mode for personal/ephemeral environments (`PROJECT_ENV=personal` or `CDK_ENV_PERSONAL=true`).
77
92
 
78
- ## Creating Secrets
93
+ ## Generated Secrets
79
94
 
80
- ### Via CDK
95
+ For secrets without a source value (e.g., database passwords):
81
96
 
82
97
  ```typescript
83
- import { Secret } from "aws-cdk-lib/aws-secretsmanager";
84
-
85
- const secret = new Secret(this, "ApiKey", {
86
- secretName: `${projectKey}/api-key`,
87
- description: "Third-party API key",
98
+ new JaypieEnvSecret(this, "DB_PASSWORD", {
99
+ generateSecretString: {
100
+ excludePunctuation: true,
101
+ passwordLength: 32,
102
+ },
88
103
  });
89
-
90
- // Grant read access
91
- secret.grantRead(lambdaFunction);
92
104
  ```
93
105
 
94
- ### Via AWS CLI
106
+ ## Tagging
95
107
 
96
- ```bash
97
- aws secretsmanager create-secret \
98
- --name "my-project/api-key" \
99
- --secret-string "sk_live_abc123"
108
+ Apply standard tags for organization:
109
+
110
+ ```typescript
111
+ new JaypieEnvSecret(this, "STRIPE_KEY", {
112
+ roleTag: CDK.ROLE.PAYMENT,
113
+ vendorTag: CDK.VENDOR.STRIPE,
114
+ });
100
115
  ```
101
116
 
102
- ## Secret Naming Convention
117
+ ## Local Development
103
118
 
104
- Use project-prefixed names:
119
+ For local development, set environment variables directly in `.env.local`:
105
120
 
121
+ ```bash
122
+ # .env.local (not committed)
123
+ ANTHROPIC_API_KEY=sk-ant-test123
124
+ MONGODB_URI=mongodb://localhost:27017/dev
106
125
  ```
107
- {project-key}/{secret-name}
108
126
 
109
- Examples:
110
- - my-api/mongodb-uri
111
- - my-api/stripe-key
112
- - my-api/auth0-secret
113
- ```
127
+ `getEnvSecret` returns these values directly without AWS calls when no `SECRET_` prefix is present.
114
128
 
115
- ## JSON Secrets
129
+ ## Alternative Approaches
116
130
 
117
- Store structured data:
131
+ ### Explicit Value
118
132
 
119
- ```bash
120
- aws secretsmanager create-secret \
121
- --name "my-project/db-credentials" \
122
- --secret-string '{"username":"admin","password":"secret123"}'
133
+ Pass a value directly instead of reading from environment:
134
+
135
+ ```typescript
136
+ new JaypieEnvSecret(this, "ApiKey", {
137
+ value: "sk_live_abc123", // Not recommended - prefer env vars
138
+ });
123
139
  ```
124
140
 
125
- Retrieve in code:
141
+ ### Manual SECRET_ Linking
142
+
143
+ For non-JaypieEnvSecret secrets, manually set the `SECRET_` prefix:
126
144
 
127
145
  ```typescript
128
- const credentialsJson = await getEnvSecret("DB_CREDENTIALS");
129
- const credentials = JSON.parse(credentialsJson);
146
+ new JaypieLambda(this, "Handler", {
147
+ environment: {
148
+ SECRET_MONGODB_URI: "my-project/mongodb-uri", // AWS secret name
149
+ },
150
+ });
130
151
  ```
131
152
 
132
- ## Caching
153
+ At runtime, `getEnvSecret("MONGODB_URI")` sees `SECRET_MONGODB_URI` and fetches from that AWS secret name.
133
154
 
134
- Secrets are cached by default to reduce API calls:
155
+ The `_SECRET` suffix also works:
135
156
 
136
157
  ```typescript
137
- // First call: fetches from Secrets Manager
138
- const key1 = await getEnvSecret("API_KEY");
139
-
140
- // Second call: returns cached value
141
- const key2 = await getEnvSecret("API_KEY");
158
+ environment: {
159
+ MONGODB_URI_SECRET: "my-project/mongodb-uri",
160
+ }
142
161
  ```
143
162
 
144
- Cache is scoped to Lambda execution context (warm starts reuse cache).
163
+ ### Direct Secret Access
145
164
 
146
- ## Rotation
147
-
148
- Configure automatic rotation for supported secrets:
165
+ Use `getSecret` when you need to fetch by exact AWS secret name:
149
166
 
150
167
  ```typescript
151
- const secret = new Secret(this, "DbPassword", {
152
- secretName: "my-project/db-password",
153
- generateSecretString: {
154
- excludePunctuation: true,
155
- passwordLength: 32,
156
- },
157
- });
168
+ import { getSecret } from "jaypie";
158
169
 
159
- secret.addRotationSchedule("Rotation", {
160
- automaticallyAfter: Duration.days(30),
161
- rotationLambda: rotationFunction,
162
- });
170
+ const secret = await getSecret("my-project/production/api-key");
163
171
  ```
164
172
 
165
- ## Local Development
166
-
167
- For local development, set environment variables directly:
173
+ Note: `getSecret` requires `AWS_SESSION_TOKEN` and always calls Secrets Manager.
168
174
 
169
- ```bash
170
- # .env.local (not committed)
171
- ANTHROPIC_API_KEY=sk-ant-test123
172
- MONGODB_URI=mongodb://localhost:27017/dev
173
- API_KEY=test_key_123
174
- ```
175
+ ## Caching
175
176
 
176
- `getEnvSecret` automatically returns these values without AWS calls since there's no `SECRET_` prefix.
177
+ Secrets are cached by default to reduce API calls. Cache is scoped to Lambda execution context (warm starts reuse cache).
177
178
 
178
179
  ## IAM Permissions
179
180
 
180
- Lambda needs `secretsmanager:GetSecretValue`:
181
+ `JaypieEnvSecret` implements `ISecret`, so grant access directly:
181
182
 
182
183
  ```typescript
184
+ const secret = new JaypieEnvSecret(this, "API_KEY");
183
185
  secret.grantRead(lambdaFunction);
184
-
185
- // Or via policy
186
- lambdaFunction.addToRolePolicy(new PolicyStatement({
187
- actions: ["secretsmanager:GetSecretValue"],
188
- resources: [secret.secretArn],
189
- }));
190
186
  ```
191
187
 
188
+ Or use the `secrets` array on `JaypieLambda`, which handles permissions automatically.
189
+
192
190
  ## Testing
193
191
 
194
192
  Mock secret functions in tests:
package/skills/skills.md CHANGED
@@ -16,7 +16,7 @@ Look up skills by alias: `mcp__jaypie__skill(alias)`
16
16
  | Category | Skills |
17
17
  |----------|--------|
18
18
  | contents | index, releasenotes |
19
- | development | documentation, errors, logs, mocks, style, tests |
20
- | infrastructure | aws, cdk, cicd, datadog, dns, dynamodb, express, lambda, secrets, variables |
19
+ | development | documentation, errors, llm, logs, mocks, monorepo, style, subpackages, tests |
20
+ | infrastructure | aws, cdk, cicd, datadog, dns, dynamodb, express, lambda, secrets, streaming, variables, websockets |
21
21
  | patterns | fabric, handlers, models, services, vocabulary |
22
22
  | meta | issues, jaypie, skills, tools |
@@ -0,0 +1,219 @@
1
+ ---
2
+ description: Create a subpackage within a monorepo
3
+ related: monorepo, tests, style
4
+ ---
5
+
6
+ # Jaypie Subpackage Setup
7
+
8
+ Create a new subpackage within an existing Jaypie monorepo.
9
+
10
+ ## Overview
11
+
12
+ - TypeScript subpackage with Vite/Vitest
13
+ - Standard Jaypie project structure
14
+ - NPM workspace integration
15
+ - ESLint configuration inheritance
16
+
17
+ ## Guidelines
18
+
19
+ - Subpackage names follow `@project-org/package-name` pattern
20
+ - Use `"version": "0.0.1"`, `"type": "module"`, and `"private": true`
21
+ - Place packages in `packages/<package-name>/` directory
22
+ - Use Vite for new TypeScript packages
23
+ - Never manually edit package.json for dependencies; use npm commands
24
+
25
+ ## Process
26
+
27
+ 1. Create package directory structure
28
+ 2. Create configuration files from templates
29
+ 3. Create basic src structure
30
+ 4. Update workspace configuration
31
+ 5. Install dependencies
32
+
33
+ ## Directory Structure
34
+
35
+ ```
36
+ packages/<package-name>/
37
+ ├── src/
38
+ │ ├── index.ts
39
+ │ └── __tests__/
40
+ │ └── index.spec.ts
41
+ ├── package.json
42
+ ├── tsconfig.json
43
+ ├── vite.config.ts
44
+ ├── vitest.config.ts
45
+ └── vitest.setup.ts
46
+ ```
47
+
48
+ ## Template Files
49
+
50
+ ### package.json
51
+
52
+ ```json
53
+ {
54
+ "name": "@project-org/package-name",
55
+ "version": "0.0.1",
56
+ "type": "module",
57
+ "private": true,
58
+ "scripts": {
59
+ "build": "vite build",
60
+ "clean": "rimraf dist",
61
+ "format": "eslint --fix",
62
+ "format:package": "sort-package-json",
63
+ "lint": "eslint",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest watch",
66
+ "typecheck": "tsc --noEmit"
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### tsconfig.json
72
+
73
+ ```json
74
+ {
75
+ "compilerOptions": {
76
+ "target": "ES2020",
77
+ "module": "ESNext",
78
+ "moduleResolution": "bundler",
79
+ "declaration": true,
80
+ "outDir": "./dist",
81
+ "strict": true,
82
+ "esModuleInterop": true,
83
+ "skipLibCheck": true,
84
+ "forceConsistentCasingInFileNames": true
85
+ },
86
+ "exclude": ["node_modules", "dist"],
87
+ "include": ["src/**/*"]
88
+ }
89
+ ```
90
+
91
+ ### vite.config.ts
92
+
93
+ ```typescript
94
+ import { defineConfig } from "vite";
95
+ import dts from "vite-plugin-dts";
96
+
97
+ export default defineConfig({
98
+ plugins: [
99
+ dts({
100
+ include: ["src"],
101
+ exclude: ["**/*.spec.ts"],
102
+ }),
103
+ ],
104
+ build: {
105
+ lib: {
106
+ entry: "./src/index.ts",
107
+ name: "PackageName",
108
+ fileName: "index",
109
+ formats: ["es"],
110
+ },
111
+ rollupOptions: {
112
+ external: [
113
+ // Add external dependencies here
114
+ "jaypie",
115
+ ],
116
+ },
117
+ target: "node22",
118
+ },
119
+ });
120
+ ```
121
+
122
+ ### vitest.config.ts
123
+
124
+ ```typescript
125
+ import { defineConfig } from "vitest/config";
126
+
127
+ export default defineConfig({
128
+ test: {
129
+ globals: true,
130
+ environment: "node",
131
+ setupFiles: ["./vitest.setup.ts"],
132
+ },
133
+ });
134
+ ```
135
+
136
+ ### vitest.setup.ts
137
+
138
+ ```typescript
139
+ import { matchers as jaypieMatchers } from "@jaypie/testkit";
140
+ import * as extendedMatchers from "jest-extended";
141
+ import { expect } from "vitest";
142
+
143
+ expect.extend(extendedMatchers);
144
+ expect.extend(jaypieMatchers);
145
+ ```
146
+
147
+ ### src/index.ts
148
+
149
+ ```typescript
150
+ // Export public API here
151
+ export {};
152
+ ```
153
+
154
+ ### src/__tests__/index.spec.ts
155
+
156
+ ```typescript
157
+ import { describe, expect, it } from "vitest";
158
+
159
+ describe("Package Name", () => {
160
+ describe("Base Cases", () => {
161
+ it("is a function", () => {
162
+ // Replace with actual export test
163
+ expect(true).toBe(true);
164
+ });
165
+ });
166
+
167
+ describe("Happy Paths", () => {
168
+ it("works", () => {
169
+ // Add happy path tests
170
+ expect(true).toBe(true);
171
+ });
172
+ });
173
+ });
174
+ ```
175
+
176
+ ## Installation Commands
177
+
178
+ Add dependencies to the subpackage:
179
+
180
+ ```bash
181
+ # Runtime dependencies
182
+ npm install <package-name> --workspace ./packages/<package-name>
183
+
184
+ # Dev dependencies
185
+ npm install <package-name> --workspace ./packages/<package-name> --save-dev
186
+ ```
187
+
188
+ Common dev dependencies for subpackages:
189
+
190
+ ```bash
191
+ npm install jest-extended --workspace ./packages/<package-name> --save-dev
192
+ ```
193
+
194
+ ## Workspace Configuration
195
+
196
+ The root `vitest.workspace.ts` uses a glob pattern that auto-discovers packages:
197
+
198
+ ```typescript
199
+ export default ["packages/*/vitest.config.{ts,js}"];
200
+ ```
201
+
202
+ New packages are automatically included when they have a `vitest.config.ts`.
203
+
204
+ ## Checklist
205
+
206
+ After creating a subpackage:
207
+
208
+ 1. ✅ Update package name in `package.json`
209
+ 2. ✅ Update `name` in `vite.config.ts` build.lib
210
+ 3. ✅ Add external dependencies to `rollupOptions.external`
211
+ 4. ✅ Run `npm install` from root to link workspace
212
+ 5. ✅ Verify with `npm run build -w packages/<package-name>`
213
+ 6. ✅ Verify with `npm run test -w packages/<package-name>`
214
+
215
+ ## Next Steps
216
+
217
+ - `skill("tests")` - Testing patterns with Vitest
218
+ - `skill("mocks")` - Mock patterns via @jaypie/testkit
219
+ - `skill("style")` - Code style conventions