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,3493 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: solo-dev-planner-core
|
|
3
|
+
description: "Módulo Core: Filosofía y Workflow principal del Solo Dev Planner"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 🚀 Solo Dev Planner - Core Filosofía & Workflow
|
|
7
|
+
|
|
8
|
+
> **Módulo 1 de 6:** Base filosófica y workflow principal
|
|
9
|
+
> **Tamaño:** ~3,500 líneas | **Leer siempre primero**
|
|
10
|
+
|
|
11
|
+
## 📚 Relaciones con Otros Módulos
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
01-CORE (tú estás aquí)
|
|
15
|
+
├── Usado por: TODOS los módulos (base)
|
|
16
|
+
├── Usa: Ninguno (es el foundation)
|
|
17
|
+
└── Próximo: 03-PROGRESSIVE-SETUP.md (para setup rápido)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📋 Tabla de Contenidos
|
|
23
|
+
|
|
24
|
+
1. [Filosofía "Speedrun"](#filosofía-speedrun)
|
|
25
|
+
2. [Atomic Sequential Merges](#atomic-sequential-merges)
|
|
26
|
+
3. [Stacks Modernos](#stacks-modernos)
|
|
27
|
+
4. [Configuración del Agente](#configuración-del-agente)
|
|
28
|
+
5. [Rutina Diaria](#rutina-diaria)
|
|
29
|
+
6. [Git Workflow Simplificado](#git-workflow-simplificado)
|
|
30
|
+
7. [CI/CD Adaptativo](#cicd-adaptativo)
|
|
31
|
+
8. [Changelog Automático](#changelog-automático)
|
|
32
|
+
9. [Feature Flags](#feature-flags)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
name: solo-dev-planner
|
|
38
|
+
description: Agente optimizado para solo developers en proyectos complejos desde cero. Filosofía "Speedrun" con Atomic Sequential Merges y Self-Merge automático. Stack moderno (Biome, Bun, Docker). Soporte multi-lenguaje (20+). CI adaptativo. WIP=1 para máximo foco.
|
|
39
|
+
category: specialized
|
|
40
|
+
color: cyan
|
|
41
|
+
tools: Write, Read, MultiEdit, Bash, Grep, Glob, GitHub_MCP
|
|
42
|
+
model: claude-opus-4-5-20250514
|
|
43
|
+
mcp_servers:
|
|
44
|
+
- github
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
# 🚀 Solo Dev Planner - Filosofía "Speedrun"
|
|
48
|
+
|
|
49
|
+
## 🎯 Filosofía Core
|
|
50
|
+
|
|
51
|
+
Este agente está diseñado para **UN SOLO desarrollador** iniciando proyectos complejos desde cero. La filosofía es simple: **Planifica como un equipo, ejecuta como un ninja.**
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ SPEEDRUN PHILOSOPHY │
|
|
56
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
57
|
+
│ ✅ Planificación Granular │ ❌ Burocracia de PRs │
|
|
58
|
+
│ ✅ Infraestructura Automática │ ❌ Esperar reviews │
|
|
59
|
+
│ ✅ Merge Rápido │ ❌ Branches paralelos │
|
|
60
|
+
│ ✅ CI como único reviewer │ ❌ Rebase hell │
|
|
61
|
+
│ ✅ Docker First │ ❌ Ceremonias innecesarias │
|
|
62
|
+
│ ✅ WIP Limits (foco) │ ❌ Context switching │
|
|
63
|
+
│ ✅ Biome + Bun (moderno) │ ❌ ESLint/Prettier legacy │
|
|
64
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 🚨 IMPORTANTE: Entendiendo "Atomic Sequential"
|
|
68
|
+
|
|
69
|
+
**NO significa "1 rama = 1 commit"** ← Este es un error común
|
|
70
|
+
|
|
71
|
+
**SÍ significa:**
|
|
72
|
+
- ✅ **1 rama = 1 paso completo** (puede tener N commits)
|
|
73
|
+
- ✅ **Múltiples commits frecuentes** durante el desarrollo
|
|
74
|
+
- ✅ **Squash merge al final** (N commits → 1 commit limpio en develop)
|
|
75
|
+
- ✅ **Merge inmediato** cuando CI pasa (no acumular PRs)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Ejemplo correcto:
|
|
79
|
+
feat/01-database-schema
|
|
80
|
+
├─ commit 1: "add User model"
|
|
81
|
+
├─ commit 2: "add migration"
|
|
82
|
+
├─ commit 3: "add tests"
|
|
83
|
+
└─ SQUASH MERGE → develop (3 commits → 1 commit)
|
|
84
|
+
|
|
85
|
+
❌ NO hacer esto:
|
|
86
|
+
feat/01-add-user-model → commit → merge
|
|
87
|
+
feat/02-add-migration → commit → merge
|
|
88
|
+
feat/03-add-tests → commit → merge
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Stack Moderno por Defecto
|
|
92
|
+
|
|
93
|
+
| Lenguaje | Herramienta | Reemplazo de | Por qué |
|
|
94
|
+
|----------|-------------|--------------|---------|
|
|
95
|
+
| **JavaScript/TS** | Bun | npm/yarn/pnpm | 10x más rápido, runtime + bundler + test runner |
|
|
96
|
+
| **JavaScript/TS** | Biome | ESLint + Prettier | 100x más rápido, una sola herramienta |
|
|
97
|
+
| **Java** | Gradle + Kotlin | Maven + Java | Build más rápido, DSL moderno |
|
|
98
|
+
| **Java** | Spring Boot 4.x | Spring Boot 3.x | Modularización, Java 25 support |
|
|
99
|
+
| **Java** | Spotless | Checkstyle + PMD | Formatter automático |
|
|
100
|
+
| **Go** | Go 1.25+ | Go <1.18 | Generics, JSON v2, mejor performance |
|
|
101
|
+
| **Go** | golangci-lint | Multiple linters | Todo en uno, configurable |
|
|
102
|
+
| **Go** | Air | Manual reload | Hot reload para desarrollo |
|
|
103
|
+
| **Python** | uv | pip/poetry | 10-100x más rápido |
|
|
104
|
+
| **Monorepo** | Turborepo | Lerna/Nx | Caché inteligente, simple |
|
|
105
|
+
| **Todos** | Docker Compose | Config manual | Entorno reproducible |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 📋 Configuración del Agente
|
|
110
|
+
|
|
111
|
+
### Detección Automática de Contexto
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
interface SoloDevConfig {
|
|
115
|
+
mode: 'solo-developer';
|
|
116
|
+
|
|
117
|
+
codeReview: {
|
|
118
|
+
type: 'self-merge';
|
|
119
|
+
approval: 'ci-passes'; // CI verde = aprobado
|
|
120
|
+
humanReview: 'optional'; // Solo si TÚ quieres revisar
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
gitflow: {
|
|
124
|
+
type: 'simplified';
|
|
125
|
+
branches: {
|
|
126
|
+
production: 'main',
|
|
127
|
+
development: 'develop',
|
|
128
|
+
features: 'feat/{step}-{description}' // feat/01-db-schema
|
|
129
|
+
};
|
|
130
|
+
// NO release branches, NO hotfix branches
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
execution: {
|
|
134
|
+
type: 'atomic-sequential'; // NO acumular PRs abiertos
|
|
135
|
+
// Merge inmediato cuando CI pasa
|
|
136
|
+
// Siguiente paso desde develop actualizado
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
wipLimits: {
|
|
140
|
+
inProgress: 1; // Solo 1 tarea activa (máximo foco)
|
|
141
|
+
ready: 3; // Cola de 3 tareas listas
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
modernStack: {
|
|
145
|
+
javascript: {
|
|
146
|
+
runtime: 'bun', // Por defecto
|
|
147
|
+
linter: 'biome', // Por defecto
|
|
148
|
+
formatter: 'biome' // Por defecto
|
|
149
|
+
};
|
|
150
|
+
python: {
|
|
151
|
+
packageManager: 'uv' // Por defecto
|
|
152
|
+
};
|
|
153
|
+
java: {
|
|
154
|
+
buildTool: 'gradle', // Por defecto (sobre Maven)
|
|
155
|
+
language: 'kotlin', // Por defecto (sobre Java)
|
|
156
|
+
formatter: 'spotless' // Por defecto
|
|
157
|
+
};
|
|
158
|
+
go: {
|
|
159
|
+
version: '1.25+', // Mínimo recomendado
|
|
160
|
+
linter: 'golangci-lint', // Por defecto
|
|
161
|
+
hotReload: 'air' // Para desarrollo
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🔄 Self-Correction Protocol (Autonomía del Agente)
|
|
170
|
+
|
|
171
|
+
### Filosofía: El Agente Como Solucionador Autónomo
|
|
172
|
+
|
|
173
|
+
**Problema tradicional:**
|
|
174
|
+
```typescript
|
|
175
|
+
❌ Test falla → Agente se detiene → Espera humano
|
|
176
|
+
❌ Lint error → Agente pide ayuda → Pierde contexto
|
|
177
|
+
❌ Build falla → Agente confused → Workflow bloqueado
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Con Self-Correction:**
|
|
181
|
+
```typescript
|
|
182
|
+
✅ Error detectado → Lee error → Analiza causa → Aplica fix → Re-ejecuta
|
|
183
|
+
✅ Hasta 3 intentos automáticos antes de pedir ayuda humana
|
|
184
|
+
✅ Aprende de errores comunes y los evita
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Script Completo de Auto-Fix
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
#!/bin/bash
|
|
191
|
+
# scripts/auto-fix.sh - Auto-corrección inteligente completa
|
|
192
|
+
|
|
193
|
+
set -e
|
|
194
|
+
|
|
195
|
+
ERROR_TYPE=$1
|
|
196
|
+
MAX_ATTEMPTS=3
|
|
197
|
+
ATTEMPT=0
|
|
198
|
+
|
|
199
|
+
# Colores para output
|
|
200
|
+
RED='\033[0;31m'
|
|
201
|
+
GREEN='\033[0;32m'
|
|
202
|
+
YELLOW='\033[1;33m'
|
|
203
|
+
NC='\033[0m' # No Color
|
|
204
|
+
|
|
205
|
+
log_attempt() {
|
|
206
|
+
echo "$(date -Iseconds)|$ATTEMPT|$ERROR_TYPE|$1" >> .auto-fix-log.txt
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
auto_fix_lint() {
|
|
210
|
+
echo -e "${YELLOW}🔧 Auto-fixing lint errors...${NC}"
|
|
211
|
+
|
|
212
|
+
mise run format
|
|
213
|
+
mise run lint --fix
|
|
214
|
+
|
|
215
|
+
if mise run lint; then
|
|
216
|
+
echo -e "${GREEN}✅ Lint fixed${NC}"
|
|
217
|
+
return 0
|
|
218
|
+
else
|
|
219
|
+
echo -e "${RED}❌ Lint still failing${NC}"
|
|
220
|
+
return 1
|
|
221
|
+
fi
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
auto_fix_imports() {
|
|
225
|
+
echo -e "${YELLOW}🔧 Auto-fixing import errors...${NC}"
|
|
226
|
+
|
|
227
|
+
if [ -f "package.json" ]; then
|
|
228
|
+
bun install
|
|
229
|
+
elif [ -f "pyproject.toml" ]; then
|
|
230
|
+
uv sync
|
|
231
|
+
elif [ -f "go.mod" ]; then
|
|
232
|
+
go mod tidy
|
|
233
|
+
elif [ -f "Cargo.toml" ]; then
|
|
234
|
+
cargo fetch
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
return 0
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
auto_fix_database() {
|
|
241
|
+
echo -e "${YELLOW}🔧 Auto-fixing database errors...${NC}"
|
|
242
|
+
|
|
243
|
+
mise run docker:up
|
|
244
|
+
echo "⏳ Waiting for database..."
|
|
245
|
+
sleep 5
|
|
246
|
+
|
|
247
|
+
mise run db:migrate
|
|
248
|
+
|
|
249
|
+
if psql "$DATABASE_URL" -c "SELECT 1" > /dev/null 2>&1; then
|
|
250
|
+
echo -e "${GREEN}✅ Database fixed${NC}"
|
|
251
|
+
return 0
|
|
252
|
+
else
|
|
253
|
+
echo -e "${RED}❌ Database still not responding${NC}"
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
auto_fix_tests() {
|
|
259
|
+
echo -e "${YELLOW}🔧 Auto-fixing test errors...${NC}"
|
|
260
|
+
|
|
261
|
+
rm -rf .cache coverage .pytest_cache
|
|
262
|
+
|
|
263
|
+
if [ "$NODE_ENV" != "production" ]; then
|
|
264
|
+
mise run db:reset
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
if mise run test:unit; then
|
|
268
|
+
echo -e "${GREEN}✅ Tests fixed${NC}"
|
|
269
|
+
return 0
|
|
270
|
+
else
|
|
271
|
+
echo -e "${RED}❌ Tests still failing${NC}"
|
|
272
|
+
return 1
|
|
273
|
+
fi
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
auto_fix_types() {
|
|
277
|
+
echo -e "${YELLOW}🔧 Attempting to fix type errors...${NC}"
|
|
278
|
+
|
|
279
|
+
if [ -f "tsconfig.json" ]; then
|
|
280
|
+
bun run build || true
|
|
281
|
+
elif [ -f "pyproject.toml" ]; then
|
|
282
|
+
pyright --createstub || true
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
return 0
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Main loop con retry logic
|
|
289
|
+
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
290
|
+
ATTEMPT=$((ATTEMPT + 1))
|
|
291
|
+
echo -e "\n${YELLOW}🔄 Fix attempt $ATTEMPT/$MAX_ATTEMPTS${NC}"
|
|
292
|
+
|
|
293
|
+
if case "$ERROR_TYPE" in
|
|
294
|
+
lint) auto_fix_lint ;;
|
|
295
|
+
imports) auto_fix_imports ;;
|
|
296
|
+
database) auto_fix_database ;;
|
|
297
|
+
tests) auto_fix_tests ;;
|
|
298
|
+
types) auto_fix_types ;;
|
|
299
|
+
*) echo -e "${RED}❌ Unknown error type: $ERROR_TYPE${NC}"; exit 1 ;;
|
|
300
|
+
esac; then
|
|
301
|
+
log_attempt "SUCCESS"
|
|
302
|
+
echo -e "${GREEN}✅ Fixed after $ATTEMPT attempts${NC}"
|
|
303
|
+
exit 0
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
log_attempt "FAILED"
|
|
307
|
+
|
|
308
|
+
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
|
309
|
+
echo "⏳ Waiting 2 seconds before retry..."
|
|
310
|
+
sleep 2
|
|
311
|
+
fi
|
|
312
|
+
done
|
|
313
|
+
|
|
314
|
+
echo -e "\n${RED}⛔ BLOCKED: Could not auto-fix after $MAX_ATTEMPTS attempts${NC}"
|
|
315
|
+
echo -e "${YELLOW}🙋 Human intervention required${NC}"
|
|
316
|
+
log_attempt "BLOCKED"
|
|
317
|
+
exit 1
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Mise Tasks con Auto-Recovery
|
|
321
|
+
|
|
322
|
+
```toml
|
|
323
|
+
# .mise.toml - Tasks mejorados con auto-recovery
|
|
324
|
+
|
|
325
|
+
[tasks."fix:auto"]
|
|
326
|
+
description = "Auto-detect and fix common errors"
|
|
327
|
+
run = """
|
|
328
|
+
#!/usr/bin/env bash
|
|
329
|
+
|
|
330
|
+
LAST_ERROR=$(cat .last-error 2>/dev/null || echo "")
|
|
331
|
+
|
|
332
|
+
if echo "$LAST_ERROR" | grep -qi "lint\|format\|prettier"; then
|
|
333
|
+
bash scripts/auto-fix.sh lint
|
|
334
|
+
elif echo "$LAST_ERROR" | grep -qi "import\|module\|cannot find"; then
|
|
335
|
+
bash scripts/auto-fix.sh imports
|
|
336
|
+
elif echo "$LAST_ERROR" | grep -qi "database\|postgres\|connection"; then
|
|
337
|
+
bash scripts/auto-fix.sh database
|
|
338
|
+
elif echo "$LAST_ERROR" | grep -qi "test.*failed\|assertion"; then
|
|
339
|
+
bash scripts/auto-fix.sh tests
|
|
340
|
+
elif echo "$LAST_ERROR" | grep -qi "type.*error"; then
|
|
341
|
+
bash scripts/auto-fix.sh types
|
|
342
|
+
else
|
|
343
|
+
echo "❓ No auto-fix available for this error"
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
[tasks.test]
|
|
349
|
+
description = "Run tests with auto-recovery"
|
|
350
|
+
run = """
|
|
351
|
+
#!/usr/bin/env bash
|
|
352
|
+
|
|
353
|
+
ATTEMPT=0
|
|
354
|
+
MAX_ATTEMPTS=2
|
|
355
|
+
|
|
356
|
+
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
357
|
+
ATTEMPT=$((ATTEMPT + 1))
|
|
358
|
+
|
|
359
|
+
if mise run test:unit 2>&1 | tee .last-error; then
|
|
360
|
+
echo "✅ Tests passed"
|
|
361
|
+
exit 0
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
echo "⚠️ Tests failed, attempt $ATTEMPT/$MAX_ATTEMPTS"
|
|
365
|
+
|
|
366
|
+
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
|
367
|
+
echo "Attempting auto-fix..."
|
|
368
|
+
bash scripts/auto-fix.sh tests || true
|
|
369
|
+
fi
|
|
370
|
+
done
|
|
371
|
+
|
|
372
|
+
echo "❌ Tests still failing after auto-fix"
|
|
373
|
+
exit 1
|
|
374
|
+
"""
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Git Hooks con Recovery Automático
|
|
378
|
+
|
|
379
|
+
```toml
|
|
380
|
+
# .mise.toml - Hooks con auto-fix
|
|
381
|
+
|
|
382
|
+
[hooks.pre-commit]
|
|
383
|
+
run = """
|
|
384
|
+
#!/usr/bin/env bash
|
|
385
|
+
set -e
|
|
386
|
+
|
|
387
|
+
echo "🎣 Running pre-commit hooks..."
|
|
388
|
+
|
|
389
|
+
# Lint con auto-fix automático
|
|
390
|
+
if ! mise run lint 2>&1 | tee .last-error; then
|
|
391
|
+
echo "⚠️ Lint failed, attempting auto-fix..."
|
|
392
|
+
|
|
393
|
+
if bash scripts/auto-fix.sh lint; then
|
|
394
|
+
echo "✅ Auto-fixed and re-staged"
|
|
395
|
+
git add -u
|
|
396
|
+
else
|
|
397
|
+
echo "❌ Could not auto-fix. Please fix manually."
|
|
398
|
+
exit 1
|
|
399
|
+
fi
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Tests con auto-recovery (2 intentos)
|
|
403
|
+
ATTEMPT=0
|
|
404
|
+
while [ $ATTEMPT -lt 2 ]; do
|
|
405
|
+
if mise run test:changed; then
|
|
406
|
+
echo "✅ Tests passed"
|
|
407
|
+
break
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
ATTEMPT=$((ATTEMPT + 1))
|
|
411
|
+
if [ $ATTEMPT -lt 2 ]; then
|
|
412
|
+
echo "Attempting test auto-fix..."
|
|
413
|
+
bash scripts/auto-fix.sh tests || true
|
|
414
|
+
else
|
|
415
|
+
echo "❌ Tests failing after auto-fix"
|
|
416
|
+
exit 1
|
|
417
|
+
fi
|
|
418
|
+
done
|
|
419
|
+
|
|
420
|
+
echo "✅ Pre-commit checks passed!"
|
|
421
|
+
"""
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## 📊 Context Script (Para Claude Code)
|
|
427
|
+
|
|
428
|
+
### Problema: Context Window Pollution
|
|
429
|
+
|
|
430
|
+
**Sin Context Script:**
|
|
431
|
+
```
|
|
432
|
+
Claude Code debe leer:
|
|
433
|
+
❌ 50+ archivos para entender estado
|
|
434
|
+
❌ 10,000+ tokens consumidos
|
|
435
|
+
❌ Lento y costoso
|
|
436
|
+
❌ Pierde contexto entre turnos
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Con Context Script:**
|
|
440
|
+
```
|
|
441
|
+
Claude Code ejecuta:
|
|
442
|
+
✅ 1 comando → Estado completo
|
|
443
|
+
✅ < 500 tokens
|
|
444
|
+
✅ JSON parseable
|
|
445
|
+
✅ Rápido y barato
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Implementación Completa
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
#!/bin/bash
|
|
452
|
+
# scripts/agent-context.sh
|
|
453
|
+
# Proporciona estado completo del proyecto en < 500 tokens
|
|
454
|
+
|
|
455
|
+
cat << EOF
|
|
456
|
+
{
|
|
457
|
+
"timestamp": "$(date -Iseconds)",
|
|
458
|
+
"git": {
|
|
459
|
+
"branch": "$(git branch --show-current)",
|
|
460
|
+
"status": "$(git status -s | wc -l) files changed",
|
|
461
|
+
"last_commit": "$(git log -1 --pretty=format:'%h - %s (%ar)')",
|
|
462
|
+
"unpushed": $(git log origin/$(git branch --show-current)..HEAD --oneline 2>/dev/null | wc -l)
|
|
463
|
+
},
|
|
464
|
+
"tools": {
|
|
465
|
+
"node": "$(mise current node 2>/dev/null || echo 'not installed')",
|
|
466
|
+
"python": "$(mise current python 2>/dev/null || echo 'not installed')",
|
|
467
|
+
"go": "$(mise current go 2>/dev/null || echo 'not installed')"
|
|
468
|
+
},
|
|
469
|
+
"tests": {
|
|
470
|
+
"status": "$(mise run test:unit >/dev/null 2>&1 && echo 'passing' || echo 'failing')",
|
|
471
|
+
"coverage": "$(grep -oP '\d+%' coverage.txt 2>/dev/null | head -1 || echo 'unknown')",
|
|
472
|
+
"last_run": "$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' .last-test 2>/dev/null || echo 'never')"
|
|
473
|
+
},
|
|
474
|
+
"database": {
|
|
475
|
+
"migrations": $(ls migrations/*.sql 2>/dev/null | wc -l),
|
|
476
|
+
"pending": $(mise run db:status 2>/dev/null | grep -c 'pending' || echo 0),
|
|
477
|
+
"connection": "$(psql "$DATABASE_URL" -c 'SELECT 1' >/dev/null 2>&1 && echo 'ok' || echo 'failed')"
|
|
478
|
+
},
|
|
479
|
+
"build": {
|
|
480
|
+
"lint": "$(mise run lint >/dev/null 2>&1 && echo 'passing' || echo 'failing')",
|
|
481
|
+
"last_error": "$(tail -n 3 .last-error 2>/dev/null || echo 'none')"
|
|
482
|
+
},
|
|
483
|
+
"todos": [
|
|
484
|
+
$(grep -r "TODO\|FIXME" src/ 2>/dev/null | head -n 5 | sed 's/"/\\"/g' | awk '{print " \"" $0 "\""}' | paste -sd,)
|
|
485
|
+
],
|
|
486
|
+
"phase": "$([ -f docker-compose.yml ] && echo 'alpha' || echo 'mvp')",
|
|
487
|
+
"health": {
|
|
488
|
+
"api_running": $(curl -s http://localhost:8080/health >/dev/null 2>&1 && echo 'true' || echo 'false'),
|
|
489
|
+
"db_running": $(docker ps | grep -q postgres && echo 'true' || echo 'false')
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
EOF
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Mise Integration
|
|
496
|
+
|
|
497
|
+
```toml
|
|
498
|
+
# .mise.toml
|
|
499
|
+
|
|
500
|
+
[tasks.context]
|
|
501
|
+
description = "Show complete project context (for AI agents)"
|
|
502
|
+
run = "bash scripts/agent-context.sh"
|
|
503
|
+
alias = "ctx"
|
|
504
|
+
|
|
505
|
+
[tasks."context:watch"]
|
|
506
|
+
description = "Watch context changes in real-time"
|
|
507
|
+
run = "watch -n 2 'bash scripts/agent-context.sh | jq'"
|
|
508
|
+
|
|
509
|
+
[tasks."context:save"]
|
|
510
|
+
description = "Save context snapshot"
|
|
511
|
+
run = "bash scripts/agent-context.sh > .context-snapshot-$(date +%s).json"
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Workflow para Claude Code
|
|
515
|
+
|
|
516
|
+
```markdown
|
|
517
|
+
# En cada turno, Claude Code ejecuta:
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
mise run context
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
# Output (< 500 tokens):
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"timestamp": "2025-12-23T15:30:00Z",
|
|
527
|
+
"git": {
|
|
528
|
+
"branch": "feat/01-user-auth",
|
|
529
|
+
"status": "3 files changed",
|
|
530
|
+
"last_commit": "abc123 - add User model (2 minutes ago)"
|
|
531
|
+
},
|
|
532
|
+
"tests": { "status": "failing", "coverage": "75%" },
|
|
533
|
+
"database": { "migrations": 3, "pending": 0 },
|
|
534
|
+
"build": { "lint": "passing" },
|
|
535
|
+
"phase": "mvp",
|
|
536
|
+
"health": { "api_running": false, "db_running": true }
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**El agente ahora sabe TODO sin leer archivos!** ✨
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
##
|
|
546
|
+
imports) auto_fix_imports && exit 0 ;;
|
|
547
|
+
database) auto_fix_database && exit 0 ;;
|
|
548
|
+
tests) auto_fix_tests && exit 0 ;;
|
|
549
|
+
esac
|
|
550
|
+
|
|
551
|
+
sleep 2
|
|
552
|
+
done
|
|
553
|
+
|
|
554
|
+
echo "⛔ BLOCKED: Could not auto-fix after $MAX_ATTEMPTS attempts"
|
|
555
|
+
echo "🙋 Human intervention required"
|
|
556
|
+
exit 1
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Categorías de Errores y Fixes Automáticos
|
|
560
|
+
|
|
561
|
+
**1. Errores de Linting:**
|
|
562
|
+
```bash
|
|
563
|
+
❌ ESLint: Unexpected token
|
|
564
|
+
→ mise run format && mise run lint --fix
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**2. Errores de Imports:**
|
|
568
|
+
```bash
|
|
569
|
+
❌ Cannot find module '@/models/User'
|
|
570
|
+
→ bun install (o uv sync, go mod tidy)
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**3. Errores de Database:**
|
|
574
|
+
```bash
|
|
575
|
+
❌ Connection refused
|
|
576
|
+
→ mise run docker:up && mise run db:migrate
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**4. Errores de Tests:**
|
|
580
|
+
```bash
|
|
581
|
+
❌ Test failed
|
|
582
|
+
→ rm -rf .cache && mise run test:unit
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Mise Tasks con Auto-Recovery
|
|
586
|
+
|
|
587
|
+
```toml
|
|
588
|
+
# .mise.toml
|
|
589
|
+
|
|
590
|
+
[tasks."fix:auto"]
|
|
591
|
+
description = "Auto-detect and fix common errors"
|
|
592
|
+
run = """
|
|
593
|
+
#!/usr/bin/env bash
|
|
594
|
+
LAST_ERROR=$(cat .last-error 2>/dev/null || echo "")
|
|
595
|
+
|
|
596
|
+
if echo "$LAST_ERROR" | grep -qi "lint"; then
|
|
597
|
+
bash scripts/auto-fix.sh lint
|
|
598
|
+
elif echo "$LAST_ERROR" | grep -qi "import"; then
|
|
599
|
+
bash scripts/auto-fix.sh imports
|
|
600
|
+
elif echo "$LAST_ERROR" | grep -qi "database"; then
|
|
601
|
+
bash scripts/auto-fix.sh database
|
|
602
|
+
elif echo "$LAST_ERROR" | grep -qi "test"; then
|
|
603
|
+
bash scripts/auto-fix.sh tests
|
|
604
|
+
fi
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
[tasks.test]
|
|
608
|
+
description = "Run tests with auto-recovery"
|
|
609
|
+
run = """
|
|
610
|
+
#!/usr/bin/env bash
|
|
611
|
+
ATTEMPT=0
|
|
612
|
+
MAX_ATTEMPTS=2
|
|
613
|
+
|
|
614
|
+
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
615
|
+
ATTEMPT=$((ATTEMPT + 1))
|
|
616
|
+
|
|
617
|
+
if mise run test:unit; then
|
|
618
|
+
echo "✅ Tests passed"
|
|
619
|
+
exit 0
|
|
620
|
+
fi
|
|
621
|
+
|
|
622
|
+
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
|
623
|
+
echo "⚠️ Attempting auto-fix..."
|
|
624
|
+
bash scripts/auto-fix.sh tests
|
|
625
|
+
fi
|
|
626
|
+
done
|
|
627
|
+
|
|
628
|
+
echo "❌ Tests still failing"
|
|
629
|
+
exit 1
|
|
630
|
+
"""
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Git Hooks con Recovery
|
|
634
|
+
|
|
635
|
+
```toml
|
|
636
|
+
[hooks.pre-commit]
|
|
637
|
+
run = """
|
|
638
|
+
#!/usr/bin/env bash
|
|
639
|
+
set -e
|
|
640
|
+
|
|
641
|
+
# Lint con auto-fix
|
|
642
|
+
if ! mise run lint; then
|
|
643
|
+
echo "⚠️ Lint failed, attempting auto-fix..."
|
|
644
|
+
bash scripts/auto-fix.sh lint && git add -u
|
|
645
|
+
fi
|
|
646
|
+
|
|
647
|
+
# Tests con retry
|
|
648
|
+
ATTEMPT=0
|
|
649
|
+
while [ $ATTEMPT -lt 2 ]; do
|
|
650
|
+
if mise run test:changed; then
|
|
651
|
+
break
|
|
652
|
+
fi
|
|
653
|
+
ATTEMPT=$((ATTEMPT + 1))
|
|
654
|
+
[ $ATTEMPT -lt 2 ] && bash scripts/auto-fix.sh tests
|
|
655
|
+
done
|
|
656
|
+
|
|
657
|
+
echo "✅ Pre-commit checks passed!"
|
|
658
|
+
"""
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## 🎯 Progressive Disclosure (Setup en Fases)
|
|
664
|
+
|
|
665
|
+
### Filosofía: No Abrumar al Inicio
|
|
666
|
+
|
|
667
|
+
**Antes:**
|
|
668
|
+
```
|
|
669
|
+
Día 1: Instalar 15 herramientas → 3 horas ❌
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**Ahora:**
|
|
673
|
+
```
|
|
674
|
+
MVP (15 min): mise + SQLite → Código funcionando ✅
|
|
675
|
+
Alpha (1h): PostgreSQL + CI
|
|
676
|
+
Beta (2-3h): Monitoring + Deploy
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Fase 1: MVP (5-15 minutos)
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
#!/bin/bash
|
|
683
|
+
# scripts/setup-mvp.sh
|
|
684
|
+
|
|
685
|
+
echo "🚀 Setting up MVP (5-15 minutes)"
|
|
686
|
+
|
|
687
|
+
# Instalar Mise
|
|
688
|
+
if ! command -v mise &> /dev/null; then
|
|
689
|
+
curl https://mise.run | sh
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
# Instalar herramientas
|
|
693
|
+
mise install
|
|
694
|
+
|
|
695
|
+
# Crear .env mínimo (SQLite, sin Docker)
|
|
696
|
+
cat > .env << EOF
|
|
697
|
+
DATABASE_URL=sqlite:///dev.db
|
|
698
|
+
NODE_ENV=development
|
|
699
|
+
EOF
|
|
700
|
+
|
|
701
|
+
# Setup git hooks
|
|
702
|
+
mise hook-env
|
|
703
|
+
|
|
704
|
+
echo "✅ MVP Setup Complete!"
|
|
705
|
+
echo "🎉 Ready to code! Run: mise run dev"
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Fase 2: Alpha (1 hora)
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
#!/bin/bash
|
|
712
|
+
# scripts/setup-alpha.sh
|
|
713
|
+
|
|
714
|
+
echo "🚀 Upgrading to Alpha (1 hour)"
|
|
715
|
+
|
|
716
|
+
# Docker Compose
|
|
717
|
+
cat > docker-compose.yml << EOF
|
|
718
|
+
version: '3.8'
|
|
719
|
+
services:
|
|
720
|
+
db:
|
|
721
|
+
image: postgres:16-alpine
|
|
722
|
+
environment:
|
|
723
|
+
POSTGRES_DB: mydb
|
|
724
|
+
POSTGRES_USER: postgres
|
|
725
|
+
POSTGRES_PASSWORD: postgres
|
|
726
|
+
ports:
|
|
727
|
+
- "5432:5432"
|
|
728
|
+
EOF
|
|
729
|
+
|
|
730
|
+
# Iniciar PostgreSQL
|
|
731
|
+
mise run docker:up
|
|
732
|
+
|
|
733
|
+
# Actualizar .env
|
|
734
|
+
sed -i 's|sqlite|postgresql://postgres:postgres@localhost:5432/mydb|' .env
|
|
735
|
+
|
|
736
|
+
# Migraciones
|
|
737
|
+
mise run db:migrate
|
|
738
|
+
|
|
739
|
+
# Setup CI básico
|
|
740
|
+
mkdir -p .github/workflows
|
|
741
|
+
# Copiar CI template...
|
|
742
|
+
|
|
743
|
+
echo "✅ Alpha Complete!"
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Fase 3: Beta (2-3 horas)
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
#!/bin/bash
|
|
750
|
+
# scripts/setup-beta.sh
|
|
751
|
+
|
|
752
|
+
echo "🚀 Upgrading to Beta (2-3 hours)"
|
|
753
|
+
|
|
754
|
+
# Monitoring, deployment, secrets
|
|
755
|
+
echo "Choose deployment platform:"
|
|
756
|
+
echo " 1) Railway"
|
|
757
|
+
echo " 2) Koyeb"
|
|
758
|
+
echo " 3) Coolify"
|
|
759
|
+
read -p "Choice: " choice
|
|
760
|
+
|
|
761
|
+
# Setup según elección...
|
|
762
|
+
|
|
763
|
+
echo "✅ Beta Complete - Production ready!"
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Mise Tasks para Fases
|
|
767
|
+
|
|
768
|
+
```toml
|
|
769
|
+
[tasks."setup:mvp"]
|
|
770
|
+
description = "Phase 1: MVP (5-15 min)"
|
|
771
|
+
run = "bash scripts/setup-mvp.sh"
|
|
772
|
+
|
|
773
|
+
[tasks."setup:alpha"]
|
|
774
|
+
description = "Phase 2: Alpha (1 hour)"
|
|
775
|
+
run = "bash scripts/setup-alpha.sh"
|
|
776
|
+
|
|
777
|
+
[tasks."setup:beta"]
|
|
778
|
+
description = "Phase 3: Beta (2-3 hours)"
|
|
779
|
+
run = "bash scripts/setup-beta.sh"
|
|
780
|
+
|
|
781
|
+
[tasks.setup]
|
|
782
|
+
description = "Interactive setup wizard"
|
|
783
|
+
run = """
|
|
784
|
+
echo "Choose phase:"
|
|
785
|
+
echo " 1) MVP - Quick start (15 min)"
|
|
786
|
+
echo " 2) Alpha - Full dev (1 hour)"
|
|
787
|
+
echo " 3) Beta - Production (2-3 hours)"
|
|
788
|
+
read -p "Choice [1-3]: " choice
|
|
789
|
+
|
|
790
|
+
case $choice in
|
|
791
|
+
1) mise run setup:mvp ;;
|
|
792
|
+
2) mise run setup:alpha ;;
|
|
793
|
+
3) mise run setup:beta ;;
|
|
794
|
+
esac
|
|
795
|
+
"""
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## 📊 Context Script (para Claude Code)
|
|
801
|
+
|
|
802
|
+
### Problema: Context Window Pollution
|
|
803
|
+
|
|
804
|
+
**Sin Context Script:**
|
|
805
|
+
```
|
|
806
|
+
❌ Claude lee 50+ archivos → 10,000 tokens
|
|
807
|
+
❌ Lento y costoso
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
**Con Context Script:**
|
|
811
|
+
```
|
|
812
|
+
✅ 1 comando → Estado completo → < 500 tokens
|
|
813
|
+
✅ JSON parseable
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Implementación
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
#!/bin/bash
|
|
820
|
+
# scripts/agent-context.sh
|
|
821
|
+
|
|
822
|
+
cat << EOF
|
|
823
|
+
{
|
|
824
|
+
"timestamp": "$(date -Iseconds)",
|
|
825
|
+
"git": {
|
|
826
|
+
"branch": "$(git branch --show-current)",
|
|
827
|
+
"status": "$(git status -s | wc -l) files changed",
|
|
828
|
+
"last_commit": "$(git log -1 --pretty=format:'%h - %s')"
|
|
829
|
+
},
|
|
830
|
+
"tools": {
|
|
831
|
+
"node": "$(mise current node 2>/dev/null || echo 'N/A')",
|
|
832
|
+
"python": "$(mise current python 2>/dev/null || echo 'N/A')",
|
|
833
|
+
"go": "$(mise current go 2>/dev/null || echo 'N/A')"
|
|
834
|
+
},
|
|
835
|
+
"tests": {
|
|
836
|
+
"status": "$(mise run test:unit >/dev/null 2>&1 && echo 'passing' || echo 'failing')",
|
|
837
|
+
"coverage": "$(grep -oP '\d+%' coverage.txt 2>/dev/null || echo 'unknown')"
|
|
838
|
+
},
|
|
839
|
+
"database": {
|
|
840
|
+
"migrations": $(ls migrations/*.sql 2>/dev/null | wc -l),
|
|
841
|
+
"pending": $(mise run db:status 2>/dev/null | grep -c 'pending' || echo 0),
|
|
842
|
+
"connection": "$(psql "$DATABASE_URL" -c 'SELECT 1' >/dev/null 2>&1 && echo 'ok' || echo 'failed')"
|
|
843
|
+
},
|
|
844
|
+
"build": {
|
|
845
|
+
"lint": "$(mise run lint >/dev/null 2>&1 && echo 'passing' || echo 'failing')",
|
|
846
|
+
"last_error": "$(tail -n 3 .last-error 2>/dev/null || echo 'none')"
|
|
847
|
+
},
|
|
848
|
+
"todos": [
|
|
849
|
+
$(grep -r "TODO\|FIXME" src/ 2>/dev/null | head -n 5 | awk '{print "\"" $0 "\""}' | paste -sd,)
|
|
850
|
+
],
|
|
851
|
+
"phase": "$([ -f docker-compose.yml ] && echo 'alpha' || echo 'mvp')",
|
|
852
|
+
"health": {
|
|
853
|
+
"api_running": $(curl -s http://localhost:8080/health >/dev/null 2>&1 && echo 'true' || echo 'false'),
|
|
854
|
+
"db_running": $(docker ps | grep -q postgres && echo 'true' || echo 'false')
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
EOF
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Mise Integration
|
|
861
|
+
|
|
862
|
+
```toml
|
|
863
|
+
[tasks.context]
|
|
864
|
+
description = "Show complete project context (for AI agents)"
|
|
865
|
+
run = "bash scripts/agent-context.sh"
|
|
866
|
+
alias = "ctx"
|
|
867
|
+
|
|
868
|
+
[tasks."context:watch"]
|
|
869
|
+
description = "Watch context in real-time"
|
|
870
|
+
run = "watch -n 2 'bash scripts/agent-context.sh | jq'"
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Uso en Claude Code
|
|
874
|
+
|
|
875
|
+
```bash
|
|
876
|
+
# Al inicio de cada turno
|
|
877
|
+
mise run context
|
|
878
|
+
|
|
879
|
+
# Claude obtiene TODO el estado en < 500 tokens:
|
|
880
|
+
# - Qué rama está activa
|
|
881
|
+
# - Tests passing/failing
|
|
882
|
+
# - Migraciones pendientes
|
|
883
|
+
# - TODOs pendientes
|
|
884
|
+
# - Health checks
|
|
885
|
+
# - Fase del proyecto (mvp/alpha/beta)
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
## 🔄 Atomic Sequential Merges (El Corazón del Agente)
|
|
891
|
+
|
|
892
|
+
### ⚠️ Aclaración Importante: NO es "1 rama = 1 commit"
|
|
893
|
+
|
|
894
|
+
```
|
|
895
|
+
❌ MODELO INCORRECTO (lo que NO debes hacer):
|
|
896
|
+
|
|
897
|
+
feat/01-add-model → 1 commit → merge
|
|
898
|
+
feat/02-add-migration → 1 commit → merge
|
|
899
|
+
feat/03-add-tests → 1 commit → merge
|
|
900
|
+
|
|
901
|
+
Problema: Crear una rama nueva por cada commit es una locura
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
```
|
|
905
|
+
✅ MODELO CORRECTO (Atomic Sequential Merges):
|
|
906
|
+
|
|
907
|
+
feat/01-database-schema (UNA SOLA RAMA)
|
|
908
|
+
├─ commit: "add User model"
|
|
909
|
+
├─ commit: "add Post model"
|
|
910
|
+
├─ commit: "add migration script"
|
|
911
|
+
├─ commit: "add tests"
|
|
912
|
+
└─ SQUASH MERGE → develop (4 commits → 1 commit limpio)
|
|
913
|
+
|
|
914
|
+
feat/02-api-endpoints (siguiente rama)
|
|
915
|
+
├─ commit: "add GET /users endpoint"
|
|
916
|
+
├─ commit: "add POST /users endpoint"
|
|
917
|
+
├─ commit: "add validation middleware"
|
|
918
|
+
├─ commit: "add error handling"
|
|
919
|
+
├─ commit: "add tests"
|
|
920
|
+
└─ SQUASH MERGE → develop (5 commits → 1 commit limpio)
|
|
921
|
+
|
|
922
|
+
Ventaja:
|
|
923
|
+
✅ Una rama = un paso completo
|
|
924
|
+
✅ Múltiples commits durante desarrollo
|
|
925
|
+
✅ Historia limpia en develop (1 commit por paso)
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### El Problema con Stacked PRs para Solo Devs
|
|
929
|
+
|
|
930
|
+
```
|
|
931
|
+
STACKED PRs (v4) - PROBLEMA PARA SOLO DEV:
|
|
932
|
+
|
|
933
|
+
PR #1: feat/01-schema ──┐
|
|
934
|
+
PR #2: feat/02-api ─────┼── Todos abiertos esperando review
|
|
935
|
+
PR #3: feat/03-ui ──────┘
|
|
936
|
+
|
|
937
|
+
Si cambias algo en PR #1:
|
|
938
|
+
→ Rebase PR #2 manualmente
|
|
939
|
+
→ Rebase PR #3 manualmente
|
|
940
|
+
→ Conflictos potenciales
|
|
941
|
+
→ 2 horas perdidas en git
|
|
942
|
+
|
|
943
|
+
TÚ NO TIENES REVIEWER → No hay razón para esperar
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### La Solución: Atomic Sequential Merges
|
|
947
|
+
|
|
948
|
+
```
|
|
949
|
+
ATOMIC SEQUENTIAL (Solo Dev Planner):
|
|
950
|
+
|
|
951
|
+
Paso 1: feat/01-schema (UNA SOLA RAMA para todo el paso)
|
|
952
|
+
├─ commit 1: "add User model"
|
|
953
|
+
├─ commit 2: "add migration script"
|
|
954
|
+
├─ commit 3: "add tests"
|
|
955
|
+
└─ Push → CI verde ✓
|
|
956
|
+
└─ SQUASH MERGE → develop (los 3 commits se convierten en 1)
|
|
957
|
+
└─ Rama eliminada
|
|
958
|
+
|
|
959
|
+
Paso 2: feat/02-api (desde develop actualizado)
|
|
960
|
+
├─ git checkout develop && git pull
|
|
961
|
+
├─ git checkout -b feat/02-api
|
|
962
|
+
├─ commit 1: "add auth endpoint"
|
|
963
|
+
├─ commit 2: "add validation"
|
|
964
|
+
├─ commit 3: "add error handling"
|
|
965
|
+
├─ commit 4: "add tests"
|
|
966
|
+
└─ Push → CI verde ✓
|
|
967
|
+
└─ SQUASH MERGE → develop (los 4 commits se convierten en 1)
|
|
968
|
+
|
|
969
|
+
RESULTADO:
|
|
970
|
+
✅ Historia lineal en develop (1 commit por paso)
|
|
971
|
+
✅ Commits frecuentes durante desarrollo (buenas prácticas)
|
|
972
|
+
✅ Sin rebase hell
|
|
973
|
+
✅ Sin PRs acumulados
|
|
974
|
+
|
|
975
|
+
IMPORTANTE:
|
|
976
|
+
❌ NO crear una rama nueva por cada commit
|
|
977
|
+
✅ Crear UNA rama por paso completo
|
|
978
|
+
✅ Hacer commits frecuentes dentro de esa rama
|
|
979
|
+
✅ Squash merge al final (N commits → 1 commit limpio)
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Implementación del Flujo
|
|
983
|
+
|
|
984
|
+
```typescript
|
|
985
|
+
class AtomicSequentialFlow {
|
|
986
|
+
async executeStep(step: PlanStep): Promise<void> {
|
|
987
|
+
// 1. Siempre partir de develop actualizado
|
|
988
|
+
await this.git.checkout('develop');
|
|
989
|
+
await this.git.pull('origin', 'develop');
|
|
990
|
+
|
|
991
|
+
// 2. Crear UNA SOLA branch para TODO el paso
|
|
992
|
+
const branchName = `feat/${step.number.toString().padStart(2, '0')}-${step.slug}`;
|
|
993
|
+
await this.git.checkoutBranch(branchName);
|
|
994
|
+
|
|
995
|
+
// 3. Implementar (el dev trabaja aquí)
|
|
996
|
+
console.log(`
|
|
997
|
+
═══════════════════════════════════════════════════════════
|
|
998
|
+
📋 PASO ${step.number}: ${step.title}
|
|
999
|
+
═══════════════════════════════════════════════════════════
|
|
1000
|
+
|
|
1001
|
+
🎯 Objetivo: ${step.objective}
|
|
1002
|
+
|
|
1003
|
+
📁 Archivos a crear/modificar:
|
|
1004
|
+
${step.files.map(f => ` - ${f}`).join('\n')}
|
|
1005
|
+
|
|
1006
|
+
💡 FLUJO DE TRABAJO RECOMENDADO:
|
|
1007
|
+
1. Implementa una parte pequeña
|
|
1008
|
+
2. Haz commit (ej: "add user model")
|
|
1009
|
+
3. Repite hasta completar el paso
|
|
1010
|
+
4. Push TODOS los commits juntos
|
|
1011
|
+
5. CI corre automáticamente
|
|
1012
|
+
6. Si CI pasa → Auto-merge con SQUASH
|
|
1013
|
+
|
|
1014
|
+
📚 Contexto Just-in-Time:
|
|
1015
|
+
${step.learningContext}
|
|
1016
|
+
|
|
1017
|
+
✅ Criterio de "Done":
|
|
1018
|
+
${step.doneCriteria.map(c => ` □ ${c}`).join('\n')}
|
|
1019
|
+
|
|
1020
|
+
═══════════════════════════════════════════════════════════
|
|
1021
|
+
`);
|
|
1022
|
+
|
|
1023
|
+
// 4. Cuando el dev termina y hace push de TODOS los commits...
|
|
1024
|
+
// CI corre automáticamente
|
|
1025
|
+
|
|
1026
|
+
// 5. Si CI pasa → Auto-merge con SQUASH
|
|
1027
|
+
// Resultado: N commits en la rama → 1 commit en develop
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
async onCIPassed(pr: PullRequest): Promise<void> {
|
|
1031
|
+
// Auto-merge con SQUASH (convierte múltiples commits en uno)
|
|
1032
|
+
await this.github.mergePR(pr.number, {
|
|
1033
|
+
method: 'squash', // ← IMPORTANTE: esto une todos los commits
|
|
1034
|
+
deleteSourceBranch: true
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
console.log(`✅ PR #${pr.number} merged automáticamente (CI pasó)`);
|
|
1038
|
+
console.log(`📦 Commits squashed: ${pr.commits.length} commits → 1 commit en develop`);
|
|
1039
|
+
console.log(`🔄 Listo para el siguiente paso`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### 🚨 Errores Comunes a Evitar
|
|
1045
|
+
|
|
1046
|
+
```bash
|
|
1047
|
+
# ❌ MAL - Crear una rama por cada commit
|
|
1048
|
+
git checkout -b feat/01-add-model
|
|
1049
|
+
git commit -m "add model"
|
|
1050
|
+
git checkout develop
|
|
1051
|
+
git checkout -b feat/02-add-migration # ← NO HACER ESTO
|
|
1052
|
+
git commit -m "add migration"
|
|
1053
|
+
|
|
1054
|
+
# ✅ BIEN - Una rama para TODO el paso
|
|
1055
|
+
git checkout -b feat/01-database-schema
|
|
1056
|
+
git commit -m "add user model"
|
|
1057
|
+
git commit -m "add migration script"
|
|
1058
|
+
git commit -m "add tests"
|
|
1059
|
+
git push # Push todos los commits juntos
|
|
1060
|
+
# → PR se crea automáticamente
|
|
1061
|
+
# → CI pasa
|
|
1062
|
+
# → Squash merge: 3 commits → 1 commit en develop
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
---
|
|
1066
|
+
|
|
1067
|
+
## 🎨 Stack Moderno: Biome + Bun
|
|
1068
|
+
|
|
1069
|
+
### Por qué Biome sobre ESLint/Prettier
|
|
1070
|
+
|
|
1071
|
+
```
|
|
1072
|
+
Benchmark: Linting 10,000 archivos TypeScript
|
|
1073
|
+
|
|
1074
|
+
ESLint + Prettier: 45 segundos
|
|
1075
|
+
Biome: 0.4 segundos ⚡ 100x más rápido
|
|
1076
|
+
|
|
1077
|
+
Configuración:
|
|
1078
|
+
ESLint + Prettier: 2 archivos + 20+ deps
|
|
1079
|
+
Biome: 1 archivo + 1 dep
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
### Setup Inicial de Biome
|
|
1083
|
+
|
|
1084
|
+
```bash
|
|
1085
|
+
# Instalar Biome (una sola dependencia)
|
|
1086
|
+
bun add --dev @biomejs/biome
|
|
1087
|
+
|
|
1088
|
+
# Inicializar config
|
|
1089
|
+
bunx @biomejs/biome init
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### biome.json (Configuración Recomendada)
|
|
1093
|
+
|
|
1094
|
+
```json
|
|
1095
|
+
{
|
|
1096
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1097
|
+
"organizeImports": {
|
|
1098
|
+
"enabled": true
|
|
1099
|
+
},
|
|
1100
|
+
"linter": {
|
|
1101
|
+
"enabled": true,
|
|
1102
|
+
"rules": {
|
|
1103
|
+
"recommended": true,
|
|
1104
|
+
"style": {
|
|
1105
|
+
"useConst": "error",
|
|
1106
|
+
"noVar": "error"
|
|
1107
|
+
},
|
|
1108
|
+
"suspicious": {
|
|
1109
|
+
"noExplicitAny": "warn"
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
"formatter": {
|
|
1114
|
+
"enabled": true,
|
|
1115
|
+
"indentStyle": "space",
|
|
1116
|
+
"indentWidth": 2,
|
|
1117
|
+
"lineWidth": 100
|
|
1118
|
+
},
|
|
1119
|
+
"javascript": {
|
|
1120
|
+
"formatter": {
|
|
1121
|
+
"quoteStyle": "single",
|
|
1122
|
+
"trailingCommas": "es5"
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Por qué Bun sobre npm/yarn/pnpm
|
|
1129
|
+
|
|
1130
|
+
```
|
|
1131
|
+
Benchmark: Install 300 packages
|
|
1132
|
+
|
|
1133
|
+
npm: 45 segundos
|
|
1134
|
+
yarn: 38 segundos
|
|
1135
|
+
pnpm: 22 segundos
|
|
1136
|
+
bun: 4 segundos ⚡ 10x más rápido
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
**Ventajas adicionales:**
|
|
1140
|
+
- Runtime de JavaScript (reemplaza Node.js)
|
|
1141
|
+
- Bundler integrado (reemplaza Webpack/Vite)
|
|
1142
|
+
- Test runner integrado (reemplaza Jest/Vitest)
|
|
1143
|
+
- TypeScript sin configuración
|
|
1144
|
+
|
|
1145
|
+
### package.json con Biome + Bun
|
|
1146
|
+
|
|
1147
|
+
```json
|
|
1148
|
+
{
|
|
1149
|
+
"name": "my-project",
|
|
1150
|
+
"type": "module",
|
|
1151
|
+
"scripts": {
|
|
1152
|
+
"dev": "bun run --hot src/index.ts",
|
|
1153
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
1154
|
+
"test": "bun test",
|
|
1155
|
+
"lint": "biome check .",
|
|
1156
|
+
"format": "biome format --write .",
|
|
1157
|
+
"check": "biome ci ."
|
|
1158
|
+
},
|
|
1159
|
+
"devDependencies": {
|
|
1160
|
+
"@biomejs/biome": "^1.9.4",
|
|
1161
|
+
"@types/bun": "latest"
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## ☕ Stack Moderno: Java + Gradle + Kotlin
|
|
1169
|
+
|
|
1170
|
+
### Por qué Gradle + Kotlin sobre Maven + Java
|
|
1171
|
+
|
|
1172
|
+
```
|
|
1173
|
+
Benchmark: Build proyecto con 50 módulos
|
|
1174
|
+
|
|
1175
|
+
Maven (Java): 2:30 min
|
|
1176
|
+
Gradle (Java): 45 seg
|
|
1177
|
+
Gradle (Kotlin): 40 seg + DSL type-safe ⚡ 3x más rápido
|
|
1178
|
+
|
|
1179
|
+
Configuración:
|
|
1180
|
+
Maven: XML verboso (pom.xml)
|
|
1181
|
+
Gradle: Kotlin DSL conciso (build.gradle.kts)
|
|
1182
|
+
|
|
1183
|
+
Features:
|
|
1184
|
+
Maven: Limitado a XML
|
|
1185
|
+
Gradle: Programable, incremental builds, build cache
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
### Setup Inicial de Gradle + Kotlin
|
|
1189
|
+
|
|
1190
|
+
```bash
|
|
1191
|
+
# Inicializar proyecto Gradle
|
|
1192
|
+
gradle init --type kotlin-application --dsl kotlin
|
|
1193
|
+
|
|
1194
|
+
# O manualmente
|
|
1195
|
+
mkdir -p src/main/kotlin src/test/kotlin
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
### build.gradle.kts (Configuración Recomendada)
|
|
1199
|
+
|
|
1200
|
+
```kotlin
|
|
1201
|
+
plugins {
|
|
1202
|
+
kotlin("jvm") version "2.2.0"
|
|
1203
|
+
kotlin("plugin.spring") version "2.2.0"
|
|
1204
|
+
id("org.springframework.boot") version "4.0.1"
|
|
1205
|
+
id("io.spring.dependency-management") version "1.1.7"
|
|
1206
|
+
id("com.diffplug.spotless") version "7.0.0"
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
group = "com.example"
|
|
1210
|
+
version = "0.0.1-SNAPSHOT"
|
|
1211
|
+
|
|
1212
|
+
java {
|
|
1213
|
+
sourceCompatibility = JavaVersion.VERSION_25
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
repositories {
|
|
1217
|
+
mavenCentral()
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
dependencies {
|
|
1221
|
+
// Spring Boot 4.x
|
|
1222
|
+
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
1223
|
+
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
|
1224
|
+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
|
1225
|
+
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
|
1226
|
+
|
|
1227
|
+
// Database
|
|
1228
|
+
runtimeOnly("org.postgresql:postgresql")
|
|
1229
|
+
|
|
1230
|
+
// Test
|
|
1231
|
+
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
|
1232
|
+
testImplementation("io.mockk:mockk:1.13.13")
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
kotlin {
|
|
1236
|
+
compilerOptions {
|
|
1237
|
+
freeCompilerArgs.add("-Xjsr305=strict")
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
tasks.withType<Test> {
|
|
1242
|
+
useJUnitPlatform()
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Spotless configuration (formatter)
|
|
1246
|
+
spotless {
|
|
1247
|
+
kotlin {
|
|
1248
|
+
target("**/*.kt")
|
|
1249
|
+
ktlint("1.5.0")
|
|
1250
|
+
.editorConfigOverride(
|
|
1251
|
+
mapOf(
|
|
1252
|
+
"indent_size" to "4",
|
|
1253
|
+
"max_line_length" to "120"
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
}
|
|
1257
|
+
kotlinGradle {
|
|
1258
|
+
target("**/*.gradle.kts")
|
|
1259
|
+
ktlint("1.5.0")
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Task para verificar formato
|
|
1264
|
+
tasks.register("check") {
|
|
1265
|
+
dependsOn("spotlessCheck", "test")
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
### gradle.properties
|
|
1270
|
+
|
|
1271
|
+
```properties
|
|
1272
|
+
# Gradle daemon para builds más rápidos
|
|
1273
|
+
org.gradle.daemon=true
|
|
1274
|
+
org.gradle.parallel=true
|
|
1275
|
+
org.gradle.caching=true
|
|
1276
|
+
|
|
1277
|
+
# Kotlin
|
|
1278
|
+
kotlin.code.style=official
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
### Comandos Gradle
|
|
1282
|
+
|
|
1283
|
+
```bash
|
|
1284
|
+
# Build
|
|
1285
|
+
./gradlew build
|
|
1286
|
+
|
|
1287
|
+
# Run (Spring Boot)
|
|
1288
|
+
./gradlew bootRun
|
|
1289
|
+
|
|
1290
|
+
# Tests
|
|
1291
|
+
./gradlew test
|
|
1292
|
+
|
|
1293
|
+
# Format code (Spotless)
|
|
1294
|
+
./gradlew spotlessApply
|
|
1295
|
+
|
|
1296
|
+
# Check format
|
|
1297
|
+
./gradlew spotlessCheck
|
|
1298
|
+
|
|
1299
|
+
# Limpiar + Build
|
|
1300
|
+
./gradlew clean build
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
### Estructura de Proyecto Spring Boot + Kotlin
|
|
1304
|
+
|
|
1305
|
+
```kotlin
|
|
1306
|
+
// src/main/kotlin/com/example/api/Application.kt
|
|
1307
|
+
package com.example.api
|
|
1308
|
+
|
|
1309
|
+
import org.springframework.boot.autoconfigure.SpringBootApplication
|
|
1310
|
+
import org.springframework.boot.runApplication
|
|
1311
|
+
|
|
1312
|
+
@SpringBootApplication
|
|
1313
|
+
class Application
|
|
1314
|
+
|
|
1315
|
+
fun main(args: Array<String>) {
|
|
1316
|
+
runApplication<Application>(*args)
|
|
1317
|
+
}
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
```kotlin
|
|
1321
|
+
// src/main/kotlin/com/example/api/controller/HealthController.kt
|
|
1322
|
+
package com.example.api.controller
|
|
1323
|
+
|
|
1324
|
+
import org.springframework.web.bind.annotation.GetMapping
|
|
1325
|
+
import org.springframework.web.bind.annotation.RequestMapping
|
|
1326
|
+
import org.springframework.web.bind.annotation.RestController
|
|
1327
|
+
|
|
1328
|
+
@RestController
|
|
1329
|
+
@RequestMapping("/health")
|
|
1330
|
+
class HealthController {
|
|
1331
|
+
|
|
1332
|
+
data class HealthResponse(val status: String, val version: String = "0.1.0")
|
|
1333
|
+
|
|
1334
|
+
@GetMapping
|
|
1335
|
+
fun health() = HealthResponse(status = "ok")
|
|
1336
|
+
}
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
### Dockerfile para Java + Gradle
|
|
1340
|
+
|
|
1341
|
+
```dockerfile
|
|
1342
|
+
# Multi-stage build para optimizar tamaño
|
|
1343
|
+
FROM gradle:8.12-jdk25 AS builder
|
|
1344
|
+
WORKDIR /app
|
|
1345
|
+
|
|
1346
|
+
# Copiar solo archivos de configuración primero (para cachear deps)
|
|
1347
|
+
COPY build.gradle.kts settings.gradle.kts gradle.properties ./
|
|
1348
|
+
COPY gradle ./gradle
|
|
1349
|
+
|
|
1350
|
+
# Descargar dependencias (se cachea si no cambian)
|
|
1351
|
+
RUN gradle dependencies --no-daemon
|
|
1352
|
+
|
|
1353
|
+
# Copiar código fuente
|
|
1354
|
+
COPY src ./src
|
|
1355
|
+
|
|
1356
|
+
# Build (sin tests para acelerar)
|
|
1357
|
+
RUN gradle build -x test --no-daemon
|
|
1358
|
+
|
|
1359
|
+
# Stage final - solo runtime
|
|
1360
|
+
FROM eclipse-temurin:25-jre-alpine
|
|
1361
|
+
WORKDIR /app
|
|
1362
|
+
|
|
1363
|
+
# Copiar jar construido
|
|
1364
|
+
COPY --from=builder /app/build/libs/*.jar app.jar
|
|
1365
|
+
|
|
1366
|
+
# Usuario no-root
|
|
1367
|
+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
1368
|
+
USER appuser
|
|
1369
|
+
|
|
1370
|
+
# Exponer puerto
|
|
1371
|
+
EXPOSE 8080
|
|
1372
|
+
|
|
1373
|
+
# Comando de inicio
|
|
1374
|
+
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
---
|
|
1378
|
+
|
|
1379
|
+
## 🐹 Stack Moderno: Go + Air + golangci-lint
|
|
1380
|
+
|
|
1381
|
+
### Por qué Go 1.25+ con Herramientas Modernas
|
|
1382
|
+
|
|
1383
|
+
```
|
|
1384
|
+
Ventajas de Go 1.25+:
|
|
1385
|
+
✅ Generics nativos (desde 1.18)
|
|
1386
|
+
✅ JSON v2 experimental (encoding/json/v2)
|
|
1387
|
+
✅ DWARF 5 debug info (binarios más pequeños)
|
|
1388
|
+
✅ Mejor manejo de errores
|
|
1389
|
+
✅ Performance optimizado (2-3% más rápido que 1.24)
|
|
1390
|
+
✅ Tooling mejorado (go doc -http, tool directives)
|
|
1391
|
+
|
|
1392
|
+
golangci-lint vs linters individuales:
|
|
1393
|
+
- Incluye 50+ linters en uno
|
|
1394
|
+
- Configurable por proyecto
|
|
1395
|
+
- 5x más rápido que correr linters separados
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
### Setup Inicial de Go Project
|
|
1399
|
+
|
|
1400
|
+
```bash
|
|
1401
|
+
# Inicializar módulo Go
|
|
1402
|
+
go mod init github.com/usuario/mi-api
|
|
1403
|
+
|
|
1404
|
+
# Instalar Air (hot reload)
|
|
1405
|
+
go install github.com/cosmtrek/air@latest
|
|
1406
|
+
|
|
1407
|
+
# Instalar golangci-lint
|
|
1408
|
+
# macOS/Linux
|
|
1409
|
+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
|
1410
|
+
|
|
1411
|
+
# O con Homebrew
|
|
1412
|
+
brew install golangci-lint
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
### go.mod
|
|
1416
|
+
|
|
1417
|
+
```go
|
|
1418
|
+
module github.com/usuario/mi-api
|
|
1419
|
+
|
|
1420
|
+
go 1.25
|
|
1421
|
+
|
|
1422
|
+
require (
|
|
1423
|
+
github.com/gin-gonic/gin v1.10.0
|
|
1424
|
+
github.com/lib/pq v1.10.9
|
|
1425
|
+
github.com/golang-jwt/jwt/v5 v5.2.1
|
|
1426
|
+
)
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
### .golangci.yml (Configuración de Linter)
|
|
1430
|
+
|
|
1431
|
+
```yaml
|
|
1432
|
+
run:
|
|
1433
|
+
timeout: 5m
|
|
1434
|
+
modules-download-mode: readonly
|
|
1435
|
+
|
|
1436
|
+
linters:
|
|
1437
|
+
enable:
|
|
1438
|
+
- errcheck # Verifica errores no manejados
|
|
1439
|
+
- gosimple # Simplificaciones de código
|
|
1440
|
+
- govet # Análisis estático
|
|
1441
|
+
- ineffassign # Asignaciones ineficientes
|
|
1442
|
+
- staticcheck # Análisis avanzado
|
|
1443
|
+
- unused # Código no usado
|
|
1444
|
+
- gofmt # Formato
|
|
1445
|
+
- goimports # Imports organizados
|
|
1446
|
+
- revive # Reemplazo rápido de golint
|
|
1447
|
+
- misspell # Errores de ortografía
|
|
1448
|
+
- gocritic # Sugerencias de mejora
|
|
1449
|
+
|
|
1450
|
+
linters-settings:
|
|
1451
|
+
gofmt:
|
|
1452
|
+
simplify: true
|
|
1453
|
+
goimports:
|
|
1454
|
+
local-prefixes: github.com/usuario/mi-api
|
|
1455
|
+
revive:
|
|
1456
|
+
rules:
|
|
1457
|
+
- name: exported
|
|
1458
|
+
severity: warning
|
|
1459
|
+
- name: var-naming
|
|
1460
|
+
severity: warning
|
|
1461
|
+
|
|
1462
|
+
issues:
|
|
1463
|
+
exclude-rules:
|
|
1464
|
+
- path: _test\.go
|
|
1465
|
+
linters:
|
|
1466
|
+
- errcheck
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
### .air.toml (Hot Reload Configuration)
|
|
1470
|
+
|
|
1471
|
+
```toml
|
|
1472
|
+
root = "."
|
|
1473
|
+
testdata_dir = "testdata"
|
|
1474
|
+
tmp_dir = "tmp"
|
|
1475
|
+
|
|
1476
|
+
[build]
|
|
1477
|
+
args_bin = []
|
|
1478
|
+
bin = "./tmp/main"
|
|
1479
|
+
cmd = "go build -o ./tmp/main ."
|
|
1480
|
+
delay = 1000
|
|
1481
|
+
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
|
1482
|
+
exclude_file = []
|
|
1483
|
+
exclude_regex = ["_test.go"]
|
|
1484
|
+
exclude_unchanged = false
|
|
1485
|
+
follow_symlink = false
|
|
1486
|
+
full_bin = ""
|
|
1487
|
+
include_dir = []
|
|
1488
|
+
include_ext = ["go", "tpl", "tmpl", "html"]
|
|
1489
|
+
include_file = []
|
|
1490
|
+
kill_delay = "0s"
|
|
1491
|
+
log = "build-errors.log"
|
|
1492
|
+
poll = false
|
|
1493
|
+
poll_interval = 0
|
|
1494
|
+
rerun = false
|
|
1495
|
+
rerun_delay = 500
|
|
1496
|
+
send_interrupt = false
|
|
1497
|
+
stop_on_error = false
|
|
1498
|
+
|
|
1499
|
+
[color]
|
|
1500
|
+
app = ""
|
|
1501
|
+
build = "yellow"
|
|
1502
|
+
main = "magenta"
|
|
1503
|
+
runner = "green"
|
|
1504
|
+
watcher = "cyan"
|
|
1505
|
+
|
|
1506
|
+
[log]
|
|
1507
|
+
main_only = false
|
|
1508
|
+
time = false
|
|
1509
|
+
|
|
1510
|
+
[misc]
|
|
1511
|
+
clean_on_exit = false
|
|
1512
|
+
|
|
1513
|
+
[screen]
|
|
1514
|
+
clear_on_rebuild = false
|
|
1515
|
+
keep_scroll = true
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
### Estructura de Proyecto Go con Gin
|
|
1519
|
+
|
|
1520
|
+
```
|
|
1521
|
+
my-api/
|
|
1522
|
+
├── cmd/
|
|
1523
|
+
│ └── api/
|
|
1524
|
+
│ └── main.go # Entry point
|
|
1525
|
+
├── internal/
|
|
1526
|
+
│ ├── handlers/ # HTTP handlers
|
|
1527
|
+
│ │ └── health.go
|
|
1528
|
+
│ ├── models/ # Data models
|
|
1529
|
+
│ └── middleware/ # Middlewares
|
|
1530
|
+
├── pkg/ # Código reutilizable
|
|
1531
|
+
├── go.mod
|
|
1532
|
+
├── go.sum
|
|
1533
|
+
├── .air.toml
|
|
1534
|
+
├── .golangci.yml
|
|
1535
|
+
├── Dockerfile
|
|
1536
|
+
└── docker-compose.yml
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
### main.go (Gin Framework)
|
|
1540
|
+
|
|
1541
|
+
```go
|
|
1542
|
+
// cmd/api/main.go
|
|
1543
|
+
package main
|
|
1544
|
+
|
|
1545
|
+
import (
|
|
1546
|
+
"log"
|
|
1547
|
+
"github.com/gin-gonic/gin"
|
|
1548
|
+
"github.com/usuario/mi-api/internal/handlers"
|
|
1549
|
+
)
|
|
1550
|
+
|
|
1551
|
+
func main() {
|
|
1552
|
+
r := gin.Default()
|
|
1553
|
+
|
|
1554
|
+
// Health endpoint
|
|
1555
|
+
r.GET("/health", handlers.Health)
|
|
1556
|
+
|
|
1557
|
+
// Iniciar servidor
|
|
1558
|
+
if err := r.Run(":8080"); err != nil {
|
|
1559
|
+
log.Fatal("Failed to start server:", err)
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
```
|
|
1563
|
+
|
|
1564
|
+
```go
|
|
1565
|
+
// internal/handlers/health.go
|
|
1566
|
+
package handlers
|
|
1567
|
+
|
|
1568
|
+
import (
|
|
1569
|
+
"net/http"
|
|
1570
|
+
"github.com/gin-gonic/gin"
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
type HealthResponse struct {
|
|
1574
|
+
Status string `json:"status"`
|
|
1575
|
+
Version string `json:"version"`
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
func Health(c *gin.Context) {
|
|
1579
|
+
c.JSON(http.StatusOK, HealthResponse{
|
|
1580
|
+
Status: "ok",
|
|
1581
|
+
Version: "0.1.0",
|
|
1582
|
+
})
|
|
1583
|
+
}
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
### Comandos Go
|
|
1587
|
+
|
|
1588
|
+
```bash
|
|
1589
|
+
# Desarrollo con hot reload
|
|
1590
|
+
air
|
|
1591
|
+
|
|
1592
|
+
# O sin air
|
|
1593
|
+
go run cmd/api/main.go
|
|
1594
|
+
|
|
1595
|
+
# Tests
|
|
1596
|
+
go test ./...
|
|
1597
|
+
|
|
1598
|
+
# Tests con cobertura
|
|
1599
|
+
go test -cover ./...
|
|
1600
|
+
|
|
1601
|
+
# Build
|
|
1602
|
+
go build -o bin/api cmd/api/main.go
|
|
1603
|
+
|
|
1604
|
+
# Lint
|
|
1605
|
+
golangci-lint run
|
|
1606
|
+
|
|
1607
|
+
# Format
|
|
1608
|
+
go fmt ./...
|
|
1609
|
+
|
|
1610
|
+
# Organizar imports
|
|
1611
|
+
goimports -w .
|
|
1612
|
+
|
|
1613
|
+
# Ver dependencias
|
|
1614
|
+
go mod tidy
|
|
1615
|
+
go mod vendor # opcional: para vendor/ local
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
### Dockerfile para Go (Multi-stage)
|
|
1619
|
+
|
|
1620
|
+
```dockerfile
|
|
1621
|
+
# Builder stage
|
|
1622
|
+
FROM golang:1.25-alpine AS builder
|
|
1623
|
+
|
|
1624
|
+
# Instalar dependencias de build
|
|
1625
|
+
RUN apk add --no-cache git
|
|
1626
|
+
|
|
1627
|
+
WORKDIR /app
|
|
1628
|
+
|
|
1629
|
+
# Copiar go.mod y go.sum primero (para cachear deps)
|
|
1630
|
+
COPY go.mod go.sum ./
|
|
1631
|
+
RUN go mod download
|
|
1632
|
+
|
|
1633
|
+
# Copiar código fuente
|
|
1634
|
+
COPY . .
|
|
1635
|
+
|
|
1636
|
+
# Build (static binary)
|
|
1637
|
+
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/api
|
|
1638
|
+
|
|
1639
|
+
# Final stage - solo el binario
|
|
1640
|
+
FROM alpine:3.21
|
|
1641
|
+
|
|
1642
|
+
# Certificados SSL para requests HTTPS
|
|
1643
|
+
RUN apk --no-cache add ca-certificates
|
|
1644
|
+
|
|
1645
|
+
WORKDIR /root/
|
|
1646
|
+
|
|
1647
|
+
# Copiar binario
|
|
1648
|
+
COPY --from=builder /app/main .
|
|
1649
|
+
|
|
1650
|
+
# Usuario no-root
|
|
1651
|
+
RUN adduser -D appuser
|
|
1652
|
+
USER appuser
|
|
1653
|
+
|
|
1654
|
+
# Exponer puerto
|
|
1655
|
+
EXPOSE 8080
|
|
1656
|
+
|
|
1657
|
+
# Comando
|
|
1658
|
+
CMD ["./main"]
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
---
|
|
1662
|
+
|
|
1663
|
+
## 📦 Templates de Proyecto Desde Cero (Actualizados)
|
|
1664
|
+
|
|
1665
|
+
### 1. API REST con TypeScript + Bun + Biome
|
|
1666
|
+
|
|
1667
|
+
[Mantener el template existente de Bun]
|
|
1668
|
+
|
|
1669
|
+
---
|
|
1670
|
+
|
|
1671
|
+
### 2. API REST con Java + Gradle + Kotlin
|
|
1672
|
+
|
|
1673
|
+
```bash
|
|
1674
|
+
my-api-java/
|
|
1675
|
+
├── src/
|
|
1676
|
+
│ ├── main/
|
|
1677
|
+
│ │ ├── kotlin/
|
|
1678
|
+
│ │ │ └── com/example/api/
|
|
1679
|
+
│ │ │ ├── Application.kt
|
|
1680
|
+
│ │ │ ├── controller/
|
|
1681
|
+
│ │ │ │ └── HealthController.kt
|
|
1682
|
+
│ │ │ └── config/
|
|
1683
|
+
│ │ └── resources/
|
|
1684
|
+
│ │ └── application.yml
|
|
1685
|
+
│ └── test/
|
|
1686
|
+
│ └── kotlin/
|
|
1687
|
+
│ └── com/example/api/
|
|
1688
|
+
│ └── HealthControllerTest.kt
|
|
1689
|
+
├── build.gradle.kts
|
|
1690
|
+
├── settings.gradle.kts
|
|
1691
|
+
├── gradle.properties
|
|
1692
|
+
├── Dockerfile
|
|
1693
|
+
└── docker-compose.yml
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
#### settings.gradle.kts
|
|
1697
|
+
|
|
1698
|
+
```kotlin
|
|
1699
|
+
rootProject.name = "my-api"
|
|
1700
|
+
```
|
|
1701
|
+
|
|
1702
|
+
#### application.yml
|
|
1703
|
+
|
|
1704
|
+
```yaml
|
|
1705
|
+
spring:
|
|
1706
|
+
application:
|
|
1707
|
+
name: my-api
|
|
1708
|
+
datasource:
|
|
1709
|
+
url: jdbc:postgresql://localhost:5432/mydb
|
|
1710
|
+
username: user
|
|
1711
|
+
password: pass
|
|
1712
|
+
jpa:
|
|
1713
|
+
hibernate:
|
|
1714
|
+
ddl-auto: validate
|
|
1715
|
+
show-sql: true
|
|
1716
|
+
properties:
|
|
1717
|
+
hibernate:
|
|
1718
|
+
dialect: org.hibernate.dialect.PostgreSQLDialect
|
|
1719
|
+
|
|
1720
|
+
server:
|
|
1721
|
+
port: 8080
|
|
1722
|
+
|
|
1723
|
+
logging:
|
|
1724
|
+
level:
|
|
1725
|
+
root: INFO
|
|
1726
|
+
com.example.api: DEBUG
|
|
1727
|
+
```
|
|
1728
|
+
|
|
1729
|
+
#### HealthControllerTest.kt
|
|
1730
|
+
|
|
1731
|
+
```kotlin
|
|
1732
|
+
package com.example.api
|
|
1733
|
+
|
|
1734
|
+
import org.junit.jupiter.api.Test
|
|
1735
|
+
import org.springframework.beans.factory.annotation.Autowired
|
|
1736
|
+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
|
1737
|
+
import org.springframework.boot.test.context.SpringBootTest
|
|
1738
|
+
import org.springframework.test.web.servlet.MockMvc
|
|
1739
|
+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
|
1740
|
+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
|
1741
|
+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|
1742
|
+
|
|
1743
|
+
@SpringBootTest
|
|
1744
|
+
@AutoConfigureMockMvc
|
|
1745
|
+
class HealthControllerTest {
|
|
1746
|
+
|
|
1747
|
+
@Autowired
|
|
1748
|
+
private lateinit var mockMvc: MockMvc
|
|
1749
|
+
|
|
1750
|
+
@Test
|
|
1751
|
+
fun `health endpoint should return ok`() {
|
|
1752
|
+
mockMvc.perform(get("/health"))
|
|
1753
|
+
.andExpect(status().isOk)
|
|
1754
|
+
.andExpect(jsonPath("$.status").value("ok"))
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
#### docker-compose.yml
|
|
1760
|
+
|
|
1761
|
+
```yaml
|
|
1762
|
+
version: '3.8'
|
|
1763
|
+
|
|
1764
|
+
services:
|
|
1765
|
+
api:
|
|
1766
|
+
build:
|
|
1767
|
+
context: .
|
|
1768
|
+
dockerfile: Dockerfile
|
|
1769
|
+
ports:
|
|
1770
|
+
- "8080:8080"
|
|
1771
|
+
environment:
|
|
1772
|
+
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb
|
|
1773
|
+
- SPRING_DATASOURCE_USERNAME=user
|
|
1774
|
+
- SPRING_DATASOURCE_PASSWORD=pass
|
|
1775
|
+
depends_on:
|
|
1776
|
+
- db
|
|
1777
|
+
|
|
1778
|
+
db:
|
|
1779
|
+
image: postgres:16-alpine
|
|
1780
|
+
ports:
|
|
1781
|
+
- "5432:5432"
|
|
1782
|
+
volumes:
|
|
1783
|
+
- postgres-data:/var/lib/postgresql/data
|
|
1784
|
+
environment:
|
|
1785
|
+
- POSTGRES_USER=user
|
|
1786
|
+
- POSTGRES_PASSWORD=pass
|
|
1787
|
+
- POSTGRES_DB=mydb
|
|
1788
|
+
|
|
1789
|
+
volumes:
|
|
1790
|
+
postgres-data:
|
|
1791
|
+
```
|
|
1792
|
+
|
|
1793
|
+
---
|
|
1794
|
+
|
|
1795
|
+
### 3. API REST con Go + Gin + Air
|
|
1796
|
+
|
|
1797
|
+
```bash
|
|
1798
|
+
my-api-go/
|
|
1799
|
+
├── cmd/
|
|
1800
|
+
│ └── api/
|
|
1801
|
+
│ └── main.go
|
|
1802
|
+
├── internal/
|
|
1803
|
+
│ ├── handlers/
|
|
1804
|
+
│ │ ├── health.go
|
|
1805
|
+
│ │ └── health_test.go
|
|
1806
|
+
│ ├── models/
|
|
1807
|
+
│ └── middleware/
|
|
1808
|
+
├── pkg/
|
|
1809
|
+
├── go.mod
|
|
1810
|
+
├── go.sum
|
|
1811
|
+
├── .air.toml
|
|
1812
|
+
├── .golangci.yml
|
|
1813
|
+
├── Dockerfile
|
|
1814
|
+
└── docker-compose.yml
|
|
1815
|
+
```
|
|
1816
|
+
|
|
1817
|
+
#### go.mod (completo)
|
|
1818
|
+
|
|
1819
|
+
```go
|
|
1820
|
+
module github.com/usuario/mi-api
|
|
1821
|
+
|
|
1822
|
+
go 1.22
|
|
1823
|
+
|
|
1824
|
+
require (
|
|
1825
|
+
github.com/gin-gonic/gin v1.9.1
|
|
1826
|
+
github.com/lib/pq v1.10.9
|
|
1827
|
+
github.com/stretchr/testify v1.8.4
|
|
1828
|
+
)
|
|
1829
|
+
```
|
|
1830
|
+
|
|
1831
|
+
#### cmd/api/main.go (con database)
|
|
1832
|
+
|
|
1833
|
+
```go
|
|
1834
|
+
package main
|
|
1835
|
+
|
|
1836
|
+
import (
|
|
1837
|
+
"database/sql"
|
|
1838
|
+
"log"
|
|
1839
|
+
"os"
|
|
1840
|
+
|
|
1841
|
+
"github.com/gin-gonic/gin"
|
|
1842
|
+
_ "github.com/lib/pq"
|
|
1843
|
+
|
|
1844
|
+
"github.com/usuario/mi-api/internal/handlers"
|
|
1845
|
+
)
|
|
1846
|
+
|
|
1847
|
+
func main() {
|
|
1848
|
+
// Database connection
|
|
1849
|
+
dbURL := os.Getenv("DATABASE_URL")
|
|
1850
|
+
if dbURL == "" {
|
|
1851
|
+
dbURL = "postgres://user:pass@localhost:5432/mydb?sslmode=disable"
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
db, err := sql.Open("postgres", dbURL)
|
|
1855
|
+
if err != nil {
|
|
1856
|
+
log.Fatal("Failed to connect to database:", err)
|
|
1857
|
+
}
|
|
1858
|
+
defer db.Close()
|
|
1859
|
+
|
|
1860
|
+
// Verificar conexión
|
|
1861
|
+
if err := db.Ping(); err != nil {
|
|
1862
|
+
log.Fatal("Failed to ping database:", err)
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
log.Println("Connected to database")
|
|
1866
|
+
|
|
1867
|
+
// Gin router
|
|
1868
|
+
r := gin.Default()
|
|
1869
|
+
|
|
1870
|
+
// Health endpoint
|
|
1871
|
+
r.GET("/health", handlers.Health)
|
|
1872
|
+
|
|
1873
|
+
// Iniciar servidor
|
|
1874
|
+
port := os.Getenv("PORT")
|
|
1875
|
+
if port == "" {
|
|
1876
|
+
port = "8080"
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if err := r.Run(":" + port); err != nil {
|
|
1880
|
+
log.Fatal("Failed to start server:", err)
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
```
|
|
1884
|
+
|
|
1885
|
+
#### internal/handlers/health_test.go
|
|
1886
|
+
|
|
1887
|
+
```go
|
|
1888
|
+
package handlers
|
|
1889
|
+
|
|
1890
|
+
import (
|
|
1891
|
+
"net/http"
|
|
1892
|
+
"net/http/httptest"
|
|
1893
|
+
"testing"
|
|
1894
|
+
|
|
1895
|
+
"github.com/gin-gonic/gin"
|
|
1896
|
+
"github.com/stretchr/testify/assert"
|
|
1897
|
+
)
|
|
1898
|
+
|
|
1899
|
+
func TestHealth(t *testing.T) {
|
|
1900
|
+
gin.SetMode(gin.TestMode)
|
|
1901
|
+
|
|
1902
|
+
w := httptest.NewRecorder()
|
|
1903
|
+
c, _ := gin.CreateTestContext(w)
|
|
1904
|
+
|
|
1905
|
+
Health(c)
|
|
1906
|
+
|
|
1907
|
+
assert.Equal(t, http.StatusOK, w.Code)
|
|
1908
|
+
assert.Contains(t, w.Body.String(), `"status":"ok"`)
|
|
1909
|
+
}
|
|
1910
|
+
```
|
|
1911
|
+
|
|
1912
|
+
#### docker-compose.yml
|
|
1913
|
+
|
|
1914
|
+
```yaml
|
|
1915
|
+
version: '3.8'
|
|
1916
|
+
|
|
1917
|
+
services:
|
|
1918
|
+
api:
|
|
1919
|
+
build:
|
|
1920
|
+
context: .
|
|
1921
|
+
dockerfile: Dockerfile
|
|
1922
|
+
target: development # Para dev con Air
|
|
1923
|
+
ports:
|
|
1924
|
+
- "8080:8080"
|
|
1925
|
+
volumes:
|
|
1926
|
+
- .:/app
|
|
1927
|
+
- go-modules:/go/pkg/mod
|
|
1928
|
+
environment:
|
|
1929
|
+
- DATABASE_URL=postgres://user:pass@db:5432/mydb?sslmode=disable
|
|
1930
|
+
depends_on:
|
|
1931
|
+
- db
|
|
1932
|
+
|
|
1933
|
+
db:
|
|
1934
|
+
image: postgres:16-alpine
|
|
1935
|
+
ports:
|
|
1936
|
+
- "5432:5432"
|
|
1937
|
+
volumes:
|
|
1938
|
+
- postgres-data:/var/lib/postgresql/data
|
|
1939
|
+
environment:
|
|
1940
|
+
- POSTGRES_USER=user
|
|
1941
|
+
- POSTGRES_PASSWORD=pass
|
|
1942
|
+
- POSTGRES_DB=mydb
|
|
1943
|
+
|
|
1944
|
+
volumes:
|
|
1945
|
+
postgres-data:
|
|
1946
|
+
go-modules:
|
|
1947
|
+
```
|
|
1948
|
+
|
|
1949
|
+
#### Dockerfile (Go con Air para dev)
|
|
1950
|
+
|
|
1951
|
+
```dockerfile
|
|
1952
|
+
# Development stage con Air
|
|
1953
|
+
FROM golang:1.25-alpine AS development
|
|
1954
|
+
|
|
1955
|
+
RUN apk add --no-cache git
|
|
1956
|
+
|
|
1957
|
+
# Instalar Air
|
|
1958
|
+
RUN go install github.com/air-verse/air@latest
|
|
1959
|
+
|
|
1960
|
+
WORKDIR /app
|
|
1961
|
+
|
|
1962
|
+
COPY go.mod go.sum ./
|
|
1963
|
+
RUN go mod download
|
|
1964
|
+
|
|
1965
|
+
COPY . .
|
|
1966
|
+
|
|
1967
|
+
CMD ["air", "-c", ".air.toml"]
|
|
1968
|
+
|
|
1969
|
+
# Production stage
|
|
1970
|
+
FROM golang:1.25-alpine AS builder
|
|
1971
|
+
|
|
1972
|
+
WORKDIR /app
|
|
1973
|
+
|
|
1974
|
+
COPY go.mod go.sum ./
|
|
1975
|
+
RUN go mod download
|
|
1976
|
+
|
|
1977
|
+
COPY . .
|
|
1978
|
+
|
|
1979
|
+
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/api
|
|
1980
|
+
|
|
1981
|
+
FROM alpine:3.21 AS production
|
|
1982
|
+
|
|
1983
|
+
RUN apk --no-cache add ca-certificates
|
|
1984
|
+
|
|
1985
|
+
WORKDIR /root/
|
|
1986
|
+
|
|
1987
|
+
COPY --from=builder /app/main .
|
|
1988
|
+
|
|
1989
|
+
RUN adduser -D appuser
|
|
1990
|
+
USER appuser
|
|
1991
|
+
|
|
1992
|
+
EXPOSE 8080
|
|
1993
|
+
|
|
1994
|
+
CMD ["./main"]
|
|
1995
|
+
```
|
|
1996
|
+
|
|
1997
|
+
### 1. API REST (Node.js + Bun + Biome)
|
|
1998
|
+
|
|
1999
|
+
```bash
|
|
2000
|
+
# Estructura inicial
|
|
2001
|
+
my-api/
|
|
2002
|
+
├── src/
|
|
2003
|
+
│ ├── index.ts
|
|
2004
|
+
│ ├── routes/
|
|
2005
|
+
│ │ └── health.ts
|
|
2006
|
+
│ ├── middleware/
|
|
2007
|
+
│ └── types/
|
|
2008
|
+
├── tests/
|
|
2009
|
+
│ └── health.test.ts
|
|
2010
|
+
├── package.json
|
|
2011
|
+
├── biome.json
|
|
2012
|
+
├── Dockerfile
|
|
2013
|
+
├── docker-compose.yml
|
|
2014
|
+
└── .gitignore
|
|
2015
|
+
```
|
|
2016
|
+
|
|
2017
|
+
#### package.json
|
|
2018
|
+
|
|
2019
|
+
```json
|
|
2020
|
+
{
|
|
2021
|
+
"name": "my-api",
|
|
2022
|
+
"type": "module",
|
|
2023
|
+
"scripts": {
|
|
2024
|
+
"dev": "bun run --hot src/index.ts",
|
|
2025
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
2026
|
+
"start": "NODE_ENV=production bun dist/index.js",
|
|
2027
|
+
"test": "bun test",
|
|
2028
|
+
"check": "biome ci ."
|
|
2029
|
+
},
|
|
2030
|
+
"dependencies": {
|
|
2031
|
+
"hono": "^4.0.0"
|
|
2032
|
+
},
|
|
2033
|
+
"devDependencies": {
|
|
2034
|
+
"@biomejs/biome": "^1.9.4",
|
|
2035
|
+
"@types/bun": "latest"
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2040
|
+
#### src/index.ts
|
|
2041
|
+
|
|
2042
|
+
```typescript
|
|
2043
|
+
import { Hono } from 'hono';
|
|
2044
|
+
import { logger } from 'hono/logger';
|
|
2045
|
+
import { healthRoute } from './routes/health';
|
|
2046
|
+
|
|
2047
|
+
const app = new Hono();
|
|
2048
|
+
|
|
2049
|
+
// Middleware
|
|
2050
|
+
app.use('*', logger());
|
|
2051
|
+
|
|
2052
|
+
// Routes
|
|
2053
|
+
app.route('/health', healthRoute);
|
|
2054
|
+
|
|
2055
|
+
// Start server
|
|
2056
|
+
const port = process.env.PORT || 3000;
|
|
2057
|
+
console.log(`🚀 Server running on http://localhost:${port}`);
|
|
2058
|
+
|
|
2059
|
+
export default {
|
|
2060
|
+
port,
|
|
2061
|
+
fetch: app.fetch,
|
|
2062
|
+
};
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
#### tests/health.test.ts
|
|
2066
|
+
|
|
2067
|
+
```typescript
|
|
2068
|
+
import { describe, expect, test } from 'bun:test';
|
|
2069
|
+
import app from '../src/index';
|
|
2070
|
+
|
|
2071
|
+
describe('Health Check', () => {
|
|
2072
|
+
test('GET /health returns 200', async () => {
|
|
2073
|
+
const req = new Request('http://localhost/health');
|
|
2074
|
+
const res = await app.fetch(req);
|
|
2075
|
+
|
|
2076
|
+
expect(res.status).toBe(200);
|
|
2077
|
+
|
|
2078
|
+
const body = await res.json();
|
|
2079
|
+
expect(body).toHaveProperty('status', 'ok');
|
|
2080
|
+
});
|
|
2081
|
+
});
|
|
2082
|
+
```
|
|
2083
|
+
|
|
2084
|
+
#### Dockerfile
|
|
2085
|
+
|
|
2086
|
+
```dockerfile
|
|
2087
|
+
FROM oven/bun:1.1-alpine as base
|
|
2088
|
+
WORKDIR /app
|
|
2089
|
+
|
|
2090
|
+
# Development
|
|
2091
|
+
FROM base as development
|
|
2092
|
+
COPY package.json bun.lockb ./
|
|
2093
|
+
RUN bun install
|
|
2094
|
+
COPY . .
|
|
2095
|
+
CMD ["bun", "run", "dev"]
|
|
2096
|
+
|
|
2097
|
+
# Production
|
|
2098
|
+
FROM base as production
|
|
2099
|
+
COPY package.json bun.lockb ./
|
|
2100
|
+
RUN bun install --production
|
|
2101
|
+
COPY . .
|
|
2102
|
+
RUN bun run build
|
|
2103
|
+
CMD ["bun", "run", "start"]
|
|
2104
|
+
```
|
|
2105
|
+
|
|
2106
|
+
#### docker-compose.yml
|
|
2107
|
+
|
|
2108
|
+
```yaml
|
|
2109
|
+
version: '3.8'
|
|
2110
|
+
|
|
2111
|
+
services:
|
|
2112
|
+
api:
|
|
2113
|
+
build:
|
|
2114
|
+
context: .
|
|
2115
|
+
target: development
|
|
2116
|
+
ports:
|
|
2117
|
+
- "3000:3000"
|
|
2118
|
+
volumes:
|
|
2119
|
+
- .:/app
|
|
2120
|
+
- /app/node_modules
|
|
2121
|
+
environment:
|
|
2122
|
+
- NODE_ENV=development
|
|
2123
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
|
|
2124
|
+
depends_on:
|
|
2125
|
+
- db
|
|
2126
|
+
|
|
2127
|
+
db:
|
|
2128
|
+
image: postgres:16-alpine
|
|
2129
|
+
ports:
|
|
2130
|
+
- "5432:5432"
|
|
2131
|
+
volumes:
|
|
2132
|
+
- postgres-data:/var/lib/postgresql/data
|
|
2133
|
+
environment:
|
|
2134
|
+
- POSTGRES_USER=user
|
|
2135
|
+
- POSTGRES_PASSWORD=pass
|
|
2136
|
+
- POSTGRES_DB=mydb
|
|
2137
|
+
|
|
2138
|
+
volumes:
|
|
2139
|
+
postgres-data:
|
|
2140
|
+
```
|
|
2141
|
+
|
|
2142
|
+
---
|
|
2143
|
+
|
|
2144
|
+
### 4. API REST con Python + FastAPI + uv
|
|
2145
|
+
|
|
2146
|
+
```bash
|
|
2147
|
+
my-api/
|
|
2148
|
+
├── app/
|
|
2149
|
+
│ ├── __init__.py
|
|
2150
|
+
│ ├── main.py
|
|
2151
|
+
│ └── routes/
|
|
2152
|
+
│ └── health.py
|
|
2153
|
+
├── tests/
|
|
2154
|
+
│ └── test_health.py
|
|
2155
|
+
├── pyproject.toml
|
|
2156
|
+
├── uv.lock
|
|
2157
|
+
├── Dockerfile
|
|
2158
|
+
└── docker-compose.yml
|
|
2159
|
+
```
|
|
2160
|
+
|
|
2161
|
+
#### pyproject.toml
|
|
2162
|
+
|
|
2163
|
+
```toml
|
|
2164
|
+
[project]
|
|
2165
|
+
name = "my-api"
|
|
2166
|
+
version = "0.1.0"
|
|
2167
|
+
description = "FastAPI project with uv"
|
|
2168
|
+
requires-python = ">=3.12"
|
|
2169
|
+
dependencies = [
|
|
2170
|
+
"fastapi>=0.109.0",
|
|
2171
|
+
"uvicorn[standard]>=0.27.0",
|
|
2172
|
+
"pydantic>=2.5.0",
|
|
2173
|
+
]
|
|
2174
|
+
|
|
2175
|
+
[project.optional-dependencies]
|
|
2176
|
+
dev = [
|
|
2177
|
+
"pytest>=7.4.0",
|
|
2178
|
+
"httpx>=0.26.0",
|
|
2179
|
+
"ruff>=0.1.0",
|
|
2180
|
+
]
|
|
2181
|
+
|
|
2182
|
+
[tool.ruff]
|
|
2183
|
+
line-length = 100
|
|
2184
|
+
target-version = "py312"
|
|
2185
|
+
|
|
2186
|
+
[tool.ruff.lint]
|
|
2187
|
+
select = ["E", "F", "I", "N", "W"]
|
|
2188
|
+
ignore = []
|
|
2189
|
+
```
|
|
2190
|
+
|
|
2191
|
+
#### app/main.py
|
|
2192
|
+
|
|
2193
|
+
```python
|
|
2194
|
+
from fastapi import FastAPI
|
|
2195
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
2196
|
+
from app.routes import health
|
|
2197
|
+
|
|
2198
|
+
app = FastAPI(title="My API")
|
|
2199
|
+
|
|
2200
|
+
# CORS
|
|
2201
|
+
app.add_middleware(
|
|
2202
|
+
CORSMiddleware,
|
|
2203
|
+
allow_origins=["*"],
|
|
2204
|
+
allow_credentials=True,
|
|
2205
|
+
allow_methods=["*"],
|
|
2206
|
+
allow_headers=["*"],
|
|
2207
|
+
)
|
|
2208
|
+
|
|
2209
|
+
# Routes
|
|
2210
|
+
app.include_router(health.router, prefix="/health", tags=["health"])
|
|
2211
|
+
|
|
2212
|
+
if __name__ == "__main__":
|
|
2213
|
+
import uvicorn
|
|
2214
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
2215
|
+
```
|
|
2216
|
+
|
|
2217
|
+
#### app/routes/health.py
|
|
2218
|
+
|
|
2219
|
+
```python
|
|
2220
|
+
from fastapi import APIRouter
|
|
2221
|
+
from pydantic import BaseModel
|
|
2222
|
+
|
|
2223
|
+
router = APIRouter()
|
|
2224
|
+
|
|
2225
|
+
class HealthResponse(BaseModel):
|
|
2226
|
+
status: str
|
|
2227
|
+
version: str = "0.1.0"
|
|
2228
|
+
|
|
2229
|
+
@router.get("/", response_model=HealthResponse)
|
|
2230
|
+
async def health_check():
|
|
2231
|
+
return {"status": "ok"}
|
|
2232
|
+
```
|
|
2233
|
+
|
|
2234
|
+
#### tests/test_health.py
|
|
2235
|
+
|
|
2236
|
+
```python
|
|
2237
|
+
from fastapi.testclient import TestClient
|
|
2238
|
+
from app.main import app
|
|
2239
|
+
|
|
2240
|
+
client = TestClient(app)
|
|
2241
|
+
|
|
2242
|
+
def test_health_check():
|
|
2243
|
+
response = client.get("/health/")
|
|
2244
|
+
assert response.status_code == 200
|
|
2245
|
+
assert response.json() == {"status": "ok", "version": "0.1.0"}
|
|
2246
|
+
```
|
|
2247
|
+
|
|
2248
|
+
#### Dockerfile
|
|
2249
|
+
|
|
2250
|
+
```dockerfile
|
|
2251
|
+
FROM python:3.12-slim as base
|
|
2252
|
+
WORKDIR /app
|
|
2253
|
+
|
|
2254
|
+
# Install uv
|
|
2255
|
+
RUN pip install uv
|
|
2256
|
+
|
|
2257
|
+
# Development
|
|
2258
|
+
FROM base as development
|
|
2259
|
+
COPY pyproject.toml uv.lock* ./
|
|
2260
|
+
RUN uv sync
|
|
2261
|
+
COPY . .
|
|
2262
|
+
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"]
|
|
2263
|
+
|
|
2264
|
+
# Production
|
|
2265
|
+
FROM base as production
|
|
2266
|
+
COPY pyproject.toml uv.lock* ./
|
|
2267
|
+
RUN uv sync --no-dev
|
|
2268
|
+
COPY . .
|
|
2269
|
+
RUN useradd -m appuser && chown -R appuser:appuser /app
|
|
2270
|
+
USER appuser
|
|
2271
|
+
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--workers", "4"]
|
|
2272
|
+
```
|
|
2273
|
+
|
|
2274
|
+
---
|
|
2275
|
+
|
|
2276
|
+
### 5. Monorepo Completo (Turborepo + Bun + Biome)
|
|
2277
|
+
|
|
2278
|
+
```bash
|
|
2279
|
+
my-monorepo/
|
|
2280
|
+
├── apps/
|
|
2281
|
+
│ ├── api/ # Backend (Bun + Hono)
|
|
2282
|
+
│ │ ├── src/
|
|
2283
|
+
│ │ ├── package.json
|
|
2284
|
+
│ │ └── Dockerfile
|
|
2285
|
+
│ ├── web/ # Frontend (Astro + React)
|
|
2286
|
+
│ │ ├── src/
|
|
2287
|
+
│ │ ├── package.json
|
|
2288
|
+
│ │ └── Dockerfile
|
|
2289
|
+
│ └── mobile/ # Mobile (Ionic + React)
|
|
2290
|
+
│ ├── src/
|
|
2291
|
+
│ └── package.json
|
|
2292
|
+
├── packages/
|
|
2293
|
+
│ ├── ui/ # React components
|
|
2294
|
+
│ ├── types/ # Shared TypeScript types
|
|
2295
|
+
│ └── config/ # Shared Biome config
|
|
2296
|
+
├── package.json # Root
|
|
2297
|
+
├── turbo.json
|
|
2298
|
+
├── biome.json
|
|
2299
|
+
├── docker-compose.yml
|
|
2300
|
+
└── .gitignore
|
|
2301
|
+
```
|
|
2302
|
+
|
|
2303
|
+
#### package.json (root)
|
|
2304
|
+
|
|
2305
|
+
```json
|
|
2306
|
+
{
|
|
2307
|
+
"name": "my-monorepo",
|
|
2308
|
+
"private": true,
|
|
2309
|
+
"workspaces": [
|
|
2310
|
+
"apps/*",
|
|
2311
|
+
"packages/*"
|
|
2312
|
+
],
|
|
2313
|
+
"scripts": {
|
|
2314
|
+
"dev": "turbo run dev",
|
|
2315
|
+
"build": "turbo run build",
|
|
2316
|
+
"test": "turbo run test",
|
|
2317
|
+
"lint": "biome check .",
|
|
2318
|
+
"format": "biome format --write .",
|
|
2319
|
+
"check": "biome ci ."
|
|
2320
|
+
},
|
|
2321
|
+
"devDependencies": {
|
|
2322
|
+
"@biomejs/biome": "^1.9.4",
|
|
2323
|
+
"turbo": "^2.0.0"
|
|
2324
|
+
},
|
|
2325
|
+
"packageManager": "bun@1.1.0"
|
|
2326
|
+
}
|
|
2327
|
+
```
|
|
2328
|
+
|
|
2329
|
+
#### turbo.json
|
|
2330
|
+
|
|
2331
|
+
```json
|
|
2332
|
+
{
|
|
2333
|
+
"$schema": "https://turbo.build/schema.json",
|
|
2334
|
+
"globalDependencies": ["**/.env.*local"],
|
|
2335
|
+
"pipeline": {
|
|
2336
|
+
"build": {
|
|
2337
|
+
"dependsOn": ["^build"],
|
|
2338
|
+
"outputs": ["dist/**", ".next/**", "build/**"]
|
|
2339
|
+
},
|
|
2340
|
+
"test": {
|
|
2341
|
+
"dependsOn": ["build"],
|
|
2342
|
+
"outputs": ["coverage/**"]
|
|
2343
|
+
},
|
|
2344
|
+
"lint": {
|
|
2345
|
+
"outputs": []
|
|
2346
|
+
},
|
|
2347
|
+
"dev": {
|
|
2348
|
+
"cache": false,
|
|
2349
|
+
"persistent": true
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
#### biome.json (root - compartido)
|
|
2356
|
+
|
|
2357
|
+
```json
|
|
2358
|
+
{
|
|
2359
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
2360
|
+
"organizeImports": {
|
|
2361
|
+
"enabled": true
|
|
2362
|
+
},
|
|
2363
|
+
"linter": {
|
|
2364
|
+
"enabled": true,
|
|
2365
|
+
"rules": {
|
|
2366
|
+
"recommended": true
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
"formatter": {
|
|
2370
|
+
"enabled": true,
|
|
2371
|
+
"indentStyle": "space",
|
|
2372
|
+
"indentWidth": 2,
|
|
2373
|
+
"lineWidth": 100
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
```
|
|
2377
|
+
|
|
2378
|
+
#### packages/ui/package.json
|
|
2379
|
+
|
|
2380
|
+
```json
|
|
2381
|
+
{
|
|
2382
|
+
"name": "@my-monorepo/ui",
|
|
2383
|
+
"version": "0.0.0",
|
|
2384
|
+
"type": "module",
|
|
2385
|
+
"main": "./dist/index.js",
|
|
2386
|
+
"types": "./dist/index.d.ts",
|
|
2387
|
+
"exports": {
|
|
2388
|
+
".": "./dist/index.js"
|
|
2389
|
+
},
|
|
2390
|
+
"scripts": {
|
|
2391
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
2392
|
+
"dev": "bun build src/index.ts --outdir dist --target node --watch"
|
|
2393
|
+
},
|
|
2394
|
+
"peerDependencies": {
|
|
2395
|
+
"react": "^18.0.0",
|
|
2396
|
+
"react-dom": "^18.0.0"
|
|
2397
|
+
},
|
|
2398
|
+
"devDependencies": {
|
|
2399
|
+
"@types/react": "^18.2.0",
|
|
2400
|
+
"@types/react-dom": "^18.2.0"
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
```
|
|
2404
|
+
|
|
2405
|
+
#### packages/types/package.json
|
|
2406
|
+
|
|
2407
|
+
```json
|
|
2408
|
+
{
|
|
2409
|
+
"name": "@my-monorepo/types",
|
|
2410
|
+
"version": "0.0.0",
|
|
2411
|
+
"type": "module",
|
|
2412
|
+
"main": "./dist/index.js",
|
|
2413
|
+
"types": "./dist/index.d.ts",
|
|
2414
|
+
"exports": {
|
|
2415
|
+
".": "./dist/index.d.ts"
|
|
2416
|
+
},
|
|
2417
|
+
"scripts": {
|
|
2418
|
+
"build": "tsc",
|
|
2419
|
+
"dev": "tsc --watch"
|
|
2420
|
+
},
|
|
2421
|
+
"devDependencies": {
|
|
2422
|
+
"typescript": "^5.3.0"
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
```
|
|
2426
|
+
|
|
2427
|
+
#### apps/api/package.json
|
|
2428
|
+
|
|
2429
|
+
```json
|
|
2430
|
+
{
|
|
2431
|
+
"name": "api",
|
|
2432
|
+
"version": "0.0.0",
|
|
2433
|
+
"type": "module",
|
|
2434
|
+
"scripts": {
|
|
2435
|
+
"dev": "bun run --hot src/index.ts",
|
|
2436
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
2437
|
+
"start": "NODE_ENV=production bun dist/index.js",
|
|
2438
|
+
"test": "bun test"
|
|
2439
|
+
},
|
|
2440
|
+
"dependencies": {
|
|
2441
|
+
"@my-monorepo/types": "workspace:*",
|
|
2442
|
+
"hono": "^4.0.0"
|
|
2443
|
+
},
|
|
2444
|
+
"devDependencies": {
|
|
2445
|
+
"@types/bun": "latest"
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
```
|
|
2449
|
+
|
|
2450
|
+
#### apps/web/package.json
|
|
2451
|
+
|
|
2452
|
+
```json
|
|
2453
|
+
{
|
|
2454
|
+
"name": "web",
|
|
2455
|
+
"version": "0.0.0",
|
|
2456
|
+
"type": "module",
|
|
2457
|
+
"scripts": {
|
|
2458
|
+
"dev": "astro dev",
|
|
2459
|
+
"build": "astro build",
|
|
2460
|
+
"preview": "astro preview"
|
|
2461
|
+
},
|
|
2462
|
+
"dependencies": {
|
|
2463
|
+
"@my-monorepo/ui": "workspace:*",
|
|
2464
|
+
"@my-monorepo/types": "workspace:*",
|
|
2465
|
+
"astro": "^4.0.0",
|
|
2466
|
+
"react": "^18.2.0",
|
|
2467
|
+
"react-dom": "^18.2.0"
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
```
|
|
2471
|
+
|
|
2472
|
+
#### docker-compose.yml (monorepo)
|
|
2473
|
+
|
|
2474
|
+
```yaml
|
|
2475
|
+
version: '3.8'
|
|
2476
|
+
|
|
2477
|
+
services:
|
|
2478
|
+
api:
|
|
2479
|
+
build:
|
|
2480
|
+
context: .
|
|
2481
|
+
dockerfile: apps/api/Dockerfile
|
|
2482
|
+
target: development
|
|
2483
|
+
ports:
|
|
2484
|
+
- "3000:3000"
|
|
2485
|
+
volumes:
|
|
2486
|
+
- .:/app
|
|
2487
|
+
- /app/node_modules
|
|
2488
|
+
environment:
|
|
2489
|
+
- NODE_ENV=development
|
|
2490
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
|
|
2491
|
+
depends_on:
|
|
2492
|
+
- db
|
|
2493
|
+
|
|
2494
|
+
web:
|
|
2495
|
+
build:
|
|
2496
|
+
context: .
|
|
2497
|
+
dockerfile: apps/web/Dockerfile
|
|
2498
|
+
ports:
|
|
2499
|
+
- "4321:4321"
|
|
2500
|
+
volumes:
|
|
2501
|
+
- .:/app
|
|
2502
|
+
- /app/node_modules
|
|
2503
|
+
environment:
|
|
2504
|
+
- PUBLIC_API_URL=http://localhost:3000
|
|
2505
|
+
|
|
2506
|
+
db:
|
|
2507
|
+
image: postgres:16-alpine
|
|
2508
|
+
ports:
|
|
2509
|
+
- "5432:5432"
|
|
2510
|
+
volumes:
|
|
2511
|
+
- postgres-data:/var/lib/postgresql/data
|
|
2512
|
+
environment:
|
|
2513
|
+
- POSTGRES_USER=user
|
|
2514
|
+
- POSTGRES_PASSWORD=pass
|
|
2515
|
+
- POSTGRES_DB=mydb
|
|
2516
|
+
|
|
2517
|
+
volumes:
|
|
2518
|
+
postgres-data:
|
|
2519
|
+
```
|
|
2520
|
+
|
|
2521
|
+
---
|
|
2522
|
+
|
|
2523
|
+
## 🔧 CI/CD Adaptativo Multi-Lenguaje
|
|
2524
|
+
|
|
2525
|
+
### GitHub Actions con Biome + Bun
|
|
2526
|
+
|
|
2527
|
+
```yaml
|
|
2528
|
+
# .github/workflows/ci.yml
|
|
2529
|
+
name: CI (Biome + Bun)
|
|
2530
|
+
|
|
2531
|
+
on:
|
|
2532
|
+
push:
|
|
2533
|
+
branches: [develop, main]
|
|
2534
|
+
pull_request:
|
|
2535
|
+
branches: [develop, main]
|
|
2536
|
+
|
|
2537
|
+
env:
|
|
2538
|
+
BUN_VERSION: '1.1.0'
|
|
2539
|
+
|
|
2540
|
+
jobs:
|
|
2541
|
+
detect-and-test:
|
|
2542
|
+
runs-on: ubuntu-latest
|
|
2543
|
+
|
|
2544
|
+
steps:
|
|
2545
|
+
- name: Checkout
|
|
2546
|
+
uses: actions/checkout@v4
|
|
2547
|
+
with:
|
|
2548
|
+
fetch-depth: 2
|
|
2549
|
+
|
|
2550
|
+
# ═══════════════════════════════════════════════════════════
|
|
2551
|
+
# DETECCIÓN AUTOMÁTICA DE STACK
|
|
2552
|
+
# ═══════════════════════════════════════════════════════════
|
|
2553
|
+
- name: Detect Tech Stack
|
|
2554
|
+
id: detect
|
|
2555
|
+
run: |
|
|
2556
|
+
if [ -f "package.json" ]; then
|
|
2557
|
+
echo "stack=node" >> $GITHUB_OUTPUT
|
|
2558
|
+
echo "pm=bun" >> $GITHUB_OUTPUT
|
|
2559
|
+
elif [ -f "pyproject.toml" ]; then
|
|
2560
|
+
echo "stack=python" >> $GITHUB_OUTPUT
|
|
2561
|
+
echo "pm=uv" >> $GITHUB_OUTPUT
|
|
2562
|
+
elif [ -f "go.mod" ]; then
|
|
2563
|
+
echo "stack=go" >> $GITHUB_OUTPUT
|
|
2564
|
+
elif [ -f "Cargo.toml" ]; then
|
|
2565
|
+
echo "stack=rust" >> $GITHUB_OUTPUT
|
|
2566
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
2567
|
+
echo "stack=java-gradle" >> $GITHUB_OUTPUT
|
|
2568
|
+
elif [ -f "pom.xml" ]; then
|
|
2569
|
+
echo "stack=java-maven" >> $GITHUB_OUTPUT
|
|
2570
|
+
fi
|
|
2571
|
+
|
|
2572
|
+
# Detectar monorepo
|
|
2573
|
+
if [ -f "turbo.json" ]; then
|
|
2574
|
+
echo "monorepo=turborepo" >> $GITHUB_OUTPUT
|
|
2575
|
+
fi
|
|
2576
|
+
|
|
2577
|
+
# ═══════════════════════════════════════════════════════════
|
|
2578
|
+
# NODE.JS + BUN + BIOME
|
|
2579
|
+
# ═══════════════════════════════════════════════════════════
|
|
2580
|
+
- name: Setup Bun
|
|
2581
|
+
if: steps.detect.outputs.stack == 'node'
|
|
2582
|
+
uses: oven-sh/setup-bun@v1
|
|
2583
|
+
with:
|
|
2584
|
+
bun-version: ${{ env.BUN_VERSION }}
|
|
2585
|
+
|
|
2586
|
+
- name: Install Dependencies
|
|
2587
|
+
if: steps.detect.outputs.stack == 'node'
|
|
2588
|
+
run: bun install --frozen-lockfile
|
|
2589
|
+
|
|
2590
|
+
- name: Biome Check
|
|
2591
|
+
if: steps.detect.outputs.stack == 'node'
|
|
2592
|
+
run: bun run check
|
|
2593
|
+
|
|
2594
|
+
- name: Run Tests
|
|
2595
|
+
if: steps.detect.outputs.stack == 'node'
|
|
2596
|
+
run: bun test
|
|
2597
|
+
|
|
2598
|
+
- name: Build
|
|
2599
|
+
if: steps.detect.outputs.stack == 'node'
|
|
2600
|
+
run: bun run build
|
|
2601
|
+
|
|
2602
|
+
# ═══════════════════════════════════════════════════════════
|
|
2603
|
+
# TURBOREPO (SI ES MONOREPO)
|
|
2604
|
+
# ═══════════════════════════════════════════════════════════
|
|
2605
|
+
- name: Turborepo Cache
|
|
2606
|
+
if: steps.detect.outputs.monorepo == 'turborepo'
|
|
2607
|
+
uses: actions/cache@v4
|
|
2608
|
+
with:
|
|
2609
|
+
path: .turbo
|
|
2610
|
+
key: turbo-${{ runner.os }}-${{ github.sha }}
|
|
2611
|
+
restore-keys: turbo-${{ runner.os }}-
|
|
2612
|
+
|
|
2613
|
+
- name: Build (Affected Only)
|
|
2614
|
+
if: steps.detect.outputs.monorepo == 'turborepo'
|
|
2615
|
+
run: bun run build --filter='...[origin/develop]'
|
|
2616
|
+
|
|
2617
|
+
- name: Test (Affected Only)
|
|
2618
|
+
if: steps.detect.outputs.monorepo == 'turborepo'
|
|
2619
|
+
run: bun test --filter='...[origin/develop]'
|
|
2620
|
+
|
|
2621
|
+
# ═══════════════════════════════════════════════════════════
|
|
2622
|
+
# PYTHON + UV
|
|
2623
|
+
# ═══════════════════════════════════════════════════════════
|
|
2624
|
+
- name: Setup Python
|
|
2625
|
+
if: steps.detect.outputs.stack == 'python'
|
|
2626
|
+
uses: actions/setup-python@v5
|
|
2627
|
+
with:
|
|
2628
|
+
python-version: '3.12'
|
|
2629
|
+
|
|
2630
|
+
- name: Install uv
|
|
2631
|
+
if: steps.detect.outputs.stack == 'python'
|
|
2632
|
+
run: pip install uv
|
|
2633
|
+
|
|
2634
|
+
- name: Install Dependencies
|
|
2635
|
+
if: steps.detect.outputs.stack == 'python'
|
|
2636
|
+
run: uv sync
|
|
2637
|
+
|
|
2638
|
+
- name: Lint (Ruff)
|
|
2639
|
+
if: steps.detect.outputs.stack == 'python'
|
|
2640
|
+
run: uv run ruff check .
|
|
2641
|
+
|
|
2642
|
+
- name: Run Tests
|
|
2643
|
+
if: steps.detect.outputs.stack == 'python'
|
|
2644
|
+
run: uv run pytest
|
|
2645
|
+
|
|
2646
|
+
# ═══════════════════════════════════════════════════════════
|
|
2647
|
+
# JAVA (GRADLE + KOTLIN)
|
|
2648
|
+
# ═══════════════════════════════════════════════════════════
|
|
2649
|
+
- name: Setup Java
|
|
2650
|
+
if: steps.detect.outputs.stack == 'java-gradle' || steps.detect.outputs.stack == 'java-maven'
|
|
2651
|
+
uses: actions/setup-java@v4
|
|
2652
|
+
with:
|
|
2653
|
+
distribution: 'temurin'
|
|
2654
|
+
java-version: '25'
|
|
2655
|
+
cache: 'gradle'
|
|
2656
|
+
|
|
2657
|
+
- name: Make gradlew executable
|
|
2658
|
+
if: steps.detect.outputs.stack == 'java-gradle'
|
|
2659
|
+
run: chmod +x ./gradlew
|
|
2660
|
+
|
|
2661
|
+
- name: Check Format (Spotless)
|
|
2662
|
+
if: steps.detect.outputs.stack == 'java-gradle'
|
|
2663
|
+
run: ./gradlew spotlessCheck
|
|
2664
|
+
|
|
2665
|
+
- name: Build and Test (Gradle)
|
|
2666
|
+
if: steps.detect.outputs.stack == 'java-gradle'
|
|
2667
|
+
run: ./gradlew build test
|
|
2668
|
+
|
|
2669
|
+
# ═══════════════════════════════════════════════════════════
|
|
2670
|
+
# GO
|
|
2671
|
+
# ═══════════════════════════════════════════════════════════
|
|
2672
|
+
- name: Setup Go
|
|
2673
|
+
if: steps.detect.outputs.stack == 'go'
|
|
2674
|
+
uses: actions/setup-go@v5
|
|
2675
|
+
with:
|
|
2676
|
+
go-version: '1.25'
|
|
2677
|
+
cache: true
|
|
2678
|
+
|
|
2679
|
+
- name: Install golangci-lint
|
|
2680
|
+
if: steps.detect.outputs.stack == 'go'
|
|
2681
|
+
run: |
|
|
2682
|
+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
|
2683
|
+
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
|
2684
|
+
|
|
2685
|
+
- name: Lint (golangci-lint)
|
|
2686
|
+
if: steps.detect.outputs.stack == 'go'
|
|
2687
|
+
run: golangci-lint run
|
|
2688
|
+
|
|
2689
|
+
- name: Install & Test (Go)
|
|
2690
|
+
if: steps.detect.outputs.stack == 'go'
|
|
2691
|
+
run: |
|
|
2692
|
+
go mod download
|
|
2693
|
+
go test -v ./...
|
|
2694
|
+
go build -v ./...
|
|
2695
|
+
|
|
2696
|
+
# ═══════════════════════════════════════════════════════════
|
|
2697
|
+
# RUST
|
|
2698
|
+
# ═══════════════════════════════════════════════════════════
|
|
2699
|
+
- name: Setup Rust
|
|
2700
|
+
if: steps.detect.outputs.stack == 'rust'
|
|
2701
|
+
uses: dtolnay/rust-toolchain@stable
|
|
2702
|
+
|
|
2703
|
+
- name: Cache Rust
|
|
2704
|
+
if: steps.detect.outputs.stack == 'rust'
|
|
2705
|
+
uses: Swatinem/rust-cache@v2
|
|
2706
|
+
|
|
2707
|
+
- name: Install & Test (Rust)
|
|
2708
|
+
if: steps.detect.outputs.stack == 'rust'
|
|
2709
|
+
run: |
|
|
2710
|
+
cargo test
|
|
2711
|
+
cargo build --release
|
|
2712
|
+
|
|
2713
|
+
# ═══════════════════════════════════════════════════════════
|
|
2714
|
+
# AUTO-MERGE (Para Solo Devs)
|
|
2715
|
+
# ═══════════════════════════════════════════════════════════
|
|
2716
|
+
auto-merge:
|
|
2717
|
+
needs: detect-and-test
|
|
2718
|
+
if: github.event_name == 'pull_request'
|
|
2719
|
+
runs-on: ubuntu-latest
|
|
2720
|
+
|
|
2721
|
+
steps:
|
|
2722
|
+
- name: Auto-merge PR
|
|
2723
|
+
uses: pascalgn/automerge-action@v0.16.2
|
|
2724
|
+
env:
|
|
2725
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
2726
|
+
MERGE_METHOD: squash
|
|
2727
|
+
MERGE_DELETE_BRANCH: true
|
|
2728
|
+
MERGE_LABELS: ''
|
|
2729
|
+
```
|
|
2730
|
+
|
|
2731
|
+
---
|
|
2732
|
+
|
|
2733
|
+
## 📅 Rutina Diaria del Solo Developer
|
|
2734
|
+
|
|
2735
|
+
### El Ritmo Semanal Óptimo
|
|
2736
|
+
|
|
2737
|
+
```typescript
|
|
2738
|
+
const WEEKLY_RHYTHM = {
|
|
2739
|
+
monday: {
|
|
2740
|
+
morning: 'PLANIFICACIÓN',
|
|
2741
|
+
tasks: [
|
|
2742
|
+
'1. Revisar backlog y priorizar',
|
|
2743
|
+
'2. Pedirle al agente el plan de la semana',
|
|
2744
|
+
'3. Configurar los pasos en GitHub Issues',
|
|
2745
|
+
'4. Empezar Paso 1 si queda tiempo'
|
|
2746
|
+
],
|
|
2747
|
+
wipLimit: 0 // Solo planificar, no codear
|
|
2748
|
+
},
|
|
2749
|
+
|
|
2750
|
+
tuesdayToThursday: {
|
|
2751
|
+
morning: 'EJECUCIÓN ATÓMICA',
|
|
2752
|
+
routine: `
|
|
2753
|
+
08:00 - git checkout develop && git pull
|
|
2754
|
+
08:05 - Revisar el paso actual (Issue de GitHub)
|
|
2755
|
+
08:10 - Crear branch: feat/XX-descripcion
|
|
2756
|
+
08:15 - 12:00 DEEP WORK (código)
|
|
2757
|
+
12:00 - Tests locales
|
|
2758
|
+
12:30 - git push → CI automático
|
|
2759
|
+
13:00 - Almuerzo (CI corriendo)
|
|
2760
|
+
14:00 - Si CI verde → Merge
|
|
2761
|
+
14:05 - Siguiente paso o continuar
|
|
2762
|
+
`,
|
|
2763
|
+
wipLimit: 1 // UNA sola tarea activa
|
|
2764
|
+
},
|
|
2765
|
+
|
|
2766
|
+
friday: {
|
|
2767
|
+
morning: 'DEPLOY + REFACTOR',
|
|
2768
|
+
tasks: [
|
|
2769
|
+
'1. Merge develop → main (si hay features completas)',
|
|
2770
|
+
'2. Deploy a staging/production',
|
|
2771
|
+
'3. Smoke test manual',
|
|
2772
|
+
'4. 2h de refactoring técnico',
|
|
2773
|
+
'5. Actualizar documentación si es necesario'
|
|
2774
|
+
],
|
|
2775
|
+
wipLimit: 0 // Solo deploy y mantenimiento
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
```
|
|
2779
|
+
|
|
2780
|
+
### Comandos Git del Día a Día
|
|
2781
|
+
|
|
2782
|
+
```bash
|
|
2783
|
+
#!/bin/bash
|
|
2784
|
+
# scripts/solo-dev-flow.sh
|
|
2785
|
+
|
|
2786
|
+
# ═══════════════════════════════════════════════════════════
|
|
2787
|
+
# INICIAR NUEVO PASO
|
|
2788
|
+
# ═══════════════════════════════════════════════════════════
|
|
2789
|
+
start_step() {
|
|
2790
|
+
local step_num=$1
|
|
2791
|
+
local step_name=$2
|
|
2792
|
+
|
|
2793
|
+
echo "🚀 Iniciando Paso $step_num: $step_name"
|
|
2794
|
+
|
|
2795
|
+
# Siempre desde develop actualizado
|
|
2796
|
+
git checkout develop
|
|
2797
|
+
git pull origin develop
|
|
2798
|
+
|
|
2799
|
+
# Crear UNA SOLA rama para TODO el paso
|
|
2800
|
+
local branch="feat/$(printf '%02d' $step_num)-$step_name"
|
|
2801
|
+
git checkout -b "$branch"
|
|
2802
|
+
|
|
2803
|
+
echo "✅ Branch '$branch' creado"
|
|
2804
|
+
echo ""
|
|
2805
|
+
echo "💡 FLUJO DE TRABAJO:"
|
|
2806
|
+
echo " 1. Implementa una parte pequeña"
|
|
2807
|
+
echo " 2. git add . && git commit -m 'descripción'"
|
|
2808
|
+
echo " 3. Repite hasta completar TODO el paso"
|
|
2809
|
+
echo " 4. git push -u origin HEAD (pushea TODOS los commits)"
|
|
2810
|
+
echo " 5. finish_step (crea PR automático)"
|
|
2811
|
+
echo ""
|
|
2812
|
+
echo "📝 Puedes hacer tantos commits como necesites en esta rama"
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
# ═══════════════════════════════════════════════════════════
|
|
2816
|
+
# FINALIZAR PASO (Push + PR automático)
|
|
2817
|
+
# ═══════════════════════════════════════════════════════════
|
|
2818
|
+
finish_step() {
|
|
2819
|
+
local message=$1
|
|
2820
|
+
|
|
2821
|
+
# Verificar que hay commits sin pushear
|
|
2822
|
+
if git diff origin/$(git branch --show-current) --quiet 2>/dev/null; then
|
|
2823
|
+
echo "⚠️ No hay cambios para pushear. ¿Ya hiciste commits?"
|
|
2824
|
+
echo "Usa: git add . && git commit -m 'tu mensaje'"
|
|
2825
|
+
return 1
|
|
2826
|
+
fi
|
|
2827
|
+
|
|
2828
|
+
# Push (puede ser de múltiples commits)
|
|
2829
|
+
echo "📤 Pusheando commits..."
|
|
2830
|
+
git push -u origin HEAD
|
|
2831
|
+
|
|
2832
|
+
# Crear PR con gh CLI
|
|
2833
|
+
gh pr create \
|
|
2834
|
+
--title "$message" \
|
|
2835
|
+
--body "## Paso Atómico
|
|
2836
|
+
|
|
2837
|
+
Este PR contiene $(git log origin/develop..HEAD --oneline | wc -l) commits que serán squashed en 1.
|
|
2838
|
+
|
|
2839
|
+
### Commits en este PR:
|
|
2840
|
+
\`\`\`
|
|
2841
|
+
$(git log origin/develop..HEAD --oneline)
|
|
2842
|
+
\`\`\`
|
|
2843
|
+
|
|
2844
|
+
**Al merge**: Todos estos commits se combinarán en uno solo usando squash merge.
|
|
2845
|
+
|
|
2846
|
+
---
|
|
2847
|
+
*Generado por Solo Dev Planner*" \
|
|
2848
|
+
--base develop
|
|
2849
|
+
|
|
2850
|
+
echo ""
|
|
2851
|
+
echo "✅ PR creado. Esperando CI..."
|
|
2852
|
+
echo "💡 El PR se mergeará automáticamente cuando CI pase"
|
|
2853
|
+
echo "📦 Squash merge: múltiples commits → 1 commit limpio en develop"
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
# ═══════════════════════════════════════════════════════════
|
|
2857
|
+
# COMMIT HELPER (opcional - para recordar hacer commits frecuentes)
|
|
2858
|
+
# ═══════════════════════════════════════════════════════════
|
|
2859
|
+
quick_commit() {
|
|
2860
|
+
local msg=$1
|
|
2861
|
+
|
|
2862
|
+
if [ -z "$msg" ]; then
|
|
2863
|
+
echo "❌ Proporciona un mensaje: quick_commit 'descripción'"
|
|
2864
|
+
return 1
|
|
2865
|
+
fi
|
|
2866
|
+
|
|
2867
|
+
git add .
|
|
2868
|
+
git commit -m "$msg"
|
|
2869
|
+
|
|
2870
|
+
echo "✅ Commit creado: $msg"
|
|
2871
|
+
echo "💡 Puedes seguir haciendo más commits en esta rama"
|
|
2872
|
+
echo "💡 Cuando termines el paso, usa: finish_step"
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
# ═══════════════════════════════════════════════════════════
|
|
2876
|
+
# VER ESTADO
|
|
2877
|
+
# ═══════════════════════════════════════════════════════════
|
|
2878
|
+
status() {
|
|
2879
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
2880
|
+
echo "📊 ESTADO DEL PROYECTO"
|
|
2881
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
2882
|
+
echo ""
|
|
2883
|
+
echo "🌳 Branch actual: $(git branch --show-current)"
|
|
2884
|
+
echo ""
|
|
2885
|
+
|
|
2886
|
+
# Commits locales sin pushear
|
|
2887
|
+
local unpushed=$(git log origin/$(git branch --show-current)..HEAD --oneline 2>/dev/null | wc -l)
|
|
2888
|
+
if [ "$unpushed" -gt 0 ]; then
|
|
2889
|
+
echo "📝 Commits locales sin pushear: $unpushed"
|
|
2890
|
+
git log origin/$(git branch --show-current)..HEAD --oneline
|
|
2891
|
+
echo ""
|
|
2892
|
+
fi
|
|
2893
|
+
|
|
2894
|
+
echo "📋 PRs abiertos:"
|
|
2895
|
+
gh pr list --state open
|
|
2896
|
+
echo ""
|
|
2897
|
+
echo "✅ PRs mergeados hoy:"
|
|
2898
|
+
gh pr list --state merged --search "merged:$(date +%Y-%m-%d)"
|
|
2899
|
+
echo ""
|
|
2900
|
+
echo "🔄 Estado de CI:"
|
|
2901
|
+
gh run list --limit 3
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
# ═══════════════════════════════════════════════════════════
|
|
2905
|
+
# EJEMPLO DE USO
|
|
2906
|
+
# ═══════════════════════════════════════════════════════════
|
|
2907
|
+
example_usage() {
|
|
2908
|
+
cat << 'EOF'
|
|
2909
|
+
═══════════════════════════════════════════════════════════
|
|
2910
|
+
📖 EJEMPLO DE USO CORRECTO
|
|
2911
|
+
═══════════════════════════════════════════════════════════
|
|
2912
|
+
|
|
2913
|
+
# 1. Iniciar paso
|
|
2914
|
+
source scripts/solo-dev-flow.sh
|
|
2915
|
+
start_step 01 "database-schema"
|
|
2916
|
+
|
|
2917
|
+
# 2. Trabajar en el paso (hacer múltiples commits)
|
|
2918
|
+
# ... editar archivos ...
|
|
2919
|
+
quick_commit "add User model"
|
|
2920
|
+
|
|
2921
|
+
# ... editar más archivos ...
|
|
2922
|
+
quick_commit "add migration script"
|
|
2923
|
+
|
|
2924
|
+
# ... editar tests ...
|
|
2925
|
+
quick_commit "add tests for User model"
|
|
2926
|
+
|
|
2927
|
+
# 3. Finalizar paso (pushea todos los commits y crea PR)
|
|
2928
|
+
finish_step "feat(db): implement database schema"
|
|
2929
|
+
|
|
2930
|
+
# 4. CI pasa → Auto-merge con SQUASH
|
|
2931
|
+
# Resultado en develop: 1 commit limpio con todos los cambios
|
|
2932
|
+
|
|
2933
|
+
# 5. Siguiente paso
|
|
2934
|
+
start_step 02 "api-endpoints"
|
|
2935
|
+
# ... repetir proceso ...
|
|
2936
|
+
|
|
2937
|
+
═══════════════════════════════════════════════════════════
|
|
2938
|
+
EOF
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
# Mostrar ayuda si se ejecuta sin argumentos
|
|
2942
|
+
if [ $# -eq 0 ]; then
|
|
2943
|
+
example_usage
|
|
2944
|
+
fi
|
|
2945
|
+
```
|
|
2946
|
+
|
|
2947
|
+
---
|
|
2948
|
+
|
|
2949
|
+
## 🎯 Generación de Plan (Output del Agente)
|
|
2950
|
+
|
|
2951
|
+
### Prompt para Iniciar Proyecto
|
|
2952
|
+
|
|
2953
|
+
```markdown
|
|
2954
|
+
MODO: Solo-Developer / Proyecto Desde Cero
|
|
2955
|
+
|
|
2956
|
+
PROYECTO: [Nombre del proyecto]
|
|
2957
|
+
|
|
2958
|
+
DESCRIPCIÓN:
|
|
2959
|
+
[Breve descripción del objetivo del proyecto]
|
|
2960
|
+
|
|
2961
|
+
STACK PREFERIDO:
|
|
2962
|
+
- Backend: [Python/FastAPI, Node/Bun, Go, Rust, etc.]
|
|
2963
|
+
- Frontend: [Astro, Next.js, SvelteKit, etc.]
|
|
2964
|
+
- Base de datos: [PostgreSQL, MongoDB, etc.]
|
|
2965
|
+
- Monorepo: [Sí/No - Si sí, usar Turborepo]
|
|
2966
|
+
|
|
2967
|
+
FEATURES PRINCIPALES:
|
|
2968
|
+
1. [Feature 1]
|
|
2969
|
+
2. [Feature 2]
|
|
2970
|
+
3. [Feature 3]
|
|
2971
|
+
|
|
2972
|
+
ENTREGABLE:
|
|
2973
|
+
Genera un plan atómico con:
|
|
2974
|
+
- Estructura de directorios inicial
|
|
2975
|
+
- Configuración de Biome + Bun (si aplica)
|
|
2976
|
+
- Docker Compose setup
|
|
2977
|
+
- CI/CD con GitHub Actions
|
|
2978
|
+
- Pasos granulares para implementar cada feature
|
|
2979
|
+
```
|
|
2980
|
+
|
|
2981
|
+
### Ejemplo: Feature "Sistema de Autenticación"
|
|
2982
|
+
|
|
2983
|
+
Cuando le pidas al agente planificar una feature, generará:
|
|
2984
|
+
|
|
2985
|
+
```markdown
|
|
2986
|
+
# 🎯 PLAN: Sistema de Autenticación
|
|
2987
|
+
|
|
2988
|
+
## 📊 Resumen
|
|
2989
|
+
- **Pasos totales:** 5
|
|
2990
|
+
- **Tiempo estimado:** 3-4 días de trabajo enfocado
|
|
2991
|
+
- **Stack:** Python/FastAPI + PostgreSQL + JWT
|
|
2992
|
+
- **Apps afectadas:** api-server
|
|
2993
|
+
- **Packages afectados:** types
|
|
2994
|
+
|
|
2995
|
+
---
|
|
2996
|
+
|
|
2997
|
+
## PASO 1: Setup Inicial + Docker
|
|
2998
|
+
|
|
2999
|
+
**Branch:** `feat/01-auth-setup`
|
|
3000
|
+
**Duración típica:** 1-2 horas
|
|
3001
|
+
|
|
3002
|
+
### Objetivo
|
|
3003
|
+
Configurar estructura base del proyecto con Docker Compose.
|
|
3004
|
+
|
|
3005
|
+
### Archivos a Crear
|
|
3006
|
+
- `pyproject.toml` - Configuración de proyecto con uv
|
|
3007
|
+
- `docker-compose.yml` - PostgreSQL + API
|
|
3008
|
+
- `Dockerfile` - Multi-stage para dev/prod
|
|
3009
|
+
- `app/main.py` - Entry point de FastAPI
|
|
3010
|
+
- `.env.example` - Variables de entorno
|
|
3011
|
+
|
|
3012
|
+
### Template pyproject.toml
|
|
3013
|
+
```toml
|
|
3014
|
+
[project]
|
|
3015
|
+
name = "auth-api"
|
|
3016
|
+
version = "0.1.0"
|
|
3017
|
+
requires-python = ">=3.12"
|
|
3018
|
+
dependencies = [
|
|
3019
|
+
"fastapi>=0.109.0",
|
|
3020
|
+
"uvicorn[standard]>=0.27.0",
|
|
3021
|
+
"sqlalchemy>=2.0.0",
|
|
3022
|
+
"asyncpg>=0.29.0",
|
|
3023
|
+
"python-jose[cryptography]>=3.3.0",
|
|
3024
|
+
"passlib[bcrypt]>=1.7.4",
|
|
3025
|
+
"python-multipart>=0.0.6",
|
|
3026
|
+
]
|
|
3027
|
+
|
|
3028
|
+
[project.optional-dependencies]
|
|
3029
|
+
dev = [
|
|
3030
|
+
"pytest>=7.4.0",
|
|
3031
|
+
"pytest-asyncio>=0.21.0",
|
|
3032
|
+
"httpx>=0.26.0",
|
|
3033
|
+
"ruff>=0.1.0",
|
|
3034
|
+
]
|
|
3035
|
+
```
|
|
3036
|
+
|
|
3037
|
+
### Comandos
|
|
3038
|
+
```bash
|
|
3039
|
+
# Inicializar proyecto
|
|
3040
|
+
uv init
|
|
3041
|
+
uv add fastapi uvicorn sqlalchemy asyncpg python-jose passlib
|
|
3042
|
+
uv add --dev pytest pytest-asyncio httpx ruff
|
|
3043
|
+
|
|
3044
|
+
# Levantar stack
|
|
3045
|
+
docker compose up -d
|
|
3046
|
+
|
|
3047
|
+
# Verificar
|
|
3048
|
+
curl http://localhost:8000/health
|
|
3049
|
+
```
|
|
3050
|
+
|
|
3051
|
+
### Done Criteria
|
|
3052
|
+
- [ ] Docker Compose levanta PostgreSQL + API
|
|
3053
|
+
- [ ] Endpoint `/health` responde 200
|
|
3054
|
+
- [ ] uv sync funciona sin errores
|
|
3055
|
+
|
|
3056
|
+
---
|
|
3057
|
+
|
|
3058
|
+
## PASO 2: Schema de Base de Datos
|
|
3059
|
+
|
|
3060
|
+
**Branch:** `feat/02-auth-schema`
|
|
3061
|
+
**Prerequisito:** Paso 1 mergeado
|
|
3062
|
+
**Duración típica:** 2-3 horas
|
|
3063
|
+
|
|
3064
|
+
### Objetivo
|
|
3065
|
+
Crear tablas `users` y `refresh_tokens` con SQLAlchemy.
|
|
3066
|
+
|
|
3067
|
+
### Archivos a Crear
|
|
3068
|
+
- `app/database.py` - Configuración de SQLAlchemy
|
|
3069
|
+
- `app/models/user.py` - Modelo de usuario
|
|
3070
|
+
- `app/models/refresh_token.py` - Modelo de refresh tokens
|
|
3071
|
+
- `alembic.ini` - Configuración de migraciones
|
|
3072
|
+
- `migrations/versions/001_create_users.py` - Migración inicial
|
|
3073
|
+
|
|
3074
|
+
### Contexto Just-in-Time: SQLAlchemy 2.0 Async
|
|
3075
|
+
|
|
3076
|
+
SQLAlchemy 2.0 cambió la sintaxis para queries async:
|
|
3077
|
+
|
|
3078
|
+
```python
|
|
3079
|
+
# app/database.py
|
|
3080
|
+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
3081
|
+
from sqlalchemy.orm import sessionmaker, declarative_base
|
|
3082
|
+
|
|
3083
|
+
DATABASE_URL = "postgresql+asyncpg://user:pass@db:5432/auth"
|
|
3084
|
+
|
|
3085
|
+
engine = create_async_engine(DATABASE_URL, echo=True)
|
|
3086
|
+
async_session = sessionmaker(
|
|
3087
|
+
engine, class_=AsyncSession, expire_on_commit=False
|
|
3088
|
+
)
|
|
3089
|
+
|
|
3090
|
+
Base = declarative_base()
|
|
3091
|
+
|
|
3092
|
+
async def get_db() -> AsyncSession:
|
|
3093
|
+
async with async_session() as session:
|
|
3094
|
+
yield session
|
|
3095
|
+
```
|
|
3096
|
+
|
|
3097
|
+
```python
|
|
3098
|
+
# app/models/user.py
|
|
3099
|
+
from sqlalchemy import Column, String, Boolean, DateTime
|
|
3100
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
3101
|
+
from app.database import Base
|
|
3102
|
+
import uuid
|
|
3103
|
+
from datetime import datetime
|
|
3104
|
+
|
|
3105
|
+
class User(Base):
|
|
3106
|
+
__tablename__ = "users"
|
|
3107
|
+
|
|
3108
|
+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
3109
|
+
email = Column(String, unique=True, index=True, nullable=False)
|
|
3110
|
+
hashed_password = Column(String, nullable=False)
|
|
3111
|
+
is_active = Column(Boolean, default=True)
|
|
3112
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
3113
|
+
```
|
|
3114
|
+
|
|
3115
|
+
### Comandos
|
|
3116
|
+
```bash
|
|
3117
|
+
# Instalar Alembic
|
|
3118
|
+
uv add alembic
|
|
3119
|
+
|
|
3120
|
+
# Inicializar migraciones
|
|
3121
|
+
alembic init migrations
|
|
3122
|
+
|
|
3123
|
+
# Crear migración
|
|
3124
|
+
alembic revision --autogenerate -m "create users table"
|
|
3125
|
+
|
|
3126
|
+
# Aplicar migración
|
|
3127
|
+
docker compose exec api alembic upgrade head
|
|
3128
|
+
```
|
|
3129
|
+
|
|
3130
|
+
### Done Criteria
|
|
3131
|
+
- [ ] Migración ejecuta sin errores
|
|
3132
|
+
- [ ] Tabla `users` existe en PostgreSQL
|
|
3133
|
+
- [ ] Modelo `User` tiene validaciones correctas
|
|
3134
|
+
|
|
3135
|
+
---
|
|
3136
|
+
|
|
3137
|
+
## PASO 3: Endpoints de Registro y Login
|
|
3138
|
+
|
|
3139
|
+
**Branch:** `feat/03-auth-endpoints`
|
|
3140
|
+
**Prerequisito:** Paso 2 mergeado
|
|
3141
|
+
**Duración típica:** 4-5 horas
|
|
3142
|
+
|
|
3143
|
+
### Objetivo
|
|
3144
|
+
Crear endpoints POST `/register` y POST `/login` con JWT.
|
|
3145
|
+
|
|
3146
|
+
### Archivos a Crear
|
|
3147
|
+
- `app/schemas/user.py` - Pydantic schemas
|
|
3148
|
+
- `app/routes/auth.py` - Endpoints de auth
|
|
3149
|
+
- `app/services/auth.py` - Lógica de negocio (hash, JWT)
|
|
3150
|
+
- `app/dependencies.py` - Dependency injection
|
|
3151
|
+
- `tests/test_auth.py` - Tests de integración
|
|
3152
|
+
|
|
3153
|
+
### Contexto Just-in-Time: JWT con python-jose
|
|
3154
|
+
|
|
3155
|
+
```python
|
|
3156
|
+
# app/services/auth.py
|
|
3157
|
+
from jose import jwt
|
|
3158
|
+
from passlib.context import CryptContext
|
|
3159
|
+
from datetime import datetime, timedelta
|
|
3160
|
+
|
|
3161
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
3162
|
+
|
|
3163
|
+
SECRET_KEY = "your-secret-key-here" # Mover a .env en prod
|
|
3164
|
+
ALGORITHM = "HS256"
|
|
3165
|
+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
3166
|
+
|
|
3167
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
3168
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
3169
|
+
|
|
3170
|
+
def get_password_hash(password: str) -> str:
|
|
3171
|
+
return pwd_context.hash(password)
|
|
3172
|
+
|
|
3173
|
+
def create_access_token(data: dict) -> str:
|
|
3174
|
+
to_encode = data.copy()
|
|
3175
|
+
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
3176
|
+
to_encode.update({"exp": expire})
|
|
3177
|
+
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
3178
|
+
```
|
|
3179
|
+
|
|
3180
|
+
### Comandos para Probar
|
|
3181
|
+
```bash
|
|
3182
|
+
# Registro
|
|
3183
|
+
curl -X POST http://localhost:8000/auth/register \
|
|
3184
|
+
-H "Content-Type: application/json" \
|
|
3185
|
+
-d '{"email": "test@example.com", "password": "secret123"}'
|
|
3186
|
+
|
|
3187
|
+
# Login
|
|
3188
|
+
curl -X POST http://localhost:8000/auth/login \
|
|
3189
|
+
-H "Content-Type: application/json" \
|
|
3190
|
+
-d '{"email": "test@example.com", "password": "secret123"}'
|
|
3191
|
+
|
|
3192
|
+
# Tests automatizados
|
|
3193
|
+
docker compose exec api pytest tests/test_auth.py -v
|
|
3194
|
+
```
|
|
3195
|
+
|
|
3196
|
+
### Done Criteria
|
|
3197
|
+
- [ ] POST `/register` crea usuario con password hasheado
|
|
3198
|
+
- [ ] POST `/login` retorna JWT válido
|
|
3199
|
+
- [ ] Passwords incorrectos retornan 401
|
|
3200
|
+
- [ ] Tests de integración pasan
|
|
3201
|
+
|
|
3202
|
+
---
|
|
3203
|
+
|
|
3204
|
+
## PASO 4: Middleware de Autenticación
|
|
3205
|
+
|
|
3206
|
+
**Branch:** `feat/04-auth-middleware`
|
|
3207
|
+
**Prerequisito:** Paso 3 mergeado
|
|
3208
|
+
**Duración típica:** 2-3 horas
|
|
3209
|
+
|
|
3210
|
+
### Objetivo
|
|
3211
|
+
Crear dependency para proteger rutas con JWT.
|
|
3212
|
+
|
|
3213
|
+
### Archivos a Crear/Modificar
|
|
3214
|
+
- `app/dependencies.py` - `get_current_user` dependency
|
|
3215
|
+
- `app/routes/users.py` - Endpoint protegido ejemplo
|
|
3216
|
+
- `tests/test_protected_routes.py` - Tests de autorización
|
|
3217
|
+
|
|
3218
|
+
### Contexto Just-in-Time: FastAPI Dependencies
|
|
3219
|
+
|
|
3220
|
+
```python
|
|
3221
|
+
# app/dependencies.py
|
|
3222
|
+
from fastapi import Depends, HTTPException, status
|
|
3223
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
3224
|
+
from jose import jwt, JWTError
|
|
3225
|
+
from app.services.auth import SECRET_KEY, ALGORITHM
|
|
3226
|
+
|
|
3227
|
+
security = HTTPBearer()
|
|
3228
|
+
|
|
3229
|
+
async def get_current_user(
|
|
3230
|
+
credentials: HTTPAuthorizationCredentials = Depends(security)
|
|
3231
|
+
) -> dict:
|
|
3232
|
+
token = credentials.credentials
|
|
3233
|
+
|
|
3234
|
+
try:
|
|
3235
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
3236
|
+
user_id: str = payload.get("sub")
|
|
3237
|
+
if user_id is None:
|
|
3238
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
3239
|
+
return {"user_id": user_id}
|
|
3240
|
+
except JWTError:
|
|
3241
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
3242
|
+
|
|
3243
|
+
# Uso en rutas protegidas:
|
|
3244
|
+
@router.get("/me")
|
|
3245
|
+
async def get_my_profile(current_user: dict = Depends(get_current_user)):
|
|
3246
|
+
return {"user_id": current_user["user_id"]}
|
|
3247
|
+
```
|
|
3248
|
+
|
|
3249
|
+
### Done Criteria
|
|
3250
|
+
- [ ] Rutas con `Depends(get_current_user)` requieren JWT
|
|
3251
|
+
- [ ] JWT inválido retorna 401
|
|
3252
|
+
- [ ] JWT válido permite acceso
|
|
3253
|
+
- [ ] Tests de autorización pasan
|
|
3254
|
+
|
|
3255
|
+
---
|
|
3256
|
+
|
|
3257
|
+
## PASO 5: Refresh Tokens
|
|
3258
|
+
|
|
3259
|
+
**Branch:** `feat/05-refresh-tokens`
|
|
3260
|
+
**Prerequisito:** Paso 4 mergeado
|
|
3261
|
+
**Duración típica:** 3-4 horas
|
|
3262
|
+
|
|
3263
|
+
### Objetivo
|
|
3264
|
+
Implementar refresh tokens para no re-loguear cada 30 min.
|
|
3265
|
+
|
|
3266
|
+
### Archivos a Crear/Modificar
|
|
3267
|
+
- `app/models/refresh_token.py` - Modelo ya creado en Paso 2
|
|
3268
|
+
- `app/routes/auth.py` - Endpoint POST `/refresh`
|
|
3269
|
+
- `app/services/auth.py` - Funciones para refresh tokens
|
|
3270
|
+
- `tests/test_refresh.py` - Tests
|
|
3271
|
+
|
|
3272
|
+
### Flujo de Refresh Tokens
|
|
3273
|
+
1. Login retorna: `{ access_token, refresh_token }`
|
|
3274
|
+
2. Access token expira en 30 min
|
|
3275
|
+
3. Frontend usa refresh token para obtener nuevo access token
|
|
3276
|
+
4. Refresh token expira en 7 días
|
|
3277
|
+
|
|
3278
|
+
### Done Criteria
|
|
3279
|
+
- [ ] Login retorna access + refresh token
|
|
3280
|
+
- [ ] POST `/refresh` con refresh token válido retorna nuevo access token
|
|
3281
|
+
- [ ] Refresh token expirado retorna 401
|
|
3282
|
+
- [ ] Tests de refresh pasan
|
|
3283
|
+
|
|
3284
|
+
---
|
|
3285
|
+
|
|
3286
|
+
## 🚀 Comandos Globales
|
|
3287
|
+
|
|
3288
|
+
```bash
|
|
3289
|
+
# Iniciar desarrollo
|
|
3290
|
+
docker compose up -d
|
|
3291
|
+
|
|
3292
|
+
# Logs en tiempo real
|
|
3293
|
+
docker compose logs -f api
|
|
3294
|
+
|
|
3295
|
+
# Ejecutar tests
|
|
3296
|
+
docker compose exec api pytest -v
|
|
3297
|
+
|
|
3298
|
+
# Aplicar migraciones
|
|
3299
|
+
docker compose exec api alembic upgrade head
|
|
3300
|
+
|
|
3301
|
+
# Acceder a shell de Python
|
|
3302
|
+
docker compose exec api python
|
|
3303
|
+
|
|
3304
|
+
# Acceder a PostgreSQL
|
|
3305
|
+
docker compose exec db psql -U user -d auth
|
|
3306
|
+
|
|
3307
|
+
# Detener todo
|
|
3308
|
+
docker compose down
|
|
3309
|
+
|
|
3310
|
+
# Limpiar volúmenes (cuidado!)
|
|
3311
|
+
docker compose down -v
|
|
3312
|
+
```
|
|
3313
|
+
|
|
3314
|
+
---
|
|
3315
|
+
|
|
3316
|
+
## 📋 Checklist Final
|
|
3317
|
+
|
|
3318
|
+
Antes de considerar la feature completa:
|
|
3319
|
+
|
|
3320
|
+
- [ ] Todos los tests pasan (100% coverage en servicios críticos)
|
|
3321
|
+
- [ ] Endpoints documentados en OpenAPI (`/docs`)
|
|
3322
|
+
- [ ] Variables sensibles en `.env` (no hardcoded)
|
|
3323
|
+
- [ ] Logs de seguridad para login fallido
|
|
3324
|
+
- [ ] Rate limiting en endpoints de auth (opcional)
|
|
3325
|
+
- [ ] CORS configurado correctamente
|
|
3326
|
+
- [ ] Docker Compose funciona en máquina limpia
|
|
3327
|
+
- [ ] README actualizado con instrucciones
|
|
3328
|
+
```
|
|
3329
|
+
|
|
3330
|
+
---
|
|
3331
|
+
|
|
3332
|
+
## 🔧 Scripts Universales
|
|
3333
|
+
|
|
3334
|
+
### scripts/test.sh (Detecta Stack)
|
|
3335
|
+
|
|
3336
|
+
```bash
|
|
3337
|
+
#!/bin/bash
|
|
3338
|
+
# scripts/test.sh - Detecta stack y corre tests
|
|
3339
|
+
set -e
|
|
3340
|
+
|
|
3341
|
+
echo "🔍 Detectando tech stack..."
|
|
3342
|
+
|
|
3343
|
+
if [ -f "package.json" ]; then
|
|
3344
|
+
echo "📦 Bun detectado"
|
|
3345
|
+
bun test
|
|
3346
|
+
elif [ -f "pyproject.toml" ]; then
|
|
3347
|
+
echo "🐍 Python (uv) detectado"
|
|
3348
|
+
uv run pytest
|
|
3349
|
+
elif [ -f "go.mod" ]; then
|
|
3350
|
+
echo "🐹 Go detectado"
|
|
3351
|
+
go test -v ./...
|
|
3352
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
3353
|
+
echo "☕ Java (Gradle) detectado"
|
|
3354
|
+
./gradlew test
|
|
3355
|
+
elif [ -f "pom.xml" ]; then
|
|
3356
|
+
echo "☕ Java (Maven) detectado"
|
|
3357
|
+
./mvnw test
|
|
3358
|
+
elif [ -f "Cargo.toml" ]; then
|
|
3359
|
+
echo "🦀 Rust detectado"
|
|
3360
|
+
cargo test
|
|
3361
|
+
else
|
|
3362
|
+
echo "❌ Stack no reconocido"
|
|
3363
|
+
exit 1
|
|
3364
|
+
fi
|
|
3365
|
+
|
|
3366
|
+
echo "✅ Tests completados!"
|
|
3367
|
+
```
|
|
3368
|
+
|
|
3369
|
+
### scripts/lint.sh (Usa Biome, Spotless, golangci-lint, etc.)
|
|
3370
|
+
|
|
3371
|
+
```bash
|
|
3372
|
+
#!/bin/bash
|
|
3373
|
+
# scripts/lint.sh
|
|
3374
|
+
set -e
|
|
3375
|
+
|
|
3376
|
+
echo "🔍 Detectando linter..."
|
|
3377
|
+
|
|
3378
|
+
if [ -f "biome.json" ]; then
|
|
3379
|
+
echo "🎨 Biome detectado"
|
|
3380
|
+
bun run check
|
|
3381
|
+
elif [ -f "pyproject.toml" ] && grep -q "ruff" pyproject.toml; then
|
|
3382
|
+
echo "🐍 Ruff detectado"
|
|
3383
|
+
uv run ruff check .
|
|
3384
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
3385
|
+
echo "☕ Spotless (Java/Gradle) detectado"
|
|
3386
|
+
./gradlew spotlessCheck
|
|
3387
|
+
elif [ -f ".golangci.yml" ]; then
|
|
3388
|
+
echo "🐹 golangci-lint detectado"
|
|
3389
|
+
golangci-lint run
|
|
3390
|
+
elif [ -f "rustfmt.toml" ]; then
|
|
3391
|
+
echo "🦀 Rustfmt detectado"
|
|
3392
|
+
cargo fmt --check
|
|
3393
|
+
else
|
|
3394
|
+
echo "⚠️ No linter configurado"
|
|
3395
|
+
fi
|
|
3396
|
+
|
|
3397
|
+
echo "✅ Lint completado!"
|
|
3398
|
+
```
|
|
3399
|
+
|
|
3400
|
+
### scripts/format.sh (Formatea código automáticamente)
|
|
3401
|
+
|
|
3402
|
+
```bash
|
|
3403
|
+
#!/bin/bash
|
|
3404
|
+
# scripts/format.sh
|
|
3405
|
+
set -e
|
|
3406
|
+
|
|
3407
|
+
echo "🎨 Formateando código..."
|
|
3408
|
+
|
|
3409
|
+
if [ -f "biome.json" ]; then
|
|
3410
|
+
echo "📦 Biome"
|
|
3411
|
+
bun run format
|
|
3412
|
+
elif [ -f "pyproject.toml" ] && grep -q "ruff" pyproject.toml; then
|
|
3413
|
+
echo "🐍 Ruff"
|
|
3414
|
+
uv run ruff format .
|
|
3415
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
3416
|
+
echo "☕ Spotless"
|
|
3417
|
+
./gradlew spotlessApply
|
|
3418
|
+
elif [ -f "go.mod" ]; then
|
|
3419
|
+
echo "🐹 gofmt + goimports"
|
|
3420
|
+
go fmt ./...
|
|
3421
|
+
if command -v goimports &> /dev/null; then
|
|
3422
|
+
goimports -w .
|
|
3423
|
+
fi
|
|
3424
|
+
elif [ -f "Cargo.toml" ]; then
|
|
3425
|
+
echo "🦀 rustfmt"
|
|
3426
|
+
cargo fmt
|
|
3427
|
+
fi
|
|
3428
|
+
|
|
3429
|
+
echo "✅ Formato aplicado!"
|
|
3430
|
+
```
|
|
3431
|
+
|
|
3432
|
+
### scripts/dev.sh (Levanta Dev Server)
|
|
3433
|
+
|
|
3434
|
+
```bash
|
|
3435
|
+
#!/bin/bash
|
|
3436
|
+
# scripts/dev.sh
|
|
3437
|
+
set -e
|
|
3438
|
+
|
|
3439
|
+
if [ -f "docker-compose.yml" ]; then
|
|
3440
|
+
echo "🐳 Levantando Docker Compose..."
|
|
3441
|
+
docker compose up
|
|
3442
|
+
elif [ -f "package.json" ]; then
|
|
3443
|
+
echo "📦 Iniciando dev server (Bun)..."
|
|
3444
|
+
bun run dev
|
|
3445
|
+
elif [ -f "pyproject.toml" ]; then
|
|
3446
|
+
echo "🐍 Iniciando dev server (uv)..."
|
|
3447
|
+
uv run uvicorn app.main:app --reload
|
|
3448
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
3449
|
+
echo "☕ Iniciando dev server (Gradle)..."
|
|
3450
|
+
./gradlew bootRun
|
|
3451
|
+
elif [ -f "go.mod" ]; then
|
|
3452
|
+
echo "🐹 Iniciando dev server (Go)..."
|
|
3453
|
+
if command -v air &> /dev/null; then
|
|
3454
|
+
air
|
|
3455
|
+
else
|
|
3456
|
+
go run cmd/api/main.go
|
|
3457
|
+
fi
|
|
3458
|
+
else
|
|
3459
|
+
echo "❌ No se encontró configuración de dev"
|
|
3460
|
+
exit 1
|
|
3461
|
+
fi
|
|
3462
|
+
```
|
|
3463
|
+
|
|
3464
|
+
### scripts/build.sh (Build optimizado por stack)
|
|
3465
|
+
|
|
3466
|
+
```bash
|
|
3467
|
+
#!/bin/bash
|
|
3468
|
+
# scripts/build.sh
|
|
3469
|
+
set -e
|
|
3470
|
+
|
|
3471
|
+
echo "🔨 Building..."
|
|
3472
|
+
|
|
3473
|
+
if [ -f "package.json" ]; then
|
|
3474
|
+
echo "📦 Bun build"
|
|
3475
|
+
bun run build
|
|
3476
|
+
elif [ -f "pyproject.toml" ]; then
|
|
3477
|
+
echo "🐍 Python no requiere build (interpretado)"
|
|
3478
|
+
elif [ -f "build.gradle.kts" ] || [ -f "build.gradle" ]; then
|
|
3479
|
+
echo "☕ Gradle build"
|
|
3480
|
+
./gradlew build -x test
|
|
3481
|
+
elif [ -f "go.mod" ]; then
|
|
3482
|
+
echo "🐹 Go build"
|
|
3483
|
+
go build -o bin/api ./cmd/api
|
|
3484
|
+
elif [ -f "Cargo.toml" ]; then
|
|
3485
|
+
echo "🦀 Rust build"
|
|
3486
|
+
cargo build --release
|
|
3487
|
+
fi
|
|
3488
|
+
|
|
3489
|
+
echo "✅ Build completado!"
|
|
3490
|
+
```
|
|
3491
|
+
|
|
3492
|
+
---
|
|
3493
|
+
|