@next-friday/create-app 9.1.1 → 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 +7 -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 +2 -2
  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 +2 -2
  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 +2 -2
  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
package/README.md CHANGED
@@ -29,7 +29,7 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
29
29
  ## What every project includes
30
30
 
31
31
  - **Code quality:** TypeScript, ESLint and Prettier with the Next Friday configs, Vitest with coverage, fast-check, Knip, dependency-cruiser, editorconfig-checker, and sort-package-json.
32
- - **Git hooks:** Husky runs gitleaks and lint-staged before each commit, commitlint on each message, and `pnpm check` before each push. Commits and pushes to `main` are blocked after the first one.
32
+ - **Git hooks:** Husky runs gitleaks, `pnpm rules:check`, and lint-staged before each commit, commitlint on each message, and `pnpm check` before each push. After the first commit, commits to `main` are blocked, and local merges into `main` always are; pushes to `main` are blocked once it exists on the remote.
33
33
  - **CI:** GitHub Actions run the static checks, tests with patch coverage, the build, end-to-end tests for Next.js and Payload, a container build and vulnerability scan, Semgrep, OSV-Scanner, CodeQL, dependency review, OpenSSF Scorecard, gitleaks, TruffleHog, zizmor, and pull request title validation. Dependabot keeps dependencies current.
34
34
  - **Deployment:** a `Dockerfile` that runs as the `node` user, and a Railway workflow that promotes one commit through `dev`, `uat`, and `prod`.
35
35
  - **Agent rules:** `CLAUDE.md` imports `AGENTS.md`. Shared rules live in `.agents/rules`, with hooks that enforce them in Claude Code and Cursor, and a Cursor Bugbot configuration.
@@ -37,9 +37,9 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
37
37
  ## Requirements
38
38
 
39
39
  - Node.js 24.22.1, the version in the generated `.nvmrc`. The CLI itself runs on the range in [`package.json#engines`](package.json).
40
- - pnpm: `npm install --global pnpm`. The generated project pins its pnpm version in `package.json#packageManager`.
40
+ - pnpm, through Corepack: `corepack enable`. The generated project pins its pnpm version in `package.json#packageManager`.
41
41
  - Git 2.28 or later.
42
- - gitleaks 8.19 or later and jq, which the generated project's Git hooks need for the first commit.
42
+ - gitleaks 8.21 or later and jq, which the generated project's Git hooks need for the first commit.
43
43
 
44
44
  ## Options
45
45
 
@@ -60,6 +60,7 @@ The name must:
60
60
 
61
61
  - use lowercase letters, digits, `.`, `_`, or `-`, and start with a letter or digit;
62
62
  - not end in `.`, and not be `node_modules`, `favicon.ico`, or a name that Windows reserves, such as `con`;
63
+ - be at most 214 characters long;
63
64
  - point to a directory that does not exist or is empty, apart from `.git`.
64
65
 
65
66
  ## What the CLI does
@@ -84,7 +85,7 @@ Then follow **After creating a project from this template** in the project's `RE
84
85
  - **`pnpm install` fails:** the CLI keeps the project and exits with 1. Fix the printed error, such as a Node.js version outside the project's `package.json#engines`, then run `pnpm install` in the project.
85
86
  - **`--skip-git`:** run `git init --initial-branch=main` before `pnpm install`, so the install can set up the Git hooks.
86
87
  - **`--skip-install`:** the Git hooks are not set up until you run `pnpm install`.
87
- - **Cancelling:** Ctrl+C exits with 130. During the copy, it also removes the partly copied project.
88
+ - **Cancelling:** Ctrl+C, SIGTERM, and SIGHUP exit with 130. During the copy, they also remove the partly copied project.
88
89
  - **Payload:** set `DATABASE_URL` in `.env` to a PostgreSQL database before you start the app.
89
90
 
90
91
  ## Contributing
package/dist/main.mjs CHANGED
@@ -1,68 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
3
3
  import { chmod, cp, readFile, readdir, rename, writeFile } from "node:fs/promises";
4
- import { existsSync, lstatSync, readFileSync, readdirSync, rmSync } from "node:fs";
4
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import path from "node:path";
7
7
  import { randomBytes } from "node:crypto";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { parseArgs } from "node:util";
10
- // src/stacks.ts
11
- const stacks = [
12
- {
13
- id: "nodejs",
14
- label: "Node.js",
15
- hint: "HTTP service without a framework",
16
- requiresDatabase: false
17
- },
18
- {
19
- id: "nestjs",
20
- label: "NestJS",
21
- hint: "HTTP API",
22
- requiresDatabase: false
23
- },
24
- {
25
- id: "nextjs",
26
- label: "Next.js",
27
- hint: "Web app",
28
- requiresDatabase: false
29
- },
30
- {
31
- id: "payload",
32
- label: "Payload",
33
- hint: "Headless CMS on Next.js and PostgreSQL",
34
- requiresDatabase: true
35
- },
36
- {
37
- id: "turborepo",
38
- label: "Turborepo",
39
- hint: "Next.js app and Payload backoffice",
40
- requiresDatabase: true
41
- }
42
- ];
43
- const developmentScripts = {
44
- nodejs: "start:dev",
45
- nestjs: "start:dev",
46
- nextjs: "dev",
47
- payload: "dev",
48
- turborepo: "dev"
49
- };
50
- /**
51
- * Check whether a string names a supported stack.
52
- * @param value - Stack name from the command line.
53
- * @returns Whether `value` is a supported stack id.
54
- */
55
- function isStackId(value) {
56
- return stacks.some((stack) => stack.id === value);
57
- }
58
- /**
59
- * Name the package a stack's template declares, which the CLI replaces with the project name.
60
- * @param stack - Supported stack id.
61
- * @returns Package name in the template's `package.json`.
62
- */
63
- function templateName(stack) {
64
- return `next-friday-template-${stack}`;
65
- }
66
10
  // src/setup.ts
67
11
  /**
68
12
  * Write `.env` from `.env.example`, replacing the `PAYLOAD_SECRET` placeholder with a random secret.
@@ -90,7 +34,11 @@ function initGit(directory) {
90
34
  return spawnSync("git", ["init", "--initial-branch=main"], {
91
35
  cwd: directory,
92
36
  env: environment,
93
- stdio: "ignore"
37
+ stdio: [
38
+ "ignore",
39
+ "ignore",
40
+ "inherit"
41
+ ]
94
42
  }).status === 0 ? "initialized" : "failed";
95
43
  }
96
44
  /**
@@ -118,6 +66,18 @@ function projectGitEnvironment() {
118
66
  localVariables.delete("GIT_CONFIG_COUNT");
119
67
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !localVariables.has(name)));
120
68
  }
69
+ // src/project-files.ts
70
+ /**
71
+ * List the files of a project directory, leaving out a Git repository the directory already held.
72
+ * @param directory - Project directory.
73
+ * @returns Absolute paths of the files.
74
+ */
75
+ async function listProjectFiles(directory) {
76
+ return (await readdir(directory, {
77
+ recursive: true,
78
+ withFileTypes: true
79
+ })).filter((entry) => entry.isFile()).map((entry) => path.join(entry.parentPath, entry.name)).filter((file) => path.relative(directory, file).split(path.sep)[0] !== ".git");
80
+ }
121
81
  // src/project-name.ts
122
82
  const packageNamePattern = /^[a-z0-9][a-z0-9._-]*$/;
123
83
  const maxPackageNameLength = 214;
@@ -146,7 +106,7 @@ function validateProjectName(name, cwd) {
146
106
  const target = path.resolve(cwd, name);
147
107
  const stats = lstatSync(target, { throwIfNoEntry: false });
148
108
  if (stats === void 0) return;
149
- if (!stats.isDirectory()) return `${target} already exists and is not a directory.`;
109
+ if (!(stats.isSymbolicLink() ? statSync(target, { throwIfNoEntry: false }) : stats)?.isDirectory()) return `${target} already exists and is not a directory.`;
150
110
  return ownEntries(target).length > 0 ? `${target} already exists and is not empty.` : void 0;
151
111
  }
152
112
  /**
@@ -157,8 +117,63 @@ function validateProjectName(name, cwd) {
157
117
  function ownEntries(directory) {
158
118
  return readdirSync(directory).filter((entry) => entry !== ".git");
159
119
  }
120
+ // src/stacks.ts
121
+ const stacks = [
122
+ {
123
+ id: "nodejs",
124
+ label: "Node.js",
125
+ hint: "HTTP service without a framework",
126
+ developmentScript: "start:dev",
127
+ requiresDatabase: false
128
+ },
129
+ {
130
+ id: "nestjs",
131
+ label: "NestJS",
132
+ hint: "HTTP API",
133
+ developmentScript: "start:dev",
134
+ requiresDatabase: false
135
+ },
136
+ {
137
+ id: "nextjs",
138
+ label: "Next.js",
139
+ hint: "Web app",
140
+ developmentScript: "dev",
141
+ requiresDatabase: false
142
+ },
143
+ {
144
+ id: "payload",
145
+ label: "Payload",
146
+ hint: "Headless CMS on Next.js and PostgreSQL",
147
+ developmentScript: "dev",
148
+ requiresDatabase: true
149
+ },
150
+ {
151
+ id: "turborepo",
152
+ label: "Turborepo",
153
+ hint: "Next.js app and Payload backoffice",
154
+ developmentScript: "dev",
155
+ requiresDatabase: true
156
+ }
157
+ ];
158
+ const stackById = Object.fromEntries(stacks.map((stack) => [stack.id, stack]));
159
+ /**
160
+ * Check whether a string names a supported stack.
161
+ * @param value - Stack name from the command line.
162
+ * @returns Whether `value` is a supported stack id.
163
+ */
164
+ function isStackId(value) {
165
+ return stacks.some((stack) => stack.id === value);
166
+ }
167
+ /**
168
+ * Name the package a stack's template declares, which the CLI replaces with the project name.
169
+ * @param stack - Supported stack id.
170
+ * @returns Package name in the template's `package.json`.
171
+ */
172
+ function templateName(stack) {
173
+ return `next-friday-template-${stack}`;
174
+ }
160
175
  // src/arguments.ts
161
- const usage = `Usage: pnpm create @next-friday/app [name] [--stack nodejs|nestjs|nextjs|payload|turborepo]
176
+ const usage = `Usage: pnpm create @next-friday/app [name] [--stack ${stacks.map((stack) => stack.id).join("|")}]
162
177
 
163
178
  name Project directory and package name, or . for the current directory. Asked when left out.
164
179
  --stack Template stack. Asked when left out.
@@ -197,18 +212,19 @@ function parseCliArguments(cliArguments) {
197
212
  }
198
213
  }
199
214
  });
200
- if (positionals.length > 1) throw new Error(`Pass one project name, not ${positionals.length}: ${positionals.join(" ")}.`);
201
- const [name] = positionals;
202
215
  const { help, stack, version } = values;
203
- if (stack !== void 0 && !isStackId(stack)) {
216
+ const isInformational = help || version;
217
+ if (!isInformational && positionals.length > 1) throw new Error(`Pass one project name, not ${positionals.length}: ${positionals.join(" ")}.`);
218
+ const [name] = positionals;
219
+ if (!isInformational && stack !== void 0 && !isStackId(stack)) {
204
220
  const ids = stacks.map((supported) => supported.id).join(", ");
205
221
  throw new Error(`Unknown stack "${stack}". Use one of: ${ids}.`);
206
222
  }
207
223
  return {
208
224
  help,
209
225
  name,
210
- stack,
211
226
  version,
227
+ stack: stack !== void 0 && isStackId(stack) ? stack : void 0,
212
228
  skipGit: values["skip-git"],
213
229
  skipInstall: values["skip-install"]
214
230
  };
@@ -219,23 +235,23 @@ function parseCliArguments(cliArguments) {
219
235
  * @param directory - Project directory.
220
236
  * @param from - Template name to replace.
221
237
  * @param to - Project name.
222
- * @returns Paths of the files that changed.
223
238
  */
224
239
  async function renameProject(directory, from, to) {
225
- const files = (await readdir(directory, {
226
- recursive: true,
227
- withFileTypes: true
228
- })).filter((entry) => entry.isFile()).map((entry) => path.join(entry.parentPath, entry.name));
229
- return (await Promise.all(files.map(async (file) => {
240
+ const files = await listProjectFiles(directory);
241
+ await Promise.all(files.map(async (file) => {
230
242
  const content = await readFile(file);
231
243
  const text = content.toString("utf8");
232
- if (content.includes(0) || !content.includes(from) || !Buffer.from(text, "utf8").equals(content)) return [];
244
+ if (content.includes(0) || !content.includes(from) || !Buffer.from(text, "utf8").equals(content)) return;
233
245
  await writeFile(file, text.replaceAll(from, () => to));
234
- return [file];
235
- }))).flat();
246
+ }));
236
247
  }
237
248
  // src/create.ts
238
249
  const templatesDirectory = fileURLToPath(new URL("../templates", import.meta.url));
250
+ const interruptSignals = [
251
+ "SIGINT",
252
+ "SIGTERM",
253
+ "SIGHUP"
254
+ ];
239
255
  var CancelledError = class extends Error {
240
256
  message = "Cancelled.";
241
257
  };
@@ -244,8 +260,8 @@ var CancelledError = class extends Error {
244
260
  * @param cliArguments - Arguments after the executable and script path.
245
261
  */
246
262
  async function create(cliArguments) {
247
- const cwd = process.cwd();
248
263
  try {
264
+ const cwd = process.cwd();
249
265
  const options = parseCliArguments(cliArguments);
250
266
  if (options.help) {
251
267
  process.stdout.write(`${usage}\n`);
@@ -255,23 +271,28 @@ async function create(cliArguments) {
255
271
  process.stdout.write(`${packageVersion()}\n`);
256
272
  return;
257
273
  }
258
- intro("Create a Next Friday project");
259
274
  const { name, stack } = await resolveProject(options, cwd);
260
275
  const manifestName = packageName(name, cwd);
261
- const directory = path.resolve(cwd, name);
276
+ const target = path.resolve(cwd, name);
277
+ const directory = existsSync(target) ? realpathSync(target) : target;
262
278
  log.step(`Copying the ${stack} template.`);
263
279
  await writeProject(stack, directory, manifestName);
264
280
  log.success(`Created ${manifestName} from the ${stack} template.`);
265
281
  const { git, isInstalled } = setUpProject(directory, options);
266
282
  outro(nextSteps(name, stack, git, isInstalled));
267
283
  } catch (error) {
268
- cancel(error instanceof Error ? error.message : String(error));
269
- process.exitCode = error instanceof CancelledError ? 130 : 1;
284
+ if (error instanceof CancelledError) {
285
+ cancel(error.message);
286
+ process.exitCode = 130;
287
+ } else {
288
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
289
+ process.exitCode = 1;
290
+ }
270
291
  }
271
292
  }
272
293
  /**
273
- * Copy, rename, and write `.env`, removing what was written when a step fails or Ctrl+C arrives.
274
- * Ctrl+C waits for the running step, so the removal does not race the copy.
294
+ * Copy, rename, and write `.env`, removing what was written when a step fails or Ctrl+C, SIGTERM, or SIGHUP arrives.
295
+ * An interrupt waits for the running step, so the removal does not race the copy.
275
296
  * @param stack - Supported stack id.
276
297
  * @param directory - Project directory.
277
298
  * @param manifestName - Package name for `package.json`.
@@ -282,7 +303,7 @@ async function writeProject(stack, directory, manifestName) {
282
303
  const interrupt = () => {
283
304
  isInterrupted = true;
284
305
  };
285
- process.on("SIGINT", interrupt);
306
+ for (const signal of interruptSignals) process.on(signal, interrupt);
286
307
  try {
287
308
  await copyTemplate(stack, directory);
288
309
  await renameProject(directory, templateName(stack), manifestName);
@@ -292,7 +313,7 @@ async function writeProject(stack, directory, manifestName) {
292
313
  removeProject(directory, kept);
293
314
  throw isInterrupted ? new CancelledError() : error;
294
315
  } finally {
295
- process.off("SIGINT", interrupt);
316
+ for (const signal of interruptSignals) process.off(signal, interrupt);
296
317
  }
297
318
  }
298
319
  /**
@@ -303,16 +324,28 @@ async function writeProject(stack, directory, manifestName) {
303
324
  */
304
325
  async function resolveProject(options, cwd) {
305
326
  if (!process.stdin.isTTY && (options.name === void 0 || options.stack === void 0)) throw new Error(`Pass the project name and --stack when not running in a terminal.\n${usage}`);
327
+ if (options.name !== void 0) assertUsableName(options.name, cwd);
328
+ intro("Create a Next Friday project");
306
329
  const name = options.name ?? await askName(cwd);
307
- const nameError = validateProjectName(name, cwd);
308
- if (nameError !== void 0) throw new Error(nameError);
330
+ const stack = options.stack ?? await askStack();
331
+ assertUsableName(name, cwd);
309
332
  return {
310
333
  name,
311
- stack: options.stack ?? await askStack()
334
+ stack
312
335
  };
313
336
  }
337
+ /**
338
+ * Throw when the project name is unusable or its directory is taken, as a prompt may have left a
339
+ * directory in place since the name was checked.
340
+ * @param name - Project name, or `.` for the current directory.
341
+ * @param cwd - Directory the project is created in.
342
+ */
343
+ function assertUsableName(name, cwd) {
344
+ const nameError = validateProjectName(name, cwd);
345
+ if (nameError !== void 0) throw new Error(nameError);
346
+ }
314
347
  const gitWarnings = {
315
- failed: "git init failed. Install Git 2.28 or later, then run the steps below.",
348
+ failed: "git init failed. Check that Git 2.28 or later is installed and the directory is writable, then run the steps below.",
316
349
  parent: "Skipped git init inside an existing Git repository. The Git hooks install only at a repository root."
317
350
  };
318
351
  /**
@@ -322,7 +355,7 @@ const gitWarnings = {
322
355
  * @returns The `git init` result, or `undefined` when skipped, and whether the install succeeded.
323
356
  */
324
357
  function setUpProject(directory, options) {
325
- const git = options.skipGit ? void 0 : initGit(directory);
358
+ const git = options.skipGit ? existingGit(directory) : initGit(directory);
326
359
  const warning = git === void 0 ? void 0 : gitWarnings[git];
327
360
  if (warning !== void 0) log.warn(warning);
328
361
  if (options.skipInstall) return {
@@ -332,7 +365,7 @@ function setUpProject(directory, options) {
332
365
  log.step("Installing dependencies with pnpm.");
333
366
  const isInstalled = didInstallDependencies(directory);
334
367
  if (!isInstalled) {
335
- log.error("pnpm install failed. Fix the error above, then run the steps below. The project needs pnpm and the Node.js version in .nvmrc.");
368
+ log.error("pnpm install failed. Fix the error above, then run the steps below. The project needs pnpm and the Node.js version in package.json#engines.");
336
369
  process.exitCode = 1;
337
370
  }
338
371
  return {
@@ -341,12 +374,21 @@ function setUpProject(directory, options) {
341
374
  };
342
375
  }
343
376
  /**
377
+ * Report a repository that skipping `git init` leaves in place.
378
+ * @param directory - Project directory.
379
+ * @returns `existing` when the directory holds `.git`, otherwise `undefined`.
380
+ */
381
+ function existingGit(directory) {
382
+ return existsSync(path.join(directory, ".git")) ? "existing" : void 0;
383
+ }
384
+ /**
344
385
  * Copy the shared template files, then the stack's files, into the project directory.
345
386
  * npm leaves `.gitignore` out of published packages, so the template keeps it as `_gitignore`.
346
387
  * @param stack - Supported stack id.
347
388
  * @param directory - Project directory.
348
389
  */
349
390
  async function copyTemplate(stack, directory) {
391
+ for (const template of ["base", stack]) if (!existsSync(path.join(templatesDirectory, template))) throw new Error(`The ${template} template is missing from ${templatesDirectory}. Reinstall @next-friday/create-app.`);
350
392
  await cp(path.join(templatesDirectory, "base"), directory, { recursive: true });
351
393
  await cp(path.join(templatesDirectory, stack), directory, { recursive: true });
352
394
  await rename(path.join(directory, "_gitignore"), path.join(directory, ".gitignore"));
@@ -358,12 +400,8 @@ async function copyTemplate(stack, directory) {
358
400
  * @param directory - Project directory.
359
401
  */
360
402
  async function markScriptsExecutable(directory) {
361
- const entries = await readdir(directory, {
362
- recursive: true,
363
- withFileTypes: true
364
- });
365
- await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
366
- const file = path.join(entry.parentPath, entry.name);
403
+ const files = await listProjectFiles(directory);
404
+ await Promise.all(files.map(async (file) => {
367
405
  if ((await readFile(file)).subarray(0, 2).toString() === "#!") await chmod(file, 493);
368
406
  }));
369
407
  }
@@ -388,14 +426,15 @@ function removeProject(directory, kept) {
388
426
  * @returns The next steps message.
389
427
  */
390
428
  function nextSteps(name, stack, git, isInstalled) {
429
+ const { developmentScript, requiresDatabase } = stackById[stack];
391
430
  const hasGit = git !== void 0 && git !== "failed";
392
431
  const commands = [
393
432
  name === "." ? [] : [`cd ${name}`],
394
433
  hasGit ? [] : ["git init --initial-branch=main"],
395
434
  hasGit && isInstalled ? [] : ["pnpm install"],
396
- [`pnpm ${developmentScripts[stack]}`]
435
+ [`pnpm ${developmentScript}`]
397
436
  ].flat();
398
- const database = stacks.find((item) => item.id === stack)?.requiresDatabase ? `Set DATABASE_URL in .env to your PostgreSQL database before pnpm ${developmentScripts[stack]}.\n` : "";
437
+ const database = requiresDatabase ? `Set DATABASE_URL in .env to your PostgreSQL database before pnpm ${developmentScript}.\n` : "";
399
438
  const readme = name === "." ? "README.md" : `${name}/README.md`;
400
439
  return `Next steps:\n${commands.map((command) => ` ${command}`).join("\n")}\n${database}Before the first commit, follow "After creating a project from this template" in ${readme}.`;
401
440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next-friday/create-app",
3
- "version": "9.1.1",
3
+ "version": "9.2.0",
4
4
  "description": "Create a Next Friday project from a template.",
5
5
  "keywords": [
6
6
  "create",
@@ -54,7 +54,8 @@
54
54
  "sort-package-json": "4.1.0",
55
55
  "tsdown": "0.23.4",
56
56
  "typescript": "6.0.3",
57
- "vitest": "5.0.5"
57
+ "vitest": "5.0.5",
58
+ "yaml": "2.9.1"
58
59
  },
59
60
  "engines": {
60
61
  "node": ">=24.15.0"
@@ -65,6 +66,7 @@
65
66
  },
66
67
  "scripts": {
67
68
  "audit": "pnpm audit",
69
+ "bands:check": "node scripts/check-bands.ts",
68
70
  "build": "tsdown",
69
71
  "changeset": "changeset",
70
72
  "changeset:status": "changeset status --since main",
@@ -72,24 +74,23 @@
72
74
  "check:publint": "publint",
73
75
  "clean": "rm -rf dist coverage *.tsbuildinfo .eslintcache node_modules/.cache",
74
76
  "dev": "tsdown --watch",
77
+ "evals:run": "node scripts/run-agent-evals.ts",
75
78
  "format": "prettier --write --cache .",
76
79
  "format:check": "prettier --check --cache . && editorconfig-checker",
77
80
  "gates:check": "node scripts/check-agent-gates.ts",
78
- "human-gates:check": "node scripts/check-human-gates.ts",
79
- "intent:check": "node scripts/check-intent.ts",
80
81
  "knip": "knip",
81
82
  "lint": "eslint . --fix --cache --max-warnings=0 --no-inline-config",
82
83
  "lint:check": "eslint . --cache --max-warnings=0 --no-inline-config",
83
84
  "loop:measure": "node scripts/measure-loop.ts",
85
+ "plan:check": "node scripts/check-plan.ts",
84
86
  "rules:check": "node scripts/sync-agent-rules.ts --check",
85
87
  "rules:sync": "node scripts/sync-agent-rules.ts",
86
88
  "sort-package-json": "sort-package-json package.json",
87
89
  "sort-package-json:check": "sort-package-json --check package.json",
88
- "suppressions:check": "bash .agents/hooks/agent-suppression-gate.sh --repo",
89
90
  "test": "vitest run",
90
91
  "test:coverage": "vitest run --coverage",
91
92
  "test:watch": "vitest",
92
93
  "typecheck": "tsc --noEmit -p tsconfig.json",
93
- "verify": "pnpm suppressions:check && pnpm rules:check && pnpm gates:check && pnpm human-gates:check && pnpm intent:check && pnpm changesets:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm typecheck && pnpm test:coverage && pnpm knip && pnpm build && pnpm check:publint"
94
+ "verify": "pnpm rules:check && pnpm gates:check && pnpm plan:check && pnpm bands:check && pnpm changesets:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm typecheck && pnpm test:coverage && pnpm knip && pnpm build && pnpm check:publint"
94
95
  }
95
96
  }
@@ -16,7 +16,12 @@ trap 'block ".agents/hooks/agent-mcp-gate.sh failed. Stop and report this error
16
16
  command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
17
17
  input=$(cat)
18
18
  tool=$(jq -r '.tool_name // empty' <<< "$input")
19
- if [[ $tool =~ (merge_pull_request|enable_pr_auto_merge|pull_request_review_write)$ ]]; then
20
- block "A code owner approves a pull request and a person merges it. Do not approve or merge; report that the pull request waits on that review."
19
+ human_gate="A code owner approves a pull request and a person merges it. Do not approve or merge; report that the pull request waits on that review."
20
+ if [[ $tool =~ (merge_pull_request|enable_pr_auto_merge)$ ]]; then
21
+ block "$human_gate"
22
+ fi
23
+ if [[ $tool =~ pull_request_review_write$ ]]; then
24
+ event=$(jq -r '(.tool_input | if type == "string" then (fromjson? // {}) else . end | objects | .event) // empty' <<< "$input" | tr '[:upper:]' '[:lower:]')
25
+ [[ $event == approve ]] && block "$human_gate"
21
26
  fi
22
27
  allow
@@ -15,7 +15,12 @@ block() {
15
15
  trap 'block ".agents/hooks/agent-production-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
16
16
  command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
17
17
  command=$(jq -r '.command // .tool_input.command // empty')
18
- [[ -n $command && -z ${RELEASE_APPROVAL:-} ]] || allow
18
+ [[ -n $command ]] || allow
19
+ approval=$(tr '[:upper:]' '[:lower:]' <<< "${RELEASE_APPROVAL:-}")
20
+ case $approval in
21
+ "" | 0 | false | no) ;;
22
+ *) allow ;;
23
+ esac
19
24
 
20
25
  release_gate="A release manager runs prod deploys, commands against prod, and release tag pushes. Do not run this; report what is ready to promote, such as the commit SHA. If a gated command appears only inside a message, write the text to a file and pass it with git commit -F <file> or gh pr create --body-file <file>."
21
26