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.
- package/README.md +11 -77
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +13 -7
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +7 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.d.ts +17 -0
- package/dist/cli/init.test.d.ts.map +1 -0
- package/dist/cli/init.test.js +881 -0
- package/dist/cli/init.test.js.map +1 -0
- package/dist/cli/mcp.d.ts +9 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +56 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/stack-config-update.test.d.ts +2 -0
- package/dist/cli/stack-config-update.test.d.ts.map +1 -0
- package/dist/cli/stack-config-update.test.js +185 -0
- package/dist/cli/stack-config-update.test.js.map +1 -0
- package/dist/cli/stack-config.d.ts +27 -0
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +80 -27
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +184 -17
- package/dist/cli/update.js.map +1 -1
- package/dist/orchestrator/plugins/astro/config.d.ts +3 -0
- package/dist/orchestrator/plugins/astro/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/astro/config.js +27 -0
- package/dist/orchestrator/plugins/astro/config.js.map +1 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js +2 -2
- package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -1
- package/dist/orchestrator/plugins/contentful/config.js +1 -1
- package/dist/orchestrator/plugins/contentful/config.js.map +1 -1
- package/dist/orchestrator/plugins/convex/config.js +1 -1
- package/dist/orchestrator/plugins/convex/config.js.map +1 -1
- package/dist/orchestrator/plugins/cypress/config.d.ts +3 -0
- package/dist/orchestrator/plugins/cypress/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/cypress/config.js +15 -0
- package/dist/orchestrator/plugins/cypress/config.js.map +1 -0
- package/dist/orchestrator/plugins/figma/config.d.ts +3 -0
- package/dist/orchestrator/plugins/figma/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/figma/config.js +31 -0
- package/dist/orchestrator/plugins/figma/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -1
- package/dist/orchestrator/plugins/index.js +20 -0
- package/dist/orchestrator/plugins/index.js.map +1 -1
- package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/jira/config.js +2 -3
- package/dist/orchestrator/plugins/jira/config.js.map +1 -1
- package/dist/orchestrator/plugins/linear/config.js +2 -2
- package/dist/orchestrator/plugins/linear/config.js.map +1 -1
- package/dist/orchestrator/plugins/netlify/config.d.ts +3 -0
- package/dist/orchestrator/plugins/netlify/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/netlify/config.js +30 -0
- package/dist/orchestrator/plugins/netlify/config.js.map +1 -0
- package/dist/orchestrator/plugins/nextjs/config.d.ts +3 -0
- package/dist/orchestrator/plugins/nextjs/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/nextjs/config.js +35 -0
- package/dist/orchestrator/plugins/nextjs/config.js.map +1 -0
- package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/nx/config.js +2 -3
- package/dist/orchestrator/plugins/nx/config.js.map +1 -1
- package/dist/orchestrator/plugins/playwright/config.d.ts +3 -0
- package/dist/orchestrator/plugins/playwright/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/playwright/config.js +25 -0
- package/dist/orchestrator/plugins/playwright/config.js.map +1 -0
- package/dist/orchestrator/plugins/prisma/config.d.ts +3 -0
- package/dist/orchestrator/plugins/prisma/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/prisma/config.js +25 -0
- package/dist/orchestrator/plugins/prisma/config.js.map +1 -0
- package/dist/orchestrator/plugins/resend/config.d.ts +3 -0
- package/dist/orchestrator/plugins/resend/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/resend/config.js +46 -0
- package/dist/orchestrator/plugins/resend/config.js.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/sanity/config.js +1 -2
- package/dist/orchestrator/plugins/sanity/config.js.map +1 -1
- package/dist/orchestrator/plugins/slack/config.js +1 -1
- package/dist/orchestrator/plugins/slack/config.js.map +1 -1
- package/dist/orchestrator/plugins/strapi/config.js +1 -1
- package/dist/orchestrator/plugins/strapi/config.js.map +1 -1
- package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/supabase/config.js +1 -2
- package/dist/orchestrator/plugins/supabase/config.js.map +1 -1
- package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/teams/config.js +1 -2
- package/dist/orchestrator/plugins/teams/config.js.map +1 -1
- package/dist/orchestrator/plugins/turborepo/config.d.ts +3 -0
- package/dist/orchestrator/plugins/turborepo/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/turborepo/config.js +15 -0
- package/dist/orchestrator/plugins/turborepo/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +7 -7
- package/dist/orchestrator/plugins/types.d.ts.map +1 -1
- package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -1
- package/dist/orchestrator/plugins/vercel/config.js +2 -3
- package/dist/orchestrator/plugins/vercel/config.js.map +1 -1
- package/dist/orchestrator/plugins/vitest/config.d.ts +3 -0
- package/dist/orchestrator/plugins/vitest/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/vitest/config.js +15 -0
- package/dist/orchestrator/plugins/vitest/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/doctor.ts +14 -7
- package/src/cli/init.test.ts +1141 -0
- package/src/cli/init.ts +8 -2
- package/src/cli/mcp.ts +77 -1
- package/src/cli/stack-config-update.test.ts +210 -0
- package/src/cli/stack-config.ts +110 -37
- package/src/cli/types.ts +1 -1
- package/src/cli/update.ts +230 -23
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/agents/api-designer.agent.md +1 -11
- package/src/orchestrator/agents/architect.agent.md +1 -9
- package/src/orchestrator/agents/content-engineer.agent.md +1 -5
- package/src/orchestrator/agents/copywriter.agent.md +1 -9
- package/src/orchestrator/agents/data-expert.agent.md +2 -6
- package/src/orchestrator/agents/database-engineer.agent.md +1 -6
- package/src/orchestrator/agents/developer.agent.md +2 -12
- package/src/orchestrator/agents/devops-expert.agent.md +1 -5
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -4
- package/src/orchestrator/agents/performance-expert.agent.md +1 -5
- package/src/orchestrator/agents/release-manager.agent.md +1 -11
- package/src/orchestrator/agents/researcher.agent.md +1 -4
- package/src/orchestrator/agents/security-expert.agent.md +2 -7
- package/src/orchestrator/agents/seo-specialist.agent.md +1 -10
- package/src/orchestrator/agents/testing-expert.agent.md +2 -11
- package/src/orchestrator/agents/ui-ux-expert.agent.md +3 -10
- package/src/orchestrator/customizations/README.md +2 -1
- package/src/orchestrator/customizations/agents/skill-matrix.json +106 -0
- package/src/orchestrator/customizations/agents/skill-matrix.md +58 -121
- package/src/orchestrator/instructions/general.instructions.md +1 -1
- package/src/orchestrator/plugins/astro/SKILL.md +288 -0
- package/src/orchestrator/plugins/astro/config.ts +28 -0
- package/src/orchestrator/plugins/chrome-devtools/config.ts +2 -2
- package/src/orchestrator/plugins/contentful/config.ts +1 -1
- package/src/orchestrator/plugins/convex/config.ts +1 -1
- package/src/orchestrator/plugins/cypress/SKILL.md +145 -0
- package/src/orchestrator/plugins/cypress/config.ts +16 -0
- package/src/orchestrator/plugins/figma/SKILL.md +85 -0
- package/src/orchestrator/plugins/figma/config.ts +32 -0
- package/src/orchestrator/plugins/index.ts +20 -0
- package/src/orchestrator/plugins/jira/config.ts +2 -3
- package/src/orchestrator/plugins/linear/config.ts +2 -2
- package/src/orchestrator/plugins/netlify/SKILL.md +134 -0
- package/src/orchestrator/plugins/netlify/config.ts +31 -0
- package/src/orchestrator/plugins/nextjs/SKILL.md +376 -0
- package/src/orchestrator/plugins/nextjs/config.ts +36 -0
- package/src/orchestrator/plugins/nx/config.ts +2 -3
- package/src/orchestrator/plugins/playwright/SKILL.md +191 -0
- package/src/orchestrator/plugins/playwright/config.ts +26 -0
- package/src/orchestrator/plugins/prisma/SKILL.md +137 -0
- package/src/orchestrator/plugins/prisma/config.ts +26 -0
- package/src/orchestrator/plugins/resend/SKILL.md +187 -0
- package/src/orchestrator/plugins/resend/config.ts +47 -0
- package/src/orchestrator/plugins/sanity/config.ts +1 -2
- package/src/orchestrator/plugins/slack/config.ts +1 -1
- package/src/orchestrator/plugins/strapi/config.ts +1 -1
- package/src/orchestrator/plugins/supabase/config.ts +1 -2
- package/src/orchestrator/plugins/teams/config.ts +1 -2
- package/src/orchestrator/plugins/turborepo/SKILL.md +121 -0
- package/src/orchestrator/plugins/turborepo/config.ts +16 -0
- package/src/orchestrator/plugins/types.ts +7 -7
- package/src/orchestrator/plugins/vercel/SKILL.md +99 -0
- package/src/orchestrator/plugins/vercel/config.ts +2 -3
- package/src/orchestrator/plugins/vitest/SKILL.md +166 -0
- package/src/orchestrator/plugins/vitest/config.ts +16 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +6 -4
- package/src/orchestrator/prompts/create-skill.prompt.md +6 -7
- package/src/orchestrator/skills/agent-hooks/SKILL.md +2 -2
- package/src/orchestrator/skills/memory-merger/SKILL.md +1 -1
- 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:
|
|
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:
|
|
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.
|
|
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.
|
|
177
|
-
-
|
|
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` |
|
|
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
|
|
96
|
-
2. **
|
|
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** —
|
|
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 (
|
|
113
|
-
- [ ] For process skills: at least one agent
|
|
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.
|
|
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.
|
|
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
|
|
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>` |
|