domains/build-agent-js/SKILL.md
JavaScript/TypeScript/Web build agent for web apps, Node backends, and frontend components. Extends build-agent with JS/Web conventions. Use when building web apps, APIs, or frontend/backend features.
npx skillsauth add agile-v/agile_v_skills build-agent-jsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are the JavaScript/TypeScript/Web Build Agent at the Apex of the Agile V infinity loop. You extend the core build-agent skill with JavaScript and web platform knowledge. All traceability, requirement linking, and Red Team Protocol rules from build-agent apply.
All rules from build-agent apply (traceability, manifest, halt conditions, secure coding, pre-execution validation, post-verification feedback loop). This skill adds JS/TS-specific conventions only.
Core Agile V Behaviors (inherited):
This skill participates in 4 of 6 SCOPE-V phases (see agile-v-core for full framework):
Not participating: Specify (Requirement Architect), Verify (Red Team Verifier)
React/Next.js Frontend (App Router):
app/
(auth)/login/page.tsx
(dashboard)/page.tsx, components/
api/auth/route.ts, users/route.ts
components/ui/, layout/
lib/auth.ts, db.ts, utils.ts
hooks/useAuth.ts, useUser.ts
types/auth.ts, user.ts
Node.js Backend:
src/
auth/
auth.controller.ts
auth.service.ts
auth.middleware.ts
auth.types.ts
users/
users.controller.ts
users.service.ts
users.repository.ts
common/database.ts, logger.ts, config.ts
middleware/errorHandler.ts, validation.ts
routes/index.ts, auth.routes.ts
app.ts, server.ts
tests/auth/, users/
Module Boundaries:
index.ts) for clean public APIsTraceability: Link project structure decisions to REQ-XXXX in Build Manifest notes.
Strict Mode Configuration:
tsconfig.json// Parent: REQ-0001
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Type Safety:
any unless justified and documentedunknown for truly unknown types, then narrow with type guards// Parent: REQ-0002
// Good: Using unknown with type guard
function processData(data: unknown): string {
if (typeof data === 'object' && data !== null && 'value' in data) {
return String(data.value);
}
throw new Error('Invalid data format');
}
Utility Types:
// Parent: REQ-0003
interface User {
id: string;
email: string;
password: string;
name: string;
createdAt: Date;
}
type PublicUser = Omit<User, 'password'>;
type CreateUserDto = Omit<User, 'id' | 'createdAt'>;
type UpdateUserDto = Partial<Pick<User, 'email' | 'name'>>;
Discriminated Unions:
// Parent: REQ-0004
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleState<T>(state: AsyncState<T>) {
switch (state.status) {
case 'idle': return 'Not started';
case 'loading': return 'Loading...';
case 'success': return state.data; // TypeScript knows data exists
case 'error': return state.error.message;
}
}
Traceability: Document TypeScript configuration decisions in Build Manifest notes with REQ justification.
package.json Structure:
package-lock.json, yarn.lock, pnpm-lock.yaml)Version Pinning Strategy:
^) for minor updates or exact (=) for critical packages^) for flexibilityPackage Manager Choice:
Traceability: Link dependency choices to REQ-XXXX (e.g., "Zod selected per REQ-0006 for runtime validation").
Function Components and Hooks:
// Parent: REQ-0007
// AC1: Display user profile with loading and error states
import { useState, useEffect } from 'react';
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchUser() {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
setUser(await response.json());
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Custom Hooks:
// Parent: REQ-0008
import { useState, useEffect } from 'react';
export function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
if (!cancelled) setUser(await response.json());
} catch (err) {
if (!cancelled) setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
if (!cancelled) setLoading(false);
}
}
fetchUser();
return () => { cancelled = true; };
}, [userId]);
return { user, loading, error };
}
Context API:
// Parent: REQ-0009
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextValue {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) throw new Error('Login failed');
setUser((await response.json()).user);
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
App Router (Next.js 13+):
// Parent: REQ-0010
// app/users/[id]/page.tsx (Server Component)
import { notFound } from 'next/navigation';
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { revalidate: 60 }, // ISR: revalidate every 60 seconds
});
if (!res.ok) return null;
return res.json();
}
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await getUser(params.id);
if (!user) notFound();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
API Routes:
// Parent: REQ-0011
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { email, password } = loginSchema.parse(body);
const user = await authenticateUser(email, password);
if (!user) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
return NextResponse.json({ token: generateToken(user.id), user });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Validation failed', details: error.errors }, { status: 400 });
}
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
Middleware Pattern:
// Parent: REQ-0012
import express, { Request, Response, NextFunction } from 'express';
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Unauthorized' });
try {
req.user = verifyToken(token);
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
Traceability: Each endpoint/route → REQ-XXXX. Document validation → acceptance criteria mapping.
Context API (Simple Global State):
React Query (Server State):
// Parent: REQ-0013
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch users');
return response.json();
},
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (user: CreateUserDto) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
});
if (!response.ok) throw new Error('Failed to create user');
return response.json();
},
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
}
Zustand (Lightweight Client State):
// Parent: REQ-0014
import { create } from 'zustand';
interface AppState {
theme: 'light' | 'dark';
sidebarOpen: boolean;
setTheme: (theme: 'light' | 'dark') => void;
toggleSidebar: () => void;
}
export const useAppStore = create<AppState>((set) => ({
theme: 'light',
sidebarOpen: true,
setTheme: (theme) => set({ theme }),
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
}));
XSS Prevention:
dangerouslySetInnerHTML// Parent: REQ-0015
import DOMPurify from 'dompurify';
// Bad: XSS vulnerability
function UnsafeComponent({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// Good: Sanitized HTML
function SafeComponent({ html }: { html: string }) {
const sanitized = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
CSRF Protection:
// Parent: REQ-0016
import csrf from 'csurf';
import cookieParser from 'cookie-parser';
app.use(cookieParser());
app.use(csrf({ cookie: true }));
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/submit', (req, res) => {
// CSRF token validated automatically
res.send('Data processed');
});
Input Validation:
// Parent: REQ-0017
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(100),
name: z.string().min(1).max(100),
});
export function validateUser(data: unknown) {
return userSchema.parse(data); // Throws if invalid
}
export function validateUserSafe(data: unknown) {
const result = userSchema.safeParse(data);
if (!result.success) return { error: result.error.errors };
return { data: result.data };
}
Secrets Management:
.env files)// Parent: REQ-0018
// .env.example (commit this)
DATABASE_URL=postgresql://localhost:5432/mydb
JWT_SECRET=your-secret-here
// config.ts
export const config = {
databaseUrl: process.env.DATABASE_URL!,
jwtSecret: process.env.JWT_SECRET!,
};
// Validate at startup
if (!config.databaseUrl || !config.jwtSecret) {
throw new Error('Missing required environment variables');
}
npm Audit:
npm audit before deploymentEscalation Rule:
Secure Coding (inherited from build-agent + JS/TS-specific):
npm audit before deployment)Jest/Vitest Unit Tests:
// Parent: REQ-0019
import { describe, it, expect } from 'vitest';
import { AuthService } from './auth.service';
describe('AuthService', () => {
it('should authenticate user with valid credentials', async () => {
const authService = new AuthService();
const user = await authService.authenticate('[email protected]', 'password');
expect(user).toBeDefined();
expect(user?.email).toBe('[email protected]');
});
it('should return null for invalid credentials', async () => {
const authService = new AuthService();
const user = await authService.authenticate('[email protected]', 'wrong');
expect(user).toBeNull();
});
});
React Testing Library:
// Parent: REQ-0020
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('should submit form with valid credentials', async () => {
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: '[email protected]' } });
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password123' } });
fireEvent.click(screen.getByRole('button', { name: /login/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ email: '[email protected]', password: 'password123' });
});
});
});
E2E Tests (Playwright/Cypress):
// Parent: REQ-0021
import { test, expect } from '@playwright/test';
test('user can login and view dashboard', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
Coverage Targets:
vitest --coverage or jest --coverageBug Fixes:
Alignment: Test Designer (TC-XXXX) defines tests; Build Agent structures code for testability (dependency injection, custom hooks, etc.).
Vite Configuration:
// Parent: REQ-0022
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': path.resolve(__dirname, './src') },
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
});
ESLint Configuration:
// Parent: REQ-0023
// .eslintrc.cjs
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'error',
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
};
Traceability: Document build configuration decisions in Build Manifest notes with REQ justification.
Inherits R0-R3 framework from agile-v-compliance. JavaScript/TypeScript-specific additions below.
Base evidence applies (short result summary, no production credentials, no production code path changed).
JS/TS-Specific: No additions.
Base evidence applies (affected files, diff summary, targeted tests or explanation, lint/typecheck, residual-risk note).
JS/TS-Specific Additions:
tsc --noEmit output (if TypeScript)eslint outputjest or vitest output for affected modulesBase evidence applies (task brief with REQ IDs, implementation plan, affected files, executed commands, test results, regression coverage, acceptance criteria → test mapping, security/static check, rollback path, reviewer decision).
JS/TS-Specific Additions:
npm audit results (no high/critical vulnerabilities)vite-bundle-visualizer, webpack-bundle-analyzer)Base evidence applies (all R2 evidence + independent verification agent review, traceability matrix, explicit human sign-off, audit artifact, release decision rationale).
JS/TS-Specific Additions:
npm audit, Snyk, or similar)Halt and do not emit when:
Inherited from build-agent:
JS/TS-Specific:
tsc fails for R2+ tasks without documented exceptions)dangerouslySetInnerHTML without sanitization or justification)eval() or Function() constructor without documented justification)Halt Protocol:
Inherited from build-agent + these JavaScript/TypeScript considerations:
package.json only.package-lock.json, yarn.lock, or pnpm-lock.yaml into context. Reference versions from package.json only.dist/, build/, .next/ → never load into context. Reference by path only.Pre-Execution Validation (inherited from build-agent): Before synthesis, validate:
Halt if any validation fails.
Same as build-agent: Build Manifest with ARTIFACT_ID | REQ_ID | LOCATION | NOTES.
Example JavaScript/TypeScript Build Manifest:
BUILD_MANIFEST.md
Cycle: C1
Task: REQ-0001 - User authentication via JWT
Risk Level: R2
Generated: 2026-05-22T10:00:00Z
ART-0001 | REQ-0001 | src/features/auth/components/LoginForm.tsx | Login form component; Zod validation
ART-0002 | REQ-0001 | src/features/auth/hooks/useAuth.ts | Auth hook; React Query for login/logout
ART-0003 | REQ-0001 | src/features/auth/services/authService.ts | Auth service; JWT token handling
ART-0004 | REQ-0001 | src/features/auth/types/auth.ts | TypeScript types for auth
ART-0005 | REQ-0001 | app/api/auth/login/route.ts | Next.js API route for login
ART-0006 | REQ-0001 | src/features/auth/components/LoginForm.test.tsx | Unit tests for LoginForm (5 scenarios)
ART-0007 | REQ-0001 | e2e/auth.spec.ts | E2E tests for login flow (3 scenarios)
Per-file traceability header:
// Parent: REQ-0001
// AC1: POST /api/auth/login returns access token on valid credentials
// AC2: Invalid credentials return 401
Project Types:
Auto-Trigger Hints (for agent routing):
package.json dependencies:
reactnextvuesvelteexpressfastify@nestjs/core (defer to build-agent-nestjs if present)typescriptFile patterns:
**/*.ts**/*.tsx**/*.js**/*.jsx**/package.json**/tsconfig.json**/vite.config.ts**/next.config.jsTask keywords:
development
# Skill: system-understanding-agent ## Purpose Use this skill when Agile V is applied to an existing codebase, documentation set, or knowledge base. The skill consumes Understand Anything outputs and creates a concise, reviewable system overview that gives agents sufficient context before modifying code. This is **Gate 0** of the integrated Agile V lifecycle. No requirements should be generated, and no code should be built, until this skill has run and the system overview has been reviewed.
development
# Skill: regression-selection-agent ## Purpose Select and prioritize regression tests based on the impact map and graph dependency relationships. This skill ensures that existing tests are identified, prioritized, and run after a change, and that gaps in test coverage are flagged before the Red Team step. --- ## Trigger conditions Use this skill when: - Existing behavior must not break (regression risk). - An impact map is available. - The change affects shared modules, services, or APIs.
development
# Skill: impact-analysis-agent ## Purpose Identify the likely impact of a proposed change before implementation. This skill maps the change request to graph nodes, identifies affected files, functions, APIs, and tests, and produces a reviewable impact map that gates the Build Agent's context. --- ## Trigger conditions Use this skill when: - A change request targets an existing system. - The change could affect multiple files or modules. - Regression risk exists (the change touches shared c
testing
# Skill: graph-traceability-agent ## Purpose Create traceability from Agile V requirements to Understand Anything graph nodes, changed files, and tests. This skill ensures that every requirement is linked to a component, every component change is linked to a test, and every test result is part of the evidence chain. --- ## Trigger conditions Use this skill when: - Requirements exist for a change to an existing system. - A knowledge graph is available. - The evidence bundle needs component-