Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .agents/skills/audit-design-system/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: audit-design-system
description: Audit & fix design system usage — migrate @trycompai/ui and lucide-react to @trycompai/design-system
---

Audit the specified files for design system compliance. **Fix every issue found immediately.**

## Rules

1. **`@trycompai/design-system`** is the primary component library. `@trycompai/ui` is legacy — only use as last resort when no DS equivalent exists.
2. **Always check DS exports first** before reaching for `@trycompai/ui`. Run `node -e "console.log(Object.keys(require('@trycompai/design-system')))"` to check.
3. **Icons**: Use `@trycompai/design-system/icons` (Carbon icons), NOT `lucide-react`. Check with `node -e "const i = require('@trycompai/design-system/icons'); console.log(Object.keys(i).filter(k => k.match(/YourSearch/i)))"`.
4. **DS components that do NOT accept `className`**: `Text`, `Stack`, `HStack`, `Badge`, `Button` — wrap in `<div>` for custom styling.
5. **Button**: Use DS `Button` with `loading`, `iconLeft`, `iconRight` props instead of manually rendering spinners/icons.
6. **Layout**: Use `PageLayout`, `PageHeader`, `Stack`, `HStack`, `Section`, `SettingGroup`.
7. **Patterns**: Sheet (`Sheet > SheetContent > SheetHeader + SheetBody`), Drawer, Collapsible.

## Process
1. Read files specified in `$ARGUMENTS`
2. Find `@trycompai/ui` imports — check if DS equivalent exists
3. Find `lucide-react` imports — find matching Carbon icons
4. Migrate components and icons
5. Run build to verify: `bunx turbo run typecheck --filter=@trycompai/app`
34 changes: 34 additions & 0 deletions .agents/skills/audit-hooks/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: audit-hooks
description: Audit & fix hooks and API usage patterns — eliminate server actions, raw fetch, and stale patterns
---

Audit the specified files for hook and API usage compliance. **Fix every issue found immediately.**

## Forbidden Patterns (fix immediately)

1. **`useAction` from `next-safe-action`** → replace with SWR hook or custom mutation hook
2. **Server actions mutating via `@db`** → delete and use API hook instead
3. **Direct `@db` in client components** → replace with `apiClient` via hook
4. **Direct `@db` in Next.js pages for mutations** → replace with `serverApi`
5. **Raw `fetch()` without `credentials: 'include'`** → use `apiClient`
6. **`window.location.reload()` after mutations** → use SWR `mutate()`
7. **`router.refresh()` after mutations** → use SWR `mutate()`
8. **`useEffect` + `apiClient.get` for data fetching** → replace with `useSWR`
9. **Callback props for data refresh** (`onXxxAdded`, `onSuccess`) → remove, rely on SWR cache sharing

## Required Patterns

- **Client data fetching**: `useSWR` with `apiClient` or custom hook
- **Client mutations**: custom hooks wrapping `apiClient` with `mutate()` for cache invalidation
- **Server components**: `serverApi` from `apps/app/src/lib/api-server.ts`
- **SWR**: `fallbackData` for SSR data, `revalidateOnMount: !initialData`
- **API response**: lists = `response.data.data`, single = `response.data`
- **`mutate()` safety**: guard against `undefined` in optimistic update functions
- **`Array.isArray()` checks**: when consuming SWR data that could be stale

## Process
1. Read files specified in `$ARGUMENTS`
2. Find forbidden patterns and fix them
3. Ensure all data fetching uses SWR hooks
4. Run typecheck to verify: `bunx turbo run typecheck --filter=@trycompai/app`
42 changes: 42 additions & 0 deletions .agents/skills/audit-rbac/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: audit-rbac
description: Audit & fix RBAC and audit log compliance in API endpoints and frontend components
---

Audit the specified files or directories for RBAC and audit log compliance. **Fix every issue found immediately.**

## Rules

### API Endpoints (NestJS — `apps/api/src/`)
1. **Every mutation endpoint** (POST, PATCH, PUT, DELETE) MUST have `@RequirePermission('resource', 'action')`. If missing, **add it**.
2. **Read endpoints** (GET) should have `@RequirePermission('resource', 'read')`. If missing, **add it**.
3. **Self-endpoints** (e.g., `/me/preferences`) may skip `@RequirePermission` — authentication via `HybridAuthGuard` is sufficient.
4. **Controller format**: Must use `@Controller({ path: 'name', version: '1' })`, NOT `@Controller('v1/name')`. If wrong, **fix it**.
5. **Guards**: Use `@UseGuards(HybridAuthGuard, PermissionGuard)` at controller or endpoint level. Never skip PermissionGuard.
6. **Webhooks**: External webhook endpoints use `@Public()` — no auth required.

### Frontend Components (`apps/app/src/`)
1. **Every mutation element** (button, form submit, toggle, switch, file upload) MUST be gated with `usePermissions` from `@/hooks/use-permissions`. If not:
- **Create/Add buttons**: Wrap with `{hasPermission('resource', 'create') && <Button>...`
- **Edit/Delete in dropdown menus**: Wrap the menu item
- **Inline form fields on detail pages**: Add `disabled={!canUpdate}`
- **Status/property selectors**: Add `disabled={!canUpdate}`
2. **Actions columns** in tables: hide entire column when user lacks write permission.
3. **No manual role string parsing** (`role.includes('admin')`) — use `hasPermission()`.
4. **Nav items**: gate with `canAccessRoute(permissions, 'routeSegment')`.
5. **Page-level**: call `requireRoutePermission('segment', orgId)` server-side.

### Permission Resources
`organization`, `member`, `control`, `evidence`, `policy`, `risk`, `vendor`, `task`, `framework`, `audit`, `finding`, `questionnaire`, `integration`, `apiKey`, `trust`, `pentest`, `app`, `compliance`

### Multi-Product RBAC
- Products (compliance, pen testing) are org-level feature flags — NOT RBAC
- `app:read` gates compliance dashboard; `pentest:read` gates security product
- Custom roles can grant access to any combination of resources
- Portal-only resources (`policy`, `compliance`) do NOT grant app access

## Process
1. Read files specified in `$ARGUMENTS` (or scan the directory)
2. Check each rule above
3. **Fix every violation immediately** — don't just report
4. Run typecheck to verify: `bunx turbo run typecheck --filter=@trycompai/api --filter=@trycompai/app`
30 changes: 30 additions & 0 deletions .agents/skills/audit-tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: audit-tests
description: Audit & fix unit tests for permission-gated components
---

Check that unit tests exist and pass for permission-gated components. **Write missing tests immediately.**

## Infrastructure
- **Framework**: Vitest with jsdom
- **Component testing**: `@testing-library/react` + `@testing-library/jest-dom`
- **Setup**: `apps/app/src/test-utils/setup.ts`
- **Permission mocks**: `apps/app/src/test-utils/mocks/permissions.ts`
- **Run**: `cd apps/app && bunx vitest run`

## Required Test Pattern

Every component importing `usePermissions` MUST have tests covering:

1. **Admin (write) user**: mutation elements visible/enabled
2. **Auditor (read-only)**: mutation elements hidden/disabled
3. **Data always visible**: read-only content renders regardless of permissions

Use `setMockPermissions`, `ADMIN_PERMISSIONS`, `AUDITOR_PERMISSIONS` from test utils.

## Process
1. Find components with `usePermissions` in `$ARGUMENTS`
2. Check for corresponding `.test.tsx` files
3. Write missing tests following the pattern above
4. Fix any failing tests
5. Run: `cd apps/app && bunx vitest run`
12 changes: 6 additions & 6 deletions .agents/skills/better-auth-best-practices/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ description: Configure Better Auth server and client, set up database adapters,

## Setup Workflow

1. Install: `npm install better-auth`
1. Install: `bun add better-auth`
2. Set env vars: `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL`
3. Create `auth.ts` with database + config
4. Create route handler for your framework
5. Run `npx @better-auth/cli@latest migrate`
5. Run `bunx @better-auth/cli@latest migrate`
6. Verify: call `GET /api/auth/ok` — should return `{ status: "ok" }`

---
Expand All @@ -32,9 +32,9 @@ Only define `baseURL`/`secret` in config if env vars are NOT set.
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.

### CLI Commands
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
- `bunx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
- `bunx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
- `bunx @better-auth/cli@latest mcp --cursor` - Add MCP to AI tools

**Re-run after adding/changing plugins.**

Expand Down Expand Up @@ -172,4 +172,4 @@ For separate client/server projects: `createAuthClient<typeof auth>()`.
- [Options Reference](https://better-auth.com/docs/reference/options)
- [LLMs.txt](https://better-auth.com/llms.txt)
- [GitHub](https://github.com/better-auth/better-auth)
- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)
- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)
185 changes: 185 additions & 0 deletions .agents/skills/code/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
---
name: code
description: "Use when writing TypeScript/React code - covers type safety, component patterns, and file organization"
---

Source Cursor rule: `.cursor/rules/code.mdc`.
Original Cursor alwaysApply: `false`.

# Code Standards

## TypeScript

### No `any`, No Unsafe Casts

```tsx
// ✅ Validate with zod
const TaskSchema = z.object({ id: z.string(), title: z.string() });
const task = TaskSchema.parse(response.data);

// ✅ Use unknown and narrow
const parseResponse = (data: unknown): Task => {
if (!isTask(data)) throw new Error('Invalid');
return data;
};

// ❌ Never
const data: any = fetchData();
const task = response as Task;
const name = user!.name;
// @ts-ignore
```

### Generics Over Any

```tsx
// ✅ Generic
const first = <T>(items: T[]): T | undefined => items[0];

// ❌ Any
const first = (items: any[]): any => items[0];
```

## React Patterns

### Named Exports, PascalCase

```tsx
// ✅ Named export, PascalCase file
// TaskCard.tsx
export function TaskCard({ task }: TaskCardProps) { ... }

// ❌ Default export, lowercase
export default function taskCard() { ... }
```

### Derive State, Avoid useEffect

```tsx
// ✅ Derived
const completedCount = tasks.filter(t => t.completed).length;

// ❌ Synced state
const [count, setCount] = useState(0);
useEffect(() => {
setCount(tasks.filter(t => t.completed).length);
}, [tasks]);
```

### When useEffect IS Appropriate

```tsx
// External subscriptions
useEffect(() => {
const sub = eventSource.subscribe(handler);
return () => sub.unsubscribe();
}, []);

// DOM measurements
useEffect(() => {
setHeight(ref.current?.getBoundingClientRect().height);
}, []);
```

### Toasts with Sonner

```tsx
import { toast } from 'sonner';

toast.success('Task created');
toast.error('Failed to save');
toast.promise(saveTask(), {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed',
});
```

## File Structure

### Colocate at Route Level

```
app/(app)/[orgId]/tasks/
├── page.tsx # Server component
├── components/
│ └── TaskList.tsx # Client component
├── hooks/
│ └── useTasks.ts # SWR hook
└── data/
└── queries.ts # Server queries
```

### Share Only When Reused 3+ Times

```
src/components/shared/ # Cross-page components
src/hooks/ # Shared hooks (useApiSWR, useDebounce)
```

## Code Quality

### File Size Limit: 300 Lines

Split large files into focused components.

### Named Parameters for 2+ Args

```tsx
// ✅ Named
const createTask = ({ title, assigneeId }: CreateTaskParams) => { ... };
createTask({ title: 'Review PR', assigneeId: user.id });

// ❌ Positional
const createTask = (title: string, assigneeId: string) => { ... };
createTask('Review PR', user.id); // What's the 2nd param?
```

### Early Returns

```tsx
// ✅ Early return
function processTask(task: Task | null) {
if (!task) return null;
if (task.deleted) return null;
return <TaskCard task={task} />;
}

// ❌ Nested
function processTask(task) {
if (task) {
if (!task.deleted) {
return <TaskCard task={task} />;
}
}
return null;
}
```

### Event Handler Naming

```tsx
// ✅ Prefix with "handle"
const handleClick = () => { ... };
const handleSubmit = (e: FormEvent) => { ... };
const handleTaskCreate = (task: Task) => { ... };
```

## Accessibility

```tsx
// Interactive elements need keyboard support
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
aria-label="Delete task"
>
<TrashIcon />
</div>

// Form inputs need labels
<label htmlFor="task-name">Task Name</label>
<input id="task-name" type="text" />
```
Loading
Loading