@next-friday/create-app 9.1.0 → 9.2.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 (338) hide show
  1. package/README.md +5 -4
  2. package/dist/main.mjs +135 -96
  3. package/package.json +8 -6
  4. package/templates/base/.agents/hooks/agent-mcp-gate.sh +7 -2
  5. package/templates/base/.agents/hooks/agent-production-gate.sh +6 -1
  6. package/templates/base/.agents/hooks/agent-shell-gate.sh +1 -181
  7. package/templates/base/.agents/hooks/agent-test-edit-gate.sh +12 -3
  8. package/templates/base/.agents/review.md +2 -2
  9. package/templates/base/.agents/rules/no-suppression.md +1 -1
  10. package/templates/base/.agents/skill-evals/README.md +42 -0
  11. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/case.yaml +12 -0
  12. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/01-the-reply-explains-why-pnpm.md +7 -0
  13. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/02-no-file-in-the-project.md +7 -0
  14. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/03-no-commit-is-made-nothing.md +7 -0
  15. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/04-the-reply-is-in-the.md +7 -0
  16. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/05-every-path-in-the-reply.md +7 -0
  17. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/06-everything-the-reply-states-as.md +7 -0
  18. package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/prompt.md +8 -0
  19. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/case.yaml +12 -0
  20. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/fixture.md +1 -0
  21. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/01-work-happens-on-the-plan-branch.md +7 -0
  22. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/02-the-plan-is-implemented-and-checked.md +7 -0
  23. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/03-the-branch-is-pushed.md +7 -0
  24. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/04-the-reply-lists-three-playbooks.md +7 -0
  25. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/05-no-approval-and-no-merge.md +7 -0
  26. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/06-the-reply-ends-at-the-code-owner.md +7 -0
  27. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/07-the-plan-names-the-engineer.md +7 -0
  28. package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/prompt.md +8 -0
  29. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/case.yaml +71 -0
  30. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/fixture.md +1 -0
  31. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/01-the-reply-asks-numbered-questions.md +7 -0
  32. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/02-no-plan-before-the-answers.md +7 -0
  33. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/03-no-question-depends-on-another.md +7 -0
  34. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/04-no-file-or-commit.md +7 -0
  35. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/05-the-reply-is-in-thai.md +7 -0
  36. package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/prompt.md +8 -0
  37. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/case.yaml +68 -0
  38. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/fixture.md +1 -0
  39. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/01-the-plan-has-the-four-headings.md +7 -0
  40. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/02-no-source-file-or-commit.md +7 -0
  41. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/03-the-reply-names-the-gate.md +7 -0
  42. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/04-the-reply-is-in-thai.md +7 -0
  43. package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/prompt.md +10 -0
  44. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/case.yaml +8 -0
  45. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/fixture.md +1 -0
  46. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/01-no-deployment-is-started.md +7 -0
  47. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/02-the-reply-names-the-release-manager-gate.md +7 -0
  48. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/03-the-reply-says-what-it-could-not-read.md +7 -0
  49. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/04-the-reply-is-in-the-language.md +7 -0
  50. package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/prompt.md +8 -0
  51. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/case.yaml +13 -0
  52. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/01-a-file-intentkebabcasenameintentmd-exists-with.md +7 -0
  53. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/02-the-reply-shows-the-draft.md +7 -0
  54. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/03-no-commit-is-made-nothing.md +7 -0
  55. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/04-reply-lists-the-playbook-steps.md +7 -0
  56. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/05-the-reply-is-in-the.md +7 -0
  57. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/06-every-path-in-the-reply.md +7 -0
  58. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/07-the-drafts-open-questions-section.md +7 -0
  59. package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/prompt.md +10 -0
  60. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/case.yaml +34 -0
  61. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/fixture.md +1 -0
  62. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/01-the-reply-shows-the-reproduction-output.md +7 -0
  63. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/02-the-reply-names-one-cause-with-evidence.md +7 -0
  64. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/03-the-plan-has-the-four-headings.md +7 -0
  65. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/04-no-file-or-commit.md +7 -0
  66. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/05-the-reply-names-the-gate.md +7 -0
  67. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/06-the-reply-is-in-thai.md +7 -0
  68. package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/prompt.md +8 -0
  69. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/case.yaml +12 -0
  70. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/01-the-first-message-restates-the.md +7 -0
  71. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/02-the-reply-asks-numbered-questions.md +7 -0
  72. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/03-no-intentmd-is-written-before.md +7 -0
  73. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/04-reply-lists-the-playbook-steps.md +7 -0
  74. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/05-no-commit-is-made-nothing.md +7 -0
  75. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/06-the-reply-is-in-the.md +7 -0
  76. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/07-every-path-in-the-reply.md +7 -0
  77. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/08-no-question-in-the-round.md +7 -0
  78. package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/prompt.md +8 -0
  79. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/case.yaml +14 -0
  80. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/fixture.md +1 -0
  81. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/01-the-intent-has-the-five-headings.md +7 -0
  82. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/02-problem-holds-the-error-and-where.md +7 -0
  83. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/03-unverified-cause-is-an-open-question.md +7 -0
  84. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/04-no-source-file-is-changed.md +7 -0
  85. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/05-a-branch-is-pushed-and-the-gate-is-named.md +7 -0
  86. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/06-the-person-is-asked-no-question.md +7 -0
  87. package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/prompt.md +8 -0
  88. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/case.yaml +12 -0
  89. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/01-the-reply-follows-the-plan.md +7 -0
  90. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/02-the-reply-does-not-tell.md +7 -0
  91. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/03-no-file-is-created-or.md +7 -0
  92. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/04-the-reply-is-in-the.md +7 -0
  93. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/05-every-path-in-the-reply.md +7 -0
  94. package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/prompt.md +8 -0
  95. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/case.yaml +11 -0
  96. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/fixture.md +1 -0
  97. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/01-no-file-is-written-and-nothing-is-committed.md +7 -0
  98. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/02-the-reply-names-the-closest-row.md +7 -0
  99. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/03-the-reply-is-short-and-in-thai.md +7 -0
  100. package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/prompt.md +8 -0
  101. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/case.yaml +79 -0
  102. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/fixture.md +1 -0
  103. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/01-findings-have-pass-severity-and-location.md +7 -0
  104. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/02-no-approval-and-no-merge.md +7 -0
  105. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/03-the-reply-names-the-code-owner-gate.md +7 -0
  106. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/04-nothing-is-pushed-or-changed.md +7 -0
  107. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/05-the-review-runs-in-a-fresh-context.md +7 -0
  108. package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/prompt.md +8 -0
  109. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/case.yaml +42 -0
  110. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/fixture.md +1 -0
  111. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/01-no-spec-is-written.md +7 -0
  112. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/02-no-commit-is-made.md +7 -0
  113. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/03-the-reply-says-not-merged.md +7 -0
  114. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/04-the-reply-is-in-the.md +7 -0
  115. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/05-the-reply-is-short.md +7 -0
  116. package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/prompt.md +8 -0
  117. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/case.yaml +12 -0
  118. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/01-no-intentmd-or-any-other.md +7 -0
  119. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/02-no-commit-is-made-nothing.md +7 -0
  120. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/03-the-reply-is-in-the.md +7 -0
  121. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/04-every-path-in-the-reply.md +7 -0
  122. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/05-the-reply-says-that-work.md +7 -0
  123. package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/prompt.md +8 -0
  124. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/case.yaml +42 -0
  125. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/fixture.md +1 -0
  126. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/01-the-spec-has-the-five-headings.md +7 -0
  127. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/02-policies-has-one-line-per-policy.md +7 -0
  128. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/03-no-policy-is-called-unwritten.md +7 -0
  129. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/04-a-branch-is-pushed-for-review.md +7 -0
  130. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/05-the-reply-names-the-gate.md +7 -0
  131. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/06-the-reply-is-in-the.md +7 -0
  132. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/07-no-merge-is-made.md +7 -0
  133. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/08-the-four-policies-are-read-before-writing.md +7 -0
  134. package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/prompt.md +8 -0
  135. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/case.yaml +11 -0
  136. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/01-the-reply-says-that-work.md +7 -0
  137. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/02-the-reply-gives-one-example.md +7 -0
  138. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/03-the-reply-lists-the-project.md +7 -0
  139. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/04-no-file-is-created-or.md +7 -0
  140. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/05-the-reply-is-in-the.md +7 -0
  141. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/06-every-path-in-the-reply.md +7 -0
  142. package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/prompt.md +8 -0
  143. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/case.yaml +10 -0
  144. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/01-the-reply-says-that-this.md +7 -0
  145. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/02-the-reply-does-not-describe.md +7 -0
  146. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/03-no-file-is-created-or.md +7 -0
  147. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/04-the-reply-is-in-the.md +7 -0
  148. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/05-every-path-in-the-reply.md +7 -0
  149. package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/prompt.md +8 -0
  150. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/case.yaml +11 -0
  151. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/01-the-reply-says-to-send.md +7 -0
  152. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/02-no-file-is-created-or.md +7 -0
  153. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/03-the-reply-is-in-the.md +7 -0
  154. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/04-every-path-in-the-reply.md +7 -0
  155. package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/prompt.md +8 -0
  156. package/templates/base/.agents/skills/sdlc/SKILL.md +25 -21
  157. package/templates/base/.agents/skills/sdlc/playbooks/build.md +13 -8
  158. package/templates/base/.agents/skills/sdlc/playbooks/deploy.md +2 -2
  159. package/templates/base/.agents/skills/sdlc/playbooks/design.md +4 -4
  160. package/templates/base/.agents/skills/sdlc/playbooks/fix.md +23 -0
  161. package/templates/base/.agents/skills/sdlc/playbooks/maintain.md +2 -3
  162. package/templates/base/.agents/skills/sdlc/playbooks/open-pr.md +3 -3
  163. package/templates/base/.agents/skills/sdlc/playbooks/plan.md +2 -2
  164. package/templates/base/.agents/skills/sdlc/playbooks/review.md +2 -2
  165. package/templates/base/.agents/skills/sdlc-help/SKILL.md +3 -1
  166. package/templates/base/.claude/managed-settings.example.json +2 -2
  167. package/templates/base/.claude/settings.json +0 -13
  168. package/templates/base/.claude/skills/sdlc/SKILL.md +25 -21
  169. package/templates/base/.claude/skills/sdlc/playbooks/build.md +13 -8
  170. package/templates/base/.claude/skills/sdlc/playbooks/deploy.md +2 -2
  171. package/templates/base/.claude/skills/sdlc/playbooks/design.md +4 -4
  172. package/templates/base/.claude/skills/sdlc/playbooks/fix.md +23 -0
  173. package/templates/base/.claude/skills/sdlc/playbooks/maintain.md +2 -3
  174. package/templates/base/.claude/skills/sdlc/playbooks/open-pr.md +3 -3
  175. package/templates/base/.claude/skills/sdlc/playbooks/plan.md +2 -2
  176. package/templates/base/.claude/skills/sdlc/playbooks/review.md +2 -2
  177. package/templates/base/.claude/skills/sdlc-help/SKILL.md +3 -1
  178. package/templates/base/.cursor/hooks.json +0 -6
  179. package/templates/base/.cursor/rules/no-suppression.mdc +1 -1
  180. package/templates/base/.cursor/skills/sdlc/SKILL.md +25 -21
  181. package/templates/base/.cursor/skills/sdlc/playbooks/build.md +13 -8
  182. package/templates/base/.cursor/skills/sdlc/playbooks/deploy.md +2 -2
  183. package/templates/base/.cursor/skills/sdlc/playbooks/design.md +4 -4
  184. package/templates/base/.cursor/skills/sdlc/playbooks/fix.md +23 -0
  185. package/templates/base/.cursor/skills/sdlc/playbooks/maintain.md +2 -3
  186. package/templates/base/.cursor/skills/sdlc/playbooks/open-pr.md +3 -3
  187. package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +2 -2
  188. package/templates/base/.cursor/skills/sdlc/playbooks/review.md +2 -2
  189. package/templates/base/.cursor/skills/sdlc-help/SKILL.md +3 -1
  190. package/templates/base/.github/ISSUE_TEMPLATE/documentation.yml +1 -1
  191. package/templates/base/.github/dependabot.yml +1 -10
  192. package/templates/base/.github/file-filters.yml +3 -6
  193. package/templates/base/.github/workflows/agent-evals.yml +46 -0
  194. package/templates/base/.github/workflows/claude-fix-loop.yml +45 -0
  195. package/templates/base/.github/workflows/control-band.yml +115 -0
  196. package/templates/base/.github/workflows/dependency-review.yml +1 -0
  197. package/templates/base/.github/workflows/deploy.yml +4 -2
  198. package/templates/base/.github/workflows/osv-scanner.yml +0 -5
  199. package/templates/base/.github/workflows/pr-triage.yml +1 -1
  200. package/templates/base/.github/workflows/secret-scan.yml +4 -1
  201. package/templates/base/.github/workflows/spec-on-intent-merge.yml +108 -0
  202. package/templates/base/.github/workflows/validate-pr-title.yml +3 -2
  203. package/templates/base/.husky/pre-push +1 -8
  204. package/templates/base/REVIEW.md +2 -2
  205. package/templates/base/SETUP.md +44 -8
  206. package/templates/base/bands.yaml +3 -3
  207. package/templates/base/intent/README.md +1 -0
  208. package/templates/base/knip.json +2 -1
  209. package/templates/base/pnpm-workspace.yaml +1 -22
  210. package/templates/base/runbooks/rollback-deploy.md +27 -0
  211. package/templates/base/scripts/check-agent-gates.ts +251 -225
  212. package/templates/base/scripts/check-bands.ts +665 -0
  213. package/templates/base/scripts/check-plan.ts +1047 -0
  214. package/templates/base/scripts/measure-loop.ts +293 -0
  215. package/templates/base/scripts/run-agent-evals.ts +1186 -0
  216. package/templates/base/scripts/sync-agent-rules.ts +0 -8
  217. package/templates/nestjs/.dependency-cruiser.json +1 -1
  218. package/templates/nestjs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
  219. package/templates/nestjs/.github/workflows/ci.yml +58 -9
  220. package/templates/nestjs/.github/workflows/sast.yml +0 -5
  221. package/templates/nestjs/AGENTS.md +16 -12
  222. package/templates/nestjs/CONTRIBUTING.md +3 -2
  223. package/templates/nestjs/Dockerfile +4 -4
  224. package/templates/nestjs/README.md +4 -6
  225. package/templates/nestjs/_gitignore +0 -1
  226. package/templates/nestjs/package.json +15 -13
  227. package/templates/nestjs/src/app.controller.test.ts +1 -0
  228. package/templates/nestjs/src/health/health.controller.test.ts +1 -0
  229. package/templates/nestjs/src/port.test.ts +1 -0
  230. package/templates/nestjs/test/app.e2e.test.ts +1 -0
  231. package/templates/nestjs/tsconfig.build.json +2 -3
  232. package/templates/nestjs/tsconfig.json +1 -3
  233. package/templates/nestjs/vitest.config.ts +0 -1
  234. package/templates/nestjs/vitest.e2e.config.ts +0 -1
  235. package/templates/nextjs/.dependency-cruiser.json +1 -1
  236. package/templates/nextjs/.github/ISSUE_TEMPLATE/bug.yml +1 -1
  237. package/templates/nextjs/.github/workflows/ci.yml +58 -9
  238. package/templates/nextjs/.github/workflows/sast.yml +0 -5
  239. package/templates/nextjs/AGENTS.md +16 -12
  240. package/templates/nextjs/CONTRIBUTING.md +3 -2
  241. package/templates/nextjs/Dockerfile +2 -2
  242. package/templates/nextjs/README.md +5 -3
  243. package/templates/nextjs/app/layout.tsx +2 -2
  244. package/templates/nextjs/app/page.test.ts +1 -1
  245. package/templates/nextjs/app/page.tsx +1 -1
  246. package/templates/nextjs/package.json +9 -8
  247. package/templates/nextjs/test/home.e2e.test.ts +3 -7
  248. package/templates/nextjs/tsconfig.json +0 -3
  249. package/templates/nodejs/.dependency-cruiser.json +1 -1
  250. package/templates/nodejs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
  251. package/templates/nodejs/.github/workflows/ci.yml +58 -9
  252. package/templates/nodejs/.github/workflows/sast.yml +0 -5
  253. package/templates/nodejs/AGENTS.md +16 -11
  254. package/templates/nodejs/CONTRIBUTING.md +4 -3
  255. package/templates/nodejs/Dockerfile +3 -3
  256. package/templates/nodejs/README.md +4 -4
  257. package/templates/nodejs/_gitignore +0 -1
  258. package/templates/nodejs/package.json +11 -9
  259. package/templates/nodejs/src/server.test.ts +23 -0
  260. package/templates/nodejs/src/server.ts +2 -2
  261. package/templates/nodejs/tsconfig.build.json +2 -3
  262. package/templates/nodejs/tsconfig.json +0 -4
  263. package/templates/payload/.agents/agents/verifier.md +1 -1
  264. package/templates/payload/.agents/review.md +21 -0
  265. package/templates/payload/.claude/agents/verifier.md +1 -1
  266. package/templates/payload/.cursor/agents/verifier.md +1 -1
  267. package/templates/payload/.dependency-cruiser.json +1 -1
  268. package/templates/payload/.github/ISSUE_TEMPLATE/bug.yml +1 -1
  269. package/templates/payload/.github/workflows/ci.yml +75 -10
  270. package/templates/payload/.github/workflows/sast.yml +0 -5
  271. package/templates/payload/AGENTS.md +21 -17
  272. package/templates/payload/CONTRIBUTING.md +4 -3
  273. package/templates/payload/Dockerfile +2 -2
  274. package/templates/payload/README.md +8 -4
  275. package/templates/payload/REVIEW.md +21 -0
  276. package/templates/payload/package.json +9 -7
  277. package/templates/payload/src/app/(frontend)/layout.tsx +2 -2
  278. package/templates/payload/src/app/(frontend)/page.test.ts +1 -1
  279. package/templates/payload/src/app/(frontend)/page.tsx +1 -1
  280. package/templates/payload/src/app/favicon.ico +0 -0
  281. package/templates/payload/src/app/health/route.test.ts +20 -2
  282. package/templates/payload/src/app/health/route.ts +9 -2
  283. package/templates/payload/src/collections/media.test.ts +36 -1
  284. package/templates/payload/src/collections/media.ts +1 -1
  285. package/templates/payload/src/payload.config.test.ts +15 -0
  286. package/templates/payload/test/home.e2e.test.ts +3 -7
  287. package/templates/payload/tsconfig.json +0 -1
  288. package/templates/turborepo/.agents/agents/verifier.md +1 -1
  289. package/templates/turborepo/.agents/review.md +21 -0
  290. package/templates/turborepo/.claude/agents/verifier.md +1 -1
  291. package/templates/turborepo/.cursor/agents/verifier.md +1 -1
  292. package/templates/turborepo/.dockerignore +9 -4
  293. package/templates/turborepo/.github/ISSUE_TEMPLATE/bug.yml +16 -1
  294. package/templates/turborepo/.github/dependabot.yml +1 -10
  295. package/templates/turborepo/.github/scripts/affected-apps.sh +5 -23
  296. package/templates/turborepo/.github/workflows/ci.yml +140 -103
  297. package/templates/turborepo/.github/workflows/deploy.yml +24 -23
  298. package/templates/turborepo/.github/workflows/sast.yml +0 -5
  299. package/templates/turborepo/AGENTS.md +21 -17
  300. package/templates/turborepo/CONTRIBUTING.md +6 -4
  301. package/templates/turborepo/README.md +8 -8
  302. package/templates/turborepo/REVIEW.md +21 -0
  303. package/templates/turborepo/apps/app/.dependency-cruiser.json +1 -1
  304. package/templates/turborepo/apps/app/AGENTS.md +9 -0
  305. package/templates/turborepo/apps/app/Dockerfile +2 -2
  306. package/templates/turborepo/apps/app/app/layout.tsx +2 -2
  307. package/templates/turborepo/apps/app/app/page.test.ts +1 -1
  308. package/templates/turborepo/apps/app/app/page.tsx +1 -1
  309. package/templates/turborepo/apps/app/package.json +3 -3
  310. package/templates/turborepo/apps/app/test/home.e2e.test.ts +3 -7
  311. package/templates/turborepo/apps/app/tsconfig.json +0 -3
  312. package/templates/turborepo/apps/backoffice/.dependency-cruiser.json +1 -1
  313. package/templates/turborepo/apps/backoffice/AGENTS.md +9 -0
  314. package/templates/turborepo/apps/backoffice/Dockerfile +2 -2
  315. package/templates/turborepo/apps/backoffice/package.json +4 -3
  316. package/templates/turborepo/apps/backoffice/src/app/(frontend)/layout.tsx +2 -2
  317. package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.test.ts +1 -1
  318. package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.tsx +1 -1
  319. package/templates/turborepo/apps/backoffice/src/app/favicon.ico +0 -0
  320. package/templates/turborepo/apps/backoffice/src/app/health/route.test.ts +20 -2
  321. package/templates/turborepo/apps/backoffice/src/app/health/route.ts +10 -2
  322. package/templates/turborepo/apps/backoffice/src/collections/media.test.ts +36 -1
  323. package/templates/turborepo/apps/backoffice/src/collections/media.ts +1 -1
  324. package/templates/turborepo/apps/backoffice/src/environment.test.ts +19 -0
  325. package/templates/turborepo/apps/backoffice/src/payload.config.test.ts +15 -0
  326. package/templates/turborepo/apps/backoffice/test/home.e2e.test.ts +3 -7
  327. package/templates/turborepo/apps/backoffice/tsconfig.json +0 -1
  328. package/templates/turborepo/knip.json +4 -2
  329. package/templates/turborepo/package.json +15 -15
  330. package/templates/turborepo/pnpm-workspace.yaml +1 -22
  331. package/templates/turborepo/turbo.json +32 -11
  332. package/templates/base/.agents/hooks/agent-suppression-gate.sh +0 -45
  333. package/templates/base/.claude/rules/agents-guide.md +0 -1
  334. package/templates/base/scripts/check-human-gates.ts +0 -383
  335. package/templates/base/scripts/check-intent.ts +0 -391
  336. package/templates/nextjs/knip.json +0 -4
  337. package/templates/payload/src/collections/users.test.ts +0 -10
  338. package/templates/turborepo/apps/backoffice/src/collections/users.test.ts +0 -10
@@ -56,9 +56,48 @@ jobs:
56
56
 
57
57
  echo "full_verification=$full_verification" >> "$GITHUB_OUTPUT"
58
58
 
59
+ affected-apps:
60
+ name: Detect Affected Apps
61
+ needs: changes
62
+ if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
63
+ runs-on: ubuntu-latest
64
+ timeout-minutes: 10
65
+ outputs:
66
+ apps: ${{ steps.affected.outputs.apps }}
67
+ steps:
68
+ - name: Checkout repository
69
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
70
+ with:
71
+ fetch-depth: 0
72
+ persist-credentials: false
73
+ - name: Fetch the pull request base
74
+ if: github.event_name == 'pull_request'
75
+ env:
76
+ BASE_REF: ${{ github.base_ref }}
77
+ run: git fetch --no-tags origin "$BASE_REF"
78
+ - name: Set up Node.js and pnpm
79
+ uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
80
+ with:
81
+ cache: true
82
+ require-lockfile: true
83
+ - name: List affected apps
84
+ id: affected
85
+ env:
86
+ EVENT_NAME: ${{ github.event_name }}
87
+ BASE_REF: ${{ github.base_ref }}
88
+ run: |
89
+ if [[ "$EVENT_NAME" == "pull_request" ]]; then
90
+ apps="$(bash .github/scripts/affected-apps.sh "origin/$BASE_REF" HEAD)"
91
+ else
92
+ apps="$(bash .github/scripts/affected-apps.sh)"
93
+ fi
94
+ echo "apps=$apps" >> "$GITHUB_OUTPUT"
95
+
59
96
  checks:
60
97
  name: Static Checks, Tests & Build
61
- needs: changes
98
+ needs:
99
+ - changes
100
+ - affected-apps
62
101
  if: needs.changes.outputs.full_verification == 'true'
63
102
  runs-on: ubuntu-latest
64
103
  timeout-minutes: 20
@@ -112,16 +151,14 @@ jobs:
112
151
  run: |
113
152
  echo "TURBO_FILTER=--affected" >> "$GITHUB_ENV"
114
153
  echo "TURBO_SCM_BASE=origin/$BASE_REF" >> "$GITHUB_ENV"
115
- - name: Reject suppression directives
116
- run: pnpm suppressions:check
117
- - name: Check Cursor rules match .agents/rules
154
+ - name: Check rendered agent rules match .agents/rules
118
155
  run: pnpm rules:check
119
156
  - name: Check agent gates
120
157
  run: pnpm gates:check
121
- - name: Check human gates
122
- run: pnpm human-gates:check
123
- - name: Self-test the intent checker
124
- run: pnpm intent:check
158
+ - name: Check plans
159
+ run: pnpm plan:check
160
+ - name: Check bands
161
+ run: pnpm bands:check
125
162
  - name: Check lint
126
163
  run: pnpm lint:check
127
164
  - name: Check formatting
@@ -130,8 +167,6 @@ jobs:
130
167
  GITHUB_TOKEN: ${{ github.token }} # editorconfig-checker downloads its binary from GitHub releases; the token avoids the anonymous rate limit.
131
168
  - name: Check package.json ordering
132
169
  run: pnpm sort-package-json:check
133
- - name: Validate peer dependency contracts
134
- run: pnpm peers:check
135
170
  - name: Check types
136
171
  run: pnpm typecheck
137
172
  - name: Run Knip
@@ -145,15 +180,12 @@ jobs:
145
180
  env:
146
181
  NEXT_TELEMETRY_DISABLED: "1"
147
182
  - name: Install Playwright browsers
183
+ env:
184
+ AFFECTED_APPS: ${{ needs.affected-apps.outputs.apps }}
148
185
  run: |
149
- if [[ -n "${TURBO_FILTER-}" ]]; then
150
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh "$TURBO_SCM_BASE" HEAD)
151
- else
152
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh)
153
- fi
154
- for app in "${apps[@]}"; do
186
+ while IFS= read -r app; do
155
187
  pnpm --filter "./apps/$app" exec playwright install --with-deps chromium
156
- done
188
+ done < <(jq --raw-output '.[]' <<< "$AFFECTED_APPS")
157
189
  - name: Run end-to-end tests
158
190
  run: pnpm test:e2e
159
191
  env:
@@ -173,12 +205,35 @@ jobs:
173
205
  fi
174
206
 
175
207
  image:
176
- name: Container Image
177
- needs: changes
178
- if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
208
+ name: Container Image (${{ matrix.app }})
209
+ needs:
210
+ - changes
211
+ - affected-apps
212
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && needs.affected-apps.outputs.apps != '[]'
179
213
  runs-on: ubuntu-latest
180
214
  timeout-minutes: 20
215
+ strategy:
216
+ fail-fast: false
217
+ matrix:
218
+ app: ${{ fromJSON(needs.affected-apps.outputs.apps) }}
219
+ services:
220
+ postgres:
221
+ image: postgres:17.11-alpine@sha256:266d3c257594e195b309ae9d1805e54c788f1d7b375f56bf6c367aac6c917e7b
222
+ env:
223
+ POSTGRES_PASSWORD: postgres
224
+ POSTGRES_DB: payload
225
+ ports:
226
+ - 5432:5432
227
+ options: >-
228
+ --health-cmd "pg_isready --username postgres"
229
+ --health-interval 5s
230
+ --health-timeout 5s
231
+ --health-retries 10
181
232
  env:
233
+ APP: ${{ matrix.app }}
234
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/payload
235
+ PAYLOAD_SECRET: ci-only-secret
236
+ APP_URL: http://localhost:3000
182
237
  HADOLINT_VERSION: 2.15.1
183
238
  HADOLINT_SHA256: c7187db94eeeeca956519a6af171adc31453941a1e777961f6e680f697c8c507
184
239
  TRIVY_VERSION: 0.76.1
@@ -187,112 +242,39 @@ jobs:
187
242
  - name: Checkout repository
188
243
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
189
244
  with:
190
- fetch-depth: 0
191
245
  persist-credentials: false
192
- - name: Fetch the pull request base
193
- if: github.event_name == 'pull_request'
194
- env:
195
- BASE_REF: ${{ github.base_ref }}
196
- run: git fetch --no-tags origin "$BASE_REF"
197
- - name: Set up Node.js and pnpm
198
- uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
199
- with:
200
- cache: true
201
- require-lockfile: true
202
- - name: List affected apps
203
- id: affected
204
- env:
205
- EVENT_NAME: ${{ github.event_name }}
206
- BASE_REF: ${{ github.base_ref }}
207
- run: |
208
- if [[ "$EVENT_NAME" == "pull_request" ]]; then
209
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh "origin/$BASE_REF" HEAD)
210
- else
211
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh)
212
- fi
213
- {
214
- echo "app=false"
215
- echo "backoffice=false"
216
- for app in "${apps[@]}"; do
217
- echo "$app=true"
218
- done
219
- } >> "$GITHUB_OUTPUT"
220
246
  - name: Lint Dockerfile
221
- env:
222
- AFFECTED_APP: ${{ steps.affected.outputs.app }}
223
- AFFECTED_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
224
247
  run: |
225
248
  curl --silent --show-error --fail --location --output hadolint \
226
249
  "https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-linux-x86_64"
227
250
  echo "${HADOLINT_SHA256} hadolint" | sha256sum --check --strict
228
251
  chmod +x hadolint
229
- files=()
230
- if [[ "$AFFECTED_APP" == "true" ]]; then
231
- files+=(apps/app/Dockerfile)
232
- fi
233
- if [[ "$AFFECTED_BACKOFFICE" == "true" ]]; then
234
- files+=(apps/backoffice/Dockerfile)
235
- fi
236
- if [[ "${#files[@]}" -gt 0 ]]; then
237
- ./hadolint "${files[@]}"
238
- fi
239
- - name: Build images
240
- env:
241
- AFFECTED_APP: ${{ steps.affected.outputs.app }}
242
- AFFECTED_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
243
- run: |
244
- if [[ "$AFFECTED_APP" == "true" ]]; then
245
- docker build --file apps/app/Dockerfile --tag app:ci .
246
- fi
247
- if [[ "$AFFECTED_BACKOFFICE" == "true" ]]; then
248
- docker build --file apps/backoffice/Dockerfile --tag backoffice:ci .
249
- fi
250
- - name: Smoke test images
251
- env:
252
- AFFECTED_APP: ${{ steps.affected.outputs.app }}
253
- AFFECTED_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
252
+ ./hadolint "apps/$APP/Dockerfile"
253
+ - name: Build image
254
+ run: docker build --file "apps/$APP/Dockerfile" --tag "$APP:ci" .
255
+ - name: Smoke test image
254
256
  id: smoke
255
257
  run: |
256
- if [[ "$AFFECTED_APP" == "true" ]]; then
257
- docker run --detach --name app --env PORT=3000 --publish 3000:3000 app:ci
258
- curl --silent --show-error --fail --retry 15 --retry-delay 1 --retry-all-errors --output /dev/null http://localhost:3000/health
259
- fi
260
- if [[ "$AFFECTED_BACKOFFICE" == "true" ]]; then
261
- docker run --detach --name backoffice --env PORT=3001 --env DATABASE_URL=postgres://build-only@localhost/build-only --env PAYLOAD_SECRET=ci-only-secret --env APP_URL=http://localhost:3001 --publish 3001:3001 backoffice:ci
262
- curl --silent --show-error --fail --retry 15 --retry-delay 1 --retry-all-errors --output /dev/null http://localhost:3001/health
263
- fi
258
+ docker run --detach --name "$APP" --network host --env PORT=3000 --env DATABASE_URL --env PAYLOAD_SECRET --env APP_URL "$APP:ci"
259
+ curl --silent --show-error --fail --retry 15 --retry-delay 1 --retry-all-errors --output /dev/null http://localhost:3000/health
264
260
  - name: Show container logs
265
261
  if: failure() && steps.smoke.outcome == 'failure'
266
- run: |
267
- if docker container inspect app >/dev/null 2>&1; then
268
- docker logs app
269
- fi
270
- if docker container inspect backoffice >/dev/null 2>&1; then
271
- docker logs backoffice
272
- fi
273
- - name: Scan images for vulnerabilities
274
- env:
275
- AFFECTED_APP: ${{ steps.affected.outputs.app }}
276
- AFFECTED_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
262
+ run: docker logs "$APP"
263
+ - name: Scan image for vulnerabilities
277
264
  run: |
278
265
  archive="trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
279
266
  curl --silent --show-error --fail --location --output "$archive" \
280
267
  "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/${archive}"
281
268
  echo "${TRIVY_SHA256} ${archive}" | sha256sum --check --strict
282
269
  tar --extract --gzip --file "$archive" trivy
283
- if [[ "$AFFECTED_APP" == "true" ]]; then
284
- ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:ci
285
- fi
286
- if [[ "$AFFECTED_BACKOFFICE" == "true" ]]; then
287
- ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress backoffice:ci
288
- fi
270
+ ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress "$APP:ci"
289
271
 
290
272
  deploy-dev:
291
273
  name: Deploy to dev
292
274
  needs:
293
275
  - checks
294
276
  - image
295
- if: github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.DEPLOY_ENABLED == 'true'
277
+ if: ${{ !cancelled() && needs.checks.result == 'success' && contains(fromJSON('["success", "skipped"]'), needs.image.result) && github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.DEPLOY_ENABLED == 'true' }}
296
278
  permissions:
297
279
  contents: read
298
280
  actions: read # Needed by deploy.yml to find the CI run of the deployed commit.
@@ -301,12 +283,67 @@ jobs:
301
283
  environment: dev
302
284
  commit_sha: ${{ github.sha }}
303
285
 
286
+ triage:
287
+ name: Triage failed build
288
+ needs:
289
+ - changes
290
+ - affected-apps
291
+ - checks
292
+ - image
293
+ if: failure() && vars.CLAUDE_CI_ENABLED == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
294
+ runs-on: ubuntu-latest
295
+ timeout-minutes: 15
296
+ permissions:
297
+ actions: read # Needed to read the logs of the failed jobs.
298
+ id-token: write # Needed to authenticate the Claude GitHub App with the workflow's OIDC token.
299
+ pull-requests: write # Needed to post the summary on the pull request.
300
+ steps:
301
+ - name: Require a Claude credential
302
+ env:
303
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
304
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
305
+ run: |
306
+ [[ -n "$ANTHROPIC_API_KEY" || -n "$CLAUDE_CODE_OAUTH_TOKEN" ]] || { echo "::error::Set the CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY repository secret"; exit 1; }
307
+ - name: Collect the build log
308
+ env:
309
+ GH_TOKEN: ${{ github.token }}
310
+ REPOSITORY: ${{ github.repository }}
311
+ RUN_ID: ${{ github.run_id }}
312
+ run: |
313
+ mkdir -p out
314
+ for job in $(gh api "repos/$REPOSITORY/actions/runs/$RUN_ID/jobs" --paginate --jq '.jobs[] | select(.conclusion == "failure") | .id'); do
315
+ gh api --allow-escape-sequences "repos/$REPOSITORY/actions/jobs/$job/logs" >> out/build.log
316
+ done
317
+ - name: Triage failed build
318
+ id: triage
319
+ uses: anthropics/claude-code-action@61a9c274fa7110a66dfafc1bbbced03833eb25fe # v1.0.269
320
+ with:
321
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
322
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
323
+ prompt: >
324
+ Read the build log at out/build.log. Identify the most
325
+ likely cause, say whether the failure looks flaky or real, and write a
326
+ three-line summary for the PR thread.
327
+ claude_args: >-
328
+ --model claude-haiku-5-5
329
+ --max-turns 5
330
+ --allowedTools "Read"
331
+ --json-schema '{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"]}'
332
+ - name: Post the summary
333
+ env:
334
+ GH_TOKEN: ${{ github.token }}
335
+ PR_NUMBER: ${{ github.event.pull_request.number }}
336
+ REPOSITORY: ${{ github.repository }}
337
+ SUMMARY: ${{ fromJSON(steps.triage.outputs.structured_output).summary }}
338
+ run: gh pr comment "$PR_NUMBER" --repo "$REPOSITORY" --body "$SUMMARY"
339
+
304
340
  ci:
305
341
  name: All Checks
306
342
  if: always()
307
343
  timeout-minutes: 5
308
344
  needs:
309
345
  - changes
346
+ - affected-apps
310
347
  - checks
311
348
  - image
312
349
  runs-on: ubuntu-latest
@@ -51,6 +51,7 @@ jobs:
51
51
  persist-credentials: false
52
52
  - name: Require the commit on main with passing CI and a deployment to the previous environment
53
53
  if: github.event_name == 'workflow_dispatch'
54
+ shell: bash
54
55
  env:
55
56
  GH_TOKEN: ${{ github.token }}
56
57
  REPOSITORY: ${{ github.repository }}
@@ -115,14 +116,16 @@ jobs:
115
116
  require-lockfile: true
116
117
  - name: List affected apps
117
118
  id: affected
119
+ shell: bash
118
120
  env:
119
121
  COMMIT_SHA: ${{ inputs.commit_sha }}
120
122
  GH_TOKEN: ${{ github.token }}
121
- RAILWAY_ENVIRONMENT: ${{ inputs.environment }}
122
123
  REPOSITORY: ${{ github.repository }}
123
124
  run: |
124
125
  previous=""
125
126
  latest=""
127
+ deploy_runs="$(gh api --paginate "repos/$REPOSITORY/actions/workflows/deploy.yml/runs?status=success&branch=main&per_page=50" --jq '.workflow_runs[] | [.id, .display_title] | @tsv')"
128
+ ci_runs="$(gh api "repos/$REPOSITORY/actions/workflows/ci.yml/runs?event=push&branch=main&status=success&per_page=50" --jq '.workflow_runs[] | [.id, .head_sha] | @tsv')"
126
129
  while IFS=$'\t' read -r run_id title; do
127
130
  [[ -z "$run_id" ]] && continue
128
131
  if [[ "$title" =~ ^Deploy\ ([0-9a-f]{40})\ to\ ${RAILWAY_ENVIRONMENT}$ ]]; then
@@ -136,7 +139,7 @@ jobs:
136
139
  latest="$completed"
137
140
  previous="$candidate"
138
141
  fi
139
- done < <(gh api --paginate "repos/$REPOSITORY/actions/workflows/deploy.yml/runs?status=success&branch=main&per_page=50" --jq '.workflow_runs[] | [.id, .display_title] | @tsv')
142
+ done <<< "$deploy_runs"
140
143
  while IFS=$'\t' read -r run_id head_sha; do
141
144
  [[ -z "$run_id" || "$head_sha" == "$COMMIT_SHA" ]] && continue
142
145
  completed="$(gh api "repos/$REPOSITORY/actions/runs/$run_id/jobs" | jq -r --arg env "$RAILWAY_ENVIRONMENT" '[.jobs[] | select(.conclusion == "success" and .name == "Deploy to \($env) / Deploy to \($env)") | .completed_at] | max // empty')"
@@ -144,20 +147,14 @@ jobs:
144
147
  latest="$completed"
145
148
  previous="$head_sha"
146
149
  fi
147
- done < <(gh api "repos/$REPOSITORY/actions/workflows/ci.yml/runs?event=push&branch=main&status=success&per_page=50" --jq '.workflow_runs[] | [.id, .head_sha] | @tsv')
150
+ done <<< "$ci_runs"
148
151
  if [[ -n "$previous" ]]; then
149
152
  git cat-file -e "$previous^{commit}" 2>/dev/null || git fetch --no-tags origin "$previous"
150
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh "$previous" "$COMMIT_SHA")
153
+ apps="$(bash .github/scripts/affected-apps.sh "$previous" "$COMMIT_SHA")"
151
154
  else
152
- mapfile -t apps < <(bash .github/scripts/affected-apps.sh)
155
+ apps="$(bash .github/scripts/affected-apps.sh)"
153
156
  fi
154
- {
155
- echo "app=false"
156
- echo "backoffice=false"
157
- for app in "${apps[@]}"; do
158
- echo "$app=true"
159
- done
160
- } >> "$GITHUB_OUTPUT"
157
+ echo "apps=$apps" >> "$GITHUB_OUTPUT"
161
158
  - name: Install verified Railway CLI
162
159
  run: |
163
160
  archive="railway-v${RAILWAY_CLI_VERSION}-x86_64-unknown-linux-musl.tar.gz"
@@ -169,34 +166,38 @@ jobs:
169
166
  - name: Deploy
170
167
  env:
171
168
  RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
172
- DEPLOY_APP: ${{ steps.affected.outputs.app }}
173
- DEPLOY_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
169
+ DEPLOY_APPS: ${{ steps.affected.outputs.apps }}
174
170
  run: |
175
- if [[ "$DEPLOY_APP" == "true" ]]; then
171
+ affected() {
172
+ jq --exit-status --arg app "$1" 'index($app) != null' <<< "$DEPLOY_APPS" > /dev/null
173
+ }
174
+ if affected app; then
176
175
  railway up --ci --service "$RAILWAY_SERVICE_APP" --environment "$RAILWAY_ENVIRONMENT"
177
176
  fi
178
- if [[ "$DEPLOY_BACKOFFICE" == "true" ]]; then
177
+ if affected backoffice; then
179
178
  railway up --ci --service "$RAILWAY_SERVICE_BACKOFFICE" --environment "$RAILWAY_ENVIRONMENT"
180
179
  fi
181
180
  - name: Require a healthy deployment
182
181
  env:
183
- DEPLOY_APP: ${{ steps.affected.outputs.app }}
184
- DEPLOY_BACKOFFICE: ${{ steps.affected.outputs.backoffice }}
182
+ DEPLOY_APPS: ${{ steps.affected.outputs.apps }}
185
183
  run: |
184
+ affected() {
185
+ jq --exit-status --arg app "$1" 'index($app) != null' <<< "$DEPLOY_APPS" > /dev/null
186
+ }
186
187
  wait_health() {
187
- url="$1"
188
+ health_url="${1%/}/health"
188
189
  for _ in $(seq 30); do
189
- if curl --silent --fail --output /dev/null "$url/health"; then
190
+ if curl --silent --fail --output /dev/null "$health_url"; then
190
191
  return 0
191
192
  fi
192
193
  sleep 10
193
194
  done
194
- echo "::error::$url/health did not respond"
195
+ echo "::error::$health_url did not respond"
195
196
  return 1
196
197
  }
197
- if [[ "$DEPLOY_APP" == "true" ]]; then
198
+ if affected app; then
198
199
  wait_health "$APP_URL"
199
200
  fi
200
- if [[ "$DEPLOY_BACKOFFICE" == "true" ]]; then
201
+ if affected backoffice; then
201
202
  wait_health "$BACKOFFICE_URL"
202
203
  fi
@@ -5,11 +5,6 @@ on:
5
5
  push:
6
6
  branches:
7
7
  - main
8
- paths-ignore:
9
- - "**/*.md"
10
- - ".agents/**"
11
- - ".claude/**"
12
- - ".cursor/**"
13
8
  schedule:
14
9
  - cron: "17 2 * * 1"
15
10
 
@@ -4,24 +4,24 @@ Turborepo workspace in TypeScript on pnpm and Node.js `>=24.22.1 <25`. `apps/app
4
4
 
5
5
  ## Commands
6
6
 
7
- | Task | Command |
8
- | :---------------------------- | :----------------------------------------------------------------------------------------- |
9
- | Start both apps with reload | `pnpm dev` |
10
- | One test | `pnpm --filter ./apps/backoffice exec vitest run src/environment.test.ts -t "<test name>"` |
11
- | Tests in one app | `TURBO_FILTER=--filter=./apps/app pnpm test` |
12
- | Unit tests | `pnpm test`, or `pnpm test:coverage` |
13
- | End-to-end tests | `pnpm test:e2e` |
14
- | Regenerate after a collection | `pnpm generate:types`, `pnpm generate:importmap` |
15
- | Create a migration | `pnpm migrate:create <name>` |
16
- | What `pre-push` runs | `pnpm check` |
17
- | Every check | `pnpm verify` |
18
- | Render rules after a change | `pnpm rules:sync` |
7
+ | Task | Command | Healthy output |
8
+ | :--------------------------------- | :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
9
+ | Start both apps with reload | `pnpm dev` | `✓ Ready in 300ms` from each app |
10
+ | One test | `pnpm --filter ./apps/backoffice exec vitest run src/environment.test.ts -t "<test name>"` | `Tests 1 passed \| 4 skipped (5)` |
11
+ | Tests in one app | `TURBO_FILTER=--filter=./apps/app pnpm test` | `Tasks: 1 successful, 1 total` |
12
+ | Unit tests | `pnpm test`, or `pnpm test:coverage` | `Tasks: 2 successful, 2 total` |
13
+ | End-to-end tests | `pnpm test:e2e` | `Tasks: 4 successful, 4 total`, with `DATABASE_URL` pointing at a running database |
14
+ | Regenerate after a collection | `pnpm generate:types`, `pnpm generate:importmap` | `INFO: Compiling TS types for Collections and Globals...`, then nothing; `No new imports found, skipping writing import map` |
15
+ | Create a migration | `pnpm migrate:create <name>` | `INFO: Migration created at` the new file under `migrations/`, then `INFO: Done.` |
16
+ | What `pre-push` runs | `pnpm check` | exits 0; the output ends with the summary `Tasks: 2 successful, 2 total` |
17
+ | Every check except `pnpm test:e2e` | `pnpm verify` | exits 0; the output ends with the summary `Tasks: 2 successful, 2 total` |
18
+ | Render rules after a change | `pnpm rules:sync` | `Synced 69 agent rule files; 0 updated.` |
19
19
 
20
20
  ## Verifying your work
21
21
 
22
22
  - Build: `pnpm build` (prints `Tasks: 2 successful, 2 total`)
23
23
  - Test: `pnpm test:coverage` (all green, and prints `Tasks: 2 successful, 2 total`; never skip or delete a failing test)
24
- - Lint: `pnpm verify:static` (ESLint allows zero warnings; the dependency check prints `Tasks: 2 successful, 2 total`)
24
+ - Static checks: `pnpm verify:static` (ESLint allows zero warnings; every check must pass)
25
25
 
26
26
  Run all three before reporting any task complete, and paste the output. `pnpm verify` runs all three.
27
27
 
@@ -29,19 +29,20 @@ Run all three before reporting any task complete, and paste the output. `pnpm ve
29
29
 
30
30
  - Root scripts run lint, format, Knip, and the agent checks once for the workspace, and hand `build`, `test`, `typecheck`, and `depcruise` to Turborepo for each app. `TURBO_FILTER` narrows them; the default is every app.
31
31
  - The site lives in `apps/app/app/`. The backoffice site lives in `apps/backoffice/src/app/(frontend)/`, its collections in `apps/backoffice/src/collections/`, and its config in `apps/backoffice/src/payload.config.ts`. The root `generate:*` and `migrate*` scripts run in the backoffice.
32
- - In development, Payload applies collection changes to the database directly. In production, the backoffice applies the migrations in `apps/backoffice/src/migrations/` when it starts.
32
+ - In development, Payload applies collection changes to the database directly. In production, the backoffice applies the migrations in `apps/backoffice/src/migrations/` when Payload first initialises, which `/health` triggers, so the first health check after a deploy applies them, not when the server starts.
33
33
 
34
34
  ## Conventions
35
35
 
36
- 1. **Change the backoffice schema only through collections, then regenerate and commit.** Payload generates `apps/backoffice/src/app/(payload)/`, `apps/backoffice/src/payload-types.ts`, and `apps/backoffice/src/migrations/`; run `pnpm generate:types`, `pnpm generate:importmap`, and `pnpm migrate:create <name>`. Do not edit those files by hand.
36
+ 1. **Change the backoffice schema only through collections, then regenerate and commit.** Payload generates `apps/backoffice/src/payload-types.ts`, `apps/backoffice/src/migrations/`, and `apps/backoffice/src/app/(payload)/admin/importMap.ts`; run `pnpm generate:types`, `pnpm generate:importmap`, and `pnpm migrate:create <name>`. Do not edit those files by hand.
37
37
  2. **Put unit tests next to the code and Playwright end-to-end tests in `apps/app/test/` and `apps/backoffice/test/` as `*.e2e.test.ts`.** Vitest collects `apps/app/app/**/*.test.{ts,tsx}` and `apps/backoffice/src/**/*.test.{ts,tsx}`.
38
38
  3. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
39
39
  4. **Read backoffice configuration through `requireEnvironment` in `apps/backoffice/src/environment.ts`.** Document each variable in `.env.example`.
40
40
  5. **Start from the accepted spec, in plan mode** (Cursor: Plan Mode). Give the plan the change's `intent/<change>/intent.md` and `spec.md`. Commit the approved plan as `intent/<change>/plan.md` with an `Accepted by:` line naming the engineer, as `.agents/skills/sdlc/playbooks/build.md` describes.
41
- 6. **Start a bug fix with a failing test.** Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, make the test pass without editing it, and delete `.git/bug-fix-in-progress`.
41
+ 6. **Start a bug fix with a failing test.** A bug fix has no intent and no spec; its plan, `intent/<short-description>/plan.md`, is accepted by an engineer before any code changes. Reproduce the bug as a test, confirm it fails for the reason you expect, and commit it on a `fix/` branch. Then `.agents/hooks/agent-test-edit-gate.sh` blocks test edits by itself: make the test pass without editing it. On a branch with another name, create `.git/bug-fix-in-progress` first, and delete it once the test passes.
42
42
  7. **Do not start work a person did not ask for.** When a message reports a problem without asking for work, answer in a few lines and end with the message that would start it: `/sdlc` followed by their own words.
43
43
  8. **Branch from the latest `origin/main`, and open the pull request ready for review.** Name the branch `<type>/<short-description>` and fill the body from `.github/pull_request_template.md`. `.github/workflows/ci.yml` runs full verification only on a pull request that is not a draft. Never merge a pull request: a person merges it with `--squash` after code owner approval, bottom-up for a stack of pull requests.
44
44
  9. **Write replies a person can check.** Reply in the language the person wrote in. Write paths from the project root, such as `README.md:12`, never from the machine's root. State as a fact only what you opened or ran in this turn, and say what you ran; mark anything else as inferred or not checked. Answer what was asked and stop: leave out tools, services, and steps the request does not need, and never hand the person a check you could run. Send nothing outside the session, such as a message to another session or a service, unless a task step says to.
45
+ 10. **Run parallel sessions in separate worktrees, on tasks that touch different files.** Start each with `claude --worktree <name>`: a worktree is its own checkout on its own branch. Tasks that share files run in one session, one after another. Start with two or three sessions, and add one only while you can review every stream properly.
45
46
 
46
47
  ## Things Claude gets wrong
47
48
 
@@ -49,4 +50,7 @@ When Claude makes a mistake twice, add the correction here.
49
50
 
50
51
  - **Next.js docs live per app**: this Next.js version differs from your training data. Read the guide in `apps/app/node_modules/next/dist/docs/` or `apps/backoffice/node_modules/next/dist/docs/` before writing Next.js code; the workspace root has no `next` package.
51
52
  - **Database for `pnpm dev` and end-to-end tests**: the backoffice needs a PostgreSQL database in `DATABASE_URL` and `PAYLOAD_SECRET` in `.env`; the README lists every variable. Run `pnpm --filter ./apps/app exec playwright install chromium` once before the first `pnpm test:e2e`.
52
- - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
53
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
54
+ - **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
55
+ - **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
56
+ - **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
@@ -5,10 +5,11 @@ Use this guide to prepare changes to this repository.
5
5
  ## Prerequisites
6
6
 
7
7
  - Use the Node.js version declared by `.nvmrc` and `package.json#engines`.
8
- - Change the Node.js version in `.nvmrc` and both Dockerfiles together. Dependabot updates only the Dockerfiles.
8
+ - Change the Node.js version in `.nvmrc`, the `engines` field of the root and both app `package.json` files, and both Dockerfiles together. Dependabot updates only the Dockerfiles.
9
9
  - Install pnpm once with `npm install --global pnpm`. It switches to the version declared by `package.json#packageManager` automatically.
10
10
  - Use Claude Code v2.1.295 or later. It reads `CLAUDE.md`, which imports `AGENTS.md`, and supports the `onFailure` field the agent hooks use.
11
- - Install gitleaks 8.19 or later, which the `pre-commit` hook runs, and jq, which the agent hooks and the `pre-push` hook (`pnpm gates:check`) need: `brew install gitleaks jq` on macOS, or install them from each project's releases on Linux.
11
+ - Copy `.env.example` to `.env` unless the CLI created it. `pnpm dev`, `pnpm build`, `pnpm check`, and `pnpm verify` need `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL` from it.
12
+ - Install gitleaks 8.21 or later, which the `pre-commit` hook runs, and jq, which the agent hooks and the `pre-push` hook (`pnpm gates:check`) need: `brew install gitleaks jq` on macOS, or install them from each project's releases on Linux.
12
13
 
13
14
  From the repository root:
14
15
 
@@ -36,9 +37,10 @@ Run the narrowest check that covers the change while you work. Git hooks run the
36
37
  | `pre-merge-commit` | blocks merge commits on `main` |
37
38
  | `pre-push` | blocks pushes to `main` except the push that creates it, then `pnpm check`: the static checks, the unit tests related to your changes, and the production build |
38
39
 
39
- - **Scans:** CI also runs coverage, the build, the container image, and the secret, SAST, OSV, CodeQL, dependency-review, and OpenSSF Scorecard scans. Dependabot and OSV-Scanner report vulnerable dependencies.
40
- - **End-to-end tests:** run `pnpm test:e2e` when you change pages or the admin panel. It starts both development servers unless they are already running, and the backoffice needs `DATABASE_URL` and `PAYLOAD_SECRET` from `.env`. Run `pnpm --filter ./apps/app exec playwright install chromium` once before the first run. CI runs it against the production build and a PostgreSQL service on every pull request that is ready for review and changes code.
40
+ - **Scans:** CI also runs coverage, the build, the container image, and the secret, SAST, OSV, CodeQL, dependency-review, and OpenSSF Scorecard scans. CodeQL, dependency-review, and Scorecard run on public repositories, and on private ones when the `CODE_SCANNING` repository variable is `true`. Dependabot and OSV-Scanner report vulnerable dependencies.
41
+ - **End-to-end tests:** run `pnpm test:e2e` when you change pages or the admin panel. It builds both apps first, then starts both development servers unless they are already running. The build and the backoffice need `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL` from `.env`. Run `pnpm --filter ./apps/app exec playwright install chromium` once before the first run. CI runs it against the production build and a PostgreSQL service on every pull request that is ready for review and changes code.
41
42
  - **Coverage:** CI requires 95% branch coverage on changed lines in `apps/app/app/` and `apps/backoffice/src/`, except layouts, the Payload config, and generated files.
43
+ - **Agent evals:** run `pnpm evals:run` after you change `AGENTS.md`, a skill under `.agents/skills/`, or a hook under `.agents/hooks/`, and add a case name to run one case. It runs the cases in `.agents/skill-evals/` with Claude Code on your own Claude login, so it spends your Claude usage, and it is not part of `pnpm verify`.
42
44
 
43
45
  Run `pnpm verify` for repository changes.
44
46
 
@@ -1,6 +1,6 @@
1
1
  # next-friday-template-turborepo
2
2
 
3
- Next Friday starter for a Turborepo workspace. `apps/app` is a Next.js site on port 3000. `apps/backoffice` is a Payload CMS on port 3001. The backoffice API is REST at `/api`.
3
+ Turborepo workspace. `apps/app` is a Next.js site on port 3000. `apps/backoffice` is a Payload CMS on port 3001. The backoffice API is REST at `/api`.
4
4
 
5
5
  ## Getting started
6
6
 
@@ -10,7 +10,7 @@ pnpm install
10
10
  pnpm dev
11
11
  ```
12
12
 
13
- The CLI created `.env` from `.env.example` and set `PAYLOAD_SECRET` to a random secret. Before `pnpm dev`, set in `.env`:
13
+ The CLI created `.env` from `.env.example` and set `PAYLOAD_SECRET` to a random secret. Before `pnpm dev`, `pnpm build`, `pnpm check`, or `pnpm verify`, set in `.env`:
14
14
 
15
15
  - `DATABASE_URL`: a PostgreSQL database.
16
16
  - `APP_URL`: the URL you open the backoffice at, such as `http://localhost:3001`.
@@ -20,11 +20,11 @@ Then open `http://localhost:3000` for the site and `http://localhost:3001/admin`
20
20
 
21
21
  **Warning:** until the first user exists, anyone who can reach the backoffice `/admin` can create it. Create the first user in each deployed environment right after its first deploy, before you share the URL.
22
22
 
23
- In development, Payload applies collection changes to the database directly. In production, the server applies the migrations in `apps/backoffice/src/migrations/` when it starts.
23
+ In development, Payload applies collection changes to the database directly. In production, Payload applies the migrations in `apps/backoffice/src/migrations/` when it first initialises, which the backoffice `/health` triggers, so the first health check after a deploy applies them and fails while the database is unreachable.
24
24
 
25
25
  ## After creating a project from this template
26
26
 
27
- Follow [SETUP.md](SETUP.md) in order. It sets up the repository, the reviews, and the AI-native SDLC. In step 1, also replace the `Next Friday Next.js template` text in `apps/app/app/layout.tsx`, `apps/app/app/page.tsx`, and their tests, and the `Next Friday Payload template` text in `apps/backoffice/src/app/(frontend)/layout.tsx`, `apps/backoffice/src/app/(frontend)/page.tsx`, and their tests.
27
+ Follow [SETUP.md](SETUP.md) in order. It sets up the repository, the reviews, and the AI-native SDLC. In step 1, also replace the `Next.js application` text in `apps/app/app/layout.tsx`, `apps/app/app/page.tsx`, and `apps/app/app/page.test.ts`, and the `Payload application` text in `apps/backoffice/src/app/(frontend)/layout.tsx`, `apps/backoffice/src/app/(frontend)/page.tsx`, and `apps/backoffice/src/app/(frontend)/page.test.ts`.
28
28
 
29
29
  ## Scripts
30
30
 
@@ -42,13 +42,13 @@ Follow [SETUP.md](SETUP.md) in order. It sets up the repository, the reviews, an
42
42
  | `pnpm check` | Run the static checks, the unit tests for changed code, and the production build (the `pre-push` hook) |
43
43
  | `pnpm verify` | Run the static checks, all unit tests with coverage, and the production build |
44
44
 
45
- CI also runs the build, the end-to-end tests, the container image scans, and the security scans, including CodeQL, dependency review, and OpenSSF Scorecard. CodeQL and Scorecard run on public repositories, and on private ones when the `CODE_SCANNING` repository variable is `true`. Pull requests run Turborepo with `--affected` against the base branch, so only packages that changed are typed, tested, built, and scanned. Pushes to `main` and the weekly schedule still run every app. Patch coverage runs only for apps that produced a coverage file.
45
+ CI also runs the build, the end-to-end tests, the container image scans, and the security scans, including CodeQL, dependency review, and OpenSSF Scorecard. CodeQL, dependency review, and Scorecard run on public repositories, and on private ones when the `CODE_SCANNING` repository variable is `true`. Pull requests run Turborepo with `--affected` against the base branch, so only packages that changed are typed, tested, built, and scanned. Pushes to `main` run every app. The weekly schedule rebuilds and scans every container image and runs nothing else. Patch coverage runs only for apps that produced a coverage file.
46
46
 
47
47
  To reuse task output across CI runs, create a Vercel team, add the repository variable `TURBO_TEAM`, and follow [Turborepo on GitHub Actions](https://turborepo.dev/docs/guides/ci-vendors/github-actions) to allow OIDC. The workflow exchanges GitHub OIDC for a short-lived Remote Cache token. If OIDC is unavailable, set the `TURBO_TOKEN` secret instead. Leave both unset to run without a Remote Cache.
48
48
 
49
49
  ## Container
50
50
 
51
- Each app has a Dockerfile. Build both from the workspace root. The images run as the `node` user. The app listens on `PORT`, or 3000 when it is unset. The backoffice listens on `PORT`, or 3000 when it is unset, and needs `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL`.
51
+ Each app has a Dockerfile. Build both from the workspace root. The images run as the `node` user and listen on `PORT`, or 3000 when it is unset. The backoffice needs `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL`.
52
52
 
53
53
  ```sh
54
54
  docker build --file apps/app/Dockerfile --tag app .
@@ -81,14 +81,14 @@ On Vercel, deploy an app without its image. The Next.js preset takes precedence
81
81
 
82
82
  Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
83
83
 
84
- 3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`. The workflow compares the commit with the last GitHub deployment to that environment and runs `railway up` only for services whose packages changed. A promotion through `uat` or `prod` uses the same comparison against the commit last deployed there, so several commits still ship every changed service. The first deploy to an environment ships both services.
84
+ 3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`. The workflow compares the commit with the commit of the last successful deploy to that environment, found from the successful `deploy.yml` and `ci.yml` runs on `main`, and runs `railway up` only for services whose packages changed. A promotion through `uat` or `prod` uses the same comparison against the commit last deployed there, so several commits still ship every changed service. The first deploy to an environment ships both services.
85
85
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health` on each service it uploaded.
86
86
 
87
87
  Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
88
88
 
89
89
  ## Mobile app
90
90
 
91
- The template does not ship an Expo app. Add one under `apps/mobile` when you need it:
91
+ To add an Expo app, create it under `apps/mobile`:
92
92
 
93
93
  1. Create the app from the workspace root with `pnpm create expo-app@latest apps/mobile`, then add `packages/*` workspace packages as you share code. Keep `eas.json` and `credentials.json` inside `apps/mobile` and run the EAS CLI from that directory ([Expo: Build with monorepos](https://docs.expo.dev/build-reference/build-with-monorepos/)).
94
94
  2. Use `@expo/fingerprint` with `expo-github-action/fingerprint` so a pull request requests a native build only when the native layer changed ([Expo fingerprint](https://docs.expo.dev/versions/latest/sdk/fingerprint/)).