code
SkillFiles & storageGuides your agent to write TypeScript and React code with proper types, component patterns, and file organization.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the code skill
About this capability
Use when writing TypeScript/React code - covers type safety, component patterns, and file organization
What this skill tells your AI
The instructions your AI receives, as published by trycompai/comp in .agents/skills/code/SKILL.md and read by ahel’s review.
Source Cursor rule: .cursor/rules/code.mdc.
Original Cursor alwaysApply: false.
Code Standards
TypeScript
No any, No Unsafe Casts
// ✅ 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
// ✅ Generic
const first = <T>(items: T[]): T | undefined => items[0];
// ❌ Any
const first = (items: any[]): any => items[0];
React Patterns
Named Exports, PascalCase
// ✅ Named export, PascalCase file
// TaskCard.tsx
export function TaskCard({ task }: TaskCardProps) { ... }
// ❌ Default export, lowercase
export default function taskCard() { ... }
Derive State, Avoid useEffect
// ✅ 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
// External subscriptions
useEffect(() => {
const sub = eventSource.subscribe(handler);
return () => sub.unsubscribe();
}, []);
// DOM measurements
useEffect(() => {
setHeight(ref.current?.getBoundingClientRect().height);
}, []);
Toasts with Sonner
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
// ✅ 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
// ✅ 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
// ✅ Prefix with "handle"
const handleClick = () => { ... };
const handleSubmit = (e: FormEvent) => { ... };
const handleTaskCreate = (task: Task) => { ... };
Accessibility
// 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" />
Signals
- GitHub stars
- 2k
- Forks
- 412
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
code-trycompai- Source
- github.com/trycompai/comp