opencastle 0.10.0 → 0.10.2

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.
Files changed (173) hide show
  1. package/README.md +11 -77
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli/doctor.js +13 -7
  4. package/dist/cli/doctor.js.map +1 -1
  5. package/dist/cli/init.d.ts.map +1 -1
  6. package/dist/cli/init.js +7 -2
  7. package/dist/cli/init.js.map +1 -1
  8. package/dist/cli/init.test.d.ts +17 -0
  9. package/dist/cli/init.test.d.ts.map +1 -0
  10. package/dist/cli/init.test.js +881 -0
  11. package/dist/cli/init.test.js.map +1 -0
  12. package/dist/cli/mcp.d.ts +9 -0
  13. package/dist/cli/mcp.d.ts.map +1 -1
  14. package/dist/cli/mcp.js +56 -0
  15. package/dist/cli/mcp.js.map +1 -1
  16. package/dist/cli/stack-config-update.test.d.ts +2 -0
  17. package/dist/cli/stack-config-update.test.d.ts.map +1 -0
  18. package/dist/cli/stack-config-update.test.js +185 -0
  19. package/dist/cli/stack-config-update.test.js.map +1 -0
  20. package/dist/cli/stack-config.d.ts +27 -0
  21. package/dist/cli/stack-config.d.ts.map +1 -1
  22. package/dist/cli/stack-config.js +80 -27
  23. package/dist/cli/stack-config.js.map +1 -1
  24. package/dist/cli/types.d.ts +1 -1
  25. package/dist/cli/types.d.ts.map +1 -1
  26. package/dist/cli/update.d.ts.map +1 -1
  27. package/dist/cli/update.js +184 -17
  28. package/dist/cli/update.js.map +1 -1
  29. package/dist/orchestrator/plugins/astro/config.d.ts +3 -0
  30. package/dist/orchestrator/plugins/astro/config.d.ts.map +1 -0
  31. package/dist/orchestrator/plugins/astro/config.js +27 -0
  32. package/dist/orchestrator/plugins/astro/config.js.map +1 -0
  33. package/dist/orchestrator/plugins/chrome-devtools/config.js +2 -2
  34. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -1
  35. package/dist/orchestrator/plugins/contentful/config.js +1 -1
  36. package/dist/orchestrator/plugins/contentful/config.js.map +1 -1
  37. package/dist/orchestrator/plugins/convex/config.js +1 -1
  38. package/dist/orchestrator/plugins/convex/config.js.map +1 -1
  39. package/dist/orchestrator/plugins/cypress/config.d.ts +3 -0
  40. package/dist/orchestrator/plugins/cypress/config.d.ts.map +1 -0
  41. package/dist/orchestrator/plugins/cypress/config.js +15 -0
  42. package/dist/orchestrator/plugins/cypress/config.js.map +1 -0
  43. package/dist/orchestrator/plugins/figma/config.d.ts +3 -0
  44. package/dist/orchestrator/plugins/figma/config.d.ts.map +1 -0
  45. package/dist/orchestrator/plugins/figma/config.js +31 -0
  46. package/dist/orchestrator/plugins/figma/config.js.map +1 -0
  47. package/dist/orchestrator/plugins/index.d.ts.map +1 -1
  48. package/dist/orchestrator/plugins/index.js +20 -0
  49. package/dist/orchestrator/plugins/index.js.map +1 -1
  50. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -1
  51. package/dist/orchestrator/plugins/jira/config.js +2 -3
  52. package/dist/orchestrator/plugins/jira/config.js.map +1 -1
  53. package/dist/orchestrator/plugins/linear/config.js +2 -2
  54. package/dist/orchestrator/plugins/linear/config.js.map +1 -1
  55. package/dist/orchestrator/plugins/netlify/config.d.ts +3 -0
  56. package/dist/orchestrator/plugins/netlify/config.d.ts.map +1 -0
  57. package/dist/orchestrator/plugins/netlify/config.js +30 -0
  58. package/dist/orchestrator/plugins/netlify/config.js.map +1 -0
  59. package/dist/orchestrator/plugins/nextjs/config.d.ts +3 -0
  60. package/dist/orchestrator/plugins/nextjs/config.d.ts.map +1 -0
  61. package/dist/orchestrator/plugins/nextjs/config.js +35 -0
  62. package/dist/orchestrator/plugins/nextjs/config.js.map +1 -0
  63. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -1
  64. package/dist/orchestrator/plugins/nx/config.js +2 -3
  65. package/dist/orchestrator/plugins/nx/config.js.map +1 -1
  66. package/dist/orchestrator/plugins/playwright/config.d.ts +3 -0
  67. package/dist/orchestrator/plugins/playwright/config.d.ts.map +1 -0
  68. package/dist/orchestrator/plugins/playwright/config.js +25 -0
  69. package/dist/orchestrator/plugins/playwright/config.js.map +1 -0
  70. package/dist/orchestrator/plugins/prisma/config.d.ts +3 -0
  71. package/dist/orchestrator/plugins/prisma/config.d.ts.map +1 -0
  72. package/dist/orchestrator/plugins/prisma/config.js +25 -0
  73. package/dist/orchestrator/plugins/prisma/config.js.map +1 -0
  74. package/dist/orchestrator/plugins/resend/config.d.ts +3 -0
  75. package/dist/orchestrator/plugins/resend/config.d.ts.map +1 -0
  76. package/dist/orchestrator/plugins/resend/config.js +46 -0
  77. package/dist/orchestrator/plugins/resend/config.js.map +1 -0
  78. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -1
  79. package/dist/orchestrator/plugins/sanity/config.js +1 -2
  80. package/dist/orchestrator/plugins/sanity/config.js.map +1 -1
  81. package/dist/orchestrator/plugins/slack/config.js +1 -1
  82. package/dist/orchestrator/plugins/slack/config.js.map +1 -1
  83. package/dist/orchestrator/plugins/strapi/config.js +1 -1
  84. package/dist/orchestrator/plugins/strapi/config.js.map +1 -1
  85. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -1
  86. package/dist/orchestrator/plugins/supabase/config.js +1 -2
  87. package/dist/orchestrator/plugins/supabase/config.js.map +1 -1
  88. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -1
  89. package/dist/orchestrator/plugins/teams/config.js +1 -2
  90. package/dist/orchestrator/plugins/teams/config.js.map +1 -1
  91. package/dist/orchestrator/plugins/turborepo/config.d.ts +3 -0
  92. package/dist/orchestrator/plugins/turborepo/config.d.ts.map +1 -0
  93. package/dist/orchestrator/plugins/turborepo/config.js +15 -0
  94. package/dist/orchestrator/plugins/turborepo/config.js.map +1 -0
  95. package/dist/orchestrator/plugins/types.d.ts +7 -7
  96. package/dist/orchestrator/plugins/types.d.ts.map +1 -1
  97. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -1
  98. package/dist/orchestrator/plugins/vercel/config.js +2 -3
  99. package/dist/orchestrator/plugins/vercel/config.js.map +1 -1
  100. package/dist/orchestrator/plugins/vitest/config.d.ts +3 -0
  101. package/dist/orchestrator/plugins/vitest/config.d.ts.map +1 -0
  102. package/dist/orchestrator/plugins/vitest/config.js +15 -0
  103. package/dist/orchestrator/plugins/vitest/config.js.map +1 -0
  104. package/package.json +1 -1
  105. package/src/cli/doctor.ts +14 -7
  106. package/src/cli/init.test.ts +1141 -0
  107. package/src/cli/init.ts +8 -2
  108. package/src/cli/mcp.ts +77 -1
  109. package/src/cli/stack-config-update.test.ts +210 -0
  110. package/src/cli/stack-config.ts +110 -37
  111. package/src/cli/types.ts +1 -1
  112. package/src/cli/update.ts +230 -23
  113. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  114. package/src/orchestrator/agents/api-designer.agent.md +1 -11
  115. package/src/orchestrator/agents/architect.agent.md +1 -9
  116. package/src/orchestrator/agents/content-engineer.agent.md +1 -5
  117. package/src/orchestrator/agents/copywriter.agent.md +1 -9
  118. package/src/orchestrator/agents/data-expert.agent.md +2 -6
  119. package/src/orchestrator/agents/database-engineer.agent.md +1 -6
  120. package/src/orchestrator/agents/developer.agent.md +2 -12
  121. package/src/orchestrator/agents/devops-expert.agent.md +1 -5
  122. package/src/orchestrator/agents/documentation-writer.agent.md +1 -4
  123. package/src/orchestrator/agents/performance-expert.agent.md +1 -5
  124. package/src/orchestrator/agents/release-manager.agent.md +1 -11
  125. package/src/orchestrator/agents/researcher.agent.md +1 -4
  126. package/src/orchestrator/agents/security-expert.agent.md +2 -7
  127. package/src/orchestrator/agents/seo-specialist.agent.md +1 -10
  128. package/src/orchestrator/agents/testing-expert.agent.md +2 -11
  129. package/src/orchestrator/agents/ui-ux-expert.agent.md +3 -10
  130. package/src/orchestrator/customizations/README.md +2 -1
  131. package/src/orchestrator/customizations/agents/skill-matrix.json +106 -0
  132. package/src/orchestrator/customizations/agents/skill-matrix.md +58 -121
  133. package/src/orchestrator/instructions/general.instructions.md +1 -1
  134. package/src/orchestrator/plugins/astro/SKILL.md +288 -0
  135. package/src/orchestrator/plugins/astro/config.ts +28 -0
  136. package/src/orchestrator/plugins/chrome-devtools/config.ts +2 -2
  137. package/src/orchestrator/plugins/contentful/config.ts +1 -1
  138. package/src/orchestrator/plugins/convex/config.ts +1 -1
  139. package/src/orchestrator/plugins/cypress/SKILL.md +145 -0
  140. package/src/orchestrator/plugins/cypress/config.ts +16 -0
  141. package/src/orchestrator/plugins/figma/SKILL.md +85 -0
  142. package/src/orchestrator/plugins/figma/config.ts +32 -0
  143. package/src/orchestrator/plugins/index.ts +20 -0
  144. package/src/orchestrator/plugins/jira/config.ts +2 -3
  145. package/src/orchestrator/plugins/linear/config.ts +2 -2
  146. package/src/orchestrator/plugins/netlify/SKILL.md +134 -0
  147. package/src/orchestrator/plugins/netlify/config.ts +31 -0
  148. package/src/orchestrator/plugins/nextjs/SKILL.md +376 -0
  149. package/src/orchestrator/plugins/nextjs/config.ts +36 -0
  150. package/src/orchestrator/plugins/nx/config.ts +2 -3
  151. package/src/orchestrator/plugins/playwright/SKILL.md +191 -0
  152. package/src/orchestrator/plugins/playwright/config.ts +26 -0
  153. package/src/orchestrator/plugins/prisma/SKILL.md +137 -0
  154. package/src/orchestrator/plugins/prisma/config.ts +26 -0
  155. package/src/orchestrator/plugins/resend/SKILL.md +187 -0
  156. package/src/orchestrator/plugins/resend/config.ts +47 -0
  157. package/src/orchestrator/plugins/sanity/config.ts +1 -2
  158. package/src/orchestrator/plugins/slack/config.ts +1 -1
  159. package/src/orchestrator/plugins/strapi/config.ts +1 -1
  160. package/src/orchestrator/plugins/supabase/config.ts +1 -2
  161. package/src/orchestrator/plugins/teams/config.ts +1 -2
  162. package/src/orchestrator/plugins/turborepo/SKILL.md +121 -0
  163. package/src/orchestrator/plugins/turborepo/config.ts +16 -0
  164. package/src/orchestrator/plugins/types.ts +7 -7
  165. package/src/orchestrator/plugins/vercel/SKILL.md +99 -0
  166. package/src/orchestrator/plugins/vercel/config.ts +2 -3
  167. package/src/orchestrator/plugins/vitest/SKILL.md +166 -0
  168. package/src/orchestrator/plugins/vitest/config.ts +16 -0
  169. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +6 -4
  170. package/src/orchestrator/prompts/create-skill.prompt.md +6 -7
  171. package/src/orchestrator/skills/agent-hooks/SKILL.md +2 -2
  172. package/src/orchestrator/skills/memory-merger/SKILL.md +1 -1
  173. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +0 -200
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: vercel-deployment
3
+ description: "Vercel deployment workflows, environment management, domain configuration, and build troubleshooting. Use when deploying, checking deployment status, reviewing build logs, or managing environments."
4
+ ---
5
+
6
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
7
+
8
+ # Vercel Deployment
9
+
10
+ Vercel-specific deployment patterns and MCP tool usage. For project-specific deployment architecture, environment variables, and key files, see [deployment-config.md](../../customizations/stack/deployment-config.md).
11
+
12
+ ## Deployment Model
13
+
14
+ Vercel uses Git-based deployments with automatic preview and production environments:
15
+
16
+ ```
17
+ main branch → Production deployment (auto)
18
+ feature/* → Preview deployment (auto)
19
+ fix/* → Preview deployment (auto)
20
+ ```
21
+
22
+ - Every push creates a deployment — no manual triggers needed
23
+ - Preview deployments get unique URLs for testing
24
+ - Production deploys only from the main branch
25
+ - Rollback by redeploying a previous commit
26
+
27
+ ## MCP Tools
28
+
29
+ The Vercel MCP server provides these tools through `https://mcp.vercel.com`:
30
+
31
+ | Tool | Purpose | Primary Agents |
32
+ |------|---------|----------------|
33
+ | `deploy_to_vercel` | Trigger a deployment | DevOps Expert |
34
+ | `get_deployment` | Check deployment status and metadata | DevOps, Release Manager |
35
+ | `get_deployment_build_logs` | Read build output for debugging | DevOps, Release Manager |
36
+ | `get_runtime_logs` | Read runtime logs for debugging | DevOps, Release Manager |
37
+ | `list_deployments` | List recent deployments | DevOps, Release Manager |
38
+ | `get_project` | Get project configuration | DevOps Expert |
39
+ | `list_projects` | List all projects in the team | DevOps Expert |
40
+ | `list_teams` | List available teams | DevOps Expert |
41
+ | `search_vercel_documentation` | Search Vercel docs | DevOps Expert |
42
+ | `check_domain_availability_and_price` | Domain availability check | DevOps Expert |
43
+
44
+ ## Environment Variables
45
+
46
+ ### Vercel Environment Scoping
47
+
48
+ Vercel supports three environment scopes — set variables for each appropriately:
49
+
50
+ | Scope | When Applied | Use For |
51
+ |-------|-------------|---------|
52
+ | **Production** | `main` branch deploys | Live secrets, production API keys |
53
+ | **Preview** | All non-production branches | Staging/test API keys |
54
+ | **Development** | `vercel dev` local server | Local development overrides |
55
+
56
+ ### Best Practices
57
+
58
+ - Set secrets via the Vercel dashboard or CLI — never commit them
59
+ - Use `NEXT_PUBLIC_*` prefix only for variables safe to expose to the browser
60
+ - Verify required env vars exist in all three scopes (production, preview, development)
61
+ - Use `.env.local` for local development; never commit this file
62
+
63
+ ## Build Troubleshooting
64
+
65
+ When builds fail, follow this workflow:
66
+
67
+ 1. **Read build logs** — use `get_deployment_build_logs` to get the full output
68
+ 2. **Check common causes:**
69
+ - Missing environment variables (works locally but not on Vercel)
70
+ - Node.js version mismatch (check `engines` in `package.json`)
71
+ - Build command mismatch (verify in project settings)
72
+ - Dependency resolution issues (lockfile out of sync)
73
+ 3. **Check runtime logs** — use `get_runtime_logs` for post-deploy errors
74
+ 4. **Verify deployment status** — use `get_deployment` to check state and error details
75
+
76
+ ## Domain Configuration
77
+
78
+ - Use `check_domain_availability_and_price` before purchasing
79
+ - Configure domains in the Vercel dashboard
80
+ - Always set up both `www` and apex domain with proper redirects
81
+ - Enable HTTPS (automatic with Vercel)
82
+ - Set appropriate DNS records (CNAME for subdomains, A record for apex)
83
+
84
+ ## Cron Jobs (vercel.json)
85
+
86
+ ```json
87
+ {
88
+ "crons": [
89
+ {
90
+ "path": "/api/cron/task-name",
91
+ "schedule": "0 */6 * * *"
92
+ }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ - Protect cron endpoints with `CRON_SECRET` — Vercel sends it in the `Authorization` header
98
+ - Maximum execution time depends on plan (10s hobby, 60s pro, 900s enterprise)
99
+ - Use `vercel.json` to declare cron schedules — not external schedulers
@@ -7,7 +7,7 @@ export const config: PluginConfig = {
7
7
  subCategory: 'deployment',
8
8
  label: 'Vercel',
9
9
  hint: 'Deployment and hosting platform',
10
- skillName: null,
10
+ skillName: 'vercel-deployment',
11
11
  mcpServerKey: 'Vercel',
12
12
  mcpConfig: {
13
13
  type: 'http',
@@ -27,7 +27,6 @@ export const config: PluginConfig = {
27
27
  'vercel/get_runtime_logs', 'vercel/list_deployments', 'vercel/list_projects',
28
28
  ],
29
29
  },
30
- docsUrl: null,
30
+ docsUrl: 'https://www.opencastle.dev/docs/plugins#vercel',
31
31
  officialDocs: 'https://vercel.com/docs',
32
- mcpPackage: null,
33
32
  };
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: vitest-testing
3
+ description: "Vitest unit and integration testing patterns, configuration, mocking, and coverage. Use when writing unit tests, configuring Vitest, or setting up test coverage."
4
+ ---
5
+
6
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
7
+
8
+ # Vitest Testing
9
+
10
+ Vitest-specific unit and integration testing patterns. For project-specific test configuration, see [testing-config.md](../../customizations/stack/testing-config.md).
11
+
12
+ ## Commands
13
+
14
+ ```bash
15
+ npx vitest # Run in watch mode
16
+ npx vitest run # Run once (CI)
17
+ npx vitest run --coverage # Run with coverage report
18
+ npx vitest run src/utils/ # Run specific directory
19
+ npx vitest run auth.test.ts # Run specific file
20
+ npx vitest run -t "should validate" # Filter by test name
21
+ npx vitest --ui # Open Vitest UI
22
+ npx vitest --reporter=json # JSON output for CI
23
+ npx vitest typecheck # Run type-checking tests
24
+ ```
25
+
26
+ ## Test Structure
27
+
28
+ ### File Naming
29
+
30
+ ```
31
+ src/
32
+ ├── utils/
33
+ │ ├── format.ts
34
+ │ └── format.test.ts # Co-located test
35
+ ├── components/
36
+ │ ├── Button.tsx
37
+ │ └── Button.test.tsx # Component test
38
+ └── __tests__/ # Integration tests
39
+ └── auth-flow.test.ts
40
+ ```
41
+
42
+ ### Writing Tests
43
+
44
+ ```typescript
45
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
46
+ import { formatPrice } from './format';
47
+
48
+ describe('formatPrice', () => {
49
+ it('should format USD prices', () => {
50
+ expect(formatPrice(9.99, 'USD')).toBe('$9.99');
51
+ });
52
+
53
+ it('should handle zero', () => {
54
+ expect(formatPrice(0, 'USD')).toBe('$0.00');
55
+ });
56
+
57
+ it('should throw for negative values', () => {
58
+ expect(() => formatPrice(-1, 'USD')).toThrow('Price cannot be negative');
59
+ });
60
+ });
61
+ ```
62
+
63
+ ## Mocking
64
+
65
+ ### Module Mocking
66
+
67
+ ```typescript
68
+ import { vi, describe, it, expect } from 'vitest';
69
+
70
+ // Mock entire module
71
+ vi.mock('./database', () => ({
72
+ getUser: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
73
+ }));
74
+
75
+ // Mock specific export
76
+ vi.mock('./config', async (importOriginal) => {
77
+ const original = await importOriginal<typeof import('./config')>();
78
+ return { ...original, API_URL: 'http://test-api.example.com' };
79
+ });
80
+ ```
81
+
82
+ ### Spy and Stub
83
+
84
+ ```typescript
85
+ import { vi, describe, it, expect } from 'vitest';
86
+
87
+ describe('notifications', () => {
88
+ it('should call the API', async () => {
89
+ const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
90
+ new Response(JSON.stringify({ ok: true }))
91
+ );
92
+
93
+ await sendNotification('Hello');
94
+
95
+ expect(fetchSpy).toHaveBeenCalledWith('/api/notify', expect.objectContaining({
96
+ method: 'POST',
97
+ }));
98
+
99
+ fetchSpy.mockRestore();
100
+ });
101
+ });
102
+ ```
103
+
104
+ ### Timer Mocking
105
+
106
+ ```typescript
107
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
108
+
109
+ describe('debounce', () => {
110
+ beforeEach(() => vi.useFakeTimers());
111
+ afterEach(() => vi.useRealTimers());
112
+
113
+ it('should debounce calls', () => {
114
+ const fn = vi.fn();
115
+ const debounced = debounce(fn, 300);
116
+
117
+ debounced();
118
+ debounced();
119
+ debounced();
120
+
121
+ expect(fn).not.toHaveBeenCalled();
122
+ vi.advanceTimersByTime(300);
123
+ expect(fn).toHaveBeenCalledOnce();
124
+ });
125
+ });
126
+ ```
127
+
128
+ ## Configuration (vitest.config.ts)
129
+
130
+ ```typescript
131
+ import { defineConfig } from 'vitest/config';
132
+
133
+ export default defineConfig({
134
+ test: {
135
+ globals: true, // Use describe/it/expect without imports
136
+ environment: 'jsdom', // 'node' | 'jsdom' | 'happy-dom'
137
+ include: ['src/**/*.test.{ts,tsx}'],
138
+ coverage: {
139
+ provider: 'v8',
140
+ reporter: ['text', 'json', 'html'],
141
+ include: ['src/**/*.{ts,tsx}'],
142
+ exclude: ['src/**/*.test.{ts,tsx}', 'src/**/*.d.ts'],
143
+ thresholds: {
144
+ branches: 80,
145
+ functions: 80,
146
+ lines: 80,
147
+ statements: 80,
148
+ },
149
+ },
150
+ setupFiles: ['./src/test-setup.ts'],
151
+ },
152
+ });
153
+ ```
154
+
155
+ ## Best Practices
156
+
157
+ - Co-locate test files next to source files (`foo.test.ts` next to `foo.ts`)
158
+ - Use `describe` blocks to group related tests
159
+ - Each test should be independent — no shared mutable state between tests
160
+ - Clean up mocks with `vi.restoreAllMocks()` in `afterEach`
161
+ - Use `vi.mock()` at the top level — Vitest hoists mock calls automatically
162
+ - Prefer `toEqual` for objects, `toBe` for primitives
163
+ - Use `test.each` for parameterized tests
164
+ - Set coverage thresholds to prevent regression
165
+ - Use `vi.useFakeTimers()` for time-dependent code — never `setTimeout` in tests
166
+ - Use `--reporter=json` in CI for machine-readable output
@@ -0,0 +1,16 @@
1
+ import type { PluginConfig } from '../types.js';
2
+
3
+ export const config: PluginConfig = {
4
+ id: 'vitest',
5
+ name: 'Vitest',
6
+ category: 'tech',
7
+ subCategory: 'testing',
8
+ label: 'Vitest',
9
+ hint: 'Vite-native unit and integration testing',
10
+ skillName: 'vitest-testing',
11
+ authType: 'none',
12
+ envVars: [],
13
+ agentToolMap: {},
14
+ docsUrl: 'https://www.opencastle.dev/docs/plugins#vitest',
15
+ officialDocs: 'https://vitest.dev',
16
+ };
@@ -69,7 +69,7 @@ The result is a single unified view of the project's tech stack:
69
69
 
70
70
  **Still verify:** `repoInfo` detects presence, not configuration details. You still need to read the actual config files for schemas, IDs, routes, etc.
71
71
 
72
- The skill matrix (`.github/customizations/agents/skill-matrix.md`) will already have the `cms` and `database` rows pre-filled based on this selection. The appropriate task management skill (`linear-task-management` for Linear, `jira-management` for Jira) and notifications skill (`slack-notifications` for Slack, `teams-notifications` for Teams) will already be installed. Verify they are correct and fill in any remaining empty rows.
72
+ The skill matrix (`.github/customizations/agents/skill-matrix.json`) will already have the `cms` and `database` binding entries pre-filled based on this selection. The appropriate task management skill (`linear-task-management` for Linear, `jira-management` for Jira) and notifications skill (`slack-notifications` for Slack, `teams-notifications` for Teams) will already be installed. Verify they are correct and fill in any remaining empty bindings.
73
73
 
74
74
  ## Workflow
75
75
 
@@ -130,6 +130,7 @@ Files are organized into subdirectories by domain:
130
130
  ├── AGENT-PERFORMANCE.md # Agent success tracking & log query recipes
131
131
  ├── agents/ # Agent framework config
132
132
  │ ├── agent-registry.md
133
+ │ ├── skill-matrix.json
133
134
  │ └── skill-matrix.md
134
135
  ├── stack/ # Tech stack config
135
136
  │ ├── api-config.md
@@ -173,9 +174,10 @@ Files are organized into subdirectories by domain:
173
174
  - Scope descriptions
174
175
  - File partition examples
175
176
 
176
- 7. **`agents/skill-matrix.md`** — If `.github/skills/` exists with skill definitions:
177
- - Map of capability slots to skill names per agent role
178
- - Which agents load which skills
177
+ 7. **`agents/skill-matrix.json`** — If `.github/skills/` exists with skill definitions:
178
+ - Capability slot bindings and `directSkills` per agent role (in JSON format)
179
+ - Which agents load which skills (slots for plugin skills, directSkills for process skills)
180
+ - Note: `skill-matrix.md` is a companion documentation file — the JSON is the source of truth
179
181
 
180
182
  #### `stack/` — Tech Stack Config (create only for detected technologies)
181
183
 
@@ -21,7 +21,7 @@ OpenCastle has two kinds of skills with different locations and registration pat
21
21
 
22
22
  | Type | Location | Bound Via | Purpose |
23
23
  |------|----------|-----------|---------|
24
- | **Process skill** | `skills/<name>/SKILL.md` | Direct reference in agent files | Stack-agnostic methodology (testing workflow, self-improvement, validation gates) |
24
+ | **Process skill** | `skills/<name>/SKILL.md` | `directSkills` in skill-matrix.json | Stack-agnostic methodology (testing workflow, self-improvement, validation gates) |
25
25
  | **Plugin skill** | `plugins/<plugin>/SKILL.md` | Capability slot in the skill matrix | Technology-specific knowledge (CMS queries, database patterns, deployment config) |
26
26
 
27
27
  > **Rule of thumb:** If the skill would need to be rewritten when switching technologies (e.g., Supabase → Convex), it belongs in a **plugin**. If it's useful regardless of stack, it's a **process skill**.
@@ -92,14 +92,13 @@ Registration differs by type:
92
92
 
93
93
  #### Process Skill
94
94
 
95
- 1. **Add to the skill matrix** — Add a row to the **Process Skills (Always Direct)** table in `.github/customizations/agents/skill-matrix.md`
96
- 2. **Reference in agent files** — Add to the `Direct Skills` section of each agent that should use it
97
- 3. **Optional: reference in instructions** — If the skill should be loaded by default, add it to the appropriate `.github/instructions/` file
95
+ 1. **Add to the skill matrix** — Add the skill name to the `directSkills` array of each relevant agent in `.github/customizations/agents/skill-matrix.json`
96
+ 2. **Optional: reference in instructions** — If the skill should be loaded by default, add it to the appropriate `.github/instructions/` file
98
97
 
99
98
  #### Plugin Skill
100
99
 
101
100
  1. **Set `skillName` in the plugin's `config.ts`** — This connects the skill to the plugin
102
- 2. **Update the skill matrix** — Set the Skill column for the matching capability slot row in `.github/customizations/agents/skill-matrix.md`
101
+ 2. **Update the skill matrix** — Add an entry to the matching capability slot's `entries` array in `.github/customizations/agents/skill-matrix.json`
103
102
  3. **No agent changes needed** — Agents resolve plugin skills through capability slots automatically
104
103
 
105
104
  ### Step 5: Validate
@@ -109,8 +108,8 @@ Registration differs by type:
109
108
  - [ ] Description is a single line (no line breaks)
110
109
  - [ ] Content follows the template structure
111
110
  - [ ] No overlap with existing skills
112
- - [ ] Skill matrix updated (process skill table or capability slot binding)
113
- - [ ] For process skills: at least one agent references the skill directly
111
+ - [ ] Skill matrix updated (`directSkills` array or capability slot binding)
112
+ - [ ] For process skills: at least one agent's `directSkills` array includes it in skill-matrix.json
114
113
  - [ ] For plugin skills: `config.ts` `skillName` matches the `name` in frontmatter
115
114
 
116
115
  ## Quality Guidelines
@@ -32,7 +32,7 @@ Session Lifecycle:
32
32
  2. **Check for checkpoint** — If `.github/customizations/SESSION-CHECKPOINT.md` exists, read it. Resume from last known state instead of re-analyzing.
33
33
  3. **Check pending approvals** — If the checkpoint has a `## Pending Approvals` section, check for replies using the configured messaging provider's MCP tools (e.g., `conversations_replies` for Slack). Read `.opencastle.json` → `stack.teamTools` to determine the provider. If no messaging is configured, skip this step.
34
34
  4. **Check dead letter queue** — Scan `.github/customizations/AGENT-FAILURES.md` for pending failures related to the current scope.
35
- 5. **Validate skill-matrix bindings** — Open `.github/customizations/agents/skill-matrix.md` and check whether the **Primary Stack** and **Tooling** tables have any filled-in rows (non-empty Technology/Skill columns). If all bindings are empty, **warn the user** that the bootstrap hasn't been run and capability slots will not resolve. Suggest running the *"Bootstrap Customizations"* prompt first. Do NOT silently continue with empty bindings.
35
+ 5. **Validate skill-matrix bindings** — Open `.github/customizations/agents/skill-matrix.json` and check whether the `bindings` object has any slots with non-empty `entries` arrays. If all entries are empty, **warn the user** that the bootstrap hasn't been run and capability slots will not resolve. Suggest running the *"Bootstrap Customizations"* prompt first. Do NOT silently continue with empty bindings.
36
36
  6. **Load domain skills** — Based on the task description, load the appropriate skills before writing code. Don't start coding without the relevant skill loaded.
37
37
 
38
38
  ### Template for Delegation Prompts
@@ -43,7 +43,7 @@ Include this reminder in every delegation:
43
43
  **Session Start:** Read `.github/customizations/LESSONS-LEARNED.md` before starting.
44
44
  Check `.github/customizations/SESSION-CHECKPOINT.md` for prior state and pending approvals.
45
45
  If pending approvals exist, check for replies via the messaging provider.
46
- Validate `.github/customizations/agents/skill-matrix.md` — warn if skill bindings are empty (bootstrap not run).
46
+ Validate `.github/customizations/agents/skill-matrix.json` — warn if skill bindings are empty (bootstrap not run).
47
47
  Load relevant skills before writing code.
48
48
  ```
49
49
 
@@ -53,7 +53,7 @@ Each lesson has a natural home in the instruction/skill hierarchy:
53
53
  | `deployment` | `.github/skills/deployment-infrastructure/SKILL.md` |
54
54
  | `delegation` | `.github/agents/team-lead.agent.md` or `.github/skills/team-lead-reference/SKILL.md` |
55
55
  | `testing` | `.github/skills/testing-workflow/SKILL.md` |
56
- | `ui-library` / `framework` | The skill mapped by the corresponding slot in the skill matrix |
56
+ | `ui` / `framework` | The skill mapped by the `framework` slot or the `react-development` direct skill |
57
57
  | Cross-cutting pattern | `.github/instructions/general.instructions.md` |
58
58
 
59
59
  ### Step 3: Draft the Merge
@@ -1,200 +0,0 @@
1
- ---
2
- name: nextjs-patterns
3
- description: "Next.js App Router best practices for server/client components, routing, API routes, and project structure. Use when creating or modifying Next.js pages, layouts, route handlers, or Server Actions."
4
- ---
5
-
6
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
7
-
8
- # Next.js Patterns (2025)
9
-
10
- ## Project Structure
11
-
12
- - **Use `app/` directory** (App Router) for all routes; colocate files near where they're used.
13
- - Top-level: `app/`, `public/`, `lib/`, `components/`, `contexts/`, `styles/`, `hooks/`, `types/`.
14
- - **Route Groups** `(admin)` — group without affecting URL. **Private Folders** `_internal` — opt out of routing.
15
- - Feature folders for large apps: `app/dashboard/`, `app/auth/`.
16
-
17
- ## Server and Client Components
18
-
19
- **Default: Server Components** — data fetching, heavy logic, non-interactive UI.
20
-
21
- **Client Components** — add `'use client'` at top. Use for interactivity, state, browser APIs.
22
-
23
- ### Decision Table
24
-
25
- | Need | Component Type | Why |
26
- |------|---------------|-----|
27
- | Fetch data at request time | Server | Direct DB/API access, no client waterfall |
28
- | Read cookies/headers | Server | Available only on the server |
29
- | Interactive UI (clicks, inputs) | Client | Requires event handlers |
30
- | Use `useState` / `useEffect` | Client | React hooks need client runtime |
31
- | Access browser APIs (localStorage, geolocation) | Client | Not available on server |
32
- | Render static/non-interactive content | Server | Smaller bundle, faster paint |
33
- | Show loading spinners for async children | Server (with `<Suspense>`) | Streams HTML progressively |
34
-
35
- ### Critical Rule
36
-
37
- **Never use `next/dynamic` with `{ ssr: false }` inside a Server Component.** This causes build/runtime errors.
38
-
39
- **Correct approach:** Move client-only logic into a dedicated `'use client'` component, then import it normally.
40
-
41
- ```tsx
42
- // Server Component — imports a Client Component directly
43
- import DashboardNavbar from '@/components/DashboardNavbar';
44
- export default async function DashboardPage() {
45
- return <><DashboardNavbar /></>;
46
- }
47
- ```
48
-
49
- ## Data Fetching Patterns
50
-
51
- ### Server-Side Fetching
52
-
53
- Fetch directly in `async` Server Components. Next.js deduplicates identical `fetch` calls.
54
-
55
- ```tsx
56
- export default async function ProjectsPage() {
57
- const projects = await fetch('https://api.example.com/projects', {
58
- next: { revalidate: 60 }, // ISR: revalidate every 60s
59
- }).then((res) => res.json());
60
- return <ul>{projects.map((p: { id: string; name: string }) => <li key={p.id}>{p.name}</li>)}</ul>;
61
- }
62
- ```
63
-
64
- ### Server Actions (mutations)
65
-
66
- Define with `'use server'`. Call from Client Components via `action` or `startTransition`.
67
-
68
- ```tsx
69
- // lib/actions.ts
70
- 'use server';
71
- import { revalidatePath } from 'next/cache';
72
- export async function createItem(formData: FormData) {
73
- const name = formData.get('name') as string;
74
- await db.items.create({ data: { name } });
75
- revalidatePath('/items');
76
- }
77
- ```
78
-
79
- ```tsx
80
- // components/CreateItemForm.tsx — calls the Server Action
81
- 'use client';
82
- import { createItem } from '@/lib/actions';
83
- export default function CreateItemForm() {
84
- return <form action={createItem}><input name="name" required /><button type="submit">Add</button></form>;
85
- }
86
- ```
87
-
88
- ## Error Handling
89
-
90
- Each route segment can export `error.tsx` (must be a Client Component) and `not-found.tsx`.
91
-
92
- ```tsx
93
- // app/dashboard/error.tsx — must be a Client Component
94
- 'use client';
95
- export default function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
96
- return <div role="alert"><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
97
- }
98
- ```
99
-
100
- ```tsx
101
- // app/projects/[id]/page.tsx — use notFound() to trigger not-found.tsx
102
- import { notFound } from 'next/navigation';
103
- export default async function ProjectPage({ params }: { params: { id: string } }) {
104
- const project = await getProject(params.id);
105
- if (!project) notFound();
106
- return <h1>{project.name}</h1>;
107
- }
108
- ```
109
-
110
- ## Middleware
111
-
112
- Place `middleware.ts` at the project root (next to `app/`). Runs before every matched request.
113
-
114
- ```ts
115
- import { NextResponse, type NextRequest } from 'next/server';
116
- export function middleware(request: NextRequest) {
117
- const token = request.cookies.get('session')?.value;
118
- if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
119
- return NextResponse.redirect(new URL('/login', request.url));
120
- }
121
- return NextResponse.next();
122
- }
123
- export const config = { matcher: ['/dashboard/:path*', '/settings/:path*'] };
124
- ```
125
-
126
- ## Component Practices & Naming
127
-
128
- - PascalCase for component files/exports. camelCase for hooks.
129
- - Shared components in `components/`. Route-specific in route folder.
130
- - TypeScript interfaces for props. Explicit types and defaults.
131
- - Co-locate tests with components.
132
- - Folders: `kebab-case`. Types/Interfaces: `PascalCase`. Constants: `UPPER_SNAKE_CASE`.
133
-
134
- ## API Routes (Route Handlers)
135
-
136
- - Location: `app/api/` (e.g., `app/api/users/route.ts`).
137
- - Export async functions named after HTTP verbs (`GET`, `POST`, etc.).
138
- - Use Web `Request`/`Response` APIs. `NextRequest`/`NextResponse` for advanced features.
139
- - Dynamic segments: `[param]`.
140
- - Validate with Zod/Yup. Return appropriate status codes.
141
- - Protect sensitive routes with middleware or server-side session checks.
142
-
143
- ## Performance Patterns
144
-
145
- ### Dynamic Imports (Client Components only)
146
-
147
- Lazy-load heavy Client Components to reduce initial bundle size.
148
-
149
- ```tsx
150
- 'use client';
151
- import dynamic from 'next/dynamic';
152
- const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
153
- loading: () => <p>Loading chart…</p>,
154
- });
155
- ```
156
-
157
- ### Parallel Data Fetching
158
-
159
- Initiate independent fetches simultaneously — don't `await` sequentially.
160
-
161
- ```tsx
162
- export default async function DashboardPage() {
163
- const [metrics, activity] = await Promise.all([getMetrics(), getRecentActivity()]);
164
- return <><MetricsPanel data={metrics} /><ActivityFeed items={activity} /></>;
165
- }
166
- ```
167
-
168
- ### Streaming with Suspense
169
-
170
- Wrap slow data sections in `<Suspense>` so the shell renders immediately.
171
-
172
- ```tsx
173
- import { Suspense } from 'react';
174
- export default function Layout({ children }: { children: React.ReactNode }) {
175
- return <main><Suspense fallback={<p>Loading…</p>}>{children}</Suspense></main>;
176
- }
177
- ```
178
-
179
- ## General Best Practices
180
-
181
- - TypeScript with `strict` mode. ESLint with official Next.js config.
182
- - Secrets in `.env.local` — never committed.
183
- - Built-in Image and Font optimization.
184
- - Suspense and loading states for async data.
185
- - Avoid large client bundles — keep logic in Server Components.
186
- - Semantic HTML and ARIA attributes.
187
- - Do NOT create example/demo files unless explicitly requested.
188
-
189
- ## Anti-Patterns
190
-
191
- | Anti-Pattern | Why It's Wrong | Do This Instead |
192
- |-------------|---------------|-----------------|
193
- | `'use client'` on every component | Bloats JS bundle, defeats RSC benefits | Default to Server Components; add `'use client'` only when needed |
194
- | Sequential `await` for independent data | Creates a waterfall, slows page load | Use `Promise.all()` for parallel fetches |
195
- | `next/dynamic` with `ssr: false` in Server Components | Build/runtime crash | Extract to a Client Component, import normally |
196
- | Fetching in `useEffect` when server fetch works | Extra client roundtrip, loading flash | Fetch in the Server Component or use Server Actions |
197
- | Giant `layout.tsx` with all providers | Hard to test, couples unrelated concerns | Split providers into a `Providers` Client Component |
198
- | Catching errors without `error.tsx` | Unhandled errors crash the page | Add `error.tsx` per route segment |
199
- | Hardcoding secrets in source files | Security risk, leaks in version control | Use `.env.local` and `process.env` |
200
- | Skipping `loading.tsx` / `<Suspense>` | Blank screen while data loads | Add `loading.tsx` or wrap in `<Suspense>` |