@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.
- package/README.md +5 -4
- package/dist/main.mjs +135 -96
- package/package.json +7 -6
- package/templates/base/.agents/hooks/agent-mcp-gate.sh +7 -2
- package/templates/base/.agents/hooks/agent-production-gate.sh +6 -1
- package/templates/base/.agents/hooks/agent-shell-gate.sh +1 -181
- package/templates/base/.agents/hooks/agent-test-edit-gate.sh +12 -3
- package/templates/base/.agents/review.md +2 -2
- package/templates/base/.agents/rules/no-suppression.md +1 -1
- package/templates/base/.agents/skill-evals/README.md +42 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/01-the-reply-explains-why-pnpm.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/02-no-file-in-the-project.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/03-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/04-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/06-everything-the-reply-states-as.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/01-work-happens-on-the-plan-branch.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/02-the-plan-is-implemented-and-checked.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/03-the-branch-is-pushed.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/04-the-reply-lists-three-playbooks.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/05-no-approval-and-no-merge.md +7 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/07-the-plan-names-the-engineer.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/case.yaml +71 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/01-the-reply-asks-numbered-questions.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/02-no-plan-before-the-answers.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/03-no-question-depends-on-another.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/04-no-file-or-commit.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/05-the-reply-is-in-thai.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/case.yaml +68 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/fixture.md +1 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/02-no-source-file-or-commit.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/03-the-reply-names-the-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/04-the-reply-is-in-thai.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/prompt.md +10 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/case.yaml +8 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/01-no-deployment-is-started.md +7 -0
- 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
- 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
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/04-the-reply-is-in-the-language.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/case.yaml +13 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/01-a-file-intentkebabcasenameintentmd-exists-with.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/02-the-reply-shows-the-draft.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/03-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/04-reply-lists-the-playbook-steps.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/05-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/06-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/07-the-drafts-open-questions-section.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/prompt.md +10 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/case.yaml +34 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/fixture.md +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/01-the-first-message-restates-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/02-the-reply-asks-numbered-questions.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/03-no-intentmd-is-written-before.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/04-reply-lists-the-playbook-steps.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/05-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/06-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/07-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/08-no-question-in-the-round.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/case.yaml +14 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/01-the-intent-has-the-five-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/02-problem-holds-the-error-and-where.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/03-unverified-cause-is-an-open-question.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/04-no-source-file-is-changed.md +7 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/06-the-person-is-asked-no-question.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/01-the-reply-follows-the-plan.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/02-the-reply-does-not-tell.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/03-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/04-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/fixture.md +1 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/02-the-reply-names-the-closest-row.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/03-the-reply-is-short-and-in-thai.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/case.yaml +79 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/01-findings-have-pass-severity-and-location.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/02-no-approval-and-no-merge.md +7 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/04-nothing-is-pushed-or-changed.md +7 -0
- 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
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/case.yaml +42 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/01-no-spec-is-written.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/02-no-commit-is-made.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/03-the-reply-says-not-merged.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/04-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/05-the-reply-is-short.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/01-no-intentmd-or-any-other.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/02-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/03-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/04-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/05-the-reply-says-that-work.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/case.yaml +42 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/01-the-spec-has-the-five-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/02-policies-has-one-line-per-policy.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/03-no-policy-is-called-unwritten.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/04-a-branch-is-pushed-for-review.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/05-the-reply-names-the-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/06-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/07-no-merge-is-made.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/08-the-four-policies-are-read-before-writing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/01-the-reply-says-that-work.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/02-the-reply-gives-one-example.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/03-the-reply-lists-the-project.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/04-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/05-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/06-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/case.yaml +10 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/01-the-reply-says-that-this.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/02-the-reply-does-not-describe.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/03-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/04-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/01-the-reply-says-to-send.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/02-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/03-the-reply-is-in-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/04-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/prompt.md +8 -0
- package/templates/base/.agents/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.agents/skills/sdlc/playbooks/build.md +13 -8
- package/templates/base/.agents/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.agents/skills/sdlc/playbooks/design.md +2 -2
- package/templates/base/.agents/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.agents/skills/sdlc/playbooks/maintain.md +2 -3
- package/templates/base/.agents/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/plan.md +2 -2
- package/templates/base/.agents/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.agents/skills/sdlc-help/SKILL.md +3 -1
- package/templates/base/.claude/managed-settings.example.json +2 -2
- package/templates/base/.claude/settings.json +0 -13
- package/templates/base/.claude/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.claude/skills/sdlc/playbooks/build.md +13 -8
- package/templates/base/.claude/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.claude/skills/sdlc/playbooks/design.md +2 -2
- package/templates/base/.claude/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.claude/skills/sdlc/playbooks/maintain.md +2 -3
- package/templates/base/.claude/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/plan.md +2 -2
- package/templates/base/.claude/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.claude/skills/sdlc-help/SKILL.md +3 -1
- package/templates/base/.cursor/hooks.json +0 -6
- package/templates/base/.cursor/rules/no-suppression.mdc +1 -1
- package/templates/base/.cursor/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.cursor/skills/sdlc/playbooks/build.md +13 -8
- package/templates/base/.cursor/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.cursor/skills/sdlc/playbooks/design.md +2 -2
- package/templates/base/.cursor/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/maintain.md +2 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +2 -2
- package/templates/base/.cursor/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.cursor/skills/sdlc-help/SKILL.md +3 -1
- package/templates/base/.github/ISSUE_TEMPLATE/documentation.yml +1 -1
- package/templates/base/.github/dependabot.yml +1 -10
- package/templates/base/.github/file-filters.yml +3 -6
- package/templates/base/.github/workflows/agent-evals.yml +46 -0
- package/templates/base/.github/workflows/claude-fix-loop.yml +45 -0
- package/templates/base/.github/workflows/control-band.yml +115 -0
- package/templates/base/.github/workflows/dependency-review.yml +1 -0
- package/templates/base/.github/workflows/deploy.yml +4 -2
- package/templates/base/.github/workflows/osv-scanner.yml +0 -5
- package/templates/base/.github/workflows/pr-triage.yml +1 -1
- package/templates/base/.github/workflows/secret-scan.yml +4 -1
- package/templates/base/.github/workflows/spec-on-intent-merge.yml +108 -0
- package/templates/base/.github/workflows/validate-pr-title.yml +3 -2
- package/templates/base/.husky/pre-push +1 -8
- package/templates/base/REVIEW.md +2 -2
- package/templates/base/SETUP.md +44 -8
- package/templates/base/bands.yaml +3 -3
- package/templates/base/intent/README.md +1 -0
- package/templates/base/knip.json +2 -1
- package/templates/base/pnpm-workspace.yaml +1 -22
- package/templates/base/runbooks/rollback-deploy.md +27 -0
- package/templates/base/scripts/check-agent-gates.ts +251 -225
- package/templates/base/scripts/check-bands.ts +665 -0
- package/templates/base/scripts/check-plan.ts +1047 -0
- package/templates/base/scripts/measure-loop.ts +293 -0
- package/templates/base/scripts/run-agent-evals.ts +1186 -0
- package/templates/base/scripts/sync-agent-rules.ts +0 -8
- package/templates/nestjs/.dependency-cruiser.json +1 -1
- package/templates/nestjs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
- package/templates/nestjs/.github/workflows/ci.yml +58 -9
- package/templates/nestjs/.github/workflows/sast.yml +0 -5
- package/templates/nestjs/AGENTS.md +16 -12
- package/templates/nestjs/CONTRIBUTING.md +3 -2
- package/templates/nestjs/Dockerfile +4 -4
- package/templates/nestjs/README.md +4 -6
- package/templates/nestjs/_gitignore +0 -1
- package/templates/nestjs/package.json +15 -13
- package/templates/nestjs/src/app.controller.test.ts +1 -0
- package/templates/nestjs/src/health/health.controller.test.ts +1 -0
- package/templates/nestjs/src/port.test.ts +1 -0
- package/templates/nestjs/test/app.e2e.test.ts +1 -0
- package/templates/nestjs/tsconfig.build.json +2 -3
- package/templates/nestjs/tsconfig.json +1 -3
- package/templates/nestjs/vitest.config.ts +0 -1
- package/templates/nestjs/vitest.e2e.config.ts +0 -1
- package/templates/nextjs/.dependency-cruiser.json +1 -1
- package/templates/nextjs/.github/ISSUE_TEMPLATE/bug.yml +1 -1
- package/templates/nextjs/.github/workflows/ci.yml +58 -9
- package/templates/nextjs/.github/workflows/sast.yml +0 -5
- package/templates/nextjs/AGENTS.md +16 -12
- package/templates/nextjs/CONTRIBUTING.md +3 -2
- package/templates/nextjs/Dockerfile +2 -2
- package/templates/nextjs/README.md +5 -3
- package/templates/nextjs/app/layout.tsx +2 -2
- package/templates/nextjs/app/page.test.ts +1 -1
- package/templates/nextjs/app/page.tsx +1 -1
- package/templates/nextjs/package.json +9 -8
- package/templates/nextjs/test/home.e2e.test.ts +3 -7
- package/templates/nextjs/tsconfig.json +0 -3
- package/templates/nodejs/.dependency-cruiser.json +1 -1
- package/templates/nodejs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
- package/templates/nodejs/.github/workflows/ci.yml +58 -9
- package/templates/nodejs/.github/workflows/sast.yml +0 -5
- package/templates/nodejs/AGENTS.md +16 -11
- package/templates/nodejs/CONTRIBUTING.md +4 -3
- package/templates/nodejs/Dockerfile +3 -3
- package/templates/nodejs/README.md +4 -4
- package/templates/nodejs/_gitignore +0 -1
- package/templates/nodejs/package.json +11 -9
- package/templates/nodejs/src/server.test.ts +23 -0
- package/templates/nodejs/src/server.ts +2 -2
- package/templates/nodejs/tsconfig.build.json +2 -3
- package/templates/nodejs/tsconfig.json +0 -4
- package/templates/payload/.agents/agents/verifier.md +1 -1
- package/templates/payload/.agents/review.md +21 -0
- package/templates/payload/.claude/agents/verifier.md +1 -1
- package/templates/payload/.cursor/agents/verifier.md +1 -1
- package/templates/payload/.dependency-cruiser.json +1 -1
- package/templates/payload/.github/ISSUE_TEMPLATE/bug.yml +1 -1
- package/templates/payload/.github/workflows/ci.yml +75 -10
- package/templates/payload/.github/workflows/sast.yml +0 -5
- package/templates/payload/AGENTS.md +21 -17
- package/templates/payload/CONTRIBUTING.md +4 -3
- package/templates/payload/Dockerfile +2 -2
- package/templates/payload/README.md +8 -4
- package/templates/payload/REVIEW.md +21 -0
- package/templates/payload/package.json +9 -7
- package/templates/payload/src/app/(frontend)/layout.tsx +2 -2
- package/templates/payload/src/app/(frontend)/page.test.ts +1 -1
- package/templates/payload/src/app/(frontend)/page.tsx +1 -1
- package/templates/payload/src/app/favicon.ico +0 -0
- package/templates/payload/src/app/health/route.test.ts +20 -2
- package/templates/payload/src/app/health/route.ts +9 -2
- package/templates/payload/src/collections/media.test.ts +36 -1
- package/templates/payload/src/collections/media.ts +1 -1
- package/templates/payload/src/payload.config.test.ts +15 -0
- package/templates/payload/test/home.e2e.test.ts +3 -7
- package/templates/payload/tsconfig.json +0 -1
- package/templates/turborepo/.agents/agents/verifier.md +1 -1
- package/templates/turborepo/.agents/review.md +21 -0
- package/templates/turborepo/.claude/agents/verifier.md +1 -1
- package/templates/turborepo/.cursor/agents/verifier.md +1 -1
- package/templates/turborepo/.dockerignore +9 -4
- package/templates/turborepo/.github/ISSUE_TEMPLATE/bug.yml +16 -1
- package/templates/turborepo/.github/dependabot.yml +1 -10
- package/templates/turborepo/.github/scripts/affected-apps.sh +5 -23
- package/templates/turborepo/.github/workflows/ci.yml +140 -103
- package/templates/turborepo/.github/workflows/deploy.yml +24 -23
- package/templates/turborepo/.github/workflows/sast.yml +0 -5
- package/templates/turborepo/AGENTS.md +21 -17
- package/templates/turborepo/CONTRIBUTING.md +6 -4
- package/templates/turborepo/README.md +8 -8
- package/templates/turborepo/REVIEW.md +21 -0
- package/templates/turborepo/apps/app/.dependency-cruiser.json +1 -1
- package/templates/turborepo/apps/app/AGENTS.md +9 -0
- package/templates/turborepo/apps/app/Dockerfile +2 -2
- package/templates/turborepo/apps/app/app/layout.tsx +2 -2
- package/templates/turborepo/apps/app/app/page.test.ts +1 -1
- package/templates/turborepo/apps/app/app/page.tsx +1 -1
- package/templates/turborepo/apps/app/package.json +3 -3
- package/templates/turborepo/apps/app/test/home.e2e.test.ts +3 -7
- package/templates/turborepo/apps/app/tsconfig.json +0 -3
- package/templates/turborepo/apps/backoffice/.dependency-cruiser.json +1 -1
- package/templates/turborepo/apps/backoffice/AGENTS.md +9 -0
- package/templates/turborepo/apps/backoffice/Dockerfile +2 -2
- package/templates/turborepo/apps/backoffice/package.json +4 -3
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/layout.tsx +2 -2
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.test.ts +1 -1
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.tsx +1 -1
- package/templates/turborepo/apps/backoffice/src/app/favicon.ico +0 -0
- package/templates/turborepo/apps/backoffice/src/app/health/route.test.ts +20 -2
- package/templates/turborepo/apps/backoffice/src/app/health/route.ts +10 -2
- package/templates/turborepo/apps/backoffice/src/collections/media.test.ts +36 -1
- package/templates/turborepo/apps/backoffice/src/collections/media.ts +1 -1
- package/templates/turborepo/apps/backoffice/src/environment.test.ts +19 -0
- package/templates/turborepo/apps/backoffice/src/payload.config.test.ts +15 -0
- package/templates/turborepo/apps/backoffice/test/home.e2e.test.ts +3 -7
- package/templates/turborepo/apps/backoffice/tsconfig.json +0 -1
- package/templates/turborepo/knip.json +4 -2
- package/templates/turborepo/package.json +15 -15
- package/templates/turborepo/pnpm-workspace.yaml +1 -22
- package/templates/turborepo/turbo.json +32 -11
- package/templates/base/.agents/hooks/agent-suppression-gate.sh +0 -45
- package/templates/base/.claude/rules/agents-guide.md +0 -1
- package/templates/base/scripts/check-human-gates.ts +0 -383
- package/templates/base/scripts/check-intent.ts +0 -391
- package/templates/nextjs/knip.json +0 -4
- package/templates/payload/src/collections/users.test.ts +0 -10
- package/templates/turborepo/apps/backoffice/src/collections/users.test.ts +0 -10
|
@@ -1,89 +1,73 @@
|
|
|
1
|
-
import {spawnSync} from "node:child_process";
|
|
1
|
+
import {execFileSync, spawnSync} from "node:child_process";
|
|
2
|
+
import {mkdtempSync, rmSync, writeFileSync} from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {tmpdir} from "node:os";
|
|
2
5
|
|
|
3
6
|
type GateCase = {
|
|
4
7
|
expectedExit: 0 | 2;
|
|
5
|
-
expectedPermission?: "allow"
|
|
8
|
+
expectedPermission?: "allow";
|
|
6
9
|
gate: string;
|
|
7
10
|
input: Record<string, unknown>;
|
|
8
11
|
name: string;
|
|
12
|
+
projectDirectory?: string;
|
|
9
13
|
releaseApproval?: string;
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
const shellGate = ".agents/hooks/agent-shell-gate.sh";
|
|
13
|
-
const suppressionGate = ".agents/hooks/agent-suppression-gate.sh";
|
|
14
17
|
const constitutionGate = ".cursor/hooks/agent-constitution-gate.sh";
|
|
15
18
|
const mcpGate = ".agents/hooks/agent-mcp-gate.sh";
|
|
16
19
|
const productionGate = ".agents/hooks/agent-production-gate.sh";
|
|
20
|
+
const testEditGate = ".agents/hooks/agent-test-edit-gate.sh";
|
|
17
21
|
const sourceFile = "src/example.ts";
|
|
18
22
|
|
|
19
23
|
const blockedCommands = [
|
|
20
|
-
"git commit -n -m x",
|
|
21
|
-
"git commit -nm x",
|
|
22
|
-
"git commit -a -n -m x",
|
|
23
|
-
"git commit -an",
|
|
24
|
-
"git commit -m x -n",
|
|
25
|
-
"git commit --amend -n",
|
|
26
|
-
"git -C . commit -n",
|
|
27
|
-
"git commit --no-verify -m x",
|
|
28
|
-
"git push --no-verify",
|
|
29
|
-
"HUSKY=0 git commit -m x",
|
|
30
|
-
"export HUSKY=0; git commit -m x",
|
|
31
|
-
"git config core.hooksPath /dev/null",
|
|
32
|
-
"gh repo delete next-friday/example",
|
|
33
|
-
"gh issue delete 1",
|
|
34
|
-
"gh release delete v1",
|
|
35
|
-
"git commit --no-verif -m x",
|
|
36
|
-
"git commit --no-v -m x",
|
|
37
|
-
'git commit "--no-verify" -m x',
|
|
38
|
-
"git commit '-n' -m x",
|
|
39
|
-
'env "HUSKY=0" git commit -m x',
|
|
40
|
-
"git config core.hookspath /dev/null",
|
|
41
|
-
"git -c core.HooksPath=/dev/null commit -m x",
|
|
42
|
-
"git -c alias.c='commit --no-verify' c",
|
|
43
|
-
"git config alias.c commit",
|
|
44
|
-
"git commit-tree HEAD^{tree} -m x",
|
|
45
|
-
"git update-ref refs/heads/main HEAD",
|
|
46
24
|
"printf 'exit 0' > .husky/pre-commit",
|
|
47
25
|
"rm -rf .husky/_",
|
|
48
26
|
"sed -i '' s/gitleaks/true/ .husky/pre-commit",
|
|
49
27
|
"tee .gitleaks.toml < /dev/null",
|
|
50
28
|
"cp /dev/null .agents/hooks/agent-shell-gate.sh",
|
|
51
|
-
"git -c core.hooks\\Path=/dev/null commit -m x",
|
|
52
|
-
"git commit --no-ver\\ify -m x",
|
|
53
|
-
"HU\\SKY=0 git commit -m x",
|
|
54
|
-
"git commit -m x --no-$(echo verify)",
|
|
55
|
-
"/usr/bin/git commit -n -m x",
|
|
56
|
-
"git --git-dir .git commit -n -m x",
|
|
57
|
-
"git commit -n; git status",
|
|
58
|
-
"git commit -m x -n&&git push",
|
|
59
29
|
"rm -rf .husky",
|
|
60
30
|
"mv .husky /tmp/h",
|
|
61
31
|
"sed -e s/a/b/ -i .husky/pre-commit",
|
|
62
32
|
"python3 -c \"open('.husky/pre-commit','w')\"",
|
|
63
33
|
"dd of=.husky/pre-commit",
|
|
64
|
-
"git config core.hooks$X x",
|
|
65
34
|
"cd .husky && python3 -c \"open('pre-commit','w')\"",
|
|
66
|
-
"GIT_CONFIG_KEY_0=core.hooksPath GIT_CONFIG_VALUE_0=/x git commit -m x",
|
|
67
|
-
'bash -c "git commit -n -m x"',
|
|
68
|
-
"eval git commit -n -m x",
|
|
69
35
|
"git checkout -- .husky/pre-commit",
|
|
70
36
|
"echo x >> .claude/settings.local.json",
|
|
71
|
-
"git merge --no-verify feature",
|
|
72
|
-
"$(git commit --no-verify -m x)",
|
|
73
|
-
"echo `git commit -n -m x`",
|
|
74
37
|
'echo "$(rm -rf .husky)"',
|
|
75
|
-
"x=$(echo $(git commit -n -m x))",
|
|
76
|
-
"echo \"$(echo ')'; git commit --no-verify -m x)\"",
|
|
77
|
-
"$(echo \\); git commit -n -m x)",
|
|
78
|
-
"$(echo x # )\ngit commit -n -m x)",
|
|
79
|
-
'echo "$(true&# )\ngit commit -n -m x)"',
|
|
80
|
-
'echo "$(true|# )\ngit commit -n -m x)"',
|
|
81
|
-
'echo "$( (true)# )\ngit commit -n -m x)"',
|
|
82
|
-
"{ git commit --no-verify -m x; }",
|
|
83
|
-
"if true; then git commit -n -m x; fi",
|
|
84
|
-
"while true; do git commit -n -m x; done",
|
|
85
|
-
"if git commit -n -m x; then true; fi",
|
|
86
38
|
"printf x > .git/config.worktree",
|
|
39
|
+
"git restore .husky/pre-commit",
|
|
40
|
+
"git rm .husky/pre-commit",
|
|
41
|
+
"git mv .husky/pre-commit /tmp/h",
|
|
42
|
+
"git apply .husky/change.patch",
|
|
43
|
+
"pushd .husky && echo x > pre-commit",
|
|
44
|
+
"ln -s /dev/null .husky/pre-commit",
|
|
45
|
+
"chmod 000 .husky/pre-commit",
|
|
46
|
+
"install -m 644 x .husky/pre-commit",
|
|
47
|
+
"truncate -s 0 .husky/pre-commit",
|
|
48
|
+
"rsync x .husky/pre-commit",
|
|
49
|
+
"patch .husky/pre-commit change.patch",
|
|
50
|
+
"sponge .husky/pre-commit",
|
|
51
|
+
"perl -pi -e s/a/b/ .husky/pre-commit",
|
|
52
|
+
"node -e \"require('fs').writeFileSync('.husky/pre-commit','')\"",
|
|
53
|
+
'ruby -e \'File.write(".husky/pre-commit", "")\'',
|
|
54
|
+
"bun -e \"Bun.write('.husky/pre-commit','')\"",
|
|
55
|
+
"/usr/bin/gh pr merge 1",
|
|
56
|
+
"gh pr merge 1; git status",
|
|
57
|
+
"git status&&gh pr merge 1",
|
|
58
|
+
'bash -c "gh pr merge 1"',
|
|
59
|
+
"eval gh pr merge 1",
|
|
60
|
+
"$(gh pr merge 1)",
|
|
61
|
+
"echo `gh pr merge 1`",
|
|
62
|
+
"x=$(echo $(gh pr merge 1))",
|
|
63
|
+
"echo \"$(echo ')'; gh pr merge 1)\"",
|
|
64
|
+
"$(echo \\); gh pr merge 1)",
|
|
65
|
+
"$(echo x # )\ngh pr merge 1)",
|
|
66
|
+
'echo "$(true&# )\ngh pr merge 1)"',
|
|
67
|
+
"{ gh pr merge 1; }",
|
|
68
|
+
"if true; then gh pr merge 1; fi",
|
|
69
|
+
"while true; do gh pr merge 1; done",
|
|
70
|
+
"if gh pr merge 1; then true; fi",
|
|
87
71
|
"gh pr merge 1 --squash",
|
|
88
72
|
"gh pr merge 1 --auto",
|
|
89
73
|
"gh -R next-friday/example pr merge 1",
|
|
@@ -95,18 +79,11 @@ const blockedCommands = [
|
|
|
95
79
|
"gh pr review 1 --approve=true",
|
|
96
80
|
"gh pr review 1 -a",
|
|
97
81
|
"gh -R next-friday/example pr review 1 -a",
|
|
98
|
-
"gh alias set m 'pr merge'",
|
|
99
82
|
"gh api repos/next-friday/example/pulls/1/merge -X PUT",
|
|
100
83
|
"gh api graphql -f query='mutation { mergePullRequest(input: {}) { clientMutationId } }'",
|
|
101
|
-
"gh api graphql -f query=@query.graphql",
|
|
102
84
|
"gh api repos/next-friday/example/pulls/1/reviews -f event=APPROVE",
|
|
103
85
|
"gh api -X POST repos/next-friday/example/actions/runs/1/pending_deployments -f state=approved",
|
|
104
86
|
"gh pr review 1 -ca",
|
|
105
|
-
"gh alias import aliases.yml",
|
|
106
|
-
"gh api graphql -Fquery=@query.graphql",
|
|
107
|
-
"gh api graphql --input query.json",
|
|
108
|
-
"gh api -X POST repos/next-friday/example/pulls/1/reviews --input review.json",
|
|
109
|
-
"gh api repos/next-friday/example/actions/runs/1/pending_deployments -F state=@state.txt",
|
|
110
87
|
"gh api graphql -f query='mutation { enablePullRequestAutoMerge(input: {}) { clientMutationId } }'",
|
|
111
88
|
"gh api graphql -f query='mutation { enqueuePullRequest(input: {}) { clientMutationId } }'",
|
|
112
89
|
"gh api graphql -f query='mutation($e: PullRequestReviewEvent!) { addPullRequestReview(input: {event: $e}) { clientMutationId } }' -f e=APPROVE",
|
|
@@ -118,79 +95,34 @@ const blockedCommands = [
|
|
|
118
95
|
"bash -c -- 'gh pr merge 1'",
|
|
119
96
|
];
|
|
120
97
|
|
|
121
|
-
const irreversibleCommands = [
|
|
122
|
-
"git push --force",
|
|
123
|
-
"git push -f origin x",
|
|
124
|
-
"git push origin x -f",
|
|
125
|
-
"git push -uf origin x",
|
|
126
|
-
"git push origin +x",
|
|
127
|
-
"git push origin :x",
|
|
128
|
-
"git push -d origin x",
|
|
129
|
-
"git push --delete origin x",
|
|
130
|
-
"git push --mirror",
|
|
131
|
-
"git reset --hard HEAD~1",
|
|
132
|
-
"git -C . reset --hard",
|
|
133
|
-
"git clean -fd",
|
|
134
|
-
"git clean -xdf",
|
|
135
|
-
"git branch -D x",
|
|
136
|
-
"git branch -df x",
|
|
137
|
-
"git branch --delete --force x",
|
|
138
|
-
"git checkout .",
|
|
139
|
-
"git checkout -- .",
|
|
140
|
-
"git checkout -f main",
|
|
141
|
-
"git restore .",
|
|
142
|
-
"git restore --worktree .",
|
|
143
|
-
"git switch --discard-changes main",
|
|
144
|
-
"git stash drop",
|
|
145
|
-
"git worktree remove --force x",
|
|
146
|
-
"git restore :/",
|
|
147
|
-
"timeout 5 git push --force",
|
|
148
|
-
"xargs git reset --hard",
|
|
149
|
-
];
|
|
150
|
-
|
|
151
98
|
const allowedCommands = [
|
|
152
99
|
'git commit -m "add login"',
|
|
153
|
-
"git commit --signoff -m x",
|
|
154
|
-
"git commit --amend --no-edit",
|
|
155
100
|
"git status",
|
|
156
101
|
"pnpm check",
|
|
157
|
-
"git commit --verbose -m x",
|
|
158
102
|
"cat .husky/pre-commit",
|
|
159
103
|
"git diff .husky/pre-commit",
|
|
160
|
-
'git commit -m "feat(x): y" && git log -n 3',
|
|
161
|
-
"git commit -m x; ls -n",
|
|
162
|
-
"git commit -m \"$(printf 'feat(x): y')\"",
|
|
163
|
-
"echo '$(git commit -n -m x)'",
|
|
164
|
-
'git commit -m "run with -n"',
|
|
165
|
-
'git commit -am "fix: -n flag handling"',
|
|
166
104
|
"git diff .husky/pre-commit > /tmp/x",
|
|
167
105
|
"ls .husky/ ; cp a b",
|
|
168
106
|
'git commit -m "chore: rm .husky/pre-commit file"',
|
|
169
|
-
'gh pr
|
|
170
|
-
'gh pr create --title "
|
|
171
|
-
"git push -n",
|
|
107
|
+
"echo '$(gh pr merge 1)'",
|
|
108
|
+
'gh pr create --title "docs(x): explain gh pr merge"',
|
|
172
109
|
"node scripts/check-agent-gates.ts",
|
|
173
110
|
"git push -u origin x",
|
|
174
|
-
"git push --dry-run --force",
|
|
175
|
-
"git push --force-with-lease origin x",
|
|
176
111
|
'gh pr create --title "fix(x): y" --body z',
|
|
177
112
|
"gh pr comment 1 --body x",
|
|
178
113
|
'gh pr review 1 -c -b "fix -a flag"',
|
|
179
114
|
'gh api repos/next-friday/example/issues/1/comments -f body="Please APPROVE"',
|
|
180
115
|
"gh api repos/next-friday/example/pulls/1",
|
|
181
116
|
"gh pr checks 1 --watch",
|
|
182
|
-
"git branch -d x",
|
|
183
117
|
"git checkout feature",
|
|
184
118
|
"git checkout .changeset/x.md",
|
|
185
|
-
"git restore --staged .",
|
|
186
|
-
"git clean -n",
|
|
187
|
-
"git reset --soft HEAD~1",
|
|
188
|
-
"git stash pop",
|
|
189
119
|
"timeout 60 pnpm test",
|
|
190
120
|
'gh api graphql -f query="query { viewer { login } }"',
|
|
191
121
|
'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
|
|
192
122
|
];
|
|
193
123
|
|
|
124
|
+
const railwayProductionCommand = "railway up -e prod";
|
|
125
|
+
|
|
194
126
|
const productionCommands = [
|
|
195
127
|
"gh workflow run deploy.yml -f environment=prod -f commit_sha=abc",
|
|
196
128
|
"gh workflow run Deploy --field 'environment=prod'",
|
|
@@ -211,7 +143,7 @@ const productionCommands = [
|
|
|
211
143
|
"gh api repos/o/r/actions/workflows/1/dispatches -f inputs[environment]=$E",
|
|
212
144
|
"bash -c 'gh workflow run deploy.yml -f environment=prod'",
|
|
213
145
|
"railway up --ci --environment prod",
|
|
214
|
-
|
|
146
|
+
railwayProductionCommand,
|
|
215
147
|
"railway up -e=prod",
|
|
216
148
|
"railway up -eprod",
|
|
217
149
|
"railway up -ce prod",
|
|
@@ -261,6 +193,9 @@ const productionCommands = [
|
|
|
261
193
|
"cd a\\\\\ngit push origin v1.0.0",
|
|
262
194
|
];
|
|
263
195
|
|
|
196
|
+
const approvalLiftsGate = ["CHG-123", "1", "CHG-1", "off", "00", " "];
|
|
197
|
+
const approvalKeepsGate = ["", "0", "false", "no", "FALSE", "No", "nO"];
|
|
198
|
+
|
|
264
199
|
const nonProductionCommands = [
|
|
265
200
|
"gh workflow run deploy.yml -f environment=uat -f commit_sha=abc",
|
|
266
201
|
"gh api repos/o/r/actions/workflows/deploy.yml/dispatches -f 'inputs[environment]=uat'",
|
|
@@ -291,11 +226,78 @@ const mcpTools = {
|
|
|
291
226
|
"mcp__github__merge_pull_request",
|
|
292
227
|
"github__merge_pull_request",
|
|
293
228
|
"github__enable_pr_auto_merge",
|
|
294
|
-
"github__pull_request_review_write",
|
|
295
229
|
],
|
|
296
230
|
allowed: ["github__create_pull_request", "github__add_issue_comment"],
|
|
297
231
|
};
|
|
298
232
|
|
|
233
|
+
const reviewWriteCases = [
|
|
234
|
+
{
|
|
235
|
+
name: "APPROVE",
|
|
236
|
+
expectedExit: 2,
|
|
237
|
+
toolInput: {
|
|
238
|
+
method: "create",
|
|
239
|
+
event: "APPROVE",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "approve in lower case",
|
|
244
|
+
expectedExit: 2,
|
|
245
|
+
toolInput: {
|
|
246
|
+
method: "create",
|
|
247
|
+
event: "approve",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "Approve in mixed case",
|
|
252
|
+
expectedExit: 2,
|
|
253
|
+
toolInput: {
|
|
254
|
+
method: "create",
|
|
255
|
+
event: "Approve",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "submit_pending with APPROVE",
|
|
260
|
+
expectedExit: 2,
|
|
261
|
+
toolInput: {
|
|
262
|
+
method: "submit_pending",
|
|
263
|
+
event: "APPROVE",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "APPROVE in a JSON string, as Cursor sends it",
|
|
268
|
+
toolInput: '{"method":"create","event":"APPROVE"}',
|
|
269
|
+
expectedExit: 2,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "COMMENT",
|
|
273
|
+
expectedExit: 0,
|
|
274
|
+
toolInput: {
|
|
275
|
+
method: "create",
|
|
276
|
+
event: "COMMENT",
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "REQUEST_CHANGES",
|
|
281
|
+
expectedExit: 0,
|
|
282
|
+
toolInput: {
|
|
283
|
+
method: "submit_pending",
|
|
284
|
+
event: "REQUEST_CHANGES",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "no event",
|
|
289
|
+
expectedExit: 0,
|
|
290
|
+
toolInput: {
|
|
291
|
+
method: "create",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "an empty JSON string",
|
|
296
|
+
toolInput: "{}",
|
|
297
|
+
expectedExit: 0,
|
|
298
|
+
},
|
|
299
|
+
];
|
|
300
|
+
|
|
299
301
|
const protectedPaths = [
|
|
300
302
|
".agents/rules/code-comments.md",
|
|
301
303
|
".agents/rules.json",
|
|
@@ -317,6 +319,61 @@ const protectedPaths = [
|
|
|
317
319
|
"src/../.husky/pre-commit",
|
|
318
320
|
];
|
|
319
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Create a repository on a branch, with one commit per file after an empty commit on `main`.
|
|
324
|
+
* @param branch - The branch to check out.
|
|
325
|
+
* @param committedFiles - Files to commit on the branch, one commit each.
|
|
326
|
+
* @returns The repository directory.
|
|
327
|
+
*/
|
|
328
|
+
function createRepository(branch: string, committedFiles: string[]): string {
|
|
329
|
+
const directory = mkdtempSync(path.join(tmpdir(), "agent-gates-"));
|
|
330
|
+
|
|
331
|
+
const runGit = (...gitArguments: string[]): void => {
|
|
332
|
+
execFileSync(
|
|
333
|
+
"git",
|
|
334
|
+
[
|
|
335
|
+
"-C",
|
|
336
|
+
directory,
|
|
337
|
+
"-c",
|
|
338
|
+
"user.name=Gate Check",
|
|
339
|
+
"-c",
|
|
340
|
+
"user.email=gate-check@example.com",
|
|
341
|
+
"-c",
|
|
342
|
+
"commit.gpgsign=false",
|
|
343
|
+
...gitArguments,
|
|
344
|
+
],
|
|
345
|
+
{
|
|
346
|
+
stdio: "ignore",
|
|
347
|
+
},
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
runGit("init", "--initial-branch=main");
|
|
352
|
+
runGit("commit", "--allow-empty", "--message", "initial");
|
|
353
|
+
runGit("checkout", "-b", branch);
|
|
354
|
+
|
|
355
|
+
for (const file of committedFiles) {
|
|
356
|
+
writeFileSync(path.join(directory, path.basename(file)), "");
|
|
357
|
+
runGit("add", "--all");
|
|
358
|
+
runGit("commit", "--message", `add ${file}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return directory;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const fixBranch = "fix/login-error";
|
|
365
|
+
const featureBranch = "feat/login";
|
|
366
|
+
const testFileName = "login.test.ts";
|
|
367
|
+
const fixBranchWithTest = createRepository(fixBranch, [testFileName]);
|
|
368
|
+
const fixBranchWithoutTest = createRepository(fixBranch, ["login.ts"]);
|
|
369
|
+
const featureBranchWithTest = createRepository(featureBranch, [testFileName]);
|
|
370
|
+
const markerRepository = createRepository(featureBranch, []);
|
|
371
|
+
|
|
372
|
+
writeFileSync(path.join(markerRepository, ".git", "bug-fix-in-progress"), "");
|
|
373
|
+
|
|
374
|
+
const testFile = `src/${testFileName}`;
|
|
375
|
+
const outsideRepository = mkdtempSync(path.join(tmpdir(), "agent-gates-outside-"));
|
|
376
|
+
|
|
320
377
|
const cases: GateCase[] = [
|
|
321
378
|
...blockedCommands.flatMap(command => [
|
|
322
379
|
{
|
|
@@ -338,29 +395,6 @@ const cases: GateCase[] = [
|
|
|
338
395
|
name: `Claude Bash: ${command}`,
|
|
339
396
|
},
|
|
340
397
|
]),
|
|
341
|
-
...irreversibleCommands.flatMap(command => [
|
|
342
|
-
{
|
|
343
|
-
gate: shellGate,
|
|
344
|
-
input: {
|
|
345
|
-
command,
|
|
346
|
-
},
|
|
347
|
-
expectedExit: 0 as const,
|
|
348
|
-
expectedPermission: "ask" as const,
|
|
349
|
-
name: `Cursor shell asks first: ${command}`,
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
gate: shellGate,
|
|
353
|
-
input: {
|
|
354
|
-
hook_event_name: "PreToolUse",
|
|
355
|
-
tool_input: {
|
|
356
|
-
command,
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
expectedExit: 0 as const,
|
|
360
|
-
expectedPermission: "ask" as const,
|
|
361
|
-
name: `Claude Bash asks first: ${command}`,
|
|
362
|
-
},
|
|
363
|
-
]),
|
|
364
398
|
...allowedCommands.map(command => ({
|
|
365
399
|
gate: shellGate,
|
|
366
400
|
input: {
|
|
@@ -390,18 +424,29 @@ const cases: GateCase[] = [
|
|
|
390
424
|
name: `Claude production: ${command}`,
|
|
391
425
|
},
|
|
392
426
|
]),
|
|
393
|
-
{
|
|
427
|
+
...approvalLiftsGate.map(releaseApproval => ({
|
|
394
428
|
gate: productionGate,
|
|
395
|
-
releaseApproval
|
|
396
|
-
expectedPermission: "allow",
|
|
397
|
-
name: "production with RELEASE_APPROVAL: railway up -e prod",
|
|
398
|
-
expectedExit: 0,
|
|
429
|
+
releaseApproval,
|
|
399
430
|
input: {
|
|
400
431
|
tool_input: {
|
|
401
|
-
command:
|
|
432
|
+
command: railwayProductionCommand,
|
|
402
433
|
},
|
|
403
434
|
},
|
|
404
|
-
|
|
435
|
+
expectedPermission: "allow" as const,
|
|
436
|
+
name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
|
|
437
|
+
expectedExit: 0 as const,
|
|
438
|
+
})),
|
|
439
|
+
...approvalKeepsGate.map(releaseApproval => ({
|
|
440
|
+
gate: productionGate,
|
|
441
|
+
releaseApproval,
|
|
442
|
+
input: {
|
|
443
|
+
tool_input: {
|
|
444
|
+
command: railwayProductionCommand,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
|
|
448
|
+
expectedExit: 2 as const,
|
|
449
|
+
})),
|
|
405
450
|
...nonProductionCommands.map(command => ({
|
|
406
451
|
gate: productionGate,
|
|
407
452
|
input: {
|
|
@@ -430,63 +475,6 @@ const cases: GateCase[] = [
|
|
|
430
475
|
expectedPermission: "allow" as const,
|
|
431
476
|
name: `MCP: ${tool}`,
|
|
432
477
|
})),
|
|
433
|
-
{
|
|
434
|
-
gate: suppressionGate,
|
|
435
|
-
name: "suppression: eslint-disable",
|
|
436
|
-
expectedExit: 2,
|
|
437
|
-
input: {
|
|
438
|
-
tool_input: {
|
|
439
|
-
file_path: sourceFile,
|
|
440
|
-
content: "// eslint-disable-next-line\n",
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
...["// NOSONAR", "# noqa: E501", "# hadolint ignore=DL3008", "# trivy:ignore:AVD-DS-0002"].map(
|
|
445
|
-
content => ({
|
|
446
|
-
gate: suppressionGate,
|
|
447
|
-
input: {
|
|
448
|
-
tool_input: {
|
|
449
|
-
file_path: sourceFile,
|
|
450
|
-
content,
|
|
451
|
-
},
|
|
452
|
-
},
|
|
453
|
-
expectedExit: 2 as const,
|
|
454
|
-
name: `suppression: ${content}`,
|
|
455
|
-
}),
|
|
456
|
-
),
|
|
457
|
-
{
|
|
458
|
-
gate: suppressionGate,
|
|
459
|
-
name: "suppression: noqa inside a word",
|
|
460
|
-
expectedExit: 0,
|
|
461
|
-
input: {
|
|
462
|
-
tool_input: {
|
|
463
|
-
file_path: sourceFile,
|
|
464
|
-
content: "const canoqaValue = 1;\n",
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
gate: suppressionGate,
|
|
470
|
-
name: "suppression: exempt directory reached through ..",
|
|
471
|
-
expectedExit: 2,
|
|
472
|
-
input: {
|
|
473
|
-
tool_input: {
|
|
474
|
-
file_path: ".agents/rules/../../src/example.ts",
|
|
475
|
-
content: "// eslint-disable-next-line\n",
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
gate: suppressionGate,
|
|
481
|
-
name: "suppression: clean edit",
|
|
482
|
-
expectedExit: 0,
|
|
483
|
-
input: {
|
|
484
|
-
tool_input: {
|
|
485
|
-
file_path: sourceFile,
|
|
486
|
-
content: "export const value = 1;\n",
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
478
|
...protectedPaths.map(file => ({
|
|
491
479
|
gate: constitutionGate,
|
|
492
480
|
input: {
|
|
@@ -497,6 +485,15 @@ const cases: GateCase[] = [
|
|
|
497
485
|
expectedExit: 2 as const,
|
|
498
486
|
name: `constitution: ${file}`,
|
|
499
487
|
})),
|
|
488
|
+
...reviewWriteCases.map(({expectedExit, name, toolInput}) => ({
|
|
489
|
+
gate: mcpGate,
|
|
490
|
+
input: {
|
|
491
|
+
tool_input: toolInput,
|
|
492
|
+
tool_name: "mcp__github__pull_request_review_write",
|
|
493
|
+
},
|
|
494
|
+
expectedExit: expectedExit as 0 | 2,
|
|
495
|
+
name: `MCP: pull_request_review_write with ${name}`,
|
|
496
|
+
})),
|
|
500
497
|
{
|
|
501
498
|
gate: constitutionGate,
|
|
502
499
|
name: "constitution: src file",
|
|
@@ -507,36 +504,51 @@ const cases: GateCase[] = [
|
|
|
507
504
|
},
|
|
508
505
|
},
|
|
509
506
|
},
|
|
507
|
+
...[
|
|
508
|
+
{
|
|
509
|
+
projectDirectory: fixBranchWithTest,
|
|
510
|
+
name: "fix branch with a committed test",
|
|
511
|
+
expectedExit: 2,
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
projectDirectory: fixBranchWithoutTest,
|
|
515
|
+
name: "fix branch without a committed test",
|
|
516
|
+
expectedExit: 0,
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
projectDirectory: featureBranchWithTest,
|
|
520
|
+
name: "other branch with a committed test",
|
|
521
|
+
expectedExit: 0,
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
projectDirectory: markerRepository,
|
|
525
|
+
name: "bug-fix-in-progress marker",
|
|
526
|
+
expectedExit: 2,
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
projectDirectory: outsideRepository,
|
|
530
|
+
name: "directory outside a Git repository",
|
|
531
|
+
expectedExit: 0,
|
|
532
|
+
},
|
|
533
|
+
].map(({expectedExit, name, projectDirectory}) => ({
|
|
534
|
+
gate: testEditGate,
|
|
535
|
+
projectDirectory,
|
|
536
|
+
input: {
|
|
537
|
+
tool_input: {
|
|
538
|
+
file_path: testFile,
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
expectedExit: expectedExit as 0 | 2,
|
|
542
|
+
name: `test edit: ${name}`,
|
|
543
|
+
})),
|
|
510
544
|
];
|
|
511
545
|
|
|
512
|
-
/**
|
|
513
|
-
* Reads the permission a gate printed in the format its tool reads: Claude Code's
|
|
514
|
-
* `hookSpecificOutput` for a `PreToolUse` input, and Cursor's `permission` otherwise.
|
|
515
|
-
* @param gateCase - The case whose input decides the format.
|
|
516
|
-
* @param stdout - The gate's standard output.
|
|
517
|
-
* @returns The permission, or undefined when the output is not JSON.
|
|
518
|
-
*/
|
|
519
|
-
function permissionOf(gateCase: GateCase, stdout: string): string | undefined {
|
|
520
|
-
try {
|
|
521
|
-
const output = JSON.parse(stdout) as {
|
|
522
|
-
hookSpecificOutput?: {permissionDecision?: string};
|
|
523
|
-
permission?: string;
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
return gateCase.input.hook_event_name === "PreToolUse" && gateCase.expectedPermission === "ask"
|
|
527
|
-
? output.hookSpecificOutput?.permissionDecision
|
|
528
|
-
: output.permission;
|
|
529
|
-
} catch {
|
|
530
|
-
return undefined;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
546
|
const failures = cases.filter(gateCase => {
|
|
535
547
|
const result = spawnSync(gateCase.gate, {
|
|
536
548
|
env: {
|
|
537
549
|
...process.env,
|
|
538
|
-
CLAUDE_PROJECT_DIR: process.cwd(),
|
|
539
|
-
CURSOR_PROJECT_DIR: process.cwd(),
|
|
550
|
+
CLAUDE_PROJECT_DIR: gateCase.projectDirectory ?? process.cwd(),
|
|
551
|
+
CURSOR_PROJECT_DIR: gateCase.projectDirectory ?? process.cwd(),
|
|
540
552
|
RELEASE_APPROVAL: gateCase.releaseApproval,
|
|
541
553
|
},
|
|
542
554
|
input: JSON.stringify(gateCase.input),
|
|
@@ -544,11 +556,25 @@ const failures = cases.filter(gateCase => {
|
|
|
544
556
|
|
|
545
557
|
const isPermissionWrong =
|
|
546
558
|
gateCase.expectedPermission !== undefined &&
|
|
547
|
-
|
|
559
|
+
(JSON.parse(result.stdout.toString()) as {permission?: string}).permission !==
|
|
560
|
+
gateCase.expectedPermission;
|
|
548
561
|
|
|
549
562
|
return result.status !== gateCase.expectedExit || isPermissionWrong;
|
|
550
563
|
});
|
|
551
564
|
|
|
565
|
+
for (const directory of [
|
|
566
|
+
fixBranchWithTest,
|
|
567
|
+
fixBranchWithoutTest,
|
|
568
|
+
featureBranchWithTest,
|
|
569
|
+
markerRepository,
|
|
570
|
+
outsideRepository,
|
|
571
|
+
]) {
|
|
572
|
+
rmSync(directory, {
|
|
573
|
+
recursive: true,
|
|
574
|
+
force: true,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
552
578
|
if (failures.length > 0) {
|
|
553
579
|
const failureLines = failures.map(gateCase => {
|
|
554
580
|
const permission =
|