@girardmedia/bootspring 1.1.0
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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Frontend Expert Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Specialized in React, Next.js App Router, modern component patterns, state management, and UI development with Tailwind CSS.
|
|
5
|
+
|
|
6
|
+
## Core Expertise
|
|
7
|
+
|
|
8
|
+
### React Server Components (Default)
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
// Server Components are the default in Next.js App Router
|
|
12
|
+
// They run on the server and can directly access data
|
|
13
|
+
|
|
14
|
+
// app/users/page.tsx - Server Component
|
|
15
|
+
import { prisma } from '@/lib/prisma';
|
|
16
|
+
|
|
17
|
+
export default async function UsersPage() {
|
|
18
|
+
// Direct database access - no API needed!
|
|
19
|
+
const users = await prisma.user.findMany({
|
|
20
|
+
take: 10,
|
|
21
|
+
orderBy: { createdAt: 'desc' }
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<main className="container mx-auto py-8">
|
|
26
|
+
<h1 className="text-2xl font-bold mb-4">Users</h1>
|
|
27
|
+
<ul className="space-y-2">
|
|
28
|
+
{users.map(user => (
|
|
29
|
+
<UserCard key={user.id} user={user} />
|
|
30
|
+
))}
|
|
31
|
+
</ul>
|
|
32
|
+
</main>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// This is also a Server Component
|
|
37
|
+
function UserCard({ user }: { user: User }) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="p-4 border rounded-lg">
|
|
40
|
+
<h2 className="font-medium">{user.name}</h2>
|
|
41
|
+
<p className="text-gray-500">{user.email}</p>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Client Components (When Needed)
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
'use client';
|
|
51
|
+
|
|
52
|
+
// Use 'use client' ONLY when you need:
|
|
53
|
+
// - useState, useEffect, useRef
|
|
54
|
+
// - Event handlers (onClick, onChange, etc.)
|
|
55
|
+
// - Browser APIs (window, document)
|
|
56
|
+
// - Third-party libraries that use hooks
|
|
57
|
+
|
|
58
|
+
import { useState, useTransition } from 'react';
|
|
59
|
+
import { updateUser } from '@/app/actions';
|
|
60
|
+
|
|
61
|
+
export function EditUserForm({ user }: { user: User }) {
|
|
62
|
+
const [name, setName] = useState(user.name);
|
|
63
|
+
const [isPending, startTransition] = useTransition();
|
|
64
|
+
|
|
65
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
startTransition(async () => {
|
|
68
|
+
await updateUser(user.id, { name });
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
74
|
+
<input
|
|
75
|
+
type="text"
|
|
76
|
+
value={name}
|
|
77
|
+
onChange={(e) => setName(e.target.value)}
|
|
78
|
+
className="border rounded px-3 py-2 w-full"
|
|
79
|
+
disabled={isPending}
|
|
80
|
+
/>
|
|
81
|
+
<button
|
|
82
|
+
type="submit"
|
|
83
|
+
disabled={isPending}
|
|
84
|
+
className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
|
85
|
+
>
|
|
86
|
+
{isPending ? 'Saving...' : 'Save'}
|
|
87
|
+
</button>
|
|
88
|
+
</form>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Component Composition Pattern
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// Compose Server and Client Components effectively
|
|
97
|
+
|
|
98
|
+
// ServerWrapper.tsx (Server Component)
|
|
99
|
+
import { prisma } from '@/lib/prisma';
|
|
100
|
+
import { InteractiveList } from './InteractiveList';
|
|
101
|
+
|
|
102
|
+
export async function ServerWrapper() {
|
|
103
|
+
const items = await prisma.item.findMany();
|
|
104
|
+
|
|
105
|
+
// Pass data to client component
|
|
106
|
+
return <InteractiveList initialItems={items} />;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// InteractiveList.tsx (Client Component)
|
|
110
|
+
'use client';
|
|
111
|
+
|
|
112
|
+
import { useState } from 'react';
|
|
113
|
+
|
|
114
|
+
export function InteractiveList({ initialItems }: { initialItems: Item[] }) {
|
|
115
|
+
const [items, setItems] = useState(initialItems);
|
|
116
|
+
const [filter, setFilter] = useState('');
|
|
117
|
+
|
|
118
|
+
const filteredItems = items.filter(item =>
|
|
119
|
+
item.name.toLowerCase().includes(filter.toLowerCase())
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<input
|
|
125
|
+
type="search"
|
|
126
|
+
placeholder="Filter..."
|
|
127
|
+
value={filter}
|
|
128
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
129
|
+
className="border rounded px-3 py-2 mb-4 w-full"
|
|
130
|
+
/>
|
|
131
|
+
<ul className="space-y-2">
|
|
132
|
+
{filteredItems.map(item => (
|
|
133
|
+
<li key={item.id} className="p-2 border rounded">
|
|
134
|
+
{item.name}
|
|
135
|
+
</li>
|
|
136
|
+
))}
|
|
137
|
+
</ul>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### State Management
|
|
144
|
+
|
|
145
|
+
#### Local State (useState)
|
|
146
|
+
```tsx
|
|
147
|
+
// For component-specific state
|
|
148
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### URL State (useSearchParams)
|
|
152
|
+
```tsx
|
|
153
|
+
'use client';
|
|
154
|
+
|
|
155
|
+
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
|
|
156
|
+
|
|
157
|
+
export function Filters() {
|
|
158
|
+
const searchParams = useSearchParams();
|
|
159
|
+
const router = useRouter();
|
|
160
|
+
const pathname = usePathname();
|
|
161
|
+
|
|
162
|
+
const updateFilter = (key: string, value: string) => {
|
|
163
|
+
const params = new URLSearchParams(searchParams);
|
|
164
|
+
if (value) {
|
|
165
|
+
params.set(key, value);
|
|
166
|
+
} else {
|
|
167
|
+
params.delete(key);
|
|
168
|
+
}
|
|
169
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<select
|
|
174
|
+
value={searchParams.get('status') ?? ''}
|
|
175
|
+
onChange={(e) => updateFilter('status', e.target.value)}
|
|
176
|
+
>
|
|
177
|
+
<option value="">All</option>
|
|
178
|
+
<option value="active">Active</option>
|
|
179
|
+
<option value="archived">Archived</option>
|
|
180
|
+
</select>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Global State (Zustand)
|
|
186
|
+
```tsx
|
|
187
|
+
// store/user-store.ts
|
|
188
|
+
import { create } from 'zustand';
|
|
189
|
+
import { persist } from 'zustand/middleware';
|
|
190
|
+
|
|
191
|
+
interface UserStore {
|
|
192
|
+
user: User | null;
|
|
193
|
+
setUser: (user: User | null) => void;
|
|
194
|
+
preferences: { theme: 'light' | 'dark' };
|
|
195
|
+
setTheme: (theme: 'light' | 'dark') => void;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const useUserStore = create<UserStore>()(
|
|
199
|
+
persist(
|
|
200
|
+
(set) => ({
|
|
201
|
+
user: null,
|
|
202
|
+
setUser: (user) => set({ user }),
|
|
203
|
+
preferences: { theme: 'light' },
|
|
204
|
+
setTheme: (theme) => set((state) => ({
|
|
205
|
+
preferences: { ...state.preferences, theme }
|
|
206
|
+
})),
|
|
207
|
+
}),
|
|
208
|
+
{ name: 'user-storage' }
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Usage in component
|
|
213
|
+
'use client';
|
|
214
|
+
|
|
215
|
+
export function ThemeToggle() {
|
|
216
|
+
const { preferences, setTheme } = useUserStore();
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<button onClick={() => setTheme(preferences.theme === 'light' ? 'dark' : 'light')}>
|
|
220
|
+
Toggle Theme
|
|
221
|
+
</button>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Tailwind CSS Patterns
|
|
227
|
+
|
|
228
|
+
#### Responsive Design
|
|
229
|
+
```tsx
|
|
230
|
+
<div className="
|
|
231
|
+
grid
|
|
232
|
+
grid-cols-1 /* Mobile: 1 column */
|
|
233
|
+
sm:grid-cols-2 /* Small: 2 columns */
|
|
234
|
+
lg:grid-cols-3 /* Large: 3 columns */
|
|
235
|
+
xl:grid-cols-4 /* XL: 4 columns */
|
|
236
|
+
gap-4
|
|
237
|
+
">
|
|
238
|
+
{items.map(item => <Card key={item.id} {...item} />)}
|
|
239
|
+
</div>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Component Variants with cva
|
|
243
|
+
```tsx
|
|
244
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
245
|
+
import { cn } from '@/lib/utils';
|
|
246
|
+
|
|
247
|
+
const buttonVariants = cva(
|
|
248
|
+
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
249
|
+
{
|
|
250
|
+
variants: {
|
|
251
|
+
variant: {
|
|
252
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
253
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
254
|
+
outline: 'border border-input bg-background hover:bg-accent',
|
|
255
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
256
|
+
},
|
|
257
|
+
size: {
|
|
258
|
+
default: 'h-10 px-4 py-2',
|
|
259
|
+
sm: 'h-9 rounded-md px-3',
|
|
260
|
+
lg: 'h-11 rounded-md px-8',
|
|
261
|
+
icon: 'h-10 w-10',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
defaultVariants: {
|
|
265
|
+
variant: 'default',
|
|
266
|
+
size: 'default',
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
interface ButtonProps
|
|
272
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
273
|
+
VariantProps<typeof buttonVariants> {}
|
|
274
|
+
|
|
275
|
+
export function Button({ className, variant, size, ...props }: ButtonProps) {
|
|
276
|
+
return (
|
|
277
|
+
<button
|
|
278
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
279
|
+
{...props}
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Loading & Error States
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
// loading.tsx - Automatic loading UI
|
|
289
|
+
export default function Loading() {
|
|
290
|
+
return (
|
|
291
|
+
<div className="flex items-center justify-center min-h-[400px]">
|
|
292
|
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500" />
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// error.tsx - Automatic error UI
|
|
298
|
+
'use client';
|
|
299
|
+
|
|
300
|
+
export default function Error({
|
|
301
|
+
error,
|
|
302
|
+
reset,
|
|
303
|
+
}: {
|
|
304
|
+
error: Error & { digest?: string };
|
|
305
|
+
reset: () => void;
|
|
306
|
+
}) {
|
|
307
|
+
return (
|
|
308
|
+
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
|
|
309
|
+
<h2 className="text-xl font-semibold">Something went wrong!</h2>
|
|
310
|
+
<p className="text-gray-500">{error.message}</p>
|
|
311
|
+
<button
|
|
312
|
+
onClick={reset}
|
|
313
|
+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
314
|
+
>
|
|
315
|
+
Try again
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Suspense Boundaries
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
import { Suspense } from 'react';
|
|
326
|
+
|
|
327
|
+
export default function DashboardPage() {
|
|
328
|
+
return (
|
|
329
|
+
<div className="grid grid-cols-2 gap-4">
|
|
330
|
+
<Suspense fallback={<CardSkeleton />}>
|
|
331
|
+
<RevenueCard />
|
|
332
|
+
</Suspense>
|
|
333
|
+
<Suspense fallback={<CardSkeleton />}>
|
|
334
|
+
<UsersCard />
|
|
335
|
+
</Suspense>
|
|
336
|
+
<Suspense fallback={<TableSkeleton />}>
|
|
337
|
+
<RecentOrders />
|
|
338
|
+
</Suspense>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function CardSkeleton() {
|
|
344
|
+
return (
|
|
345
|
+
<div className="h-32 bg-gray-100 rounded-lg animate-pulse" />
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Component Checklist
|
|
351
|
+
|
|
352
|
+
- [ ] Server Component by default
|
|
353
|
+
- [ ] 'use client' only when necessary
|
|
354
|
+
- [ ] Props typed with TypeScript
|
|
355
|
+
- [ ] Loading state handled
|
|
356
|
+
- [ ] Error boundary in place
|
|
357
|
+
- [ ] Accessible (keyboard nav, ARIA labels)
|
|
358
|
+
- [ ] Responsive (mobile-first)
|
|
359
|
+
- [ ] No inline styles (use Tailwind)
|
|
360
|
+
- [ ] Component is focused and small
|
|
361
|
+
- [ ] Reusable where appropriate
|
|
362
|
+
|
|
363
|
+
## Trigger Keywords
|
|
364
|
+
component, react, ui, tailwind, state, client, server component, hook, useState, useEffect, form, button, input, modal, dropdown, responsive, mobile, layout, grid, flex, styling, css
|
package/agents/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Agents Module
|
|
3
|
+
* Programmatic access to agent data and invocation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// Agent registry from cli/agent.js
|
|
10
|
+
const AGENTS = {
|
|
11
|
+
'database-expert': {
|
|
12
|
+
name: 'Database Expert',
|
|
13
|
+
expertise: ['SQL', 'Prisma', 'PostgreSQL', 'migrations', 'query optimization'],
|
|
14
|
+
description: 'Expert in database design, queries, and optimization'
|
|
15
|
+
},
|
|
16
|
+
'security-expert': {
|
|
17
|
+
name: 'Security Expert',
|
|
18
|
+
expertise: ['OWASP', 'authentication', 'authorization', 'encryption', 'vulnerabilities'],
|
|
19
|
+
description: 'Expert in application security and best practices'
|
|
20
|
+
},
|
|
21
|
+
'frontend-expert': {
|
|
22
|
+
name: 'Frontend Expert',
|
|
23
|
+
expertise: ['React', 'Next.js', 'Tailwind', 'components', 'state management'],
|
|
24
|
+
description: 'Expert in frontend development and UI'
|
|
25
|
+
},
|
|
26
|
+
'backend-expert': {
|
|
27
|
+
name: 'Backend Expert',
|
|
28
|
+
expertise: ['Node.js', 'APIs', 'server actions', 'middleware', 'serverless'],
|
|
29
|
+
description: 'Expert in backend development and server-side logic'
|
|
30
|
+
},
|
|
31
|
+
'api-expert': {
|
|
32
|
+
name: 'API Expert',
|
|
33
|
+
expertise: ['REST', 'GraphQL', 'tRPC', 'webhooks', 'API design'],
|
|
34
|
+
description: 'Expert in API design and integration'
|
|
35
|
+
},
|
|
36
|
+
'testing-expert': {
|
|
37
|
+
name: 'Testing Expert',
|
|
38
|
+
expertise: ['Vitest', 'Jest', 'Playwright', 'coverage', 'TDD'],
|
|
39
|
+
description: 'Expert in testing strategies and implementation'
|
|
40
|
+
},
|
|
41
|
+
'performance-expert': {
|
|
42
|
+
name: 'Performance Expert',
|
|
43
|
+
expertise: ['optimization', 'caching', 'profiling', 'Core Web Vitals', 'lazy loading'],
|
|
44
|
+
description: 'Expert in application performance optimization'
|
|
45
|
+
},
|
|
46
|
+
'devops-expert': {
|
|
47
|
+
name: 'DevOps Expert',
|
|
48
|
+
expertise: ['CI/CD', 'Docker', 'deployment', 'monitoring', 'infrastructure'],
|
|
49
|
+
description: 'Expert in DevOps practices and deployment'
|
|
50
|
+
},
|
|
51
|
+
'ui-ux-expert': {
|
|
52
|
+
name: 'UI/UX Expert',
|
|
53
|
+
expertise: ['design systems', 'accessibility', 'user experience', 'responsive design'],
|
|
54
|
+
description: 'Expert in UI/UX design and implementation'
|
|
55
|
+
},
|
|
56
|
+
'architecture-expert': {
|
|
57
|
+
name: 'Architecture Expert',
|
|
58
|
+
expertise: ['system design', 'patterns', 'scalability', 'microservices'],
|
|
59
|
+
description: 'Expert in software architecture and system design'
|
|
60
|
+
},
|
|
61
|
+
'code-review-expert': {
|
|
62
|
+
name: 'Code Review Expert',
|
|
63
|
+
expertise: ['code quality', 'best practices', 'refactoring', 'maintainability'],
|
|
64
|
+
description: 'Expert in code review and quality assessment'
|
|
65
|
+
},
|
|
66
|
+
'vercel-expert': {
|
|
67
|
+
name: 'Vercel Expert',
|
|
68
|
+
expertise: ['Vercel', 'serverless', 'edge functions', 'deployment', 'analytics'],
|
|
69
|
+
description: 'Expert in Vercel platform and deployment'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all available agents
|
|
75
|
+
*/
|
|
76
|
+
function getAgents() {
|
|
77
|
+
return AGENTS;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get a specific agent by name
|
|
82
|
+
*/
|
|
83
|
+
function getAgent(name) {
|
|
84
|
+
return AGENTS[name] || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* List agent names
|
|
89
|
+
*/
|
|
90
|
+
function listAgents() {
|
|
91
|
+
return Object.keys(AGENTS);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get agent context file path
|
|
96
|
+
*/
|
|
97
|
+
function getAgentContextPath(name) {
|
|
98
|
+
const agentsDir = path.dirname(__dirname);
|
|
99
|
+
return path.join(agentsDir, 'agents', name, 'context.md');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Load agent context file
|
|
104
|
+
*/
|
|
105
|
+
function loadAgentContext(name) {
|
|
106
|
+
const contextPath = getAgentContextPath(name);
|
|
107
|
+
if (fs.existsSync(contextPath)) {
|
|
108
|
+
return fs.readFileSync(contextPath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Find agents by expertise keyword
|
|
115
|
+
*/
|
|
116
|
+
function findAgentsByExpertise(keyword) {
|
|
117
|
+
const matches = [];
|
|
118
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
119
|
+
|
|
120
|
+
for (const [id, agent] of Object.entries(AGENTS)) {
|
|
121
|
+
const hasMatch = agent.expertise.some(exp =>
|
|
122
|
+
exp.toLowerCase().includes(lowerKeyword)
|
|
123
|
+
);
|
|
124
|
+
if (hasMatch) {
|
|
125
|
+
matches.push({ id, ...agent });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return matches;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
AGENTS,
|
|
134
|
+
getAgents,
|
|
135
|
+
getAgent,
|
|
136
|
+
listAgents,
|
|
137
|
+
getAgentContextPath,
|
|
138
|
+
loadAgentContext,
|
|
139
|
+
findAgentsByExpertise
|
|
140
|
+
};
|