javi-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exceptions-spring
|
|
3
|
+
description: >
|
|
4
|
+
Spring Boot exception handling. @ControllerAdvice, ProblemDetail, GlobalExceptionHandler.
|
|
5
|
+
Trigger: apigen-exceptions, GlobalExceptionHandler, @ControllerAdvice, ProblemDetail
|
|
6
|
+
tools:
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
- Edit
|
|
10
|
+
- Bash
|
|
11
|
+
- Grep
|
|
12
|
+
metadata:
|
|
13
|
+
author: apigen-team
|
|
14
|
+
version: "1.0"
|
|
15
|
+
tags: [exceptions, spring-boot, error-handling, java]
|
|
16
|
+
scope: ["apigen-exceptions/**", "apigen-core/**/exception/**"]
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Exceptions Spring Boot (apigen-exceptions)
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
apigen:
|
|
25
|
+
exceptions:
|
|
26
|
+
include-stack-trace: ${DEBUG:false}
|
|
27
|
+
include-message: always
|
|
28
|
+
log-full-details: true
|
|
29
|
+
problem-base-uri: https://api.example.com/errors
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Base Exception Hierarchy
|
|
33
|
+
|
|
34
|
+
```java
|
|
35
|
+
public abstract class ApiException extends RuntimeException {
|
|
36
|
+
|
|
37
|
+
private final String errorCode;
|
|
38
|
+
private final HttpStatus status;
|
|
39
|
+
private final Map<String, Object> context;
|
|
40
|
+
|
|
41
|
+
protected ApiException(String message, String errorCode,
|
|
42
|
+
HttpStatus status, Map<String, Object> context) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.errorCode = errorCode;
|
|
45
|
+
this.status = status;
|
|
46
|
+
this.context = context != null ? context : Map.of();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public ProblemDetail toProblemDetail(String baseUri) {
|
|
50
|
+
ProblemDetail problem = ProblemDetail.forStatus(status);
|
|
51
|
+
problem.setType(URI.create(baseUri + "/" + errorCode));
|
|
52
|
+
problem.setTitle(getTitle());
|
|
53
|
+
problem.setDetail(getMessage());
|
|
54
|
+
problem.setProperty("code", errorCode);
|
|
55
|
+
context.forEach(problem::setProperty);
|
|
56
|
+
return problem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected abstract String getTitle();
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Specific Exception Types
|
|
64
|
+
|
|
65
|
+
```java
|
|
66
|
+
// Validation exceptions
|
|
67
|
+
public class ValidationException extends ApiException {
|
|
68
|
+
private final List<FieldError> fieldErrors;
|
|
69
|
+
|
|
70
|
+
public ValidationException(List<FieldError> errors) {
|
|
71
|
+
super("Validation failed", "VAL-001", HttpStatus.BAD_REQUEST,
|
|
72
|
+
Map.of("errors", errors));
|
|
73
|
+
this.fieldErrors = errors;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Override
|
|
77
|
+
protected String getTitle() {
|
|
78
|
+
return "Validation Error";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resource exceptions
|
|
83
|
+
public class ResourceNotFoundException extends ApiException {
|
|
84
|
+
public ResourceNotFoundException(String resourceType, Object id) {
|
|
85
|
+
super(String.format("%s with id '%s' not found", resourceType, id),
|
|
86
|
+
"RES-404", HttpStatus.NOT_FOUND,
|
|
87
|
+
Map.of("resourceType", resourceType, "resourceId", id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Override
|
|
91
|
+
protected String getTitle() {
|
|
92
|
+
return "Resource Not Found";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Business rule exceptions
|
|
97
|
+
public class BusinessRuleException extends ApiException {
|
|
98
|
+
public BusinessRuleException(String message, String code,
|
|
99
|
+
Map<String, Object> context) {
|
|
100
|
+
super(message, code, HttpStatus.UNPROCESSABLE_ENTITY, context);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Override
|
|
104
|
+
protected String getTitle() {
|
|
105
|
+
return "Business Rule Violation";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Conflict exceptions
|
|
110
|
+
public class ConflictException extends ApiException {
|
|
111
|
+
public ConflictException(String message, String field, Object value) {
|
|
112
|
+
super(message, "CONFLICT-409", HttpStatus.CONFLICT,
|
|
113
|
+
Map.of("field", field, "value", value));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Override
|
|
117
|
+
protected String getTitle() {
|
|
118
|
+
return "Resource Conflict";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Global Exception Handler
|
|
124
|
+
|
|
125
|
+
```java
|
|
126
|
+
@RestControllerAdvice
|
|
127
|
+
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
128
|
+
public class GlobalExceptionHandler {
|
|
129
|
+
|
|
130
|
+
private final ExceptionProperties properties;
|
|
131
|
+
private final MessageSource messageSource;
|
|
132
|
+
|
|
133
|
+
@ExceptionHandler(ApiException.class)
|
|
134
|
+
public ProblemDetail handleApiException(ApiException ex,
|
|
135
|
+
HttpServletRequest request) {
|
|
136
|
+
log.warn("API Exception: {} - {}", ex.getErrorCode(), ex.getMessage());
|
|
137
|
+
|
|
138
|
+
ProblemDetail problem = ex.toProblemDetail(properties.getProblemBaseUri());
|
|
139
|
+
problem.setInstance(URI.create(request.getRequestURI()));
|
|
140
|
+
return problem;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
144
|
+
public ProblemDetail handleValidation(MethodArgumentNotValidException ex,
|
|
145
|
+
HttpServletRequest request) {
|
|
146
|
+
List<FieldError> errors = ex.getBindingResult().getFieldErrors()
|
|
147
|
+
.stream()
|
|
148
|
+
.map(fe -> new FieldError(fe.getField(),
|
|
149
|
+
fe.getDefaultMessage(),
|
|
150
|
+
fe.getRejectedValue()))
|
|
151
|
+
.toList();
|
|
152
|
+
|
|
153
|
+
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
|
|
154
|
+
problem.setType(URI.create(properties.getProblemBaseUri() + "/validation"));
|
|
155
|
+
problem.setTitle("Validation Error");
|
|
156
|
+
problem.setDetail(errors.size() + " validation errors");
|
|
157
|
+
problem.setProperty("errors", errors);
|
|
158
|
+
problem.setInstance(URI.create(request.getRequestURI()));
|
|
159
|
+
return problem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@ExceptionHandler(ConstraintViolationException.class)
|
|
163
|
+
public ProblemDetail handleConstraintViolation(
|
|
164
|
+
ConstraintViolationException ex,
|
|
165
|
+
HttpServletRequest request) {
|
|
166
|
+
|
|
167
|
+
List<FieldError> errors = ex.getConstraintViolations().stream()
|
|
168
|
+
.map(cv -> new FieldError(
|
|
169
|
+
getPropertyPath(cv),
|
|
170
|
+
cv.getMessage(),
|
|
171
|
+
cv.getInvalidValue()))
|
|
172
|
+
.toList();
|
|
173
|
+
|
|
174
|
+
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
|
|
175
|
+
problem.setType(URI.create(properties.getProblemBaseUri() + "/validation"));
|
|
176
|
+
problem.setTitle("Constraint Violation");
|
|
177
|
+
problem.setProperty("errors", errors);
|
|
178
|
+
return problem;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@ExceptionHandler(DataIntegrityViolationException.class)
|
|
182
|
+
public ProblemDetail handleDataIntegrity(DataIntegrityViolationException ex) {
|
|
183
|
+
log.error("Data integrity violation", ex);
|
|
184
|
+
|
|
185
|
+
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.CONFLICT);
|
|
186
|
+
problem.setTitle("Data Conflict");
|
|
187
|
+
problem.setDetail("A data integrity constraint was violated");
|
|
188
|
+
// Don't expose DB details
|
|
189
|
+
return problem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@ExceptionHandler(OptimisticLockingFailureException.class)
|
|
193
|
+
public ProblemDetail handleOptimisticLock(
|
|
194
|
+
OptimisticLockingFailureException ex) {
|
|
195
|
+
|
|
196
|
+
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.CONFLICT);
|
|
197
|
+
problem.setTitle("Concurrent Modification");
|
|
198
|
+
problem.setDetail("Resource was modified by another request");
|
|
199
|
+
problem.setProperty("code", "OPT-LOCK-001");
|
|
200
|
+
return problem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@ExceptionHandler(Exception.class)
|
|
204
|
+
public ProblemDetail handleGeneric(Exception ex, HttpServletRequest request) {
|
|
205
|
+
String requestId = UUID.randomUUID().toString();
|
|
206
|
+
log.error("Unhandled exception [requestId={}]", requestId, ex);
|
|
207
|
+
|
|
208
|
+
ProblemDetail problem = ProblemDetail.forStatus(
|
|
209
|
+
HttpStatus.INTERNAL_SERVER_ERROR);
|
|
210
|
+
problem.setTitle("Internal Server Error");
|
|
211
|
+
problem.setDetail("An unexpected error occurred");
|
|
212
|
+
problem.setProperty("requestId", requestId);
|
|
213
|
+
|
|
214
|
+
if (properties.isIncludeStackTrace()) {
|
|
215
|
+
problem.setProperty("stackTrace", getStackTrace(ex));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return problem;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Field Error Record
|
|
224
|
+
|
|
225
|
+
```java
|
|
226
|
+
public record FieldError(
|
|
227
|
+
String field,
|
|
228
|
+
String message,
|
|
229
|
+
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
230
|
+
Object rejectedValue
|
|
231
|
+
) {}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Exception Auto Configuration
|
|
235
|
+
|
|
236
|
+
```java
|
|
237
|
+
@AutoConfiguration
|
|
238
|
+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
|
239
|
+
@EnableConfigurationProperties(ExceptionProperties.class)
|
|
240
|
+
public class ExceptionAutoConfiguration {
|
|
241
|
+
|
|
242
|
+
@Bean
|
|
243
|
+
@ConditionalOnMissingBean
|
|
244
|
+
public GlobalExceptionHandler globalExceptionHandler(
|
|
245
|
+
ExceptionProperties properties,
|
|
246
|
+
@Autowired(required = false) MessageSource messageSource) {
|
|
247
|
+
return new GlobalExceptionHandler(properties, messageSource);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@Bean
|
|
251
|
+
public ProblemDetailExceptionHandler problemDetailHandler() {
|
|
252
|
+
return new ProblemDetailExceptionHandler();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Custom Problem Detail Extensions
|
|
258
|
+
|
|
259
|
+
```java
|
|
260
|
+
public class ExtendedProblemDetail extends ProblemDetail {
|
|
261
|
+
|
|
262
|
+
private String code;
|
|
263
|
+
private Instant timestamp;
|
|
264
|
+
private String traceId;
|
|
265
|
+
|
|
266
|
+
public static ExtendedProblemDetail forStatusAndCode(
|
|
267
|
+
HttpStatus status, String code) {
|
|
268
|
+
|
|
269
|
+
ExtendedProblemDetail problem = new ExtendedProblemDetail();
|
|
270
|
+
problem.setStatus(status.value());
|
|
271
|
+
problem.setCode(code);
|
|
272
|
+
problem.setTimestamp(Instant.now());
|
|
273
|
+
problem.setTraceId(MDC.get("traceId"));
|
|
274
|
+
return problem;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Testing Exceptions
|
|
280
|
+
|
|
281
|
+
```java
|
|
282
|
+
@WebMvcTest(UserController.class)
|
|
283
|
+
class UserControllerExceptionTest {
|
|
284
|
+
|
|
285
|
+
@Autowired
|
|
286
|
+
private MockMvc mockMvc;
|
|
287
|
+
|
|
288
|
+
@MockBean
|
|
289
|
+
private UserService userService;
|
|
290
|
+
|
|
291
|
+
@Test
|
|
292
|
+
void shouldReturnProblemDetailForNotFound() throws Exception {
|
|
293
|
+
when(userService.findById(any()))
|
|
294
|
+
.thenThrow(new ResourceNotFoundException("User", "123"));
|
|
295
|
+
|
|
296
|
+
mockMvc.perform(get("/api/users/123"))
|
|
297
|
+
.andExpect(status().isNotFound())
|
|
298
|
+
.andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
|
|
299
|
+
.andExpect(jsonPath("$.type").value(containsString("RES-404")))
|
|
300
|
+
.andExpect(jsonPath("$.title").value("Resource Not Found"))
|
|
301
|
+
.andExpect(jsonPath("$.resourceType").value("User"));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@Test
|
|
305
|
+
void shouldReturnValidationErrors() throws Exception {
|
|
306
|
+
String invalidJson = """
|
|
307
|
+
{"email": "not-an-email", "name": ""}
|
|
308
|
+
""";
|
|
309
|
+
|
|
310
|
+
mockMvc.perform(post("/api/users")
|
|
311
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
312
|
+
.content(invalidJson))
|
|
313
|
+
.andExpect(status().isBadRequest())
|
|
314
|
+
.andExpect(jsonPath("$.errors").isArray())
|
|
315
|
+
.andExpect(jsonPath("$.errors.length()").value(2));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Related Skills
|
|
321
|
+
|
|
322
|
+
- `error-handling`: Error handling concepts
|
|
323
|
+
- `spring-boot-4`: Spring Boot 4.0 patterns
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastapi
|
|
3
|
+
description: >
|
|
4
|
+
FastAPI development patterns with Pydantic v2, async services, and dependency injection.
|
|
5
|
+
Trigger: fastapi, python api, async python, pydantic, uvicorn
|
|
6
|
+
tools:
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
- Bash
|
|
10
|
+
- Grep
|
|
11
|
+
metadata:
|
|
12
|
+
author: plataforma-industrial
|
|
13
|
+
version: "2.0"
|
|
14
|
+
tags: [python, fastapi, async, api]
|
|
15
|
+
updated: "2026-02"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# FastAPI Development Skill
|
|
19
|
+
|
|
20
|
+
## Stack
|
|
21
|
+
|
|
22
|
+
```txt
|
|
23
|
+
fastapi==0.110.0
|
|
24
|
+
uvicorn[standard]==0.28.0
|
|
25
|
+
pydantic==2.6.3
|
|
26
|
+
pydantic-settings==2.2.1
|
|
27
|
+
python-jose[cryptography]==3.3.0
|
|
28
|
+
httpx==0.27.0
|
|
29
|
+
redis==5.0.2
|
|
30
|
+
sqlalchemy==2.0.28
|
|
31
|
+
asyncpg==0.29.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
src/
|
|
38
|
+
└── app_name/
|
|
39
|
+
├── __init__.py
|
|
40
|
+
├── main.py
|
|
41
|
+
├── config.py
|
|
42
|
+
├── api/
|
|
43
|
+
│ ├── deps.py
|
|
44
|
+
│ └── routes/
|
|
45
|
+
├── core/
|
|
46
|
+
│ ├── security.py
|
|
47
|
+
│ └── exceptions.py
|
|
48
|
+
├── models/
|
|
49
|
+
├── services/
|
|
50
|
+
└── agents/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Main Application Pattern
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
# main.py
|
|
57
|
+
from contextlib import asynccontextmanager
|
|
58
|
+
from fastapi import FastAPI
|
|
59
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
60
|
+
|
|
61
|
+
@asynccontextmanager
|
|
62
|
+
async def lifespan(app: FastAPI):
|
|
63
|
+
# Startup
|
|
64
|
+
app.state.cache = CacheService()
|
|
65
|
+
await app.state.cache.connect()
|
|
66
|
+
yield
|
|
67
|
+
# Shutdown
|
|
68
|
+
await app.state.cache.disconnect()
|
|
69
|
+
|
|
70
|
+
app = FastAPI(
|
|
71
|
+
title="Service Name",
|
|
72
|
+
version="1.0.0",
|
|
73
|
+
lifespan=lifespan,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
app.add_middleware(
|
|
77
|
+
CORSMiddleware,
|
|
78
|
+
allow_origins=settings.cors_origins,
|
|
79
|
+
allow_credentials=True,
|
|
80
|
+
allow_methods=["*"],
|
|
81
|
+
allow_headers=["*"],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
app.include_router(health.router, tags=["health"])
|
|
85
|
+
app.include_router(api.router, prefix="/api/v1", tags=["api"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Configuration with Pydantic Settings
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# config.py
|
|
92
|
+
from pydantic_settings import BaseSettings
|
|
93
|
+
from functools import lru_cache
|
|
94
|
+
|
|
95
|
+
class Settings(BaseSettings):
|
|
96
|
+
debug: bool = False
|
|
97
|
+
cors_origins: list[str] = ["*"]
|
|
98
|
+
database_url: str = "postgresql+asyncpg://user:pass@localhost/db"
|
|
99
|
+
redis_url: str = "redis://localhost:6379"
|
|
100
|
+
jwt_secret: str
|
|
101
|
+
jwt_algorithm: str = "HS256"
|
|
102
|
+
|
|
103
|
+
class Config:
|
|
104
|
+
env_file = ".env"
|
|
105
|
+
case_sensitive = False
|
|
106
|
+
|
|
107
|
+
@lru_cache
|
|
108
|
+
def get_settings() -> Settings:
|
|
109
|
+
return Settings()
|
|
110
|
+
|
|
111
|
+
settings = get_settings()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Pydantic Models (v2)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pydantic import BaseModel, Field
|
|
118
|
+
from datetime import datetime
|
|
119
|
+
from typing import Literal
|
|
120
|
+
|
|
121
|
+
class MessageRequest(BaseModel):
|
|
122
|
+
content: str = Field(..., min_length=1, max_length=4000)
|
|
123
|
+
context: dict | None = None
|
|
124
|
+
|
|
125
|
+
class MessageResponse(BaseModel):
|
|
126
|
+
message: str
|
|
127
|
+
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
|
128
|
+
|
|
129
|
+
class StreamChunk(BaseModel):
|
|
130
|
+
content: str
|
|
131
|
+
done: bool = False
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Dependency Injection
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# api/deps.py
|
|
138
|
+
from typing import Annotated
|
|
139
|
+
from fastapi import Depends, HTTPException, Header, status
|
|
140
|
+
from jose import jwt, JWTError
|
|
141
|
+
|
|
142
|
+
async def get_cache(request: Request) -> CacheService:
|
|
143
|
+
return request.app.state.cache
|
|
144
|
+
|
|
145
|
+
async def verify_token(authorization: Annotated[str, Header()]) -> dict:
|
|
146
|
+
if not authorization.startswith("Bearer "):
|
|
147
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
148
|
+
|
|
149
|
+
token = authorization[7:]
|
|
150
|
+
try:
|
|
151
|
+
return jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm])
|
|
152
|
+
except JWTError:
|
|
153
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
154
|
+
|
|
155
|
+
async def get_tenant_id(token: Annotated[dict, Depends(verify_token)]) -> str:
|
|
156
|
+
tenant_id = token.get("tenant_id")
|
|
157
|
+
if not tenant_id:
|
|
158
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
|
159
|
+
return tenant_id
|
|
160
|
+
|
|
161
|
+
# Type aliases
|
|
162
|
+
TokenDep = Annotated[dict, Depends(verify_token)]
|
|
163
|
+
TenantDep = Annotated[str, Depends(get_tenant_id)]
|
|
164
|
+
CacheDep = Annotated[CacheService, Depends(get_cache)]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Routes
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
# api/routes/items.py
|
|
171
|
+
from fastapi import APIRouter
|
|
172
|
+
from fastapi.responses import StreamingResponse
|
|
173
|
+
|
|
174
|
+
router = APIRouter()
|
|
175
|
+
|
|
176
|
+
@router.post("", response_model=ItemResponse)
|
|
177
|
+
async def create_item(
|
|
178
|
+
request: ItemRequest,
|
|
179
|
+
tenant_id: TenantDep,
|
|
180
|
+
service: ServiceDep,
|
|
181
|
+
):
|
|
182
|
+
return await service.create(request, tenant_id)
|
|
183
|
+
|
|
184
|
+
@router.post("/stream")
|
|
185
|
+
async def stream_response(request: Request, service: ServiceDep):
|
|
186
|
+
async def generate():
|
|
187
|
+
async for chunk in service.stream():
|
|
188
|
+
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
189
|
+
yield "data: [DONE]\n\n"
|
|
190
|
+
|
|
191
|
+
return StreamingResponse(generate(), media_type="text/event-stream")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Async Service Pattern
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
class ItemService:
|
|
198
|
+
def __init__(self, cache: CacheService):
|
|
199
|
+
self.cache = cache
|
|
200
|
+
|
|
201
|
+
async def create(self, request: ItemRequest, tenant_id: str) -> ItemResponse:
|
|
202
|
+
# Business logic here
|
|
203
|
+
result = await self._process(request)
|
|
204
|
+
await self.cache.set(f"item:{result.id}", result.model_dump())
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
async def stream(self) -> AsyncIterator[StreamChunk]:
|
|
208
|
+
async for chunk in self._generate():
|
|
209
|
+
yield StreamChunk(content=chunk)
|
|
210
|
+
yield StreamChunk(content="", done=True)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Redis Cache Service
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
import redis.asyncio as redis
|
|
217
|
+
import json
|
|
218
|
+
|
|
219
|
+
class CacheService:
|
|
220
|
+
def __init__(self):
|
|
221
|
+
self.redis: redis.Redis | None = None
|
|
222
|
+
|
|
223
|
+
async def connect(self):
|
|
224
|
+
self.redis = await redis.from_url(settings.redis_url)
|
|
225
|
+
|
|
226
|
+
async def disconnect(self):
|
|
227
|
+
if self.redis:
|
|
228
|
+
await self.redis.close()
|
|
229
|
+
|
|
230
|
+
async def get(self, key: str) -> Any | None:
|
|
231
|
+
data = await self.redis.get(key)
|
|
232
|
+
return json.loads(data) if data else None
|
|
233
|
+
|
|
234
|
+
async def set(self, key: str, value: Any, ttl: int = 3600):
|
|
235
|
+
await self.redis.setex(key, ttl, json.dumps(value))
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Custom Exceptions
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from fastapi import HTTPException, status
|
|
242
|
+
|
|
243
|
+
class ServiceException(HTTPException):
|
|
244
|
+
def __init__(self, detail: str):
|
|
245
|
+
super().__init__(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=detail)
|
|
246
|
+
|
|
247
|
+
class RateLimitException(HTTPException):
|
|
248
|
+
def __init__(self):
|
|
249
|
+
super().__init__(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Rate limit exceeded")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Testing
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
import pytest
|
|
256
|
+
from httpx import AsyncClient
|
|
257
|
+
from app.main import app
|
|
258
|
+
|
|
259
|
+
@pytest.fixture
|
|
260
|
+
async def client():
|
|
261
|
+
async with AsyncClient(app=app, base_url="http://test") as ac:
|
|
262
|
+
yield ac
|
|
263
|
+
|
|
264
|
+
@pytest.mark.asyncio
|
|
265
|
+
async def test_create_item(client: AsyncClient, auth_token: str):
|
|
266
|
+
response = await client.post(
|
|
267
|
+
"/api/v1/items",
|
|
268
|
+
json={"content": "test"},
|
|
269
|
+
headers={"Authorization": f"Bearer {auth_token}"},
|
|
270
|
+
)
|
|
271
|
+
assert response.status_code == 200
|
|
272
|
+
assert "message" in response.json()
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Dockerfile
|
|
276
|
+
|
|
277
|
+
```dockerfile
|
|
278
|
+
FROM python:3.12-slim
|
|
279
|
+
WORKDIR /app
|
|
280
|
+
COPY requirements.txt .
|
|
281
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
282
|
+
COPY src/ ./src/
|
|
283
|
+
ENV PYTHONPATH=/app/src
|
|
284
|
+
EXPOSE 8000
|
|
285
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Best Practices
|
|
289
|
+
|
|
290
|
+
1. **Use Pydantic v2**: `BaseModel`, `Field`, `model_dump()`, `model_validate_json()`
|
|
291
|
+
2. **Async everywhere**: Never block the event loop with sync operations
|
|
292
|
+
3. **Dependency injection**: Use `Annotated[Type, Depends(fn)]` pattern
|
|
293
|
+
4. **Type hints**: Full typing for all functions and returns
|
|
294
|
+
5. **Lifespan context**: Manage startup/shutdown with `asynccontextmanager`
|
|
295
|
+
|
|
296
|
+
## Related Skills
|
|
297
|
+
|
|
298
|
+
- `jwt-auth`: Authentication patterns
|
|
299
|
+
- `redis-cache`: Response caching
|
|
300
|
+
- `langchain`: LLM integration
|
|
301
|
+
- `opentelemetry`: Observability
|
|
302
|
+
- `docker-containers`: Deployment
|