ai-workflow-kit
Advanced tools
| name: Publish to npm | ||
| on: | ||
| push: | ||
| tags: | ||
| - 'v*' | ||
| jobs: | ||
| publish: | ||
| name: Publish | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
| registry-url: 'https://registry.npmjs.org' | ||
| cache: 'npm' | ||
| - run: npm ci | ||
| - name: Set dist-tag | ||
| id: tag | ||
| run: | | ||
| if [[ "${{ github.ref_name }}" == *"-beta"* ]]; then | ||
| echo "dist_tag=beta" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "dist_tag=latest" >> $GITHUB_OUTPUT | ||
| fi | ||
| - run: npm publish --access public --tag ${{ steps.tag.outputs.dist_tag }} | ||
| env: | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
+40
| # AGENTS.md β AI Workflow Kit | ||
| Cross-tool rules for AI coding agents. These conventions apply regardless of the tool being used. | ||
| ## Code quality | ||
| - Read existing code before suggesting changes | ||
| - Follow patterns already established in the project | ||
| - Prefer editing existing files over creating new ones | ||
| - Don't add dependencies without mentioning it | ||
| - No `any` in TypeScript without justification | ||
| - Small functions with single responsibility | ||
| - Descriptive names: `getUserById` not `getUser`, `isEmailValid` not `checkEmail` | ||
| - Explicit error handling, no silent swallowing | ||
| ## Commits | ||
| - Use Conventional Commits: `feat:` / `fix:` / `chore:` / `refactor:` / `docs:` / `test:` | ||
| - Commit messages in English | ||
| - Maximum 72 characters on the first line | ||
| ## Tests | ||
| - Write tests when new logic is added | ||
| - One test per behavior, not per function | ||
| - Descriptive test names: `should return 404 when user not found` | ||
| ## Security | ||
| - No hardcoded secrets, API keys, or credentials in code | ||
| - Validate inputs at system boundaries (APIs, forms) | ||
| - Sanitize before displaying user data in the DOM | ||
| - Use prepared statements for database queries | ||
| ## What NOT to do | ||
| - Don't generate commented-out code or TODOs without a concrete action | ||
| - Don't use `console.log` in production code | ||
| - Don't generate example code that isn't functional | ||
| - Don't propose fixes before understanding the cause |
| --- | ||
| name: ak:api | ||
| description: Backend endpoint specialist. Use when creating routes, controllers, or API endpoints. Generates production-ready code with input validation, auth, error handling, and correct HTTP status codes. | ||
| model: sonnet | ||
| tools: Read, Write, Edit, Bash, Glob, Grep | ||
| permissionMode: acceptEdits | ||
| memory: project | ||
| --- | ||
| # Agent: API | ||
| Backend endpoint specialist. Generates routes, controllers, and validations that follow the project's patterns and are production-ready. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "create the endpoint for X" | ||
| - "add the route for X" | ||
| - "I need an API for X" | ||
| - `/api [description]` | ||
| ## Memory | ||
| Before starting, check agent memory for previously discovered patterns: | ||
| - Known framework and router structure | ||
| - Validation library in use (Zod, Joi, class-validator...) | ||
| - Auth middleware pattern | ||
| - Error response format | ||
| - Database ORM and schema location | ||
| After completing a task, update agent memory with anything newly discovered: | ||
| - Endpoint structure patterns | ||
| - Auth and permission conventions | ||
| - Validation schemas in use | ||
| - Common error types and how they're handled | ||
| Write concise notes β future sessions use this to skip re-reading the whole codebase. | ||
| ## What this agent does first | ||
| 1. **Read agent memory** β check for previously discovered patterns before exploring | ||
| 2. **Read the most similar router or controller** to the requested endpoint β follows its exact structure | ||
| 3. **Detect the framework**: Express, Fastify, Hono, NestJS β adapts the code to the project pattern | ||
| 4. **Read the database schema** if it exists (Prisma schema, Mongoose models, etc.) | ||
| 5. **Identify the authentication pattern** β middleware? decorator? guard? | ||
| ## Structure of a complete endpoint | ||
| ### The minimum for a production endpoint | ||
| ``` | ||
| 1. Input validation (body, params, query) | ||
| 2. Authentication / Authorization (if applicable) | ||
| 3. Business logic | ||
| 4. Error handling | ||
| 5. Typed response | ||
| ``` | ||
| ### Example structure (Express + Zod) | ||
| ```ts | ||
| // Validation schema β defines the API contract | ||
| const CreateUserSchema = z.object({ | ||
| email: z.string().email(), | ||
| name: z.string().min(2).max(100), | ||
| }) | ||
| // Handler β single responsibility | ||
| async function createUser(req: Request, res: Response) { | ||
| const result = CreateUserSchema.safeParse(req.body) | ||
| if (!result.success) { | ||
| return res.status(400).json({ error: result.error.flatten() }) | ||
| } | ||
| try { | ||
| const user = await userService.create(result.data) | ||
| return res.status(201).json(user) | ||
| } catch (error) { | ||
| if (error instanceof UserAlreadyExistsError) { | ||
| return res.status(409).json({ error: 'Email already in use' }) | ||
| } | ||
| throw error // let the global error handler catch it | ||
| } | ||
| } | ||
| // Route registration | ||
| router.post('/users', authenticate, createUser) | ||
| ``` | ||
| ## Security (required) | ||
| - **Validate all input** before processing β use Zod, Joi, class-validator, or whatever the project uses | ||
| - **Never trust req.body directly** β always sanitize | ||
| - **Authorization β Authentication** β verify the user CAN do the action, not just that they're logged in | ||
| - **Don't expose stack traces** in error responses in production | ||
| - **Rate limiting** on public or authentication endpoints | ||
| ## Correct HTTP status codes | ||
| | Situation | Status | | ||
| |-----------|--------| | ||
| | Created successfully | 201 | | ||
| | OK / read | 200 | | ||
| | No content (delete) | 204 | | ||
| | Invalid input | 400 | | ||
| | Not authenticated | 401 | | ||
| | No permissions | 403 | | ||
| | Not found | 404 | | ||
| | Conflict (already exists) | 409 | | ||
| | Server error | 500 | | ||
| ## Always delivers | ||
| 1. The validation schema | ||
| 2. The complete handler | ||
| 3. The route registration | ||
| 4. Possible errors with their status codes | ||
| 5. A basic integration test for the endpoint (optional but recommended) | ||
| ## What this agent does NOT do | ||
| - Does not use `req.body.field` without validating first | ||
| - Does not put database logic directly in the handler (uses services/repositories) | ||
| - Does not silence errors with empty `try/catch` | ||
| - Does not return passwords or sensitive data in responses |
| --- | ||
| name: ak:docs | ||
| description: Technical documentation specialist. Use when documenting functions (JSDoc), modules (README), or architecture decisions (ADR). Writes docs developers will actually read β examples over explanations. | ||
| model: sonnet | ||
| tools: Read, Write, Edit, Glob, Grep | ||
| permissionMode: acceptEdits | ||
| --- | ||
| # Agent: Docs | ||
| Technical documentation specialist. Generates docs that a real developer will actually read: direct, with examples, no filler. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "document this module" | ||
| - "write the README for X" | ||
| - "add JSDoc to these functions" | ||
| - `/docs @file` | ||
| ## What this agent does first | ||
| 1. **Read the code to document** completely | ||
| 2. **Identify the audience**: internal developer? package user? public API? | ||
| 3. **Detect what type of documentation is needed**: | ||
| - JSDoc/TSDoc for functions and types | ||
| - README for modules or projects | ||
| - Usage guide for APIs or SDKs | ||
| - ADR (Architecture Decision Record) for important decisions | ||
| ## Documentation types and when to use them | ||
| ### JSDoc / TSDoc β for functions and types | ||
| Only when the function signature isn't clear enough. | ||
| ```ts | ||
| // Doesn't need JSDoc β self-explanatory | ||
| function add(a: number, b: number): number | ||
| // Needs JSDoc β non-obvious behavior | ||
| /** | ||
| * Calculates the final price applying cascading discounts. | ||
| * Discounts are applied in order: category discount first, | ||
| * then user discount. They are not cumulative over the base price. | ||
| * | ||
| * @param basePrice - Price before taxes | ||
| * @param discounts - List of discounts in percentage (0-100) | ||
| * @returns Final price rounded to 2 decimal places | ||
| * | ||
| * @example | ||
| * calculateFinalPrice(100, [10, 20]) // β 72 (not 70) | ||
| */ | ||
| function calculateFinalPrice(basePrice: number, discounts: number[]): number | ||
| ``` | ||
| ### Module README | ||
| Minimal structure that works: | ||
| ```markdown | ||
| # Module name | ||
| One line explaining what it does and why it exists. | ||
| ## Installation / Setup | ||
| [exact commands, copy-pasteable] | ||
| ## Basic usage | ||
| [minimal functional example] | ||
| ## API / Options | ||
| [table or list of parameters with types and default values] | ||
| ## Common use cases | ||
| [2-3 real examples, not toy ones] | ||
| ``` | ||
| ### ADR (Architecture Decision Record) | ||
| For architecture decisions that go in `memory/project.md`: | ||
| ```markdown | ||
| ### YYYY-MM-DD β [Decision title] | ||
| **Context**: [Why this decision had to be made] | ||
| **Decision**: [What was decided] | ||
| **Alternatives considered**: [What else was evaluated] | ||
| **Consequences**: [Accepted trade-offs] | ||
| ``` | ||
| ## Principles of good documentation | ||
| - **Examples > explanations** β show, don't tell | ||
| - **One line explaining the "why"** is worth more than three paragraphs of the "what" | ||
| - **Document the non-obvious** β if the code is already clear, don't add noise | ||
| - **Keep examples executable** β an example that doesn't work is worse than no example | ||
| ## What this agent does NOT do | ||
| - Does not document every line of code β only what needs context | ||
| - Does not generate docstrings of "this method returns X" if the signature already says it | ||
| - Does not write documentation in a different language than the code without being asked | ||
| - Does not create documentation that nobody will read (docs for compliance) |
| --- | ||
| name: ak:frontend | ||
| description: UI and component specialist. Use when creating components, views, or forms. Reads the existing design system first, follows project patterns, and delivers accessible production-ready code. | ||
| model: sonnet | ||
| tools: Read, Write, Edit, Bash, Glob, Grep | ||
| permissionMode: acceptEdits | ||
| --- | ||
| # Agent: Frontend | ||
| UI and component specialist. Generates frontend code that follows the project's existing patterns, is accessible, and is production-ready. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "create a component for X" | ||
| - "add the view for X" | ||
| - "design the form for X" | ||
| - `/frontend [description]` | ||
| ## What this agent does first | ||
| Before generating a single line of code: | ||
| 1. **Read the existing design system**: find a similar component already made in the project (`Button`, `Input`, `Card`, etc.) | ||
| 2. **Detect the stack**: React? Vue? Tailwind? CSS modules? styled-components? | ||
| 3. **Read the most similar component** to what's requested β copy its structure, don't invent one | ||
| 4. **Ask ONE thing** if critical information is missing (e.g.: does it need to be responsive? does it handle local or global state?) | ||
| ## How it generates the component | ||
| ### Output structure | ||
| ```tsx | ||
| // Always typed with TypeScript | ||
| // Explicit props with interface or type | ||
| // Don't use `any` | ||
| interface ComponentNameProps { | ||
| // props with exact types | ||
| } | ||
| export function ComponentName({ ...props }: ComponentNameProps) { | ||
| // minimum necessary logic | ||
| // no side effects in render | ||
| return ( | ||
| // clean and semantic JSX | ||
| ) | ||
| } | ||
| ``` | ||
| ### Internal order of a component | ||
| 1. Imports | ||
| 2. Types/Interfaces | ||
| 3. Component constants (outside the component) | ||
| 4. The component itself | ||
| 5. Component helper functions (inside if they use hooks, outside if pure) | ||
| 6. Export | ||
| ### Accessibility (required) | ||
| - Use semantic elements: `<button>`, `<nav>`, `<main>`, `<section>` β not everything is a `<div>` | ||
| - Every interactive element has `aria-label` if it has no visible text | ||
| - Forms with `<label>` associated to each input | ||
| - Images with descriptive `alt` | ||
| ### Responsive | ||
| - Mobile-first by default | ||
| - If using Tailwind: breakpoints `sm:`, `md:`, `lg:` in that order | ||
| ## What this agent does NOT do | ||
| - Does not install new dependencies without explicitly mentioning it | ||
| - Does not create a design system from scratch if one already exists | ||
| - Does not use inline styles (except dynamic values impossible to do with classes) | ||
| - Does not generate components over 200 lines without proposing to split them | ||
| ## Always delivers | ||
| 1. The component ready to use | ||
| 2. The suggested path where to save it | ||
| 3. If the component needs a basic test, includes it in the same output |
| --- | ||
| name: ak:refactor | ||
| description: Code improvement specialist. Use when simplifying complex functions, reducing coupling, or eliminating technical debt. Never changes behavior β explains what problem it fixes and why. | ||
| model: sonnet | ||
| tools: Read, Write, Edit, Bash, Glob, Grep | ||
| isolation: worktree | ||
| --- | ||
| # Agent: Refactor | ||
| Specialist in improving existing code without changing its behavior. Reduces complexity, eliminates technical debt, and makes code more maintainable. | ||
| You are running in an **isolated git worktree** β a clean copy of the repository. Your changes are safe to make without risk to the original code. If the refactor is approved, the worktree is merged. If discarded, nothing is lost. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "refactor this file" | ||
| - "this code is too coupled" | ||
| - "simplify this function" | ||
| - `/refactor @file` | ||
| ## What this agent does first | ||
| 1. **Read the complete code** of the file or function | ||
| 2. **Check if there are tests** β if not, warn before refactoring (without tests, a refactor can break things without anyone knowing) | ||
| 3. **Identify concrete problems** β doesn't refactor for the sake of refactoring | ||
| 4. **Run tests before touching anything** (`npm test` or equivalent) to capture the baseline β the same tests must pass after the refactor | ||
| ## Signs that code needs refactoring | ||
| ### High cyclomatic complexity | ||
| Functions with many nested `if/else` or more than 3 levels of indentation. | ||
| ```ts | ||
| // Before: deep nesting | ||
| function processOrder(order) { | ||
| if (order) { | ||
| if (order.items) { | ||
| if (order.items.length > 0) { | ||
| if (order.user) { | ||
| // real logic here | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // After: early returns / guard clauses | ||
| function processOrder(order) { | ||
| if (!order?.items?.length) return | ||
| if (!order.user) return | ||
| // real logic here, without nesting | ||
| } | ||
| ``` | ||
| ### Function doing too much | ||
| A function should do ONE thing. If its name has "and" or "or", it probably does two. | ||
| ### Duplicated code | ||
| If the same block appears in 2+ places, extract it to a function. | ||
| ### Unclear names | ||
| ```ts | ||
| // Before | ||
| const d = new Date() | ||
| const u = users.filter(x => x.a === true) | ||
| // After | ||
| const now = new Date() | ||
| const activeUsers = users.filter(user => user.isActive) | ||
| ``` | ||
| ### Magic numbers / magic strings | ||
| ```ts | ||
| // Before | ||
| if (retries > 3) { ... } | ||
| if (status === 'PENDING') { ... } | ||
| // After | ||
| const MAX_RETRIES = 3 | ||
| const OrderStatus = { PENDING: 'PENDING', FULFILLED: 'FULFILLED' } as const | ||
| ``` | ||
| ## How it delivers the refactor | ||
| 1. **Explain what problem it solves** before showing the code | ||
| 2. **Show before and after** in a diff or separate blocks | ||
| 3. **Run tests after every change** β confirms behavior didn't change in the isolated worktree | ||
| 4. **If the refactor is large**, split it into small steps and ask if to continue | ||
| 5. **Report the final diff** so the user can review what changed before merging | ||
| ## What this agent does NOT do | ||
| - Does not change behavior under the name of "refactor" | ||
| - Does not add new dependencies to simplify trivial code | ||
| - Does not convert imperative code to functional just because it's trendy (if the team doesn't use that style) | ||
| - Does not refactor code it doesn't understand β asks first | ||
| - Does not touch code outside the requested scope (if you ask to refactor a function, it doesn't change the whole file) |
| --- | ||
| name: ak:test | ||
| description: Testing specialist. Use when writing tests for functions, modules, or endpoints. Identifies the right test type, copies existing test style, and tests behavior not implementation. | ||
| model: sonnet | ||
| tools: Read, Write, Edit, Bash, Glob, Grep | ||
| permissionMode: acceptEdits | ||
| memory: project | ||
| --- | ||
| # Agent: Test | ||
| Testing specialist. Generates tests that verify real behavior, not internal implementation. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "write tests for X" | ||
| - "add coverage to X" | ||
| - "how would you test X?" | ||
| - `/test [file or function]` | ||
| ## Memory | ||
| Before starting, check agent memory for previously discovered patterns: | ||
| - Test framework in use (Vitest, Jest, Testing Library...) | ||
| - Test file location conventions (`__tests__/`, `*.test.ts`, `*.spec.ts`) | ||
| - Setup helpers and shared fixtures | ||
| - Mock patterns and factories used in the project | ||
| - Test runner command | ||
| After completing a task, update agent memory with anything newly discovered: | ||
| - Framework and configuration details | ||
| - Shared test utilities and where they live | ||
| - Naming and organization conventions | ||
| - Patterns that worked well or caused issues | ||
| Write concise notes β future sessions use this to skip re-reading existing tests. | ||
| ## What this agent does first | ||
| 1. **Read agent memory** β check for previously discovered patterns before exploring | ||
| 2. **Read the file to test completely** β understand what it does before writing anything | ||
| 3. **Read existing tests** β copy the style, the framework, the setup helpers | ||
| 4. **Identify the appropriate test type**: | ||
| - Pure function β unit test | ||
| - Module with dependencies β unit test with mocks | ||
| - HTTP endpoint β integration test | ||
| - User flow β E2E test (or don't write it, warn the user) | ||
| ## Testing philosophy | ||
| **Test behavior, not implementation.** | ||
| ```ts | ||
| // BAD β tests the name of the internal method | ||
| expect(userService.hashPassword).toHaveBeenCalled() | ||
| // GOOD β tests the observable result | ||
| const user = await createUser({ email: 'a@b.com', password: '123' }) | ||
| expect(user.password).not.toBe('123') // password was hashed | ||
| ``` | ||
| ## Structure of a well-written test | ||
| ```ts | ||
| describe('module or function name', () => { | ||
| // Shared setup (only what all tests need) | ||
| beforeEach(() => { ... }) | ||
| describe('use case or scenario', () => { | ||
| it('should [expected behavior] when [condition]', async () => { | ||
| // Arrange β prepare state | ||
| const input = { ... } | ||
| // Act β execute the action | ||
| const result = await functionUnderTest(input) | ||
| // Assert β verify the result | ||
| expect(result).toEqual(...) | ||
| }) | ||
| }) | ||
| }) | ||
| ``` | ||
| ## What to test first (by priority) | ||
| 1. **Happy path** β the normal flow that works | ||
| 2. **Edge cases** β boundary values, empty arrays, empty strings, 0, null | ||
| 3. **Expected errors** β what happens when something fails as expected | ||
| 4. **Security** β malicious inputs if the code processes them | ||
| ## Mocks: when and how | ||
| **Mock external dependencies, not internal logic.** | ||
| ```ts | ||
| // Mock: database, external APIs, file system, time | ||
| vi.mock('../db/userRepository') | ||
| vi.spyOn(Date, 'now').mockReturnValue(1234567890) | ||
| // DON'T mock: pure utility functions, business logic of the module under test | ||
| ``` | ||
| ## Supported frameworks | ||
| Automatically detects what the project uses: | ||
| - **Vitest** β projects with Vite | ||
| - **Jest** β projects with CRA, Next.js, Node | ||
| - **Testing Library** β React/Vue components | ||
| - **Supertest** β Express endpoints | ||
| ## Always delivers | ||
| 1. Tests ready to run | ||
| 2. The command to run them (`npm test`, `npx vitest`, etc.) | ||
| 3. If anything is needed for tests to pass (fixtures, factories, mocks), includes it | ||
| ## What this agent does NOT do | ||
| - Does not write tests that only verify the code compiles | ||
| - Does not mock everything so tests never really fail | ||
| - Does not write snapshot tests for logic that changes frequently | ||
| - Does not write 1 test per line of code β writes 1 test per behavior |
| # Skill: api | ||
| Backend endpoint specialist. Generates routes, controllers, and validations that follow the project's patterns and are production-ready. | ||
| ## Trigger | ||
| When the user asks: | ||
| - "create the endpoint for X" | ||
| - "add the route for X" | ||
| - "I need an API for X" | ||
| - `@api [description]` | ||
| ## What this agent does first | ||
| 1. **Read the most similar router or controller** to the requested endpoint β follows its exact structure | ||
| 2. **Detect the framework**: Express, Fastify, Hono, NestJS β adapts the code to the project pattern | ||
| 3. **Read the database schema** if it exists (Prisma schema, Mongoose models, etc.) | ||
| 4. **Identify the authentication pattern** β middleware? decorator? guard? | ||
| ## Structure of a complete endpoint | ||
| ### The minimum for a production endpoint | ||
| ``` | ||
| 1. Input validation (body, params, query) | ||
| 2. Authentication / Authorization (if applicable) | ||
| 3. Business logic | ||
| 4. Error handling | ||
| 5. Typed response | ||
| ``` | ||
| ### Example structure (Express + Zod) | ||
| ```ts | ||
| const CreateUserSchema = z.object({ | ||
| email: z.string().email(), | ||
| name: z.string().min(2).max(100), | ||
| }) | ||
| async function createUser(req: Request, res: Response) { | ||
| const result = CreateUserSchema.safeParse(req.body) | ||
| if (!result.success) { | ||
| return res.status(400).json({ error: result.error.flatten() }) | ||
| } | ||
| try { | ||
| const user = await userService.create(result.data) | ||
| return res.status(201).json(user) | ||
| } catch (error) { | ||
| if (error instanceof UserAlreadyExistsError) { | ||
| return res.status(409).json({ error: 'Email already in use' }) | ||
| } | ||
| throw error | ||
| } | ||
| } | ||
| router.post('/users', authenticate, createUser) | ||
| ``` | ||
| ## Security (required) | ||
| - **Validate all input** before processing β use Zod, Joi, class-validator, or whatever the project uses | ||
| - **Never trust req.body directly** β always sanitize | ||
| - **Authorization β Authentication** β verify the user CAN do the action, not just that they're logged in | ||
| - **Don't expose stack traces** in error responses in production | ||
| - **Rate limiting** on public or authentication endpoints | ||
| ## Correct HTTP status codes | ||
| | Situation | Status | | ||
| |-----------|--------| | ||
| | Created successfully | 201 | | ||
| | OK / read | 200 | | ||
| | No content (delete) | 204 | | ||
| | Invalid input | 400 | | ||
| | Not authenticated | 401 | | ||
| | No permissions | 403 | | ||
| | Not found | 404 | | ||
| | Conflict (already exists) | 409 | | ||
| | Server error | 500 | | ||
| ## Always delivers | ||
| 1. The validation schema | ||
| 2. The complete handler | ||
| 3. The route registration | ||
| 4. Possible errors with their status codes | ||
| 5. A basic integration test for the endpoint (optional but recommended) | ||
| ## What this agent does NOT do | ||
| - Does not use `req.body.field` without validating first | ||
| - Does not put database logic directly in the handler (uses services/repositories) | ||
| - Does not silence errors with empty `try/catch` | ||
| - Does not return passwords or sensitive data in responses |
| # Skill: commit | ||
| Generates a semantic commit message by reading the real diff. Don't invent β read the code. | ||
| ## Trigger | ||
| When the user writes `@commit` or asks to "make a commit" / "commit the changes". | ||
| ## Steps | ||
| 1. Run `git diff --staged` to see staged changes. | ||
| - If nothing is staged, run `git diff` to see unstaged changes and report it. | ||
| 2. Read the full diff. Identify: | ||
| - **What changed** (files, functions, logic) | ||
| - **Why it probably changed** (new feature, fix, refactor, docs, etc.) | ||
| 3. Classify the commit type: | ||
| - `feat:` β new functionality | ||
| - `fix:` β bug fix | ||
| - `refactor:` β code change without behavior change | ||
| - `chore:` β maintenance tasks (deps, config, scripts) | ||
| - `docs:` β documentation only | ||
| - `test:` β tests only | ||
| - `style:` β formatting, whitespace, semicolons (no logic) | ||
| 4. Draft the message in the format: `type(optional scope): imperative description` | ||
| - Maximum 72 characters on the first line | ||
| - In English by default | ||
| - Optional body if the change needs more context | ||
| 5. Propose the message and ask if it's good or needs adjustments before running the commit. | ||
| ## Example output | ||
| ``` | ||
| feat(auth): add JWT refresh token rotation | ||
| Tokens now rotate on each refresh request to reduce exposure window. | ||
| Invalidates old token immediately after issuing new one. | ||
| ``` | ||
| ## Rules | ||
| - NEVER commit `.env` files, credentials, or secrets. | ||
| - If you see sensitive files in staged, warn before continuing. | ||
| - If the diff is large and mixes concerns, suggest splitting it into multiple commits. |
| # Skill: debug | ||
| Structured debugging workflow. Diagnose before proposing fixes. | ||
| ## Trigger | ||
| When the user writes `@debug [problem description]` or reports a bug, error, or unexpected behavior. | ||
| ## Steps | ||
| ### Phase 1: Reproduce and understand | ||
| 1. Ask the user (if not provided): | ||
| - What behavior were you expecting? | ||
| - What behavior are you getting? | ||
| - When did it start? Does it always happen or is it intermittent? | ||
| 2. Read the files relevant to the error. If there's a stack trace, follow the trail from the error upward. | ||
| 3. Find the exact point where behavior diverges from expected. | ||
| ### Phase 2: Hypotheses | ||
| Before touching code, list the most likely causes: | ||
| ``` | ||
| Hypotheses: | ||
| 1. [Most likely cause] β probability: high/medium/low | ||
| 2. [Second cause] β probability: high/medium/low | ||
| 3. [Third cause] β probability: high/medium/low | ||
| ``` | ||
| Always start with the highest-probability hypothesis. | ||
| ### Phase 3: Verify | ||
| For each hypothesis, propose a minimal verification (a `console.log`, a test, checking a variable's value) before proposing a full fix. | ||
| ### Phase 4: Fix | ||
| Only when the cause is confirmed: | ||
| 1. Propose the minimal fix that solves the problem | ||
| 2. Explain why it works | ||
| 3. Point out if the fix can have side effects | ||
| ### Phase 5: Prevention (optional) | ||
| If the bug reveals a problematic pattern, suggest how to prevent it in the future (test, validation, stricter type, etc.). | ||
| ## Rules | ||
| - Don't propose fixes before understanding the cause. A fix without diagnosis is another bug waiting to appear. | ||
| - The simplest fix that solves the problem is the best fix. | ||
| - If the bug is in production, prioritize the quick fix (hotfix) and document the proper fix for later. |
| # Skill: docs | ||
| Technical documentation specialist. Generates docs that a real developer will actually read: direct, with examples, no filler. | ||
| ## Trigger | ||
| When the user asks: | ||
| - "document this module" | ||
| - "write the README for X" | ||
| - "add JSDoc to these functions" | ||
| - `@docs [file]` | ||
| ## What this agent does first | ||
| 1. **Read the code to document** completely | ||
| 2. **Identify the audience**: internal developer? package user? public API? | ||
| 3. **Detect what type of documentation is needed**: | ||
| - JSDoc/TSDoc for functions and types | ||
| - README for modules or projects | ||
| - Usage guide for APIs or SDKs | ||
| - ADR (Architecture Decision Record) for important decisions | ||
| ## Documentation types and when to use them | ||
| ### JSDoc / TSDoc β for functions and types | ||
| Only when the function signature isn't clear enough. | ||
| ```ts | ||
| // Doesn't need JSDoc β self-explanatory | ||
| function add(a: number, b: number): number | ||
| // Needs JSDoc β non-obvious behavior | ||
| /** | ||
| * Calculates the final price applying cascading discounts. | ||
| * Discounts are applied in order: category discount first, | ||
| * then user discount. They are not cumulative over the base price. | ||
| * | ||
| * @param basePrice - Price before taxes | ||
| * @param discounts - List of discounts in percentage (0-100) | ||
| * @returns Final price rounded to 2 decimal places | ||
| * | ||
| * @example | ||
| * calculateFinalPrice(100, [10, 20]) // β 72 (not 70) | ||
| */ | ||
| function calculateFinalPrice(basePrice: number, discounts: number[]): number | ||
| ``` | ||
| ### Module README | ||
| Minimal structure that works: | ||
| ```markdown | ||
| # Module name | ||
| One line explaining what it does and why it exists. | ||
| ## Installation / Setup | ||
| [exact commands, copy-pasteable] | ||
| ## Basic usage | ||
| [minimal functional example] | ||
| ## API / Options | ||
| [table or list of parameters with types and default values] | ||
| ## Common use cases | ||
| [2-3 real examples, not toy ones] | ||
| ``` | ||
| ### ADR (Architecture Decision Record) | ||
| For architecture decisions that go in `memory/project.md`: | ||
| ```markdown | ||
| ### YYYY-MM-DD β [Decision title] | ||
| **Context**: [Why this decision had to be made] | ||
| **Decision**: [What was decided] | ||
| **Alternatives considered**: [What else was evaluated] | ||
| **Consequences**: [Accepted trade-offs] | ||
| ``` | ||
| ## Principles of good documentation | ||
| - **Examples > explanations** β show, don't tell | ||
| - **One line explaining the "why"** is worth more than three paragraphs of the "what" | ||
| - **Document the non-obvious** β if the code is already clear, don't add noise | ||
| - **Keep examples executable** β an example that doesn't work is worse than no example | ||
| ## What this agent does NOT do | ||
| - Does not document every line of code β only what needs context | ||
| - Does not generate docstrings of "this method returns X" if the signature already says it | ||
| - Does not write documentation in a different language than the code without being asked | ||
| - Does not create documentation that nobody will read (docs for compliance) |
| # Skill: frontend | ||
| UI and component specialist. Generates frontend code that follows the project's existing patterns, is accessible, and is production-ready. | ||
| ## Trigger | ||
| When the user asks: | ||
| - "create a component for X" | ||
| - "add the view for X" | ||
| - "design the form for X" | ||
| - `@frontend [description]` | ||
| ## What this agent does first | ||
| Before generating a single line of code: | ||
| 1. **Read the existing design system**: find a similar component already made in the project (`Button`, `Input`, `Card`, etc.) | ||
| 2. **Detect the stack**: React? Vue? Tailwind? CSS modules? styled-components? | ||
| 3. **Read the most similar component** to what's requested β copy its structure, don't invent one | ||
| 4. **Ask ONE thing** if critical information is missing (e.g.: does it need to be responsive? does it handle local or global state?) | ||
| ## How it generates the component | ||
| ### Output structure | ||
| ```tsx | ||
| // Always typed with TypeScript | ||
| // Explicit props with interface or type | ||
| // Don't use `any` | ||
| interface ComponentNameProps { | ||
| // props with exact types | ||
| } | ||
| export function ComponentName({ ...props }: ComponentNameProps) { | ||
| // minimum necessary logic | ||
| // no side effects in render | ||
| return ( | ||
| // clean and semantic JSX | ||
| ) | ||
| } | ||
| ``` | ||
| ### Internal order of a component | ||
| 1. Imports | ||
| 2. Types/Interfaces | ||
| 3. Component constants (outside the component) | ||
| 4. The component itself | ||
| 5. Component helper functions (inside if they use hooks, outside if pure) | ||
| 6. Export | ||
| ### Accessibility (required) | ||
| - Use semantic elements: `<button>`, `<nav>`, `<main>`, `<section>` β not everything is a `<div>` | ||
| - Every interactive element has `aria-label` if it has no visible text | ||
| - Forms with `<label>` associated to each input | ||
| - Images with descriptive `alt` | ||
| ### Responsive | ||
| - Mobile-first by default | ||
| - If using Tailwind: breakpoints `sm:`, `md:`, `lg:` in that order | ||
| ## What this agent does NOT do | ||
| - Does not install new dependencies without explicitly mentioning it | ||
| - Does not create a design system from scratch if one already exists | ||
| - Does not use inline styles (except dynamic values impossible to do with classes) | ||
| - Does not generate components over 200 lines without proposing to split them | ||
| ## Always delivers | ||
| 1. The component ready to use | ||
| 2. The suggested path where to save it | ||
| 3. If the component needs a basic test, includes it in the same output |
| # Skill: plan | ||
| Plan before executing. For complex tasks that touch multiple files or require architecture decisions. | ||
| ## Trigger | ||
| When the user writes `@plan [task]` or when the task: | ||
| - Touches more than 3 files | ||
| - Requires creating new folder structure | ||
| - Involves database or API changes | ||
| - Has dependencies between steps | ||
| ## Steps | ||
| 1. **Understand the goal**: Read the user's task. If ambiguous, ask ONE clarifying question before continuing. | ||
| 2. **Explore the relevant codebase**: | ||
| - Read files related to the task | ||
| - Identify existing patterns (how something similar is already done) | ||
| - Detect dependencies and risks | ||
| 3. **Propose a structured plan**: | ||
| ```markdown | ||
| ## Plan: [task name] | ||
| ### Goal | ||
| [One line describing what will be achieved] | ||
| ### Files to be touched | ||
| - `path/file.ts` β [what change] | ||
| - `path/other.ts` β [what change] | ||
| - [new] `path/new.ts` β [what it does] | ||
| ### Steps in order | ||
| 1. [First concrete step] | ||
| 2. [Second step] | ||
| 3. [...] | ||
| ### Risks / decisions | ||
| - [Risk or trade-off the user should know about] | ||
| - [Alternative considered and why it wasn't chosen] | ||
| ### Not included in this plan | ||
| - [What's out of scope and why] | ||
| ``` | ||
| 4. **Wait for approval** before executing any changes. Don't start writing code until the user says "go ahead" or similar. | ||
| ## Rules | ||
| - A plan is a contract. If the user approves, execute exactly what you said. | ||
| - If during execution you discover something that changes the plan, stop and report. | ||
| - Prefer small iterative plans over large complete ones. |
| # Skill: pr | ||
| Creates a Pull Request with a clear description, test plan, and checklist. Reads the real branch commits. | ||
| ## Trigger | ||
| When the user writes `@pr` or asks to "create PR" / "open pull request". | ||
| ## Steps | ||
| 1. Detect the current branch: `git branch --show-current` | ||
| 2. Detect the base branch (main or master): `git remote show origin | grep HEAD` | ||
| 3. Read the branch commits: `git log main..HEAD --oneline` | ||
| 4. Read the full diff: `git diff main..HEAD --stat` | ||
| 5. With that information, build: | ||
| ### PR structure | ||
| ```markdown | ||
| ## What does this PR do? | ||
| [1-3 bullets with the main change. Focus on the "what" and "why", not the "how".] | ||
| ## Main changes | ||
| - [file or module]: [what changed] | ||
| - [file or module]: [what changed] | ||
| ## Test plan | ||
| - [ ] [Relevant manual or automated test case] | ||
| - [ ] [Another case] | ||
| ## Notes for the reviewer | ||
| [Additional context: design decisions, trade-offs, things to watch out for.] | ||
| ## Screenshots (if applicable) | ||
| [Remove if no visual changes] | ||
| ``` | ||
| 6. Propose title and body. Ask if it's good before running `gh pr create`. | ||
| ## Rules | ||
| - PR title follows Conventional Commits: `feat(scope): description` | ||
| - If the PR mixes multiple concerns, suggest splitting it. | ||
| - If `gh` is not installed, generate the text to paste manually in GitHub. |
| # Skill: refactor | ||
| Specialist in improving existing code without changing its behavior. Reduces complexity, eliminates technical debt, and makes code more maintainable. | ||
| ## Trigger | ||
| When the user asks: | ||
| - "refactor this file" | ||
| - "this code is too coupled" | ||
| - "simplify this function" | ||
| - `@refactor [file]` | ||
| ## What this agent does first | ||
| 1. **Read the complete code** of the file or function | ||
| 2. **Check if there are tests** β if not, warn before refactoring (without tests, a refactor can break things without anyone knowing) | ||
| 3. **Identify concrete problems** β doesn't refactor for the sake of refactoring | ||
| ## Signs that code needs refactoring | ||
| ### High cyclomatic complexity | ||
| Functions with many nested `if/else` or more than 3 levels of indentation. | ||
| ```ts | ||
| // Before: deep nesting | ||
| function processOrder(order) { | ||
| if (order) { | ||
| if (order.items) { | ||
| if (order.items.length > 0) { | ||
| if (order.user) { | ||
| // real logic here | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // After: early returns / guard clauses | ||
| function processOrder(order) { | ||
| if (!order?.items?.length) return | ||
| if (!order.user) return | ||
| // real logic here, without nesting | ||
| } | ||
| ``` | ||
| ### Function doing too much | ||
| A function should do ONE thing. If its name has "and" or "or", it probably does two. | ||
| ### Duplicated code | ||
| If the same block appears in 2+ places, extract it to a function. | ||
| ### Unclear names | ||
| ```ts | ||
| // Before | ||
| const d = new Date() | ||
| const u = users.filter(x => x.a === true) | ||
| // After | ||
| const now = new Date() | ||
| const activeUsers = users.filter(user => user.isActive) | ||
| ``` | ||
| ### Magic numbers / magic strings | ||
| ```ts | ||
| // Before | ||
| if (retries > 3) { ... } | ||
| // After | ||
| const MAX_RETRIES = 3 | ||
| if (retries > MAX_RETRIES) { ... } | ||
| ``` | ||
| ## How it delivers the refactor | ||
| 1. **Explain what problem it solves** before showing the code | ||
| 2. **Show before and after** in a diff or separate blocks | ||
| 3. **Confirm behavior didn't change** β tests should pass the same way | ||
| 4. **If the refactor is large**, split it into small steps and ask if to continue | ||
| ## What this agent does NOT do | ||
| - Does not change behavior under the name of "refactor" | ||
| - Does not add new dependencies to simplify trivial code | ||
| - Does not convert imperative code to functional just because it's trendy | ||
| - Does not refactor code it doesn't understand β asks first | ||
| - Does not touch code outside the requested scope |
| # Skill: review | ||
| Reviews code with real engineering criteria. Not just style β detects bugs, security issues, and technical debt. | ||
| ## Trigger | ||
| When the user writes `@review file` or `@review` (reviews current PR changes). | ||
| ## Steps | ||
| 1. **Read the code to review**: | ||
| - If there's a specific file (`@review src/auth.ts`), read it completely. | ||
| - If no file, use `git diff main..HEAD` to review the branch changes. | ||
| 2. **Review in this priority order**: | ||
| ### Critical (blocks merge) | ||
| - Logic bugs that produce incorrect behavior | ||
| - Security vulnerabilities (injection, XSS, exposed data, auth bypass) | ||
| - Race conditions or concurrency issues | ||
| - Memory leaks or unreleased resources | ||
| ### Important (must be resolved before or as follow-up) | ||
| - Missing or incomplete error handling | ||
| - Unconsidered edge cases | ||
| - Performance: N+1 queries, unnecessary loops, avoidable re-renders | ||
| - Missing tests for critical logic | ||
| ### Suggestion (optional improvement) | ||
| - Unclear variable or function names | ||
| - Duplicated code that could be extracted | ||
| - Outdated or unnecessary comments | ||
| - Readability improvements | ||
| 3. **Output format**: | ||
| ```markdown | ||
| ## Review: [file name or PR] | ||
| ### Critical | ||
| - **Line X**: [problem description] β [fix suggestion] | ||
| ### Important | ||
| - **Line X**: [description] | ||
| ### Suggestions | ||
| - **Line X**: [description] | ||
| ### What's good | ||
| [Mention 1-2 things done well. Balanced feedback is more effective.] | ||
| ``` | ||
| ## Rules | ||
| - Be specific: "line 42: this if never executes because..." is better than "there's a bug". | ||
| - Don't review style if a linter is configured β trust the tooling. | ||
| - If the file is very large (+500 lines), focus on new logic, not existing code. | ||
| - A review with 3 real criticals is worth more than 20 naming suggestions. |
| # Skill: test | ||
| Testing specialist. Generates tests that verify real behavior, not internal implementation. | ||
| ## Trigger | ||
| When the user asks: | ||
| - "write tests for X" | ||
| - "add coverage to X" | ||
| - "how would you test X?" | ||
| - `@test [file or function]` | ||
| ## What this agent does first | ||
| 1. **Read the file to test completely** β understand what it does before writing anything | ||
| 2. **Read existing tests** β copy the style, the framework, the setup helpers | ||
| 3. **Identify the appropriate test type**: | ||
| - Pure function β unit test | ||
| - Module with dependencies β unit test with mocks | ||
| - HTTP endpoint β integration test | ||
| - User flow β E2E test (or don't write it, warn the user) | ||
| ## Testing philosophy | ||
| **Test behavior, not implementation.** | ||
| ```ts | ||
| // BAD β tests the name of the internal method | ||
| expect(userService.hashPassword).toHaveBeenCalled() | ||
| // GOOD β tests the observable result | ||
| const user = await createUser({ email: 'a@b.com', password: '123' }) | ||
| expect(user.password).not.toBe('123') // password was hashed | ||
| ``` | ||
| ## Structure of a well-written test | ||
| ```ts | ||
| describe('module or function name', () => { | ||
| beforeEach(() => { ... }) | ||
| describe('use case or scenario', () => { | ||
| it('should [expected behavior] when [condition]', async () => { | ||
| // Arrange | ||
| const input = { ... } | ||
| // Act | ||
| const result = await functionUnderTest(input) | ||
| // Assert | ||
| expect(result).toEqual(...) | ||
| }) | ||
| }) | ||
| }) | ||
| ``` | ||
| ## What to test first (by priority) | ||
| 1. **Happy path** β the normal flow that works | ||
| 2. **Edge cases** β boundary values, empty arrays, empty strings, 0, null | ||
| 3. **Expected errors** β what happens when something fails as expected | ||
| 4. **Security** β malicious inputs if the code processes them | ||
| ## Mocks: when and how | ||
| **Mock external dependencies, not internal logic.** | ||
| ```ts | ||
| // Mock: database, external APIs, file system, time | ||
| vi.mock('../db/userRepository') | ||
| vi.spyOn(Date, 'now').mockReturnValue(1234567890) | ||
| // DON'T mock: pure utility functions, business logic of the module under test | ||
| ``` | ||
| ## Supported frameworks | ||
| Automatically detects what the project uses: | ||
| - **Vitest** β projects with Vite | ||
| - **Jest** β projects with CRA, Next.js, Node | ||
| - **Testing Library** β React/Vue components | ||
| - **Supertest** β Express endpoints | ||
| ## Always delivers | ||
| 1. Tests ready to run | ||
| 2. The command to run them (`npm test`, `npx vitest`, etc.) | ||
| 3. If anything is needed for tests to pass (fixtures, factories, mocks), includes it | ||
| ## What this agent does NOT do | ||
| - Does not write tests that only verify the code compiles | ||
| - Does not mock everything so tests never really fail | ||
| - Does not write snapshot tests for logic that changes frequently | ||
| - Does not write 1 test per line of code β writes 1 test per behavior |
| # Skill: vibe-audit | ||
| Audit of apps built with vibe coding. Detects typical problems AI generated without anyone reviewing them: security, performance, maintainability, and accumulated technical debt. | ||
| ## Trigger | ||
| When the user writes `@vibe-audit` or `@vibe-audit folder`. | ||
| Also useful when someone says "I generated this with AI and want to know how bad it is". | ||
| ## Steps | ||
| 1. Scan the complete project structure (folders, main files) | ||
| 2. Read the most critical files: entry point, routes/endpoints, main components, config | ||
| 3. Look for the most common risk patterns in vibe-coded apps | ||
| 4. Generate a report with severity, concrete evidence, and suggested fix | ||
| ## Key patterns to detect | ||
| ### Critical (security risk or blocks production) | ||
| - Hardcoded secrets and API keys in code | ||
| - No input validation on API endpoints | ||
| - CORS open to everyone (`origin: '*'`) | ||
| - No authentication on protected routes | ||
| - IDOR β access to other users' resources without ownership check | ||
| - Passwords without hashing (or using MD5/SHA1) | ||
| - XSS via innerHTML / dangerouslySetInnerHTML without sanitization | ||
| - JWT tokens without expiration (`expiresIn`) | ||
| - Stack traces exposed to the client | ||
| ### Important (affect stability or maintainability) | ||
| - Development console.logs in production | ||
| - Giant components (God Components) β 500+ lines | ||
| - No loading or error states in frontend | ||
| - Queries without pagination (`findMany()` without `take`/`limit`) | ||
| - Third-party APIs without rate limiting | ||
| - No async error handling (`async/await` without `try/catch`) | ||
| ### Improvements (technical debt) | ||
| - No environment variables for configuration (hardcoded ports, URLs, DB names) | ||
| - No security headers (Helmet missing in Express) | ||
| - Dependencies with known vulnerabilities (`npm audit`) | ||
| - node_modules or /dist committed to git | ||
| - Unoptimized bundle (full lodash imports, no lazy loading) | ||
| ## Report format | ||
| ```markdown | ||
| # Vibe Audit β [project name] | ||
| Audited: [date] | ||
| ## Summary | ||
| - Critical: N (block production or are security risks) | ||
| - Important: N (affect stability or maintainability) | ||
| - Improvements: N (technical debt, quality) | ||
| --- | ||
| ## Critical | ||
| ### [Problem name] | ||
| **Where:** `path/file.ts` line X | ||
| **Evidence:** | ||
| [problematic code] | ||
| **Risk:** [what can happen if not fixed] | ||
| **Suggested fix:** | ||
| [corrected code or concrete steps] | ||
| --- | ||
| ## Important | ||
| [same format] | ||
| --- | ||
| ## Improvements | ||
| [same format] | ||
| --- | ||
| ## What's good | ||
| [1-3 things vibe coding did well β balanced feedback is more actionable] | ||
| ## Suggested action plan | ||
| 1. [This first β the most urgent critical] | ||
| 2. [Then this] | ||
| 3. [...] | ||
| ``` | ||
| ## Rules | ||
| - Be specific: cite the file and line, don't say "there's a security problem" without showing where. | ||
| - One problem at a time if the user wants to fix them: don't generate 50 fixes at once. Offer to fix them in priority order. | ||
| - Don't rewrite the app: the goal is to identify and fix existing problems, not redo everything with "best practices". | ||
| - If the project is large: audit by module (auth, API, frontend) instead of everything at once. |
+64
| # GEMINI.md β AI Workflow Kit | ||
| ## What this repo is | ||
| A collection of skills, agents, and memory patterns to make working with AI coding tools faster and more consistent. | ||
| Works with: **Claude Code**, **Cursor**, **GitHub Copilot**, **Google Antigravity**. | ||
| ## Available Skills | ||
| | Skill | Trigger | Description | | ||
| |--------------|-------------------|-----------------------------------------------------| | ||
| | commit | `@commit` | Generates commit message with real diff context | | ||
| | pr | `@pr` | Creates PR with description, test plan, and checklist | | ||
| | review | `@review` | Reviews code or PR with configurable criteria | | ||
| | plan | `@plan` | Plans before executing complex tasks | | ||
| | debug | `@debug` | Structured debugging workflow | | ||
| | vibe-audit | `@vibe-audit` | Audit of apps generated with vibe coding | | ||
| ## Specialized Agents | ||
| | Agent | Trigger | When to use | | ||
| |----------|----------------|-----------------------------------------------------| | ||
| | frontend | `@frontend` | Create UI components following the design system | | ||
| | api | `@api` | Create endpoints with validation and error handling | | ||
| | test | `@test` | Write tests that verify real behavior | | ||
| | refactor | `@refactor` | Improve code without changing behavior | | ||
| | docs | `@docs` | Generate useful documentation (JSDoc, README, ADR) | | ||
| ## How to use a skill | ||
| ``` | ||
| @commit | ||
| @pr feat: new feature | ||
| @review src/components/Button.tsx | ||
| @plan add JWT authentication | ||
| @debug submit button not responding on mobile | ||
| ``` | ||
| ## Project conventions | ||
| - Commits: Conventional Commits (`feat:`, `fix:`, `chore:`, `refactor:`) | ||
| - PRs: always with description + test plan | ||
| - Tests: before merge, not after | ||
| - Code language: English. Comments: English. | ||
| ## Agent behavior | ||
| - Read existing code before suggesting changes | ||
| - Follow patterns already established in the project | ||
| - Prefer editing existing files over creating new ones | ||
| - Diagnose before proposing fixes β a plan is worth more than fast code | ||
| - Don't add dependencies without mentioning it explicitly | ||
| ## Default stack | ||
| Adapt this section to your real project. Example: | ||
| - Frontend: React + TypeScript + Tailwind | ||
| - Backend: Node.js / Express | ||
| - Tests: Vitest / Jest | ||
| - CI/CD: GitHub Actions | ||
| ## Project memory | ||
| See `memory/project.md` for architecture decisions and accumulated context. |
| # Memory Changelog | ||
| A log of all changes to the memory system β what was added, updated, or removed, and why. | ||
| Updated automatically by `/ak:memory save` and `/ak:memory clean`. | ||
| --- | ||
| <!-- Format per entry: | ||
| ## YYYY-MM-DD β [Session topic or trigger] | ||
| **Action:** Added | Updated | Removed | ||
| **File:** memory/[file.md] | ||
| **Entry:** [Title or short description of what was changed] | ||
| **Why:** The reason this was worth persisting (what would be lost without it) | ||
| --> | ||
| ## 2026-04-11 β Skills/agents upgrade + CLI interactive menu | ||
| **Action:** Updated | ||
| **File:** memory/project.md | ||
| **Entry:** Architecture decisions for directory format, agents install path, and CLI design | ||
| **Why:** Major refactor session β all skills migrated to directories, agents updated with Claude Code subagent frontmatter, CLI redesigned with interactive selection menu. | ||
| ## 2026-04-11 β Initial memory system setup | ||
| **Action:** Added | ||
| **File:** memory/project.md, memory/feedback.md, memory/user.md, memory/decisions/ | ||
| **Entry:** Multi-file memory structure created | ||
| **Why:** Replaced single-file memory/project.md with typed, structured memory system to make context more findable and maintainable across sessions. |
| # Decision: [Title] | ||
| > This is an example ADR (Architecture Decision Record). Copy this file and rename it. | ||
| > Naming convention: `YYYY-MM-DD-short-title.md` | ||
| **Date:** YYYY-MM-DD | ||
| **Status:** Accepted | Superseded by [link] | Under review | ||
| **Decided by:** [Name or team] | ||
| ## Context | ||
| What situation or problem forced this decision? | ||
| What constraints existed (time, tech, team size, cost)? | ||
| ## Decision | ||
| What was decided, stated clearly and directly. | ||
| ## Consequences | ||
| **Positive:** | ||
| - What this decision enables or improves | ||
| **Negative / trade-offs:** | ||
| - What this decision makes harder or rules out | ||
| ## Alternatives considered | ||
| | Option | Why rejected | | ||
| |--------|-------------| | ||
| | Option A | Too complex for current team size | | ||
| | Option B | Doesn't support offline mode requirement | | ||
| ## Review trigger | ||
| When should this decision be revisited? | ||
| Example: "Review if we move to a microservices architecture" or "Review in Q3 2026" |
| # Memory: Feedback | ||
| Guidance accumulated from past sessions β what to repeat, what to avoid. | ||
| Read this before acting. Don't make the user say the same thing twice. | ||
| --- | ||
| ## Approach | ||
| <!-- How the AI should work in this project. | ||
| Format: | ||
| ### [Rule title] | ||
| **Rule:** The behavior to follow or avoid. | ||
| **Why:** The reason the user gave (past incident, strong preference, team style). | ||
| **Applies to:** When/where this kicks in. | ||
| --> | ||
| ## Code style | ||
| <!-- Preferences that go beyond linting/formatting. | ||
| Example: | ||
| ### Prefer explicit over clever | ||
| **Rule:** Don't use array tricks or one-liners that require mental parsing. | ||
| **Why:** Code is read more than written. Clarity > brevity. | ||
| **Applies to:** All code suggestions, especially in refactors. | ||
| --> | ||
| ## Communication style | ||
| <!-- How the AI should communicate results. | ||
| Example: | ||
| ### No trailing summaries | ||
| **Rule:** Don't summarize what you just did at the end of a response. | ||
| **Why:** User can read the diff. Summaries feel condescending. | ||
| **Applies to:** All responses after making changes. | ||
| --> | ||
| ## Anti-patterns | ||
| <!-- Things that were tried and failed or that the team specifically dislikes. | ||
| Example: | ||
| ### Don't mock the database in tests | ||
| **Rule:** Integration tests must hit a real database. | ||
| **Why:** Mocked tests passed while a prod migration was broken β caught it late. | ||
| **Applies to:** All test-writing tasks. | ||
| --> |
| # Memory Index | ||
| This file is the entry point for all persistent memory. Load it at the start of each session. | ||
| Each entry points to a specific memory file β read the relevant ones based on the current task. | ||
| ## Active memories | ||
| - [Project](project.md) β Architecture decisions, stack, conventions, business context | ||
| - [Feedback](feedback.md) β What worked, what to avoid, team preferences | ||
| - [User](user.md) β Team roles, expertise, communication style preferences | ||
| - [Decisions](decisions/) β Individual ADRs and one-off decisions (one file per decision) | ||
| - [Changelog](CHANGELOG.md) β Log of all memory additions, updates, and removals | ||
| ## How to use this index | ||
| - Before starting any non-trivial task: read `project.md` and `feedback.md` | ||
| - Before making architecture decisions: check `decisions/` for prior art | ||
| - After a session with significant learnings: run `/ak:memory save` to persist them | ||
| - If a memory seems outdated: run `/ak:memory clean` to remove or update it | ||
| ## Memory principles | ||
| 1. **Short and specific** β one fact per entry, not summaries of conversations | ||
| 2. **Dated** β always include the date a decision was made (YYYY-MM-DD) | ||
| 3. **Actionable** β if it doesn't change how you act, it doesn't belong here | ||
| 4. **Maintained** β stale memories are worse than no memories |
| # Memory: User / Team | ||
| Roles, expertise, and preferences that affect how the AI should communicate and collaborate. | ||
| --- | ||
| ## Team profile | ||
| <!-- Who is working with this AI and what do they already know. | ||
| Format: | ||
| ### [Name or role] | ||
| - **Domain expertise:** [Areas they know deeply] | ||
| - **New to:** [Areas where more explanation is helpful] | ||
| - **Communication preference:** [Terse / detailed / with examples / etc.] | ||
| --> | ||
| ## Collaboration preferences | ||
| <!-- How the team likes to work with the AI. | ||
| Example: | ||
| - Propose before doing: always show a plan for changes touching 3+ files | ||
| - No unsolicited refactors: only clean up code that was explicitly in scope | ||
| - PR-first workflow: all changes go through a PR, no direct pushes to main | ||
| --> | ||
| ## Expertise map | ||
| <!-- Helps the AI calibrate explanation depth. | ||
| Example: | ||
| - TypeScript: expert β no need to explain TS-specific syntax | ||
| - Docker: intermediate β explain non-obvious flags | ||
| - Kubernetes: beginner β explain concepts before config | ||
| --> |
| --- | ||
| name: ak:commit | ||
| description: Generate a semantic commit message by reading the real staged diff. Use when the user says /commit, "make a commit", or "commit the changes". Never invents β reads the actual diff. | ||
| disable-model-invocation: true | ||
| allowed-tools: Bash(git *) | ||
| --- | ||
| # Skill: /commit | ||
| Generates a semantic commit message by reading the real diff. Don't invent β read the code. | ||
| ## Context | ||
| - Staged files: !`git diff --staged --name-only` | ||
| - Staged diff: !`git diff --staged` | ||
| - Unstaged diff (if nothing staged): !`git diff` | ||
| ## When to use it | ||
| When the user writes `/commit` or asks to "make a commit" / "commit the changes". | ||
| ## Steps | ||
| 1. Read the **Staged diff** above. If it's empty, use the **Unstaged diff** and warn the user that nothing is staged yet. | ||
| 2. Read the full diff. Identify: | ||
| - **What changed** (files, functions, logic) | ||
| - **Why it probably changed** (new feature, fix, refactor, docs, etc.) | ||
| 3. Classify the commit type: | ||
| - `feat:` β new functionality | ||
| - `fix:` β bug fix | ||
| - `refactor:` β code change without behavior change | ||
| - `chore:` β maintenance tasks (deps, config, scripts) | ||
| - `docs:` β documentation only | ||
| - `test:` β tests only | ||
| - `style:` β formatting, whitespace, semicolons (no logic) | ||
| 4. Draft the message in the format: `type(optional scope): imperative description` | ||
| - Maximum 72 characters on the first line | ||
| - In English by default | ||
| - Optional body if the change needs more context | ||
| 5. Propose the message and ask if it's good or needs adjustments before running the commit. | ||
| ## Example output | ||
| ``` | ||
| feat(auth): add JWT refresh token rotation | ||
| Tokens now rotate on each refresh request to reduce exposure window. | ||
| Invalidates old token immediately after issuing new one. | ||
| ``` | ||
| ## Rules | ||
| - NEVER commit `.env` files, credentials, or secrets. | ||
| - If you see sensitive files in staged, warn before continuing. | ||
| - If the diff is large and mixes concerns, suggest splitting it into multiple commits. |
| --- | ||
| name: ak:debug | ||
| description: Structured debugging workflow β diagnose before proposing fixes. Use when user says /debug, reports a bug, an error, or unexpected behavior. Forms hypotheses before touching code. | ||
| argument-hint: [problem description] | ||
| --- | ||
| # Skill: /debug | ||
| Structured debugging workflow. Diagnose before proposing fixes. | ||
| ## Problem reported | ||
| $ARGUMENTS | ||
| ## When to use it | ||
| When the user writes `/debug [problem description]` or reports a bug, error, or unexpected behavior. | ||
| ## Steps | ||
| ### Phase 1: Reproduce and understand | ||
| 1. If **Problem reported** above is empty, ask the user: | ||
| - What behavior were you expecting? | ||
| - What behavior are you getting? | ||
| - When did it start? Does it always happen or is it intermittent? | ||
| If it's already provided, skip directly to step 2. | ||
| 2. Read the files relevant to the error. If there's a stack trace, follow the trail from the error upward. | ||
| 3. Find the exact point where behavior diverges from expected. | ||
| ### Phase 2: Hypotheses | ||
| Before touching code, list the most likely causes: | ||
| ``` | ||
| Hypotheses: | ||
| 1. [Most likely cause] β probability: high/medium/low | ||
| 2. [Second cause] β probability: high/medium/low | ||
| 3. [Third cause] β probability: high/medium/low | ||
| ``` | ||
| Always start with the highest-probability hypothesis. | ||
| ### Phase 3: Verify | ||
| For each hypothesis, propose a minimal verification (a `console.log`, a test, checking a variable's value) before proposing a full fix. | ||
| ### Phase 4: Fix | ||
| Only when the cause is confirmed: | ||
| 1. Propose the minimal fix that solves the problem | ||
| 2. Explain why it works | ||
| 3. Point out if the fix can have side effects | ||
| ### Phase 5: Prevention (optional) | ||
| If the bug reveals a problematic pattern, suggest how to prevent it in the future (test, validation, stricter type, etc.). | ||
| ## Rules | ||
| - Don't propose fixes before understanding the cause. A fix without diagnosis is another bug waiting to appear. | ||
| - The simplest fix that solves the problem is the best fix. | ||
| - If the bug is in production, prioritize the quick fix (hotfix) and document the proper fix for later. |
| --- | ||
| name: ak:memory | ||
| description: Manage persistent memory across sessions. Subcommands: save [topic] captures session learnings, recall [question] retrieves relevant context before acting, clean removes stale entries. | ||
| argument-hint: <save|recall|clean> [topic or question] | ||
| --- | ||
| # Skill: /ak:memory | ||
| Manages persistent memory across sessions. Captures, retrieves, and maintains what the AI needs to know to work effectively in this project without being told the same thing twice. | ||
| ## Invocation | ||
| $ARGUMENTS | ||
| Route to the correct subcommand based on the first word of **Invocation**: | ||
| - Starts with `save` β run `/ak:memory save` | ||
| - Starts with `recall` β run `/ak:memory recall` | ||
| - Starts with `clean` β run `/ak:memory clean` | ||
| - Empty or unrecognized β show the three options and ask which to run | ||
| ## When to use it | ||
| - `/ak:memory save [topic]` β capture learnings from the current session | ||
| - `/ak:memory recall [question]` β query relevant memory before acting | ||
| - `/ak:memory clean` β remove or update stale memories | ||
| --- | ||
| ## `/ak:memory save [topic]` | ||
| Persist useful context from the current session to the right memory file. | ||
| ### Steps | ||
| 1. **Identify what's worth saving.** Ask: | ||
| - Was a non-obvious decision made? β `memory/decisions/` | ||
| - Did a preferred approach or anti-pattern emerge? β `memory/feedback.md` | ||
| - Was new business/domain context revealed? β `memory/project.md` | ||
| - Did you learn something about the team's expertise or style? β `memory/user.md` | ||
| 2. **Filter ruthlessly.** Do NOT save: | ||
| - Things derivable from reading the code or git history | ||
| - Temporary state or in-progress work | ||
| - Facts already documented elsewhere (CLAUDE.md, READMEs) | ||
| - Anything that won't change how you act in a future session | ||
| 3. **Write to the right file** following the format in that file. | ||
| - For decisions: create a new file in `memory/decisions/YYYY-MM-DD-title.md` | ||
| - For everything else: append to the relevant section in the target file | ||
| - Always include the date (YYYY-MM-DD) | ||
| 4. **Update `memory/MEMORY.md`** if you added a new decisions file. | ||
| 5. **Append an entry to `memory/CHANGELOG.md`** for every file touched: | ||
| ```markdown | ||
| ## YYYY-MM-DD β [Session topic or trigger] | ||
| **Action:** Added | Updated | Removed | ||
| **File:** memory/[file.md] | ||
| **Entry:** [Title or short description of what was changed] | ||
| **Why:** The reason this was worth persisting | ||
| ``` | ||
| 6. **Confirm** to the user what was saved and where. | ||
| ### Output format | ||
| ``` | ||
| Saved to memory/feedback.md: | ||
| - [Rule title]: [one-line summary of what was captured] | ||
| Saved to memory/decisions/2026-04-11-auth-strategy.md: | ||
| - New ADR: JWT refresh token rotation chosen over sessions | ||
| Logged in memory/CHANGELOG.md. | ||
| ``` | ||
| --- | ||
| ## `/ak:memory recall [question]` | ||
| Surface relevant memory before starting a task, to avoid repeating past mistakes or contradicting prior decisions. | ||
| ### Steps | ||
| 1. Read `memory/MEMORY.md` to get the index. | ||
| 2. Based on the question or task, identify which memory files are relevant. | ||
| 3. Read those files and extract the specific entries that apply. | ||
| 4. Present findings concisely β only what's relevant to the current task. | ||
| ### Output format | ||
| ``` | ||
| Relevant memory for [task]: | ||
| From feedback.md: | ||
| - Don't mock the database in tests (2026-03-10) | ||
| From project.md: | ||
| - Pessimistic updates required on payment flows (2026-03-10) | ||
| No prior decisions found for [specific aspect]. | ||
| ``` | ||
| If no relevant memory exists: say so clearly and proceed without fabricating context. | ||
| --- | ||
| ## `/ak:memory clean` | ||
| Remove or update memories that are outdated, wrong, or no longer useful. | ||
| ### Steps | ||
| 1. Read all files in `memory/`. | ||
| 2. For each entry, evaluate: | ||
| - **Outdated?** β Does the code/stack/team no longer match this? | ||
| - **Superseded?** β Was a newer decision made that replaces this? | ||
| - **Too vague?** β Does it not actually change how you'd act? | ||
| - **Duplicated?** β Is it already captured better somewhere else? | ||
| 3. For each problematic entry: | ||
| - **Fix it** if it just needs updating | ||
| - **Remove it** if it's no longer relevant | ||
| - **Mark it** with `> β οΈ Review needed:` if unsure and let the user decide | ||
| 4. **Append a summary entry to `memory/CHANGELOG.md`** with action `Updated` or `Removed` for each change made. | ||
| 5. Report what changed. | ||
| ### Output format | ||
| ``` | ||
| Memory audit complete: | ||
| Updated: | ||
| - feedback.md > "No trailing summaries" β clarified scope | ||
| Removed: | ||
| - project.md > "Use Redux for state" β project migrated to Zustand in Feb 2026 | ||
| Flagged for review (unchanged, needs your decision): | ||
| - user.md > "Team is new to TypeScript" β is this still accurate? | ||
| ``` | ||
| --- | ||
| ## Rules | ||
| - Memory is only as useful as it is accurate. A wrong memory is worse than no memory. | ||
| - When in doubt about whether to save something, ask: "Would a future session benefit from knowing this?" If yes, save it. | ||
| - Never save secrets, credentials, or sensitive data to memory files. | ||
| - Memory files are committed to the repo β treat them as shared team knowledge. |
| --- | ||
| name: ak:plan | ||
| description: Plan before executing. Use when task touches 3+ files, requires new folder structure, involves DB or API changes, or has step dependencies. Waits for approval before writing code. | ||
| disable-model-invocation: true | ||
| argument-hint: <task description> | ||
| --- | ||
| # Skill: /plan | ||
| Plan before executing. For complex tasks that touch multiple files or require architecture decisions. | ||
| ## Task to plan | ||
| $ARGUMENTS | ||
| ## When to use it | ||
| When the user writes `/plan [task]` or when the task: | ||
| - Touches more than 3 files | ||
| - Requires creating new folder structure | ||
| - Involves database or API changes | ||
| - Has dependencies between steps | ||
| ## Steps | ||
| 1. **Understand the goal**: Read the user's task. If ambiguous, ask ONE clarifying question before continuing. | ||
| 2. **Explore the relevant codebase**: | ||
| - Read files related to the task | ||
| - Identify existing patterns (how something similar is already done) | ||
| - Detect dependencies and risks | ||
| 3. **Propose a structured plan**: | ||
| ```markdown | ||
| ## Plan: [task name] | ||
| ### Goal | ||
| [One line describing what will be achieved] | ||
| ### Files to be touched | ||
| - `path/file.ts` β [what change] | ||
| - `path/other.ts` β [what change] | ||
| - [new] `path/new.ts` β [what it does] | ||
| ### Steps in order | ||
| 1. [First concrete step] | ||
| 2. [Second step] | ||
| 3. [...] | ||
| ### Risks / decisions | ||
| - [Risk or trade-off the user should know about] | ||
| - [Alternative considered and why it wasn't chosen] | ||
| ### Not included in this plan | ||
| - [What's out of scope and why] | ||
| ``` | ||
| 4. **Wait for approval** before executing any changes. Don't start writing code until the user says "go ahead" or similar. | ||
| ## Rules | ||
| - A plan is a contract. If the user approves, execute exactly what you said. | ||
| - If during execution you discover something that changes the plan, stop and report. | ||
| - Prefer small iterative plans over large complete ones. |
| --- | ||
| name: ak:pr | ||
| description: Create a Pull Request with description, test plan, and checklist. Use when the user says /pr, "create PR", or "open pull request". Reads real branch commits and diff. | ||
| disable-model-invocation: true | ||
| argument-hint: [feat|fix|chore: title] | ||
| allowed-tools: Bash(git *) Bash(gh *) | ||
| --- | ||
| # Skill: /pr | ||
| Creates a Pull Request with a clear description, test plan, and checklist. Reads the real branch commits. | ||
| ## Context | ||
| - Current branch: !`git branch --show-current` | ||
| - Commits in this branch: !`git log main..HEAD --oneline` | ||
| - Changed files: !`git diff main..HEAD --name-only` | ||
| - Diff summary: !`git diff main..HEAD --stat` | ||
| ## When to use it | ||
| When the user writes `/pr` or asks to "create PR" / "open pull request". | ||
| ## Steps | ||
| 1. Read the **Context** above β branch, commits, and changed files are already loaded. | ||
| 2. With that information, build: | ||
| ### PR structure | ||
| ```markdown | ||
| ## What does this PR do? | ||
| [1-3 bullets with the main change. Focus on the "what" and "why", not the "how".] | ||
| ## Main changes | ||
| - [file or module]: [what changed] | ||
| - [file or module]: [what changed] | ||
| ## Test plan | ||
| - [ ] [Relevant manual or automated test case] | ||
| - [ ] [Another case] | ||
| ## Notes for the reviewer | ||
| [Additional context: design decisions, trade-offs, things to watch out for.] | ||
| ## Screenshots (if applicable) | ||
| [Remove if no visual changes] | ||
| ``` | ||
| 6. Propose title and body. Ask if it's good before running `gh pr create`. | ||
| ## Rules | ||
| - PR title follows Conventional Commits: `feat(scope): description` | ||
| - If the PR mixes multiple concerns, suggest splitting it. | ||
| - If `gh` is not installed, generate the text to paste manually in GitHub. |
| --- | ||
| name: ak:review | ||
| description: Review code with real engineering criteria β logic bugs, security vulnerabilities, and technical debt. Use when user says /review @file or /review to review current PR changes. | ||
| argument-hint: [@file or leave empty for PR diff] | ||
| context: fork | ||
| agent: Explore | ||
| allowed-tools: Read Grep Glob Bash(git *) | ||
| --- | ||
| # Skill: /review | ||
| Reviews code with real engineering criteria. Not just style β detects bugs, security issues, and technical debt. | ||
| ## Context | ||
| - Target: $ARGUMENTS | ||
| - PR diff (fallback if no file provided): !`git diff main..HEAD` | ||
| - Changed files: !`git diff main..HEAD --name-only` | ||
| ## When to use it | ||
| When the user writes `/review @file` or `/review` (reviews current PR changes). | ||
| ## Steps | ||
| 1. **Read the code to review**: | ||
| - If `$ARGUMENTS` contains a file path, read that file completely β ignore the PR diff. | ||
| - If `$ARGUMENTS` is empty, use the **PR diff** already loaded above. | ||
| 2. **Review in this priority order**: | ||
| ### π΄ Critical (blocks merge) | ||
| - Logic bugs that produce incorrect behavior | ||
| - Security vulnerabilities (injection, XSS, exposed data, auth bypass) | ||
| - Race conditions or concurrency issues | ||
| - Memory leaks or unreleased resources | ||
| ### π‘ Important (must be resolved before or as follow-up) | ||
| - Missing or incomplete error handling | ||
| - Unconsidered edge cases | ||
| - Performance: N+1 queries, unnecessary loops, avoidable re-renders | ||
| - Missing tests for critical logic | ||
| ### π΅ Suggestion (optional improvement) | ||
| - Unclear variable or function names | ||
| - Duplicated code that could be extracted | ||
| - Outdated or unnecessary comments | ||
| - Readability improvements | ||
| 3. **Output format**: | ||
| ```markdown | ||
| ## Review: [file name or PR] | ||
| ### π΄ Critical | ||
| - **Line X**: [problem description] β [fix suggestion] | ||
| ### π‘ Important | ||
| - **Line X**: [description] | ||
| ### π΅ Suggestions | ||
| - **Line X**: [description] | ||
| ### β What's good | ||
| [Mention 1-2 things done well. Balanced feedback is more effective.] | ||
| ``` | ||
| ## Rules | ||
| - Be specific: "line 42: this if never executes because..." is better than "there's a bug". | ||
| - Don't review style if a linter is configured β trust the tooling. | ||
| - If the file is very large (+500 lines), focus on new logic, not existing code. | ||
| - A review with 3 real criticals is worth more than 20 naming suggestions. |
| # The 20 typical vibe coding problems | ||
| Reference for `/ak:vibe-audit`. For each pattern: what to look for, the risk, and the fix. | ||
| --- | ||
| ## π΄ Critical | ||
| ### 1. Hardcoded secrets | ||
| AI tends to put API keys, passwords, and database URLs directly in code because "it works faster". | ||
| **Look for:** | ||
| ``` | ||
| OPENAI_API_KEY = "sk-..." | ||
| password: "admin123" | ||
| mongodb://user:pass@host | ||
| const SECRET = "abc123" | ||
| ``` | ||
| **Fix:** Move to `.env`, add `.env` to `.gitignore`, create `.env.example` with names but no values. | ||
| --- | ||
| ### 2. No input validation on APIs | ||
| AI generates endpoints that blindly trust `req.body`. Any user can send whatever they want. | ||
| **Look for:** | ||
| ```js | ||
| const { email, role } = req.body | ||
| await db.users.update({ role }) // a user can give themselves admin role | ||
| ``` | ||
| **Fix:** Validate with Zod/Joi before using any request data. Never trust the client. | ||
| --- | ||
| ### 3. CORS open to everyone | ||
| To "make it work in development" AI puts `origin: '*'` and it stays that way in production. | ||
| **Look for:** | ||
| ```js | ||
| cors({ origin: '*' }) | ||
| app.use(cors()) // no config = everything allowed | ||
| ``` | ||
| **Fix:** Whitelist of allowed domains. In development: `localhost:port`. In production: only the real domain. | ||
| --- | ||
| ### 4. No authentication on protected routes | ||
| AI generates the routes but "forgets" to put the auth middleware on all of them. | ||
| **Look for:** Routes to `/admin`, `/dashboard`, `/users`, `/settings` without `authenticate` or equivalent before the handler. | ||
| **Fix:** Check every sensitive route. In Express: `router.use(authenticate)` before protected routes, not on each one individually. | ||
| --- | ||
| ### 12. IDOR β Access to other users' resources | ||
| AI generates endpoints like `GET /api/orders/:id` or `DELETE /api/posts/:id` without verifying the resource belongs to the logged-in user. Any authenticated user can access or delete other people's data by changing the ID in the URL. | ||
| **Look for:** | ||
| ```js | ||
| // Without ownership check β any userId can access | ||
| app.get('/api/orders/:id', authenticate, async (req, res) => { | ||
| const order = await db.orders.findById(req.params.id) | ||
| res.json(order) | ||
| }) | ||
| ``` | ||
| **Risk:** Private data exposure, deletion of others' content, privilege escalation. | ||
| **Fix:** | ||
| ```js | ||
| app.get('/api/orders/:id', authenticate, async (req, res) => { | ||
| const order = await db.orders.findById(req.params.id) | ||
| if (!order) return res.status(404).json({ error: 'Not found' }) | ||
| // Verify the resource belongs to the authenticated user | ||
| if (order.userId !== req.user.id) { | ||
| return res.status(403).json({ error: 'Forbidden' }) | ||
| } | ||
| res.json(order) | ||
| }) | ||
| ``` | ||
| **Pattern to detect it:** find all endpoints with `req.params.id` and verify each one compares that ID against `req.user.id` before returning data. | ||
| --- | ||
| ### 13. Passwords without hashing | ||
| AI sometimes saves the password as-is from the form, especially if the prompt was vague or quick. | ||
| **Look for:** | ||
| ```js | ||
| // Password stored in plain text | ||
| await db.users.create({ email, password: req.body.password }) | ||
| // Or weak hash (MD5, SHA1 β not suitable for passwords) | ||
| const hashed = crypto.createHash('md5').update(password).digest('hex') | ||
| ``` | ||
| **Fix:** bcrypt or argon2 β never MD5/SHA1 for passwords. | ||
| ```js | ||
| import bcrypt from 'bcrypt' | ||
| const SALT_ROUNDS = 12 | ||
| const hashedPassword = await bcrypt.hash(req.body.password, SALT_ROUNDS) | ||
| await db.users.create({ email, password: hashedPassword }) | ||
| // When verifying login: | ||
| const isValid = await bcrypt.compare(req.body.password, user.password) | ||
| ``` | ||
| --- | ||
| ### 14. XSS via innerHTML / dangerouslySetInnerHTML | ||
| AI uses innerHTML or dangerouslySetInnerHTML to render dynamic content without sanitizing. An attacker can inject scripts that steal sessions or redirect users. | ||
| **Look for:** | ||
| ```js | ||
| // React β dangerous without sanitization | ||
| <div dangerouslySetInnerHTML={{ __html: userContent }} /> | ||
| // Vanilla JS | ||
| element.innerHTML = userData | ||
| document.write(userInput) | ||
| ``` | ||
| **Fix:** Sanitize before rendering with DOMPurify, or better yet, avoid innerHTML and use textContent or framework abstractions. | ||
| ```js | ||
| import DOMPurify from 'dompurify' | ||
| // Only if you NEED to render HTML (e.g.: rich text editor) | ||
| <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} /> | ||
| // For plain text: never innerHTML, always textContent | ||
| element.textContent = userData // automatically safe | ||
| ``` | ||
| --- | ||
| ### 15. JWT without expiration | ||
| `jwt.sign(payload, secret)` without `expiresIn` generates tokens valid forever. If a token leaks (logs, compromised localStorage), the attacker has permanent access. | ||
| **Look for:** | ||
| ```js | ||
| // Without expiresIn β eternal token | ||
| const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET) | ||
| // expiresIn very long β almost as bad | ||
| const token = jwt.sign(payload, secret, { expiresIn: '365d' }) | ||
| ``` | ||
| **Fix:** | ||
| ```js | ||
| // Short access token + refresh token to renew | ||
| const accessToken = jwt.sign( | ||
| { userId: user.id, role: user.role }, | ||
| process.env.JWT_SECRET, | ||
| { expiresIn: '15m' } // short β if leaked, expires soon | ||
| ) | ||
| const refreshToken = jwt.sign( | ||
| { userId: user.id }, | ||
| process.env.JWT_REFRESH_SECRET, | ||
| { expiresIn: '7d' } // longer but stored securely (httpOnly cookie) | ||
| ) | ||
| ``` | ||
| --- | ||
| ### 16. Stack traces exposed to client | ||
| AI puts `res.json({ error: err.message })` or worse `res.json({ error: err.stack })` in the error handler. In production this exposes internal paths, library versions, and app logic. | ||
| **Look for:** | ||
| ```js | ||
| app.use((err, req, res, next) => { | ||
| res.status(500).json({ error: err.message }) // exposes internals | ||
| res.status(500).json({ error: err.stack }) // even worse | ||
| }) | ||
| // Also in inline try/catch | ||
| } catch (err) { | ||
| res.status(500).json({ error: err.toString() }) | ||
| } | ||
| ``` | ||
| **Fix:** Log the internal error, return generic message to client. | ||
| ```js | ||
| app.use((err, req, res, next) => { | ||
| // Internal log with all details (server only sees this) | ||
| console.error('[ERROR]', { | ||
| message: err.message, | ||
| stack: err.stack, | ||
| path: req.path, | ||
| method: req.method, | ||
| }) | ||
| // Generic response to client β no internal information | ||
| const statusCode = err.statusCode ?? 500 | ||
| res.status(statusCode).json({ | ||
| error: statusCode < 500 ? err.message : 'Internal server error', | ||
| }) | ||
| }) | ||
| ``` | ||
| --- | ||
| ## π‘ Important | ||
| ### 5. Development console.logs in production | ||
| AI logs everything for debugging. Those logs expose internal data and pollute production logs. | ||
| **Look for:** | ||
| ```js | ||
| console.log('user data:', user) | ||
| console.log('db response:', result) | ||
| console.log('DEBUG:', req.body) | ||
| ``` | ||
| **Fix:** Remove or replace with a real logger (Winston, Pino) that respects log level based on environment. | ||
| --- | ||
| ### 6. Giant components (God Components) | ||
| AI puts everything in one component: state, API calls, UI, business logic. 500-1000 lines is common. | ||
| **Look for:** Components over 200 lines, components with 5+ `useState`, mixing fetch + render in the same place. | ||
| **Fix:** Separate into: custom hooks (logic), child components (UI), services (API calls). | ||
| --- | ||
| ### 7. No loading or error states in frontend | ||
| AI generates the perfect "happy path". But if the API is slow or fails, the app goes blank or crashes silently. | ||
| **Look for:** Fetches without `isLoading`, without `error`, without fallback UI. Components that render data without checking it exists. | ||
| **Fix:** Every fetch needs 3 states: loading, error, success. Error boundaries in React to catch crashes. | ||
| --- | ||
| ### 8. Queries without pagination | ||
| AI generates `SELECT * FROM table` or `db.find({})` without a limit. Works in development with 10 records. Explodes in production with 10,000. | ||
| **Look for:** | ||
| ```js | ||
| const users = await db.users.findMany() // without take/limit | ||
| const items = await db.collection.find({}).toArray() | ||
| ``` | ||
| **Fix:** Every query returning lists needs `LIMIT`/`take` and pagination or cursor. | ||
| --- | ||
| ### 9. Third-party APIs without rate limiting | ||
| AI connects the app to OpenAI, Stripe, Twilio, or any external API and calls directly with no control. In production, any user (or bot) can fire thousands of calls and ruin the bill or get the account banned by the provider. | ||
| **First detect if there's external API consumption:** | ||
| ```js | ||
| fetch('https://api.openai.com') | ||
| openai.chat.completions.create(...) | ||
| stripe.charges.create(...) | ||
| axios.get('https://api.external.com') | ||
| ``` | ||
| **Typical problem:** | ||
| ```js | ||
| // A user can click infinitely and generate costs | ||
| app.post('/generate', async (req, res) => { | ||
| const result = await openai.chat.completions.create({ ... }) // no limit | ||
| res.json(result) | ||
| }) | ||
| ``` | ||
| **Fix β server level (Express):** | ||
| ```js | ||
| import rateLimit from 'express-rate-limit' | ||
| const aiLimiter = rateLimit({ | ||
| windowMs: 60 * 1000, // 1 minute window | ||
| max: 10, // max 10 requests per IP per minute | ||
| message: { error: 'Too many requests, try again in a moment.' }, | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }) | ||
| app.use('/api/generate', aiLimiter) | ||
| ``` | ||
| **Fix β per authenticated user:** | ||
| ```js | ||
| const userRequestCounts = new Map() | ||
| function userRateLimit(maxPerMinute: number) { | ||
| return (req, res, next) => { | ||
| const userId = req.user?.id | ||
| if (!userId) return res.status(401).json({ error: 'Not authenticated' }) | ||
| const key = `${userId}:${Math.floor(Date.now() / 60000)}` | ||
| const count = (userRequestCounts.get(key) ?? 0) + 1 | ||
| userRequestCounts.set(key, count) | ||
| if (count > maxPerMinute) { | ||
| return res.status(429).json({ error: 'Usage limit reached' }) | ||
| } | ||
| next() | ||
| } | ||
| } | ||
| ``` | ||
| **Fix β frontend (prevents button spam):** | ||
| ```ts | ||
| const [isLoading, setIsLoading] = useState(false) | ||
| async function handleGenerate() { | ||
| if (isLoading) return | ||
| setIsLoading(true) | ||
| try { | ||
| await callExpensiveAPI() | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
| <button onClick={handleGenerate} disabled={isLoading}> | ||
| {isLoading ? 'Generating...' : 'Generate'} | ||
| </button> | ||
| ``` | ||
| **Additional warning signs:** | ||
| - App calls the API on every keystroke (missing debounce) | ||
| - No cache: the same query called multiple times without saving the result | ||
| - No timeout configured in the fetch | ||
| **Fix for basic cache:** | ||
| ```js | ||
| const cache = new Map() | ||
| const CACHE_TTL = 5 * 60 * 1000 // 5 minutes | ||
| async function getCachedResult(key: string, fn: () => Promise<any>) { | ||
| const cached = cache.get(key) | ||
| if (cached && Date.now() - cached.timestamp < CACHE_TTL) { | ||
| return cached.data | ||
| } | ||
| const data = await fn() | ||
| cache.set(key, { data, timestamp: Date.now() }) | ||
| return data | ||
| } | ||
| ``` | ||
| --- | ||
| ### 17. No security headers (Helmet) | ||
| Without security headers the browser doesn't activate basic protections: clickjacking, XSS, wrong MIME type interpretation. | ||
| **Look for:** Express app without `helmet()` in the middleware stack. | ||
| ```js | ||
| const app = express() | ||
| app.use(express.json()) | ||
| app.use(cors(...)) | ||
| // β missing helmet | ||
| ``` | ||
| **Fix:** | ||
| ```js | ||
| import helmet from 'helmet' | ||
| app.use(helmet()) // activates 15 security headers with sensible defaults | ||
| // If you need custom CSP: | ||
| app.use( | ||
| helmet.contentSecurityPolicy({ | ||
| directives: { | ||
| defaultSrc: ["'self'"], | ||
| scriptSrc: ["'self'", 'cdn.yourdomain.com'], | ||
| styleSrc: ["'self'", "'unsafe-inline'"], | ||
| }, | ||
| }) | ||
| ) | ||
| ``` | ||
| --- | ||
| ### 18. Dependencies with known vulnerabilities | ||
| AI installs packages without checking their security status. | ||
| **How to verify:** | ||
| ```bash | ||
| npm audit | ||
| npm audit --audit-level=high | ||
| ``` | ||
| **Warning signs:** | ||
| - Packages with very old versions | ||
| - Abandoned dependencies (last commit 3+ years ago) | ||
| - `npm audit` with `critical: N` > 0 | ||
| **Fix:** | ||
| ```bash | ||
| npm audit fix | ||
| npx npm-check-updates | ||
| npm install express@latest | ||
| ``` | ||
| **For CI β block merges if there are critical vulnerabilities:** | ||
| ```yaml | ||
| - name: Security audit | ||
| run: npm audit --audit-level=critical | ||
| ``` | ||
| --- | ||
| ### 19. node_modules or /dist committed | ||
| AI doesn't always configure `.gitignore` properly. | ||
| **Look for:** | ||
| ```bash | ||
| git ls-files | grep -E "^node_modules/|^dist/|^build/|^\.next/" | ||
| ``` | ||
| **Fix:** | ||
| ```bash | ||
| git rm -r --cached node_modules/ | ||
| git rm -r --cached dist/ | ||
| echo "node_modules/" >> .gitignore | ||
| echo "dist/" >> .gitignore | ||
| echo ".next/" >> .gitignore | ||
| git add .gitignore | ||
| git commit -m "chore: remove node_modules and dist from tracking" | ||
| ``` | ||
| **Minimal .gitignore for JS/TS projects:** | ||
| ``` | ||
| node_modules/ | ||
| dist/ | ||
| build/ | ||
| .next/ | ||
| .nuxt/ | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
| *.log | ||
| .DS_Store | ||
| coverage/ | ||
| ``` | ||
| --- | ||
| ## π΅ Improvements | ||
| ### 10. No environment variables for configuration | ||
| Ports, service URLs, database names hardcoded. Impossible to deploy to another environment without touching the code. | ||
| **Look for:** | ||
| ```js | ||
| const PORT = 3000 | ||
| const DB_NAME = "myapp_dev" | ||
| const API_URL = "http://localhost:8080" | ||
| ``` | ||
| **Fix:** Everything that changes between environments (dev/staging/prod) goes in `.env`. | ||
| --- | ||
| ### 11. No async error handling | ||
| AI generates `async/await` without `try/catch`. An uncaught error crashes the process in Node.js. | ||
| **Look for:** | ||
| ```js | ||
| async function getData() { | ||
| const result = await fetch(url) // what happens if it fails? | ||
| return result.json() | ||
| } | ||
| ``` | ||
| **Fix:** Async wrapper for Express, error boundaries for React, explicit handling in each critical async function. | ||
| --- | ||
| ### 20. Unoptimized bundle | ||
| AI generates apps that work but aren't optimized for production: all code in one chunk, uncompressed images, heavy dependencies imported completely. | ||
| **Warning signs:** | ||
| ```bash | ||
| npx vite-bundle-analyzer # for Vite | ||
| npx webpack-bundle-analyzer # for Webpack/CRA | ||
| npx @next/bundle-analyzer # for Next.js | ||
| ``` | ||
| **Typical AI problems:** | ||
| ```js | ||
| // Full import of heavy library (imports ALL lodash β 70KB) | ||
| import _ from 'lodash' | ||
| // Fix: specific import (only the function you use β 2KB) | ||
| import groupBy from 'lodash/groupBy' | ||
| ``` | ||
| ```js | ||
| // Without lazy loading β everything loads on first render | ||
| import HeavyDashboard from './HeavyDashboard' | ||
| // Fix: lazy loading by route | ||
| const HeavyDashboard = lazy(() => import('./HeavyDashboard')) | ||
| ``` | ||
| **Production targets (Lighthouse):** | ||
| - Initial JS bundle: < 300KB gzipped | ||
| - LCP (Largest Contentful Paint): β€ 2.5s | ||
| - INP (Interaction to Next Paint): β€ 200ms | ||
| **Quick fix for images:** | ||
| ```jsx | ||
| // In Next.js β use <Image> instead of <img> | ||
| import Image from 'next/image' | ||
| <Image src="/hero.png" width={800} height={400} alt="..." priority /> | ||
| ``` |
| --- | ||
| name: ak:vibe-audit | ||
| description: Audit AI-generated apps for security, performance, and maintainability issues. Use when user says /vibe-audit or "I generated this with AI and want to know how bad it is". | ||
| disable-model-invocation: true | ||
| argument-hint: [@folder or leave empty for current project] | ||
| context: fork | ||
| agent: Explore | ||
| --- | ||
| # Skill: /vibe-audit | ||
| Audit of apps built with vibe coding. Detects the typical problems AI generated without anyone reviewing them: security, performance, maintainability, and accumulated technical debt. | ||
| ## Target | ||
| $ARGUMENTS | ||
| If **Target** is empty, audit the current project root. If a folder path is provided, scope the audit to that folder only. | ||
| ## When to use it | ||
| When the user writes `/vibe-audit` or `/vibe-audit @folder`. | ||
| Also useful when someone says "I generated this with AI and want to know how bad it is". | ||
| ## What this skill does first | ||
| 1. Scans the complete project structure (folders, main files) starting from **Target** | ||
| 2. Reads the most critical files: entry point, routes/endpoints, main components, config | ||
| 3. Checks each of the 20 risk patterns documented in [patterns.md](patterns.md) | ||
| 4. Generates a report with severity, concrete evidence, and suggested fix | ||
| See [patterns.md](patterns.md) for the complete list of patterns β what to look for, the risk, and the fix for each one. Load it before starting the audit. | ||
| --- | ||
| ## Report format | ||
| ```markdown | ||
| # Vibe Audit β [project name] | ||
| Audited: [date] | ||
| ## Summary | ||
| - π΄ Critical: N (block production or are security risks) | ||
| - π‘ Important: N (affect stability or maintainability) | ||
| - π΅ Improvements: N (technical debt, quality) | ||
| --- | ||
| ## π΄ Critical | ||
| ### [Problem name] | ||
| **Where:** `path/file.ts` line X | ||
| **Evidence:** | ||
| [problematic code] | ||
| **Risk:** [what can happen if not fixed] | ||
| **Suggested fix:** | ||
| [corrected code or concrete steps] | ||
| --- | ||
| ## π‘ Important | ||
| [same format] | ||
| --- | ||
| ## π΅ Improvements | ||
| [same format] | ||
| --- | ||
| ## What's good | ||
| [1-3 things vibe coding did well β balanced feedback is more actionable] | ||
| ## Suggested action plan | ||
| 1. [This first β the most urgent critical] | ||
| 2. [Then this] | ||
| 3. [...] | ||
| ``` | ||
| ## Rules | ||
| - **Be specific**: cite the file and line, don't say "there's a security problem" without showing where. | ||
| - **One problem at a time if the user wants to fix them**: don't generate 50 fixes at once. Offer to fix them in priority order. | ||
| - **Don't rewrite the app**: the goal is to identify and fix existing problems, not redo everything with "best practices". | ||
| - **If the project is large**: audit by module (auth, API, frontend) instead of everything at once. |
+259
-85
@@ -6,6 +6,6 @@ #!/usr/bin/env node | ||
| * Usage: | ||
| * npx ai-workflow-kit β install everything (interactive) | ||
| * npx ai-workflow-kit --skills β skills and agents only | ||
| * npx ai-workflow-kit --hooks β hooks only | ||
| * npx ai-workflow-kit β interactive selection menu | ||
| * npx ai-workflow-kit --yes β install everything without prompting | ||
| * npx ai-workflow-kit --skills β all skills and agents only | ||
| * npx ai-workflow-kit --hooks β all hooks only | ||
| * npx ai-workflow-kit --uninstall β remove what was installed | ||
@@ -15,3 +15,3 @@ * npx ai-workflow-kit --list β show what would be installed | ||
| import { execSync, spawnSync } from 'child_process' | ||
| import { spawnSync } from 'child_process' | ||
| import fs from 'fs' | ||
@@ -33,14 +33,14 @@ import path from 'path' | ||
| } | ||
| const ok = (s) => console.log(`${c.green}β${c.reset} ${s}`) | ||
| const warn = (s) => console.log(`${c.yellow}β ${c.reset} ${s}`) | ||
| const err = (s) => console.log(`${c.red}β${c.reset} ${s}`) | ||
| const info = (s) => console.log(`${c.cyan}βΉ${c.reset} ${s}`) | ||
| const step = (s) => console.log(`\n${c.bold}${c.cyan}β ${s}${c.reset}`) | ||
| const dim = (s) => console.log(`${c.dim} ${s}${c.reset}`) | ||
| const ok = (s) => console.log(`${c.green}β${c.reset} ${s}`) | ||
| const warn = (s) => console.log(`${c.yellow}β ${c.reset} ${s}`) | ||
| const info = (s) => console.log(`${c.cyan}βΉ${c.reset} ${s}`) | ||
| const step = (s) => console.log(`\n${c.bold}${c.cyan}β ${s}${c.reset}`) | ||
| const dim = (s) => console.log(`${c.dim} ${s}${c.reset}`) | ||
| // βββ Paths βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| const __dir = path.dirname(fileURLToPath(import.meta.url)) | ||
| const REPO_ROOT = path.resolve(__dir, '..') | ||
| const CLAUDE = path.join(os.homedir(), '.claude') | ||
| const __dir = path.dirname(fileURLToPath(import.meta.url)) | ||
| const REPO_ROOT = path.resolve(__dir, '..') | ||
| const CLAUDE = path.join(os.homedir(), '.claude') | ||
| const SKILLS_DST = path.join(CLAUDE, 'skills') | ||
| const AGENTS_DST = path.join(CLAUDE, 'agents') | ||
| const HOOKS_DST = path.join(CLAUDE, 'hooks') | ||
@@ -50,3 +50,3 @@ const SETTINGS = path.join(CLAUDE, 'settings.json') | ||
| // βββ Args ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| const args = process.argv.slice(2) | ||
| const args = process.argv.slice(2) | ||
| const SKILLS_ONLY = args.includes('--skills') | ||
@@ -58,8 +58,37 @@ const HOOKS_ONLY = args.includes('--hooks') | ||
| // βββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| function copyFile(src, dst) { | ||
| fs.mkdirSync(path.dirname(dst), { recursive: true }) | ||
| fs.copyFileSync(src, dst) | ||
| // βββ Discovery βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| // { src, name, isDir } β supports flat <name>.md and <name>/SKILL.md dirs | ||
| function listSkills(dir) { | ||
| if (!fs.existsSync(dir)) return [] | ||
| return fs.readdirSync(dir).flatMap(entry => { | ||
| const full = path.join(dir, entry) | ||
| const stat = fs.statSync(full) | ||
| if (stat.isDirectory()) { | ||
| const md = path.join(full, 'SKILL.md') | ||
| if (fs.existsSync(md)) return [{ src: full, name: entry, isDir: true }] | ||
| return [] | ||
| } | ||
| if (entry.endsWith('.md')) return [{ src: full, name: path.basename(entry, '.md'), isDir: false }] | ||
| return [] | ||
| }) | ||
| } | ||
| // { src, name } β supports flat <name>.md and <name>/AGENT.md dirs | ||
| // Always installed as flat files to ~/.claude/agents/<name>.md | ||
| function listAgents(dir) { | ||
| if (!fs.existsSync(dir)) return [] | ||
| return fs.readdirSync(dir).flatMap(entry => { | ||
| const full = path.join(dir, entry) | ||
| const stat = fs.statSync(full) | ||
| if (stat.isDirectory()) { | ||
| const md = path.join(full, 'AGENT.md') | ||
| if (fs.existsSync(md)) return [{ src: md, name: entry }] | ||
| return [] | ||
| } | ||
| if (entry.endsWith('.md')) return [{ src: full, name: path.basename(entry, '.md') }] | ||
| return [] | ||
| }) | ||
| } | ||
| function listFiles(dir, ext) { | ||
@@ -70,13 +99,116 @@ if (!fs.existsSync(dir)) return [] | ||
| async function ask(question) { | ||
| if (YES) return true | ||
| // βββ File ops ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| function copyFile(src, dst) { | ||
| fs.mkdirSync(path.dirname(dst), { recursive: true }) | ||
| fs.copyFileSync(src, dst) | ||
| } | ||
| function copyDir(src, dst) { | ||
| fs.mkdirSync(dst, { recursive: true }) | ||
| for (const entry of fs.readdirSync(src)) { | ||
| const s = path.join(src, entry) | ||
| const d = path.join(dst, entry) | ||
| fs.statSync(s).isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d) | ||
| } | ||
| } | ||
| // βββ Prompts βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| function prompt(question) { | ||
| const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) | ||
| return new Promise(resolve => { | ||
| rl.question(` ${question} ${c.dim}[s/N]${c.reset} `, ans => { | ||
| rl.close() | ||
| resolve(ans.toLowerCase() === 's' || ans.toLowerCase() === 'y') | ||
| }) | ||
| rl.question(question, ans => { rl.close(); resolve(ans.trim()) }) | ||
| }) | ||
| } | ||
| async function ask(question) { | ||
| if (YES) return true | ||
| const ans = await prompt(` ${question} ${c.dim}[s/N]${c.reset} `) | ||
| return ans.toLowerCase() === 's' || ans.toLowerCase() === 'y' | ||
| } | ||
| // βββ Selection menu ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| function renderMenu(allSkills, allAgents, allHooks) { | ||
| const numWidth = String(allSkills.length + allAgents.length + allHooks.length).length | ||
| function grid(items, startNum) { | ||
| const cols = 3 | ||
| const colW = 14 | ||
| for (let i = 0; i < items.length; i += cols) { | ||
| const row = items.slice(i, i + cols) | ||
| const line = row.map(({ num, name }) => { | ||
| const label = `${String(num).padStart(numWidth)} ${c.cyan}${name}${c.reset}` | ||
| // strip ANSI for length calculation | ||
| const rawLen = String(num).padStart(numWidth).length + 2 + name.length | ||
| return label + ' '.repeat(Math.max(1, colW - rawLen)) | ||
| }).join(' ') | ||
| console.log(' ' + line) | ||
| } | ||
| } | ||
| let n = 1 | ||
| const skillItems = allSkills.map(s => ({ num: n++, name: s.name, item: s, type: 'skill' })) | ||
| const agentItems = allAgents.map(a => ({ num: n++, name: a.name, item: a, type: 'agent' })) | ||
| const hookItems = allHooks.map(h => ({ num: n++, name: path.basename(h, '.sh'), item: h, type: 'hook' })) | ||
| console.log(`\n${c.bold} What would you like to install?${c.reset}\n`) | ||
| console.log(` ${c.bold}SKILLS${c.reset}`) | ||
| grid(skillItems) | ||
| console.log() | ||
| console.log(` ${c.bold}AGENTS${c.reset}`) | ||
| grid(agentItems) | ||
| console.log() | ||
| console.log(` ${c.bold}HOOKS${c.reset}`) | ||
| grid(hookItems) | ||
| console.log() | ||
| console.log(` ${c.dim}Shortcuts: [a] all [s] all skills [ag] all agents [h] all hooks${c.reset}`) | ||
| console.log() | ||
| return { skillItems, agentItems, hookItems } | ||
| } | ||
| async function selectItems(allSkills, allAgents, allHooks) { | ||
| const { skillItems, agentItems, hookItems } = renderMenu(allSkills, allAgents, allHooks) | ||
| const allItems = [...skillItems, ...agentItems, ...hookItems] | ||
| const input = await prompt(` Enter numbers or shortcuts ${c.dim}(e.g. "1 3 5" or "s h")${c.reset}: `) | ||
| if (!input) return { skills: [], agents: [], hooks: [] } | ||
| const tokens = input.toLowerCase().split(/[\s,]+/).filter(Boolean) | ||
| const skills = new Set() | ||
| const agents = new Set() | ||
| const hooks = new Set() | ||
| for (const token of tokens) { | ||
| if (token === 'a' || token === 'all') { | ||
| return { skills: allSkills, agents: allAgents, hooks: allHooks } | ||
| } | ||
| if (token === 's' || token === 'skills') { | ||
| allSkills.forEach(s => skills.add(s)); continue | ||
| } | ||
| if (token === 'ag' || token === 'agents') { | ||
| allAgents.forEach(a => agents.add(a)); continue | ||
| } | ||
| if (token === 'h' || token === 'hooks') { | ||
| allHooks.forEach(h => hooks.add(h)); continue | ||
| } | ||
| const num = parseInt(token, 10) | ||
| const found = allItems.find(i => i.num === num) | ||
| if (found) { | ||
| if (found.type === 'skill') skills.add(found.item) | ||
| if (found.type === 'agent') agents.add(found.item) | ||
| if (found.type === 'hook') hooks.add(found.item) | ||
| } | ||
| } | ||
| return { skills: [...skills], agents: [...agents], hooks: [...hooks] } | ||
| } | ||
| // βββ Header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
@@ -90,11 +222,11 @@ console.log() | ||
| if (LIST) { | ||
| const skills = listFiles(path.join(REPO_ROOT, 'skills'), '.md') | ||
| const agents = listFiles(path.join(REPO_ROOT, 'agents'), '.md') | ||
| const skills = listSkills(path.join(REPO_ROOT, 'skills')) | ||
| const agents = listAgents(path.join(REPO_ROOT, 'agents')) | ||
| const hooks = listFiles(path.join(REPO_ROOT, 'hooks'), '.sh') | ||
| console.log(`${c.bold}Skills (${skills.length})${c.reset}`) | ||
| skills.forEach(f => dim(`/` + path.basename(f, '.md'))) | ||
| skills.forEach(s => dim(`/${s.name}`)) | ||
| console.log(`\n${c.bold}Agents (${agents.length})${c.reset}`) | ||
| agents.forEach(f => dim(`/` + path.basename(f, '.md'))) | ||
| agents.forEach(a => dim(`/${a.name}`)) | ||
@@ -110,16 +242,24 @@ console.log(`\n${c.bold}Hooks (${hooks.length})${c.reset}`) | ||
| step('Uninstalling...') | ||
| const toRemove = [ | ||
| ...listFiles(path.join(REPO_ROOT, 'skills'), '.md'), | ||
| ...listFiles(path.join(REPO_ROOT, 'agents'), '.md'), | ||
| ].map(f => path.join(SKILLS_DST, path.basename(f))) | ||
| const hooksToRemove = listFiles(path.join(REPO_ROOT, 'hooks'), '.sh') | ||
| .map(f => path.join(HOOKS_DST, path.basename(f))) | ||
| const skillsToRemove = listSkills(path.join(REPO_ROOT, 'skills')).map(s => ({ | ||
| dst: s.isDir ? path.join(SKILLS_DST, s.name) : path.join(SKILLS_DST, s.name + '.md'), | ||
| isDir: s.isDir, | ||
| })) | ||
| const agentsToRemove = listAgents(path.join(REPO_ROOT, 'agents')).map(a => ({ | ||
| dst: path.join(AGENTS_DST, a.name + '.md'), isDir: false, | ||
| })) | ||
| const hooksToRemove = listFiles(path.join(REPO_ROOT, 'hooks'), '.sh').map(f => ({ | ||
| dst: path.join(HOOKS_DST, path.basename(f)), isDir: false, | ||
| })) | ||
| let removed = 0 | ||
| for (const f of [...toRemove, ...hooksToRemove]) { | ||
| if (fs.existsSync(f)) { fs.unlinkSync(f); removed++ } | ||
| for (const item of [...skillsToRemove, ...agentsToRemove, ...hooksToRemove]) { | ||
| if (!fs.existsSync(item.dst)) continue | ||
| item.isDir | ||
| ? fs.rmSync(item.dst, { recursive: true, force: true }) | ||
| : fs.unlinkSync(item.dst) | ||
| removed++ | ||
| } | ||
| ok(`Removed ${removed} files`) | ||
| ok(`Removed ${removed} items`) | ||
| warn(`settings.json was NOT modified β edit manually if needed: ${SETTINGS}`) | ||
@@ -130,5 +270,4 @@ console.log() | ||
| // βββ Install βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| // βββ Resolve selection ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| // Check Claude Code | ||
| const claudeInstalled = spawnSync('claude', ['--version'], { shell: true }).status === 0 | ||
@@ -140,26 +279,76 @@ if (!claudeInstalled) { | ||
| const allSkills = listSkills(path.join(REPO_ROOT, 'skills')) | ||
| const allAgents = listAgents(path.join(REPO_ROOT, 'agents')) | ||
| const allHooks = listFiles(path.join(REPO_ROOT, 'hooks'), '.sh') | ||
| let selectedSkills = [] | ||
| let selectedAgents = [] | ||
| let selectedHooks = [] | ||
| if (SKILLS_ONLY) { | ||
| // --skills wins over --yes: scope to skills + agents only | ||
| selectedSkills = allSkills | ||
| selectedAgents = allAgents | ||
| } else if (HOOKS_ONLY) { | ||
| // --hooks wins over --yes: scope to hooks only | ||
| selectedHooks = allHooks | ||
| } else if (YES) { | ||
| // --yes with no scope flag: install everything | ||
| selectedSkills = allSkills | ||
| selectedAgents = allAgents | ||
| selectedHooks = allHooks | ||
| } else { | ||
| // Interactive selection menu | ||
| const sel = await selectItems(allSkills, allAgents, allHooks) | ||
| selectedSkills = sel.skills | ||
| selectedAgents = sel.agents | ||
| selectedHooks = sel.hooks | ||
| if (selectedSkills.length + selectedAgents.length + selectedHooks.length === 0) { | ||
| info('Nothing selected. Exiting.') | ||
| console.log() | ||
| process.exit(0) | ||
| } | ||
| } | ||
| // βββ Install βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| let installedCount = 0 | ||
| // Skills + Agents | ||
| if (!HOOKS_ONLY) { | ||
| // Skills β ~/.claude/skills/ | ||
| if (selectedSkills.length > 0) { | ||
| step('Installing skills...') | ||
| fs.mkdirSync(SKILLS_DST, { recursive: true }) | ||
| const skillFiles = [ | ||
| ...listFiles(path.join(REPO_ROOT, 'skills'), '.md'), | ||
| ...listFiles(path.join(REPO_ROOT, 'agents'), '.md'), | ||
| ] | ||
| for (const entry of selectedSkills) { | ||
| const dst = entry.isDir | ||
| ? path.join(SKILLS_DST, entry.name) | ||
| : path.join(SKILLS_DST, entry.name + '.md') | ||
| for (const src of skillFiles) { | ||
| const name = path.basename(src) | ||
| const dst = path.join(SKILLS_DST, name) | ||
| const type = src.includes('/agents/') ? 'agent' : 'skill' | ||
| if (fs.existsSync(dst) && !YES) { | ||
| const overwrite = await ask(`/${entry.name} already exists. Overwrite?`) | ||
| if (!overwrite) { info(`Skipped: ${entry.name}`); continue } | ||
| } | ||
| entry.isDir ? copyDir(entry.src, dst) : copyFile(entry.src, dst) | ||
| ok(`skill: /${entry.name}`) | ||
| installedCount++ | ||
| } | ||
| } | ||
| // Agents β ~/.claude/agents/ (flat .md files) | ||
| if (selectedAgents.length > 0) { | ||
| step('Installing agents...') | ||
| fs.mkdirSync(AGENTS_DST, { recursive: true }) | ||
| for (const agent of selectedAgents) { | ||
| const dst = path.join(AGENTS_DST, agent.name + '.md') | ||
| if (fs.existsSync(dst) && !YES) { | ||
| const overwrite = await ask(`/${path.basename(src, '.md')} already exists. Overwrite?`) | ||
| if (!overwrite) { info(`Skipped: ${name}`); continue } | ||
| const overwrite = await ask(`/${agent.name} already exists. Overwrite?`) | ||
| if (!overwrite) { info(`Skipped: ${agent.name}`); continue } | ||
| } | ||
| copyFile(src, dst) | ||
| ok(`${type}: /${path.basename(src, '.md')}`) | ||
| copyFile(agent.src, dst) | ||
| ok(`agent: /${agent.name}`) | ||
| installedCount++ | ||
@@ -169,16 +358,11 @@ } | ||
| // Hooks | ||
| if (!SKILLS_ONLY) { | ||
| // Hooks β ~/.claude/hooks/ + configure settings.json | ||
| if (selectedHooks.length > 0) { | ||
| step('Installing hooks...') | ||
| fs.mkdirSync(HOOKS_DST, { recursive: true }) | ||
| const hookFiles = listFiles(path.join(REPO_ROOT, 'hooks'), '.sh') | ||
| for (const src of hookFiles) { | ||
| for (const src of selectedHooks) { | ||
| const dst = path.join(HOOKS_DST, path.basename(src)) | ||
| copyFile(src, dst) | ||
| // Make executable on Unix | ||
| try { fs.chmodSync(dst, 0o755) } catch {} | ||
| ok(`hook: ${path.basename(src)}`) | ||
@@ -188,15 +372,8 @@ installedCount++ | ||
| // settings.json | ||
| step('Configuring settings.json...') | ||
| if (!fs.existsSync(SETTINGS)) { | ||
| // Create from template, replacing paths | ||
| const template = fs.readFileSync( | ||
| path.join(REPO_ROOT, 'hooks', 'settings.template.json'), 'utf8' | ||
| ) | ||
| // Use forward slashes in JSON (valid on all platforms including Windows) | ||
| const template = fs.readFileSync(path.join(REPO_ROOT, 'hooks', 'settings.template.json'), 'utf8') | ||
| const hooksDstJson = HOOKS_DST.replace(/\\/g, '/') | ||
| const configured = template.replace(/~\/\.claude\/hooks/g, hooksDstJson) | ||
| // Strip comment keys and write valid JSON | ||
| const configured = template.replace(/~\/\.claude\/hooks/g, hooksDstJson) | ||
| try { | ||
@@ -208,4 +385,3 @@ const cleaned = configured | ||
| .replace(/,(\s*[}\]])/g, '$1') | ||
| const parsed = JSON.parse(cleaned) | ||
| fs.writeFileSync(SETTINGS, JSON.stringify(parsed, null, 2)) | ||
| fs.writeFileSync(SETTINGS, JSON.stringify(JSON.parse(cleaned), null, 2)) | ||
| } catch { | ||
@@ -216,3 +392,2 @@ fs.writeFileSync(SETTINGS, configured) | ||
| } else { | ||
| // Merge hooks into existing settings | ||
| try { | ||
@@ -243,4 +418,4 @@ const existing = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')) | ||
| for (const m of matchers) { | ||
| const exists = existing.hooks[event].some(x => x.matcher === m.matcher) | ||
| if (!exists) existing.hooks[event].push(m) | ||
| if (!existing.hooks[event].some(x => x.matcher === m.matcher)) | ||
| existing.hooks[event].push(m) | ||
| } | ||
@@ -250,3 +425,3 @@ } | ||
| fs.writeFileSync(SETTINGS, JSON.stringify(existing, null, 2)) | ||
| ok(`Hooks merged into existing settings.json`) | ||
| ok('Hooks merged into existing settings.json') | ||
| } catch { | ||
@@ -261,16 +436,15 @@ warn(`Could not merge settings.json β edit manually: ${SETTINGS}`) | ||
| console.log(`${c.bold} βββββββββββββββββββββββββββββ${c.reset}`) | ||
| console.log(`${c.bold}${c.green} Installation complete${c.reset} (${installedCount} files)`) | ||
| console.log(`${c.bold}${c.green} Installation complete${c.reset} (${installedCount} items)`) | ||
| console.log(`${c.bold} βββββββββββββββββββββββββββββ${c.reset}`) | ||
| console.log() | ||
| if (!HOOKS_ONLY) { | ||
| console.log(` ${c.dim}Skills available:${c.reset} /commit /pr /review /plan /debug /vibe-audit`) | ||
| console.log(` ${c.dim}Agents available:${c.reset} /frontend /api /test /refactor /docs`) | ||
| } | ||
| if (!SKILLS_ONLY) { | ||
| console.log(` ${c.dim}Hooks active:${c.reset} pre-bash-safety Β· pre-commit-secrets Β· post-format Β· lint Β· notify`) | ||
| } | ||
| if (selectedSkills.length > 0) | ||
| console.log(` ${c.dim}Skills:${c.reset} ${selectedSkills.map(s => '/' + s.name).join(' ')}`) | ||
| if (selectedAgents.length > 0) | ||
| console.log(` ${c.dim}Agents:${c.reset} ${selectedAgents.map(a => '/' + a.name).join(' ')}`) | ||
| if (selectedHooks.length > 0) | ||
| console.log(` ${c.dim}Hooks:${c.reset} ${selectedHooks.map(h => path.basename(h, '.sh')).join(' ')}`) | ||
| console.log() | ||
| console.log(` Restart Claude Code to apply changes.`) | ||
| console.log(' Restart Claude Code to apply changes.') | ||
| console.log() |
+9
-4
@@ -12,2 +12,8 @@ # Changelog | ||
| ## [2.1.0] - 2026-04-11 | ||
| ### Added | ||
| - Update README.es.md to introduce AI Workflow Kit and enhance installation instructions | ||
| - Add documentation for Google Antigravity and cross-tool agent rules | ||
| ## [2.0.0] - 2026-04-09 | ||
@@ -47,6 +53,5 @@ | ||
| - Eval framework with Vitest + LLM-based evals via Anthropic SDK | ||
| - Spanish README (`README.es.md`)[1.1.0]: https://github.com/bezael/ai-workflow-kit/compare/...v1.1.0 | ||
| [1.1.0]: https://github.com/bezael/ai-workflow-kit/compare/...v1.1.0 | ||
| - Spanish README (`README.es.md`)[1.1.0]: https://github.com/bezael/ai-workflow-kit/compare/...v1.1.0[2.0.0]: https://github.com/bezael/ai-workflow-kit/compare/v1.1.0...v2.0.0 | ||
| [Unreleased]: https://github.com/bezael/ai-workflow-kit/compare/v2.0.0...HEAD | ||
| [2.0.0]: https://github.com/bezael/ai-workflow-kit/compare/v1.1.0...v2.0.0 | ||
| [Unreleased]: https://github.com/bezael/ai-workflow-kit/compare/v2.1.0...HEAD | ||
| [2.1.0]: https://github.com/bezael/ai-workflow-kit/compare/v2.0.0...v2.1.0 |
+24
-10
@@ -6,14 +6,17 @@ # CLAUDE.md β AI Workflow Kit | ||
| A collection of skills, agents, and memory patterns to make working with AI coding tools faster and more consistent. | ||
| Works with: **Claude Code**, **Cursor**, **GitHub Copilot**. | ||
| Works with: **Claude Code**, **Cursor**, **GitHub Copilot**, **Google Antigravity**. | ||
| ## Available Skills | ||
| | Command | Description | | ||
| |------------------|-----------------------------------------------------| | ||
| | `/ak:commit` | Generates commit message with real diff context | | ||
| | `/ak:pr` | Creates PR with description, test plan, and checklist | | ||
| | `/ak:review` | Reviews code or PR with configurable criteria | | ||
| | `/ak:plan` | Plans before executing complex tasks | | ||
| | `/ak:debug` | Structured debugging workflow | | ||
| | `/ak:vibe-audit` | Audit of apps generated with vibe coding | | ||
| | Command | Description | | ||
| |------------------------------|-----------------------------------------------------| | ||
| | `/ak:commit` | Generates commit message with real diff context | | ||
| | `/ak:pr` | Creates PR with description, test plan, and checklist | | ||
| | `/ak:review` | Reviews code or PR with configurable criteria | | ||
| | `/ak:plan` | Plans before executing complex tasks | | ||
| | `/ak:debug` | Structured debugging workflow | | ||
| | `/ak:vibe-audit` | Audit of apps generated with vibe coding | | ||
| | `/ak:memory save [topic]` | Persist learnings from the current session | | ||
| | `/ak:memory recall [question]` | Retrieve relevant memory before acting | | ||
| | `/ak:memory clean` | Remove or update stale memories | | ||
@@ -57,2 +60,13 @@ ## Specialized Agents | ||
| See `memory/project.md` for architecture decisions and accumulated context. | ||
| Memory is split across multiple files for clarity and maintainability: | ||
| | File | Contents | | ||
| |------|----------| | ||
| | `memory/MEMORY.md` | Index β start here, lists all memory files | | ||
| | `memory/project.md` | Architecture decisions, stack, business context | | ||
| | `memory/feedback.md` | What to repeat, what to avoid, team preferences | | ||
| | `memory/user.md` | Team roles, expertise, communication style | | ||
| | `memory/decisions/` | Individual ADRs β one file per decision | | ||
| **At the start of each non-trivial session:** read `memory/MEMORY.md`, then load the relevant files. | ||
| **At the end of a session with learnings:** run `/ak:memory save` to persist them. |
+27
-17
@@ -36,8 +36,8 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest' | ||
| describe('CLI --list', () => { | ||
| it('lists 6 skills', () => { | ||
| it('lists 7 skills', () => { | ||
| const r = runCLI(['--list'], tmpHome) | ||
| expect(r.exitCode).toBe(0) | ||
| // 6 skills: commit, pr, review, plan, debug, vibe-audit | ||
| // 7 skills: commit, pr, review, plan, debug, vibe-audit, memory | ||
| const skillMatches = r.stdout.match(/\/[a-z-]+/g) ?? [] | ||
| expect(skillMatches.length).toBeGreaterThanOrEqual(6) | ||
| expect(skillMatches.length).toBeGreaterThanOrEqual(7) | ||
| }) | ||
@@ -73,15 +73,25 @@ | ||
| it('copies skill files to ~/.claude/skills/', () => { | ||
| it('copies skill directories to ~/.claude/skills/', () => { | ||
| const dir = path.join(skillsHome, '.claude', 'skills') | ||
| const files = fs.readdirSync(dir) | ||
| expect(files).toContain('commit.md') | ||
| expect(files).toContain('pr.md') | ||
| expect(files).toContain('review.md') | ||
| expect(files).toContain('plan.md') | ||
| expect(files).toContain('debug.md') | ||
| expect(files).toContain('vibe-audit.md') | ||
| const entries = fs.readdirSync(dir) | ||
| expect(entries).toContain('commit') | ||
| expect(entries).toContain('pr') | ||
| expect(entries).toContain('review') | ||
| expect(entries).toContain('plan') | ||
| expect(entries).toContain('debug') | ||
| expect(entries).toContain('vibe-audit') | ||
| expect(entries).toContain('memory') | ||
| }) | ||
| it('copies agent files to ~/.claude/skills/', () => { | ||
| const dir = path.join(skillsHome, '.claude', 'skills') | ||
| it('each skill directory contains SKILL.md', () => { | ||
| const skillsDir = path.join(skillsHome, '.claude', 'skills') | ||
| for (const name of ['commit', 'pr', 'review', 'plan', 'debug', 'vibe-audit', 'memory']) { | ||
| const skillMd = path.join(skillsDir, name, 'SKILL.md') | ||
| expect(fs.existsSync(skillMd), `${name}/SKILL.md should exist`).toBe(true) | ||
| } | ||
| }) | ||
| it('copies agent files to ~/.claude/agents/', () => { | ||
| const dir = path.join(skillsHome, '.claude', 'agents') | ||
| expect(fs.existsSync(dir)).toBe(true) | ||
| const files = fs.readdirSync(dir) | ||
@@ -202,8 +212,8 @@ expect(files).toContain('frontend.md') | ||
| it('removes skill files', () => { | ||
| it('removes skill directories', () => { | ||
| const skillsDir = path.join(uninstallHome, '.claude', 'skills') | ||
| if (fs.existsSync(skillsDir)) { | ||
| const files = fs.readdirSync(skillsDir) | ||
| expect(files).not.toContain('commit.md') | ||
| expect(files).not.toContain('pr.md') | ||
| const entries = fs.readdirSync(skillsDir) | ||
| expect(entries).not.toContain('commit') | ||
| expect(entries).not.toContain('pr') | ||
| } | ||
@@ -210,0 +220,0 @@ // If dir doesn't exist, that's also acceptable (fully cleaned up) |
+24
-5
@@ -9,11 +9,25 @@ /** | ||
| * | ||
| * Requires: ANTHROPIC_API_KEY env var | ||
| * Requires: ANTHROPIC_API_KEY β set in .env or as an environment variable | ||
| */ | ||
| import { runAll as runCommit } from './skills/commit.eval.js' | ||
| import { runAll as runReview } from './skills/review.eval.js' | ||
| import { runAll as runVibeAudit } from './skills/vibe-audit.eval.js' | ||
| import { readFileSync, existsSync } from 'fs' | ||
| import { resolve } from 'path' | ||
| // Load .env from project root if present (works on Node 18+, no extra deps) | ||
| const envPath = resolve(process.cwd(), '.env') | ||
| if (existsSync(envPath)) { | ||
| for (const line of readFileSync(envPath, 'utf8').split('\n')) { | ||
| const match = line.match(/^([^#\s][^=]*)=(.*)$/) | ||
| if (match) { | ||
| const key = match[1].trim() | ||
| const value = match[2].trim().replace(/^(['"])(.*)\1$/, '$2') | ||
| process.env[key] ??= value // don't override vars already set in the shell | ||
| } | ||
| } | ||
| } | ||
| if (!process.env.ANTHROPIC_API_KEY) { | ||
| console.error('\nβ ANTHROPIC_API_KEY is not set. Skill evals require the Anthropic API.\n') | ||
| console.error('\nβ ANTHROPIC_API_KEY is not set.') | ||
| console.error(' Add it to a .env file in the project root:') | ||
| console.error(' ANTHROPIC_API_KEY=sk-ant-...\n') | ||
| process.exit(1) | ||
@@ -26,2 +40,7 @@ } | ||
| // Dynamic imports so they load AFTER process.env is populated above | ||
| const { runAll: runCommit } = await import('./skills/commit.eval.js') | ||
| const { runAll: runReview } = await import('./skills/review.eval.js') | ||
| const { runAll: runVibeAudit } = await import('./skills/vibe-audit.eval.js') | ||
| const suites = [ | ||
@@ -28,0 +47,0 @@ { name: 'commit', fn: runCommit }, |
@@ -22,3 +22,3 @@ /** | ||
| function loadSkill(name) { | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', `${name}.md`), 'utf8') | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', name, 'SKILL.md'), 'utf8') | ||
| } | ||
@@ -25,0 +25,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
| function loadSkill(name) { | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', `${name}.md`), 'utf8') | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', name, 'SKILL.md'), 'utf8') | ||
| } | ||
@@ -25,0 +25,0 @@ |
@@ -23,3 +23,3 @@ /** | ||
| function loadSkill(name) { | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', `${name}.md`), 'utf8') | ||
| return fs.readFileSync(path.join(REPO_ROOT, 'skills', name, 'SKILL.md'), 'utf8') | ||
| } | ||
@@ -26,0 +26,0 @@ |
+63
-4
@@ -68,2 +68,4 @@ #!/bin/bash | ||
| SETTINGS_FILE="$CLAUDE_DIR/settings.json" | ||
| GEMINI_DIR="$HOME/.gemini" | ||
| ANTIGRAVITY_SKILLS_DIR="$GEMINI_DIR/antigravity/skills" | ||
@@ -94,5 +96,6 @@ # βββ DRY RUN wrapper βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| SKILLS_TO_REMOVE=(commit pr review plan debug) | ||
| SKILLS_TO_REMOVE=(commit pr review plan debug vibe-audit) | ||
| AGENTS_TO_REMOVE=(frontend api test refactor docs) | ||
| HOOKS_TO_REMOVE=(pre-bash-safety pre-commit-secrets post-write-format post-edit-lint notify-done) | ||
| ANTIGRAVITY_SKILLS_TO_REMOVE=(commit pr review plan debug vibe-audit frontend api test refactor docs) | ||
@@ -115,2 +118,10 @@ for skill in "${SKILLS_TO_REMOVE[@]}" "${AGENTS_TO_REMOVE[@]}"; do | ||
| for skill in "${ANTIGRAVITY_SKILLS_TO_REMOVE[@]}"; do | ||
| DIR="$ANTIGRAVITY_SKILLS_DIR/$skill" | ||
| if [ -d "$DIR" ]; then | ||
| run "rm -rf '$DIR'" | ||
| success "Eliminado Antigravity skill: $DIR" | ||
| fi | ||
| done | ||
| warn "settings.json NO se eliminΓ³ automΓ‘ticamente." | ||
@@ -154,2 +165,11 @@ warn "Si quieres eliminarlo: rm $SETTINGS_FILE" | ||
| # Detectar Google Antigravity | ||
| ANTIGRAVITY_DETECTED=false | ||
| if [ -d "$GEMINI_DIR" ]; then | ||
| ANTIGRAVITY_DETECTED=true | ||
| success "Google Antigravity detectado: $GEMINI_DIR" | ||
| else | ||
| info "Google Antigravity no detectado (~/.gemini no existe). Los skills de Antigravity se instalarΓ‘n de todas formas." | ||
| fi | ||
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
@@ -221,2 +241,37 @@ # INSTALAR SKILLS Y AGENTES | ||
| info "Skills instalados: $INSTALLED_SKILLS | Omitidos: $SKIPPED_SKILLS" | ||
| # βββ Instalar skills de Google Antigravity ββββββββββββββββββββββββββββββββ | ||
| step "Instalando skills para Google Antigravity..." | ||
| run "mkdir -p '$ANTIGRAVITY_SKILLS_DIR'" | ||
| INSTALLED_AG=0 | ||
| SKIPPED_AG=0 | ||
| for skill_dir in "$SCRIPT_DIR"/antigravity-skills/*/; do | ||
| skill_name=$(basename "$skill_dir") | ||
| dest="$ANTIGRAVITY_SKILLS_DIR/$skill_name" | ||
| if [ -d "$dest" ]; then | ||
| if $DRY_RUN; then | ||
| run "cp -r '$skill_dir' '$ANTIGRAVITY_SKILLS_DIR/'" | ||
| ((INSTALLED_AG++)) | ||
| else | ||
| read -r -p " El skill de Antigravity '$skill_name' ya existe. ΒΏSobreescribir? [s/N] " confirm | ||
| if [[ "$confirm" =~ ^[sS]$ ]]; then | ||
| cp -r "$skill_dir" "$ANTIGRAVITY_SKILLS_DIR/" | ||
| success "Actualizado Antigravity skill: $skill_name" | ||
| ((INSTALLED_AG++)) | ||
| else | ||
| info "Omitido: $skill_name" | ||
| ((SKIPPED_AG++)) | ||
| fi | ||
| fi | ||
| else | ||
| run "cp -r '$skill_dir' '$ANTIGRAVITY_SKILLS_DIR/'" | ||
| success "Instalado Antigravity skill: $skill_name" | ||
| ((INSTALLED_AG++)) | ||
| fi | ||
| done | ||
| info "Antigravity skills instalados: $INSTALLED_AG | Omitidos: $SKIPPED_AG" | ||
| fi | ||
@@ -362,6 +417,10 @@ | ||
| if ! $HOOKS_ONLY; then | ||
| echo -e " ${GREEN}Skills:${RESET} $SKILLS_DIR" | ||
| echo -e " Comandos disponibles: /commit /pr /review /plan /debug" | ||
| echo -e " Agentes disponibles: /frontend /api /test /refactor /docs" | ||
| echo -e " ${GREEN}Claude Code:${RESET} $SKILLS_DIR" | ||
| echo -e " Comandos: /ak:commit /ak:pr /ak:review /ak:plan /ak:debug /ak:vibe-audit" | ||
| echo -e " Agentes: /ak:frontend /ak:api /ak:test /ak:refactor /ak:docs" | ||
| echo "" | ||
| echo -e " ${GREEN}Antigravity:${RESET} $ANTIGRAVITY_SKILLS_DIR" | ||
| echo -e " Skills: @commit @pr @review @plan @debug @vibe-audit" | ||
| echo -e " Agentes: @frontend @api @test @refactor @docs" | ||
| echo "" | ||
| fi | ||
@@ -368,0 +427,0 @@ |
+38
-15
@@ -1,25 +0,48 @@ | ||
| # Project Memory | ||
| # Memory: Project | ||
| Use this file to store architecture decisions, project context, and accumulated learnings. | ||
| Claude Code will read it every session if referenced in CLAUDE.md. | ||
| Architecture decisions, stack context, and accumulated project knowledge. | ||
| ## Architecture decisions | ||
| --- | ||
| <!-- Example: | ||
| ### 2026-04-05 β Auth with JWT | ||
| We chose refresh token rotation because the team already had experience with it | ||
| and token theft risk was a priority. Session cookies were evaluated but the frontend | ||
| is mobile-first which complicates cookie handling. | ||
| --> | ||
| ## Stack and conventions | ||
| <!-- Document the real project stack here when you have it --> | ||
| - Runtime: Node.js (ESM, `"type": "module"`) | ||
| - CLI entry: `bin/cli.js` β interactive installer, no external deps | ||
| - Tests/evals: `evals/` β uses Anthropic SDK directly, requires `ANTHROPIC_API_KEY` in `.env` | ||
| - Package: `ai-workflow-kit`, distributed via `npx` | ||
| ## What NOT to do | ||
| ## Architecture decisions | ||
| <!-- Negative learnings: things that were tried and didn't work --> | ||
| ### 2026-04-11 β Skills and agents use `<name>/SKILL.md` / `<name>/AGENT.md` directory format | ||
| **Decision:** All skills live in `skills/<name>/SKILL.md`; all agents in `agents/<name>/AGENT.md`. Supporting files (e.g. `patterns.md`) live alongside the main file in the same directory. | ||
| **Why:** Allows skills to ship with companion files. Claude Code also supports this format natively. Flat `.md` files are treated as legacy. | ||
| **Alternatives considered:** Single flat `.md` files β rejected because vibe-audit needed a companion `patterns.md`. | ||
| **Still valid?** Yes | ||
| ### 2026-04-11 β Agents are always installed as flat `.md` files to `~/.claude/agents/` | ||
| **Decision:** Even though agents are authored in `<name>/AGENT.md` directories, the CLI installs only the `AGENT.md` file to `~/.claude/agents/<name>.md`. | ||
| **Why:** Claude Code agents directory expects flat files, not subdirectories. | ||
| **Still valid?** Yes | ||
| ### 2026-04-11 β CLI: interactive numbered menu, no external deps | ||
| **Decision:** `bin/cli.js` uses Node.js built-in `readline` for the interactive selection menu. | ||
| **Why:** Keeps the installer zero-dependency. Menu supports numbers, shortcuts (a/s/ag/h), and combinations. | ||
| **Still valid?** Yes | ||
| ## Business context | ||
| <!-- Domain information that the AI needs to make better decisions --> | ||
| <!-- Domain knowledge the AI needs to make better decisions. | ||
| Example: | ||
| - "Order" always means a confirmed purchase, not a cart | ||
| - Users can have multiple roles simultaneously | ||
| - Free tier is limited to 3 projects, paid has no limit | ||
| --> | ||
| ## What NOT to do | ||
| <!-- Hard-won negative learnings. | ||
| Example: | ||
| ### 2026-03-10 β Don't use optimistic updates on payment flows | ||
| Caused a bug where UI showed success but payment failed silently. | ||
| Use pessimistic updates (wait for server confirmation) on anything money-related. | ||
| --> |
+4
-3
| { | ||
| "name": "ai-workflow-kit", | ||
| "version": "2.0.0", | ||
| "description": "Skills, agents & hooks for Claude Code, Cursor and GitHub Copilot", | ||
| "version": "2.2.0-beta.1", | ||
| "description": "Skills, agents & hooks for Claude Code, Cursor, GitHub Copilot, and Google Antigravity", | ||
| "type": "module", | ||
@@ -17,3 +17,4 @@ "bin": { | ||
| "release:minor": "npx node scripts/release.js minor", | ||
| "release:major": "npx node scripts/release.js major" | ||
| "release:major": "npx node scripts/release.js major", | ||
| "release:beta": "npx node scripts/release.js beta" | ||
| }, | ||
@@ -20,0 +21,0 @@ "devDependencies": { |
+162
-53
@@ -1,83 +0,192 @@ | ||
| # Hooks | ||
| # AI Workflow Kit | ||
| Scripts que Claude Code ejecuta automΓ‘ticamente antes o despuΓ©s de sus acciones. | ||
| La diferencia entre un repo de skills y uno de hooks es que los hooks **no requieren que el dev los active** β simplemente funcionan. | ||
|  | ||
| ## Hooks disponibles | ||
| Skills, agentes y hooks para trabajar con herramientas de AI coding de forma consistente y profesional. | ||
| Compatible con **Claude Code**, **Cursor**, **GitHub Copilot** y **Google Antigravity**. | ||
| | Hook | Evento | QuΓ© hace | | ||
| |------|--------|----------| | ||
| | `pre-bash-safety.sh` | Antes de cualquier Bash | Bloquea comandos destructivos, advierte sobre los peligrosos | | ||
| | `pre-commit-secrets.sh` | Antes de `git commit` | Escanea staged files buscando API keys, tokens, passwords | | ||
| | `post-write-format.sh` | DespuΓ©s de Write o Edit | Formatea el archivo con Prettier o Biome automΓ‘ticamente | | ||
| | `post-edit-lint.sh` | DespuΓ©s de Edit | Corre ESLint y devuelve errores para que Claude los corrija | | ||
| | `notify-done.sh` | Cuando Claude termina | NotificaciΓ³n de escritorio (Mac, Linux, Windows) | | ||
| ## InstalaciΓ³n | ||
| ### 1. Copia los hooks | ||
| ```bash | ||
| npx ai-workflow-kit | ||
| ``` | ||
| Reinicia tu herramienta de AI. TendrΓ‘s disponibles `/ak:commit`, `/ak:pr`, `/ak:plan`, `/ak:debug`, `/ak:review`, `/ak:vibe-audit`, `/ak:frontend`, `/ak:api`, `/ak:test`, `/ak:refactor`, `/ak:docs` β mΓ‘s 5 hooks automΓ‘ticos. | ||
| ```bash | ||
| mkdir -p ~/.claude/hooks | ||
| cp hooks/*.sh ~/.claude/hooks/ | ||
| chmod +x ~/.claude/hooks/*.sh | ||
| npx ai-workflow-kit --skills # solo skills y agentes | ||
| npx ai-workflow-kit --hooks # solo hooks | ||
| npx ai-workflow-kit --yes # sin confirmaciones | ||
| npx ai-workflow-kit --list # ver quΓ© se instalarΓa | ||
| npx ai-workflow-kit --uninstall | ||
| ``` | ||
| ### 2. Configura settings.json | ||
| O manualmente: | ||
| **Global** (aplica a todos tus proyectos): | ||
| ```bash | ||
| cp hooks/settings.template.json ~/.claude/settings.json | ||
| cp -r skills/* ~/.claude/skills/ | ||
| cp -r agents/* ~/.claude/skills/ | ||
| cp -r hooks/*.sh ~/.claude/hooks/ | ||
| chmod +x ~/.claude/hooks/*.sh | ||
| ``` | ||
| **Por proyecto** (solo este proyecto): | ||
| ## Estructura | ||
| ``` | ||
| ai-workflow-kit/ | ||
| βββ CLAUDE.md # Instrucciones para Claude Code | ||
| βββ GEMINI.md # Instrucciones para Google Antigravity | ||
| βββ AGENTS.md # Reglas cross-tool (todas las herramientas AI) | ||
| βββ .cursorrules # Reglas para Cursor | ||
| βββ .github/ | ||
| β βββ copilot-instructions.md # Instrucciones para GitHub Copilot | ||
| βββ antigravity-skills/ | ||
| β βββ commit/SKILL.md # @commit β genera mensajes de commit semΓ‘nticos | ||
| β βββ pr/SKILL.md # @pr β crea PRs con descripciΓ³n completa | ||
| β βββ review/SKILL.md # @review β revisa cΓ³digo con criterios reales | ||
| β βββ plan/SKILL.md # @plan β planifica antes de ejecutar | ||
| β βββ debug/SKILL.md # @debug β workflow de debugging estructurado | ||
| β βββ vibe-audit/SKILL.md # @vibe-audit β audita apps generadas con vibe coding | ||
| β βββ frontend/SKILL.md # @frontend β genera componentes de UI | ||
| β βββ api/SKILL.md # @api β genera endpoints con validaciΓ³n | ||
| β βββ test/SKILL.md # @test β escribe tests orientados a comportamiento | ||
| β βββ refactor/SKILL.md # @refactor β mejora cΓ³digo sin romper nada | ||
| β βββ docs/SKILL.md # @docs β JSDoc, README, ADR | ||
| βββ skills/ | ||
| β βββ commit.md # /ak:commit β genera mensajes de commit semΓ‘nticos | ||
| β βββ pr.md # /ak:pr β crea PRs con descripciΓ³n completa | ||
| β βββ review.md # /ak:review β revisa cΓ³digo con criterios reales de ingenierΓa | ||
| β βββ plan.md # /ak:plan β planifica antes de ejecutar | ||
| β βββ debug.md # /ak:debug β workflow de debugging estructurado | ||
| βββ agents/ | ||
| β βββ frontend.md # /ak:frontend β genera componentes de UI | ||
| β βββ api.md # /ak:api β genera endpoints con validaciΓ³n | ||
| β βββ test.md # /ak:test β escribe tests orientados a comportamiento | ||
| β βββ refactor.md # /ak:refactor β mejora cΓ³digo sin romper nada | ||
| β βββ docs.md # /ak:docs β JSDoc, README, ADR | ||
| βββ hooks/ | ||
| β βββ README.md # CΓ³mo instalar y personalizar hooks | ||
| β βββ settings.template.json # ConfiguraciΓ³n lista para copiar | ||
| β βββ pre-bash-safety.sh # Bloquea comandos destructivos | ||
| β βββ pre-commit-secrets.sh # Detecta API keys antes de commitear | ||
| β βββ post-write-format.sh # Auto-formatea con Prettier/Biome | ||
| β βββ post-edit-lint.sh # Lintea despuΓ©s de cada ediciΓ³n | ||
| β βββ notify-done.sh # NotificaciΓ³n de escritorio cuando Claude termina | ||
| βββ memory/ | ||
| βββ project.md # Memoria persistente del proyecto | ||
| ``` | ||
| ## Skills disponibles | ||
| | Skill | Comando | QuΓ© hace | | ||
| |-------|---------|----------| | ||
| | commit | `/ak:commit` | Lee el diff real y genera un mensaje de commit semΓ‘ntico | | ||
| | pr | `/ak:pr` | Crea PR con descripciΓ³n, plan de tests y checklist | | ||
| | review | `/ak:review @file` | Revisa cΓ³digo: bugs, seguridad, performance | | ||
| | plan | `/ak:plan [tarea]` | Planifica antes de ejecutar tareas complejas | | ||
| | debug | `/ak:debug [problema]` | Diagnostica con hipΓ³tesis antes de proponer fixes | | ||
| | vibe-audit | `/ak:vibe-audit` | AuditorΓa completa de apps generadas con vibe coding | | ||
| ## Agentes especializados | ||
| | Agente | Comando | QuΓ© hace | | ||
| |--------|---------|----------| | ||
| | frontend | `/ak:frontend [descripciΓ³n]` | Genera componentes siguiendo el design system del proyecto | | ||
| | api | `/ak:api [descripciΓ³n]` | Genera endpoints con validaciΓ³n, auth y manejo de errores | | ||
| | test | `/ak:test @file` | Escribe tests por comportamiento, no por implementaciΓ³n | | ||
| | refactor | `/ak:refactor @file` | Mejora cΓ³digo sin cambiar comportamiento | | ||
| | docs | `/ak:docs @file` | Genera JSDoc, README o ADR segΓΊn se necesite | | ||
| ## Hooks disponibles | ||
| Los hooks se ejecutan **automΓ‘ticamente** β el dev no necesita activarlos. | ||
| | Hook | Evento | QuΓ© hace | | ||
| |------|--------|----------| | ||
| | `pre-bash-safety` | Antes de Bash | Bloquea `rm -rf /`, force push, drop table, etc. | | ||
| | `pre-commit-secrets` | Antes de `git commit` | Escanea archivos staged buscando API keys y tokens | | ||
| | `post-write-format` | DespuΓ©s de Write/Edit | Formatea con Prettier o Biome automΓ‘ticamente | | ||
| | `post-edit-lint` | DespuΓ©s de Edit | Corre ESLint y devuelve errores a Claude | | ||
| | `notify-done` | Cuando Claude termina | NotificaciΓ³n de escritorio (Mac/Linux/Windows) | | ||
| Ver `hooks/README.md` para instrucciones de instalaciΓ³n. | ||
| ## CΓ³mo usar con Claude Code | ||
| ### Instalar los skills | ||
| ```bash | ||
| mkdir -p .claude | ||
| cp hooks/settings.template.json .claude/settings.json | ||
| cp skills/*.md ~/.claude/skills/ | ||
| ``` | ||
| Edita las rutas en `settings.json` si instalaste los hooks en otro lugar. | ||
| ### Usar en cualquier proyecto | ||
| ### 3. Verifica | ||
| Agrega a tu `CLAUDE.md`: | ||
| ```markdown | ||
| ## Skills disponibles | ||
| Ver ~/.claude/skills/ para la lista completa. | ||
| Memoria del proyecto en memory/project.md. | ||
| ``` | ||
| ### Usar con Cursor | ||
| Las reglas en `.cursorrules` se aplican automΓ‘ticamente. Copia el archivo a la raΓz de tu proyecto. | ||
| ### Usar con GitHub Copilot | ||
| El archivo `.github/copilot-instructions.md` se usa automΓ‘ticamente en repos de GitHub. | ||
| ### Usar con Google Antigravity | ||
| Copia `GEMINI.md` y `AGENTS.md` a la raΓz de tu proyecto. El installer copia los skills a `~/.gemini/antigravity/skills/` automΓ‘ticamente. | ||
| ```bash | ||
| claude # abre Claude Code | ||
| /hooks # muestra los hooks activos | ||
| # Copiar reglas del proyecto | ||
| cp GEMINI.md tu-proyecto/ | ||
| cp AGENTS.md tu-proyecto/ | ||
| # O instalar todos los skills de Antigravity globalmente | ||
| npx ai-workflow-kit --skills | ||
| ``` | ||
| ## CΓ³mo funcionan los eventos | ||
| Una vez instalados, invoca los skills con `@` en el sidebar de Antigravity: | ||
| - `@commit`, `@pr`, `@review`, `@plan`, `@debug`, `@vibe-audit` | ||
| - `@frontend`, `@api`, `@test`, `@refactor`, `@docs` | ||
| | Evento Claude Code | CuΓ‘ndo se dispara | | ||
| |-------------------|-------------------| | ||
| | `PreToolUse` | Antes de que Claude use una herramienta (Bash, Write, Edit...) | | ||
| | `PostToolUse` | DespuΓ©s de que Claude usa una herramienta | | ||
| | `Stop` | Cuando Claude termina de responder | | ||
| | `Notification` | Cuando Claude quiere notificar algo | | ||
| ## Versionado y Changelog | ||
| ## Personalizar un hook | ||
| Este proyecto sigue [Semantic Versioning](https://semver.org/) y [Keep a Changelog](https://keepachangelog.com/). | ||
| Cada script lee el input como JSON desde `stdin`. Estructura del input: | ||
| Ver [CHANGELOG.md](./CHANGELOG.md) para el historial completo de releases. | ||
| ```json | ||
| // PreToolUse / PostToolUse | ||
| { | ||
| "tool_name": "Bash", | ||
| "command": "git status", // solo en Bash | ||
| "file_path": "/ruta/archivo" // solo en Write/Edit | ||
| } | ||
| ### Publicar una nueva versiΓ³n | ||
| // Stop | ||
| { | ||
| "stop_reason": "end_turn" | ||
| } | ||
| ```bash | ||
| npm run release:patch # 1.0.0 β 1.0.1 bug fixes | ||
| npm run release:minor # 1.0.0 β 1.1.0 nuevos skills, agentes o hooks | ||
| npm run release:major # 1.0.0 β 2.0.0 breaking changes | ||
| ``` | ||
| Salida: | ||
| - `exit 0` β permitir / continuar | ||
| - `exit 1` + mensaje en stderr β bloquear + mostrar mensaje al usuario | ||
| El script de release automΓ‘ticamente: | ||
| - Lee los commits desde el ΓΊltimo tag y los agrupa por tipo (`feat` β Added, `fix` β Fixed, `refactor` β Changed) | ||
| - Agrega la nueva entrada al inicio de `CHANGELOG.md` | ||
| - Actualiza la versiΓ³n en `package.json` | ||
| - Crea un commit y un tag anotado | ||
| - Hace push de ambos al remoto | ||
| ## Seguridad | ||
| > Requiere un working tree limpio y mensajes de commit en formato Conventional Commits (`feat:`, `fix:`, `refactor:`, etc.). | ||
| Los hooks tienen acceso completo al sistema. Revisa cada script antes de instalarlo. | ||
| Los hooks de este repo solo leen informaciΓ³n β nunca escriben ni modifican archivos | ||
| excepto `post-write-format.sh` que formatea el archivo reciΓ©n escrito. | ||
| ## CΓ³mo contribuir | ||
| 1. Haz fork del repo | ||
| 2. Agrega tu skill en `skills/nombre.md` siguiendo el patrΓ³n existente | ||
| 3. Documenta el trigger, los pasos y las reglas | ||
| 4. Abre un PR con `/ak:pr` | ||
| ## FilosofΓa | ||
| - **Diagnosticar antes de actuar** β un plan aprobado vale mΓ‘s que cΓ³digo rΓ‘pido | ||
| - **Skills cross-tool** β los mismos patrones funcionan en Claude Code, Cursor, Copilot y Antigravity | ||
| - **Memoria persistente** β la IA debe recordar el contexto, no pedirlo cada vez | ||
| - **Output predecible** β cada skill produce el mismo formato, siempre |
+32
-1
@@ -6,3 +6,3 @@ # AI Workflow Kit | ||
| Skills, agents, and hooks for working with AI coding tools consistently and professionally. | ||
| Works with **Claude Code**, **Cursor**, and **GitHub Copilot**. | ||
| Works with **Claude Code**, **Cursor**, **GitHub Copilot**, and **Google Antigravity**. | ||
@@ -39,5 +39,19 @@ ## Installation | ||
| βββ CLAUDE.md # Instructions for Claude Code | ||
| βββ GEMINI.md # Instructions for Google Antigravity | ||
| βββ AGENTS.md # Cross-tool rules (all AI tools) | ||
| βββ .cursorrules # Rules for Cursor | ||
| βββ .github/ | ||
| β βββ copilot-instructions.md # Instructions for GitHub Copilot | ||
| βββ antigravity-skills/ | ||
| β βββ commit/SKILL.md # @commit β generates semantic commit messages | ||
| β βββ pr/SKILL.md # @pr β creates PRs with full description | ||
| β βββ review/SKILL.md # @review β reviews code with real criteria | ||
| β βββ plan/SKILL.md # @plan β plans before executing | ||
| β βββ debug/SKILL.md # @debug β structured debugging workflow | ||
| β βββ vibe-audit/SKILL.md # @vibe-audit β audits vibe-coded apps | ||
| β βββ frontend/SKILL.md # @frontend β generates UI components | ||
| β βββ api/SKILL.md # @api β generates endpoints with validation | ||
| β βββ test/SKILL.md # @test β writes behavior-driven tests | ||
| β βββ refactor/SKILL.md # @refactor β improves code without breaking anything | ||
| β βββ docs/SKILL.md # @docs β JSDoc, README, ADR | ||
| βββ skills/ | ||
@@ -129,2 +143,19 @@ β βββ commit.md # /ak:commit β generates semantic commit messages | ||
| ### Use with Google Antigravity | ||
| Copy `GEMINI.md` and `AGENTS.md` to your project root. The installer copies skills to `~/.gemini/antigravity/skills/` automatically. | ||
| ```bash | ||
| # Copy project rules | ||
| cp GEMINI.md your-project/ | ||
| cp AGENTS.md your-project/ | ||
| # Or install all Antigravity skills globally | ||
| npx ai-workflow-kit --skills | ||
| ``` | ||
| Once installed, invoke skills with `@` in the Antigravity sidebar: | ||
| - `@commit`, `@pr`, `@review`, `@plan`, `@debug`, `@vibe-audit` | ||
| - `@frontend`, `@api`, `@test`, `@refactor`, `@docs` | ||
| ## Versioning & Changelog | ||
@@ -131,0 +162,0 @@ |
+28
-12
@@ -158,4 +158,4 @@ #!/usr/bin/env node | ||
| if (!["patch", "minor", "major"].includes(bump)) { | ||
| console.error("Usage: node scripts/release.js [patch|minor|major]"); | ||
| if (!["patch", "minor", "major", "beta"].includes(bump)) { | ||
| console.error("Usage: node scripts/release.js [patch|minor|major|beta]"); | ||
| process.exit(1); | ||
@@ -191,9 +191,22 @@ } | ||
| const [major, minor, patch] = currentVersion.split(".").map(Number); | ||
| const next = | ||
| bump === "major" | ||
| ? `${major + 1}.0.0` | ||
| : bump === "minor" | ||
| ? `${major}.${minor + 1}.0` | ||
| : `${major}.${minor}.${patch + 1}`; | ||
| // Beta: if current is already X.Y.Z-beta.N β increment N, else bump minor and start beta.1 | ||
| let next; | ||
| if (bump === "beta") { | ||
| const betaMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)-beta\.(\d+)$/); | ||
| if (betaMatch) { | ||
| const [, ma, mi, pa, n] = betaMatch; | ||
| next = `${ma}.${mi}.${pa}-beta.${Number(n) + 1}`; | ||
| } else { | ||
| const [ma, mi] = currentVersion.split(".").map(Number); | ||
| next = `${ma}.${mi + 1}.0-beta.1`; | ||
| } | ||
| } else { | ||
| const [major, minor, patch] = currentVersion.replace(/-.*/, "").split(".").map(Number); | ||
| next = | ||
| bump === "major" | ||
| ? `${major + 1}.0.0` | ||
| : bump === "minor" | ||
| ? `${major}.${minor + 1}.0` | ||
| : `${major}.${minor}.${patch + 1}`; | ||
| } | ||
@@ -206,4 +219,6 @@ const today = new Date().toISOString().slice(0, 10); | ||
| updateChangelog(entry, next, tag); | ||
| console.log("β CHANGELOG.md updated"); | ||
| if (bump !== "beta") { | ||
| updateChangelog(entry, next, tag); | ||
| console.log("β CHANGELOG.md updated"); | ||
| } | ||
@@ -218,3 +233,4 @@ // Bump version in package.json directly (avoids npm version's own git check) | ||
| // Single commit with both files + annotated tag | ||
| run("git add CHANGELOG.md package.json"); | ||
| const filesToAdd = bump === "beta" ? "package.json" : "CHANGELOG.md package.json"; | ||
| run(`git add ${filesToAdd}`); | ||
| run(`git commit -m "chore(release): v${next}"`); | ||
@@ -221,0 +237,0 @@ run(`git tag -a v${next} -m "v${next}"`); |
| # Agent: API | ||
| Backend endpoint specialist. Generates routes, controllers, and validations that follow the project's patterns and are production-ready. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "create the endpoint for X" | ||
| - "add the route for X" | ||
| - "I need an API for X" | ||
| - `/api [description]` | ||
| ## What this agent does first | ||
| 1. **Read the most similar router or controller** to the requested endpoint β follows its exact structure | ||
| 2. **Detect the framework**: Express, Fastify, Hono, NestJS β adapts the code to the project pattern | ||
| 3. **Read the database schema** if it exists (Prisma schema, Mongoose models, etc.) | ||
| 4. **Identify the authentication pattern** β middleware? decorator? guard? | ||
| ## Structure of a complete endpoint | ||
| ### The minimum for a production endpoint | ||
| ``` | ||
| 1. Input validation (body, params, query) | ||
| 2. Authentication / Authorization (if applicable) | ||
| 3. Business logic | ||
| 4. Error handling | ||
| 5. Typed response | ||
| ``` | ||
| ### Example structure (Express + Zod) | ||
| ```ts | ||
| // Validation schema β defines the API contract | ||
| const CreateUserSchema = z.object({ | ||
| email: z.string().email(), | ||
| name: z.string().min(2).max(100), | ||
| }) | ||
| // Handler β single responsibility | ||
| async function createUser(req: Request, res: Response) { | ||
| const result = CreateUserSchema.safeParse(req.body) | ||
| if (!result.success) { | ||
| return res.status(400).json({ error: result.error.flatten() }) | ||
| } | ||
| try { | ||
| const user = await userService.create(result.data) | ||
| return res.status(201).json(user) | ||
| } catch (error) { | ||
| if (error instanceof UserAlreadyExistsError) { | ||
| return res.status(409).json({ error: 'Email already in use' }) | ||
| } | ||
| throw error // let the global error handler catch it | ||
| } | ||
| } | ||
| // Route registration | ||
| router.post('/users', authenticate, createUser) | ||
| ``` | ||
| ## Security (required) | ||
| - **Validate all input** before processing β use Zod, Joi, class-validator, or whatever the project uses | ||
| - **Never trust req.body directly** β always sanitize | ||
| - **Authorization β Authentication** β verify the user CAN do the action, not just that they're logged in | ||
| - **Don't expose stack traces** in error responses in production | ||
| - **Rate limiting** on public or authentication endpoints | ||
| ## Correct HTTP status codes | ||
| | Situation | Status | | ||
| |-----------|--------| | ||
| | Created successfully | 201 | | ||
| | OK / read | 200 | | ||
| | No content (delete) | 204 | | ||
| | Invalid input | 400 | | ||
| | Not authenticated | 401 | | ||
| | No permissions | 403 | | ||
| | Not found | 404 | | ||
| | Conflict (already exists) | 409 | | ||
| | Server error | 500 | | ||
| ## Always delivers | ||
| 1. The validation schema | ||
| 2. The complete handler | ||
| 3. The route registration | ||
| 4. Possible errors with their status codes | ||
| 5. A basic integration test for the endpoint (optional but recommended) | ||
| ## What this agent does NOT do | ||
| - Does not use `req.body.field` without validating first | ||
| - Does not put database logic directly in the handler (uses services/repositories) | ||
| - Does not silence errors with empty `try/catch` | ||
| - Does not return passwords or sensitive data in responses |
| # Agent: Docs | ||
| Technical documentation specialist. Generates docs that a real developer will actually read: direct, with examples, no filler. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "document this module" | ||
| - "write the README for X" | ||
| - "add JSDoc to these functions" | ||
| - `/docs @file` | ||
| ## What this agent does first | ||
| 1. **Read the code to document** completely | ||
| 2. **Identify the audience**: internal developer? package user? public API? | ||
| 3. **Detect what type of documentation is needed**: | ||
| - JSDoc/TSDoc for functions and types | ||
| - README for modules or projects | ||
| - Usage guide for APIs or SDKs | ||
| - ADR (Architecture Decision Record) for important decisions | ||
| ## Documentation types and when to use them | ||
| ### JSDoc / TSDoc β for functions and types | ||
| Only when the function signature isn't clear enough. | ||
| ```ts | ||
| // Doesn't need JSDoc β self-explanatory | ||
| function add(a: number, b: number): number | ||
| // Needs JSDoc β non-obvious behavior | ||
| /** | ||
| * Calculates the final price applying cascading discounts. | ||
| * Discounts are applied in order: category discount first, | ||
| * then user discount. They are not cumulative over the base price. | ||
| * | ||
| * @param basePrice - Price before taxes | ||
| * @param discounts - List of discounts in percentage (0-100) | ||
| * @returns Final price rounded to 2 decimal places | ||
| * | ||
| * @example | ||
| * calculateFinalPrice(100, [10, 20]) // β 72 (not 70) | ||
| */ | ||
| function calculateFinalPrice(basePrice: number, discounts: number[]): number | ||
| ``` | ||
| ### Module README | ||
| Minimal structure that works: | ||
| ```markdown | ||
| # Module name | ||
| One line explaining what it does and why it exists. | ||
| ## Installation / Setup | ||
| [exact commands, copy-pasteable] | ||
| ## Basic usage | ||
| [minimal functional example] | ||
| ## API / Options | ||
| [table or list of parameters with types and default values] | ||
| ## Common use cases | ||
| [2-3 real examples, not toy ones] | ||
| ``` | ||
| ### ADR (Architecture Decision Record) | ||
| For architecture decisions that go in `memory/project.md`: | ||
| ```markdown | ||
| ### YYYY-MM-DD β [Decision title] | ||
| **Context**: [Why this decision had to be made] | ||
| **Decision**: [What was decided] | ||
| **Alternatives considered**: [What else was evaluated] | ||
| **Consequences**: [Accepted trade-offs] | ||
| ``` | ||
| ## Principles of good documentation | ||
| - **Examples > explanations** β show, don't tell | ||
| - **One line explaining the "why"** is worth more than three paragraphs of the "what" | ||
| - **Document the non-obvious** β if the code is already clear, don't add noise | ||
| - **Keep examples executable** β an example that doesn't work is worse than no example | ||
| ## What this agent does NOT do | ||
| - Does not document every line of code β only what needs context | ||
| - Does not generate docstrings of "this method returns X" if the signature already says it | ||
| - Does not write documentation in a different language than the code without being asked | ||
| - Does not create documentation that nobody will read (docs for compliance) |
| # Agent: Frontend | ||
| UI and component specialist. Generates frontend code that follows the project's existing patterns, is accessible, and is production-ready. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "create a component for X" | ||
| - "add the view for X" | ||
| - "design the form for X" | ||
| - `/frontend [description]` | ||
| ## What this agent does first | ||
| Before generating a single line of code: | ||
| 1. **Read the existing design system**: find a similar component already made in the project (`Button`, `Input`, `Card`, etc.) | ||
| 2. **Detect the stack**: React? Vue? Tailwind? CSS modules? styled-components? | ||
| 3. **Read the most similar component** to what's requested β copy its structure, don't invent one | ||
| 4. **Ask ONE thing** if critical information is missing (e.g.: does it need to be responsive? does it handle local or global state?) | ||
| ## How it generates the component | ||
| ### Output structure | ||
| ```tsx | ||
| // Always typed with TypeScript | ||
| // Explicit props with interface or type | ||
| // Don't use `any` | ||
| interface ComponentNameProps { | ||
| // props with exact types | ||
| } | ||
| export function ComponentName({ ...props }: ComponentNameProps) { | ||
| // minimum necessary logic | ||
| // no side effects in render | ||
| return ( | ||
| // clean and semantic JSX | ||
| ) | ||
| } | ||
| ``` | ||
| ### Internal order of a component | ||
| 1. Imports | ||
| 2. Types/Interfaces | ||
| 3. Component constants (outside the component) | ||
| 4. The component itself | ||
| 5. Component helper functions (inside if they use hooks, outside if pure) | ||
| 6. Export | ||
| ### Accessibility (required) | ||
| - Use semantic elements: `<button>`, `<nav>`, `<main>`, `<section>` β not everything is a `<div>` | ||
| - Every interactive element has `aria-label` if it has no visible text | ||
| - Forms with `<label>` associated to each input | ||
| - Images with descriptive `alt` | ||
| ### Responsive | ||
| - Mobile-first by default | ||
| - If using Tailwind: breakpoints `sm:`, `md:`, `lg:` in that order | ||
| ## What this agent does NOT do | ||
| - Does not install new dependencies without explicitly mentioning it | ||
| - Does not create a design system from scratch if one already exists | ||
| - Does not use inline styles (except dynamic values impossible to do with classes) | ||
| - Does not generate components over 200 lines without proposing to split them | ||
| ## Always delivers | ||
| 1. The component ready to use | ||
| 2. The suggested path where to save it | ||
| 3. If the component needs a basic test, includes it in the same output |
| # Agent: Refactor | ||
| Specialist in improving existing code without changing its behavior. Reduces complexity, eliminates technical debt, and makes code more maintainable. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "refactor this file" | ||
| - "this code is too coupled" | ||
| - "simplify this function" | ||
| - `/refactor @file` | ||
| ## What this agent does first | ||
| 1. **Read the complete code** of the file or function | ||
| 2. **Check if there are tests** β if not, warn before refactoring (without tests, a refactor can break things without anyone knowing) | ||
| 3. **Identify concrete problems** β doesn't refactor for the sake of refactoring | ||
| ## Signs that code needs refactoring | ||
| ### High cyclomatic complexity | ||
| Functions with many nested `if/else` or more than 3 levels of indentation. | ||
| ```ts | ||
| // Before: deep nesting | ||
| function processOrder(order) { | ||
| if (order) { | ||
| if (order.items) { | ||
| if (order.items.length > 0) { | ||
| if (order.user) { | ||
| // real logic here | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // After: early returns / guard clauses | ||
| function processOrder(order) { | ||
| if (!order?.items?.length) return | ||
| if (!order.user) return | ||
| // real logic here, without nesting | ||
| } | ||
| ``` | ||
| ### Function doing too much | ||
| A function should do ONE thing. If its name has "and" or "or", it probably does two. | ||
| ### Duplicated code | ||
| If the same block appears in 2+ places, extract it to a function. | ||
| ### Unclear names | ||
| ```ts | ||
| // Before | ||
| const d = new Date() | ||
| const u = users.filter(x => x.a === true) | ||
| // After | ||
| const now = new Date() | ||
| const activeUsers = users.filter(user => user.isActive) | ||
| ``` | ||
| ### Magic numbers / magic strings | ||
| ```ts | ||
| // Before | ||
| if (retries > 3) { ... } | ||
| if (status === 'PENDING') { ... } | ||
| // After | ||
| const MAX_RETRIES = 3 | ||
| const OrderStatus = { PENDING: 'PENDING', FULFILLED: 'FULFILLED' } as const | ||
| ``` | ||
| ## How it delivers the refactor | ||
| 1. **Explain what problem it solves** before showing the code | ||
| 2. **Show before and after** in a diff or separate blocks | ||
| 3. **Confirm behavior didn't change** β tests should pass the same way | ||
| 4. **If the refactor is large**, split it into small steps and ask if to continue | ||
| ## What this agent does NOT do | ||
| - Does not change behavior under the name of "refactor" | ||
| - Does not add new dependencies to simplify trivial code | ||
| - Does not convert imperative code to functional just because it's trendy (if the team doesn't use that style) | ||
| - Does not refactor code it doesn't understand β asks first | ||
| - Does not touch code outside the requested scope (if you ask to refactor a function, it doesn't change the whole file) |
| # Agent: Test | ||
| Testing specialist. Generates tests that verify real behavior, not internal implementation. | ||
| ## When to invoke it | ||
| When the user asks: | ||
| - "write tests for X" | ||
| - "add coverage to X" | ||
| - "how would you test X?" | ||
| - `/test [file or function]` | ||
| ## What this agent does first | ||
| 1. **Read the file to test completely** β understand what it does before writing anything | ||
| 2. **Read existing tests** β copy the style, the framework, the setup helpers | ||
| 3. **Identify the appropriate test type**: | ||
| - Pure function β unit test | ||
| - Module with dependencies β unit test with mocks | ||
| - HTTP endpoint β integration test | ||
| - User flow β E2E test (or don't write it, warn the user) | ||
| ## Testing philosophy | ||
| **Test behavior, not implementation.** | ||
| ```ts | ||
| // BAD β tests the name of the internal method | ||
| expect(userService.hashPassword).toHaveBeenCalled() | ||
| // GOOD β tests the observable result | ||
| const user = await createUser({ email: 'a@b.com', password: '123' }) | ||
| expect(user.password).not.toBe('123') // password was hashed | ||
| ``` | ||
| ## Structure of a well-written test | ||
| ```ts | ||
| describe('module or function name', () => { | ||
| // Shared setup (only what all tests need) | ||
| beforeEach(() => { ... }) | ||
| describe('use case or scenario', () => { | ||
| it('should [expected behavior] when [condition]', async () => { | ||
| // Arrange β prepare state | ||
| const input = { ... } | ||
| // Act β execute the action | ||
| const result = await functionUnderTest(input) | ||
| // Assert β verify the result | ||
| expect(result).toEqual(...) | ||
| }) | ||
| }) | ||
| }) | ||
| ``` | ||
| ## What to test first (by priority) | ||
| 1. **Happy path** β the normal flow that works | ||
| 2. **Edge cases** β boundary values, empty arrays, empty strings, 0, null | ||
| 3. **Expected errors** β what happens when something fails as expected | ||
| 4. **Security** β malicious inputs if the code processes them | ||
| ## Mocks: when and how | ||
| **Mock external dependencies, not internal logic.** | ||
| ```ts | ||
| // Mock: database, external APIs, file system, time | ||
| vi.mock('../db/userRepository') | ||
| vi.spyOn(Date, 'now').mockReturnValue(1234567890) | ||
| // DON'T mock: pure utility functions, business logic of the module under test | ||
| ``` | ||
| ## Supported frameworks | ||
| Automatically detects what the project uses: | ||
| - **Vitest** β projects with Vite | ||
| - **Jest** β projects with CRA, Next.js, Node | ||
| - **Testing Library** β React/Vue components | ||
| - **Supertest** β Express endpoints | ||
| ## Always delivers | ||
| 1. Tests ready to run | ||
| 2. The command to run them (`npm test`, `npx vitest`, etc.) | ||
| 3. If anything is needed for tests to pass (fixtures, factories, mocks), includes it | ||
| ## What this agent does NOT do | ||
| - Does not write tests that only verify the code compiles | ||
| - Does not mock everything so tests never really fail | ||
| - Does not write snapshot tests for logic that changes frequently | ||
| - Does not write 1 test per line of code β writes 1 test per behavior |
| # Skill: /commit | ||
| Generates a semantic commit message by reading the real diff. Don't invent β read the code. | ||
| ## When to use it | ||
| When the user writes `/commit` or asks to "make a commit" / "commit the changes". | ||
| ## Steps | ||
| 1. Run `git diff --staged` to see staged changes. | ||
| - If nothing is staged, run `git diff` to see unstaged changes and report it. | ||
| 2. Read the full diff. Identify: | ||
| - **What changed** (files, functions, logic) | ||
| - **Why it probably changed** (new feature, fix, refactor, docs, etc.) | ||
| 3. Classify the commit type: | ||
| - `feat:` β new functionality | ||
| - `fix:` β bug fix | ||
| - `refactor:` β code change without behavior change | ||
| - `chore:` β maintenance tasks (deps, config, scripts) | ||
| - `docs:` β documentation only | ||
| - `test:` β tests only | ||
| - `style:` β formatting, whitespace, semicolons (no logic) | ||
| 4. Draft the message in the format: `type(optional scope): imperative description` | ||
| - Maximum 72 characters on the first line | ||
| - In English by default | ||
| - Optional body if the change needs more context | ||
| 5. Propose the message and ask if it's good or needs adjustments before running the commit. | ||
| ## Example output | ||
| ``` | ||
| feat(auth): add JWT refresh token rotation | ||
| Tokens now rotate on each refresh request to reduce exposure window. | ||
| Invalidates old token immediately after issuing new one. | ||
| ``` | ||
| ## Rules | ||
| - NEVER commit `.env` files, credentials, or secrets. | ||
| - If you see sensitive files in staged, warn before continuing. | ||
| - If the diff is large and mixes concerns, suggest splitting it into multiple commits. |
| # Skill: /debug | ||
| Structured debugging workflow. Diagnose before proposing fixes. | ||
| ## When to use it | ||
| When the user writes `/debug [problem description]` or reports a bug, error, or unexpected behavior. | ||
| ## Steps | ||
| ### Phase 1: Reproduce and understand | ||
| 1. Ask the user (if not provided): | ||
| - What behavior were you expecting? | ||
| - What behavior are you getting? | ||
| - When did it start? Does it always happen or is it intermittent? | ||
| 2. Read the files relevant to the error. If there's a stack trace, follow the trail from the error upward. | ||
| 3. Find the exact point where behavior diverges from expected. | ||
| ### Phase 2: Hypotheses | ||
| Before touching code, list the most likely causes: | ||
| ``` | ||
| Hypotheses: | ||
| 1. [Most likely cause] β probability: high/medium/low | ||
| 2. [Second cause] β probability: high/medium/low | ||
| 3. [Third cause] β probability: high/medium/low | ||
| ``` | ||
| Always start with the highest-probability hypothesis. | ||
| ### Phase 3: Verify | ||
| For each hypothesis, propose a minimal verification (a `console.log`, a test, checking a variable's value) before proposing a full fix. | ||
| ### Phase 4: Fix | ||
| Only when the cause is confirmed: | ||
| 1. Propose the minimal fix that solves the problem | ||
| 2. Explain why it works | ||
| 3. Point out if the fix can have side effects | ||
| ### Phase 5: Prevention (optional) | ||
| If the bug reveals a problematic pattern, suggest how to prevent it in the future (test, validation, stricter type, etc.). | ||
| ## Rules | ||
| - Don't propose fixes before understanding the cause. A fix without diagnosis is another bug waiting to appear. | ||
| - The simplest fix that solves the problem is the best fix. | ||
| - If the bug is in production, prioritize the quick fix (hotfix) and document the proper fix for later. |
| # Skill: /plan | ||
| Plan before executing. For complex tasks that touch multiple files or require architecture decisions. | ||
| ## When to use it | ||
| When the user writes `/plan [task]` or when the task: | ||
| - Touches more than 3 files | ||
| - Requires creating new folder structure | ||
| - Involves database or API changes | ||
| - Has dependencies between steps | ||
| ## Steps | ||
| 1. **Understand the goal**: Read the user's task. If ambiguous, ask ONE clarifying question before continuing. | ||
| 2. **Explore the relevant codebase**: | ||
| - Read files related to the task | ||
| - Identify existing patterns (how something similar is already done) | ||
| - Detect dependencies and risks | ||
| 3. **Propose a structured plan**: | ||
| ```markdown | ||
| ## Plan: [task name] | ||
| ### Goal | ||
| [One line describing what will be achieved] | ||
| ### Files to be touched | ||
| - `path/file.ts` β [what change] | ||
| - `path/other.ts` β [what change] | ||
| - [new] `path/new.ts` β [what it does] | ||
| ### Steps in order | ||
| 1. [First concrete step] | ||
| 2. [Second step] | ||
| 3. [...] | ||
| ### Risks / decisions | ||
| - [Risk or trade-off the user should know about] | ||
| - [Alternative considered and why it wasn't chosen] | ||
| ### Not included in this plan | ||
| - [What's out of scope and why] | ||
| ``` | ||
| 4. **Wait for approval** before executing any changes. Don't start writing code until the user says "go ahead" or similar. | ||
| ## Rules | ||
| - A plan is a contract. If the user approves, execute exactly what you said. | ||
| - If during execution you discover something that changes the plan, stop and report. | ||
| - Prefer small iterative plans over large complete ones. |
-44
| # Skill: /pr | ||
| Creates a Pull Request with a clear description, test plan, and checklist. Reads the real branch commits. | ||
| ## When to use it | ||
| When the user writes `/pr` or asks to "create PR" / "open pull request". | ||
| ## Steps | ||
| 1. Detect the current branch: `git branch --show-current` | ||
| 2. Detect the base branch (main or master): `git remote show origin | grep HEAD` | ||
| 3. Read the branch commits: `git log main..HEAD --oneline` | ||
| 4. Read the full diff: `git diff main..HEAD --stat` | ||
| 5. With that information, build: | ||
| ### PR structure | ||
| ```markdown | ||
| ## What does this PR do? | ||
| [1-3 bullets with the main change. Focus on the "what" and "why", not the "how".] | ||
| ## Main changes | ||
| - [file or module]: [what changed] | ||
| - [file or module]: [what changed] | ||
| ## Test plan | ||
| - [ ] [Relevant manual or automated test case] | ||
| - [ ] [Another case] | ||
| ## Notes for the reviewer | ||
| [Additional context: design decisions, trade-offs, things to watch out for.] | ||
| ## Screenshots (if applicable) | ||
| [Remove if no visual changes] | ||
| ``` | ||
| 6. Propose title and body. Ask if it's good before running `gh pr create`. | ||
| ## Rules | ||
| - PR title follows Conventional Commits: `feat(scope): description` | ||
| - If the PR mixes multiple concerns, suggest splitting it. | ||
| - If `gh` is not installed, generate the text to paste manually in GitHub. |
| # Skill: /review | ||
| Reviews code with real engineering criteria. Not just style β detects bugs, security issues, and technical debt. | ||
| ## When to use it | ||
| When the user writes `/review @file` or `/review` (reviews current PR changes). | ||
| ## Steps | ||
| 1. **Read the code to review**: | ||
| - If there's a specific file (`/review @src/auth.ts`), read it completely. | ||
| - If no file, use `git diff main..HEAD` to review the branch changes. | ||
| 2. **Review in this priority order**: | ||
| ### π΄ Critical (blocks merge) | ||
| - Logic bugs that produce incorrect behavior | ||
| - Security vulnerabilities (injection, XSS, exposed data, auth bypass) | ||
| - Race conditions or concurrency issues | ||
| - Memory leaks or unreleased resources | ||
| ### π‘ Important (must be resolved before or as follow-up) | ||
| - Missing or incomplete error handling | ||
| - Unconsidered edge cases | ||
| - Performance: N+1 queries, unnecessary loops, avoidable re-renders | ||
| - Missing tests for critical logic | ||
| ### π΅ Suggestion (optional improvement) | ||
| - Unclear variable or function names | ||
| - Duplicated code that could be extracted | ||
| - Outdated or unnecessary comments | ||
| - Readability improvements | ||
| 3. **Output format**: | ||
| ```markdown | ||
| ## Review: [file name or PR] | ||
| ### π΄ Critical | ||
| - **Line X**: [problem description] β [fix suggestion] | ||
| ### π‘ Important | ||
| - **Line X**: [description] | ||
| ### π΅ Suggestions | ||
| - **Line X**: [description] | ||
| ### β What's good | ||
| [Mention 1-2 things done well. Balanced feedback is more effective.] | ||
| ``` | ||
| ## Rules | ||
| - Be specific: "line 42: this if never executes because..." is better than "there's a bug". | ||
| - Don't review style if a linter is configured β trust the tooling. | ||
| - If the file is very large (+500 lines), focus on new logic, not existing code. | ||
| - A review with 3 real criticals is worth more than 20 naming suggestions. |
| # Skill: /vibe-audit | ||
| Audit of apps built with vibe coding. Detects the typical problems AI generated without anyone reviewing them: security, performance, maintainability, and accumulated technical debt. | ||
| ## When to use it | ||
| When the user writes `/vibe-audit` or `/vibe-audit @folder`. | ||
| Also useful when someone says "I generated this with AI and want to know how bad it is". | ||
| ## What this skill does first | ||
| 1. Scans the complete project structure (folders, main files) | ||
| 2. Reads the most critical files: entry point, routes/endpoints, main components, config | ||
| 3. Looks for the 20 most common risk patterns in vibe-coded apps | ||
| 4. Generates a report with severity, concrete evidence, and suggested fix | ||
| --- | ||
| ## The 20 typical vibe coding problems | ||
| ### π΄ 1. Hardcoded secrets | ||
| AI tends to put API keys, passwords, and database URLs directly in code because "it works faster". | ||
| **Look for:** | ||
| ``` | ||
| OPENAI_API_KEY = "sk-..." | ||
| password: "admin123" | ||
| mongodb://user:pass@host | ||
| const SECRET = "abc123" | ||
| ``` | ||
| **Fix:** Move to `.env`, add `.env` to `.gitignore`, create `.env.example` with names but no values. | ||
| --- | ||
| ### π΄ 2. No input validation on APIs | ||
| AI generates endpoints that blindly trust `req.body`. Any user can send whatever they want. | ||
| **Look for:** | ||
| ```js | ||
| const { email, role } = req.body | ||
| await db.users.update({ role }) // a user can give themselves admin role | ||
| ``` | ||
| **Fix:** Validate with Zod/Joi before using any request data. Never trust the client. | ||
| --- | ||
| ### π΄ 3. CORS open to everyone | ||
| To "make it work in development" AI puts `origin: '*'` and it stays that way in production. | ||
| **Look for:** | ||
| ```js | ||
| cors({ origin: '*' }) | ||
| app.use(cors()) // no config = everything allowed | ||
| ``` | ||
| **Fix:** Whitelist of allowed domains. In development: `localhost:port`. In production: only the real domain. | ||
| --- | ||
| ### π΄ 4. No authentication on protected routes | ||
| AI generates the routes but "forgets" to put the auth middleware on all of them. | ||
| **Look for:** Routes to `/admin`, `/dashboard`, `/users`, `/settings` without `authenticate` or equivalent before the handler. | ||
| **Fix:** Check every sensitive route. In Express: `router.use(authenticate)` before protected routes, not on each one individually. | ||
| --- | ||
| ### π‘ 5. Development console.logs in production | ||
| AI logs everything for debugging. Those logs expose internal data and pollute production logs. | ||
| **Look for:** | ||
| ```js | ||
| console.log('user data:', user) | ||
| console.log('db response:', result) | ||
| console.log('DEBUG:', req.body) | ||
| ``` | ||
| **Fix:** Remove or replace with a real logger (Winston, Pino) that respects log level based on environment. | ||
| --- | ||
| ### π‘ 6. Giant components (God Components) | ||
| AI puts everything in one component: state, API calls, UI, business logic. 500-1000 lines is common. | ||
| **Look for:** Components over 200 lines, components with 5+ `useState`, mixing fetch + render in the same place. | ||
| **Fix:** Separate into: custom hooks (logic), child components (UI), services (API calls). | ||
| --- | ||
| ### π‘ 7. No loading or error states in frontend | ||
| AI generates the perfect "happy path". But if the API is slow or fails, the app goes blank or crashes silently. | ||
| **Look for:** Fetches without `isLoading`, without `error`, without fallback UI. Components that render data without checking it exists. | ||
| **Fix:** Every fetch needs 3 states: loading, error, success. Error boundaries in React to catch crashes. | ||
| --- | ||
| ### π‘ 8. Queries without pagination | ||
| AI generates `SELECT * FROM table` or `db.find({})` without a limit. Works in development with 10 records. Explodes in production with 10,000. | ||
| **Look for:** | ||
| ```js | ||
| const users = await db.users.findMany() // without take/limit | ||
| const items = await db.collection.find({}).toArray() | ||
| ``` | ||
| **Fix:** Every query returning lists needs `LIMIT`/`take` and pagination or cursor. | ||
| --- | ||
| ### π‘ 9. Third-party APIs without rate limiting | ||
| AI connects the app to OpenAI, Stripe, Twilio, or any external API and calls directly with no control. In production, any user (or bot) can fire thousands of calls and ruin the bill or get the account banned by the provider. | ||
| **First detect if there's external API consumption:** | ||
| ```js | ||
| // Look for in the code | ||
| fetch('https://api.openai.com') | ||
| openai.chat.completions.create(...) | ||
| stripe.charges.create(...) | ||
| axios.get('https://api.external.com') | ||
| ``` | ||
| **Typical problems:** | ||
| ```js | ||
| // A user can click infinitely and generate costs | ||
| app.post('/generate', async (req, res) => { | ||
| const result = await openai.chat.completions.create({ ... }) // no limit | ||
| res.json(result) | ||
| }) | ||
| ``` | ||
| **Fix by layer β apply what applies based on the stack:** | ||
| *Server level (Express) β the most important:* | ||
| ```js | ||
| import rateLimit from 'express-rate-limit' | ||
| // Global limit for routes that consume external APIs | ||
| const aiLimiter = rateLimit({ | ||
| windowMs: 60 * 1000, // 1 minute window | ||
| max: 10, // max 10 requests per IP per minute | ||
| message: { error: 'Too many requests, try again in a moment.' }, | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }) | ||
| app.use('/api/generate', aiLimiter) | ||
| app.use('/api/ai', aiLimiter) | ||
| ``` | ||
| *Per authenticated user (more granular):* | ||
| ```js | ||
| // Limit by userId, not just by IP (shared IPs on NAT are unreliable) | ||
| const userRequestCounts = new Map() | ||
| function userRateLimit(maxPerMinute: number) { | ||
| return (req, res, next) => { | ||
| const userId = req.user?.id | ||
| if (!userId) return res.status(401).json({ error: 'Not authenticated' }) | ||
| const key = `${userId}:${Math.floor(Date.now() / 60000)}` | ||
| const count = (userRequestCounts.get(key) ?? 0) + 1 | ||
| userRequestCounts.set(key, count) | ||
| if (count > maxPerMinute) { | ||
| return res.status(429).json({ error: 'Usage limit reached' }) | ||
| } | ||
| next() | ||
| } | ||
| } | ||
| ``` | ||
| *Frontend level β prevents button spam:* | ||
| ```ts | ||
| // Disable button while a call is in progress | ||
| const [isLoading, setIsLoading] = useState(false) | ||
| async function handleGenerate() { | ||
| if (isLoading) return // prevents double submit | ||
| setIsLoading(true) | ||
| try { | ||
| await callExpensiveAPI() | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
| <button onClick={handleGenerate} disabled={isLoading}> | ||
| {isLoading ? 'Generating...' : 'Generate'} | ||
| </button> | ||
| ``` | ||
| **Additional warning signs:** | ||
| - App calls the API on every keystroke (missing debounce) | ||
| - No cache: the same query is called multiple times without saving the result | ||
| - No timeout configured in the fetch: if the external API hangs, the request hangs forever | ||
| **Fix for basic cache:** | ||
| ```js | ||
| const cache = new Map() | ||
| const CACHE_TTL = 5 * 60 * 1000 // 5 minutes | ||
| async function getCachedResult(key: string, fn: () => Promise<any>) { | ||
| const cached = cache.get(key) | ||
| if (cached && Date.now() - cached.timestamp < CACHE_TTL) { | ||
| return cached.data | ||
| } | ||
| const data = await fn() | ||
| cache.set(key, { data, timestamp: Date.now() }) | ||
| return data | ||
| } | ||
| ``` | ||
| --- | ||
| ### π΄ 12. IDOR β Access to other users' resources | ||
| AI generates endpoints like `GET /api/orders/:id` or `DELETE /api/posts/:id` without verifying the resource belongs to the logged-in user. Any authenticated user can access or delete other people's data by changing the ID in the URL. | ||
| **Look for:** | ||
| ```js | ||
| // Without ownership check β any userId can access | ||
| app.get('/api/orders/:id', authenticate, async (req, res) => { | ||
| const order = await db.orders.findById(req.params.id) | ||
| res.json(order) | ||
| }) | ||
| ``` | ||
| **Risk:** Private data exposure, deletion of others' content, privilege escalation. | ||
| **Fix:** | ||
| ```js | ||
| app.get('/api/orders/:id', authenticate, async (req, res) => { | ||
| const order = await db.orders.findById(req.params.id) | ||
| if (!order) return res.status(404).json({ error: 'Not found' }) | ||
| // Verify the resource belongs to the authenticated user | ||
| if (order.userId !== req.user.id) { | ||
| return res.status(403).json({ error: 'Forbidden' }) | ||
| } | ||
| res.json(order) | ||
| }) | ||
| ``` | ||
| **Pattern to detect it:** find all endpoints with `req.params.id` and verify each one compares that ID against `req.user.id` before returning data. | ||
| --- | ||
| ### π΄ 13. Passwords without hashing | ||
| AI sometimes saves the password as-is from the form, especially if the prompt was vague or quick. | ||
| **Look for:** | ||
| ```js | ||
| // Password stored in plain text | ||
| await db.users.create({ email, password: req.body.password }) | ||
| // Or weak hash (MD5, SHA1 β not suitable for passwords) | ||
| const hashed = crypto.createHash('md5').update(password).digest('hex') | ||
| ``` | ||
| **Fix:** bcrypt or argon2 β never MD5/SHA1 for passwords. | ||
| ```js | ||
| import bcrypt from 'bcrypt' | ||
| const SALT_ROUNDS = 12 | ||
| const hashedPassword = await bcrypt.hash(req.body.password, SALT_ROUNDS) | ||
| await db.users.create({ email, password: hashedPassword }) | ||
| // When verifying login: | ||
| const isValid = await bcrypt.compare(req.body.password, user.password) | ||
| ``` | ||
| --- | ||
| ### π΄ 14. XSS via innerHTML / dangerouslySetInnerHTML | ||
| AI uses innerHTML or dangerouslySetInnerHTML to render dynamic content without sanitizing. An attacker can inject scripts that steal sessions or redirect users. | ||
| **Look for:** | ||
| ```js | ||
| // React β dangerous without sanitization | ||
| <div dangerouslySetInnerHTML={{ __html: userContent }} /> | ||
| // Vanilla JS | ||
| element.innerHTML = userData | ||
| document.write(userInput) | ||
| ``` | ||
| **Fix:** Sanitize before rendering with DOMPurify, or better yet, avoid innerHTML and use textContent or framework abstractions. | ||
| ```js | ||
| import DOMPurify from 'dompurify' | ||
| // Only if you NEED to render HTML (e.g.: rich text editor) | ||
| <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} /> | ||
| // For plain text: never innerHTML, always textContent | ||
| element.textContent = userData // automatically safe | ||
| ``` | ||
| --- | ||
| ### π΄ 15. JWT without expiration | ||
| `jwt.sign(payload, secret)` without `expiresIn` generates tokens valid forever. If a token leaks (logs, compromised localStorage), the attacker has permanent access. | ||
| **Look for:** | ||
| ```js | ||
| // Without expiresIn β eternal token | ||
| const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET) | ||
| // expiresIn very long β almost as bad | ||
| const token = jwt.sign(payload, secret, { expiresIn: '365d' }) | ||
| ``` | ||
| **Fix:** | ||
| ```js | ||
| // Short access token + refresh token to renew | ||
| const accessToken = jwt.sign( | ||
| { userId: user.id, role: user.role }, | ||
| process.env.JWT_SECRET, | ||
| { expiresIn: '15m' } // short β if leaked, expires soon | ||
| ) | ||
| const refreshToken = jwt.sign( | ||
| { userId: user.id }, | ||
| process.env.JWT_REFRESH_SECRET, | ||
| { expiresIn: '7d' } // longer but stored securely (httpOnly cookie) | ||
| ) | ||
| ``` | ||
| --- | ||
| ### π΄ 16. Stack traces exposed to client | ||
| AI puts `res.json({ error: err.message })` or worse `res.json({ error: err.stack })` in the error handler. In production this exposes internal paths, library versions, and app logic. | ||
| **Look for:** | ||
| ```js | ||
| app.use((err, req, res, next) => { | ||
| res.status(500).json({ error: err.message }) // exposes internals | ||
| res.status(500).json({ error: err.stack }) // even worse | ||
| }) | ||
| // Also in inline try/catch | ||
| } catch (err) { | ||
| res.status(500).json({ error: err.toString() }) | ||
| } | ||
| ``` | ||
| **Fix:** Log the internal error, return generic message to client. | ||
| ```js | ||
| app.use((err, req, res, next) => { | ||
| // Internal log with all details (server only sees this) | ||
| console.error('[ERROR]', { | ||
| message: err.message, | ||
| stack: err.stack, | ||
| path: req.path, | ||
| method: req.method, | ||
| }) | ||
| // Generic response to client β no internal information | ||
| const statusCode = err.statusCode ?? 500 | ||
| res.status(statusCode).json({ | ||
| error: statusCode < 500 ? err.message : 'Internal server error', | ||
| }) | ||
| }) | ||
| ``` | ||
| --- | ||
| ### π‘ 17. No security headers (Helmet) | ||
| Without security headers the browser doesn't activate basic protections: the app can be loaded in third-party iframes (clickjacking), the browser may execute scripts from unexpected sources (XSS), or interpret files with the wrong MIME type. | ||
| **Look for:** Express app without `helmet()` in the middleware stack. | ||
| ```js | ||
| // AI generates this β without security headers | ||
| const app = express() | ||
| app.use(express.json()) | ||
| app.use(cors(...)) | ||
| // β missing helmet | ||
| ``` | ||
| **Fix:** | ||
| ```js | ||
| import helmet from 'helmet' | ||
| app.use(helmet()) // activates 15 security headers with sensible defaults | ||
| // If you need custom CSP (e.g.: app loads scripts from CDN): | ||
| app.use( | ||
| helmet.contentSecurityPolicy({ | ||
| directives: { | ||
| defaultSrc: ["'self'"], | ||
| scriptSrc: ["'self'", 'cdn.yourdomain.com'], | ||
| styleSrc: ["'self'", "'unsafe-inline'"], // unsafe-inline only if necessary | ||
| }, | ||
| }) | ||
| ) | ||
| ``` | ||
| --- | ||
| ### π‘ 18. Dependencies with known vulnerabilities | ||
| AI installs packages without checking their security status. `npm audit` frequently reveals critical vulnerabilities in transitive dependencies that nobody detected. | ||
| **How to verify:** | ||
| ```bash | ||
| npm audit | ||
| # or to see only critical and high: | ||
| npm audit --audit-level=high | ||
| ``` | ||
| **Warning signs:** | ||
| - Packages with very old versions (`"express": "^4.17.1"` when 4.21+ exists) | ||
| - Abandoned dependencies (last commit 3+ years ago) | ||
| - `npm audit` with `critical: N` > 0 | ||
| **Fix:** | ||
| ```bash | ||
| # Auto-update what doesn't break semver | ||
| npm audit fix | ||
| # See what can be updated | ||
| npx npm-check-updates | ||
| # Update a specific dependency | ||
| npm install express@latest | ||
| ``` | ||
| **For CI β block merges if there are critical vulnerabilities:** | ||
| ```yaml | ||
| # .github/workflows/security.yml | ||
| - name: Security audit | ||
| run: npm audit --audit-level=critical | ||
| ``` | ||
| --- | ||
| ### π‘ 19. node_modules or /dist committed | ||
| AI doesn't always configure `.gitignore` properly. Committing `node_modules` (500MB+) or `/dist` pollutes git history, slows down clones, and in the worst case uploads compiled code with embedded secrets. | ||
| **Look for:** | ||
| ```bash | ||
| git ls-files | grep -E "^node_modules/|^dist/|^build/|^\.next/" | ||
| ``` | ||
| **If something appears:** | ||
| ```bash | ||
| # Remove from tracking without deleting the local file | ||
| git rm -r --cached node_modules/ | ||
| git rm -r --cached dist/ | ||
| git rm -r --cached .next/ | ||
| # Add to .gitignore | ||
| echo "node_modules/" >> .gitignore | ||
| echo "dist/" >> .gitignore | ||
| echo ".next/" >> .gitignore | ||
| echo "build/" >> .gitignore | ||
| git add .gitignore | ||
| git commit -m "chore: remove node_modules and dist from tracking" | ||
| ``` | ||
| **Minimal .gitignore for JS/TS projects:** | ||
| ``` | ||
| node_modules/ | ||
| dist/ | ||
| build/ | ||
| .next/ | ||
| .nuxt/ | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
| *.log | ||
| .DS_Store | ||
| coverage/ | ||
| ``` | ||
| --- | ||
| ### π΅ 20. Unoptimized bundle | ||
| AI generates apps that work but aren't optimized for production: all code in one chunk, uncompressed images, heavy dependencies imported completely. | ||
| **Warning signs:** | ||
| ```bash | ||
| # Analyze the bundle | ||
| npx vite-bundle-analyzer # for Vite | ||
| npx webpack-bundle-analyzer # for Webpack/CRA | ||
| npx @next/bundle-analyzer # for Next.js | ||
| ``` | ||
| **Typical AI problems:** | ||
| ```js | ||
| // Full import of heavy library (imports ALL lodash β 70KB) | ||
| import _ from 'lodash' | ||
| const result = _.groupBy(items, 'category') | ||
| // Fix: specific import (only the function you use β 2KB) | ||
| import groupBy from 'lodash/groupBy' | ||
| ``` | ||
| ```js | ||
| // Without lazy loading β everything loads on first render | ||
| import HeavyDashboard from './HeavyDashboard' | ||
| import AdminPanel from './AdminPanel' | ||
| // Fix: lazy loading by route | ||
| const HeavyDashboard = lazy(() => import('./HeavyDashboard')) | ||
| const AdminPanel = lazy(() => import('./AdminPanel')) | ||
| ``` | ||
| **Production targets (Lighthouse):** | ||
| - Initial JS bundle: < 300KB gzipped | ||
| - LCP (Largest Contentful Paint): β€ 2.5s | ||
| - INP (Interaction to Next Paint): β€ 200ms | ||
| **Quick fix for images** (AI almost never optimizes them): | ||
| ```jsx | ||
| // In Next.js β use <Image> instead of <img> | ||
| import Image from 'next/image' | ||
| <Image src="/hero.png" width={800} height={400} alt="..." priority /> | ||
| // In Vite β compression plugin | ||
| // vite.config.ts | ||
| import viteImagemin from 'vite-plugin-imagemin' | ||
| ``` | ||
| --- | ||
| ### π΅ 10. No environment variables for configuration | ||
| Ports, service URLs, database names hardcoded. Impossible to deploy to another environment without touching the code. | ||
| **Look for:** | ||
| ```js | ||
| const PORT = 3000 | ||
| const DB_NAME = "myapp_dev" | ||
| const API_URL = "http://localhost:8080" | ||
| ``` | ||
| **Fix:** Everything that changes between environments (dev/staging/prod) goes in `.env`. | ||
| --- | ||
| ### π΅ 11. No async error handling | ||
| AI generates `async/await` without `try/catch`. An uncaught error crashes the process in Node.js. | ||
| **Look for:** | ||
| ```js | ||
| async function getData() { | ||
| const result = await fetch(url) // what happens if it fails? | ||
| return result.json() | ||
| } | ||
| ``` | ||
| **Fix:** Async wrapper for Express, error boundaries for React, explicit handling in each critical async function. | ||
| --- | ||
| ## Report format | ||
| ```markdown | ||
| # Vibe Audit β [project name] | ||
| Audited: [date] | ||
| ## Summary | ||
| - π΄ Critical: N (block production or are security risks) | ||
| - π‘ Important: N (affect stability or maintainability) | ||
| - π΅ Improvements: N (technical debt, quality) | ||
| --- | ||
| ## π΄ Critical | ||
| ### [Problem name] | ||
| **Where:** `path/file.ts` line X | ||
| **Evidence:** | ||
| [problematic code] | ||
| **Risk:** [what can happen if not fixed] | ||
| **Suggested fix:** | ||
| [corrected code or concrete steps] | ||
| --- | ||
| ## π‘ Important | ||
| [same format] | ||
| --- | ||
| ## π΅ Improvements | ||
| [same format] | ||
| --- | ||
| ## What's good | ||
| [1-3 things vibe coding did well β balanced feedback is more actionable] | ||
| ## Suggested action plan | ||
| 1. [This first β the most urgent critical] | ||
| 2. [Then this] | ||
| 3. [...] | ||
| ``` | ||
| ## Rules | ||
| - **Be specific**: cite the file and line, don't say "there's a security problem" without showing where. | ||
| - **One problem at a time if the user wants to fix them**: don't generate 50 fixes at once. Offer to fix them in priority order. | ||
| - **Don't rewrite the app**: the goal is to identify and fix existing problems, not redo everything with "best practices". | ||
| - **If the project is large**: audit by module (auth, API, frontend) instead of everything at once. |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
533382
13.79%73
40.38%1792
11.79%194
19.02%2
100%16
14.29%