commit 0da5021d18797fc6d929316a903dc3ca2e2c4bf6 Author: Hongming Wang Date: Wed May 6 13:53:20 2026 -0700 import from local vendored copy (2026-05-06) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c8fb9d3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,5 @@ +name: CI +on: [push, pull_request] +jobs: + validate: + uses: Molecule-AI/molecule-ci/.github/workflows/validate-plugin.yml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2af45b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Credentials — never commit. Use .env.example as the template. +.env +.env.local +.env.*.local +.env.* +!.env.example +!.env.sample + +# Private keys + certs +*.pem +*.key +*.crt +*.p12 +*.pfx + +# Secret directories +.secrets/ + +# Workspace auth tokens +.auth-token +.auth_token diff --git a/.molecule-ci/scripts/requirements.txt b/.molecule-ci/scripts/requirements.txt new file mode 100644 index 0000000..3aecde9 --- /dev/null +++ b/.molecule-ci/scripts/requirements.txt @@ -0,0 +1 @@ +pyyaml>=6.0 diff --git a/.molecule-ci/scripts/validate-plugin.py b/.molecule-ci/scripts/validate-plugin.py new file mode 100644 index 0000000..c42e916 --- /dev/null +++ b/.molecule-ci/scripts/validate-plugin.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Validate a Molecule AI plugin repo.""" +import os, sys, yaml + +errors = [] + +# 1. plugin.yaml exists +if not os.path.isfile("plugin.yaml"): + print("::error::plugin.yaml not found at repo root") + sys.exit(1) + +with open("plugin.yaml") as f: + plugin = yaml.safe_load(f) + +# 2. Required fields +for field in ["name", "version", "description"]: + if not plugin.get(field): + errors.append(f"Missing required field: {field}") + +# 3. Version format +v = str(plugin.get("version", "")) +if v and not all(c in "0123456789." for c in v): + errors.append(f"Invalid version format: {v}") + +# 4. Runtimes type +runtimes = plugin.get("runtimes") +if runtimes is not None and not isinstance(runtimes, list): + errors.append(f"runtimes must be a list, got {type(runtimes).__name__}") + +# 5. Has content +content_paths = ["SKILL.md", "hooks", "skills", "rules"] +found = [p for p in content_paths if os.path.exists(p)] +if not found: + errors.append("Plugin must contain at least one of: SKILL.md, hooks/, skills/, rules/") + +# 6. SKILL.md formatting check +if os.path.isfile("SKILL.md"): + with open("SKILL.md") as f: + first_line = f.readline().strip() + if first_line and not first_line.startswith("#"): + print("::warning::SKILL.md should start with a markdown heading (e.g., # Plugin Name)") + +if errors: + for e in errors: + print(f"::error::{e}") + sys.exit(1) + +print(f"✓ plugin.yaml valid: {plugin['name']} v{plugin['version']}") +if found: + print(f" Content: {', '.join(found)}") +if runtimes: + print(f" Runtimes: {', '.join(runtimes)}") diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ed9f6a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,166 @@ +# Everything Claude Code (ECC) — Agent Instructions + +This is a **production-ready AI coding plugin** providing 38 specialized agents, 156 skills, 72 commands, and automated hook workflows for software development. + +**Version:** 1.9.0 + +## Core Principles + +1. **Agent-First** — Delegate to specialized agents for domain tasks +2. **Test-Driven** — Write tests before implementation, 80%+ coverage required +3. **Security-First** — Never compromise on security; validate all inputs +4. **Immutability** — Always create new objects, never mutate existing ones +5. **Plan Before Execute** — Plan complex features before writing code + +## Available Agents + +| Agent | Purpose | When to Use | +|-------|---------|-------------| +| planner | Implementation planning | Complex features, refactoring | +| architect | System design and scalability | Architectural decisions | +| tdd-guide | Test-driven development | New features, bug fixes | +| code-reviewer | Code quality and maintainability | After writing/modifying code | +| security-reviewer | Vulnerability detection | Before commits, sensitive code | +| build-error-resolver | Fix build/type errors | When build fails | +| e2e-runner | End-to-end Playwright testing | Critical user flows | +| refactor-cleaner | Dead code cleanup | Code maintenance | +| doc-updater | Documentation and codemaps | Updating docs | +| cpp-reviewer | C++ code review | C++ projects | +| cpp-build-resolver | C++ build errors | C++ build failures | +| docs-lookup | Documentation lookup via Context7 | API/docs questions | +| go-reviewer | Go code review | Go projects | +| go-build-resolver | Go build errors | Go build failures | +| kotlin-reviewer | Kotlin code review | Kotlin/Android/KMP projects | +| kotlin-build-resolver | Kotlin/Gradle build errors | Kotlin build failures | +| database-reviewer | PostgreSQL/Supabase specialist | Schema design, query optimization | +| python-reviewer | Python code review | Python projects | +| java-reviewer | Java and Spring Boot code review | Java/Spring Boot projects | +| java-build-resolver | Java/Maven/Gradle build errors | Java build failures | +| loop-operator | Autonomous loop execution | Run loops safely, monitor stalls, intervene | +| harness-optimizer | Harness config tuning | Reliability, cost, throughput | +| rust-reviewer | Rust code review | Rust projects | +| rust-build-resolver | Rust build errors | Rust build failures | +| pytorch-build-resolver | PyTorch runtime/CUDA/training errors | PyTorch build/training failures | +| typescript-reviewer | TypeScript/JavaScript code review | TypeScript/JavaScript projects | + +## Agent Orchestration + +Use agents proactively without user prompt: +- Complex feature requests → **planner** +- Code just written/modified → **code-reviewer** +- Bug fix or new feature → **tdd-guide** +- Architectural decision → **architect** +- Security-sensitive code → **security-reviewer** +- Autonomous loops / loop monitoring → **loop-operator** +- Harness config reliability and cost → **harness-optimizer** + +Use parallel execution for independent operations — launch multiple agents simultaneously. + +## Security Guidelines + +**Before ANY commit:** +- No hardcoded secrets (API keys, passwords, tokens) +- All user inputs validated +- SQL injection prevention (parameterized queries) +- XSS prevention (sanitized HTML) +- CSRF protection enabled +- Authentication/authorization verified +- Rate limiting on all endpoints +- Error messages don't leak sensitive data + +**Secret management:** NEVER hardcode secrets. Use environment variables or a secret manager. Validate required secrets at startup. Rotate any exposed secrets immediately. + +**If security issue found:** STOP → use security-reviewer agent → fix CRITICAL issues → rotate exposed secrets → review codebase for similar issues. + +## Coding Style + +**Immutability (CRITICAL):** Always create new objects, never mutate. Return new copies with changes applied. + +**File organization:** Many small files over few large ones. 200-400 lines typical, 800 max. Organize by feature/domain, not by type. High cohesion, low coupling. + +**Error handling:** Handle errors at every level. Provide user-friendly messages in UI code. Log detailed context server-side. Never silently swallow errors. + +**Input validation:** Validate all user input at system boundaries. Use schema-based validation. Fail fast with clear messages. Never trust external data. + +**Code quality checklist:** +- Functions small (<50 lines), files focused (<800 lines) +- No deep nesting (>4 levels) +- Proper error handling, no hardcoded values +- Readable, well-named identifiers + +## Testing Requirements + +**Minimum coverage: 80%** + +Test types (all required): +1. **Unit tests** — Individual functions, utilities, components +2. **Integration tests** — API endpoints, database operations +3. **E2E tests** — Critical user flows + +**TDD workflow (mandatory):** +1. Write test first (RED) — test should FAIL +2. Write minimal implementation (GREEN) — test should PASS +3. Refactor (IMPROVE) — verify coverage 80%+ + +Troubleshoot failures: check test isolation → verify mocks → fix implementation (not tests, unless tests are wrong). + +## Development Workflow + +1. **Plan** — Use planner agent, identify dependencies and risks, break into phases +2. **TDD** — Use tdd-guide agent, write tests first, implement, refactor +3. **Review** — Use code-reviewer agent immediately, address CRITICAL/HIGH issues +4. **Capture knowledge in the right place** + - Personal debugging notes, preferences, and temporary context → auto memory + - Team/project knowledge (architecture decisions, API changes, runbooks) → the project's existing docs structure + - If the current task already produces the relevant docs or code comments, do not duplicate the same information elsewhere + - If there is no obvious project doc location, ask before creating a new top-level file +5. **Commit** — Conventional commits format, comprehensive PR summaries + +## Workflow Surface Policy + +- `skills/` is the canonical workflow surface. +- New workflow contributions should land in `skills/` first. +- `commands/` is a legacy slash-entry compatibility surface and should only be added or updated when a shim is still required for migration or cross-harness parity. + +## Git Workflow + +**Commit format:** `: ` — Types: feat, fix, refactor, docs, test, chore, perf, ci + +**PR workflow:** Analyze full commit history → draft comprehensive summary → include test plan → push with `-u` flag. + +## Architecture Patterns + +**API response format:** Consistent envelope with success indicator, data payload, error message, and pagination metadata. + +**Repository pattern:** Encapsulate data access behind standard interface (findAll, findById, create, update, delete). Business logic depends on abstract interface, not storage mechanism. + +**Skeleton projects:** Search for battle-tested templates, evaluate with parallel agents (security, extensibility, relevance), clone best match, iterate within proven structure. + +## Performance + +**Context management:** Avoid last 20% of context window for large refactoring and multi-file features. Lower-sensitivity tasks (single edits, docs, simple fixes) tolerate higher utilization. + +**Build troubleshooting:** Use build-error-resolver agent → analyze errors → fix incrementally → verify after each fix. + +## Project Structure + +``` +agents/ — 38 specialized subagents +skills/ — 156 workflow skills and domain knowledge +commands/ — 72 slash commands +hooks/ — Trigger-based automations +rules/ — Always-follow guidelines (common + per-language) +scripts/ — Cross-platform Node.js utilities +mcp-configs/ — 14 MCP server configurations +tests/ — Test suite +``` + +`commands/` remains in the repo for compatibility, but the long-term direction is skills-first. + +## Success Metrics + +- All tests pass with 80%+ coverage +- No security vulnerabilities +- Code is readable and maintainable +- Performance is acceptable +- User requirements are met diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b8a58a3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,121 @@ +# plugin-ecc — Everything Claude Code + +ECC is a **plugin aggregator** that bundles the Everything Claude Code (ECC) ecosystem — 38 agents, 156 skills, 72 commands, and hook workflows — for use on the Molecule AI platform. + +This repository itself is minimal: it wires the ecosystem together via `plugin.yaml` and provides plugin-specific conventions. Most content lives in `rules/`, `skills/`, and `AGENTS.md`. + +**Version:** 1.9.0 +**Runtime:** `claude_code`, `deepagents`, `hermes` + +--- + +## Repository Layout + +``` +ecc/ +├── AGENTS.md — Agent manifest (38 agents, their purposes, when to use) +├── plugin.yaml — Plugin manifest (runtimes, rules, skills, prompt_fragments) +├── rules/ +│ ├── everything-claude-code-guardrails.md — Generated guardrails from ECC repo +│ └── node.md — Node.js development rules +├── skills/ — Skill definitions (installed as workflow surface) +└── adapters/ — Harness adaptor (wraps ECC via plugins_registry) +``` + +--- + +## Conventions + +### File Naming + +| Directory | Convention | Example | +|-----------|-----------|---------| +| `rules/` | lowercase with hyphens | `codebase-conventions.md` | +| `skills/` | lowercase with hyphens | `tdd-workflow.md` | +| `agents/` | lowercase with hyphens | `code-reviewer.md` | +| `commands/` | lowercase with hyphens | `security-review.md` | +| `scripts/` | lowercase with hyphens (CommonJS) | `session-start.js` | + +### Markdown Files + +All `.md` files in `skills/`, `agents/`, `commands/`, and `rules/` must pass markdownlint before commit: + +```bash +npx markdownlint '**/*.md' --ignore node_modules +``` + +### YAML Frontmatter + +- **Agents**: must have `name`, `description`, `tools`, `model` +- **Skills**: must have "When to Use", "How It Works", "Examples" sections +- **Commands**: must have `description:` frontmatter line + +--- + +## Development + +### Setup + +```bash +# No npm install needed for the plugin repo itself +# (adapters/ depends on plugins_registry at runtime) +``` + +### Validation + +```bash +# Validate the plugin structure (CI uses molecule-ci workflow) +# Run locally via: +gh workflow run validate-plugin.yml --ref main +``` + +### Adding a New Rule + +1. Create `rules/.md` with frontmatter (`name`, `description`) +2. Add it to `rules/` list in `plugin.yaml` +3. Add it to the `rules/` section in `AGENTS.md` if it affects agent behavior +4. Run markdownlint validation + +### Adding a New Skill + +1. Create `skills/.md` with frontmatter (`name`, `description`) +2. Add it to `skills/` list in `plugin.yaml` +3. Skills are the canonical workflow surface — prefer adding here over `commands/` + +--- + +## Release Process + +See `runbooks/release-procedure.md` for full details. + +Brief overview: +1. Ensure all markdown files pass linting +2. Bump version in `plugin.yaml` (semver) +3. Update `AGENTS.md` version number if agents list changed +4. Commit with `chore: bump version to X.Y.Z` +5. Tag and push: `git tag vX.Y.Z && git push origin main --tags` +6. Create GitHub release with `## Changelog` section + +--- + +## Known Issues + +See `known-issues.md` for current issues and workarounds. + +--- + +## Key Principles + +1. **Agent-First** — Delegate to specialized agents for domain tasks +2. **Test-Driven** — 80%+ coverage required for any new scripts +3. **Security-First** — No hardcoded secrets; validate all inputs +4. **Immutability** — Always create new objects, never mutate existing ones +5. **Plan Before Execute** — Use planner agent for complex features + +--- + +## Workflow Surface + +- `skills/` is the **canonical** workflow surface +- `commands/` is legacy for slash-command compatibility only +- New workflows should land in `skills/` first diff --git a/README.md b/README.md new file mode 100644 index 0000000..71c5eb3 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# ECC — Claude Code Guardrails and Standards + +Plugin for Claude Code (and Claude Code-compatible runtimes). Provides coding guardrails, +development standards, and production API conventions — everything a Claude Code agent +needs to write production-quality code without prompting. + +## What it provides + +**Skills (invoke via the Skill tool):** +| Skill | What it does | +|-------|-------------| +| `ecc:api-design` | REST API design patterns — resource naming, HTTP methods, status codes, pagination, filtering, error responses | +| `ecc:coding-standards` | Code quality standards — naming, modularity, error handling, testing conventions | +| `ecc:deep-research` | Research methodology for technical investigations — how to gather context, validate assumptions, cite sources | +| `ecc:security-review` | Security-first code review — common vulnerability patterns, injection vectors, secrets handling | +| `ecc:tdd-workflow` | Test-driven development workflow — red-green-refactor cycle, what makes a good test | + +**Rules (ambient — always active):** +- `everything-claude-code-guardrails.md` — platform-wide Claude Code guardrails +- `node.md` — Node.js / TypeScript conventions +- `plugin-ecc-conventions.md` — Molecule AI internal conventions + +**Prompt fragments:** +- `AGENTS.md` — injected into the agent's system prompt on startup + +## When to use it + +Install on any Claude Code (or compatible) workspace. The skills are invoked on-demand +via the `Skill` tool; the rules and prompt fragments activate automatically. + +## Installation + +### In org template (org.yaml) +```yaml +plugins: + - ecc +``` + +### From URL (community install) +``` +github://Molecule-AI/molecule-ai-plugin-ecc +``` + +## License +Business Source License 1.1 — © Molecule AI. diff --git a/adapters/__pycache__/claude_code.cpython-313.pyc b/adapters/__pycache__/claude_code.cpython-313.pyc new file mode 100644 index 0000000..05ae115 Binary files /dev/null and b/adapters/__pycache__/claude_code.cpython-313.pyc differ diff --git a/adapters/__pycache__/deepagents.cpython-313.pyc b/adapters/__pycache__/deepagents.cpython-313.pyc new file mode 100644 index 0000000..1382f63 Binary files /dev/null and b/adapters/__pycache__/deepagents.cpython-313.pyc differ diff --git a/adapters/claude_code.py b/adapters/claude_code.py new file mode 100644 index 0000000..dc33217 --- /dev/null +++ b/adapters/claude_code.py @@ -0,0 +1,2 @@ +"""Claude Code adaptor — uses the generic rule+skill installer.""" +from plugins_registry.builtins import AgentskillsAdaptor as Adaptor # noqa: F401 diff --git a/adapters/deepagents.py b/adapters/deepagents.py new file mode 100644 index 0000000..9572dfb --- /dev/null +++ b/adapters/deepagents.py @@ -0,0 +1,2 @@ +"""DeepAgents adaptor — uses the generic rule+skill installer.""" +from plugins_registry.builtins import AgentskillsAdaptor as Adaptor # noqa: F401 diff --git a/known-issues.md b/known-issues.md new file mode 100644 index 0000000..e925bf4 --- /dev/null +++ b/known-issues.md @@ -0,0 +1,56 @@ +# Known Issues + +Active and recently resolved issues for `plugin-ecc`. + +--- + +## Active Issues + +*(None currently open. This section is updated when issues are filed.)* + +--- + +## Recently Resolved + +*(No recently resolved issues.)* + +--- + +## How to Update This File + +When a new issue is identified: +1. Add it under **Active Issues** with the template below +2. Include: symptom, cause (if known), workaround +3. When fixed, move it to **Recently Resolved** and add the fix version + +### Issue Template + +```markdown +## [TICKET-NUMBER] + +**Severity:** P0 / P1 / P2 / P3 +**Status:** Workaround / Fix in progress / Fix available +**Affected versions:** All / vX.Y.Z+ + +**Symptoms:** +**Cause:** +**Workaround:** +**Fix (if available):** +``` + +--- + +## Severity Definitions + +| Level | Description | +|-------|-------------| +| P0 | Plugin fails to load; no workaround | +| P1 | Core feature broken; major impact | +| P2 | Non-core feature broken; minor impact | +| P3 | Cosmetic or edge case | + +--- + +## Reporting New Issues + +Use the internal Molecule-AI/internal repo issue tracker. Tag with `plugin-ecc`. diff --git a/plugin.yaml b/plugin.yaml new file mode 100644 index 0000000..42bd84e --- /dev/null +++ b/plugin.yaml @@ -0,0 +1,25 @@ +name: ecc +version: 1.0.0 +description: Everything Claude Code — coding guardrails, standards, and development skills +author: Molecule AI +tags: [claude-code, coding, standards, guardrails] + +runtimes: + - claude_code + - deepagents + - hermes + +rules: + - rules/everything-claude-code-guardrails.md + - rules/node.md + - rules/plugin-ecc-conventions.md + +prompt_fragments: + - AGENTS.md + +skills: + - api-design + - coding-standards + - deep-research + - security-review + - tdd-workflow \ No newline at end of file diff --git a/rules/everything-claude-code-guardrails.md b/rules/everything-claude-code-guardrails.md new file mode 100644 index 0000000..ea62940 --- /dev/null +++ b/rules/everything-claude-code-guardrails.md @@ -0,0 +1,34 @@ +# Everything Claude Code Guardrails + +Generated by ECC Tools from repository history. Review before treating it as a hard policy file. + +## Commit Workflow + +- Prefer `conventional` commit messaging with prefixes such as fix, test, feat, docs. +- Keep new changes aligned with the existing pull-request and review flow already present in the repo. + +## Architecture + +- Preserve the current `hybrid` module organization. +- Respect the current test layout: `separate`. + +## Code Style + +- Use `camelCase` file naming. +- Prefer `relative` imports and `mixed` exports. + +## ECC Defaults + +- Current recommended install profile: `full`. +- Validate risky config changes in PRs and keep the install manifest in source control. + +## Detected Workflows + +- database-migration: Database schema changes with migration files +- feature-development: Standard feature implementation workflow +- add-language-rules: Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines. + +## Review Reminder + +- Regenerate this bundle when repository conventions materially change. +- Keep suppressions narrow and auditable. \ No newline at end of file diff --git a/rules/node.md b/rules/node.md new file mode 100644 index 0000000..5cf890a --- /dev/null +++ b/rules/node.md @@ -0,0 +1,47 @@ +# Node.js Rules for everything-claude-code + +> Project-specific rules for the ECC codebase. Extends common rules. + +## Stack + +- **Runtime**: Node.js >=18 (no transpilation, plain CommonJS) +- **Test runner**: `node tests/run-all.js` — individual files via `node tests/**/*.test.js` +- **Linter**: ESLint (`@eslint/js`, flat config) +- **Coverage**: c8 +- **Lint**: markdownlint-cli for `.md` files + +## File Conventions + +- `scripts/` — Node.js utilities, hooks. CommonJS (`require`/`module.exports`) +- `agents/`, `commands/`, `skills/`, `rules/` — Markdown with YAML frontmatter +- `tests/` — Mirror the `scripts/` structure. Test files named `*.test.js` +- File naming: **lowercase with hyphens** (e.g. `session-start.js`, `post-edit-format.js`) + +## Code Style + +- CommonJS only — no ESM (`import`/`export`) unless file ends in `.mjs` +- No TypeScript — plain `.js` throughout +- Prefer `const` over `let`; never `var` +- Keep hook scripts under 200 lines — extract helpers to `scripts/lib/` +- All hooks must `exit 0` on non-critical errors (never block tool execution unexpectedly) + +## Hook Development + +- Hook scripts normally receive JSON on stdin, but hooks routed through `scripts/hooks/run-with-flags.js` can export `run(rawInput)` and let the wrapper handle parsing/gating +- Async hooks: mark `"async": true` in `settings.json` with a timeout ≤30s +- Blocking hooks (PreToolUse, stop): keep fast (<200ms) — no network calls +- Use `run-with-flags.js` wrapper for all hooks so `ECC_HOOK_PROFILE` and `ECC_DISABLED_HOOKS` runtime gating works +- Always exit 0 on parse errors; log to stderr with `[HookName]` prefix + +## Testing Requirements + +- Run `node tests/run-all.js` before committing +- New scripts in `scripts/lib/` require a matching test in `tests/lib/` +- New hooks require at least one integration test in `tests/hooks/` + +## Markdown / Agent Files + +- Agents: YAML frontmatter with `name`, `description`, `tools`, `model` +- Skills: sections — When to Use, How It Works, Examples +- Commands: `description:` frontmatter line required +- Run `npx markdownlint-cli '**/*.md' --ignore node_modules` before committing diff --git a/rules/plugin-ecc-conventions.md b/rules/plugin-ecc-conventions.md new file mode 100644 index 0000000..4e2deb6 --- /dev/null +++ b/rules/plugin-ecc-conventions.md @@ -0,0 +1,69 @@ +# plugin-ecc Conventions + +> Plugin-specific rules for `plugin-ecc`. These complement the ECC ecosystem rules and are specific to how this plugin is packaged, released, and maintained. + +## Error Handling + +- **Parse errors in rules/skills**: Log to stderr with a `[PluginName]` prefix and `exit 0`. Never block tool execution due to a malformed doc. +- **Adapter errors**: The `adapters/` layer is a thin wrapper — any runtime errors from `plugins_registry` should propagate, not be swallowed. +- **Markdown parse failures**: If a markdown file fails to parse (missing frontmatter, broken sections), report in the agent response but do not abort the session. + +## Logging Patterns + +- **In hooks** (via `run-with-flags.js`): Use `console.error` for diagnostic output — stdout is reserved for the JSON response. +- **Prefix all log lines**: `[ECC] : ` — e.g., `[ECC] guardrails: rule loaded`, `[ECC] adapter: init`. +- **No token/secret logging**: Never log API keys, tokens, or user credentials, even at DEBUG level. +- **Structured fields**: Prefer `console.error('[ECC] key=value key2=value2')` over freeform strings for parseable output. + +## Config / Environment Variables + +| Variable | Purpose | Required | +|----------|---------|----------| +| `ECC_HOOK_PROFILE` | Gate which hooks fire at runtime | No | +| `ECC_DISABLED_HOOKS` | Colon-separated list of hooks to skip | No | +| `PLUGINS_REGISTRY_URL` | Override registry base URL | No | + +- Validate required env vars at adapter init time. Fail fast with a clear message. +- No hardcoded defaults for credentials — require them to be set or inherited from the harness. + +## Release Process + +### Version Bump + +1. Update `version` in `plugin.yaml` (semver: `MAJOR.MINOR.PATCH`). +2. Update version number in `AGENTS.md` (`**Version:** X.Y.Z`) if agents list changed. +3. Update `**Version:**` in `CLAUDE.md` if conventions changed. + +### Pre-Release Checklist + +- [ ] All markdown files pass `npx markdownlint '**/*.md' --ignore node_modules` +- [ ] `plugin.yaml` is valid YAML and references all existing files +- [ ] No `.pyc` or `__pycache__` files committed (gitignore covers this — verify) +- [ ] Changelog section in release notes covers: new rules, removed rules, breaking changes + +### Tag & Push + +```bash +git tag -a vX.Y.Z -m "chore: release vX.Y.Z" +git push origin main +git push origin --tags +``` + +### Post-Release + +Create a GitHub Release with: +- Tag version and SHA +- `## Changelog` section listing all changes since last release +- Any migration notes if `rules/` or `skills/` were reorganized + +## File Integrity + +- All `.md` files committed to the plugin must be valid Markdown — test with `npx markdownlint`. +- No binary files in `rules/`, `skills/`, `agents/`, or `commands/`. +- The `.gitignore` must cover: `__pycache__/`, `*.pyc`, `.DS_Store`, editor backups. + +## Adapter Layer + +- The `adapters/claude_code.py` file wraps the ECC ecosystem via `plugins_registry.builtins.AgentskillsAdaptor`. +- Do not add business logic to the adapter — keep it as a thin import shim. +- If the adapter needs configuration, add it to `plugin.yaml` under an `adapter:` section, not as code constants. diff --git a/runbooks/known-failure-modes.md b/runbooks/known-failure-modes.md new file mode 100644 index 0000000..8529949 --- /dev/null +++ b/runbooks/known-failure-modes.md @@ -0,0 +1,143 @@ +# Known Failure Modes + +This document tracks known issues in `plugin-ecc`, their symptoms, and workarounds. + +--- + +## Markdown Lint Failures in CI + +**Symptom:** CI `validate-plugin.yml` workflow fails on the `markdownlint` step. + +**Cause:** A new or edited `.md` file violates markdownlint rules (e.g., line length > 80, missing blank lines around headings, or inline HTML). + +**Fix:** +```bash +npx markdownlint '**/*.md' --ignore node_modules +# Fix reported issues manually or auto-fix: +npx markdownlint '**/*.md' --ignore node_modules --fix +git add -A && git commit --amend --no-edit +git push --force-with-lease +``` + +**Prevention:** Run the lint command before every commit. + +--- + +## Missing Referenced File in plugin.yaml + +**Symptom:** CI fails with "MISSING: rules/xxx.md" or "MISSING: skills/xxx.md". + +**Cause:** `plugin.yaml` references a file that doesn't exist on disk (file deleted, renamed, or never created). + +**Fix:** +1. Check the `rules:` and `skills:` lists in `plugin.yaml` +2. For each entry, verify the corresponding `.md` file exists +3. Either add the missing file or remove the reference from `plugin.yaml` + +```bash +python3 -c " +import yaml, os +with open('plugin.yaml') as f: + data = yaml.safe_load(f) +for key in ['rules', 'skills']: + for ref in data.get(key, []): + path = ref if ref.endswith('.md') else ref + '.md' + exists = os.path.exists(path) + print(f'[{\"OK\" if exists else \"MISSING\"}] {path}') +" +``` + +--- + +## Adapter Import Failure + +**Symptom:** Python import error at runtime: +``` +ModuleNotFoundError: No module named 'plugins_registry' +``` + +**Cause:** `plugins_registry` is not installed in the runtime environment. It is a harness-level dependency, not bundled in the plugin. + +**Workaround:** This is expected in isolated dev environments. The adapter works when the plugin is installed in the Molecule AI harness. If testing locally, see `runbooks/local-dev-setup.md`. + +--- + +## Stale __pycache__ / .pyc Files + +**Symptom:** "module has no attribute X" errors after a rename. + +**Cause:** Python bytecode cache (`__pycache__/`) holds stale `.pyc` files from a renamed or deleted module. + +**Fix:** +```bash +find . -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null +find . -name '*.pyc' -delete +# Re-run the failing test/import +``` + +**Prevention:** `.gitignore` excludes these; they won't be committed. Always work in a clean venv. + +--- + +## Duplicate Rule/Skill Names + +**Symptom:** CI passes but agents load duplicate rules, or behavior is inconsistent. + +**Cause:** The same rule or skill appears multiple times in `plugin.yaml`'s list. The harness may deduplicate, but behavior can be unpredictable. + +**Fix:** +```bash +python3 -c " +import yaml +with open('plugin.yaml') as f: + data = yaml.safe_load(f) +for key in ['rules', 'skills']: + items = data.get(key, []) + seen = set() + for item in items: + if item in seen: + print(f'DUPLICATE in {key}: {item}') + seen.add(item) +" +``` + +--- + +## Large Context from AGENTS.md + +**Symptom:** Agent runs out of context quickly, or first response is delayed. + +**Cause:** `AGENTS.md` is 167 lines. If included in the system prompt on every message, it consumes significant context budget. + +**Mitigation:** `AGENTS.md` is referenced via `prompt_fragments` in `plugin.yaml`, so it is loaded selectively. No action needed unless the file grows significantly beyond its current size. + +--- + +## Git Push Blocked by Pre-Receive Hook + +**Symptom:** `git push` fails with: +``` +remote: error: pre-receive hook declined +``` + +**Cause:** Either (a) branch protection requires PR review, or (b) CI failed on the latest commit. + +**Fix:** +1. Check CI status at: `https://github.com/Molecule-AI/molecule-ai-plugin-ecc/actions` +2. If CI failed, fix the failures and push again +3. If CI passed but push still blocked, use a PR instead of pushing directly to `main` + +--- + +## Template: Adding a New Issue + +When a new issue is discovered, add it here with: + +```markdown +## + +**Symptom:** +**Cause:** +**Fix/Workaround:** +**Prevention:** +``` diff --git a/runbooks/local-dev-setup.md b/runbooks/local-dev-setup.md new file mode 100644 index 0000000..e6d515e --- /dev/null +++ b/runbooks/local-dev-setup.md @@ -0,0 +1,161 @@ +# Local Development Setup + +This runbook covers setting up a local development environment for `plugin-ecc`. + +--- + +## Prerequisites + +- Node.js >= 18 (for markdownlint) +- Python 3.11+ (for the `adapters/` layer) +- `gh` CLI authenticated: `gh auth status` +- Write access to `Molecule-AI/molecule-ai-plugin-ecc` + +--- + +## Clone & Bootstrap + +```bash +# Clone the repo +git clone https://github.com/Molecule-AI/molecule-ai-plugin-ecc.git +cd molecule-ai-plugin-ecc + +# Install markdownlint CLI (used for pre-commit linting) +npm install -g markdownlint-cli + +# Verify markdownlint works +npx markdownlint '**/*.md' --ignore node_modules +``` + +--- + +## Adapter Dev Environment + +The `adapters/` layer requires the `plugins_registry` package: + +```bash +# Create a virtualenv +python3 -m venv .venv +source .venv/bin/activate + +# Install the adapter runtime dependencies +pip install plugins_registry # published from molecule-core +``` + +To test the adapter: + +```bash +python -c "from adapters.claude_code import Adaptor; print('Adaptor OK')" +``` + +--- + +## Validating Locally + +### Markdown Lint + +```bash +npx markdownlint '**/*.md' --ignore node_modules +``` + +### YAML Validation + +```bash +# Ensure plugin.yaml is valid YAML and all referenced files exist +python3 -c " +import yaml, sys +with open('plugin.yaml') as f: + data = yaml.safe_load(f) +refs = data.get('rules', []) + data.get('skills', []) +for ref in refs: + path = ref if ref.endswith('.md') else ref + '.md' + if not __import__('os').path.exists(path): + print(f'MISSING: {path}') +print('plugin.yaml OK') +" +``` + +--- + +## IDE Setup + +### VS Code + +Recommended extensions (see `.vscode/extensions.json` if present): +- `esbenp.prettier-vscode` — Markdown formatting +- `DavidAnson.vscode-markdownlint` — Inline linting + +Settings for this project (`.vscode/settings.json`): +```json +{ + "editor.formatOnSave": true, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "markdownlint.config": { + "MD013": false, + "MD033": false + } +} +``` + +--- + +## Pre-Commit Checklist + +Before pushing changes: + +```bash +# 1. Lint all markdown +npx markdownlint '**/*.md' --ignore node_modules || exit 1 + +# 2. Validate plugin.yaml +python3 -c "import yaml; yaml.safe_load(open('plugin.yaml'))" || exit 1 + +# 3. Check no pycache +find . -name '__pycache__' -o -name '*.pyc' | grep -v '.gitignore' && echo "FAIL: pycache found" && exit 1 + +# 4. Run tests if any exist +node tests/run-all.js 2>/dev/null || true +``` + +--- + +## Working with the Skills/Agents/Commands Surface + +The plugin exposes workflow definitions from the ECC ecosystem. To inspect which skills are available: + +```bash +ls skills/ +cat skills/*.md | grep -A2 'name:' +``` + +Adding a new skill: +1. Create `skills/.md` with proper YAML frontmatter +2. Add ` - ` to the `skills:` list in `plugin.yaml` +3. Run the YAML validation above to confirm the reference is valid + +--- + +## Troubleshooting + +### markdownlint command not found + +```bash +npm install -g markdownlint-cli +# If still not found, check your PATH or use npx +npx markdownlint-cli '**/*.md' +``` + +### Adapter import fails + +Verify `plugins_registry` is installed in the active venv: +```bash +pip show plugins_registry +# If not installed: +pip install git+https://github.com/Molecule-AI/plugins_registry.git +``` + +### CI failure on markdownlint + +GitHub Actions runs the same `markdownlint` check. Failures usually mean a new `.md` file or edit broke lint rules. Run locally first to catch. diff --git a/runbooks/release-procedure.md b/runbooks/release-procedure.md new file mode 100644 index 0000000..4a54227 --- /dev/null +++ b/runbooks/release-procedure.md @@ -0,0 +1,155 @@ +# Release Procedure + +This runbook describes the steps to release a new version of `plugin-ecc`. + +--- + +## Overview + +Releases are version-tagged GitHub releases. The plugin follows semver (`MAJOR.MINOR.PATCH`). The CI workflow validates the plugin structure on every push. + +--- + +## Pre-Release Steps + +### 1. Review Changed Files + +```bash +git fetch origin main +git log origin/main..HEAD --oneline +``` + +Identify what changed: new rules, new skills, breaking changes, or just a version bump. + +### 2. Run Full Validation + +```bash +# Markdown lint +npx markdownlint '**/*.md' --ignore node_modules + +# YAML structure +python3 -c " +import yaml, os +with open('plugin.yaml') as f: + data = yaml.safe_load(f) +for key in ['rules', 'skills']: + for ref in data.get(key, []): + path = ref if ref.endswith('.md') else ref + '.md' + if not os.path.exists(path): + print(f'MISSING: {path}') +print('All refs OK') +" +``` + +### 3. Bump Version + +Edit `plugin.yaml`: +```yaml +version: X.Y.Z # ← update this +``` + +If agents list changed, update the version in `AGENTS.md`: +```markdown +**Version:** X.Y.Z +``` + +Also update `CLAUDE.md`: +```markdown +**Version:** X.Y.Z +``` + +### 4. Commit Version Bump + +```bash +git add plugin.yaml AGENTS.md CLAUDE.md +git commit -m "chore: bump version to X.Y.Z" +``` + +--- + +## Release Tagging + +### Lightweight Tag (patch/minor) + +```bash +git tag vX.Y.Z +git push origin main +git push origin --tags +``` + +### Annotated Tag (recommended for releases) + +```bash +git tag -a vX.Y.Z -m "Release vX.Y.Z — " +git push origin main +git push origin --tags +``` + +--- + +## Create GitHub Release + +```bash +gh release create vX.Y.Z \ + --title "plugin-ecc vX.Y.Z" \ + --notes "$(cat <<'EOF' +## Changelog + +- +- + +### Migration Notes + +EOF +)" +``` + +Or via the GitHub web UI: +1. Go to https://github.com/Molecule-AI/molecule-ai-plugin-ecc/releases/new +2. Select the tag `vX.Y.Z` +3. Paste the changelog into the release body +4. Publish release + +--- + +## Post-Release Verification + +1. **CI green**: Confirm the `validate-plugin.yml` workflow passed on the release commit. +2. **Tag visible**: `git fetch --tags && git tag -l` shows the new tag. +3. **Release page**: The GitHub Release is published with changelog. +4. **Plugin registry**: If molecule-core pulls from the registry, confirm the new version is indexed. + +--- + +## Rollback + +If a release is bad: + +```bash +# Remove the remote tag +git push origin :refs/tags/vX.Y.Z + +# Revert the version commit +git revert HEAD --no-edit +git push origin main +``` + +Then cut a new release with the fix. + +--- + +## Release Types + +| Type | When to use | +|------|-------------| +| `patch` | Bug fixes, doc corrections, non-breaking additions | +| `minor` | New rules/skills, backward-compatible changes | +| `major` | Breaking changes to `plugin.yaml` schema, removed rules/skills | + +--- + +## Automation Opportunities + +- **Auto-bump on PR merge**: A GitHub Action can detect `feat:` commits and auto-PR a version bump. +- **Auto-changelog**: `generate-release-notes` action can draft changelog from conventional commits. +- Currently these are manual; implement if release frequency increases. diff --git a/skills/api-design/SKILL.md b/skills/api-design/SKILL.md new file mode 100644 index 0000000..a45aca0 --- /dev/null +++ b/skills/api-design/SKILL.md @@ -0,0 +1,523 @@ +--- +name: api-design +description: REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs. +origin: ECC +--- + +# API Design Patterns + +Conventions and best practices for designing consistent, developer-friendly REST APIs. + +## When to Activate + +- Designing new API endpoints +- Reviewing existing API contracts +- Adding pagination, filtering, or sorting +- Implementing error handling for APIs +- Planning API versioning strategy +- Building public or partner-facing APIs + +## Resource Design + +### URL Structure + +``` +# Resources are nouns, plural, lowercase, kebab-case +GET /api/v1/users +GET /api/v1/users/:id +POST /api/v1/users +PUT /api/v1/users/:id +PATCH /api/v1/users/:id +DELETE /api/v1/users/:id + +# Sub-resources for relationships +GET /api/v1/users/:id/orders +POST /api/v1/users/:id/orders + +# Actions that don't map to CRUD (use verbs sparingly) +POST /api/v1/orders/:id/cancel +POST /api/v1/auth/login +POST /api/v1/auth/refresh +``` + +### Naming Rules + +``` +# GOOD +/api/v1/team-members # kebab-case for multi-word resources +/api/v1/orders?status=active # query params for filtering +/api/v1/users/123/orders # nested resources for ownership + +# BAD +/api/v1/getUsers # verb in URL +/api/v1/user # singular (use plural) +/api/v1/team_members # snake_case in URLs +/api/v1/users/123/getOrders # verb in nested resource +``` + +## HTTP Methods and Status Codes + +### Method Semantics + +| Method | Idempotent | Safe | Use For | +|--------|-----------|------|---------| +| GET | Yes | Yes | Retrieve resources | +| POST | No | No | Create resources, trigger actions | +| PUT | Yes | No | Full replacement of a resource | +| PATCH | No* | No | Partial update of a resource | +| DELETE | Yes | No | Remove a resource | + +*PATCH can be made idempotent with proper implementation + +### Status Code Reference + +``` +# Success +200 OK — GET, PUT, PATCH (with response body) +201 Created — POST (include Location header) +204 No Content — DELETE, PUT (no response body) + +# Client Errors +400 Bad Request — Validation failure, malformed JSON +401 Unauthorized — Missing or invalid authentication +403 Forbidden — Authenticated but not authorized +404 Not Found — Resource doesn't exist +409 Conflict — Duplicate entry, state conflict +422 Unprocessable Entity — Semantically invalid (valid JSON, bad data) +429 Too Many Requests — Rate limit exceeded + +# Server Errors +500 Internal Server Error — Unexpected failure (never expose details) +502 Bad Gateway — Upstream service failed +503 Service Unavailable — Temporary overload, include Retry-After +``` + +### Common Mistakes + +``` +# BAD: 200 for everything +{ "status": 200, "success": false, "error": "Not found" } + +# GOOD: Use HTTP status codes semantically +HTTP/1.1 404 Not Found +{ "error": { "code": "not_found", "message": "User not found" } } + +# BAD: 500 for validation errors +# GOOD: 400 or 422 with field-level details + +# BAD: 200 for created resources +# GOOD: 201 with Location header +HTTP/1.1 201 Created +Location: /api/v1/users/abc-123 +``` + +## Response Format + +### Success Response + +```json +{ + "data": { + "id": "abc-123", + "email": "alice@example.com", + "name": "Alice", + "created_at": "2025-01-15T10:30:00Z" + } +} +``` + +### Collection Response (with Pagination) + +```json +{ + "data": [ + { "id": "abc-123", "name": "Alice" }, + { "id": "def-456", "name": "Bob" } + ], + "meta": { + "total": 142, + "page": 1, + "per_page": 20, + "total_pages": 8 + }, + "links": { + "self": "/api/v1/users?page=1&per_page=20", + "next": "/api/v1/users?page=2&per_page=20", + "last": "/api/v1/users?page=8&per_page=20" + } +} +``` + +### Error Response + +```json +{ + "error": { + "code": "validation_error", + "message": "Request validation failed", + "details": [ + { + "field": "email", + "message": "Must be a valid email address", + "code": "invalid_format" + }, + { + "field": "age", + "message": "Must be between 0 and 150", + "code": "out_of_range" + } + ] + } +} +``` + +### Response Envelope Variants + +```typescript +// Option A: Envelope with data wrapper (recommended for public APIs) +interface ApiResponse { + data: T; + meta?: PaginationMeta; + links?: PaginationLinks; +} + +interface ApiError { + error: { + code: string; + message: string; + details?: FieldError[]; + }; +} + +// Option B: Flat response (simpler, common for internal APIs) +// Success: just return the resource directly +// Error: return error object +// Distinguish by HTTP status code +``` + +## Pagination + +### Offset-Based (Simple) + +``` +GET /api/v1/users?page=2&per_page=20 + +# Implementation +SELECT * FROM users +ORDER BY created_at DESC +LIMIT 20 OFFSET 20; +``` + +**Pros:** Easy to implement, supports "jump to page N" +**Cons:** Slow on large offsets (OFFSET 100000), inconsistent with concurrent inserts + +### Cursor-Based (Scalable) + +``` +GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20 + +# Implementation +SELECT * FROM users +WHERE id > :cursor_id +ORDER BY id ASC +LIMIT 21; -- fetch one extra to determine has_next +``` + +```json +{ + "data": [...], + "meta": { + "has_next": true, + "next_cursor": "eyJpZCI6MTQzfQ" + } +} +``` + +**Pros:** Consistent performance regardless of position, stable with concurrent inserts +**Cons:** Cannot jump to arbitrary page, cursor is opaque + +### When to Use Which + +| Use Case | Pagination Type | +|----------|----------------| +| Admin dashboards, small datasets (<10K) | Offset | +| Infinite scroll, feeds, large datasets | Cursor | +| Public APIs | Cursor (default) with offset (optional) | +| Search results | Offset (users expect page numbers) | + +## Filtering, Sorting, and Search + +### Filtering + +``` +# Simple equality +GET /api/v1/orders?status=active&customer_id=abc-123 + +# Comparison operators (use bracket notation) +GET /api/v1/products?price[gte]=10&price[lte]=100 +GET /api/v1/orders?created_at[after]=2025-01-01 + +# Multiple values (comma-separated) +GET /api/v1/products?category=electronics,clothing + +# Nested fields (dot notation) +GET /api/v1/orders?customer.country=US +``` + +### Sorting + +``` +# Single field (prefix - for descending) +GET /api/v1/products?sort=-created_at + +# Multiple fields (comma-separated) +GET /api/v1/products?sort=-featured,price,-created_at +``` + +### Full-Text Search + +``` +# Search query parameter +GET /api/v1/products?q=wireless+headphones + +# Field-specific search +GET /api/v1/users?email=alice +``` + +### Sparse Fieldsets + +``` +# Return only specified fields (reduces payload) +GET /api/v1/users?fields=id,name,email +GET /api/v1/orders?fields=id,total,status&include=customer.name +``` + +## Authentication and Authorization + +### Token-Based Auth + +``` +# Bearer token in Authorization header +GET /api/v1/users +Authorization: Bearer eyJhbGciOiJIUzI1NiIs... + +# API key (for server-to-server) +GET /api/v1/data +X-API-Key: sk_live_abc123 +``` + +### Authorization Patterns + +```typescript +// Resource-level: check ownership +app.get("/api/v1/orders/:id", async (req, res) => { + const order = await Order.findById(req.params.id); + if (!order) return res.status(404).json({ error: { code: "not_found" } }); + if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } }); + return res.json({ data: order }); +}); + +// Role-based: check permissions +app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => { + await User.delete(req.params.id); + return res.status(204).send(); +}); +``` + +## Rate Limiting + +### Headers + +``` +HTTP/1.1 200 OK +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 95 +X-RateLimit-Reset: 1640000000 + +# When exceeded +HTTP/1.1 429 Too Many Requests +Retry-After: 60 +{ + "error": { + "code": "rate_limit_exceeded", + "message": "Rate limit exceeded. Try again in 60 seconds." + } +} +``` + +### Rate Limit Tiers + +| Tier | Limit | Window | Use Case | +|------|-------|--------|----------| +| Anonymous | 30/min | Per IP | Public endpoints | +| Authenticated | 100/min | Per user | Standard API access | +| Premium | 1000/min | Per API key | Paid API plans | +| Internal | 10000/min | Per service | Service-to-service | + +## Versioning + +### URL Path Versioning (Recommended) + +``` +/api/v1/users +/api/v2/users +``` + +**Pros:** Explicit, easy to route, cacheable +**Cons:** URL changes between versions + +### Header Versioning + +``` +GET /api/users +Accept: application/vnd.myapp.v2+json +``` + +**Pros:** Clean URLs +**Cons:** Harder to test, easy to forget + +### Versioning Strategy + +``` +1. Start with /api/v1/ — don't version until you need to +2. Maintain at most 2 active versions (current + previous) +3. Deprecation timeline: + - Announce deprecation (6 months notice for public APIs) + - Add Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT + - Return 410 Gone after sunset date +4. Non-breaking changes don't need a new version: + - Adding new fields to responses + - Adding new optional query parameters + - Adding new endpoints +5. Breaking changes require a new version: + - Removing or renaming fields + - Changing field types + - Changing URL structure + - Changing authentication method +``` + +## Implementation Patterns + +### TypeScript (Next.js API Route) + +```typescript +import { z } from "zod"; +import { NextRequest, NextResponse } from "next/server"; + +const createUserSchema = z.object({ + email: z.string().email(), + name: z.string().min(1).max(100), +}); + +export async function POST(req: NextRequest) { + const body = await req.json(); + const parsed = createUserSchema.safeParse(body); + + if (!parsed.success) { + return NextResponse.json({ + error: { + code: "validation_error", + message: "Request validation failed", + details: parsed.error.issues.map(i => ({ + field: i.path.join("."), + message: i.message, + code: i.code, + })), + }, + }, { status: 422 }); + } + + const user = await createUser(parsed.data); + + return NextResponse.json( + { data: user }, + { + status: 201, + headers: { Location: `/api/v1/users/${user.id}` }, + }, + ); +} +``` + +### Python (Django REST Framework) + +```python +from rest_framework import serializers, viewsets, status +from rest_framework.response import Response + +class CreateUserSerializer(serializers.Serializer): + email = serializers.EmailField() + name = serializers.CharField(max_length=100) + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["id", "email", "name", "created_at"] + +class UserViewSet(viewsets.ModelViewSet): + serializer_class = UserSerializer + permission_classes = [IsAuthenticated] + + def get_serializer_class(self): + if self.action == "create": + return CreateUserSerializer + return UserSerializer + + def create(self, request): + serializer = CreateUserSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = UserService.create(**serializer.validated_data) + return Response( + {"data": UserSerializer(user).data}, + status=status.HTTP_201_CREATED, + headers={"Location": f"/api/v1/users/{user.id}"}, + ) +``` + +### Go (net/http) + +```go +func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) { + var req CreateUserRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body") + return + } + + if err := req.Validate(); err != nil { + writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error()) + return + } + + user, err := h.service.Create(r.Context(), req) + if err != nil { + switch { + case errors.Is(err, domain.ErrEmailTaken): + writeError(w, http.StatusConflict, "email_taken", "Email already registered") + default: + writeError(w, http.StatusInternalServerError, "internal_error", "Internal error") + } + return + } + + w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID)) + writeJSON(w, http.StatusCreated, map[string]any{"data": user}) +} +``` + +## API Design Checklist + +Before shipping a new endpoint: + +- [ ] Resource URL follows naming conventions (plural, kebab-case, no verbs) +- [ ] Correct HTTP method used (GET for reads, POST for creates, etc.) +- [ ] Appropriate status codes returned (not 200 for everything) +- [ ] Input validated with schema (Zod, Pydantic, Bean Validation) +- [ ] Error responses follow standard format with codes and messages +- [ ] Pagination implemented for list endpoints (cursor or offset) +- [ ] Authentication required (or explicitly marked as public) +- [ ] Authorization checked (user can only access their own resources) +- [ ] Rate limiting configured +- [ ] Response does not leak internal details (stack traces, SQL errors) +- [ ] Consistent naming with existing endpoints (camelCase vs snake_case) +- [ ] Documented (OpenAPI/Swagger spec updated) diff --git a/skills/api-design/agents/openai.yaml b/skills/api-design/agents/openai.yaml new file mode 100644 index 0000000..b83fe25 --- /dev/null +++ b/skills/api-design/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "API Design" + short_description: "REST API design patterns and best practices" + brand_color: "#F97316" + default_prompt: "Design REST API: resources, status codes, pagination" +policy: + allow_implicit_invocation: true diff --git a/skills/coding-standards/SKILL.md b/skills/coding-standards/SKILL.md new file mode 100644 index 0000000..200b55c --- /dev/null +++ b/skills/coding-standards/SKILL.md @@ -0,0 +1,530 @@ +--- +name: coding-standards +description: Universal coding standards, best practices, and patterns for TypeScript, JavaScript, React, and Node.js development. +origin: ECC +--- + +# Coding Standards & Best Practices + +Universal coding standards applicable across all projects. + +## When to Activate + +- Starting a new project or module +- Reviewing code for quality and maintainability +- Refactoring existing code to follow conventions +- Enforcing naming, formatting, or structural consistency +- Setting up linting, formatting, or type-checking rules +- Onboarding new contributors to coding conventions + +## Code Quality Principles + +### 1. Readability First +- Code is read more than written +- Clear variable and function names +- Self-documenting code preferred over comments +- Consistent formatting + +### 2. KISS (Keep It Simple, Stupid) +- Simplest solution that works +- Avoid over-engineering +- No premature optimization +- Easy to understand > clever code + +### 3. DRY (Don't Repeat Yourself) +- Extract common logic into functions +- Create reusable components +- Share utilities across modules +- Avoid copy-paste programming + +### 4. YAGNI (You Aren't Gonna Need It) +- Don't build features before they're needed +- Avoid speculative generality +- Add complexity only when required +- Start simple, refactor when needed + +## TypeScript/JavaScript Standards + +### Variable Naming + +```typescript +// PASS: GOOD: Descriptive names +const marketSearchQuery = 'election' +const isUserAuthenticated = true +const totalRevenue = 1000 + +// FAIL: BAD: Unclear names +const q = 'election' +const flag = true +const x = 1000 +``` + +### Function Naming + +```typescript +// PASS: GOOD: Verb-noun pattern +async function fetchMarketData(marketId: string) { } +function calculateSimilarity(a: number[], b: number[]) { } +function isValidEmail(email: string): boolean { } + +// FAIL: BAD: Unclear or noun-only +async function market(id: string) { } +function similarity(a, b) { } +function email(e) { } +``` + +### Immutability Pattern (CRITICAL) + +```typescript +// PASS: ALWAYS use spread operator +const updatedUser = { + ...user, + name: 'New Name' +} + +const updatedArray = [...items, newItem] + +// FAIL: NEVER mutate directly +user.name = 'New Name' // BAD +items.push(newItem) // BAD +``` + +### Error Handling + +```typescript +// PASS: GOOD: Comprehensive error handling +async function fetchData(url: string) { + try { + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + return await response.json() + } catch (error) { + console.error('Fetch failed:', error) + throw new Error('Failed to fetch data') + } +} + +// FAIL: BAD: No error handling +async function fetchData(url) { + const response = await fetch(url) + return response.json() +} +``` + +### Async/Await Best Practices + +```typescript +// PASS: GOOD: Parallel execution when possible +const [users, markets, stats] = await Promise.all([ + fetchUsers(), + fetchMarkets(), + fetchStats() +]) + +// FAIL: BAD: Sequential when unnecessary +const users = await fetchUsers() +const markets = await fetchMarkets() +const stats = await fetchStats() +``` + +### Type Safety + +```typescript +// PASS: GOOD: Proper types +interface Market { + id: string + name: string + status: 'active' | 'resolved' | 'closed' + created_at: Date +} + +function getMarket(id: string): Promise { + // Implementation +} + +// FAIL: BAD: Using 'any' +function getMarket(id: any): Promise { + // Implementation +} +``` + +## React Best Practices + +### Component Structure + +```typescript +// PASS: GOOD: Functional component with types +interface ButtonProps { + children: React.ReactNode + onClick: () => void + disabled?: boolean + variant?: 'primary' | 'secondary' +} + +export function Button({ + children, + onClick, + disabled = false, + variant = 'primary' +}: ButtonProps) { + return ( + + ) +} + +// FAIL: BAD: No types, unclear structure +export function Button(props) { + return +} +``` + +### Custom Hooks + +```typescript +// PASS: GOOD: Reusable custom hook +export function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value) + }, delay) + + return () => clearTimeout(handler) + }, [value, delay]) + + return debouncedValue +} + +// Usage +const debouncedQuery = useDebounce(searchQuery, 500) +``` + +### State Management + +```typescript +// PASS: GOOD: Proper state updates +const [count, setCount] = useState(0) + +// Functional update for state based on previous state +setCount(prev => prev + 1) + +// FAIL: BAD: Direct state reference +setCount(count + 1) // Can be stale in async scenarios +``` + +### Conditional Rendering + +```typescript +// PASS: GOOD: Clear conditional rendering +{isLoading && } +{error && } +{data && } + +// FAIL: BAD: Ternary hell +{isLoading ? : error ? : data ? : null} +``` + +## API Design Standards + +### REST API Conventions + +``` +GET /api/markets # List all markets +GET /api/markets/:id # Get specific market +POST /api/markets # Create new market +PUT /api/markets/:id # Update market (full) +PATCH /api/markets/:id # Update market (partial) +DELETE /api/markets/:id # Delete market + +# Query parameters for filtering +GET /api/markets?status=active&limit=10&offset=0 +``` + +### Response Format + +```typescript +// PASS: GOOD: Consistent response structure +interface ApiResponse { + success: boolean + data?: T + error?: string + meta?: { + total: number + page: number + limit: number + } +} + +// Success response +return NextResponse.json({ + success: true, + data: markets, + meta: { total: 100, page: 1, limit: 10 } +}) + +// Error response +return NextResponse.json({ + success: false, + error: 'Invalid request' +}, { status: 400 }) +``` + +### Input Validation + +```typescript +import { z } from 'zod' + +// PASS: GOOD: Schema validation +const CreateMarketSchema = z.object({ + name: z.string().min(1).max(200), + description: z.string().min(1).max(2000), + endDate: z.string().datetime(), + categories: z.array(z.string()).min(1) +}) + +export async function POST(request: Request) { + const body = await request.json() + + try { + const validated = CreateMarketSchema.parse(body) + // Proceed with validated data + } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json({ + success: false, + error: 'Validation failed', + details: error.errors + }, { status: 400 }) + } + } +} +``` + +## File Organization + +### Project Structure + +``` +src/ +├── app/ # Next.js App Router +│ ├── api/ # API routes +│ ├── markets/ # Market pages +│ └── (auth)/ # Auth pages (route groups) +├── components/ # React components +│ ├── ui/ # Generic UI components +│ ├── forms/ # Form components +│ └── layouts/ # Layout components +├── hooks/ # Custom React hooks +├── lib/ # Utilities and configs +│ ├── api/ # API clients +│ ├── utils/ # Helper functions +│ └── constants/ # Constants +├── types/ # TypeScript types +└── styles/ # Global styles +``` + +### File Naming + +``` +components/Button.tsx # PascalCase for components +hooks/useAuth.ts # camelCase with 'use' prefix +lib/formatDate.ts # camelCase for utilities +types/market.types.ts # camelCase with .types suffix +``` + +## Comments & Documentation + +### When to Comment + +```typescript +// PASS: GOOD: Explain WHY, not WHAT +// Use exponential backoff to avoid overwhelming the API during outages +const delay = Math.min(1000 * Math.pow(2, retryCount), 30000) + +// Deliberately using mutation here for performance with large arrays +items.push(newItem) + +// FAIL: BAD: Stating the obvious +// Increment counter by 1 +count++ + +// Set name to user's name +name = user.name +``` + +### JSDoc for Public APIs + +```typescript +/** + * Searches markets using semantic similarity. + * + * @param query - Natural language search query + * @param limit - Maximum number of results (default: 10) + * @returns Array of markets sorted by similarity score + * @throws {Error} If OpenAI API fails or Redis unavailable + * + * @example + * ```typescript + * const results = await searchMarkets('election', 5) + * console.log(results[0].name) // "Trump vs Biden" + * ``` + */ +export async function searchMarkets( + query: string, + limit: number = 10 +): Promise { + // Implementation +} +``` + +## Performance Best Practices + +### Memoization + +```typescript +import { useMemo, useCallback } from 'react' + +// PASS: GOOD: Memoize expensive computations +const sortedMarkets = useMemo(() => { + return markets.sort((a, b) => b.volume - a.volume) +}, [markets]) + +// PASS: GOOD: Memoize callbacks +const handleSearch = useCallback((query: string) => { + setSearchQuery(query) +}, []) +``` + +### Lazy Loading + +```typescript +import { lazy, Suspense } from 'react' + +// PASS: GOOD: Lazy load heavy components +const HeavyChart = lazy(() => import('./HeavyChart')) + +export function Dashboard() { + return ( + }> + + + ) +} +``` + +### Database Queries + +```typescript +// PASS: GOOD: Select only needed columns +const { data } = await supabase + .from('markets') + .select('id, name, status') + .limit(10) + +// FAIL: BAD: Select everything +const { data } = await supabase + .from('markets') + .select('*') +``` + +## Testing Standards + +### Test Structure (AAA Pattern) + +```typescript +test('calculates similarity correctly', () => { + // Arrange + const vector1 = [1, 0, 0] + const vector2 = [0, 1, 0] + + // Act + const similarity = calculateCosineSimilarity(vector1, vector2) + + // Assert + expect(similarity).toBe(0) +}) +``` + +### Test Naming + +```typescript +// PASS: GOOD: Descriptive test names +test('returns empty array when no markets match query', () => { }) +test('throws error when OpenAI API key is missing', () => { }) +test('falls back to substring search when Redis unavailable', () => { }) + +// FAIL: BAD: Vague test names +test('works', () => { }) +test('test search', () => { }) +``` + +## Code Smell Detection + +Watch for these anti-patterns: + +### 1. Long Functions +```typescript +// FAIL: BAD: Function > 50 lines +function processMarketData() { + // 100 lines of code +} + +// PASS: GOOD: Split into smaller functions +function processMarketData() { + const validated = validateData() + const transformed = transformData(validated) + return saveData(transformed) +} +``` + +### 2. Deep Nesting +```typescript +// FAIL: BAD: 5+ levels of nesting +if (user) { + if (user.isAdmin) { + if (market) { + if (market.isActive) { + if (hasPermission) { + // Do something + } + } + } + } +} + +// PASS: GOOD: Early returns +if (!user) return +if (!user.isAdmin) return +if (!market) return +if (!market.isActive) return +if (!hasPermission) return + +// Do something +``` + +### 3. Magic Numbers +```typescript +// FAIL: BAD: Unexplained numbers +if (retryCount > 3) { } +setTimeout(callback, 500) + +// PASS: GOOD: Named constants +const MAX_RETRIES = 3 +const DEBOUNCE_DELAY_MS = 500 + +if (retryCount > MAX_RETRIES) { } +setTimeout(callback, DEBOUNCE_DELAY_MS) +``` + +**Remember**: Code quality is not negotiable. Clear, maintainable code enables rapid development and confident refactoring. diff --git a/skills/coding-standards/agents/openai.yaml b/skills/coding-standards/agents/openai.yaml new file mode 100644 index 0000000..b0dda0e --- /dev/null +++ b/skills/coding-standards/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Coding Standards" + short_description: "Universal coding standards and best practices" + brand_color: "#3B82F6" + default_prompt: "Apply standards: immutability, error handling, type safety" +policy: + allow_implicit_invocation: true diff --git a/skills/deep-research/SKILL.md b/skills/deep-research/SKILL.md new file mode 100644 index 0000000..5a412b7 --- /dev/null +++ b/skills/deep-research/SKILL.md @@ -0,0 +1,155 @@ +--- +name: deep-research +description: Multi-source deep research using firecrawl and exa MCPs. Searches the web, synthesizes findings, and delivers cited reports with source attribution. Use when the user wants thorough research on any topic with evidence and citations. +origin: ECC +--- + +# Deep Research + +Produce thorough, cited research reports from multiple web sources using firecrawl and exa MCP tools. + +## When to Activate + +- User asks to research any topic in depth +- Competitive analysis, technology evaluation, or market sizing +- Due diligence on companies, investors, or technologies +- Any question requiring synthesis from multiple sources +- User says "research", "deep dive", "investigate", or "what's the current state of" + +## MCP Requirements + +At least one of: +- **firecrawl** — `firecrawl_search`, `firecrawl_scrape`, `firecrawl_crawl` +- **exa** — `web_search_exa`, `web_search_advanced_exa`, `crawling_exa` + +Both together give the best coverage. Configure in `~/.claude.json` or `~/.codex/config.toml`. + +## Workflow + +### Step 1: Understand the Goal + +Ask 1-2 quick clarifying questions: +- "What's your goal — learning, making a decision, or writing something?" +- "Any specific angle or depth you want?" + +If the user says "just research it" — skip ahead with reasonable defaults. + +### Step 2: Plan the Research + +Break the topic into 3-5 research sub-questions. Example: +- Topic: "Impact of AI on healthcare" + - What are the main AI applications in healthcare today? + - What clinical outcomes have been measured? + - What are the regulatory challenges? + - What companies are leading this space? + - What's the market size and growth trajectory? + +### Step 3: Execute Multi-Source Search + +For EACH sub-question, search using available MCP tools: + +**With firecrawl:** +``` +firecrawl_search(query: "", limit: 8) +``` + +**With exa:** +``` +web_search_exa(query: "", numResults: 8) +web_search_advanced_exa(query: "", numResults: 5, startPublishedDate: "2025-01-01") +``` + +**Search strategy:** +- Use 2-3 different keyword variations per sub-question +- Mix general and news-focused queries +- Aim for 15-30 unique sources total +- Prioritize: academic, official, reputable news > blogs > forums + +### Step 4: Deep-Read Key Sources + +For the most promising URLs, fetch full content: + +**With firecrawl:** +``` +firecrawl_scrape(url: "") +``` + +**With exa:** +``` +crawling_exa(url: "", tokensNum: 5000) +``` + +Read 3-5 key sources in full for depth. Do not rely only on search snippets. + +### Step 5: Synthesize and Write Report + +Structure the report: + +```markdown +# [Topic]: Research Report +*Generated: [date] | Sources: [N] | Confidence: [High/Medium/Low]* + +## Executive Summary +[3-5 sentence overview of key findings] + +## 1. [First Major Theme] +[Findings with inline citations] +- Key point ([Source Name](url)) +- Supporting data ([Source Name](url)) + +## 2. [Second Major Theme] +... + +## 3. [Third Major Theme] +... + +## Key Takeaways +- [Actionable insight 1] +- [Actionable insight 2] +- [Actionable insight 3] + +## Sources +1. [Title](url) — [one-line summary] +2. ... + +## Methodology +Searched [N] queries across web and news. Analyzed [M] sources. +Sub-questions investigated: [list] +``` + +### Step 6: Deliver + +- **Short topics**: Post the full report in chat +- **Long reports**: Post the executive summary + key takeaways, save full report to a file + +## Parallel Research with Subagents + +For broad topics, use Claude Code's Task tool to parallelize: + +``` +Launch 3 research agents in parallel: +1. Agent 1: Research sub-questions 1-2 +2. Agent 2: Research sub-questions 3-4 +3. Agent 3: Research sub-question 5 + cross-cutting themes +``` + +Each agent searches, reads sources, and returns findings. The main session synthesizes into the final report. + +## Quality Rules + +1. **Every claim needs a source.** No unsourced assertions. +2. **Cross-reference.** If only one source says it, flag it as unverified. +3. **Recency matters.** Prefer sources from the last 12 months. +4. **Acknowledge gaps.** If you couldn't find good info on a sub-question, say so. +5. **No hallucination.** If you don't know, say "insufficient data found." +6. **Separate fact from inference.** Label estimates, projections, and opinions clearly. + +## Examples + +``` +"Research the current state of nuclear fusion energy" +"Deep dive into Rust vs Go for backend services in 2026" +"Research the best strategies for bootstrapping a SaaS business" +"What's happening with the US housing market right now?" +"Investigate the competitive landscape for AI code editors" +``` diff --git a/skills/deep-research/agents/openai.yaml b/skills/deep-research/agents/openai.yaml new file mode 100644 index 0000000..51ac12b --- /dev/null +++ b/skills/deep-research/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Deep Research" + short_description: "Multi-source deep research with firecrawl and exa MCPs" + brand_color: "#6366F1" + default_prompt: "Research the given topic using firecrawl and exa, produce a cited report" +policy: + allow_implicit_invocation: true diff --git a/skills/security-review/SKILL.md b/skills/security-review/SKILL.md new file mode 100644 index 0000000..af848b9 --- /dev/null +++ b/skills/security-review/SKILL.md @@ -0,0 +1,495 @@ +--- +name: security-review +description: Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns. +origin: ECC +--- + +# Security Review Skill + +This skill ensures all code follows security best practices and identifies potential vulnerabilities. + +## When to Activate + +- Implementing authentication or authorization +- Handling user input or file uploads +- Creating new API endpoints +- Working with secrets or credentials +- Implementing payment features +- Storing or transmitting sensitive data +- Integrating third-party APIs + +## Security Checklist + +### 1. Secrets Management + +#### FAIL: NEVER Do This +```typescript +const apiKey = "sk-proj-xxxxx" // Hardcoded secret +const dbPassword = "password123" // In source code +``` + +#### PASS: ALWAYS Do This +```typescript +const apiKey = process.env.OPENAI_API_KEY +const dbUrl = process.env.DATABASE_URL + +// Verify secrets exist +if (!apiKey) { + throw new Error('OPENAI_API_KEY not configured') +} +``` + +#### Verification Steps +- [ ] No hardcoded API keys, tokens, or passwords +- [ ] All secrets in environment variables +- [ ] `.env.local` in .gitignore +- [ ] No secrets in git history +- [ ] Production secrets in hosting platform (Vercel, Railway) + +### 2. Input Validation + +#### Always Validate User Input +```typescript +import { z } from 'zod' + +// Define validation schema +const CreateUserSchema = z.object({ + email: z.string().email(), + name: z.string().min(1).max(100), + age: z.number().int().min(0).max(150) +}) + +// Validate before processing +export async function createUser(input: unknown) { + try { + const validated = CreateUserSchema.parse(input) + return await db.users.create(validated) + } catch (error) { + if (error instanceof z.ZodError) { + return { success: false, errors: error.errors } + } + throw error + } +} +``` + +#### File Upload Validation +```typescript +function validateFileUpload(file: File) { + // Size check (5MB max) + const maxSize = 5 * 1024 * 1024 + if (file.size > maxSize) { + throw new Error('File too large (max 5MB)') + } + + // Type check + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] + if (!allowedTypes.includes(file.type)) { + throw new Error('Invalid file type') + } + + // Extension check + const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'] + const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0] + if (!extension || !allowedExtensions.includes(extension)) { + throw new Error('Invalid file extension') + } + + return true +} +``` + +#### Verification Steps +- [ ] All user inputs validated with schemas +- [ ] File uploads restricted (size, type, extension) +- [ ] No direct use of user input in queries +- [ ] Whitelist validation (not blacklist) +- [ ] Error messages don't leak sensitive info + +### 3. SQL Injection Prevention + +#### FAIL: NEVER Concatenate SQL +```typescript +// DANGEROUS - SQL Injection vulnerability +const query = `SELECT * FROM users WHERE email = '${userEmail}'` +await db.query(query) +``` + +#### PASS: ALWAYS Use Parameterized Queries +```typescript +// Safe - parameterized query +const { data } = await supabase + .from('users') + .select('*') + .eq('email', userEmail) + +// Or with raw SQL +await db.query( + 'SELECT * FROM users WHERE email = $1', + [userEmail] +) +``` + +#### Verification Steps +- [ ] All database queries use parameterized queries +- [ ] No string concatenation in SQL +- [ ] ORM/query builder used correctly +- [ ] Supabase queries properly sanitized + +### 4. Authentication & Authorization + +#### JWT Token Handling +```typescript +// FAIL: WRONG: localStorage (vulnerable to XSS) +localStorage.setItem('token', token) + +// PASS: CORRECT: httpOnly cookies +res.setHeader('Set-Cookie', + `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`) +``` + +#### Authorization Checks +```typescript +export async function deleteUser(userId: string, requesterId: string) { + // ALWAYS verify authorization first + const requester = await db.users.findUnique({ + where: { id: requesterId } + }) + + if (requester.role !== 'admin') { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 403 } + ) + } + + // Proceed with deletion + await db.users.delete({ where: { id: userId } }) +} +``` + +#### Row Level Security (Supabase) +```sql +-- Enable RLS on all tables +ALTER TABLE users ENABLE ROW LEVEL SECURITY; + +-- Users can only view their own data +CREATE POLICY "Users view own data" + ON users FOR SELECT + USING (auth.uid() = id); + +-- Users can only update their own data +CREATE POLICY "Users update own data" + ON users FOR UPDATE + USING (auth.uid() = id); +``` + +#### Verification Steps +- [ ] Tokens stored in httpOnly cookies (not localStorage) +- [ ] Authorization checks before sensitive operations +- [ ] Row Level Security enabled in Supabase +- [ ] Role-based access control implemented +- [ ] Session management secure + +### 5. XSS Prevention + +#### Sanitize HTML +```typescript +import DOMPurify from 'isomorphic-dompurify' + +// ALWAYS sanitize user-provided HTML +function renderUserContent(html: string) { + const clean = DOMPurify.sanitize(html, { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'], + ALLOWED_ATTR: [] + }) + return
+} +``` + +#### Content Security Policy +```typescript +// next.config.js +const securityHeaders = [ + { + key: 'Content-Security-Policy', + value: ` + default-src 'self'; + script-src 'self' 'unsafe-eval' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: https:; + font-src 'self'; + connect-src 'self' https://api.example.com; + `.replace(/\s{2,}/g, ' ').trim() + } +] +``` + +#### Verification Steps +- [ ] User-provided HTML sanitized +- [ ] CSP headers configured +- [ ] No unvalidated dynamic content rendering +- [ ] React's built-in XSS protection used + +### 6. CSRF Protection + +#### CSRF Tokens +```typescript +import { csrf } from '@/lib/csrf' + +export async function POST(request: Request) { + const token = request.headers.get('X-CSRF-Token') + + if (!csrf.verify(token)) { + return NextResponse.json( + { error: 'Invalid CSRF token' }, + { status: 403 } + ) + } + + // Process request +} +``` + +#### SameSite Cookies +```typescript +res.setHeader('Set-Cookie', + `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`) +``` + +#### Verification Steps +- [ ] CSRF tokens on state-changing operations +- [ ] SameSite=Strict on all cookies +- [ ] Double-submit cookie pattern implemented + +### 7. Rate Limiting + +#### API Rate Limiting +```typescript +import rateLimit from 'express-rate-limit' + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // 100 requests per window + message: 'Too many requests' +}) + +// Apply to routes +app.use('/api/', limiter) +``` + +#### Expensive Operations +```typescript +// Aggressive rate limiting for searches +const searchLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 10, // 10 requests per minute + message: 'Too many search requests' +}) + +app.use('/api/search', searchLimiter) +``` + +#### Verification Steps +- [ ] Rate limiting on all API endpoints +- [ ] Stricter limits on expensive operations +- [ ] IP-based rate limiting +- [ ] User-based rate limiting (authenticated) + +### 8. Sensitive Data Exposure + +#### Logging +```typescript +// FAIL: WRONG: Logging sensitive data +console.log('User login:', { email, password }) +console.log('Payment:', { cardNumber, cvv }) + +// PASS: CORRECT: Redact sensitive data +console.log('User login:', { email, userId }) +console.log('Payment:', { last4: card.last4, userId }) +``` + +#### Error Messages +```typescript +// FAIL: WRONG: Exposing internal details +catch (error) { + return NextResponse.json( + { error: error.message, stack: error.stack }, + { status: 500 } + ) +} + +// PASS: CORRECT: Generic error messages +catch (error) { + console.error('Internal error:', error) + return NextResponse.json( + { error: 'An error occurred. Please try again.' }, + { status: 500 } + ) +} +``` + +#### Verification Steps +- [ ] No passwords, tokens, or secrets in logs +- [ ] Error messages generic for users +- [ ] Detailed errors only in server logs +- [ ] No stack traces exposed to users + +### 9. Blockchain Security (Solana) + +#### Wallet Verification +```typescript +import { verify } from '@solana/web3.js' + +async function verifyWalletOwnership( + publicKey: string, + signature: string, + message: string +) { + try { + const isValid = verify( + Buffer.from(message), + Buffer.from(signature, 'base64'), + Buffer.from(publicKey, 'base64') + ) + return isValid + } catch (error) { + return false + } +} +``` + +#### Transaction Verification +```typescript +async function verifyTransaction(transaction: Transaction) { + // Verify recipient + if (transaction.to !== expectedRecipient) { + throw new Error('Invalid recipient') + } + + // Verify amount + if (transaction.amount > maxAmount) { + throw new Error('Amount exceeds limit') + } + + // Verify user has sufficient balance + const balance = await getBalance(transaction.from) + if (balance < transaction.amount) { + throw new Error('Insufficient balance') + } + + return true +} +``` + +#### Verification Steps +- [ ] Wallet signatures verified +- [ ] Transaction details validated +- [ ] Balance checks before transactions +- [ ] No blind transaction signing + +### 10. Dependency Security + +#### Regular Updates +```bash +# Check for vulnerabilities +npm audit + +# Fix automatically fixable issues +npm audit fix + +# Update dependencies +npm update + +# Check for outdated packages +npm outdated +``` + +#### Lock Files +```bash +# ALWAYS commit lock files +git add package-lock.json + +# Use in CI/CD for reproducible builds +npm ci # Instead of npm install +``` + +#### Verification Steps +- [ ] Dependencies up to date +- [ ] No known vulnerabilities (npm audit clean) +- [ ] Lock files committed +- [ ] Dependabot enabled on GitHub +- [ ] Regular security updates + +## Security Testing + +### Automated Security Tests +```typescript +// Test authentication +test('requires authentication', async () => { + const response = await fetch('/api/protected') + expect(response.status).toBe(401) +}) + +// Test authorization +test('requires admin role', async () => { + const response = await fetch('/api/admin', { + headers: { Authorization: `Bearer ${userToken}` } + }) + expect(response.status).toBe(403) +}) + +// Test input validation +test('rejects invalid input', async () => { + const response = await fetch('/api/users', { + method: 'POST', + body: JSON.stringify({ email: 'not-an-email' }) + }) + expect(response.status).toBe(400) +}) + +// Test rate limiting +test('enforces rate limits', async () => { + const requests = Array(101).fill(null).map(() => + fetch('/api/endpoint') + ) + + const responses = await Promise.all(requests) + const tooManyRequests = responses.filter(r => r.status === 429) + + expect(tooManyRequests.length).toBeGreaterThan(0) +}) +``` + +## Pre-Deployment Security Checklist + +Before ANY production deployment: + +- [ ] **Secrets**: No hardcoded secrets, all in env vars +- [ ] **Input Validation**: All user inputs validated +- [ ] **SQL Injection**: All queries parameterized +- [ ] **XSS**: User content sanitized +- [ ] **CSRF**: Protection enabled +- [ ] **Authentication**: Proper token handling +- [ ] **Authorization**: Role checks in place +- [ ] **Rate Limiting**: Enabled on all endpoints +- [ ] **HTTPS**: Enforced in production +- [ ] **Security Headers**: CSP, X-Frame-Options configured +- [ ] **Error Handling**: No sensitive data in errors +- [ ] **Logging**: No sensitive data logged +- [ ] **Dependencies**: Up to date, no vulnerabilities +- [ ] **Row Level Security**: Enabled in Supabase +- [ ] **CORS**: Properly configured +- [ ] **File Uploads**: Validated (size, type) +- [ ] **Wallet Signatures**: Verified (if blockchain) + +## Resources + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Next.js Security](https://nextjs.org/docs/security) +- [Supabase Security](https://supabase.com/docs/guides/auth) +- [Web Security Academy](https://portswigger.net/web-security) + +--- + +**Remember**: Security is not optional. One vulnerability can compromise the entire platform. When in doubt, err on the side of caution. diff --git a/skills/security-review/agents/openai.yaml b/skills/security-review/agents/openai.yaml new file mode 100644 index 0000000..9af8302 --- /dev/null +++ b/skills/security-review/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Security Review" + short_description: "Comprehensive security checklist and vulnerability detection" + brand_color: "#EF4444" + default_prompt: "Run security checklist: secrets, input validation, injection prevention" +policy: + allow_implicit_invocation: true diff --git a/skills/tdd-workflow/SKILL.md b/skills/tdd-workflow/SKILL.md new file mode 100644 index 0000000..63c6309 --- /dev/null +++ b/skills/tdd-workflow/SKILL.md @@ -0,0 +1,410 @@ +--- +name: tdd-workflow +description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests. +origin: ECC +--- + +# Test-Driven Development Workflow + +This skill ensures all code development follows TDD principles with comprehensive test coverage. + +## When to Activate + +- Writing new features or functionality +- Fixing bugs or issues +- Refactoring existing code +- Adding API endpoints +- Creating new components + +## Core Principles + +### 1. Tests BEFORE Code +ALWAYS write tests first, then implement code to make tests pass. + +### 2. Coverage Requirements +- Minimum 80% coverage (unit + integration + E2E) +- All edge cases covered +- Error scenarios tested +- Boundary conditions verified + +### 3. Test Types + +#### Unit Tests +- Individual functions and utilities +- Component logic +- Pure functions +- Helpers and utilities + +#### Integration Tests +- API endpoints +- Database operations +- Service interactions +- External API calls + +#### E2E Tests (Playwright) +- Critical user flows +- Complete workflows +- Browser automation +- UI interactions + +## TDD Workflow Steps + +### Step 1: Write User Journeys +``` +As a [role], I want to [action], so that [benefit] + +Example: +As a user, I want to search for markets semantically, +so that I can find relevant markets even without exact keywords. +``` + +### Step 2: Generate Test Cases +For each user journey, create comprehensive test cases: + +```typescript +describe('Semantic Search', () => { + it('returns relevant markets for query', async () => { + // Test implementation + }) + + it('handles empty query gracefully', async () => { + // Test edge case + }) + + it('falls back to substring search when Redis unavailable', async () => { + // Test fallback behavior + }) + + it('sorts results by similarity score', async () => { + // Test sorting logic + }) +}) +``` + +### Step 3: Run Tests (They Should Fail) +```bash +npm test +# Tests should fail - we haven't implemented yet +``` + +### Step 4: Implement Code +Write minimal code to make tests pass: + +```typescript +// Implementation guided by tests +export async function searchMarkets(query: string) { + // Implementation here +} +``` + +### Step 5: Run Tests Again +```bash +npm test +# Tests should now pass +``` + +### Step 6: Refactor +Improve code quality while keeping tests green: +- Remove duplication +- Improve naming +- Optimize performance +- Enhance readability + +### Step 7: Verify Coverage +```bash +npm run test:coverage +# Verify 80%+ coverage achieved +``` + +## Testing Patterns + +### Unit Test Pattern (Jest/Vitest) +```typescript +import { render, screen, fireEvent } from '@testing-library/react' +import { Button } from './Button' + +describe('Button Component', () => { + it('renders with correct text', () => { + render() + expect(screen.getByText('Click me')).toBeInTheDocument() + }) + + it('calls onClick when clicked', () => { + const handleClick = jest.fn() + render() + + fireEvent.click(screen.getByRole('button')) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it('is disabled when disabled prop is true', () => { + render() + expect(screen.getByRole('button')).toBeDisabled() + }) +}) +``` + +### API Integration Test Pattern +```typescript +import { NextRequest } from 'next/server' +import { GET } from './route' + +describe('GET /api/markets', () => { + it('returns markets successfully', async () => { + const request = new NextRequest('http://localhost/api/markets') + const response = await GET(request) + const data = await response.json() + + expect(response.status).toBe(200) + expect(data.success).toBe(true) + expect(Array.isArray(data.data)).toBe(true) + }) + + it('validates query parameters', async () => { + const request = new NextRequest('http://localhost/api/markets?limit=invalid') + const response = await GET(request) + + expect(response.status).toBe(400) + }) + + it('handles database errors gracefully', async () => { + // Mock database failure + const request = new NextRequest('http://localhost/api/markets') + // Test error handling + }) +}) +``` + +### E2E Test Pattern (Playwright) +```typescript +import { test, expect } from '@playwright/test' + +test('user can search and filter markets', async ({ page }) => { + // Navigate to markets page + await page.goto('/') + await page.click('a[href="/markets"]') + + // Verify page loaded + await expect(page.locator('h1')).toContainText('Markets') + + // Search for markets + await page.fill('input[placeholder="Search markets"]', 'election') + + // Wait for debounce and results + await page.waitForTimeout(600) + + // Verify search results displayed + const results = page.locator('[data-testid="market-card"]') + await expect(results).toHaveCount(5, { timeout: 5000 }) + + // Verify results contain search term + const firstResult = results.first() + await expect(firstResult).toContainText('election', { ignoreCase: true }) + + // Filter by status + await page.click('button:has-text("Active")') + + // Verify filtered results + await expect(results).toHaveCount(3) +}) + +test('user can create a new market', async ({ page }) => { + // Login first + await page.goto('/creator-dashboard') + + // Fill market creation form + await page.fill('input[name="name"]', 'Test Market') + await page.fill('textarea[name="description"]', 'Test description') + await page.fill('input[name="endDate"]', '2025-12-31') + + // Submit form + await page.click('button[type="submit"]') + + // Verify success message + await expect(page.locator('text=Market created successfully')).toBeVisible() + + // Verify redirect to market page + await expect(page).toHaveURL(/\/markets\/test-market/) +}) +``` + +## Test File Organization + +``` +src/ +├── components/ +│ ├── Button/ +│ │ ├── Button.tsx +│ │ ├── Button.test.tsx # Unit tests +│ │ └── Button.stories.tsx # Storybook +│ └── MarketCard/ +│ ├── MarketCard.tsx +│ └── MarketCard.test.tsx +├── app/ +│ └── api/ +│ └── markets/ +│ ├── route.ts +│ └── route.test.ts # Integration tests +└── e2e/ + ├── markets.spec.ts # E2E tests + ├── trading.spec.ts + └── auth.spec.ts +``` + +## Mocking External Services + +### Supabase Mock +```typescript +jest.mock('@/lib/supabase', () => ({ + supabase: { + from: jest.fn(() => ({ + select: jest.fn(() => ({ + eq: jest.fn(() => Promise.resolve({ + data: [{ id: 1, name: 'Test Market' }], + error: null + })) + })) + })) + } +})) +``` + +### Redis Mock +```typescript +jest.mock('@/lib/redis', () => ({ + searchMarketsByVector: jest.fn(() => Promise.resolve([ + { slug: 'test-market', similarity_score: 0.95 } + ])), + checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true })) +})) +``` + +### OpenAI Mock +```typescript +jest.mock('@/lib/openai', () => ({ + generateEmbedding: jest.fn(() => Promise.resolve( + new Array(1536).fill(0.1) // Mock 1536-dim embedding + )) +})) +``` + +## Test Coverage Verification + +### Run Coverage Report +```bash +npm run test:coverage +``` + +### Coverage Thresholds +```json +{ + "jest": { + "coverageThresholds": { + "global": { + "branches": 80, + "functions": 80, + "lines": 80, + "statements": 80 + } + } + } +} +``` + +## Common Testing Mistakes to Avoid + +### FAIL: WRONG: Testing Implementation Details +```typescript +// Don't test internal state +expect(component.state.count).toBe(5) +``` + +### PASS: CORRECT: Test User-Visible Behavior +```typescript +// Test what users see +expect(screen.getByText('Count: 5')).toBeInTheDocument() +``` + +### FAIL: WRONG: Brittle Selectors +```typescript +// Breaks easily +await page.click('.css-class-xyz') +``` + +### PASS: CORRECT: Semantic Selectors +```typescript +// Resilient to changes +await page.click('button:has-text("Submit")') +await page.click('[data-testid="submit-button"]') +``` + +### FAIL: WRONG: No Test Isolation +```typescript +// Tests depend on each other +test('creates user', () => { /* ... */ }) +test('updates same user', () => { /* depends on previous test */ }) +``` + +### PASS: CORRECT: Independent Tests +```typescript +// Each test sets up its own data +test('creates user', () => { + const user = createTestUser() + // Test logic +}) + +test('updates user', () => { + const user = createTestUser() + // Update logic +}) +``` + +## Continuous Testing + +### Watch Mode During Development +```bash +npm test -- --watch +# Tests run automatically on file changes +``` + +### Pre-Commit Hook +```bash +# Runs before every commit +npm test && npm run lint +``` + +### CI/CD Integration +```yaml +# GitHub Actions +- name: Run Tests + run: npm test -- --coverage +- name: Upload Coverage + uses: codecov/codecov-action@v3 +``` + +## Best Practices + +1. **Write Tests First** - Always TDD +2. **One Assert Per Test** - Focus on single behavior +3. **Descriptive Test Names** - Explain what's tested +4. **Arrange-Act-Assert** - Clear test structure +5. **Mock External Dependencies** - Isolate unit tests +6. **Test Edge Cases** - Null, undefined, empty, large +7. **Test Error Paths** - Not just happy paths +8. **Keep Tests Fast** - Unit tests < 50ms each +9. **Clean Up After Tests** - No side effects +10. **Review Coverage Reports** - Identify gaps + +## Success Metrics + +- 80%+ code coverage achieved +- All tests passing (green) +- No skipped or disabled tests +- Fast test execution (< 30s for unit tests) +- E2E tests cover critical user flows +- Tests catch bugs before production + +--- + +**Remember**: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability. diff --git a/skills/tdd-workflow/agents/openai.yaml b/skills/tdd-workflow/agents/openai.yaml new file mode 100644 index 0000000..425c7d1 --- /dev/null +++ b/skills/tdd-workflow/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "TDD Workflow" + short_description: "Test-driven development with 80%+ coverage" + brand_color: "#22C55E" + default_prompt: "Follow TDD: write tests first, implement, verify 80%+ coverage" +policy: + allow_implicit_invocation: true