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.
Files changed (500) hide show
  1. package/.gitignore.template +105 -0
  2. package/.releaserc +44 -0
  3. package/README.md +45 -0
  4. package/ai-config/.skillignore +15 -0
  5. package/ai-config/AUTO_INVOKE.md +300 -0
  6. package/ai-config/agents/_TEMPLATE.md +93 -0
  7. package/ai-config/agents/business/api-designer.md +1657 -0
  8. package/ai-config/agents/business/business-analyst.md +1331 -0
  9. package/ai-config/agents/business/product-strategist.md +206 -0
  10. package/ai-config/agents/business/project-manager.md +178 -0
  11. package/ai-config/agents/business/requirements-analyst.md +1277 -0
  12. package/ai-config/agents/business/technical-writer.md +1679 -0
  13. package/ai-config/agents/creative/ux-designer.md +205 -0
  14. package/ai-config/agents/data-ai/ai-engineer.md +487 -0
  15. package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
  16. package/ai-config/agents/data-ai/data-engineer.md +173 -0
  17. package/ai-config/agents/data-ai/data-scientist.md +672 -0
  18. package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
  19. package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
  20. package/ai-config/agents/development/angular-expert.md +620 -0
  21. package/ai-config/agents/development/backend-architect.md +795 -0
  22. package/ai-config/agents/development/database-specialist.md +212 -0
  23. package/ai-config/agents/development/frontend-specialist.md +686 -0
  24. package/ai-config/agents/development/fullstack-engineer.md +668 -0
  25. package/ai-config/agents/development/golang-pro.md +338 -0
  26. package/ai-config/agents/development/java-enterprise.md +400 -0
  27. package/ai-config/agents/development/javascript-pro.md +422 -0
  28. package/ai-config/agents/development/nextjs-pro.md +474 -0
  29. package/ai-config/agents/development/python-pro.md +570 -0
  30. package/ai-config/agents/development/react-pro.md +487 -0
  31. package/ai-config/agents/development/rust-pro.md +246 -0
  32. package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
  33. package/ai-config/agents/development/typescript-pro.md +336 -0
  34. package/ai-config/agents/development/vue-specialist.md +605 -0
  35. package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
  36. package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
  37. package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
  38. package/ai-config/agents/infrastructure/incident-responder.md +519 -0
  39. package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
  40. package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
  41. package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
  42. package/ai-config/agents/orchestrator.md +241 -0
  43. package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
  44. package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
  45. package/ai-config/agents/quality/code-reviewer.md +363 -0
  46. package/ai-config/agents/quality/dependency-manager.md +743 -0
  47. package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
  48. package/ai-config/agents/quality/performance-tester.md +1086 -0
  49. package/ai-config/agents/quality/security-auditor.md +133 -0
  50. package/ai-config/agents/quality/test-engineer.md +453 -0
  51. package/ai-config/agents/specialists/api-designer.md +87 -0
  52. package/ai-config/agents/specialists/backend-architect.md +73 -0
  53. package/ai-config/agents/specialists/code-reviewer.md +77 -0
  54. package/ai-config/agents/specialists/db-optimizer.md +75 -0
  55. package/ai-config/agents/specialists/devops-engineer.md +83 -0
  56. package/ai-config/agents/specialists/documentation-writer.md +78 -0
  57. package/ai-config/agents/specialists/frontend-developer.md +75 -0
  58. package/ai-config/agents/specialists/performance-analyst.md +82 -0
  59. package/ai-config/agents/specialists/refactor-specialist.md +74 -0
  60. package/ai-config/agents/specialists/security-auditor.md +74 -0
  61. package/ai-config/agents/specialists/test-engineer.md +81 -0
  62. package/ai-config/agents/specialists/ux-consultant.md +76 -0
  63. package/ai-config/agents/specialized/agent-generator.md +1190 -0
  64. package/ai-config/agents/specialized/blockchain-developer.md +149 -0
  65. package/ai-config/agents/specialized/code-migrator.md +892 -0
  66. package/ai-config/agents/specialized/context-manager.md +978 -0
  67. package/ai-config/agents/specialized/documentation-writer.md +1078 -0
  68. package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
  69. package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
  70. package/ai-config/agents/specialized/error-detective.md +1034 -0
  71. package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
  72. package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
  73. package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
  74. package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
  75. package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
  76. package/ai-config/agents/specialized/game-developer.md +1963 -0
  77. package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
  78. package/ai-config/agents/specialized/mobile-developer.md +188 -0
  79. package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
  80. package/ai-config/agents/specialized/plan-executor.md +485 -0
  81. package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
  82. package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
  83. package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
  84. package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
  85. package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
  86. package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
  87. package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
  88. package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
  89. package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
  90. package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
  91. package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
  92. package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
  93. package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
  94. package/ai-config/agents/specialized/template-writer.md +347 -0
  95. package/ai-config/agents/specialized/test-runner.md +99 -0
  96. package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
  97. package/ai-config/agents/specialized/wave-executor.md +138 -0
  98. package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
  99. package/ai-config/commands/git/changelog.md +32 -0
  100. package/ai-config/commands/git/ci-local.md +70 -0
  101. package/ai-config/commands/git/commit.md +35 -0
  102. package/ai-config/commands/git/fix-issue.md +23 -0
  103. package/ai-config/commands/git/pr-create.md +42 -0
  104. package/ai-config/commands/git/pr-review.md +50 -0
  105. package/ai-config/commands/git/worktree.md +39 -0
  106. package/ai-config/commands/refactoring/cleanup.md +24 -0
  107. package/ai-config/commands/refactoring/dead-code.md +40 -0
  108. package/ai-config/commands/refactoring/extract.md +31 -0
  109. package/ai-config/commands/testing/e2e.md +30 -0
  110. package/ai-config/commands/testing/tdd.md +36 -0
  111. package/ai-config/commands/testing/test-coverage.md +30 -0
  112. package/ai-config/commands/testing/test-fix.md +24 -0
  113. package/ai-config/commands/workflow/generate-agents-md.md +85 -0
  114. package/ai-config/commands/workflow/planning.md +47 -0
  115. package/ai-config/commands/workflows/compound.md +89 -0
  116. package/ai-config/commands/workflows/plan.md +77 -0
  117. package/ai-config/commands/workflows/review.md +78 -0
  118. package/ai-config/commands/workflows/work.md +75 -0
  119. package/ai-config/config.yaml +18 -0
  120. package/ai-config/hooks/_TEMPLATE.md +96 -0
  121. package/ai-config/hooks/block-dangerous-commands.md +75 -0
  122. package/ai-config/hooks/commit-guard.md +90 -0
  123. package/ai-config/hooks/context-loader.md +73 -0
  124. package/ai-config/hooks/improve-prompt.md +91 -0
  125. package/ai-config/hooks/learning-log.md +72 -0
  126. package/ai-config/hooks/model-router.md +86 -0
  127. package/ai-config/hooks/secret-scanner.md +64 -0
  128. package/ai-config/hooks/skill-validator.md +102 -0
  129. package/ai-config/hooks/task-artifact.md +114 -0
  130. package/ai-config/hooks/validate-workflow.md +100 -0
  131. package/ai-config/prompts/base.md +71 -0
  132. package/ai-config/prompts/modes/debug.md +34 -0
  133. package/ai-config/prompts/modes/deploy.md +40 -0
  134. package/ai-config/prompts/modes/research.md +32 -0
  135. package/ai-config/prompts/modes/review.md +33 -0
  136. package/ai-config/prompts/review-policy.md +79 -0
  137. package/ai-config/skills/_TEMPLATE.md +157 -0
  138. package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
  139. package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
  140. package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
  141. package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
  142. package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
  143. package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
  144. package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
  145. package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
  146. package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
  147. package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
  148. package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
  149. package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
  150. package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
  151. package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
  152. package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
  153. package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
  154. package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
  155. package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
  156. package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
  157. package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
  158. package/ai-config/skills/backend/websockets/SKILL.md +532 -0
  159. package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
  160. package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
  161. package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
  162. package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
  163. package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
  164. package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
  165. package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
  166. package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
  167. package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
  168. package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
  169. package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
  170. package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
  171. package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
  172. package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
  173. package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
  174. package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
  175. package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
  176. package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
  177. package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
  178. package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
  179. package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
  180. package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
  181. package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
  182. package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
  183. package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
  184. package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
  185. package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
  186. package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
  187. package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
  188. package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
  189. package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
  190. package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
  191. package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
  192. package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
  193. package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
  194. package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
  195. package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
  196. package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
  197. package/ai-config/skills/prompt-improver/SKILL.md +125 -0
  198. package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
  199. package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
  200. package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
  201. package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
  202. package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
  203. package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
  204. package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
  205. package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
  206. package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
  207. package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
  208. package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
  209. package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
  210. package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
  211. package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
  212. package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
  213. package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
  214. package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
  215. package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
  216. package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
  217. package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
  218. package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
  219. package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
  220. package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
  221. package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
  222. package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
  223. package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
  224. package/ci-local/README.md +170 -0
  225. package/ci-local/ci-local.sh +297 -0
  226. package/ci-local/hooks/commit-msg +74 -0
  227. package/ci-local/hooks/pre-commit +162 -0
  228. package/ci-local/hooks/pre-push +41 -0
  229. package/ci-local/install.sh +49 -0
  230. package/ci-local/semgrep.yml +214 -0
  231. package/dist/commands/analyze.d.ts +9 -0
  232. package/dist/commands/analyze.d.ts.map +1 -0
  233. package/dist/commands/analyze.js +55 -0
  234. package/dist/commands/analyze.js.map +1 -0
  235. package/dist/commands/analyze.test.d.ts +2 -0
  236. package/dist/commands/analyze.test.d.ts.map +1 -0
  237. package/dist/commands/analyze.test.js +145 -0
  238. package/dist/commands/analyze.test.js.map +1 -0
  239. package/dist/commands/doctor.d.ts +7 -0
  240. package/dist/commands/doctor.d.ts.map +1 -0
  241. package/dist/commands/doctor.js +158 -0
  242. package/dist/commands/doctor.js.map +1 -0
  243. package/dist/commands/doctor.test.d.ts +2 -0
  244. package/dist/commands/doctor.test.d.ts.map +1 -0
  245. package/dist/commands/doctor.test.js +200 -0
  246. package/dist/commands/doctor.test.js.map +1 -0
  247. package/dist/commands/init.d.ts +9 -0
  248. package/dist/commands/init.d.ts.map +1 -0
  249. package/dist/commands/init.js +283 -0
  250. package/dist/commands/init.js.map +1 -0
  251. package/dist/commands/init.test.d.ts +2 -0
  252. package/dist/commands/init.test.d.ts.map +1 -0
  253. package/dist/commands/init.test.js +271 -0
  254. package/dist/commands/init.test.js.map +1 -0
  255. package/dist/commands/sync.d.ts +8 -0
  256. package/dist/commands/sync.d.ts.map +1 -0
  257. package/dist/commands/sync.js +201 -0
  258. package/dist/commands/sync.js.map +1 -0
  259. package/dist/constants.d.ts +21 -0
  260. package/dist/constants.d.ts.map +1 -0
  261. package/dist/constants.js +57 -0
  262. package/dist/constants.js.map +1 -0
  263. package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
  264. package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
  265. package/dist/e2e/aggressive.e2e.test.js +350 -0
  266. package/dist/e2e/aggressive.e2e.test.js.map +1 -0
  267. package/dist/e2e/commands.e2e.test.d.ts +2 -0
  268. package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
  269. package/dist/e2e/commands.e2e.test.js +213 -0
  270. package/dist/e2e/commands.e2e.test.js.map +1 -0
  271. package/dist/index.d.ts +3 -0
  272. package/dist/index.d.ts.map +1 -0
  273. package/dist/index.js +82 -0
  274. package/dist/index.js.map +1 -0
  275. package/dist/lib/common.d.ts +17 -0
  276. package/dist/lib/common.d.ts.map +1 -0
  277. package/dist/lib/common.js +111 -0
  278. package/dist/lib/common.js.map +1 -0
  279. package/dist/lib/common.test.d.ts +2 -0
  280. package/dist/lib/common.test.d.ts.map +1 -0
  281. package/dist/lib/common.test.js +316 -0
  282. package/dist/lib/common.test.js.map +1 -0
  283. package/dist/lib/frontmatter.d.ts +18 -0
  284. package/dist/lib/frontmatter.d.ts.map +1 -0
  285. package/dist/lib/frontmatter.js +61 -0
  286. package/dist/lib/frontmatter.js.map +1 -0
  287. package/dist/lib/frontmatter.test.d.ts +2 -0
  288. package/dist/lib/frontmatter.test.d.ts.map +1 -0
  289. package/dist/lib/frontmatter.test.js +257 -0
  290. package/dist/lib/frontmatter.test.js.map +1 -0
  291. package/dist/lib/template.d.ts +24 -0
  292. package/dist/lib/template.d.ts.map +1 -0
  293. package/dist/lib/template.js +78 -0
  294. package/dist/lib/template.js.map +1 -0
  295. package/dist/lib/template.test.d.ts +2 -0
  296. package/dist/lib/template.test.d.ts.map +1 -0
  297. package/dist/lib/template.test.js +201 -0
  298. package/dist/lib/template.test.js.map +1 -0
  299. package/dist/types/index.d.ts +48 -0
  300. package/dist/types/index.d.ts.map +1 -0
  301. package/dist/types/index.js +2 -0
  302. package/dist/types/index.js.map +1 -0
  303. package/dist/ui/AnalyzeUI.d.ts +7 -0
  304. package/dist/ui/AnalyzeUI.d.ts.map +1 -0
  305. package/dist/ui/AnalyzeUI.js +100 -0
  306. package/dist/ui/AnalyzeUI.js.map +1 -0
  307. package/dist/ui/App.d.ts +13 -0
  308. package/dist/ui/App.d.ts.map +1 -0
  309. package/dist/ui/App.js +100 -0
  310. package/dist/ui/App.js.map +1 -0
  311. package/dist/ui/CIContext.d.ts +9 -0
  312. package/dist/ui/CIContext.d.ts.map +1 -0
  313. package/dist/ui/CIContext.js +9 -0
  314. package/dist/ui/CIContext.js.map +1 -0
  315. package/dist/ui/CISelector.d.ts +8 -0
  316. package/dist/ui/CISelector.d.ts.map +1 -0
  317. package/dist/ui/CISelector.js +45 -0
  318. package/dist/ui/CISelector.js.map +1 -0
  319. package/dist/ui/Doctor.d.ts +3 -0
  320. package/dist/ui/Doctor.d.ts.map +1 -0
  321. package/dist/ui/Doctor.js +89 -0
  322. package/dist/ui/Doctor.js.map +1 -0
  323. package/dist/ui/Header.d.ts +8 -0
  324. package/dist/ui/Header.d.ts.map +1 -0
  325. package/dist/ui/Header.js +30 -0
  326. package/dist/ui/Header.js.map +1 -0
  327. package/dist/ui/MemorySelector.d.ts +8 -0
  328. package/dist/ui/MemorySelector.d.ts.map +1 -0
  329. package/dist/ui/MemorySelector.js +46 -0
  330. package/dist/ui/MemorySelector.js.map +1 -0
  331. package/dist/ui/NameInput.d.ts +8 -0
  332. package/dist/ui/NameInput.d.ts.map +1 -0
  333. package/dist/ui/NameInput.js +69 -0
  334. package/dist/ui/NameInput.js.map +1 -0
  335. package/dist/ui/OptionSelector.d.ts +12 -0
  336. package/dist/ui/OptionSelector.d.ts.map +1 -0
  337. package/dist/ui/OptionSelector.js +69 -0
  338. package/dist/ui/OptionSelector.js.map +1 -0
  339. package/dist/ui/Progress.d.ts +11 -0
  340. package/dist/ui/Progress.d.ts.map +1 -0
  341. package/dist/ui/Progress.js +58 -0
  342. package/dist/ui/Progress.js.map +1 -0
  343. package/dist/ui/StackSelector.d.ts +9 -0
  344. package/dist/ui/StackSelector.d.ts.map +1 -0
  345. package/dist/ui/StackSelector.js +65 -0
  346. package/dist/ui/StackSelector.js.map +1 -0
  347. package/dist/ui/Summary.d.ts +12 -0
  348. package/dist/ui/Summary.d.ts.map +1 -0
  349. package/dist/ui/Summary.js +114 -0
  350. package/dist/ui/Summary.js.map +1 -0
  351. package/dist/ui/SyncUI.d.ts +10 -0
  352. package/dist/ui/SyncUI.d.ts.map +1 -0
  353. package/dist/ui/SyncUI.js +64 -0
  354. package/dist/ui/SyncUI.js.map +1 -0
  355. package/dist/ui/Welcome.d.ts +7 -0
  356. package/dist/ui/Welcome.d.ts.map +1 -0
  357. package/dist/ui/Welcome.js +45 -0
  358. package/dist/ui/Welcome.js.map +1 -0
  359. package/dist/ui/theme.d.ts +10 -0
  360. package/dist/ui/theme.d.ts.map +1 -0
  361. package/dist/ui/theme.js +9 -0
  362. package/dist/ui/theme.js.map +1 -0
  363. package/modules/engram/.gitignore-snippet.txt +6 -0
  364. package/modules/engram/.mcp-config-snippet.json +11 -0
  365. package/modules/engram/README.md +146 -0
  366. package/modules/engram/install-engram.sh +216 -0
  367. package/modules/ghagga/.env.example +43 -0
  368. package/modules/ghagga/README.md +153 -0
  369. package/modules/ghagga/docker-compose.yml +80 -0
  370. package/modules/ghagga/setup-ghagga.sh +139 -0
  371. package/modules/memory-simple/.project/NOTES.md +22 -0
  372. package/modules/memory-simple/README.md +23 -0
  373. package/modules/obsidian-brain/.obsidian/app.json +23 -0
  374. package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
  375. package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
  376. package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
  377. package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
  378. package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
  379. package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
  380. package/modules/obsidian-brain/.obsidian/graph.json +37 -0
  381. package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
  382. package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
  383. package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
  384. package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
  385. package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
  386. package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
  387. package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
  388. package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
  389. package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
  390. package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
  391. package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
  392. package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
  393. package/modules/obsidian-brain/.project/Memory/README.md +61 -0
  394. package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
  395. package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
  396. package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
  397. package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
  398. package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
  399. package/modules/obsidian-brain/README.md +268 -0
  400. package/modules/obsidian-brain/new-wave.sh +182 -0
  401. package/package.json +51 -0
  402. package/schemas/agent.schema.json +34 -0
  403. package/schemas/ai-config.schema.json +28 -0
  404. package/schemas/skill.schema.json +44 -0
  405. package/src/commands/analyze.test.ts +145 -0
  406. package/src/commands/analyze.ts +69 -0
  407. package/src/commands/doctor.test.ts +208 -0
  408. package/src/commands/doctor.ts +163 -0
  409. package/src/commands/init.test.ts +298 -0
  410. package/src/commands/init.ts +285 -0
  411. package/src/constants.ts +69 -0
  412. package/src/e2e/aggressive.e2e.test.ts +557 -0
  413. package/src/e2e/commands.e2e.test.ts +298 -0
  414. package/src/index.tsx +106 -0
  415. package/src/lib/common.test.ts +318 -0
  416. package/src/lib/common.ts +127 -0
  417. package/src/lib/frontmatter.test.ts +291 -0
  418. package/src/lib/frontmatter.ts +77 -0
  419. package/src/lib/template.test.ts +226 -0
  420. package/src/lib/template.ts +99 -0
  421. package/src/types/index.ts +53 -0
  422. package/src/ui/AnalyzeUI.tsx +133 -0
  423. package/src/ui/App.tsx +175 -0
  424. package/src/ui/CIContext.tsx +25 -0
  425. package/src/ui/CISelector.tsx +72 -0
  426. package/src/ui/Doctor.tsx +122 -0
  427. package/src/ui/Header.tsx +48 -0
  428. package/src/ui/MemorySelector.tsx +73 -0
  429. package/src/ui/NameInput.tsx +82 -0
  430. package/src/ui/OptionSelector.tsx +100 -0
  431. package/src/ui/Progress.tsx +88 -0
  432. package/src/ui/StackSelector.tsx +101 -0
  433. package/src/ui/Summary.tsx +134 -0
  434. package/src/ui/Welcome.tsx +54 -0
  435. package/src/ui/theme.ts +10 -0
  436. package/stryker.config.json +19 -0
  437. package/tasks/_TEMPLATE/files-edited.md +3 -0
  438. package/tasks/_TEMPLATE/plan.md +3 -0
  439. package/tasks/_TEMPLATE/research.md +3 -0
  440. package/tasks/_TEMPLATE/verification.md +5 -0
  441. package/templates/common/dependabot/cargo.yml +11 -0
  442. package/templates/common/dependabot/github-actions.yml +16 -0
  443. package/templates/common/dependabot/gomod.yml +15 -0
  444. package/templates/common/dependabot/gradle.yml +15 -0
  445. package/templates/common/dependabot/header.yml +3 -0
  446. package/templates/common/dependabot/maven.yml +15 -0
  447. package/templates/common/dependabot/npm.yml +20 -0
  448. package/templates/common/dependabot/pip.yml +11 -0
  449. package/templates/dependabot.yml +162 -0
  450. package/templates/github/ci-go.yml +41 -0
  451. package/templates/github/ci-java.yml +45 -0
  452. package/templates/github/ci-monorepo.yml +150 -0
  453. package/templates/github/ci-node.yml +42 -0
  454. package/templates/github/ci-python.yml +42 -0
  455. package/templates/github/ci-rust.yml +42 -0
  456. package/templates/github/dependabot-automerge.yml +40 -0
  457. package/templates/gitlab/gitlab-ci-go.yml +88 -0
  458. package/templates/gitlab/gitlab-ci-java.yml +79 -0
  459. package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
  460. package/templates/gitlab/gitlab-ci-node.yml +63 -0
  461. package/templates/gitlab/gitlab-ci-python.yml +147 -0
  462. package/templates/gitlab/gitlab-ci-rust.yml +67 -0
  463. package/templates/global/claude-settings.json +98 -0
  464. package/templates/global/codex-config.toml +8 -0
  465. package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
  466. package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
  467. package/templates/global/gemini-commands/cleanup.toml +20 -0
  468. package/templates/global/gemini-commands/commit.toml +15 -0
  469. package/templates/global/gemini-commands/dead-code.toml +22 -0
  470. package/templates/global/gemini-commands/plan.toml +30 -0
  471. package/templates/global/gemini-commands/review.toml +17 -0
  472. package/templates/global/gemini-commands/sdd-apply.toml +22 -0
  473. package/templates/global/gemini-commands/sdd-ff.toml +14 -0
  474. package/templates/global/gemini-commands/sdd-new.toml +21 -0
  475. package/templates/global/gemini-commands/sdd-verify.toml +21 -0
  476. package/templates/global/gemini-commands/tdd.toml +26 -0
  477. package/templates/global/gemini-settings.json +8 -0
  478. package/templates/global/opencode-config.json +44 -0
  479. package/templates/global/sdd-instructions.md +47 -0
  480. package/templates/global/sdd-orchestrator-claude.md +46 -0
  481. package/templates/global/sdd-orchestrator-copilot.md +34 -0
  482. package/templates/renovate.json +69 -0
  483. package/templates/woodpecker/monorepo/backend.yml +34 -0
  484. package/templates/woodpecker/monorepo/frontend.yml +34 -0
  485. package/templates/woodpecker/monorepo/summary.yml +25 -0
  486. package/templates/woodpecker/woodpecker-go.yml +51 -0
  487. package/templates/woodpecker/woodpecker-java.yml +67 -0
  488. package/templates/woodpecker/woodpecker-node.yml +47 -0
  489. package/templates/woodpecker/woodpecker-python.yml +108 -0
  490. package/templates/woodpecker/woodpecker-rust.yml +57 -0
  491. package/tsconfig.json +19 -0
  492. package/vitest.config.ts +16 -0
  493. package/workflows/reusable-build-go.yml +111 -0
  494. package/workflows/reusable-build-java.yml +120 -0
  495. package/workflows/reusable-build-node.yml +145 -0
  496. package/workflows/reusable-build-python.yml +159 -0
  497. package/workflows/reusable-build-rust.yml +135 -0
  498. package/workflows/reusable-docker.yml +120 -0
  499. package/workflows/reusable-ghagga-review.yml +165 -0
  500. package/workflows/reusable-release.yml +91 -0
@@ -0,0 +1,4503 @@
1
+ ---
2
+ name: freelance-project-planner-v4
3
+ description: Agente completo para planificación freelance multi-lenguaje con GitFlow, Stacked PRs, y Preview Environments
4
+ trigger: >
5
+ freelance v4, GitFlow, stacked PRs, preview environments, merge queue, CODEOWNERS,
6
+ multi-language planning, auto-sync, Slack Discord integration, changelog
7
+ category: specialized
8
+ color: green
9
+ tools: Write, Read, MultiEdit, Bash, Grep, Glob
10
+ config:
11
+ model: opus
12
+ metadata:
13
+ version: "2.0"
14
+ updated: "2026-02"
15
+ ---
16
+
17
+ ## 🎯 Filosofía Core: GitFlow + Stacked PRs + Infrastructure First
18
+
19
+ Este agente combina las mejores prácticas de:
20
+ - **GitFlow** - Estrategia de branching profesional
21
+ - **Stacked PRs** - PRs pequeños y apilados para review incremental
22
+ - **Infrastructure First** - Docker y CI/CD como prioridad #1
23
+ - **Aprendizaje Progresivo** - Cada tarea enseña conceptos cuando los necesitas
24
+ - **Kanban Light + XP Adaptado** - Metodología ágil optimizada para freelancers
25
+
26
+ ### Orden de Prioridades
27
+ ```
28
+ 1️⃣ Dockerización completa (dev + prod)
29
+ 2️⃣ GitHub Actions (CI/CD con soporte para stacks)
30
+ 3️⃣ GitFlow setup (branches + protections)
31
+ 4️⃣ Issues organizados en Stacked PRs
32
+ 5️⃣ Desarrollo incremental con review continuo
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 📋 Metodología: Kanban Light + XP Adaptado (Heredado de v1)
38
+
39
+ ### Framework Híbrido para Freelancers
40
+
41
+ Este agente hereda la metodología probada de v1, optimizada para desarrolladores freelance:
42
+
43
+ ```typescript
44
+ const KANBAN_XP_CONFIG = {
45
+ // KANBAN LIGHT
46
+ kanban: {
47
+ // Tablero simplificado
48
+ columns: ['Backlog', 'Ready', 'In Progress', 'Review', 'Done'],
49
+
50
+ // WIP Limits - CRÍTICO para freelancers
51
+ wipLimits: {
52
+ ready: 5,
53
+ inProgress: 2, // Máximo 2 tareas activas
54
+ review: 3
55
+ },
56
+
57
+ // Carriles de prioridad
58
+ priorityLanes: [
59
+ { name: '🔥 Crítico', color: 'red', sla: '24h' },
60
+ { name: '⚡ Alta', color: 'orange', sla: '3d' },
61
+ { name: '📝 Normal', color: 'blue', sla: '1w' },
62
+ { name: '🔧 Técnico', color: 'gray', sla: '2w' }
63
+ ],
64
+
65
+ // Sin ceremonias innecesarias
66
+ ceremonies: {
67
+ dailyStandup: false, // No necesario para solo dev
68
+ weeklyPlanning: true, // Lunes: revisar backlog
69
+ weeklyDemo: true, // Viernes: demo al cliente
70
+ retrospective: 'monthly' // Una vez al mes
71
+ }
72
+ },
73
+
74
+ // XP ADAPTADO
75
+ xp: {
76
+ // TDD Selectivo - Solo en áreas críticas
77
+ tdd: {
78
+ enabled: true,
79
+ scope: 'critical-only',
80
+ criticalAreas: [
81
+ 'payment-processing',
82
+ 'authentication',
83
+ 'data-validation',
84
+ 'public-apis',
85
+ 'business-logic-core'
86
+ ]
87
+ },
88
+
89
+ // CI/CD Obligatorio
90
+ continuousIntegration: {
91
+ required: true,
92
+ onEveryPR: true,
93
+ testsRequired: ['unit', 'lint', 'type-check'],
94
+ optionalTests: ['integration', 'e2e']
95
+ },
96
+
97
+ // Refactorización Planificada
98
+ refactoring: {
99
+ schedule: 'friday-after-demo',
100
+ maxTimePerWeek: '2h',
101
+ triggerThreshold: {
102
+ cyclomaticComplexity: 10,
103
+ duplicateCode: 0.8
104
+ }
105
+ },
106
+
107
+ // Pair Programming (adaptado)
108
+ pairProgramming: {
109
+ mode: 'rubber-duck', // Hablar con uno mismo o el pato
110
+ codeReview: 'self-review-24h', // Auto-review después de 24h
111
+ aiAssisted: true // Usar Claude como pair
112
+ },
113
+
114
+ // Diseño Simple
115
+ simpleDesign: {
116
+ yagni: true, // No construir lo que no necesitas
117
+ kiss: true, // Mantener simple
118
+ dryThreshold: 3 // Extraer después de 3 repeticiones
119
+ }
120
+ }
121
+ };
122
+ ```
123
+
124
+ ### Tablero Kanban con Stacked PRs
125
+
126
+ ```
127
+ ┌─────────────────────────────────────────────────────────────────────────────┐
128
+ │ 📋 KANBAN BOARD - Proyecto X │
129
+ ├─────────────┬─────────────┬─────────────┬─────────────┬─────────────────────┤
130
+ │ 📥 BACKLOG │ ✅ READY │ 🔨 IN PROG │ 👀 REVIEW │ ✅ DONE │
131
+ │ │ (max 5) │ (max 2) │ (max 3) │ │
132
+ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────────────┤
133
+ │ │ │ │ │ │
134
+ │ 🔥 CRÍTICO │ │ │ │ │
135
+ │ ─────────── │ │ ┌─────────┐ │ │ │
136
+ │ │ │ │Stack #1 │ │ │ │
137
+ │ │ │ │PR 02/04 │ │ │ │
138
+ │ │ │ │Auth API │ │ │ │
139
+ │ │ │ └─────────┘ │ │ │
140
+ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────────────┤
141
+ │ ⚡ ALTA │ │ │ │ │
142
+ │ ─────────── │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │
143
+ │ ┌─────────┐ │ │Stack #2 │ │ │Stack #1 │ │ │Stack #1 │ │ │Stack #1 │ │
144
+ │ │Feature │ │ │PR 01/03 │ │ │PR 03/04 │ │ │PR 01/04 │ │ │PR 01/04 │ │
145
+ │ │Payment │ │ │Stripe │ │ │JWT Mid │ │ │DB Schema│ │ │Complete │ │
146
+ │ └─────────┘ │ └─────────┘ │ └─────────┘ │ └─────────┘ │ └─────────┘ │
147
+ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────────────┤
148
+ │ 📝 NORMAL │ │ │ │ │
149
+ │ ─────────── │ ┌─────────┐ │ │ │ │
150
+ │ ┌─────────┐ │ │Bug fix │ │ │ │ │
151
+ │ │Feature │ │ │#234 │ │ │ │ │
152
+ │ │Reports │ │ └─────────┘ │ │ │ │
153
+ │ └─────────┘ │ │ │ │ │
154
+ ├─────────────┼─────────────┼─────────────┼─────────────┼─────────────────────┤
155
+ │ 🔧 TÉCNICO │ │ │ │ │
156
+ │ ─────────── │ ┌─────────┐ │ │ │ │
157
+ │ ┌─────────┐ │ │Refactor │ │ │ │ │
158
+ │ │Tech Debt│ │ │Utils │ │ │ │ │
159
+ │ │Cleanup │ │ └─────────┘ │ │ │ │
160
+ │ └─────────┘ │ │ │ │ │
161
+ └─────────────┴─────────────┴─────────────┴─────────────┴─────────────────────┘
162
+
163
+ 📊 WIP Status: 2/2 (límite alcanzado)
164
+ 📈 Velocity: 8 PRs/semana
165
+ ⏱️ Avg Cycle Time: 1.5 días
166
+ ```
167
+
168
+ ### TDD Selectivo en Stacked PRs
169
+
170
+ ```typescript
171
+ class SelectiveTDDStrategy {
172
+ /**
173
+ * Determina si un PR del stack requiere TDD
174
+ */
175
+ shouldUseTDD(pr: StackedPR): boolean {
176
+ // TDD obligatorio en áreas críticas
177
+ const criticalPatterns = [
178
+ /payment/i,
179
+ /auth/i,
180
+ /security/i,
181
+ /validation/i,
182
+ /api.*endpoint/i
183
+ ];
184
+
185
+ const isCritical = criticalPatterns.some(pattern =>
186
+ pattern.test(pr.title) || pattern.test(pr.branch)
187
+ );
188
+
189
+ // TDD recomendado si tiene lógica de negocio compleja
190
+ const hasComplexLogic = pr.estimatedLines > 200 ||
191
+ pr.conceptsIntroduced.length > 2;
192
+
193
+ return isCritical || hasComplexLogic;
194
+ }
195
+
196
+ /**
197
+ * Genera guía de TDD para el PR
198
+ */
199
+ generateTDDGuide(pr: StackedPR): string {
200
+ if (!this.shouldUseTDD(pr)) {
201
+ return `
202
+ ### 🧪 Testing para este PR
203
+
204
+ Este PR no requiere TDD estricto, pero sí:
205
+ - [ ] Tests unitarios básicos
206
+ - [ ] Verificación manual
207
+
208
+ **Razón**: No es área crítica del sistema.
209
+ `;
210
+ }
211
+
212
+ return `
213
+ ### 🧪 TDD Requerido para este PR
214
+
215
+ Este PR está en un **área crítica** y requiere Test-Driven Development:
216
+
217
+ #### Ciclo TDD
218
+ \`\`\`
219
+ 1. 🔴 RED: Escribir test que falla
220
+ 2. 🟢 GREEN: Código mínimo para pasar
221
+ 3. 🔵 REFACTOR: Mejorar sin romper tests
222
+ \`\`\`
223
+
224
+ #### Tests Requeridos
225
+ ${this.generateRequiredTests(pr)}
226
+
227
+ #### Cobertura Mínima
228
+ - Líneas: 80%
229
+ - Branches: 75%
230
+ - Functions: 90%
231
+
232
+ **⚠️ El PR no será aprobado sin tests adecuados.**
233
+ `;
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### CI/CD con Soporte Kanban + Stacks
239
+
240
+ ```yaml
241
+ # .github/workflows/kanban-ci.yml
242
+ name: Kanban + Stacked PR CI
243
+
244
+ on:
245
+ pull_request:
246
+ types: [opened, synchronize, reopened, labeled]
247
+
248
+ jobs:
249
+ # Verificar WIP limits
250
+ check-wip:
251
+ runs-on: ubuntu-latest
252
+ steps:
253
+ - name: Check WIP Limits
254
+ uses: actions/github-script@v7
255
+ with:
256
+ script: |
257
+ // Contar PRs "in progress" del autor
258
+ const author = context.payload.pull_request.user.login;
259
+ const { data: prs } = await github.rest.pulls.list({
260
+ owner: context.repo.owner,
261
+ repo: context.repo.repo,
262
+ state: 'open',
263
+ });
264
+
265
+ const inProgressPRs = prs.filter(pr =>
266
+ pr.user.login === author &&
267
+ pr.labels.some(l => l.name === 'in-progress')
268
+ );
269
+
270
+ const WIP_LIMIT = 2;
271
+
272
+ if (inProgressPRs.length >= WIP_LIMIT) {
273
+ core.warning(`⚠️ WIP Limit alcanzado (${inProgressPRs.length}/${WIP_LIMIT})`);
274
+ core.warning('Completa PRs existentes antes de iniciar nuevos');
275
+ }
276
+
277
+ # Tests según criticidad
278
+ selective-tests:
279
+ runs-on: ubuntu-latest
280
+ outputs:
281
+ needs_tdd: ${{ steps.check.outputs.needs_tdd }}
282
+ steps:
283
+ - uses: actions/checkout@v4
284
+
285
+ - name: Detect Critical Area
286
+ id: check
287
+ run: |
288
+ FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
289
+
290
+ # Patrones críticos que requieren TDD
291
+ if echo "$FILES" | grep -qE "(payment|auth|security|validation)"; then
292
+ echo "needs_tdd=true" >> $GITHUB_OUTPUT
293
+ echo "🔴 Área crítica detectada - TDD requerido"
294
+ else
295
+ echo "needs_tdd=false" >> $GITHUB_OUTPUT
296
+ fi
297
+
298
+ - name: Run Standard Tests
299
+ run: |
300
+ npm ci
301
+ npm run lint
302
+ npm run type-check
303
+ npm run test:unit
304
+
305
+ - name: Run Extended Tests (Critical Areas)
306
+ if: steps.check.outputs.needs_tdd == 'true'
307
+ run: |
308
+ npm run test:integration
309
+ npm run test:coverage -- --threshold 80
310
+ ```
311
+
312
+ ### Refactorización Continua Planificada
313
+
314
+ ```typescript
315
+ class RefactoringScheduler {
316
+ /**
317
+ * Programa tareas de refactorización según métricas
318
+ */
319
+ async scheduleRefactoring(
320
+ analysis: ProjectAnalysis
321
+ ): Promise<RefactoringTask[]> {
322
+ const tasks: RefactoringTask[] = [];
323
+
324
+ // Análisis de complejidad ciclomática
325
+ for (const module of analysis.modules) {
326
+ if (module.cyclomaticComplexity > 10) {
327
+ tasks.push({
328
+ type: 'complexity',
329
+ title: `🔧 Simplificar: ${module.name}`,
330
+ priority: this.calculatePriority(module.cyclomaticComplexity),
331
+ estimate: `${Math.ceil(module.cyclomaticComplexity / 3)}h`,
332
+ schedule: 'friday-after-demo',
333
+ benefits: ['Mejor mantenibilidad', 'Menos bugs', 'Más testeable']
334
+ });
335
+ }
336
+ }
337
+
338
+ // Análisis de código duplicado
339
+ for (const dup of analysis.duplications) {
340
+ if (dup.similarity > 0.8 && dup.occurrences >= 3) {
341
+ tasks.push({
342
+ type: 'duplication',
343
+ title: `🔧 Extraer: ${dup.pattern}`,
344
+ priority: 'P2',
345
+ estimate: '2h',
346
+ schedule: 'friday-after-demo',
347
+ affectedFiles: dup.files
348
+ });
349
+ }
350
+ }
351
+
352
+ return tasks;
353
+ }
354
+
355
+ /**
356
+ * Integra refactorización como PRs del stack
357
+ */
358
+ createRefactoringStack(tasks: RefactoringTask[]): StackedPR[] {
359
+ // Agrupar refactorizaciones relacionadas
360
+ const groups = this.groupRelatedRefactorings(tasks);
361
+
362
+ return groups.map((group, index) => ({
363
+ stackOrder: index + 1,
364
+ branch: `refactor/${this.slugify(group.name)}`,
365
+ title: `🔧 Refactor: ${group.name}`,
366
+ type: 'technical',
367
+ learningObjectives: [
368
+ 'Patrones de refactorización',
369
+ 'Clean code principles',
370
+ 'SOLID aplicado'
371
+ ]
372
+ }));
373
+ }
374
+ }
375
+ ```
376
+
377
+ ### Rutina Semanal del Freelancer
378
+
379
+ ```typescript
380
+ const WEEKLY_ROUTINE = {
381
+ monday: {
382
+ name: 'Planificación',
383
+ activities: [
384
+ 'Review del backlog',
385
+ 'Priorizar tareas de la semana',
386
+ 'Actualizar tablero Kanban',
387
+ 'Identificar stacks a trabajar',
388
+ 'Comunicar plan al cliente (opcional)'
389
+ ],
390
+ duration: '1-2h'
391
+ },
392
+
393
+ tuesdayToThursday: {
394
+ name: 'Desarrollo',
395
+ activities: [
396
+ 'Tomar PR del stack (máximo 2 activos)',
397
+ 'TDD en áreas críticas',
398
+ 'Commits pequeños y frecuentes',
399
+ 'Self-review antes de marcar ready',
400
+ 'Mover siguiente PR del stack cuando se mergea'
401
+ ],
402
+ wipLimit: 2,
403
+ focusTime: '4-6h/día'
404
+ },
405
+
406
+ friday: {
407
+ name: 'Review y Demo',
408
+ morning: [
409
+ 'Code review personal (24h después de escribir)',
410
+ 'Preparar demo',
411
+ 'Actualizar documentación'
412
+ ],
413
+ afternoon: [
414
+ 'Demo al cliente (15-20 min)',
415
+ 'Recoger feedback',
416
+ 'Refactorización planificada (2h máx)',
417
+ 'Retrospectiva personal'
418
+ ],
419
+ demoFormat: {
420
+ duration: '15-20 min',
421
+ structure: [
422
+ '5 min: Qué se completó (PRs mergeados)',
423
+ '10 min: Demo de funcionalidad',
424
+ '5 min: Próximos pasos y feedback'
425
+ ]
426
+ }
427
+ }
428
+ };
429
+ ```
430
+
431
+ ---
432
+
433
+ ## 🌳 GitFlow: Estrategia de Branching
434
+
435
+ ### Estructura de Ramas
436
+
437
+ ```
438
+ main (producción)
439
+
440
+ ├── hotfix/urgent-fix ──────────────────────────┐
441
+ │ │
442
+ develop (integración) ◄─────────────────────────┤
443
+ │ │
444
+ ├── release/v1.0.0 ─────────────────────────────┤
445
+ │ │
446
+ ├── feature/user-auth ◄── Stack de PRs │
447
+ │ ├── feature/user-auth/01-db-schema │
448
+ │ ├── feature/user-auth/02-api-endpoints │
449
+ │ ├── feature/user-auth/03-jwt-middleware │
450
+ │ └── feature/user-auth/04-frontend-forms │
451
+ │ │
452
+ └── feature/payment-system ◄── Otro stack │
453
+ ├── feature/payment-system/01-stripe-setup │
454
+ ├── feature/payment-system/02-checkout-api │
455
+ └── feature/payment-system/03-webhooks │
456
+ ```
457
+
458
+ ### Reglas de GitFlow
459
+
460
+ ```typescript
461
+ const GITFLOW_RULES = {
462
+ branches: {
463
+ main: {
464
+ purpose: 'Código en producción',
465
+ protection: 'strict',
466
+ mergeFrom: ['release/*', 'hotfix/*'],
467
+ directCommits: false
468
+ },
469
+ develop: {
470
+ purpose: 'Integración de features completadas',
471
+ protection: 'standard',
472
+ mergeFrom: ['feature/*', 'release/*', 'hotfix/*'],
473
+ directCommits: false
474
+ },
475
+ 'feature/*': {
476
+ purpose: 'Desarrollo de nuevas funcionalidades',
477
+ basedOn: 'develop',
478
+ mergeTo: 'develop',
479
+ naming: 'feature/{feature-name}/{stack-number}-{description}'
480
+ },
481
+ 'release/*': {
482
+ purpose: 'Preparación de releases',
483
+ basedOn: 'develop',
484
+ mergeTo: ['main', 'develop'],
485
+ naming: 'release/v{major}.{minor}.{patch}'
486
+ },
487
+ 'hotfix/*': {
488
+ purpose: 'Fixes urgentes en producción',
489
+ basedOn: 'main',
490
+ mergeTo: ['main', 'develop'],
491
+ naming: 'hotfix/{issue-id}-{description}'
492
+ }
493
+ }
494
+ };
495
+ ```
496
+
497
+ ---
498
+
499
+ ## 📚 Stacked PRs: La Estrategia
500
+
501
+ ### ¿Qué son los Stacked PRs?
502
+
503
+ ```
504
+ ❌ ENFOQUE TRADICIONAL (PR Monolítico):
505
+ ┌─────────────────────────────────────────────────────────────┐
506
+ │ PR #45: "Implementar sistema de autenticación" │
507
+ │ ├── 2,500 líneas cambiadas │
508
+ │ ├── 45 archivos modificados │
509
+ │ ├── Review time: 3-5 días │
510
+ │ └── Conflictos frecuentes con develop │
511
+ │ │
512
+ │ Resultado: 😫 Reviews eternos, merge hell │
513
+ └─────────────────────────────────────────────────────────────┘
514
+
515
+ ✅ ENFOQUE STACKED PRs (Este agente):
516
+ ┌─────────────────────────────────────────────────────────────┐
517
+ │ Stack: "Sistema de Autenticación" │
518
+ │ │
519
+ │ PR #45: "01-db-schema" (base) │
520
+ │ ├── 150 líneas, 3 archivos │
521
+ │ ├── Review: 30 min │
522
+ │ └── ✅ Merged │
523
+ │ │ │
524
+ │ ▼ │
525
+ │ PR #46: "02-api-endpoints" (depende de #45) │
526
+ │ ├── 200 líneas, 5 archivos │
527
+ │ ├── Review: 45 min │
528
+ │ └── ✅ Merged │
529
+ │ │ │
530
+ │ ▼ │
531
+ │ PR #47: "03-jwt-middleware" (depende de #46) │
532
+ │ ├── 180 líneas, 4 archivos │
533
+ │ └── 🔄 En review │
534
+ │ │ │
535
+ │ ▼ │
536
+ │ PR #48: "04-frontend-forms" (depende de #47) │
537
+ │ ├── 300 líneas, 8 archivos │
538
+ │ └── ⏳ Draft (esperando #47) │
539
+ │ │
540
+ │ Resultado: 🚀 Reviews rápidos, merge incremental │
541
+ └─────────────────────────────────────────────────────────────┘
542
+ ```
543
+
544
+ ### Beneficios de Stacked PRs
545
+
546
+ | Aspecto | PR Monolítico | Stacked PRs |
547
+ |---------|---------------|-------------|
548
+ | **Tamaño** | 1000+ líneas | 100-300 líneas |
549
+ | **Review time** | 3-5 días | 30-60 min cada |
550
+ | **Calidad review** | Superficial | Profundo |
551
+ | **Conflictos** | Frecuentes | Raros |
552
+ | **Rollback** | Todo o nada | Granular |
553
+ | **Feedback** | Al final | Continuo |
554
+ | **Aprendizaje** | Tardío | Inmediato |
555
+
556
+ ---
557
+
558
+ ## 🔧 Implementación del Sistema
559
+
560
+ ### 1. Estructura de una Iteración con Stacked PRs
561
+
562
+ ```typescript
563
+ interface StackedIteration {
564
+ iterationNumber: number;
565
+ feature: string;
566
+
567
+ // El stack de PRs para esta iteración
568
+ prStack: StackedPR[];
569
+
570
+ // Configuración del stack
571
+ stackConfig: {
572
+ baseBranch: 'develop';
573
+ featureBranch: string; // feature/{feature-name}
574
+ namingPattern: '{feature}/{number}-{slug}';
575
+ };
576
+ }
577
+
578
+ interface StackedPR {
579
+ stackOrder: number; // Posición en el stack (01, 02, 03...)
580
+ branch: string; // feature/auth/01-db-schema
581
+ dependsOn: string | null; // Branch del que depende
582
+
583
+ // Contenido del PR
584
+ title: string;
585
+ description: string;
586
+ changes: FileChange[];
587
+
588
+ // Metadatos
589
+ estimatedReviewTime: string;
590
+ linesChanged: number;
591
+ filesAffected: number;
592
+
593
+ // Aprendizaje (de v3)
594
+ learningObjectives: string[];
595
+ conceptsIntroduced: string[];
596
+ }
597
+
598
+ // Ejemplo de un stack completo
599
+ const authenticationStack: StackedIteration = {
600
+ iterationNumber: 1,
601
+ feature: 'user-authentication',
602
+ stackConfig: {
603
+ baseBranch: 'develop',
604
+ featureBranch: 'feature/user-auth',
605
+ namingPattern: 'feature/user-auth/{number}-{slug}'
606
+ },
607
+ prStack: [
608
+ {
609
+ stackOrder: 1,
610
+ branch: 'feature/user-auth/01-db-schema',
611
+ dependsOn: null, // Base del stack
612
+ title: '01: Database schema for users',
613
+ description: 'Add User model and migrations',
614
+ estimatedReviewTime: '30 min',
615
+ linesChanged: 150,
616
+ filesAffected: 4,
617
+ learningObjectives: ['Database migrations', 'User model design'],
618
+ conceptsIntroduced: ['Prisma schema', 'Database relations']
619
+ },
620
+ {
621
+ stackOrder: 2,
622
+ branch: 'feature/user-auth/02-api-endpoints',
623
+ dependsOn: 'feature/user-auth/01-db-schema',
624
+ title: '02: Auth API endpoints',
625
+ description: 'POST /auth/register, POST /auth/login',
626
+ estimatedReviewTime: '45 min',
627
+ linesChanged: 200,
628
+ filesAffected: 6,
629
+ learningObjectives: ['REST API design', 'Input validation'],
630
+ conceptsIntroduced: ['Zod validation', 'API routes']
631
+ },
632
+ {
633
+ stackOrder: 3,
634
+ branch: 'feature/user-auth/03-jwt-middleware',
635
+ dependsOn: 'feature/user-auth/02-api-endpoints',
636
+ title: '03: JWT authentication middleware',
637
+ description: 'Token generation and validation',
638
+ estimatedReviewTime: '45 min',
639
+ linesChanged: 180,
640
+ filesAffected: 5,
641
+ learningObjectives: ['JWT tokens', 'Middleware pattern'],
642
+ conceptsIntroduced: ['jose library', 'Auth middleware']
643
+ },
644
+ {
645
+ stackOrder: 4,
646
+ branch: 'feature/user-auth/04-frontend-forms',
647
+ dependsOn: 'feature/user-auth/03-jwt-middleware',
648
+ title: '04: Login and register forms',
649
+ description: 'React components with form validation',
650
+ estimatedReviewTime: '60 min',
651
+ linesChanged: 350,
652
+ filesAffected: 10,
653
+ learningObjectives: ['React forms', 'Client-side validation'],
654
+ conceptsIntroduced: ['React Hook Form', 'Toast notifications']
655
+ }
656
+ ]
657
+ };
658
+ ```
659
+
660
+ ### 2. Generador de Stacked PRs
661
+
662
+ ```typescript
663
+ class StackedPRGenerator {
664
+ private githubMCP: GitHubMCPClient;
665
+
666
+ /**
667
+ * Genera un stack completo de PRs para una feature
668
+ */
669
+ async generateFeatureStack(
670
+ repo: Repository,
671
+ feature: FeatureAnalysis,
672
+ iteration: Iteration
673
+ ): Promise<PRStack> {
674
+
675
+ // 1. Dividir la feature en chunks lógicos
676
+ const chunks = await this.divideFeatureIntoChunks(feature);
677
+
678
+ // 2. Crear la rama base de la feature
679
+ const featureBranch = `feature/${this.slugify(feature.name)}`;
680
+ await this.createBranch(repo, featureBranch, 'develop');
681
+
682
+ // 3. Generar cada PR del stack
683
+ const stack: StackedPR[] = [];
684
+ let previousBranch = 'develop';
685
+
686
+ for (let i = 0; i < chunks.length; i++) {
687
+ const chunk = chunks[i];
688
+ const stackNumber = String(i + 1).padStart(2, '0');
689
+ const branchName = `${featureBranch}/${stackNumber}-${chunk.slug}`;
690
+
691
+ // Crear branch basada en la anterior
692
+ await this.createBranch(repo, branchName, previousBranch);
693
+
694
+ // Generar el PR (como draft si no es el primero)
695
+ const pr = await this.createStackedPR(repo, {
696
+ branch: branchName,
697
+ base: previousBranch,
698
+ title: `[${iteration.number}/${stackNumber}] ${chunk.title}`,
699
+ body: this.generatePRBody(chunk, i, chunks.length, feature),
700
+ draft: i > 0, // Solo el primero no es draft
701
+ labels: this.generateLabels(chunk, stackNumber),
702
+ });
703
+
704
+ stack.push({
705
+ ...pr,
706
+ stackOrder: i + 1,
707
+ dependsOn: i === 0 ? null : stack[i - 1].branch,
708
+ chunk: chunk
709
+ });
710
+
711
+ previousBranch = branchName;
712
+ }
713
+
714
+ // 4. Crear issue de tracking del stack
715
+ await this.createStackTrackingIssue(repo, feature, stack);
716
+
717
+ return { feature, stack, featureBranch };
718
+ }
719
+
720
+ /**
721
+ * Divide una feature en chunks reviewables
722
+ * Regla: Máximo 300 líneas por PR, máximo 10 archivos
723
+ */
724
+ private async divideFeatureIntoChunks(feature: FeatureAnalysis): Promise<Chunk[]> {
725
+ const chunks: Chunk[] = [];
726
+
727
+ // Estrategia de división por capas
728
+ const layers = [
729
+ { name: 'database', pattern: ['schema', 'migrations', 'models'] },
730
+ { name: 'backend', pattern: ['api', 'services', 'middleware'] },
731
+ { name: 'frontend', pattern: ['components', 'hooks', 'pages'] },
732
+ { name: 'tests', pattern: ['test', 'spec', '__tests__'] },
733
+ ];
734
+
735
+ for (const layer of layers) {
736
+ const layerChanges = feature.changes.filter(c =>
737
+ layer.pattern.some(p => c.path.includes(p))
738
+ );
739
+
740
+ if (layerChanges.length > 0) {
741
+ // Si el layer es muy grande, subdividir
742
+ if (this.calculateLines(layerChanges) > 300) {
743
+ chunks.push(...this.subdivideLayer(layer.name, layerChanges));
744
+ } else {
745
+ chunks.push({
746
+ slug: layer.name,
747
+ title: this.generateChunkTitle(layer.name, feature),
748
+ changes: layerChanges,
749
+ layer: layer.name
750
+ });
751
+ }
752
+ }
753
+ }
754
+
755
+ return this.orderChunksByDependency(chunks);
756
+ }
757
+
758
+ /**
759
+ * Genera el body del PR con formato de stack
760
+ */
761
+ private generatePRBody(
762
+ chunk: Chunk,
763
+ index: number,
764
+ total: number,
765
+ feature: FeatureAnalysis
766
+ ): string {
767
+ return `
768
+ ## 📚 Stack Progress: ${index + 1}/${total}
769
+
770
+ \`\`\`
771
+ ${this.generateStackVisualization(index, total)}
772
+ \`\`\`
773
+
774
+ ---
775
+
776
+ ## 🎯 Objetivo de este PR
777
+
778
+ ${chunk.description}
779
+
780
+ ## 📦 Parte del Stack: "${feature.name}"
781
+
782
+ | # | PR | Estado |
783
+ |---|-----|--------|
784
+ ${this.generateStackTable(index, total)}
785
+
786
+ ## 🔗 Dependencias
787
+
788
+ ${index === 0
789
+ ? '✅ Este es el **base** del stack - no tiene dependencias'
790
+ : `⬆️ Depende de: PR anterior en el stack`
791
+ }
792
+
793
+ ${index < total - 1
794
+ ? `⬇️ Siguiente PR depende de este`
795
+ : `🏁 Este es el **último** PR del stack`
796
+ }
797
+
798
+ ---
799
+
800
+ ## 📚 Lo que Aprenderás (Aprendizaje Progresivo)
801
+
802
+ ${chunk.learningObjectives?.map(obj => `- ${obj}`).join('\n') || 'N/A'}
803
+
804
+ ## 📖 Conceptos Introducidos
805
+
806
+ ${chunk.conceptsIntroduced?.map(c => `\`${c}\``).join(' • ') || 'N/A'}
807
+
808
+ ---
809
+
810
+ ## ✅ Checklist
811
+
812
+ - [ ] Código sigue las convenciones del proyecto
813
+ - [ ] Tests agregados/actualizados
814
+ - [ ] Sin console.logs o código de debug
815
+ - [ ] PR es pequeño y enfocado (~${chunk.estimatedLines} líneas)
816
+
817
+ ## 🧪 Cómo Probar
818
+
819
+ \`\`\`bash
820
+ # Checkout este stack
821
+ git fetch origin ${chunk.branch}
822
+ git checkout ${chunk.branch}
823
+
824
+ # Ejecutar tests
825
+ npm test
826
+
827
+ # Verificar localmente
828
+ npm run dev
829
+ \`\`\`
830
+
831
+ ---
832
+
833
+ ## 📝 Notas para el Reviewer
834
+
835
+ ${chunk.reviewNotes || 'Revisar los cambios y aprobar si todo está correcto.'}
836
+
837
+ ---
838
+
839
+ _🔗 Este PR es parte de un **Stacked PR**. Por favor, revisar en orden._
840
+ _📚 Generado con freelance-project-planner-v4_
841
+ `;
842
+ }
843
+
844
+ /**
845
+ * Visualización ASCII del progreso del stack
846
+ */
847
+ private generateStackVisualization(current: number, total: number): string {
848
+ let viz = '';
849
+ for (let i = 0; i < total; i++) {
850
+ const status = i < current ? '✅' : i === current ? '👉' : '⏳';
851
+ const connector = i < total - 1 ? '\n │\n ▼\n' : '';
852
+ viz += `${status} PR ${String(i + 1).padStart(2, '0')}${connector}`;
853
+ }
854
+ return viz;
855
+ }
856
+
857
+ /**
858
+ * Crea issue de tracking para el stack completo
859
+ */
860
+ private async createStackTrackingIssue(
861
+ repo: Repository,
862
+ feature: FeatureAnalysis,
863
+ stack: StackedPR[]
864
+ ): Promise<Issue> {
865
+ const body = `
866
+ # 📚 Stack Tracking: ${feature.name}
867
+
868
+ ## 📊 Estado del Stack
869
+
870
+ | # | PR | Branch | Estado | Lines |
871
+ |---|-----|--------|--------|-------|
872
+ ${stack.map((pr, i) =>
873
+ `| ${i + 1} | #${pr.number} | \`${pr.branch}\` | ${i === 0 ? '🔄 Review' : '📝 Draft'} | ~${pr.linesChanged} |`
874
+ ).join('\n')}
875
+
876
+ ## 📈 Progreso
877
+
878
+ \`\`\`
879
+ [${stack.map((_, i) => i === 0 ? '🔄' : '⬜').join('')}] 0/${stack.length} merged
880
+ \`\`\`
881
+
882
+ ## 🔗 Orden de Merge
883
+
884
+ 1. Merge PR #${stack[0].number} a \`develop\`
885
+ 2. Actualizar base de PR #${stack[1]?.number || 'N/A'} a \`develop\`
886
+ 3. Repetir hasta completar el stack
887
+
888
+ ## 📚 Aprendizaje Acumulado
889
+
890
+ Al completar este stack, habrás aprendido:
891
+
892
+ ${stack.flatMap(pr => pr.learningObjectives || []).map(obj => `- ${obj}`).join('\n')}
893
+
894
+ ## ⚠️ Notas
895
+
896
+ - Los PRs deben mergearse **en orden**
897
+ - Después de mergear uno, actualizar el base del siguiente
898
+ - Si hay conflictos, resolverlos antes de continuar
899
+
900
+ ---
901
+
902
+ _Este issue se actualiza automáticamente al mergear PRs del stack_
903
+ `;
904
+
905
+ return await this.githubMCP.createIssue(repo, {
906
+ title: `📚 Stack: ${feature.name}`,
907
+ body: body,
908
+ labels: ['stack-tracking', 'feature'],
909
+ });
910
+ }
911
+ }
912
+ ```
913
+
914
+ ### 3. GitHub Actions para Stacked PRs
915
+
916
+ ```yaml
917
+ # .github/workflows/stacked-pr-ci.yml
918
+ name: Stacked PR CI
919
+
920
+ on:
921
+ pull_request:
922
+ types: [opened, synchronize, reopened]
923
+
924
+ jobs:
925
+ # Detectar si es parte de un stack
926
+ detect-stack:
927
+ runs-on: ubuntu-latest
928
+ outputs:
929
+ is_stacked: ${{ steps.detect.outputs.is_stacked }}
930
+ stack_position: ${{ steps.detect.outputs.stack_position }}
931
+ stack_base: ${{ steps.detect.outputs.stack_base }}
932
+ steps:
933
+ - name: Detect Stacked PR
934
+ id: detect
935
+ run: |
936
+ BRANCH="${{ github.head_ref }}"
937
+
938
+ # Pattern: feature/{name}/{number}-{slug}
939
+ if [[ $BRANCH =~ ^feature/([^/]+)/([0-9]+)-(.+)$ ]]; then
940
+ echo "is_stacked=true" >> $GITHUB_OUTPUT
941
+ echo "stack_position=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
942
+ echo "stack_base=feature/${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
943
+ else
944
+ echo "is_stacked=false" >> $GITHUB_OUTPUT
945
+ fi
946
+
947
+ # Tests estándar
948
+ test:
949
+ runs-on: ubuntu-latest
950
+ steps:
951
+ - uses: actions/checkout@v4
952
+
953
+ - name: Setup Node.js
954
+ uses: actions/setup-node@v4
955
+ with:
956
+ node-version: '20'
957
+ cache: 'npm'
958
+
959
+ - run: npm ci
960
+ - run: npm run lint
961
+ - run: npm run type-check
962
+ - run: npm run test
963
+
964
+ # Validación específica para stacked PRs
965
+ validate-stack:
966
+ runs-on: ubuntu-latest
967
+ needs: detect-stack
968
+ if: needs.detect-stack.outputs.is_stacked == 'true'
969
+ steps:
970
+ - uses: actions/checkout@v4
971
+ with:
972
+ fetch-depth: 0
973
+
974
+ - name: Validate Stack Order
975
+ run: |
976
+ POSITION=${{ needs.detect-stack.outputs.stack_position }}
977
+ BASE=${{ needs.detect-stack.outputs.stack_base }}
978
+
979
+ echo "📚 Stacked PR detectado"
980
+ echo " Posición: $POSITION"
981
+ echo " Stack base: $BASE"
982
+
983
+ # Verificar que los PRs anteriores existen
984
+ if [ "$POSITION" -gt "01" ]; then
985
+ PREV_POSITION=$(printf "%02d" $((10#$POSITION - 1)))
986
+ echo " Verificando PR anterior: ${PREV_POSITION}"
987
+
988
+ # Verificar que la rama anterior existe
989
+ if ! git ls-remote --heads origin | grep -q "${BASE}/${PREV_POSITION}"; then
990
+ echo "⚠️ Warning: PR anterior no encontrado"
991
+ fi
992
+ fi
993
+
994
+ - name: Check PR Size
995
+ run: |
996
+ LINES_CHANGED=$(git diff --shortstat origin/${{ github.base_ref }}...HEAD | grep -oP '\d+(?= insertion)' || echo "0")
997
+
998
+ echo "📊 Líneas cambiadas: $LINES_CHANGED"
999
+
1000
+ if [ "$LINES_CHANGED" -gt 400 ]; then
1001
+ echo "⚠️ Warning: PR muy grande para un stack ($LINES_CHANGED líneas)"
1002
+ echo " Considera dividirlo más"
1003
+ else
1004
+ echo "✅ Tamaño del PR apropiado para review"
1005
+ fi
1006
+
1007
+ # Comentario automático con info del stack
1008
+ stack-comment:
1009
+ runs-on: ubuntu-latest
1010
+ needs: [detect-stack, test]
1011
+ if: needs.detect-stack.outputs.is_stacked == 'true'
1012
+ permissions:
1013
+ pull-requests: write
1014
+ steps:
1015
+ - uses: actions/github-script@v7
1016
+ with:
1017
+ script: |
1018
+ const position = '${{ needs.detect-stack.outputs.stack_position }}';
1019
+ const stackBase = '${{ needs.detect-stack.outputs.stack_base }}';
1020
+
1021
+ const comment = `
1022
+ ## 📚 Stacked PR Info
1023
+
1024
+ | Propiedad | Valor |
1025
+ |-----------|-------|
1026
+ | **Stack** | \`${stackBase}\` |
1027
+ | **Posición** | #${position} |
1028
+ | **CI Status** | ✅ Passed |
1029
+
1030
+ ### 📋 Checklist de Merge
1031
+
1032
+ ${position === '01' ?
1033
+ '- [ ] Este es el **primer PR** del stack - puede mergearse directamente' :
1034
+ '- [ ] Verificar que el PR anterior está mergeado\n- [ ] Actualizar base branch si es necesario'
1035
+ }
1036
+
1037
+ ---
1038
+ _🤖 Comentario automático de Stacked PR CI_
1039
+ `;
1040
+
1041
+ github.rest.issues.createComment({
1042
+ issue_number: context.issue.number,
1043
+ owner: context.repo.owner,
1044
+ repo: context.repo.repo,
1045
+ body: comment
1046
+ });
1047
+ ```
1048
+
1049
+ ### 4. Workflow de Merge para Stacks
1050
+
1051
+ ```typescript
1052
+ class StackMergeManager {
1053
+ /**
1054
+ * Maneja el merge de un PR dentro de un stack
1055
+ */
1056
+ async handleStackMerge(
1057
+ repo: Repository,
1058
+ mergedPR: PullRequest
1059
+ ): Promise<void> {
1060
+ const stackInfo = this.parseStackBranch(mergedPR.head.ref);
1061
+
1062
+ if (!stackInfo) return; // No es un stacked PR
1063
+
1064
+ console.log(`📚 Stack PR merged: ${mergedPR.title}`);
1065
+ console.log(` Stack: ${stackInfo.feature}`);
1066
+ console.log(` Position: ${stackInfo.position}`);
1067
+
1068
+ // 1. Encontrar el siguiente PR en el stack
1069
+ const nextBranch = this.getNextStackBranch(stackInfo);
1070
+ const nextPR = await this.findPRByBranch(repo, nextBranch);
1071
+
1072
+ if (nextPR) {
1073
+ // 2. Actualizar el base del siguiente PR
1074
+ await this.githubMCP.updatePullRequest(repo, nextPR.number, {
1075
+ base: 'develop' // Ahora apunta a develop en lugar del PR mergeado
1076
+ });
1077
+
1078
+ // 3. Quitar el draft status
1079
+ await this.githubMCP.updatePullRequest(repo, nextPR.number, {
1080
+ draft: false
1081
+ });
1082
+
1083
+ // 4. Agregar comentario
1084
+ await this.githubMCP.addComment(repo, nextPR.number, `
1085
+ 🎉 **PR anterior mergeado!**
1086
+
1087
+ Este PR ahora está listo para review. El base ha sido actualizado a \`develop\`.
1088
+
1089
+ 📊 Stack Progress: ${stackInfo.position}/${stackInfo.total} completado
1090
+ `);
1091
+
1092
+ console.log(` ✅ Next PR #${nextPR.number} actualizado y listo para review`);
1093
+ }
1094
+
1095
+ // 5. Actualizar issue de tracking
1096
+ await this.updateStackTrackingIssue(repo, stackInfo, mergedPR);
1097
+ }
1098
+
1099
+ /**
1100
+ * Actualiza el issue de tracking del stack
1101
+ */
1102
+ private async updateStackTrackingIssue(
1103
+ repo: Repository,
1104
+ stackInfo: StackInfo,
1105
+ mergedPR: PullRequest
1106
+ ): Promise<void> {
1107
+ const trackingIssue = await this.findStackTrackingIssue(repo, stackInfo.feature);
1108
+
1109
+ if (!trackingIssue) return;
1110
+
1111
+ // Actualizar el body con el nuevo estado
1112
+ const newBody = this.updateStackProgressInBody(
1113
+ trackingIssue.body,
1114
+ stackInfo.position,
1115
+ 'merged'
1116
+ );
1117
+
1118
+ await this.githubMCP.updateIssue(repo, trackingIssue.number, {
1119
+ body: newBody
1120
+ });
1121
+
1122
+ // Si es el último PR, cerrar el tracking issue
1123
+ if (stackInfo.position === stackInfo.total) {
1124
+ await this.githubMCP.updateIssue(repo, trackingIssue.number, {
1125
+ state: 'closed'
1126
+ });
1127
+
1128
+ await this.githubMCP.addComment(repo, trackingIssue.number, `
1129
+ 🎉 **Stack Completado!**
1130
+
1131
+ Todos los PRs del stack han sido mergeados exitosamente.
1132
+
1133
+ ## 📚 Resumen de Aprendizaje
1134
+
1135
+ Durante este stack, aprendiste:
1136
+ ${stackInfo.allLearningObjectives.map(obj => `- ✅ ${obj}`).join('\n')}
1137
+
1138
+ ---
1139
+
1140
+ _Stack completado en ${this.calculateStackDuration(stackInfo)}_
1141
+ `);
1142
+ }
1143
+ }
1144
+ }
1145
+ ```
1146
+
1147
+ ---
1148
+
1149
+ ## 🐳 Docker First (Heredado de v3)
1150
+
1151
+ ### Generación Automática de Docker
1152
+
1153
+ Se mantiene toda la funcionalidad de v3 para Docker:
1154
+ - Dockerfile multi-stage
1155
+ - docker-compose para dev y prod
1156
+ - .dockerignore optimizado
1157
+ - Scripts de conveniencia
1158
+
1159
+ ```typescript
1160
+ // Ver implementación completa en v3
1161
+ // Esta versión hereda todo el sistema Docker de v3
1162
+ ```
1163
+
1164
+ ---
1165
+
1166
+ ## 📚 Aprendizaje Progresivo en Stacked PRs
1167
+
1168
+ ### Integración de Aprendizaje por PR
1169
+
1170
+ Cada PR del stack incluye su propio contexto de aprendizaje:
1171
+
1172
+ ```markdown
1173
+ ## 📚 Stack: Sistema de Autenticación
1174
+
1175
+ ### PR 01/04: Database Schema
1176
+
1177
+ #### 🎯 Objetivo
1178
+ Crear el esquema de base de datos para usuarios.
1179
+
1180
+ #### 📚 Lo que Aprenderás
1181
+ - Diseño de modelos de datos
1182
+ - Migrations en Prisma
1183
+ - Relaciones entre tablas
1184
+
1185
+ #### 📖 Contexto Just-in-Time
1186
+
1187
+ <details>
1188
+ <summary>🗄️ ¿Qué son las migrations?</summary>
1189
+
1190
+ Las migrations son "versiones" de tu base de datos. Cada migration
1191
+ describe un cambio (crear tabla, agregar columna, etc.).
1192
+
1193
+ **¿Por qué importan?**
1194
+ - Trackean cambios en la BD como código
1195
+ - Permiten rollback si algo sale mal
1196
+ - Todos en el equipo tienen la misma estructura
1197
+
1198
+ **Comando clave:**
1199
+ ```bash
1200
+ npx prisma migrate dev --name add_users_table
1201
+ ```
1202
+ </details>
1203
+
1204
+ #### 🔗 Conexión con PRs Siguientes
1205
+ Este PR crea la base para:
1206
+ - PR 02: Usará el modelo User en los endpoints
1207
+ - PR 03: Agregará campos para tokens JWT
1208
+ - PR 04: Mostrará datos del usuario en el frontend
1209
+
1210
+ ---
1211
+
1212
+ ### PR 02/04: API Endpoints
1213
+
1214
+ #### 🎯 Objetivo
1215
+ Crear endpoints de registro y login.
1216
+
1217
+ #### 📚 Lo que Aprenderás
1218
+ - Diseño de APIs REST
1219
+ - Validación de inputs
1220
+ - Manejo de errores
1221
+
1222
+ #### 🔗 Construyendo sobre PR 01
1223
+ Ahora que tienes el modelo User, puedes:
1224
+ - Crear usuarios en la base de datos
1225
+ - Validar credenciales
1226
+ - Retornar datos del usuario
1227
+
1228
+ #### 📖 Contexto Just-in-Time
1229
+
1230
+ <details>
1231
+ <summary>🔒 ¿Por qué validar inputs?</summary>
1232
+
1233
+ **Nunca confíes en datos del cliente.**
1234
+
1235
+ ```typescript
1236
+ // ❌ MAL: Sin validación
1237
+ app.post('/register', (req, res) => {
1238
+ const { email, password } = req.body;
1239
+ // ¿Y si email es un array? ¿Y si password es undefined?
1240
+ });
1241
+
1242
+ // ✅ BIEN: Con Zod
1243
+ const registerSchema = z.object({
1244
+ email: z.string().email(),
1245
+ password: z.string().min(8)
1246
+ });
1247
+
1248
+ app.post('/register', (req, res) => {
1249
+ const data = registerSchema.parse(req.body);
1250
+ // Ahora data.email y data.password son strings válidos
1251
+ });
1252
+ ```
1253
+ </details>
1254
+ ```
1255
+
1256
+ ### Mapa de Aprendizaje por Stack
1257
+
1258
+ ```typescript
1259
+ class StackLearningTracker {
1260
+ generateLearningMap(stack: PRStack): string {
1261
+ return `
1262
+ # 🗺️ Mapa de Aprendizaje - Stack: ${stack.feature}
1263
+
1264
+ \`\`\`
1265
+ PR 01: Database Schema
1266
+ ├── 📚 Prisma migrations
1267
+ ├── 📚 Diseño de modelos
1268
+ └── ✅ Completado
1269
+
1270
+
1271
+ PR 02: API Endpoints
1272
+ ├── 📚 REST API design
1273
+ ├── 📚 Zod validation
1274
+ ├── 📚 Error handling
1275
+ └── 🔄 En review
1276
+
1277
+
1278
+ PR 03: JWT Middleware
1279
+ ├── 📚 JSON Web Tokens
1280
+ ├── 📚 Middleware pattern
1281
+ ├── 📚 Auth flow
1282
+ └── ⏳ Esperando
1283
+
1284
+
1285
+ PR 04: Frontend Forms
1286
+ ├── 📚 React Hook Form
1287
+ ├── 📚 Client validation
1288
+ ├── 📚 Toast notifications
1289
+ └── ⏳ Draft
1290
+
1291
+ \`\`\`
1292
+
1293
+ ## 📊 Progreso de Aprendizaje
1294
+
1295
+ | Concepto | PR | Estado |
1296
+ |----------|-----|--------|
1297
+ | Prisma migrations | 01 | ✅ |
1298
+ | Diseño de modelos | 01 | ✅ |
1299
+ | REST API design | 02 | 🔄 |
1300
+ | Zod validation | 02 | 🔄 |
1301
+ | JWT tokens | 03 | ⏳ |
1302
+ | Middleware | 03 | ⏳ |
1303
+ | React forms | 04 | ⏳ |
1304
+
1305
+ **Conceptos dominados: 2/7 (29%)**
1306
+ `;
1307
+ }
1308
+ }
1309
+ ```
1310
+
1311
+ ---
1312
+
1313
+ ## 🚀 Workflow Completo del Agente
1314
+
1315
+ ```typescript
1316
+ class FreelancePlannerV4 {
1317
+ async executeFull(
1318
+ projectPath: string,
1319
+ options: PlannerOptions
1320
+ ): Promise<ExecutionResult> {
1321
+ console.log('🚀 Freelance Project Planner v4.0');
1322
+ console.log('📚 GitFlow + Stacked PRs + Infrastructure First\n');
1323
+
1324
+ // FASE 0: Análisis
1325
+ console.log('📊 FASE 0: Análisis del Proyecto');
1326
+ const analysis = await this.analyzer.analyzeProject(projectPath);
1327
+
1328
+ // FASE 1: Docker (prioridad máxima - heredado de v3)
1329
+ console.log('\n🐳 FASE 1: Dockerización');
1330
+ const dockerSetup = await this.dockerGenerator.generateDockerSetup(analysis);
1331
+
1332
+ // FASE 2: GitHub Actions con soporte para stacks
1333
+ console.log('\n⚙️ FASE 2: GitHub Actions (con Stacked PR support)');
1334
+ const workflows = await this.generateStackAwareWorkflows(analysis);
1335
+
1336
+ // FASE 3: GitFlow Setup
1337
+ console.log('\n🌳 FASE 3: GitFlow Setup');
1338
+ await this.setupGitFlow(analysis.repo);
1339
+
1340
+ // FASE 4: Planificación en Stacked PRs
1341
+ console.log('\n📚 FASE 4: Generación de Iteraciones como Stacked PRs');
1342
+ const iterations = await this.planIterationsAsStacks(analysis);
1343
+
1344
+ // FASE 5: Crear PRs y tracking issues
1345
+ console.log('\n🔗 FASE 5: Creación de Stacked PRs en GitHub');
1346
+ const stacks = await this.createAllStacks(analysis.repo, iterations);
1347
+
1348
+ // Resumen final
1349
+ this.printExecutionSummary(analysis, dockerSetup, workflows, stacks);
1350
+
1351
+ return { analysis, dockerSetup, workflows, stacks };
1352
+ }
1353
+
1354
+ private async setupGitFlow(repo: Repository): Promise<void> {
1355
+ // Crear ramas principales
1356
+ const branches = ['develop', 'staging'];
1357
+
1358
+ for (const branch of branches) {
1359
+ try {
1360
+ await this.githubMCP.createBranch(repo, branch, 'main');
1361
+ console.log(` ✅ Rama creada: ${branch}`);
1362
+ } catch {
1363
+ console.log(` ℹ️ Rama ${branch} ya existe`);
1364
+ }
1365
+ }
1366
+
1367
+ // Configurar protecciones
1368
+ await this.setupBranchProtections(repo);
1369
+
1370
+ // Configurar reglas de merge
1371
+ await this.setupMergeRules(repo);
1372
+
1373
+ console.log(' ✅ GitFlow configurado');
1374
+ }
1375
+
1376
+ private async planIterationsAsStacks(
1377
+ analysis: ProjectAnalysis
1378
+ ): Promise<StackedIteration[]> {
1379
+ const features = await this.extractFeatures(analysis);
1380
+ const iterations: StackedIteration[] = [];
1381
+
1382
+ let iterationNumber = 1;
1383
+ for (const feature of features) {
1384
+ // Dividir cada feature en un stack de PRs
1385
+ const chunks = await this.divideFeatureIntoChunks(feature);
1386
+
1387
+ iterations.push({
1388
+ iterationNumber: iterationNumber++,
1389
+ feature: feature.name,
1390
+ description: feature.description,
1391
+ prStack: chunks.map((chunk, index) => ({
1392
+ stackOrder: index + 1,
1393
+ branch: `feature/${this.slugify(feature.name)}/${String(index + 1).padStart(2, '0')}-${chunk.slug}`,
1394
+ title: chunk.title,
1395
+ description: chunk.description,
1396
+ dependsOn: index === 0 ? null : chunks[index - 1].slug,
1397
+ estimatedLines: chunk.estimatedLines,
1398
+ learningObjectives: chunk.learningObjectives,
1399
+ conceptsIntroduced: chunk.conceptsIntroduced
1400
+ })),
1401
+ totalEstimatedReviewTime: this.calculateTotalReviewTime(chunks),
1402
+ learningPath: this.generateLearningPath(chunks)
1403
+ });
1404
+
1405
+ console.log(` 📚 Iteración ${iterationNumber - 1}: ${feature.name}`);
1406
+ console.log(` └── ${chunks.length} PRs en el stack`);
1407
+ }
1408
+
1409
+ return iterations;
1410
+ }
1411
+
1412
+ private printExecutionSummary(
1413
+ analysis: ProjectAnalysis,
1414
+ dockerSetup: DockerSetup,
1415
+ workflows: Workflows,
1416
+ stacks: PRStack[]
1417
+ ): void {
1418
+ const totalPRs = stacks.reduce((sum, s) => sum + s.prStack.length, 0);
1419
+
1420
+ console.log(`
1421
+ =====================================
1422
+ ✅ Setup Completado - v4 GitFlow + Stacked PRs
1423
+ =====================================
1424
+
1425
+ 🐳 Docker:
1426
+ - Dockerfile (multi-stage)
1427
+ - docker-compose.dev.yml
1428
+ - docker-compose.prod.yml
1429
+
1430
+ ⚙️ GitHub Actions:
1431
+ - CI Pipeline
1432
+ - Stacked PR Validator
1433
+ - Auto-update bases on merge
1434
+
1435
+ 🌳 GitFlow:
1436
+ - main (protected)
1437
+ - develop (integration)
1438
+ - feature/* (stacked PRs)
1439
+
1440
+ 📚 Stacked PRs:
1441
+ - ${stacks.length} features planificadas
1442
+ - ${totalPRs} PRs totales
1443
+ - Review time estimado: ~${this.calculateTotalReviewTime(stacks)} horas
1444
+
1445
+ 📋 Iteraciones:
1446
+ ${stacks.map((s, i) => `
1447
+ Iteración ${i + 1}: ${s.feature}
1448
+ └── Stack de ${s.prStack.length} PRs:
1449
+ ${s.prStack.map((pr, j) => ` ${j + 1}. ${pr.title} (~${pr.estimatedLines} líneas)`).join('\n')}
1450
+ `).join('')}
1451
+
1452
+ 🎓 Aprendizaje Integrado:
1453
+ - Cada PR incluye contexto de aprendizaje
1454
+ - Conceptos introducidos progresivamente
1455
+ - Reflexión al completar cada stack
1456
+
1457
+ 📚 Workflow Recomendado:
1458
+ 1. Tomar el primer PR del stack
1459
+ 2. Review y merge a develop
1460
+ 3. CI actualiza automáticamente el siguiente PR
1461
+ 4. Repetir hasta completar el stack
1462
+ 5. Reflexionar sobre conceptos aprendidos
1463
+
1464
+ =====================================
1465
+ `);
1466
+ }
1467
+ }
1468
+ ```
1469
+
1470
+ ---
1471
+
1472
+ ## 📋 Comandos CLI
1473
+
1474
+ ```bash
1475
+ # Setup completo con GitFlow y Stacked PRs
1476
+ freelance-planner-v4 setup ./mi-proyecto \
1477
+ --github-repo "usuario/proyecto" \
1478
+ --gitflow \
1479
+ --stacked-prs
1480
+
1481
+ # Crear stack para una feature específica
1482
+ freelance-planner-v4 create-stack ./mi-proyecto \
1483
+ --feature "user-authentication" \
1484
+ --github-repo "usuario/proyecto"
1485
+
1486
+ # Ver estado de todos los stacks
1487
+ freelance-planner-v4 stack-status ./mi-proyecto \
1488
+ --github-repo "usuario/proyecto"
1489
+
1490
+ # Merge el siguiente PR de un stack
1491
+ freelance-planner-v4 merge-next ./mi-proyecto \
1492
+ --stack "user-authentication"
1493
+
1494
+ # Generar reporte de aprendizaje
1495
+ freelance-planner-v4 learning-report ./mi-proyecto \
1496
+ --stack "user-authentication"
1497
+ ```
1498
+
1499
+ ---
1500
+
1501
+ ## 🎯 Resumen de Diferencias: v1 vs v2 vs v3 vs v4
1502
+
1503
+ | Aspecto | v1 | v2 | v3 | v4 |
1504
+ |---------|----|----|----|----|
1505
+ | **Modelo** | Sonnet | Sonnet | Opus 4.5 | Opus 4.5 |
1506
+ | **GitHub MCP** | ❌ | ✅ | ✅ | ✅ |
1507
+ | **Docker Priority** | ⚪ | ⚪ | 🟢 #1 | 🟢 #1 |
1508
+ | **Kanban + WIP** | ✅ | ✅ | ⚪ | ✅ |
1509
+ | **XP/TDD Selectivo** | ✅ | ✅ | ⚪ | ✅ |
1510
+ | **GitFlow** | ❌ | ❌ | ❌ | ✅ |
1511
+ | **Stacked PRs** | ❌ | ❌ | ❌ | ✅ |
1512
+ | **Aprendizaje Progresivo** | ❌ | ❌ | ✅ | ✅ |
1513
+ | **PRs por feature** | 1 grande | 1 grande | 1 grande | N pequeños |
1514
+ | **Review time** | Largo | Largo | Largo | 30-60 min |
1515
+
1516
+ ### v4 = Lo mejor de todas las versiones
1517
+
1518
+ ```
1519
+ v4 = v1 (Kanban + XP)
1520
+ + v2 (GitHub MCP automation)
1521
+ + v3 (Docker first + Learning)
1522
+ + NEW (GitFlow + Stacked PRs)
1523
+ ```
1524
+
1525
+ ---
1526
+
1527
+ ## 🔑 Beneficios de v4
1528
+
1529
+ ### De v1 - Kanban Light + XP Adaptado
1530
+ 1. **WIP Limits** - Máximo 2 tareas en progreso
1531
+ 2. **TDD Selectivo** - Tests solo en áreas críticas
1532
+ 3. **Refactorización planificada** - Viernes después de demo
1533
+ 4. **Rutina semanal** - Lunes planificación, Viernes demo
1534
+ 5. **Mínimo overhead** - Sin ceremonias innecesarias
1535
+
1536
+ ### De v2 - GitHub MCP
1537
+ 6. **Automatización total** - Repos, issues, projects creados automáticamente
1538
+ 7. **CI/CD generado** - Workflows según tech stack
1539
+ 8. **PR templates** - Formato consistente
1540
+
1541
+ ### De v3 - Infrastructure First + Learning
1542
+ 9. **Docker primero** - Entorno reproducible desde día 1
1543
+ 10. **Aprendizaje progresivo** - Conceptos cuando los necesitas
1544
+ 11. **Documentación just-in-time** - No leer 50 páginas antes
1545
+
1546
+ ### Nuevos en v4 - GitFlow + Stacked PRs
1547
+ 12. **Reviews más rápidos** - PRs pequeños = review en 30-60 min
1548
+ 13. **Feedback continuo** - No esperar a terminar toda la feature
1549
+ 14. **Menos conflictos** - Merge incremental reduce merge hell
1550
+ 15. **Mejor tracking** - Issue de tracking por stack
1551
+ 16. **Rollback granular** - Revertir un PR sin afectar todo
1552
+ 17. **GitFlow profesional** - main/develop/feature/release/hotfix
1553
+ 18. **Aprendizaje por PR** - Conceptos en chunks aún más pequeños
1554
+
1555
+ ---
1556
+
1557
+ ## 🎯 ¿Cuándo usar v4?
1558
+
1559
+ | Escenario | Recomendación |
1560
+ |-----------|---------------|
1561
+ | Proyecto nuevo con equipo de 1+ personas | ✅ v4 |
1562
+ | Features grandes que necesitan review incremental | ✅ v4 |
1563
+ | Quieres aprender mientras desarrollas | ✅ v4 |
1564
+ | Proyecto profesional con GitFlow | ✅ v4 |
1565
+ | Proyecto muy simple o prototipo rápido | ❌ Usa v1 |
1566
+ | Solo necesitas automatización GitHub básica | ❌ Usa v2 |
1567
+
1568
+ ---
1569
+
1570
+ ## 📊 Comparativa Visual
1571
+
1572
+ ```
1573
+ ┌─────────────────────────────────────────────────┐
1574
+ │ FREELANCE PLANNER v4 │
1575
+ │ "The Complete Package" │
1576
+ └─────────────────────────────────────────────────┘
1577
+
1578
+ ┌──────────────────────────────┼──────────────────────────────┐
1579
+ │ │ │
1580
+ ▼ ▼ ▼
1581
+ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
1582
+ │ 📋 KANBAN + XP │ │ 🐳 INFRASTRUCTURE │ │ 📚 STACKED PRs │
1583
+ │ (from v1) │ │ (from v3) │ │ (NEW in v4) │
1584
+ ├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤
1585
+ │ • WIP Limits (2) │ │ • Docker first │ │ • GitFlow │
1586
+ │ • TDD selectivo │ │ • CI/CD priority │ │ • PRs pequeños │
1587
+ │ • Refactor viernes │ │ • Learning │ │ • Review rápido │
1588
+ │ • Demo semanal │ │ progresivo │ │ • Merge incremental │
1589
+ │ • Mínimo overhead │ │ • Just-in-time docs │ │ • Stack tracking │
1590
+ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘
1591
+ │ │ │
1592
+ └──────────────────────────────┼──────────────────────────────┘
1593
+
1594
+
1595
+ ┌─────────────────────────────────────────────────┐
1596
+ │ 🔗 GITHUB MCP (from v2) │
1597
+ │ Auto-create: repos, issues, projects, CI/CD │
1598
+ └─────────────────────────────────────────────────┘
1599
+ ```
1600
+
1601
+ ---
1602
+
1603
+ ## 🛠️ Tooling para Stacked PRs
1604
+
1605
+ ### Herramientas Recomendadas
1606
+
1607
+ El manejo manual de stacked PRs puede ser tedioso. Estas herramientas automatizan el proceso:
1608
+
1609
+ ```typescript
1610
+ const STACKED_PR_TOOLS = {
1611
+ // Herramientas especializadas
1612
+ graphite: {
1613
+ name: 'Graphite',
1614
+ type: 'cli + web',
1615
+ pros: ['Mejor UX', 'Dashboard web', 'Auto-sync', 'Merge queue'],
1616
+ cons: ['Requiere cuenta', 'Freemium'],
1617
+ recommendation: 'Mejor para uso frecuente de stacks'
1618
+ },
1619
+
1620
+ gitTown: {
1621
+ name: 'git-town',
1622
+ type: 'cli',
1623
+ pros: ['Open source', 'Sin cuenta', 'Integrado con git'],
1624
+ cons: ['Solo CLI', 'Menos features'],
1625
+ recommendation: 'Mejor para puristas de git'
1626
+ },
1627
+
1628
+ ghStack: {
1629
+ name: 'ghstack (Facebook)',
1630
+ type: 'cli',
1631
+ pros: ['Usado en producción en Meta', 'Robusto'],
1632
+ cons: ['Curva de aprendizaje', 'Pensado para grandes equipos'],
1633
+ recommendation: 'Para proyectos muy grandes'
1634
+ },
1635
+
1636
+ custom: {
1637
+ name: 'Scripts personalizados',
1638
+ type: 'bash/python',
1639
+ pros: ['Control total', 'Sin dependencias', 'Personalizable'],
1640
+ cons: ['Hay que mantenerlo', 'Más trabajo inicial'],
1641
+ recommendation: 'Cuando necesitas control total'
1642
+ }
1643
+ };
1644
+ ```
1645
+
1646
+ ### Opción 1: Graphite (Recomendada para la mayoría)
1647
+
1648
+ ```bash
1649
+ # Instalación
1650
+ npm install -g @withgraphite/graphite-cli
1651
+
1652
+ # Autenticación
1653
+ gt auth
1654
+
1655
+ # Flujo de trabajo con Graphite
1656
+ # =============================
1657
+
1658
+ # 1. Crear primer PR del stack
1659
+ gt create -m "01: Database schema for users"
1660
+
1661
+ # 2. Hacer cambios y crear siguiente PR (se apila automáticamente)
1662
+ gt create -m "02: API endpoints for auth"
1663
+
1664
+ # 3. Crear más PRs del stack
1665
+ gt create -m "03: JWT middleware"
1666
+ gt create -m "04: Frontend login forms"
1667
+
1668
+ # Ver el stack completo
1669
+ gt log
1670
+
1671
+ # Output:
1672
+ # ┌── 04: Frontend login forms (current)
1673
+ # ├── 03: JWT middleware
1674
+ # ├── 02: API endpoints for auth
1675
+ # └── 01: Database schema for users
1676
+ # │
1677
+ # └── main
1678
+
1679
+ # Sincronizar después de que se mergea un PR
1680
+ gt sync # Rebasa automáticamente todos los PRs del stack
1681
+
1682
+ # Navegar entre PRs del stack
1683
+ gt up # Ir al PR siguiente
1684
+ gt down # Ir al PR anterior
1685
+ gt top # Ir al último PR del stack
1686
+ gt bottom # Ir al primer PR del stack
1687
+
1688
+ # Submit todos los PRs a GitHub
1689
+ gt submit --stack
1690
+ ```
1691
+
1692
+ ### Opción 2: git-town (Open Source)
1693
+
1694
+ ```bash
1695
+ # Instalación
1696
+ # macOS
1697
+ brew install git-town
1698
+
1699
+ # Linux
1700
+ curl -sL https://git-town.com/install.sh | bash
1701
+
1702
+ # Windows
1703
+ scoop install git-town
1704
+
1705
+ # Configuración inicial
1706
+ git town config
1707
+
1708
+ # Flujo de trabajo con git-town
1709
+ # =============================
1710
+
1711
+ # 1. Crear rama para feature
1712
+ git town hack feature/user-auth-01-schema
1713
+
1714
+ # 2. Hacer cambios y commit
1715
+ git add . && git commit -m "Add user schema"
1716
+
1717
+ # 3. Crear siguiente rama del stack
1718
+ git town append feature/user-auth-02-api
1719
+
1720
+ # 4. Sincronizar con upstream
1721
+ git town sync
1722
+
1723
+ # 5. Crear PR
1724
+ git town propose
1725
+
1726
+ # Ver estado
1727
+ git town status
1728
+ ```
1729
+
1730
+ ### Opción 3: Scripts Personalizados (Agnóstico)
1731
+
1732
+ ```bash
1733
+ #!/bin/bash
1734
+ # scripts/stack-create.sh
1735
+ # Crear un nuevo PR en el stack
1736
+
1737
+ set -e
1738
+
1739
+ FEATURE_NAME=$1
1740
+ DESCRIPTION=$2
1741
+
1742
+ if [ -z "$FEATURE_NAME" ] || [ -z "$DESCRIPTION" ]; then
1743
+ echo "Uso: ./scripts/stack-create.sh <feature-name> <description>"
1744
+ echo "Ejemplo: ./scripts/stack-create.sh user-auth 'Add JWT middleware'"
1745
+ exit 1
1746
+ fi
1747
+
1748
+ # Detectar el número del siguiente PR en el stack
1749
+ CURRENT_BRANCH=$(git branch --show-current)
1750
+ STACK_BASE="feature/${FEATURE_NAME}"
1751
+
1752
+ if [[ $CURRENT_BRANCH =~ ^${STACK_BASE}/([0-9]+)- ]]; then
1753
+ CURRENT_NUM=${BASH_REMATCH[1]}
1754
+ NEXT_NUM=$(printf "%02d" $((10#$CURRENT_NUM + 1)))
1755
+ else
1756
+ # Es el primer PR del stack
1757
+ NEXT_NUM="01"
1758
+ fi
1759
+
1760
+ # Crear slug del description
1761
+ SLUG=$(echo "$DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
1762
+
1763
+ NEW_BRANCH="${STACK_BASE}/${NEXT_NUM}-${SLUG}"
1764
+
1765
+ echo "📚 Creando nueva rama del stack: $NEW_BRANCH"
1766
+
1767
+ # Crear rama basada en la actual
1768
+ git checkout -b "$NEW_BRANCH"
1769
+
1770
+ echo "✅ Rama creada. Ahora puedes hacer tus cambios."
1771
+ echo ""
1772
+ echo "Cuando termines:"
1773
+ echo " git add ."
1774
+ echo " git commit -m '${NEXT_NUM}: ${DESCRIPTION}'"
1775
+ echo " ./scripts/stack-push.sh"
1776
+ ```
1777
+
1778
+ ```bash
1779
+ #!/bin/bash
1780
+ # scripts/stack-sync.sh
1781
+ # Sincronizar stack después de merge
1782
+
1783
+ set -e
1784
+
1785
+ FEATURE_NAME=$1
1786
+
1787
+ if [ -z "$FEATURE_NAME" ]; then
1788
+ # Detectar feature del branch actual
1789
+ CURRENT_BRANCH=$(git branch --show-current)
1790
+ if [[ $CURRENT_BRANCH =~ ^feature/([^/]+)/ ]]; then
1791
+ FEATURE_NAME=${BASH_REMATCH[1]}
1792
+ else
1793
+ echo "Uso: ./scripts/stack-sync.sh <feature-name>"
1794
+ exit 1
1795
+ fi
1796
+ fi
1797
+
1798
+ STACK_BASE="feature/${FEATURE_NAME}"
1799
+
1800
+ echo "🔄 Sincronizando stack: $STACK_BASE"
1801
+
1802
+ # Obtener cambios de origin
1803
+ git fetch origin
1804
+
1805
+ # Obtener todas las ramas del stack
1806
+ STACK_BRANCHES=$(git branch -r | grep "origin/${STACK_BASE}/" | sort | sed 's/origin\///')
1807
+
1808
+ # Actualizar develop primero
1809
+ git checkout develop
1810
+ git pull origin develop
1811
+
1812
+ PREV_BRANCH="develop"
1813
+
1814
+ for BRANCH in $STACK_BRANCHES; do
1815
+ echo "📌 Actualizando: $BRANCH"
1816
+
1817
+ git checkout "$BRANCH"
1818
+
1819
+ # Rebase sobre la rama anterior del stack (o develop si es la primera)
1820
+ git rebase "$PREV_BRANCH"
1821
+
1822
+ # Push con force-with-lease (seguro)
1823
+ git push origin "$BRANCH" --force-with-lease
1824
+
1825
+ PREV_BRANCH="$BRANCH"
1826
+ done
1827
+
1828
+ echo "✅ Stack sincronizado"
1829
+ ```
1830
+
1831
+ ```bash
1832
+ #!/bin/bash
1833
+ # scripts/stack-status.sh
1834
+ # Ver estado del stack
1835
+
1836
+ FEATURE_NAME=$1
1837
+
1838
+ if [ -z "$FEATURE_NAME" ]; then
1839
+ CURRENT_BRANCH=$(git branch --show-current)
1840
+ if [[ $CURRENT_BRANCH =~ ^feature/([^/]+)/ ]]; then
1841
+ FEATURE_NAME=${BASH_REMATCH[1]}
1842
+ else
1843
+ echo "Uso: ./scripts/stack-status.sh <feature-name>"
1844
+ exit 1
1845
+ fi
1846
+ fi
1847
+
1848
+ STACK_BASE="feature/${FEATURE_NAME}"
1849
+
1850
+ echo "📚 Stack: $STACK_BASE"
1851
+ echo "================================"
1852
+
1853
+ # Obtener PRs del stack
1854
+ gh pr list --search "head:${STACK_BASE}/" --json number,title,state,mergeable,headRefName \
1855
+ --jq '.[] | "PR #\(.number) [\(.state)] \(.title)"' | sort
1856
+
1857
+ echo ""
1858
+ echo "Branches locales:"
1859
+ git branch | grep "$STACK_BASE" | while read branch; do
1860
+ COMMITS_AHEAD=$(git rev-list --count develop.."$branch" 2>/dev/null || echo "?")
1861
+ echo " $branch (+$COMMITS_AHEAD commits)"
1862
+ done
1863
+ ```
1864
+
1865
+ ```python
1866
+ #!/usr/bin/env python3
1867
+ # scripts/stack-manager.py
1868
+ # Gestor de stacks multi-plataforma
1869
+
1870
+ import subprocess
1871
+ import sys
1872
+ import re
1873
+ import json
1874
+ from pathlib import Path
1875
+
1876
+ class StackManager:
1877
+ def __init__(self):
1878
+ self.current_branch = self._run_git("branch --show-current")
1879
+
1880
+ def _run_git(self, cmd: str) -> str:
1881
+ result = subprocess.run(
1882
+ f"git {cmd}",
1883
+ shell=True,
1884
+ capture_output=True,
1885
+ text=True
1886
+ )
1887
+ return result.stdout.strip()
1888
+
1889
+ def _run_gh(self, cmd: str) -> dict:
1890
+ result = subprocess.run(
1891
+ f"gh {cmd}",
1892
+ shell=True,
1893
+ capture_output=True,
1894
+ text=True
1895
+ )
1896
+ if result.returncode == 0:
1897
+ return json.loads(result.stdout) if result.stdout else {}
1898
+ return {}
1899
+
1900
+ def detect_stack(self) -> dict:
1901
+ """Detecta información del stack actual"""
1902
+ match = re.match(r'^feature/([^/]+)/(\d+)-(.+)$', self.current_branch)
1903
+ if match:
1904
+ return {
1905
+ 'feature': match.group(1),
1906
+ 'position': int(match.group(2)),
1907
+ 'slug': match.group(3),
1908
+ 'is_stack': True
1909
+ }
1910
+ return {'is_stack': False}
1911
+
1912
+ def list_stack_branches(self, feature: str) -> list:
1913
+ """Lista todas las ramas de un stack"""
1914
+ branches = self._run_git("branch -a")
1915
+ pattern = f"feature/{feature}/"
1916
+ stack_branches = [
1917
+ b.strip().replace("remotes/origin/", "")
1918
+ for b in branches.split("\n")
1919
+ if pattern in b
1920
+ ]
1921
+ return sorted(set(stack_branches))
1922
+
1923
+ def get_stack_prs(self, feature: str) -> list:
1924
+ """Obtiene los PRs de GitHub para el stack"""
1925
+ prs = self._run_gh(
1926
+ f'pr list --search "head:feature/{feature}/" '
1927
+ f'--json number,title,state,headRefName,mergeable'
1928
+ )
1929
+ return sorted(prs, key=lambda x: x.get('headRefName', ''))
1930
+
1931
+ def create_stack_branch(self, feature: str, description: str) -> str:
1932
+ """Crea una nueva rama en el stack"""
1933
+ branches = self.list_stack_branches(feature)
1934
+
1935
+ if branches:
1936
+ # Encontrar el siguiente número
1937
+ numbers = []
1938
+ for b in branches:
1939
+ match = re.search(r'/(\d+)-', b)
1940
+ if match:
1941
+ numbers.append(int(match.group(1)))
1942
+ next_num = max(numbers) + 1 if numbers else 1
1943
+ else:
1944
+ next_num = 1
1945
+
1946
+ # Crear slug
1947
+ slug = re.sub(r'[^a-z0-9]+', '-', description.lower()).strip('-')
1948
+ branch_name = f"feature/{feature}/{next_num:02d}-{slug}"
1949
+
1950
+ self._run_git(f"checkout -b {branch_name}")
1951
+ return branch_name
1952
+
1953
+ def sync_stack(self, feature: str):
1954
+ """Sincroniza todo el stack con develop"""
1955
+ print(f"🔄 Sincronizando stack: {feature}")
1956
+
1957
+ self._run_git("fetch origin")
1958
+ branches = self.list_stack_branches(feature)
1959
+
1960
+ self._run_git("checkout develop")
1961
+ self._run_git("pull origin develop")
1962
+
1963
+ prev_branch = "develop"
1964
+ for branch in branches:
1965
+ print(f" 📌 Rebasing: {branch}")
1966
+ self._run_git(f"checkout {branch}")
1967
+ self._run_git(f"rebase {prev_branch}")
1968
+ self._run_git(f"push origin {branch} --force-with-lease")
1969
+ prev_branch = branch
1970
+
1971
+ print("✅ Stack sincronizado")
1972
+
1973
+ def print_status(self, feature: str):
1974
+ """Imprime el estado del stack"""
1975
+ branches = self.list_stack_branches(feature)
1976
+ prs = self.get_stack_prs(feature)
1977
+
1978
+ pr_map = {pr['headRefName']: pr for pr in prs}
1979
+
1980
+ print(f"\n📚 Stack: feature/{feature}")
1981
+ print("=" * 50)
1982
+
1983
+ for i, branch in enumerate(branches):
1984
+ pr = pr_map.get(branch, {})
1985
+ pr_num = pr.get('number', '?')
1986
+ state = pr.get('state', 'NO PR')
1987
+
1988
+ icon = '✅' if state == 'MERGED' else '🔄' if state == 'OPEN' else '⬜'
1989
+ connector = '└──' if i == len(branches) - 1 else '├──'
1990
+
1991
+ print(f" {connector} {icon} #{pr_num} {branch}")
1992
+
1993
+ print(f"\n └── develop (base)")
1994
+
1995
+ if __name__ == "__main__":
1996
+ manager = StackManager()
1997
+
1998
+ if len(sys.argv) < 2:
1999
+ print("Uso: stack-manager.py <comando> [args]")
2000
+ print("Comandos: status, sync, create, list")
2001
+ sys.exit(1)
2002
+
2003
+ command = sys.argv[1]
2004
+
2005
+ if command == "status":
2006
+ info = manager.detect_stack()
2007
+ if info['is_stack']:
2008
+ manager.print_status(info['feature'])
2009
+ else:
2010
+ print("No estás en una rama de stack")
2011
+
2012
+ elif command == "sync":
2013
+ info = manager.detect_stack()
2014
+ if info['is_stack']:
2015
+ manager.sync_stack(info['feature'])
2016
+ elif len(sys.argv) > 2:
2017
+ manager.sync_stack(sys.argv[2])
2018
+ else:
2019
+ print("Especifica el feature: stack-manager.py sync <feature>")
2020
+
2021
+ elif command == "create":
2022
+ if len(sys.argv) < 4:
2023
+ print("Uso: stack-manager.py create <feature> <description>")
2024
+ sys.exit(1)
2025
+ branch = manager.create_stack_branch(sys.argv[2], sys.argv[3])
2026
+ print(f"✅ Creada rama: {branch}")
2027
+
2028
+ elif command == "list":
2029
+ if len(sys.argv) > 2:
2030
+ branches = manager.list_stack_branches(sys.argv[2])
2031
+ for b in branches:
2032
+ print(f" - {b}")
2033
+ else:
2034
+ print("Especifica el feature: stack-manager.py list <feature>")
2035
+ ```
2036
+
2037
+ ---
2038
+
2039
+ ## 🌐 Preview Environments por PR (Multi-Lenguaje)
2040
+
2041
+ ### Detección Automática de Tech Stack
2042
+
2043
+ ```typescript
2044
+ class TechStackDetector {
2045
+ /**
2046
+ * Detecta el tech stack del proyecto para configurar
2047
+ * el preview environment correcto
2048
+ */
2049
+ async detect(projectPath: string): Promise<TechStack> {
2050
+ const files = await this.scanProjectFiles(projectPath);
2051
+
2052
+ return {
2053
+ // Lenguaje principal
2054
+ language: this.detectLanguage(files),
2055
+
2056
+ // Framework
2057
+ framework: this.detectFramework(files),
2058
+
2059
+ // Package manager
2060
+ packageManager: this.detectPackageManager(files),
2061
+
2062
+ // Runtime
2063
+ runtime: this.detectRuntime(files),
2064
+
2065
+ // Base de datos
2066
+ database: this.detectDatabase(files),
2067
+
2068
+ // Preview platform recomendada
2069
+ previewPlatform: this.recommendPreviewPlatform(files)
2070
+ };
2071
+ }
2072
+
2073
+ private detectLanguage(files: string[]): Language {
2074
+ const languageIndicators = {
2075
+ 'package.json': 'javascript',
2076
+ 'tsconfig.json': 'typescript',
2077
+ 'requirements.txt': 'python',
2078
+ 'Pipfile': 'python',
2079
+ 'pyproject.toml': 'python',
2080
+ 'go.mod': 'go',
2081
+ 'Cargo.toml': 'rust',
2082
+ 'pom.xml': 'java',
2083
+ 'build.gradle': 'java',
2084
+ 'Gemfile': 'ruby',
2085
+ 'composer.json': 'php',
2086
+ 'mix.exs': 'elixir',
2087
+ 'Package.swift': 'swift',
2088
+ 'pubspec.yaml': 'dart',
2089
+ '*.csproj': 'csharp',
2090
+ };
2091
+
2092
+ for (const [indicator, language] of Object.entries(languageIndicators)) {
2093
+ if (files.some(f => f.includes(indicator.replace('*', '')))) {
2094
+ return language as Language;
2095
+ }
2096
+ }
2097
+
2098
+ return 'unknown';
2099
+ }
2100
+
2101
+ private detectFramework(files: string[]): Framework {
2102
+ const frameworkIndicators = {
2103
+ // JavaScript/TypeScript
2104
+ 'next.config': 'nextjs',
2105
+ 'nuxt.config': 'nuxt',
2106
+ 'svelte.config': 'sveltekit',
2107
+ 'astro.config': 'astro',
2108
+ 'remix.config': 'remix',
2109
+ 'angular.json': 'angular',
2110
+ 'vite.config': 'vite',
2111
+
2112
+ // Python
2113
+ 'manage.py': 'django',
2114
+ 'app/main.py': 'fastapi',
2115
+ 'flask': 'flask',
2116
+
2117
+ // Go
2118
+ 'go.mod': 'go-native',
2119
+
2120
+ // Ruby
2121
+ 'config/routes.rb': 'rails',
2122
+
2123
+ // PHP
2124
+ 'artisan': 'laravel',
2125
+ 'symfony.lock': 'symfony',
2126
+
2127
+ // Java
2128
+ 'spring': 'spring-boot',
2129
+ };
2130
+
2131
+ for (const [indicator, framework] of Object.entries(frameworkIndicators)) {
2132
+ if (files.some(f => f.toLowerCase().includes(indicator))) {
2133
+ return framework as Framework;
2134
+ }
2135
+ }
2136
+
2137
+ return 'generic';
2138
+ }
2139
+
2140
+ private recommendPreviewPlatform(files: string[]): PreviewPlatform {
2141
+ const framework = this.detectFramework(files);
2142
+
2143
+ const platformMap: Record<string, PreviewPlatform> = {
2144
+ // Vercel - mejor para Next.js y frameworks JS
2145
+ 'nextjs': 'vercel',
2146
+ 'nuxt': 'vercel',
2147
+ 'sveltekit': 'vercel',
2148
+ 'remix': 'vercel',
2149
+ 'astro': 'vercel',
2150
+
2151
+ // Railway - mejor para backends
2152
+ 'django': 'railway',
2153
+ 'fastapi': 'railway',
2154
+ 'flask': 'railway',
2155
+ 'rails': 'railway',
2156
+ 'laravel': 'railway',
2157
+ 'spring-boot': 'railway',
2158
+ 'go-native': 'railway',
2159
+
2160
+ // Fly.io - mejor para Docker
2161
+ 'generic': 'flyio',
2162
+ };
2163
+
2164
+ return platformMap[framework] || 'flyio';
2165
+ }
2166
+ }
2167
+ ```
2168
+
2169
+ ### GitHub Actions para Preview Environments
2170
+
2171
+ ```yaml
2172
+ # .github/workflows/preview-deploy.yml
2173
+ name: Deploy Preview Environment
2174
+
2175
+ on:
2176
+ pull_request:
2177
+ types: [opened, synchronize, reopened]
2178
+
2179
+ jobs:
2180
+ detect-stack:
2181
+ runs-on: ubuntu-latest
2182
+ outputs:
2183
+ language: ${{ steps.detect.outputs.language }}
2184
+ framework: ${{ steps.detect.outputs.framework }}
2185
+ platform: ${{ steps.detect.outputs.platform }}
2186
+ steps:
2187
+ - uses: actions/checkout@v4
2188
+
2189
+ - name: Detect Tech Stack
2190
+ id: detect
2191
+ run: |
2192
+ # Detectar lenguaje
2193
+ if [ -f "package.json" ]; then
2194
+ if [ -f "tsconfig.json" ]; then
2195
+ echo "language=typescript" >> $GITHUB_OUTPUT
2196
+ else
2197
+ echo "language=javascript" >> $GITHUB_OUTPUT
2198
+ fi
2199
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
2200
+ echo "language=python" >> $GITHUB_OUTPUT
2201
+ elif [ -f "go.mod" ]; then
2202
+ echo "language=go" >> $GITHUB_OUTPUT
2203
+ elif [ -f "Cargo.toml" ]; then
2204
+ echo "language=rust" >> $GITHUB_OUTPUT
2205
+ elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then
2206
+ echo "language=java" >> $GITHUB_OUTPUT
2207
+ elif [ -f "Gemfile" ]; then
2208
+ echo "language=ruby" >> $GITHUB_OUTPUT
2209
+ elif [ -f "composer.json" ]; then
2210
+ echo "language=php" >> $GITHUB_OUTPUT
2211
+ else
2212
+ echo "language=unknown" >> $GITHUB_OUTPUT
2213
+ fi
2214
+
2215
+ # Detectar framework
2216
+ if [ -f "next.config.js" ] || [ -f "next.config.mjs" ] || [ -f "next.config.ts" ]; then
2217
+ echo "framework=nextjs" >> $GITHUB_OUTPUT
2218
+ echo "platform=vercel" >> $GITHUB_OUTPUT
2219
+ elif [ -f "nuxt.config.ts" ] || [ -f "nuxt.config.js" ]; then
2220
+ echo "framework=nuxt" >> $GITHUB_OUTPUT
2221
+ echo "platform=vercel" >> $GITHUB_OUTPUT
2222
+ elif [ -f "manage.py" ]; then
2223
+ echo "framework=django" >> $GITHUB_OUTPUT
2224
+ echo "platform=railway" >> $GITHUB_OUTPUT
2225
+ elif grep -q "fastapi" requirements.txt 2>/dev/null || grep -q "fastapi" pyproject.toml 2>/dev/null; then
2226
+ echo "framework=fastapi" >> $GITHUB_OUTPUT
2227
+ echo "platform=railway" >> $GITHUB_OUTPUT
2228
+ elif [ -f "go.mod" ]; then
2229
+ echo "framework=go" >> $GITHUB_OUTPUT
2230
+ echo "platform=flyio" >> $GITHUB_OUTPUT
2231
+ else
2232
+ echo "framework=generic" >> $GITHUB_OUTPUT
2233
+ echo "platform=flyio" >> $GITHUB_OUTPUT
2234
+ fi
2235
+
2236
+ # ==========================================
2237
+ # Deploy a Vercel (JS/TS Frameworks)
2238
+ # ==========================================
2239
+ deploy-vercel:
2240
+ needs: detect-stack
2241
+ if: needs.detect-stack.outputs.platform == 'vercel'
2242
+ runs-on: ubuntu-latest
2243
+ steps:
2244
+ - uses: actions/checkout@v4
2245
+
2246
+ - name: Deploy to Vercel
2247
+ id: deploy
2248
+ uses: amondnet/vercel-action@v25
2249
+ with:
2250
+ vercel-token: ${{ secrets.VERCEL_TOKEN }}
2251
+ vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
2252
+ vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
2253
+ scope: ${{ secrets.VERCEL_ORG_ID }}
2254
+
2255
+ - name: Comment Preview URL
2256
+ uses: actions/github-script@v7
2257
+ with:
2258
+ script: |
2259
+ const prNumber = context.issue.number;
2260
+ const previewUrl = '${{ steps.deploy.outputs.preview-url }}';
2261
+
2262
+ // Detectar si es parte de un stack
2263
+ const branch = context.payload.pull_request.head.ref;
2264
+ const stackMatch = branch.match(/^feature\/([^\/]+)\/(\d+)-/);
2265
+ const stackInfo = stackMatch
2266
+ ? `\n\n📚 **Stack:** \`${stackMatch[1]}\` | **Position:** #${stackMatch[2]}`
2267
+ : '';
2268
+
2269
+ const body = `## 🚀 Preview Deployed!
2270
+
2271
+ | Environment | URL |
2272
+ |-------------|-----|
2273
+ | **Preview** | [${previewUrl}](${previewUrl}) |
2274
+ ${stackInfo}
2275
+
2276
+ ### 🧪 Test Checklist
2277
+ - [ ] Funcionalidad principal verificada
2278
+ - [ ] Responsive design checkeado
2279
+ - [ ] No hay errores en consola
2280
+
2281
+ ---
2282
+ _Deployed with Vercel • Framework: ${{ needs.detect-stack.outputs.framework }}_`;
2283
+
2284
+ github.rest.issues.createComment({
2285
+ issue_number: prNumber,
2286
+ owner: context.repo.owner,
2287
+ repo: context.repo.repo,
2288
+ body: body
2289
+ });
2290
+
2291
+ # ==========================================
2292
+ # Deploy a Railway (Backend Frameworks)
2293
+ # ==========================================
2294
+ deploy-railway:
2295
+ needs: detect-stack
2296
+ if: needs.detect-stack.outputs.platform == 'railway'
2297
+ runs-on: ubuntu-latest
2298
+ steps:
2299
+ - uses: actions/checkout@v4
2300
+
2301
+ - name: Install Railway CLI
2302
+ run: npm install -g @railway/cli
2303
+
2304
+ - name: Deploy to Railway
2305
+ id: deploy
2306
+ env:
2307
+ RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
2308
+ PR_NUMBER: ${{ github.event.pull_request.number }}
2309
+ run: |
2310
+ # Deploy con nombre basado en PR
2311
+ PREVIEW_NAME="pr-${PR_NUMBER}"
2312
+
2313
+ railway up --detach --environment "$PREVIEW_NAME"
2314
+
2315
+ # Obtener URL del deployment
2316
+ PREVIEW_URL=$(railway status --json | jq -r '.deploymentUrl')
2317
+ echo "url=$PREVIEW_URL" >> $GITHUB_OUTPUT
2318
+
2319
+ - name: Comment Preview URL
2320
+ uses: actions/github-script@v7
2321
+ with:
2322
+ script: |
2323
+ const body = `## 🚀 Preview Deployed!
2324
+
2325
+ | Environment | URL |
2326
+ |-------------|-----|
2327
+ | **Preview** | [${{ steps.deploy.outputs.url }}](${{ steps.deploy.outputs.url }}) |
2328
+
2329
+ ### 📋 API Endpoints para probar
2330
+ \`\`\`bash
2331
+ curl ${{ steps.deploy.outputs.url }}/health
2332
+ curl ${{ steps.deploy.outputs.url }}/api/v1/
2333
+ \`\`\`
2334
+
2335
+ ---
2336
+ _Deployed with Railway • Framework: ${{ needs.detect-stack.outputs.framework }}_`;
2337
+
2338
+ github.rest.issues.createComment({
2339
+ issue_number: context.issue.number,
2340
+ owner: context.repo.owner,
2341
+ repo: context.repo.repo,
2342
+ body: body
2343
+ });
2344
+
2345
+ # ==========================================
2346
+ # Deploy a Fly.io (Docker/Generic)
2347
+ # ==========================================
2348
+ deploy-flyio:
2349
+ needs: detect-stack
2350
+ if: needs.detect-stack.outputs.platform == 'flyio'
2351
+ runs-on: ubuntu-latest
2352
+ steps:
2353
+ - uses: actions/checkout@v4
2354
+
2355
+ - name: Setup Fly.io
2356
+ uses: superfly/flyctl-actions/setup-flyctl@master
2357
+
2358
+ - name: Deploy to Fly.io
2359
+ id: deploy
2360
+ env:
2361
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
2362
+ PR_NUMBER: ${{ github.event.pull_request.number }}
2363
+ REPO_NAME: ${{ github.event.repository.name }}
2364
+ run: |
2365
+ APP_NAME="${REPO_NAME}-pr-${PR_NUMBER}"
2366
+
2367
+ # Crear app si no existe
2368
+ flyctl apps create "$APP_NAME" --org personal 2>/dev/null || true
2369
+
2370
+ # Deploy
2371
+ flyctl deploy --app "$APP_NAME" --remote-only
2372
+
2373
+ # Obtener URL
2374
+ PREVIEW_URL="https://${APP_NAME}.fly.dev"
2375
+ echo "url=$PREVIEW_URL" >> $GITHUB_OUTPUT
2376
+
2377
+ - name: Comment Preview URL
2378
+ uses: actions/github-script@v7
2379
+ with:
2380
+ script: |
2381
+ const body = `## 🚀 Preview Deployed!
2382
+
2383
+ | Environment | URL |
2384
+ |-------------|-----|
2385
+ | **Preview** | [${{ steps.deploy.outputs.url }}](${{ steps.deploy.outputs.url }}) |
2386
+
2387
+ ### 🐳 Docker Build
2388
+ - Image built and deployed successfully
2389
+ - Language: ${{ needs.detect-stack.outputs.language }}
2390
+
2391
+ ---
2392
+ _Deployed with Fly.io_`;
2393
+
2394
+ github.rest.issues.createComment({
2395
+ issue_number: context.issue.number,
2396
+ owner: context.repo.owner,
2397
+ repo: context.repo.repo,
2398
+ body: body
2399
+ });
2400
+
2401
+ # ==========================================
2402
+ # Cleanup Preview on PR Close
2403
+ # ==========================================
2404
+ cleanup-preview:
2405
+ if: github.event.action == 'closed'
2406
+ runs-on: ubuntu-latest
2407
+ steps:
2408
+ - name: Cleanup Vercel Preview
2409
+ if: env.VERCEL_TOKEN != ''
2410
+ continue-on-error: true
2411
+ run: |
2412
+ echo "Vercel previews se limpian automáticamente"
2413
+
2414
+ - name: Cleanup Railway Preview
2415
+ if: env.RAILWAY_TOKEN != ''
2416
+ continue-on-error: true
2417
+ env:
2418
+ RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
2419
+ PR_NUMBER: ${{ github.event.pull_request.number }}
2420
+ run: |
2421
+ PREVIEW_NAME="pr-${PR_NUMBER}"
2422
+ railway environment delete "$PREVIEW_NAME" --yes || true
2423
+
2424
+ - name: Cleanup Fly.io Preview
2425
+ if: env.FLY_API_TOKEN != ''
2426
+ continue-on-error: true
2427
+ env:
2428
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
2429
+ PR_NUMBER: ${{ github.event.pull_request.number }}
2430
+ REPO_NAME: ${{ github.event.repository.name }}
2431
+ run: |
2432
+ APP_NAME="${REPO_NAME}-pr-${PR_NUMBER}"
2433
+ flyctl apps destroy "$APP_NAME" --yes || true
2434
+ ```
2435
+
2436
+ ---
2437
+
2438
+ ## 🔄 Auto-Sync Después de Merge
2439
+
2440
+ ### Workflow de Auto-Sincronización
2441
+
2442
+ ```yaml
2443
+ # .github/workflows/stack-auto-sync.yml
2444
+ name: Stack Auto-Sync
2445
+
2446
+ on:
2447
+ pull_request:
2448
+ types: [closed]
2449
+
2450
+ jobs:
2451
+ sync-stack:
2452
+ if: github.event.pull_request.merged == true
2453
+ runs-on: ubuntu-latest
2454
+ permissions:
2455
+ contents: write
2456
+ pull-requests: write
2457
+
2458
+ steps:
2459
+ - uses: actions/checkout@v4
2460
+ with:
2461
+ fetch-depth: 0
2462
+ token: ${{ secrets.GITHUB_TOKEN }}
2463
+
2464
+ - name: Detect Stack and Update Next PR
2465
+ id: sync
2466
+ env:
2467
+ MERGED_BRANCH: ${{ github.event.pull_request.head.ref }}
2468
+ run: |
2469
+ echo "📚 Branch mergeado: $MERGED_BRANCH"
2470
+
2471
+ # Detectar si es parte de un stack
2472
+ if [[ $MERGED_BRANCH =~ ^feature/([^/]+)/([0-9]+)-(.+)$ ]]; then
2473
+ FEATURE="${BASH_REMATCH[1]}"
2474
+ CURRENT_NUM="${BASH_REMATCH[2]}"
2475
+
2476
+ echo "feature=$FEATURE" >> $GITHUB_OUTPUT
2477
+ echo "position=$CURRENT_NUM" >> $GITHUB_OUTPUT
2478
+ echo "is_stack=true" >> $GITHUB_OUTPUT
2479
+
2480
+ # Calcular siguiente número
2481
+ NEXT_NUM=$(printf "%02d" $((10#$CURRENT_NUM + 1)))
2482
+ echo "next_position=$NEXT_NUM" >> $GITHUB_OUTPUT
2483
+
2484
+ # Buscar siguiente rama del stack
2485
+ NEXT_BRANCH=$(git branch -r | grep "origin/feature/${FEATURE}/${NEXT_NUM}" | head -1 | xargs | sed 's/origin\///')
2486
+
2487
+ if [ -n "$NEXT_BRANCH" ]; then
2488
+ echo "next_branch=$NEXT_BRANCH" >> $GITHUB_OUTPUT
2489
+ echo "has_next=true" >> $GITHUB_OUTPUT
2490
+ echo "✅ Siguiente rama encontrada: $NEXT_BRANCH"
2491
+ else
2492
+ echo "has_next=false" >> $GITHUB_OUTPUT
2493
+ echo "📌 Este era el último PR del stack"
2494
+ fi
2495
+ else
2496
+ echo "is_stack=false" >> $GITHUB_OUTPUT
2497
+ echo "ℹ️ No es parte de un stack"
2498
+ fi
2499
+
2500
+ - name: Update Next PR Base
2501
+ if: steps.sync.outputs.has_next == 'true'
2502
+ env:
2503
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2504
+ run: |
2505
+ NEXT_BRANCH="${{ steps.sync.outputs.next_branch }}"
2506
+
2507
+ # Encontrar el PR de la siguiente rama
2508
+ NEXT_PR=$(gh pr list --head "$NEXT_BRANCH" --json number --jq '.[0].number')
2509
+
2510
+ if [ -n "$NEXT_PR" ]; then
2511
+ echo "📌 Actualizando PR #$NEXT_PR"
2512
+
2513
+ # Cambiar base a develop
2514
+ gh pr edit "$NEXT_PR" --base develop
2515
+
2516
+ # Quitar draft status si lo tiene
2517
+ gh pr ready "$NEXT_PR" 2>/dev/null || true
2518
+
2519
+ # Agregar comentario
2520
+ gh pr comment "$NEXT_PR" --body "🎉 **PR anterior mergeado!**
2521
+
2522
+ Este PR ahora está listo para review.
2523
+
2524
+ 📊 **Stack Progress:** ${{ steps.sync.outputs.position }}/${{ steps.sync.outputs.next_position }} completado
2525
+
2526
+ ---
2527
+ _Auto-sync by Stack Manager_"
2528
+
2529
+ echo "✅ PR #$NEXT_PR actualizado"
2530
+ fi
2531
+
2532
+ - name: Update Stack Tracking Issue
2533
+ if: steps.sync.outputs.is_stack == 'true'
2534
+ env:
2535
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2536
+ FEATURE: ${{ steps.sync.outputs.feature }}
2537
+ POSITION: ${{ steps.sync.outputs.position }}
2538
+ HEAD_REF: ${{ github.event.pull_request.head.ref }}
2539
+ HAS_NEXT: ${{ steps.sync.outputs.has_next }}
2540
+ run: |
2541
+ # Buscar issue de tracking del stack
2542
+ TRACKING_ISSUE=$(gh issue list --search "Stack: $FEATURE in:title" --json number --jq '.[0].number')
2543
+
2544
+ if [ "$HAS_NEXT" = "true" ]; then
2545
+ NEXT_MSG="➡️ Siguiente PR listo para review"
2546
+ else
2547
+ NEXT_MSG="🎉 Stack completado!"
2548
+ fi
2549
+
2550
+ if [ -n "$TRACKING_ISSUE" ]; then
2551
+ # Agregar comentario de progreso
2552
+ gh issue comment "$TRACKING_ISSUE" --body "✅ **PR ${POSITION} mergeado**
2553
+
2554
+ Branch: \`${HEAD_REF}\`
2555
+
2556
+ ${NEXT_MSG}"
2557
+
2558
+ # Si no hay siguiente PR, cerrar el tracking issue
2559
+ if [ "$HAS_NEXT" != "true" ]; then
2560
+ gh issue close "$TRACKING_ISSUE" --comment "🎉 **Stack completado!**
2561
+
2562
+ Todos los PRs han sido mergeados exitosamente."
2563
+ fi
2564
+ fi
2565
+
2566
+ # Rebase automático de PRs siguientes
2567
+ rebase-remaining-stack:
2568
+ needs: sync-stack
2569
+ if: needs.sync-stack.outputs.is_stack == 'true'
2570
+ runs-on: ubuntu-latest
2571
+ steps:
2572
+ - uses: actions/checkout@v4
2573
+ with:
2574
+ fetch-depth: 0
2575
+ token: ${{ secrets.GITHUB_TOKEN }}
2576
+
2577
+ - name: Rebase All Remaining PRs in Stack
2578
+ run: |
2579
+ FEATURE="${{ needs.sync-stack.outputs.feature }}"
2580
+ CURRENT_POS="${{ needs.sync-stack.outputs.position }}"
2581
+
2582
+ git fetch origin
2583
+ git checkout develop
2584
+ git pull origin develop
2585
+
2586
+ # Obtener todas las ramas restantes del stack
2587
+ REMAINING_BRANCHES=$(git branch -r | grep "origin/feature/${FEATURE}/" | sort | while read branch; do
2588
+ BRANCH_NUM=$(echo "$branch" | grep -oP '\d{2}(?=-)' || echo "00")
2589
+ if [ "$BRANCH_NUM" -gt "$CURRENT_POS" ]; then
2590
+ echo "${branch#origin/}"
2591
+ fi
2592
+ done)
2593
+
2594
+ PREV_BRANCH="develop"
2595
+
2596
+ for BRANCH in $REMAINING_BRANCHES; do
2597
+ echo "🔄 Rebasing: $BRANCH"
2598
+
2599
+ git checkout "$BRANCH"
2600
+ git rebase "$PREV_BRANCH" || {
2601
+ echo "⚠️ Conflicto en $BRANCH - requiere resolución manual"
2602
+ git rebase --abort
2603
+ continue
2604
+ }
2605
+
2606
+ git push origin "$BRANCH" --force-with-lease
2607
+ PREV_BRANCH="$BRANCH"
2608
+ done
2609
+
2610
+ echo "✅ Rebase completado"
2611
+ ```
2612
+
2613
+ ---
2614
+
2615
+ ## 📋 Merge Queue Configuration
2616
+
2617
+ ### Setup de Merge Queue en GitHub
2618
+
2619
+ ```typescript
2620
+ class MergeQueueSetup {
2621
+ /**
2622
+ * Configura merge queue para el repositorio
2623
+ * Requiere GitHub Enterprise o repo público
2624
+ */
2625
+ async setupMergeQueue(repo: Repository): Promise<void> {
2626
+ // Habilitar merge queue en branch protection
2627
+ await this.githubMCP.updateBranchProtection(repo, 'develop', {
2628
+ required_status_checks: {
2629
+ strict: true,
2630
+ contexts: ['ci/tests', 'ci/lint', 'ci/type-check']
2631
+ },
2632
+ enforce_admins: false,
2633
+ required_pull_request_reviews: {
2634
+ required_approving_review_count: 1
2635
+ },
2636
+ // Merge Queue configuration
2637
+ merge_queue: {
2638
+ enabled: true,
2639
+ merge_method: 'squash',
2640
+ // Agrupar hasta 5 PRs para merge
2641
+ batch_size: 5,
2642
+ // Esperar 5 minutos antes de procesar
2643
+ wait_time_minutes: 5,
2644
+ // Requerir que CI pase en el batch
2645
+ require_branch_update: true
2646
+ }
2647
+ });
2648
+ }
2649
+ }
2650
+ ```
2651
+
2652
+ ### Workflow para Merge Queue
2653
+
2654
+ ```yaml
2655
+ # .github/workflows/merge-queue.yml
2656
+ name: Merge Queue CI
2657
+
2658
+ on:
2659
+ merge_group:
2660
+ types: [checks_requested]
2661
+
2662
+ jobs:
2663
+ # Este job corre cuando un PR entra al merge queue
2664
+ merge-queue-tests:
2665
+ runs-on: ubuntu-latest
2666
+ steps:
2667
+ - uses: actions/checkout@v4
2668
+
2669
+ - name: Detect Language and Setup
2670
+ id: setup
2671
+ run: |
2672
+ if [ -f "package.json" ]; then
2673
+ echo "runtime=node" >> $GITHUB_OUTPUT
2674
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
2675
+ echo "runtime=python" >> $GITHUB_OUTPUT
2676
+ elif [ -f "go.mod" ]; then
2677
+ echo "runtime=go" >> $GITHUB_OUTPUT
2678
+ elif [ -f "Cargo.toml" ]; then
2679
+ echo "runtime=rust" >> $GITHUB_OUTPUT
2680
+ fi
2681
+
2682
+ # Node.js
2683
+ - name: Setup Node.js
2684
+ if: steps.setup.outputs.runtime == 'node'
2685
+ uses: actions/setup-node@v4
2686
+ with:
2687
+ node-version: '20'
2688
+ cache: 'npm'
2689
+
2690
+ - name: Node.js Tests
2691
+ if: steps.setup.outputs.runtime == 'node'
2692
+ run: |
2693
+ npm ci
2694
+ npm run lint
2695
+ npm run type-check || true
2696
+ npm run test
2697
+ npm run build
2698
+
2699
+ # Python
2700
+ - name: Setup Python
2701
+ if: steps.setup.outputs.runtime == 'python'
2702
+ uses: actions/setup-python@v5
2703
+ with:
2704
+ python-version: '3.11'
2705
+ cache: 'pip'
2706
+
2707
+ - name: Python Tests
2708
+ if: steps.setup.outputs.runtime == 'python'
2709
+ run: |
2710
+ pip install -r requirements.txt
2711
+ pip install pytest flake8
2712
+ flake8 . --max-line-length=100 || true
2713
+ pytest
2714
+
2715
+ # Go
2716
+ - name: Setup Go
2717
+ if: steps.setup.outputs.runtime == 'go'
2718
+ uses: actions/setup-go@v5
2719
+ with:
2720
+ go-version: '1.21'
2721
+
2722
+ - name: Go Tests
2723
+ if: steps.setup.outputs.runtime == 'go'
2724
+ run: |
2725
+ go mod download
2726
+ go vet ./...
2727
+ go test -race ./...
2728
+ go build ./...
2729
+
2730
+ # Rust
2731
+ - name: Setup Rust
2732
+ if: steps.setup.outputs.runtime == 'rust'
2733
+ uses: dtolnay/rust-toolchain@stable
2734
+
2735
+ - name: Rust Tests
2736
+ if: steps.setup.outputs.runtime == 'rust'
2737
+ run: |
2738
+ cargo clippy -- -D warnings
2739
+ cargo test
2740
+ cargo build --release
2741
+ ```
2742
+
2743
+ ---
2744
+
2745
+ ## 👥 CODEOWNERS Templates (Multi-Lenguaje)
2746
+
2747
+ ### Generador de CODEOWNERS
2748
+
2749
+ ```typescript
2750
+ class CodeownersGenerator {
2751
+ /**
2752
+ * Genera archivo CODEOWNERS basado en la estructura del proyecto
2753
+ */
2754
+ async generate(projectPath: string): Promise<string> {
2755
+ const structure = await this.analyzeProjectStructure(projectPath);
2756
+ const techStack = await this.detectTechStack(projectPath);
2757
+
2758
+ let codeowners = `# ==========================================
2759
+ # CODEOWNERS - Auto-generated
2760
+ # ==========================================
2761
+ # Este archivo asigna reviewers automáticamente
2762
+ # basado en las áreas del código modificadas.
2763
+ #
2764
+ # Formato: <pattern> <@owner1> <@owner2>
2765
+ # Más específico = mayor prioridad
2766
+ # ==========================================
2767
+
2768
+ # ==========================================
2769
+ # Default - Todo el repositorio
2770
+ # ==========================================
2771
+ * @${structure.defaultOwner || 'tu-usuario'}
2772
+
2773
+ `;
2774
+
2775
+ // Agregar secciones según el stack detectado
2776
+ codeowners += this.generateSectionsByStack(techStack, structure);
2777
+
2778
+ return codeowners;
2779
+ }
2780
+
2781
+ private generateSectionsByStack(stack: TechStack, structure: ProjectStructure): string {
2782
+ let sections = '';
2783
+
2784
+ // Sección de infraestructura (común a todos)
2785
+ sections += `# ==========================================
2786
+ # Infrastructure & DevOps
2787
+ # ==========================================
2788
+ /.github/ @${structure.devopsOwner || structure.defaultOwner}
2789
+ /docker/ @${structure.devopsOwner || structure.defaultOwner}
2790
+ /Dockerfile* @${structure.devopsOwner || structure.defaultOwner}
2791
+ /docker-compose* @${structure.devopsOwner || structure.defaultOwner}
2792
+ /.env* @${structure.devopsOwner || structure.defaultOwner}
2793
+ /Makefile @${structure.devopsOwner || structure.defaultOwner}
2794
+ /scripts/ @${structure.devopsOwner || structure.defaultOwner}
2795
+
2796
+ `;
2797
+
2798
+ // Secciones específicas por lenguaje
2799
+ switch (stack.language) {
2800
+ case 'javascript':
2801
+ case 'typescript':
2802
+ sections += this.generateJavaScriptSections(structure);
2803
+ break;
2804
+ case 'python':
2805
+ sections += this.generatePythonSections(structure);
2806
+ break;
2807
+ case 'go':
2808
+ sections += this.generateGoSections(structure);
2809
+ break;
2810
+ case 'rust':
2811
+ sections += this.generateRustSections(structure);
2812
+ break;
2813
+ case 'java':
2814
+ sections += this.generateJavaSections(structure);
2815
+ break;
2816
+ case 'ruby':
2817
+ sections += this.generateRubySections(structure);
2818
+ break;
2819
+ case 'php':
2820
+ sections += this.generatePHPSections(structure);
2821
+ break;
2822
+ default:
2823
+ sections += this.generateGenericSections(structure);
2824
+ }
2825
+
2826
+ return sections;
2827
+ }
2828
+
2829
+ private generateJavaScriptSections(structure: ProjectStructure): string {
2830
+ return `# ==========================================
2831
+ # JavaScript/TypeScript Project
2832
+ # ==========================================
2833
+
2834
+ # Frontend
2835
+ /src/components/ @${structure.frontendOwner || structure.defaultOwner}
2836
+ /src/pages/ @${structure.frontendOwner || structure.defaultOwner}
2837
+ /src/app/ @${structure.frontendOwner || structure.defaultOwner}
2838
+ /src/views/ @${structure.frontendOwner || structure.defaultOwner}
2839
+ /src/hooks/ @${structure.frontendOwner || structure.defaultOwner}
2840
+ /src/styles/ @${structure.frontendOwner || structure.defaultOwner}
2841
+ /public/ @${structure.frontendOwner || structure.defaultOwner}
2842
+
2843
+ # Backend / API
2844
+ /src/api/ @${structure.backendOwner || structure.defaultOwner}
2845
+ /src/server/ @${structure.backendOwner || structure.defaultOwner}
2846
+ /src/routes/ @${structure.backendOwner || structure.defaultOwner}
2847
+ /src/controllers/ @${structure.backendOwner || structure.defaultOwner}
2848
+ /src/services/ @${structure.backendOwner || structure.defaultOwner}
2849
+ /src/middleware/ @${structure.backendOwner || structure.defaultOwner}
2850
+
2851
+ # Database
2852
+ /src/models/ @${structure.backendOwner || structure.defaultOwner}
2853
+ /src/db/ @${structure.backendOwner || structure.defaultOwner}
2854
+ /prisma/ @${structure.backendOwner || structure.defaultOwner}
2855
+ /drizzle/ @${structure.backendOwner || structure.defaultOwner}
2856
+ /migrations/ @${structure.backendOwner || structure.defaultOwner}
2857
+
2858
+ # Shared / Utils
2859
+ /src/lib/ @${structure.defaultOwner}
2860
+ /src/utils/ @${structure.defaultOwner}
2861
+ /src/types/ @${structure.defaultOwner}
2862
+
2863
+ # Tests
2864
+ /__tests__/ @${structure.defaultOwner}
2865
+ /tests/ @${structure.defaultOwner}
2866
+ *.test.ts @${structure.defaultOwner}
2867
+ *.test.tsx @${structure.defaultOwner}
2868
+ *.spec.ts @${structure.defaultOwner}
2869
+
2870
+ # Config
2871
+ /package.json @${structure.defaultOwner}
2872
+ /tsconfig.json @${structure.defaultOwner}
2873
+ /*.config.js @${structure.defaultOwner}
2874
+ /*.config.ts @${structure.defaultOwner}
2875
+ /*.config.mjs @${structure.defaultOwner}
2876
+
2877
+ `;
2878
+ }
2879
+
2880
+ private generatePythonSections(structure: ProjectStructure): string {
2881
+ return `# ==========================================
2882
+ # Python Project
2883
+ # ==========================================
2884
+
2885
+ # API / Web
2886
+ /app/ @${structure.backendOwner || structure.defaultOwner}
2887
+ /api/ @${structure.backendOwner || structure.defaultOwner}
2888
+ /views/ @${structure.backendOwner || structure.defaultOwner}
2889
+ /routes/ @${structure.backendOwner || structure.defaultOwner}
2890
+
2891
+ # Models / Database
2892
+ /models/ @${structure.backendOwner || structure.defaultOwner}
2893
+ /migrations/ @${structure.backendOwner || structure.defaultOwner}
2894
+ /alembic/ @${structure.backendOwner || structure.defaultOwner}
2895
+
2896
+ # Services / Business Logic
2897
+ /services/ @${structure.backendOwner || structure.defaultOwner}
2898
+ /domain/ @${structure.backendOwner || structure.defaultOwner}
2899
+ /core/ @${structure.backendOwner || structure.defaultOwner}
2900
+
2901
+ # Utils
2902
+ /utils/ @${structure.defaultOwner}
2903
+ /lib/ @${structure.defaultOwner}
2904
+ /helpers/ @${structure.defaultOwner}
2905
+
2906
+ # Tests
2907
+ /tests/ @${structure.defaultOwner}
2908
+ test_*.py @${structure.defaultOwner}
2909
+ *_test.py @${structure.defaultOwner}
2910
+
2911
+ # Config
2912
+ /requirements*.txt @${structure.defaultOwner}
2913
+ /pyproject.toml @${structure.defaultOwner}
2914
+ /setup.py @${structure.defaultOwner}
2915
+ /Pipfile* @${structure.defaultOwner}
2916
+
2917
+ # Django specific
2918
+ /templates/ @${structure.frontendOwner || structure.defaultOwner}
2919
+ /static/ @${structure.frontendOwner || structure.defaultOwner}
2920
+ /manage.py @${structure.backendOwner || structure.defaultOwner}
2921
+
2922
+ `;
2923
+ }
2924
+
2925
+ private generateGoSections(structure: ProjectStructure): string {
2926
+ return `# ==========================================
2927
+ # Go Project
2928
+ # ==========================================
2929
+
2930
+ # API / Handlers
2931
+ /api/ @${structure.backendOwner || structure.defaultOwner}
2932
+ /handlers/ @${structure.backendOwner || structure.defaultOwner}
2933
+ /routes/ @${structure.backendOwner || structure.defaultOwner}
2934
+
2935
+ # Internal packages
2936
+ /internal/ @${structure.backendOwner || structure.defaultOwner}
2937
+ /pkg/ @${structure.defaultOwner}
2938
+
2939
+ # Commands / CLI
2940
+ /cmd/ @${structure.backendOwner || structure.defaultOwner}
2941
+
2942
+ # Models / Database
2943
+ /models/ @${structure.backendOwner || structure.defaultOwner}
2944
+ /repository/ @${structure.backendOwner || structure.defaultOwner}
2945
+ /store/ @${structure.backendOwner || structure.defaultOwner}
2946
+
2947
+ # Services
2948
+ /services/ @${structure.backendOwner || structure.defaultOwner}
2949
+ /domain/ @${structure.backendOwner || structure.defaultOwner}
2950
+
2951
+ # Tests
2952
+ *_test.go @${structure.defaultOwner}
2953
+
2954
+ # Config
2955
+ /go.mod @${structure.defaultOwner}
2956
+ /go.sum @${structure.defaultOwner}
2957
+
2958
+ `;
2959
+ }
2960
+
2961
+ private generateRustSections(structure: ProjectStructure): string {
2962
+ return `# ==========================================
2963
+ # Rust Project
2964
+ # ==========================================
2965
+
2966
+ # Source code
2967
+ /src/ @${structure.defaultOwner}
2968
+ /src/bin/ @${structure.backendOwner || structure.defaultOwner}
2969
+ /src/lib.rs @${structure.defaultOwner}
2970
+ /src/main.rs @${structure.backendOwner || structure.defaultOwner}
2971
+
2972
+ # API / Web
2973
+ /src/api/ @${structure.backendOwner || structure.defaultOwner}
2974
+ /src/handlers/ @${structure.backendOwner || structure.defaultOwner}
2975
+ /src/routes/ @${structure.backendOwner || structure.defaultOwner}
2976
+
2977
+ # Domain
2978
+ /src/models/ @${structure.backendOwner || structure.defaultOwner}
2979
+ /src/domain/ @${structure.backendOwner || structure.defaultOwner}
2980
+ /src/services/ @${structure.backendOwner || structure.defaultOwner}
2981
+
2982
+ # Tests
2983
+ /tests/ @${structure.defaultOwner}
2984
+
2985
+ # Config
2986
+ /Cargo.toml @${structure.defaultOwner}
2987
+ /Cargo.lock @${structure.defaultOwner}
2988
+
2989
+ `;
2990
+ }
2991
+
2992
+ private generateJavaSections(structure: ProjectStructure): string {
2993
+ return `# ==========================================
2994
+ # Java Project
2995
+ # ==========================================
2996
+
2997
+ # Controllers / API
2998
+ **/controller/ @${structure.backendOwner || structure.defaultOwner}
2999
+ **/controllers/ @${structure.backendOwner || structure.defaultOwner}
3000
+ **/api/ @${structure.backendOwner || structure.defaultOwner}
3001
+
3002
+ # Services
3003
+ **/service/ @${structure.backendOwner || structure.defaultOwner}
3004
+ **/services/ @${structure.backendOwner || structure.defaultOwner}
3005
+
3006
+ # Models / Entities
3007
+ **/model/ @${structure.backendOwner || structure.defaultOwner}
3008
+ **/models/ @${structure.backendOwner || structure.defaultOwner}
3009
+ **/entity/ @${structure.backendOwner || structure.defaultOwner}
3010
+ **/entities/ @${structure.backendOwner || structure.defaultOwner}
3011
+
3012
+ # Repository / DAO
3013
+ **/repository/ @${structure.backendOwner || structure.defaultOwner}
3014
+ **/repositories/ @${structure.backendOwner || structure.defaultOwner}
3015
+ **/dao/ @${structure.backendOwner || structure.defaultOwner}
3016
+
3017
+ # Config
3018
+ **/config/ @${structure.devopsOwner || structure.defaultOwner}
3019
+ **/configuration/ @${structure.devopsOwner || structure.defaultOwner}
3020
+
3021
+ # Tests
3022
+ **/test/ @${structure.defaultOwner}
3023
+ *Test.java @${structure.defaultOwner}
3024
+
3025
+ # Build
3026
+ /pom.xml @${structure.defaultOwner}
3027
+ /build.gradle @${structure.defaultOwner}
3028
+ /settings.gradle @${structure.defaultOwner}
3029
+
3030
+ `;
3031
+ }
3032
+
3033
+ private generateRubySections(structure: ProjectStructure): string {
3034
+ return `# ==========================================
3035
+ # Ruby Project
3036
+ # ==========================================
3037
+
3038
+ # Controllers
3039
+ /app/controllers/ @${structure.backendOwner || structure.defaultOwner}
3040
+
3041
+ # Models
3042
+ /app/models/ @${structure.backendOwner || structure.defaultOwner}
3043
+
3044
+ # Views
3045
+ /app/views/ @${structure.frontendOwner || structure.defaultOwner}
3046
+
3047
+ # Services / Jobs
3048
+ /app/services/ @${structure.backendOwner || structure.defaultOwner}
3049
+ /app/jobs/ @${structure.backendOwner || structure.defaultOwner}
3050
+
3051
+ # Database
3052
+ /db/ @${structure.backendOwner || structure.defaultOwner}
3053
+ /db/migrate/ @${structure.backendOwner || structure.defaultOwner}
3054
+
3055
+ # Config
3056
+ /config/ @${structure.devopsOwner || structure.defaultOwner}
3057
+
3058
+ # Tests
3059
+ /spec/ @${structure.defaultOwner}
3060
+ /test/ @${structure.defaultOwner}
3061
+
3062
+ # Gems
3063
+ /Gemfile @${structure.defaultOwner}
3064
+ /Gemfile.lock @${structure.defaultOwner}
3065
+
3066
+ `;
3067
+ }
3068
+
3069
+ private generatePHPSections(structure: ProjectStructure): string {
3070
+ return `# ==========================================
3071
+ # PHP Project
3072
+ # ==========================================
3073
+
3074
+ # Laravel / Symfony Controllers
3075
+ /app/Http/Controllers/ @${structure.backendOwner || structure.defaultOwner}
3076
+ /src/Controller/ @${structure.backendOwner || structure.defaultOwner}
3077
+
3078
+ # Models
3079
+ /app/Models/ @${structure.backendOwner || structure.defaultOwner}
3080
+ /src/Entity/ @${structure.backendOwner || structure.defaultOwner}
3081
+
3082
+ # Views / Templates
3083
+ /resources/views/ @${structure.frontendOwner || structure.defaultOwner}
3084
+ /templates/ @${structure.frontendOwner || structure.defaultOwner}
3085
+
3086
+ # Services
3087
+ /app/Services/ @${structure.backendOwner || structure.defaultOwner}
3088
+ /src/Service/ @${structure.backendOwner || structure.defaultOwner}
3089
+
3090
+ # Database
3091
+ /database/ @${structure.backendOwner || structure.defaultOwner}
3092
+ /migrations/ @${structure.backendOwner || structure.defaultOwner}
3093
+
3094
+ # Config
3095
+ /config/ @${structure.devopsOwner || structure.defaultOwner}
3096
+
3097
+ # Tests
3098
+ /tests/ @${structure.defaultOwner}
3099
+
3100
+ # Composer
3101
+ /composer.json @${structure.defaultOwner}
3102
+ /composer.lock @${structure.defaultOwner}
3103
+
3104
+ `;
3105
+ }
3106
+
3107
+ private generateGenericSections(structure: ProjectStructure): string {
3108
+ return `# ==========================================
3109
+ # Generic Project Structure
3110
+ # ==========================================
3111
+
3112
+ # Source code
3113
+ /src/ @${structure.defaultOwner}
3114
+
3115
+ # API
3116
+ /api/ @${structure.backendOwner || structure.defaultOwner}
3117
+
3118
+ # Config
3119
+ /config/ @${structure.devopsOwner || structure.defaultOwner}
3120
+
3121
+ # Tests
3122
+ /tests/ @${structure.defaultOwner}
3123
+ /test/ @${structure.defaultOwner}
3124
+
3125
+ # Documentation
3126
+ /docs/ @${structure.defaultOwner}
3127
+ /*.md @${structure.defaultOwner}
3128
+
3129
+ `;
3130
+ }
3131
+ }
3132
+ ```
3133
+
3134
+ ### Template de CODEOWNERS para Freelancers
3135
+
3136
+ ```bash
3137
+ # .github/CODEOWNERS
3138
+ # ==========================================
3139
+ # CODEOWNERS para Proyecto Freelance
3140
+ # ==========================================
3141
+ # Como freelancer, tú eres el owner de todo.
3142
+ # Este archivo sirve para:
3143
+ # 1. Auto-asignarte como reviewer
3144
+ # 2. Documentar la estructura del proyecto
3145
+ # 3. Facilitar onboarding si agregas colaboradores
3146
+ # ==========================================
3147
+
3148
+ # Default - Todo el repositorio
3149
+ * @tu-usuario-github
3150
+
3151
+ # ==========================================
3152
+ # Infrastructure
3153
+ # ==========================================
3154
+ /.github/ @tu-usuario-github
3155
+ /docker/ @tu-usuario-github
3156
+ /Dockerfile* @tu-usuario-github
3157
+ /docker-compose* @tu-usuario-github
3158
+ /scripts/ @tu-usuario-github
3159
+
3160
+ # ==========================================
3161
+ # Configuración Crítica (requiere atención extra)
3162
+ # ==========================================
3163
+ /.env* @tu-usuario-github
3164
+ /**/secrets* @tu-usuario-github
3165
+ /**/credentials* @tu-usuario-github
3166
+
3167
+ # ==========================================
3168
+ # Adapta según tu stack (descomenta lo que aplique)
3169
+ # ==========================================
3170
+
3171
+ # --- JavaScript/TypeScript ---
3172
+ # /src/components/ @tu-usuario-github
3173
+ # /src/pages/ @tu-usuario-github
3174
+ # /src/api/ @tu-usuario-github
3175
+ # /prisma/ @tu-usuario-github
3176
+
3177
+ # --- Python ---
3178
+ # /app/ @tu-usuario-github
3179
+ # /api/ @tu-usuario-github
3180
+ # /migrations/ @tu-usuario-github
3181
+
3182
+ # --- Go ---
3183
+ # /cmd/ @tu-usuario-github
3184
+ # /internal/ @tu-usuario-github
3185
+ # /pkg/ @tu-usuario-github
3186
+
3187
+ # ==========================================
3188
+ # Tests (siempre revisar con cuidado)
3189
+ # ==========================================
3190
+ /tests/ @tu-usuario-github
3191
+ /__tests__/ @tu-usuario-github
3192
+ *_test.* @tu-usuario-github
3193
+ *.test.* @tu-usuario-github
3194
+ *.spec.* @tu-usuario-github
3195
+ ```
3196
+
3197
+ ---
3198
+
3199
+ ## 📝 Changelog Automático por Stack
3200
+
3201
+ ### Generador de Changelog
3202
+
3203
+ ```typescript
3204
+ class StackChangelogGenerator {
3205
+ /**
3206
+ * Genera changelog automático basado en PRs mergeados del stack
3207
+ */
3208
+ async generateChangelog(
3209
+ repo: Repository,
3210
+ stack: PRStack
3211
+ ): Promise<string> {
3212
+ const mergedPRs = stack.prStack.filter(pr => pr.merged);
3213
+
3214
+ const entries = await Promise.all(
3215
+ mergedPRs.map(async pr => ({
3216
+ type: this.detectChangeType(pr),
3217
+ scope: this.detectScope(pr),
3218
+ description: this.cleanTitle(pr.title),
3219
+ prNumber: pr.number,
3220
+ breaking: this.isBreakingChange(pr),
3221
+ commits: await this.getPRCommits(repo, pr.number)
3222
+ }))
3223
+ );
3224
+
3225
+ return this.formatChangelog(stack, entries);
3226
+ }
3227
+
3228
+ private detectChangeType(pr: StackedPR): ChangeType {
3229
+ const title = pr.title.toLowerCase();
3230
+ const branch = pr.branch.toLowerCase();
3231
+
3232
+ // Detectar por prefijo convencional
3233
+ if (title.match(/^(feat|feature)/)) return 'feat';
3234
+ if (title.match(/^fix/)) return 'fix';
3235
+ if (title.match(/^(refactor|refactoring)/)) return 'refactor';
3236
+ if (title.match(/^(docs|documentation)/)) return 'docs';
3237
+ if (title.match(/^(test|tests)/)) return 'test';
3238
+ if (title.match(/^(chore|build|ci)/)) return 'chore';
3239
+ if (title.match(/^(perf|performance)/)) return 'perf';
3240
+ if (title.match(/^style/)) return 'style';
3241
+
3242
+ // Detectar por contenido
3243
+ if (branch.includes('fix') || branch.includes('bug')) return 'fix';
3244
+ if (branch.includes('refactor')) return 'refactor';
3245
+ if (branch.includes('test')) return 'test';
3246
+
3247
+ return 'feat'; // Default
3248
+ }
3249
+
3250
+ private detectScope(pr: StackedPR): string {
3251
+ // Intentar extraer scope del título: "feat(auth): ..."
3252
+ const scopeMatch = pr.title.match(/^\w+\(([^)]+)\)/);
3253
+ if (scopeMatch) return scopeMatch[1];
3254
+
3255
+ // Detectar por branch
3256
+ const branchParts = pr.branch.split('/');
3257
+ if (branchParts.length >= 2) {
3258
+ return branchParts[1].split('-')[0]; // feature/auth-login -> auth
3259
+ }
3260
+
3261
+ // Detectar por archivos cambiados
3262
+ if (pr.filesChanged) {
3263
+ if (pr.filesChanged.some(f => f.includes('api/'))) return 'api';
3264
+ if (pr.filesChanged.some(f => f.includes('component'))) return 'ui';
3265
+ if (pr.filesChanged.some(f => f.includes('model'))) return 'db';
3266
+ if (pr.filesChanged.some(f => f.includes('test'))) return 'test';
3267
+ }
3268
+
3269
+ return 'core';
3270
+ }
3271
+
3272
+ private formatChangelog(
3273
+ stack: PRStack,
3274
+ entries: ChangelogEntry[]
3275
+ ): string {
3276
+ const version = this.calculateVersion(entries);
3277
+ const date = new Date().toISOString().split('T')[0];
3278
+
3279
+ // Agrupar por tipo
3280
+ const grouped = this.groupByType(entries);
3281
+
3282
+ let changelog = `# Changelog
3283
+
3284
+ ## [${version}] - ${date}
3285
+
3286
+ ### Stack: ${stack.feature}
3287
+
3288
+ `;
3289
+
3290
+ // Breaking changes primero
3291
+ const breaking = entries.filter(e => e.breaking);
3292
+ if (breaking.length > 0) {
3293
+ changelog += `### ⚠️ BREAKING CHANGES
3294
+
3295
+ ${breaking.map(e => `- **${e.scope}**: ${e.description} (#${e.prNumber})`).join('\n')}
3296
+
3297
+ `;
3298
+ }
3299
+
3300
+ // Features
3301
+ if (grouped.feat?.length > 0) {
3302
+ changelog += `### ✨ Features
3303
+
3304
+ ${grouped.feat.map(e => `- **${e.scope}**: ${e.description} (#${e.prNumber})`).join('\n')}
3305
+
3306
+ `;
3307
+ }
3308
+
3309
+ // Bug fixes
3310
+ if (grouped.fix?.length > 0) {
3311
+ changelog += `### 🐛 Bug Fixes
3312
+
3313
+ ${grouped.fix.map(e => `- **${e.scope}**: ${e.description} (#${e.prNumber})`).join('\n')}
3314
+
3315
+ `;
3316
+ }
3317
+
3318
+ // Performance
3319
+ if (grouped.perf?.length > 0) {
3320
+ changelog += `### ⚡ Performance
3321
+
3322
+ ${grouped.perf.map(e => `- **${e.scope}**: ${e.description} (#${e.prNumber})`).join('\n')}
3323
+
3324
+ `;
3325
+ }
3326
+
3327
+ // Refactor
3328
+ if (grouped.refactor?.length > 0) {
3329
+ changelog += `### ♻️ Refactoring
3330
+
3331
+ ${grouped.refactor.map(e => `- **${e.scope}**: ${e.description} (#${e.prNumber})`).join('\n')}
3332
+
3333
+ `;
3334
+ }
3335
+
3336
+ // Aprendizaje completado (específico de este agente)
3337
+ changelog += `### 📚 Learning Completed
3338
+
3339
+ ${stack.prStack
3340
+ .flatMap(pr => pr.learningObjectives || [])
3341
+ .filter((obj, i, arr) => arr.indexOf(obj) === i)
3342
+ .map(obj => `- ✅ ${obj}`)
3343
+ .join('\n')}
3344
+
3345
+ `;
3346
+
3347
+ // PRs incluidos
3348
+ changelog += `### 📋 PRs Included
3349
+
3350
+ | # | Title | Type |
3351
+ |---|-------|------|
3352
+ ${stack.prStack.map(pr => `| #${pr.number} | ${this.cleanTitle(pr.title)} | ${this.detectChangeType(pr)} |`).join('\n')}
3353
+
3354
+ `;
3355
+
3356
+ return changelog;
3357
+ }
3358
+ }
3359
+ ```
3360
+
3361
+ ### GitHub Action para Changelog Automático
3362
+
3363
+ ```yaml
3364
+ # .github/workflows/changelog.yml
3365
+ name: Generate Changelog
3366
+
3367
+ on:
3368
+ push:
3369
+ branches: [main]
3370
+ paths-ignore:
3371
+ - 'CHANGELOG.md'
3372
+
3373
+ jobs:
3374
+ generate-changelog:
3375
+ runs-on: ubuntu-latest
3376
+ permissions:
3377
+ contents: write
3378
+
3379
+ steps:
3380
+ - uses: actions/checkout@v4
3381
+ with:
3382
+ fetch-depth: 0
3383
+
3384
+ - name: Generate Changelog
3385
+ id: changelog
3386
+ run: |
3387
+ # Obtener PRs mergeados desde el último tag
3388
+ LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
3389
+
3390
+ if [ -n "$LAST_TAG" ]; then
3391
+ COMMITS_SINCE="$LAST_TAG..HEAD"
3392
+ else
3393
+ COMMITS_SINCE="HEAD~50..HEAD"
3394
+ fi
3395
+
3396
+ # Generar changelog
3397
+ echo "# Changelog" > CHANGELOG_NEW.md
3398
+ echo "" >> CHANGELOG_NEW.md
3399
+ echo "## [Unreleased] - $(date +%Y-%m-%d)" >> CHANGELOG_NEW.md
3400
+ echo "" >> CHANGELOG_NEW.md
3401
+
3402
+ # Features
3403
+ FEATURES=$(git log $COMMITS_SINCE --oneline --grep="^feat" --grep="^feature" --regexp-ignore-case | head -20)
3404
+ if [ -n "$FEATURES" ]; then
3405
+ echo "### ✨ Features" >> CHANGELOG_NEW.md
3406
+ echo "" >> CHANGELOG_NEW.md
3407
+ echo "$FEATURES" | while read line; do
3408
+ HASH=$(echo "$line" | cut -d' ' -f1)
3409
+ MSG=$(echo "$line" | cut -d' ' -f2-)
3410
+ PR=$(git log -1 --format=%B $HASH | grep -oP '#\d+' | head -1)
3411
+ echo "- $MSG $PR" >> CHANGELOG_NEW.md
3412
+ done
3413
+ echo "" >> CHANGELOG_NEW.md
3414
+ fi
3415
+
3416
+ # Fixes
3417
+ FIXES=$(git log $COMMITS_SINCE --oneline --grep="^fix" --regexp-ignore-case | head -20)
3418
+ if [ -n "$FIXES" ]; then
3419
+ echo "### 🐛 Bug Fixes" >> CHANGELOG_NEW.md
3420
+ echo "" >> CHANGELOG_NEW.md
3421
+ echo "$FIXES" | while read line; do
3422
+ HASH=$(echo "$line" | cut -d' ' -f1)
3423
+ MSG=$(echo "$line" | cut -d' ' -f2-)
3424
+ PR=$(git log -1 --format=%B $HASH | grep -oP '#\d+' | head -1)
3425
+ echo "- $MSG $PR" >> CHANGELOG_NEW.md
3426
+ done
3427
+ echo "" >> CHANGELOG_NEW.md
3428
+ fi
3429
+
3430
+ # Combinar con changelog existente
3431
+ if [ -f "CHANGELOG.md" ]; then
3432
+ tail -n +2 CHANGELOG.md >> CHANGELOG_NEW.md
3433
+ fi
3434
+
3435
+ mv CHANGELOG_NEW.md CHANGELOG.md
3436
+
3437
+ - name: Commit Changelog
3438
+ run: |
3439
+ git config user.name "github-actions[bot]"
3440
+ git config user.email "github-actions[bot]@users.noreply.github.com"
3441
+
3442
+ git add CHANGELOG.md
3443
+ git diff --staged --quiet || git commit -m "docs: update changelog [skip ci]"
3444
+ git push
3445
+ ```
3446
+
3447
+ ---
3448
+
3449
+ ## 🔔 Notificaciones Slack/Discord
3450
+
3451
+ ### Configuración de Notificaciones
3452
+
3453
+ ```typescript
3454
+ class NotificationManager {
3455
+ /**
3456
+ * Gestiona notificaciones a Slack y Discord
3457
+ */
3458
+
3459
+ async notifyStackProgress(
3460
+ stack: PRStack,
3461
+ event: StackEvent
3462
+ ): Promise<void> {
3463
+ const message = this.buildMessage(stack, event);
3464
+
3465
+ // Enviar a todos los canales configurados
3466
+ await Promise.all([
3467
+ this.sendToSlack(message),
3468
+ this.sendToDiscord(message)
3469
+ ]);
3470
+ }
3471
+
3472
+ private buildMessage(stack: PRStack, event: StackEvent): NotificationMessage {
3473
+ const progressBar = this.buildProgressBar(stack);
3474
+
3475
+ switch (event.type) {
3476
+ case 'pr_merged':
3477
+ return {
3478
+ title: `✅ PR Mergeado - Stack: ${stack.feature}`,
3479
+ color: '#00C853',
3480
+ fields: [
3481
+ { name: 'PR', value: `#${event.pr.number}`, inline: true },
3482
+ { name: 'Progreso', value: progressBar, inline: true },
3483
+ { name: 'Siguiente', value: event.nextPR ? `#${event.nextPR.number}` : 'Stack completado!' }
3484
+ ]
3485
+ };
3486
+
3487
+ case 'pr_ready':
3488
+ return {
3489
+ title: `🔔 PR Listo para Review - Stack: ${stack.feature}`,
3490
+ color: '#2196F3',
3491
+ fields: [
3492
+ { name: 'PR', value: `#${event.pr.number}`, inline: true },
3493
+ { name: 'Título', value: event.pr.title },
3494
+ { name: 'Estimado', value: event.pr.estimatedReviewTime || '30 min' }
3495
+ ]
3496
+ };
3497
+
3498
+ case 'stack_completed':
3499
+ return {
3500
+ title: `🎉 Stack Completado: ${stack.feature}`,
3501
+ color: '#4CAF50',
3502
+ fields: [
3503
+ { name: 'PRs Mergeados', value: `${stack.prStack.length}` },
3504
+ { name: 'Conceptos Aprendidos', value: stack.learningPath?.length.toString() || 'N/A' }
3505
+ ]
3506
+ };
3507
+
3508
+ default:
3509
+ return {
3510
+ title: `📚 Stack Update: ${stack.feature}`,
3511
+ color: '#9E9E9E',
3512
+ fields: []
3513
+ };
3514
+ }
3515
+ }
3516
+
3517
+ private buildProgressBar(stack: PRStack): string {
3518
+ const total = stack.prStack.length;
3519
+ const completed = stack.prStack.filter(pr => pr.merged).length;
3520
+ const filled = '█'.repeat(completed);
3521
+ const empty = '░'.repeat(total - completed);
3522
+ return `${filled}${empty} ${completed}/${total}`;
3523
+ }
3524
+ }
3525
+ ```
3526
+
3527
+ ### GitHub Action para Notificaciones
3528
+
3529
+ ```yaml
3530
+ # .github/workflows/notify.yml
3531
+ name: Stack Notifications
3532
+
3533
+ on:
3534
+ pull_request:
3535
+ types: [closed, ready_for_review]
3536
+
3537
+ jobs:
3538
+ notify:
3539
+ runs-on: ubuntu-latest
3540
+ steps:
3541
+ - name: Detect Stack Info
3542
+ id: stack
3543
+ env:
3544
+ BRANCH: ${{ github.event.pull_request.head.ref }}
3545
+ run: |
3546
+
3547
+ if [[ $BRANCH =~ ^feature/([^/]+)/([0-9]+)- ]]; then
3548
+ echo "feature=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
3549
+ echo "position=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
3550
+ echo "is_stack=true" >> $GITHUB_OUTPUT
3551
+
3552
+ # Contar PRs del stack
3553
+ TOTAL=$(gh pr list --search "head:feature/${BASH_REMATCH[1]}/" --json number | jq length)
3554
+ MERGED=$(gh pr list --search "head:feature/${BASH_REMATCH[1]}/ is:merged" --json number | jq length)
3555
+
3556
+ echo "total=$TOTAL" >> $GITHUB_OUTPUT
3557
+ echo "merged=$MERGED" >> $GITHUB_OUTPUT
3558
+ else
3559
+ echo "is_stack=false" >> $GITHUB_OUTPUT
3560
+ fi
3561
+ env:
3562
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3563
+
3564
+ # Notificación Slack
3565
+ - name: Notify Slack
3566
+ if: steps.stack.outputs.is_stack == 'true' && env.SLACK_WEBHOOK != ''
3567
+ env:
3568
+ SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
3569
+ FEATURE: ${{ steps.stack.outputs.feature }}
3570
+ POSITION: ${{ steps.stack.outputs.position }}
3571
+ TOTAL: ${{ steps.stack.outputs.total }}
3572
+ MERGED: ${{ steps.stack.outputs.merged }}
3573
+ EVENT: ${{ github.event.action }}
3574
+ PR_TITLE: ${{ github.event.pull_request.title }}
3575
+ PR_URL: ${{ github.event.pull_request.html_url }}
3576
+ PR_MERGED: ${{ github.event.pull_request.merged }}
3577
+ run: |
3578
+ if [ "$EVENT" = "closed" ] && [ "$PR_MERGED" = "true" ]; then
3579
+ COLOR="#00C853"
3580
+ EMOJI="✅"
3581
+ STATUS="mergeado"
3582
+ else
3583
+ COLOR="#2196F3"
3584
+ EMOJI="🔔"
3585
+ STATUS="listo para review"
3586
+ fi
3587
+
3588
+ # Construir progress bar
3589
+ PROGRESS=""
3590
+ for i in $(seq 1 $TOTAL); do
3591
+ if [ $i -le $MERGED ]; then
3592
+ PROGRESS="${PROGRESS}█"
3593
+ else
3594
+ PROGRESS="${PROGRESS}░"
3595
+ fi
3596
+ done
3597
+
3598
+ curl -X POST "$SLACK_WEBHOOK" \
3599
+ -H 'Content-Type: application/json' \
3600
+ -d "{
3601
+ \"attachments\": [{
3602
+ \"color\": \"$COLOR\",
3603
+ \"blocks\": [
3604
+ {
3605
+ \"type\": \"header\",
3606
+ \"text\": {
3607
+ \"type\": \"plain_text\",
3608
+ \"text\": \"$EMOJI Stack: $FEATURE\",
3609
+ \"emoji\": true
3610
+ }
3611
+ },
3612
+ {
3613
+ \"type\": \"section\",
3614
+ \"fields\": [
3615
+ {
3616
+ \"type\": \"mrkdwn\",
3617
+ \"text\": \"*PR #$POSITION $STATUS*\"
3618
+ },
3619
+ {
3620
+ \"type\": \"mrkdwn\",
3621
+ \"text\": \"*Progreso:* \`$PROGRESS\` $MERGED/$TOTAL\"
3622
+ }
3623
+ ]
3624
+ },
3625
+ {
3626
+ \"type\": \"section\",
3627
+ \"text\": {
3628
+ \"type\": \"mrkdwn\",
3629
+ \"text\": \"<$PR_URL|$PR_TITLE>\"
3630
+ }
3631
+ }
3632
+ ]
3633
+ }]
3634
+ }"
3635
+
3636
+ # Notificación Discord
3637
+ - name: Notify Discord
3638
+ if: steps.stack.outputs.is_stack == 'true' && env.DISCORD_WEBHOOK != ''
3639
+ env:
3640
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
3641
+ FEATURE: ${{ steps.stack.outputs.feature }}
3642
+ POSITION: ${{ steps.stack.outputs.position }}
3643
+ TOTAL: ${{ steps.stack.outputs.total }}
3644
+ MERGED: ${{ steps.stack.outputs.merged }}
3645
+ EVENT: ${{ github.event.action }}
3646
+ PR_TITLE: ${{ github.event.pull_request.title }}
3647
+ PR_URL: ${{ github.event.pull_request.html_url }}
3648
+ PR_MERGED: ${{ github.event.pull_request.merged }}
3649
+ run: |
3650
+ if [ "$EVENT" = "closed" ] && [ "$PR_MERGED" = "true" ]; then
3651
+ COLOR="65280" # Green
3652
+ EMOJI="✅"
3653
+ STATUS="mergeado"
3654
+ else
3655
+ COLOR="3447003" # Blue
3656
+ EMOJI="🔔"
3657
+ STATUS="listo para review"
3658
+ fi
3659
+
3660
+ curl -X POST "$DISCORD_WEBHOOK" \
3661
+ -H 'Content-Type: application/json' \
3662
+ -d "{
3663
+ \"embeds\": [{
3664
+ \"title\": \"$EMOJI Stack: $FEATURE\",
3665
+ \"color\": $COLOR,
3666
+ \"fields\": [
3667
+ {
3668
+ \"name\": \"PR\",
3669
+ \"value\": \"#$POSITION $STATUS\",
3670
+ \"inline\": true
3671
+ },
3672
+ {
3673
+ \"name\": \"Progreso\",
3674
+ \"value\": \"$MERGED/$TOTAL\",
3675
+ \"inline\": true
3676
+ },
3677
+ {
3678
+ \"name\": \"Título\",
3679
+ \"value\": \"[$PR_TITLE]($PR_URL)\"
3680
+ }
3681
+ ]
3682
+ }]
3683
+ }"
3684
+ ```
3685
+
3686
+ ---
3687
+
3688
+ ## 🔙 Rollback Strategy para Stacks
3689
+
3690
+ ### Estrategias de Rollback
3691
+
3692
+ ```typescript
3693
+ class StackRollbackManager {
3694
+ /**
3695
+ * Gestiona rollbacks de stacks parcialmente mergeados
3696
+ */
3697
+
3698
+ async rollbackStack(
3699
+ repo: Repository,
3700
+ stack: PRStack,
3701
+ options: RollbackOptions
3702
+ ): Promise<RollbackResult> {
3703
+ const strategy = this.selectStrategy(stack, options);
3704
+
3705
+ switch (strategy) {
3706
+ case 'revert':
3707
+ return this.revertStrategy(repo, stack, options);
3708
+
3709
+ case 'feature-flag':
3710
+ return this.featureFlagStrategy(repo, stack, options);
3711
+
3712
+ case 'branch-reset':
3713
+ return this.branchResetStrategy(repo, stack, options);
3714
+
3715
+ default:
3716
+ throw new Error(`Unknown rollback strategy: ${strategy}`);
3717
+ }
3718
+ }
3719
+
3720
+ /**
3721
+ * Selecciona la mejor estrategia de rollback
3722
+ */
3723
+ private selectStrategy(
3724
+ stack: PRStack,
3725
+ options: RollbackOptions
3726
+ ): RollbackStrategy {
3727
+ const mergedCount = stack.prStack.filter(pr => pr.merged).length;
3728
+
3729
+ // Si solo un PR fue mergeado, revert es simple
3730
+ if (mergedCount === 1) {
3731
+ return 'revert';
3732
+ }
3733
+
3734
+ // Si hay feature flags configurados, usarlos
3735
+ if (options.hasFeatureFlags) {
3736
+ return 'feature-flag';
3737
+ }
3738
+
3739
+ // Si el stack tiene cambios de DB complejos, preferir feature flag
3740
+ const hasDBMigrations = stack.prStack.some(pr =>
3741
+ pr.filesChanged?.some(f => f.includes('migration'))
3742
+ );
3743
+ if (hasDBMigrations && options.hasFeatureFlags) {
3744
+ return 'feature-flag';
3745
+ }
3746
+
3747
+ // Default: revert commits
3748
+ return 'revert';
3749
+ }
3750
+
3751
+ /**
3752
+ * Estrategia 1: Revert commits
3753
+ * Crea PRs de revert para cada PR mergeado
3754
+ */
3755
+ private async revertStrategy(
3756
+ repo: Repository,
3757
+ stack: PRStack,
3758
+ options: RollbackOptions
3759
+ ): Promise<RollbackResult> {
3760
+ const mergedPRs = stack.prStack
3761
+ .filter(pr => pr.merged)
3762
+ .reverse(); // Revertir en orden inverso
3763
+
3764
+ const revertPRs: PullRequest[] = [];
3765
+
3766
+ for (const pr of mergedPRs) {
3767
+ // Obtener el merge commit
3768
+ const mergeCommit = await this.getMergeCommit(repo, pr);
3769
+
3770
+ // Crear branch de revert
3771
+ const revertBranch = `revert/${pr.branch.replace('feature/', '')}`;
3772
+
3773
+ await this.githubMCP.createBranch(repo, revertBranch, 'develop');
3774
+
3775
+ // Ejecutar git revert
3776
+ // Esto requiere un workflow o GitHub Action
3777
+ const revertPR = await this.createRevertPR(repo, pr, revertBranch);
3778
+
3779
+ revertPRs.push(revertPR);
3780
+ }
3781
+
3782
+ return {
3783
+ strategy: 'revert',
3784
+ revertPRs,
3785
+ instructions: `
3786
+ ## 🔙 Rollback del Stack: ${stack.feature}
3787
+
3788
+ ### PRs de Revert Creados
3789
+
3790
+ ${revertPRs.map(pr => `- [ ] #${pr.number} - Revert: ${pr.title}`).join('\n')}
3791
+
3792
+ ### Instrucciones
3793
+
3794
+ 1. Review y merge los PRs de revert **en orden**
3795
+ 2. Verificar que la funcionalidad fue removida
3796
+ 3. Si hay migraciones de DB, ejecutar rollback manualmente
3797
+
3798
+ ### Comandos útiles
3799
+
3800
+ \`\`\`bash
3801
+ # Verificar estado después del rollback
3802
+ git log --oneline develop -10
3803
+
3804
+ # Si necesitas hacer rollback manual
3805
+ git revert <commit-hash>
3806
+ \`\`\`
3807
+ `
3808
+ };
3809
+ }
3810
+
3811
+ /**
3812
+ * Estrategia 2: Feature Flags
3813
+ * Desactiva la funcionalidad sin remover código
3814
+ */
3815
+ private async featureFlagStrategy(
3816
+ repo: Repository,
3817
+ stack: PRStack,
3818
+ options: RollbackOptions
3819
+ ): Promise<RollbackResult> {
3820
+ const flagName = this.generateFlagName(stack.feature);
3821
+
3822
+ // Crear PR para desactivar feature flag
3823
+ const disablePR = await this.createFeatureFlagPR(repo, stack, flagName, false);
3824
+
3825
+ return {
3826
+ strategy: 'feature-flag',
3827
+ flagName,
3828
+ disablePR,
3829
+ instructions: `
3830
+ ## 🚩 Rollback via Feature Flag: ${stack.feature}
3831
+
3832
+ ### Feature Flag
3833
+
3834
+ \`\`\`
3835
+ Flag: ${flagName}
3836
+ Status: DISABLED
3837
+ \`\`\`
3838
+
3839
+ ### PR Creado
3840
+
3841
+ - [ ] #${disablePR.number} - Disable feature flag: ${flagName}
3842
+
3843
+ ### Para servicios de Feature Flags
3844
+
3845
+ #### LaunchDarkly
3846
+ \`\`\`bash
3847
+ ldcli flags update --flag=${flagName} --on=false
3848
+ \`\`\`
3849
+
3850
+ #### Unleash
3851
+ \`\`\`bash
3852
+ curl -X POST "https://unleash.example.com/api/admin/features/${flagName}/toggle/off"
3853
+ \`\`\`
3854
+
3855
+ #### ConfigCat
3856
+ \`\`\`bash
3857
+ configcat flag update ${flagName} --value false
3858
+ \`\`\`
3859
+
3860
+ #### Archivo de configuración
3861
+ \`\`\`json
3862
+ // config/features.json
3863
+ {
3864
+ "${flagName}": false
3865
+ }
3866
+ \`\`\`
3867
+
3868
+ ### Ventajas de este approach
3869
+
3870
+ 1. ✅ Rollback instantáneo (sin deploy)
3871
+ 2. ✅ No requiere revert de código
3872
+ 3. ✅ Fácil de re-activar cuando esté listo
3873
+ 4. ✅ No afecta migraciones de DB
3874
+ `
3875
+ };
3876
+ }
3877
+
3878
+ /**
3879
+ * Estrategia 3: Branch Reset
3880
+ * Reset de develop a un commit anterior (último recurso)
3881
+ */
3882
+ private async branchResetStrategy(
3883
+ repo: Repository,
3884
+ stack: PRStack,
3885
+ options: RollbackOptions
3886
+ ): Promise<RollbackResult> {
3887
+ // Encontrar el commit antes del primer PR del stack
3888
+ const firstPR = stack.prStack.find(pr => pr.merged);
3889
+ const commitBeforeStack = await this.getCommitBefore(repo, firstPR);
3890
+
3891
+ return {
3892
+ strategy: 'branch-reset',
3893
+ targetCommit: commitBeforeStack,
3894
+ instructions: `
3895
+ ## ⚠️ Rollback via Branch Reset: ${stack.feature}
3896
+
3897
+ ### ⚠️ ADVERTENCIA
3898
+
3899
+ Este es un **rollback destructivo**. Úsalo solo como último recurso.
3900
+
3901
+ ### Commit Target
3902
+
3903
+ \`\`\`
3904
+ ${commitBeforeStack}
3905
+ \`\`\`
3906
+
3907
+ ### Pasos
3908
+
3909
+ 1. **Crear backup de develop actual**
3910
+ \`\`\`bash
3911
+ git checkout develop
3912
+ git checkout -b backup/develop-before-rollback
3913
+ git push origin backup/develop-before-rollback
3914
+ \`\`\`
3915
+
3916
+ 2. **Deshabilitar branch protection temporalmente**
3917
+ - Ve a Settings → Branches → develop
3918
+ - Deshabilita protección temporalmente
3919
+
3920
+ 3. **Ejecutar reset**
3921
+ \`\`\`bash
3922
+ git checkout develop
3923
+ git reset --hard ${commitBeforeStack}
3924
+ git push origin develop --force
3925
+ \`\`\`
3926
+
3927
+ 4. **Re-habilitar branch protection**
3928
+
3929
+ 5. **Notificar al equipo**
3930
+ - Todos deben hacer \`git fetch && git reset --hard origin/develop\`
3931
+
3932
+ ### Riesgos
3933
+
3934
+ - ❌ Otros PRs mergeados después del stack también se perderán
3935
+ - ❌ Historial de git se reescribe
3936
+ - ❌ Puede causar conflictos en branches activos
3937
+ `
3938
+ };
3939
+ }
3940
+
3941
+ private async createRevertPR(
3942
+ repo: Repository,
3943
+ originalPR: MergedPR,
3944
+ revertBranch: string
3945
+ ): Promise<PullRequest> {
3946
+ return await this.githubMCP.createPullRequest(repo, {
3947
+ title: `🔙 Revert: ${originalPR.title}`,
3948
+ body: `
3949
+ ## Revert PR #${originalPR.number}
3950
+
3951
+ Este PR revierte los cambios de #${originalPR.number}.
3952
+
3953
+ ### Razón del Revert
3954
+
3955
+ <!-- Describir por qué se está haciendo rollback -->
3956
+
3957
+ ### Checklist
3958
+
3959
+ - [ ] Verificar que el revert no rompe otras funcionalidades
3960
+ - [ ] Tests pasan
3961
+ - [ ] No hay errores en logs
3962
+
3963
+ ### Original PR
3964
+
3965
+ - PR: #${originalPR.number}
3966
+ - Branch: \`${originalPR.branch}\`
3967
+ - Merged: ${originalPR.mergedAt}
3968
+
3969
+ ---
3970
+ _Generado automáticamente por Stack Rollback Manager_
3971
+ `,
3972
+ head: revertBranch,
3973
+ base: 'develop'
3974
+ });
3975
+ }
3976
+ }
3977
+ ```
3978
+
3979
+ ### GitHub Action para Rollback
3980
+
3981
+ ```yaml
3982
+ # .github/workflows/stack-rollback.yml
3983
+ name: Stack Rollback
3984
+
3985
+ on:
3986
+ workflow_dispatch:
3987
+ inputs:
3988
+ feature:
3989
+ description: 'Feature name del stack (ej: user-auth)'
3990
+ required: true
3991
+ type: string
3992
+ strategy:
3993
+ description: 'Estrategia de rollback'
3994
+ required: true
3995
+ type: choice
3996
+ options:
3997
+ - revert
3998
+ - feature-flag
3999
+ rollback_to:
4000
+ description: 'Número de PR hasta donde hacer rollback (opcional)'
4001
+ required: false
4002
+ type: string
4003
+
4004
+ jobs:
4005
+ rollback:
4006
+ runs-on: ubuntu-latest
4007
+ permissions:
4008
+ contents: write
4009
+ pull-requests: write
4010
+
4011
+ steps:
4012
+ - uses: actions/checkout@v4
4013
+ with:
4014
+ fetch-depth: 0
4015
+ token: ${{ secrets.GITHUB_TOKEN }}
4016
+
4017
+ - name: Get Stack PRs
4018
+ id: stack
4019
+ env:
4020
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4021
+ run: |
4022
+ FEATURE="${{ github.event.inputs.feature }}"
4023
+
4024
+ # Obtener PRs mergeados del stack
4025
+ MERGED_PRS=$(gh pr list \
4026
+ --search "head:feature/${FEATURE}/ is:merged" \
4027
+ --json number,title,headRefName,mergeCommit \
4028
+ --jq 'sort_by(.headRefName) | reverse')
4029
+
4030
+ echo "merged_prs=$MERGED_PRS" >> $GITHUB_OUTPUT
4031
+ echo "📚 PRs mergeados del stack:"
4032
+ echo "$MERGED_PRS" | jq -r '.[] | " #\(.number) - \(.title)"'
4033
+
4034
+ - name: Revert Strategy
4035
+ if: github.event.inputs.strategy == 'revert'
4036
+ env:
4037
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4038
+ run: |
4039
+ FEATURE="${{ github.event.inputs.feature }}"
4040
+ MERGED_PRS='${{ steps.stack.outputs.merged_prs }}'
4041
+
4042
+ echo "$MERGED_PRS" | jq -c '.[]' | while read pr; do
4043
+ PR_NUM=$(echo "$pr" | jq -r '.number')
4044
+ PR_TITLE=$(echo "$pr" | jq -r '.title')
4045
+ MERGE_COMMIT=$(echo "$pr" | jq -r '.mergeCommit.oid')
4046
+ BRANCH_NAME=$(echo "$pr" | jq -r '.headRefName')
4047
+
4048
+ echo "🔙 Revirtiendo PR #$PR_NUM"
4049
+
4050
+ # Crear branch de revert
4051
+ REVERT_BRANCH="revert/${BRANCH_NAME#feature/}"
4052
+ git checkout develop
4053
+ git pull origin develop
4054
+ git checkout -b "$REVERT_BRANCH"
4055
+
4056
+ # Ejecutar revert
4057
+ git revert "$MERGE_COMMIT" --no-edit || {
4058
+ echo "⚠️ Conflicto en revert. Requiere resolución manual."
4059
+ exit 1
4060
+ }
4061
+
4062
+ # Push y crear PR
4063
+ git push origin "$REVERT_BRANCH"
4064
+
4065
+ gh pr create \
4066
+ --title "🔙 Revert: $PR_TITLE" \
4067
+ --body "Reverts #$PR_NUM
4068
+
4069
+ ## Stack Rollback
4070
+
4071
+ Este PR es parte del rollback del stack \`$FEATURE\`.
4072
+
4073
+ ### Original
4074
+ - PR: #$PR_NUM
4075
+ - Commit: $MERGE_COMMIT
4076
+
4077
+ ---
4078
+ _Auto-generated by Stack Rollback workflow_" \
4079
+ --base develop \
4080
+ --head "$REVERT_BRANCH"
4081
+ done
4082
+
4083
+ - name: Feature Flag Strategy
4084
+ if: github.event.inputs.strategy == 'feature-flag'
4085
+ run: |
4086
+ FEATURE="${{ github.event.inputs.feature }}"
4087
+ FLAG_NAME="feature_${FEATURE//-/_}"
4088
+
4089
+ echo "🚩 Desactivando feature flag: $FLAG_NAME"
4090
+
4091
+ # Crear branch para deshabilitar flag
4092
+ git checkout -b "disable-flag/${FEATURE}"
4093
+
4094
+ # Buscar y actualizar archivos de configuración
4095
+ # Esto depende de cómo se manejen los feature flags en el proyecto
4096
+
4097
+ # Ejemplo: archivo JSON
4098
+ if [ -f "config/features.json" ]; then
4099
+ jq ".[\"$FLAG_NAME\"] = false" config/features.json > tmp.json
4100
+ mv tmp.json config/features.json
4101
+ fi
4102
+
4103
+ # Ejemplo: archivo .env
4104
+ if [ -f ".env.example" ]; then
4105
+ echo "${FLAG_NAME^^}=false" >> .env.example
4106
+ fi
4107
+
4108
+ git add .
4109
+ git commit -m "chore: disable feature flag $FLAG_NAME" || echo "No changes"
4110
+ git push origin "disable-flag/${FEATURE}"
4111
+
4112
+ gh pr create \
4113
+ --title "🚩 Disable feature: $FEATURE" \
4114
+ --body "## Feature Flag Rollback
4115
+
4116
+ Desactiva el feature flag \`$FLAG_NAME\` para hacer rollback del stack \`$FEATURE\`.
4117
+
4118
+ ### Acción requerida
4119
+
4120
+ Después de mergear este PR, también desactiva el flag en tu servicio de feature flags si usas uno externo.
4121
+
4122
+ ---
4123
+ _Auto-generated by Stack Rollback workflow_" \
4124
+ --base develop \
4125
+ --head "disable-flag/${FEATURE}"
4126
+
4127
+ - name: Create Rollback Issue
4128
+ env:
4129
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4130
+ run: |
4131
+ FEATURE="${{ github.event.inputs.feature }}"
4132
+ STRATEGY="${{ github.event.inputs.strategy }}"
4133
+
4134
+ gh issue create \
4135
+ --title "🔙 Rollback: Stack $FEATURE" \
4136
+ --body "## Stack Rollback en Progreso
4137
+
4138
+ ### Detalles
4139
+
4140
+ - **Stack:** $FEATURE
4141
+ - **Estrategia:** $STRATEGY
4142
+ - **Iniciado por:** @${{ github.actor }}
4143
+ - **Fecha:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
4144
+
4145
+ ### Status
4146
+
4147
+ - [ ] PRs de rollback creados
4148
+ - [ ] PRs de rollback mergeados
4149
+ - [ ] Verificación de rollback completado
4150
+ - [ ] Notificación al equipo
4151
+
4152
+ ### PRs de Rollback
4153
+
4154
+ <!-- Los PRs de rollback se agregarán aquí -->
4155
+
4156
+ ---
4157
+ _Auto-generated by Stack Rollback workflow_" \
4158
+ --label "rollback"
4159
+ ```
4160
+
4161
+ ---
4162
+
4163
+ ## 📊 Detección Automática de Tech Stack
4164
+
4165
+ ### Detector Multi-Lenguaje
4166
+
4167
+ ```typescript
4168
+ class UniversalTechStackDetector {
4169
+ /**
4170
+ * Detecta el tech stack del proyecto de forma agnóstica
4171
+ */
4172
+ async detect(projectPath: string): Promise<TechStack> {
4173
+ const files = await this.scanDirectory(projectPath);
4174
+
4175
+ return {
4176
+ language: this.detectLanguage(files),
4177
+ framework: this.detectFramework(files),
4178
+ packageManager: this.detectPackageManager(files),
4179
+ database: this.detectDatabase(files),
4180
+ runtime: this.detectRuntime(files),
4181
+ buildTool: this.detectBuildTool(files),
4182
+ testFramework: this.detectTestFramework(files),
4183
+ cicdPlatform: this.detectCICDPlatform(files),
4184
+ containerization: this.detectContainerization(files),
4185
+ cloudProvider: this.detectCloudProvider(files)
4186
+ };
4187
+ }
4188
+
4189
+ private detectLanguage(files: string[]): LanguageInfo {
4190
+ const indicators: Record<string, LanguageConfig> = {
4191
+ // JavaScript / TypeScript
4192
+ 'package.json': {
4193
+ language: 'javascript',
4194
+ checkForTypeScript: (f: string[]) => f.some(x => x.includes('tsconfig'))
4195
+ },
4196
+
4197
+ // Python
4198
+ 'requirements.txt': { language: 'python' },
4199
+ 'Pipfile': { language: 'python' },
4200
+ 'pyproject.toml': { language: 'python' },
4201
+ 'setup.py': { language: 'python' },
4202
+
4203
+ // Go
4204
+ 'go.mod': { language: 'go' },
4205
+
4206
+ // Rust
4207
+ 'Cargo.toml': { language: 'rust' },
4208
+
4209
+ // Java
4210
+ 'pom.xml': { language: 'java', buildTool: 'maven' },
4211
+ 'build.gradle': { language: 'java', buildTool: 'gradle' },
4212
+ 'build.gradle.kts': { language: 'kotlin', buildTool: 'gradle' },
4213
+
4214
+ // Ruby
4215
+ 'Gemfile': { language: 'ruby' },
4216
+
4217
+ // PHP
4218
+ 'composer.json': { language: 'php' },
4219
+
4220
+ // C# / .NET
4221
+ '*.csproj': { language: 'csharp' },
4222
+ '*.sln': { language: 'csharp' },
4223
+
4224
+ // Elixir
4225
+ 'mix.exs': { language: 'elixir' },
4226
+
4227
+ // Swift
4228
+ 'Package.swift': { language: 'swift' },
4229
+
4230
+ // Dart / Flutter
4231
+ 'pubspec.yaml': { language: 'dart' },
4232
+
4233
+ // Scala
4234
+ 'build.sbt': { language: 'scala' },
4235
+
4236
+ // Clojure
4237
+ 'project.clj': { language: 'clojure' },
4238
+ 'deps.edn': { language: 'clojure' },
4239
+ };
4240
+
4241
+ for (const [indicator, config] of Object.entries(indicators)) {
4242
+ if (files.some(f => this.matchPattern(f, indicator))) {
4243
+ let language = config.language;
4244
+
4245
+ // Check for TypeScript
4246
+ if (config.checkForTypeScript && config.checkForTypeScript(files)) {
4247
+ language = 'typescript';
4248
+ }
4249
+
4250
+ return {
4251
+ name: language,
4252
+ version: this.detectLanguageVersion(files, language),
4253
+ buildTool: config.buildTool
4254
+ };
4255
+ }
4256
+ }
4257
+
4258
+ return { name: 'unknown' };
4259
+ }
4260
+
4261
+ private detectFramework(files: string[]): FrameworkInfo {
4262
+ const frameworkIndicators: Record<string, FrameworkConfig> = {
4263
+ // JavaScript Frameworks
4264
+ 'next.config': { name: 'nextjs', type: 'fullstack', language: 'javascript' },
4265
+ 'nuxt.config': { name: 'nuxt', type: 'fullstack', language: 'javascript' },
4266
+ 'svelte.config': { name: 'sveltekit', type: 'fullstack', language: 'javascript' },
4267
+ 'astro.config': { name: 'astro', type: 'frontend', language: 'javascript' },
4268
+ 'remix.config': { name: 'remix', type: 'fullstack', language: 'javascript' },
4269
+ 'angular.json': { name: 'angular', type: 'frontend', language: 'typescript' },
4270
+ 'vite.config': { name: 'vite', type: 'frontend', language: 'javascript' },
4271
+ 'gatsby-config': { name: 'gatsby', type: 'frontend', language: 'javascript' },
4272
+ '.eleventy': { name: 'eleventy', type: 'frontend', language: 'javascript' },
4273
+ 'vue.config': { name: 'vue', type: 'frontend', language: 'javascript' },
4274
+
4275
+ // Node.js Backend
4276
+ 'nest-cli.json': { name: 'nestjs', type: 'backend', language: 'typescript' },
4277
+ 'express': { name: 'express', type: 'backend', language: 'javascript' },
4278
+ 'fastify': { name: 'fastify', type: 'backend', language: 'javascript' },
4279
+ 'koa': { name: 'koa', type: 'backend', language: 'javascript' },
4280
+ 'hono': { name: 'hono', type: 'backend', language: 'javascript' },
4281
+
4282
+ // Python Frameworks
4283
+ 'manage.py': { name: 'django', type: 'fullstack', language: 'python' },
4284
+ 'fastapi': { name: 'fastapi', type: 'backend', language: 'python' },
4285
+ 'flask': { name: 'flask', type: 'backend', language: 'python' },
4286
+ 'starlette': { name: 'starlette', type: 'backend', language: 'python' },
4287
+ 'tornado': { name: 'tornado', type: 'backend', language: 'python' },
4288
+ 'pyramid': { name: 'pyramid', type: 'backend', language: 'python' },
4289
+
4290
+ // Go Frameworks
4291
+ 'gin': { name: 'gin', type: 'backend', language: 'go' },
4292
+ 'fiber': { name: 'fiber', type: 'backend', language: 'go' },
4293
+ 'echo': { name: 'echo', type: 'backend', language: 'go' },
4294
+ 'chi': { name: 'chi', type: 'backend', language: 'go' },
4295
+
4296
+ // Rust Frameworks
4297
+ 'actix': { name: 'actix-web', type: 'backend', language: 'rust' },
4298
+ 'axum': { name: 'axum', type: 'backend', language: 'rust' },
4299
+ 'rocket': { name: 'rocket', type: 'backend', language: 'rust' },
4300
+ 'warp': { name: 'warp', type: 'backend', language: 'rust' },
4301
+
4302
+ // Java/Kotlin Frameworks
4303
+ 'spring': { name: 'spring-boot', type: 'backend', language: 'java' },
4304
+ 'quarkus': { name: 'quarkus', type: 'backend', language: 'java' },
4305
+ 'micronaut': { name: 'micronaut', type: 'backend', language: 'java' },
4306
+ 'ktor': { name: 'ktor', type: 'backend', language: 'kotlin' },
4307
+
4308
+ // Ruby Frameworks
4309
+ 'config/routes.rb': { name: 'rails', type: 'fullstack', language: 'ruby' },
4310
+ 'sinatra': { name: 'sinatra', type: 'backend', language: 'ruby' },
4311
+ 'hanami': { name: 'hanami', type: 'backend', language: 'ruby' },
4312
+
4313
+ // PHP Frameworks
4314
+ 'artisan': { name: 'laravel', type: 'fullstack', language: 'php' },
4315
+ 'symfony.lock': { name: 'symfony', type: 'backend', language: 'php' },
4316
+ 'slim': { name: 'slim', type: 'backend', language: 'php' },
4317
+
4318
+ // Elixir Frameworks
4319
+ 'phoenix': { name: 'phoenix', type: 'fullstack', language: 'elixir' },
4320
+
4321
+ // .NET Frameworks
4322
+ 'Startup.cs': { name: 'aspnet-core', type: 'backend', language: 'csharp' },
4323
+ };
4324
+
4325
+ for (const [indicator, config] of Object.entries(frameworkIndicators)) {
4326
+ if (this.hasIndicator(files, indicator)) {
4327
+ return config;
4328
+ }
4329
+ }
4330
+
4331
+ return { name: 'generic', type: 'unknown' };
4332
+ }
4333
+
4334
+ private detectDatabase(files: string[]): DatabaseInfo[] {
4335
+ const databases: DatabaseInfo[] = [];
4336
+
4337
+ const dbIndicators: Record<string, DatabaseInfo> = {
4338
+ // SQL Databases
4339
+ 'prisma/schema.prisma': { type: 'sql', name: 'prisma' },
4340
+ 'drizzle': { type: 'sql', name: 'drizzle' },
4341
+ 'sequelize': { type: 'sql', name: 'sequelize' },
4342
+ 'typeorm': { type: 'sql', name: 'typeorm' },
4343
+ 'knexfile': { type: 'sql', name: 'knex' },
4344
+ 'alembic': { type: 'sql', name: 'alembic' },
4345
+ 'sqlalchemy': { type: 'sql', name: 'sqlalchemy' },
4346
+ 'diesel': { type: 'sql', name: 'diesel' },
4347
+ 'gorm': { type: 'sql', name: 'gorm' },
4348
+ 'activerecord': { type: 'sql', name: 'activerecord' },
4349
+ 'eloquent': { type: 'sql', name: 'eloquent' },
4350
+ 'ecto': { type: 'sql', name: 'ecto' },
4351
+
4352
+ // NoSQL Databases
4353
+ 'mongodb': { type: 'nosql', name: 'mongodb' },
4354
+ 'mongoose': { type: 'nosql', name: 'mongoose' },
4355
+ 'redis': { type: 'cache', name: 'redis' },
4356
+ 'dynamodb': { type: 'nosql', name: 'dynamodb' },
4357
+ 'firebase': { type: 'nosql', name: 'firebase' },
4358
+ 'supabase': { type: 'sql', name: 'supabase' },
4359
+ };
4360
+
4361
+ for (const [indicator, dbInfo] of Object.entries(dbIndicators)) {
4362
+ if (this.hasIndicator(files, indicator)) {
4363
+ databases.push(dbInfo);
4364
+ }
4365
+ }
4366
+
4367
+ return databases;
4368
+ }
4369
+
4370
+ private detectTestFramework(files: string[]): TestFrameworkInfo[] {
4371
+ const testFrameworks: TestFrameworkInfo[] = [];
4372
+
4373
+ const testIndicators: Record<string, TestFrameworkInfo> = {
4374
+ // JavaScript Testing
4375
+ 'jest.config': { name: 'jest', language: 'javascript' },
4376
+ 'vitest.config': { name: 'vitest', language: 'javascript' },
4377
+ 'mocha': { name: 'mocha', language: 'javascript' },
4378
+ 'playwright.config': { name: 'playwright', language: 'javascript', type: 'e2e' },
4379
+ 'cypress.config': { name: 'cypress', language: 'javascript', type: 'e2e' },
4380
+
4381
+ // Python Testing
4382
+ 'pytest.ini': { name: 'pytest', language: 'python' },
4383
+ 'conftest.py': { name: 'pytest', language: 'python' },
4384
+ 'unittest': { name: 'unittest', language: 'python' },
4385
+
4386
+ // Go Testing
4387
+ '_test.go': { name: 'go-test', language: 'go' },
4388
+
4389
+ // Rust Testing
4390
+ '#[test]': { name: 'rust-test', language: 'rust' },
4391
+
4392
+ // Java Testing
4393
+ 'junit': { name: 'junit', language: 'java' },
4394
+ 'testng': { name: 'testng', language: 'java' },
4395
+
4396
+ // Ruby Testing
4397
+ 'rspec': { name: 'rspec', language: 'ruby' },
4398
+ 'minitest': { name: 'minitest', language: 'ruby' },
4399
+
4400
+ // PHP Testing
4401
+ 'phpunit.xml': { name: 'phpunit', language: 'php' },
4402
+ };
4403
+
4404
+ for (const [indicator, info] of Object.entries(testIndicators)) {
4405
+ if (this.hasIndicator(files, indicator)) {
4406
+ testFrameworks.push(info);
4407
+ }
4408
+ }
4409
+
4410
+ return testFrameworks;
4411
+ }
4412
+
4413
+ private detectContainerization(files: string[]): ContainerInfo {
4414
+ if (files.some(f => f.includes('Dockerfile'))) {
4415
+ return {
4416
+ type: 'docker',
4417
+ hasCompose: files.some(f => f.includes('docker-compose')),
4418
+ hasKubernetes: files.some(f =>
4419
+ f.includes('k8s') ||
4420
+ f.includes('kubernetes') ||
4421
+ f.includes('helm')
4422
+ )
4423
+ };
4424
+ }
4425
+
4426
+ if (files.some(f => f.includes('Containerfile'))) {
4427
+ return { type: 'podman' };
4428
+ }
4429
+
4430
+ return { type: 'none' };
4431
+ }
4432
+ }
4433
+ ```
4434
+
4435
+ ---
4436
+
4437
+ ## 🎯 Actualización del Workflow Principal
4438
+
4439
+ ```typescript
4440
+ class FreelancePlannerV4Enhanced {
4441
+ async executeFull(
4442
+ projectPath: string,
4443
+ options: PlannerOptions
4444
+ ): Promise<ExecutionResult> {
4445
+ console.log('🚀 Freelance Project Planner v4.1 (Enhanced)');
4446
+ console.log('📚 GitFlow + Stacked PRs + Multi-Language Support\n');
4447
+
4448
+ // FASE 0: Detección de Tech Stack
4449
+ console.log('🔍 FASE 0: Detección de Tech Stack');
4450
+ const techStack = await this.techDetector.detect(projectPath);
4451
+ this.printTechStackSummary(techStack);
4452
+
4453
+ // FASE 1: Docker (adaptado al stack detectado)
4454
+ console.log('\n🐳 FASE 1: Dockerización');
4455
+ const dockerSetup = await this.dockerGenerator.generate(techStack);
4456
+
4457
+ // FASE 2: GitHub Actions (multi-lenguaje)
4458
+ console.log('\n⚙️ FASE 2: GitHub Actions');
4459
+ const workflows = await this.workflowGenerator.generate(techStack, {
4460
+ stackedPRs: true,
4461
+ previewEnvironments: options.previewEnvironments,
4462
+ notifications: options.notifications
4463
+ });
4464
+
4465
+ // FASE 3: GitFlow Setup
4466
+ console.log('\n🌳 FASE 3: GitFlow Setup');
4467
+ await this.setupGitFlow(options.repo);
4468
+
4469
+ // FASE 4: CODEOWNERS
4470
+ console.log('\n👥 FASE 4: CODEOWNERS');
4471
+ const codeowners = await this.codeownersGenerator.generate(projectPath);
4472
+
4473
+ // FASE 5: Stacked PR Tooling
4474
+ console.log('\n🛠️ FASE 5: Stacked PR Tooling');
4475
+ await this.setupStackTooling(projectPath);
4476
+
4477
+ // FASE 6: Planificación como Stacks
4478
+ console.log('\n📚 FASE 6: Generación de Stacks');
4479
+ const stacks = await this.planIterationsAsStacks(analysis);
4480
+
4481
+ // FASE 7: Notificaciones
4482
+ if (options.notifications) {
4483
+ console.log('\n🔔 FASE 7: Configuración de Notificaciones');
4484
+ await this.setupNotifications(options);
4485
+ }
4486
+
4487
+ // FASE 8: Rollback Strategy
4488
+ console.log('\n🔙 FASE 8: Documentación de Rollback');
4489
+ const rollbackDocs = await this.generateRollbackDocs(stacks);
4490
+
4491
+ this.printFinalSummary({
4492
+ techStack,
4493
+ dockerSetup,
4494
+ workflows,
4495
+ codeowners,
4496
+ stacks,
4497
+ rollbackDocs
4498
+ });
4499
+
4500
+ return { /* ... */ };
4501
+ }
4502
+ }
4503
+ ```