@next-friday/create-app 9.1.1 → 9.2.1
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 +14 -10
- 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 +11 -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/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 +11 -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 +70 -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/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/case.yaml +67 -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/prompt.md +10 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/case.yaml +7 -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/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/case.yaml +12 -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/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 +33 -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/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/case.yaml +11 -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/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 +11 -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/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 +10 -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.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 +41 -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/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 +11 -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/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 +41 -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/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 +10 -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/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 +9 -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/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 +10 -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/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 +14 -9
- package/templates/base/.agents/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.agents/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.agents/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.agents/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.agents/skills/sdlc-help/SKILL.md +4 -2
- 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 +14 -9
- package/templates/base/.claude/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.claude/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.claude/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.claude/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.claude/skills/sdlc-help/SKILL.md +4 -2
- 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 +14 -9
- package/templates/base/.cursor/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.cursor/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.cursor/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.cursor/skills/sdlc-help/SKILL.md +4 -2
- 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 +265 -226
- 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 +1197 -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 +17 -13
- 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 +21 -19
- 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 +17 -13
- 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 +15 -14
- 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 +17 -12
- 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 +13 -11
- 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 +22 -18
- 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 +17 -15
- 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 +22 -18
- 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 +9 -9
- 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 +12 -11
- 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 +16 -16
- 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,85 @@
|
|
|
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
|
|
|
16
|
+
const repositoryVariables = execFileSync("git", ["rev-parse", "--local-env-vars"], {
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
}).split("\n");
|
|
19
|
+
|
|
20
|
+
const childEnvironment = {
|
|
21
|
+
...process.env,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (const name of repositoryVariables) {
|
|
25
|
+
delete childEnvironment[name];
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
const shellGate = ".agents/hooks/agent-shell-gate.sh";
|
|
13
|
-
const suppressionGate = ".agents/hooks/agent-suppression-gate.sh";
|
|
14
29
|
const constitutionGate = ".cursor/hooks/agent-constitution-gate.sh";
|
|
15
30
|
const mcpGate = ".agents/hooks/agent-mcp-gate.sh";
|
|
16
31
|
const productionGate = ".agents/hooks/agent-production-gate.sh";
|
|
32
|
+
const testEditGate = ".agents/hooks/agent-test-edit-gate.sh";
|
|
17
33
|
const sourceFile = "src/example.ts";
|
|
18
34
|
|
|
19
35
|
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
36
|
"printf 'exit 0' > .husky/pre-commit",
|
|
47
37
|
"rm -rf .husky/_",
|
|
48
38
|
"sed -i '' s/gitleaks/true/ .husky/pre-commit",
|
|
49
39
|
"tee .gitleaks.toml < /dev/null",
|
|
50
40
|
"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
41
|
"rm -rf .husky",
|
|
60
42
|
"mv .husky /tmp/h",
|
|
61
43
|
"sed -e s/a/b/ -i .husky/pre-commit",
|
|
62
44
|
"python3 -c \"open('.husky/pre-commit','w')\"",
|
|
63
45
|
"dd of=.husky/pre-commit",
|
|
64
|
-
"git config core.hooks$X x",
|
|
65
46
|
"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
47
|
"git checkout -- .husky/pre-commit",
|
|
70
48
|
"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
49
|
'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
50
|
"printf x > .git/config.worktree",
|
|
51
|
+
"git restore .husky/pre-commit",
|
|
52
|
+
"git rm .husky/pre-commit",
|
|
53
|
+
"git mv .husky/pre-commit /tmp/h",
|
|
54
|
+
"git apply .husky/change.patch",
|
|
55
|
+
"pushd .husky && echo x > pre-commit",
|
|
56
|
+
"ln -s /dev/null .husky/pre-commit",
|
|
57
|
+
"chmod 000 .husky/pre-commit",
|
|
58
|
+
"install -m 644 x .husky/pre-commit",
|
|
59
|
+
"truncate -s 0 .husky/pre-commit",
|
|
60
|
+
"rsync x .husky/pre-commit",
|
|
61
|
+
"patch .husky/pre-commit change.patch",
|
|
62
|
+
"sponge .husky/pre-commit",
|
|
63
|
+
"perl -pi -e s/a/b/ .husky/pre-commit",
|
|
64
|
+
"node -e \"require('fs').writeFileSync('.husky/pre-commit','')\"",
|
|
65
|
+
'ruby -e \'File.write(".husky/pre-commit", "")\'',
|
|
66
|
+
"bun -e \"Bun.write('.husky/pre-commit','')\"",
|
|
67
|
+
"/usr/bin/gh pr merge 1",
|
|
68
|
+
"gh pr merge 1; git status",
|
|
69
|
+
"git status&&gh pr merge 1",
|
|
70
|
+
'bash -c "gh pr merge 1"',
|
|
71
|
+
"eval gh pr merge 1",
|
|
72
|
+
"$(gh pr merge 1)",
|
|
73
|
+
"echo `gh pr merge 1`",
|
|
74
|
+
"x=$(echo $(gh pr merge 1))",
|
|
75
|
+
"echo \"$(echo ')'; gh pr merge 1)\"",
|
|
76
|
+
"$(echo \\); gh pr merge 1)",
|
|
77
|
+
"$(echo x # )\ngh pr merge 1)",
|
|
78
|
+
'echo "$(true&# )\ngh pr merge 1)"',
|
|
79
|
+
"{ gh pr merge 1; }",
|
|
80
|
+
"if true; then gh pr merge 1; fi",
|
|
81
|
+
"while true; do gh pr merge 1; done",
|
|
82
|
+
"if gh pr merge 1; then true; fi",
|
|
87
83
|
"gh pr merge 1 --squash",
|
|
88
84
|
"gh pr merge 1 --auto",
|
|
89
85
|
"gh -R next-friday/example pr merge 1",
|
|
@@ -95,18 +91,11 @@ const blockedCommands = [
|
|
|
95
91
|
"gh pr review 1 --approve=true",
|
|
96
92
|
"gh pr review 1 -a",
|
|
97
93
|
"gh -R next-friday/example pr review 1 -a",
|
|
98
|
-
"gh alias set m 'pr merge'",
|
|
99
94
|
"gh api repos/next-friday/example/pulls/1/merge -X PUT",
|
|
100
95
|
"gh api graphql -f query='mutation { mergePullRequest(input: {}) { clientMutationId } }'",
|
|
101
|
-
"gh api graphql -f query=@query.graphql",
|
|
102
96
|
"gh api repos/next-friday/example/pulls/1/reviews -f event=APPROVE",
|
|
103
97
|
"gh api -X POST repos/next-friday/example/actions/runs/1/pending_deployments -f state=approved",
|
|
104
98
|
"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
99
|
"gh api graphql -f query='mutation { enablePullRequestAutoMerge(input: {}) { clientMutationId } }'",
|
|
111
100
|
"gh api graphql -f query='mutation { enqueuePullRequest(input: {}) { clientMutationId } }'",
|
|
112
101
|
"gh api graphql -f query='mutation($e: PullRequestReviewEvent!) { addPullRequestReview(input: {event: $e}) { clientMutationId } }' -f e=APPROVE",
|
|
@@ -118,79 +107,34 @@ const blockedCommands = [
|
|
|
118
107
|
"bash -c -- 'gh pr merge 1'",
|
|
119
108
|
];
|
|
120
109
|
|
|
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
110
|
const allowedCommands = [
|
|
152
111
|
'git commit -m "add login"',
|
|
153
|
-
"git commit --signoff -m x",
|
|
154
|
-
"git commit --amend --no-edit",
|
|
155
112
|
"git status",
|
|
156
113
|
"pnpm check",
|
|
157
|
-
"git commit --verbose -m x",
|
|
158
114
|
"cat .husky/pre-commit",
|
|
159
115
|
"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
116
|
"git diff .husky/pre-commit > /tmp/x",
|
|
167
117
|
"ls .husky/ ; cp a b",
|
|
168
118
|
'git commit -m "chore: rm .husky/pre-commit file"',
|
|
169
|
-
'gh pr
|
|
170
|
-
'gh pr create --title "
|
|
171
|
-
"git push -n",
|
|
119
|
+
"echo '$(gh pr merge 1)'",
|
|
120
|
+
'gh pr create --title "docs(x): explain gh pr merge"',
|
|
172
121
|
"node scripts/check-agent-gates.ts",
|
|
173
122
|
"git push -u origin x",
|
|
174
|
-
"git push --dry-run --force",
|
|
175
|
-
"git push --force-with-lease origin x",
|
|
176
123
|
'gh pr create --title "fix(x): y" --body z',
|
|
177
124
|
"gh pr comment 1 --body x",
|
|
178
125
|
'gh pr review 1 -c -b "fix -a flag"',
|
|
179
126
|
'gh api repos/next-friday/example/issues/1/comments -f body="Please APPROVE"',
|
|
180
127
|
"gh api repos/next-friday/example/pulls/1",
|
|
181
128
|
"gh pr checks 1 --watch",
|
|
182
|
-
"git branch -d x",
|
|
183
129
|
"git checkout feature",
|
|
184
130
|
"git checkout .changeset/x.md",
|
|
185
|
-
"git restore --staged .",
|
|
186
|
-
"git clean -n",
|
|
187
|
-
"git reset --soft HEAD~1",
|
|
188
|
-
"git stash pop",
|
|
189
131
|
"timeout 60 pnpm test",
|
|
190
132
|
'gh api graphql -f query="query { viewer { login } }"',
|
|
191
133
|
'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
|
|
192
134
|
];
|
|
193
135
|
|
|
136
|
+
const railwayProductionCommand = "railway up -e prod";
|
|
137
|
+
|
|
194
138
|
const productionCommands = [
|
|
195
139
|
"gh workflow run deploy.yml -f environment=prod -f commit_sha=abc",
|
|
196
140
|
"gh workflow run Deploy --field 'environment=prod'",
|
|
@@ -211,7 +155,7 @@ const productionCommands = [
|
|
|
211
155
|
"gh api repos/o/r/actions/workflows/1/dispatches -f inputs[environment]=$E",
|
|
212
156
|
"bash -c 'gh workflow run deploy.yml -f environment=prod'",
|
|
213
157
|
"railway up --ci --environment prod",
|
|
214
|
-
|
|
158
|
+
railwayProductionCommand,
|
|
215
159
|
"railway up -e=prod",
|
|
216
160
|
"railway up -eprod",
|
|
217
161
|
"railway up -ce prod",
|
|
@@ -261,6 +205,9 @@ const productionCommands = [
|
|
|
261
205
|
"cd a\\\\\ngit push origin v1.0.0",
|
|
262
206
|
];
|
|
263
207
|
|
|
208
|
+
const approvalLiftsGate = ["CHG-123", "1", "CHG-1", "off", "00", " "];
|
|
209
|
+
const approvalKeepsGate = ["", "0", "false", "no", "FALSE", "No", "nO"];
|
|
210
|
+
|
|
264
211
|
const nonProductionCommands = [
|
|
265
212
|
"gh workflow run deploy.yml -f environment=uat -f commit_sha=abc",
|
|
266
213
|
"gh api repos/o/r/actions/workflows/deploy.yml/dispatches -f 'inputs[environment]=uat'",
|
|
@@ -291,11 +238,78 @@ const mcpTools = {
|
|
|
291
238
|
"mcp__github__merge_pull_request",
|
|
292
239
|
"github__merge_pull_request",
|
|
293
240
|
"github__enable_pr_auto_merge",
|
|
294
|
-
"github__pull_request_review_write",
|
|
295
241
|
],
|
|
296
242
|
allowed: ["github__create_pull_request", "github__add_issue_comment"],
|
|
297
243
|
};
|
|
298
244
|
|
|
245
|
+
const reviewWriteCases = [
|
|
246
|
+
{
|
|
247
|
+
name: "APPROVE",
|
|
248
|
+
expectedExit: 2,
|
|
249
|
+
toolInput: {
|
|
250
|
+
method: "create",
|
|
251
|
+
event: "APPROVE",
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "approve in lower case",
|
|
256
|
+
expectedExit: 2,
|
|
257
|
+
toolInput: {
|
|
258
|
+
method: "create",
|
|
259
|
+
event: "approve",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "Approve in mixed case",
|
|
264
|
+
expectedExit: 2,
|
|
265
|
+
toolInput: {
|
|
266
|
+
method: "create",
|
|
267
|
+
event: "Approve",
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "submit_pending with APPROVE",
|
|
272
|
+
expectedExit: 2,
|
|
273
|
+
toolInput: {
|
|
274
|
+
method: "submit_pending",
|
|
275
|
+
event: "APPROVE",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "APPROVE in a JSON string, as Cursor sends it",
|
|
280
|
+
toolInput: '{"method":"create","event":"APPROVE"}',
|
|
281
|
+
expectedExit: 2,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "COMMENT",
|
|
285
|
+
expectedExit: 0,
|
|
286
|
+
toolInput: {
|
|
287
|
+
method: "create",
|
|
288
|
+
event: "COMMENT",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "REQUEST_CHANGES",
|
|
293
|
+
expectedExit: 0,
|
|
294
|
+
toolInput: {
|
|
295
|
+
method: "submit_pending",
|
|
296
|
+
event: "REQUEST_CHANGES",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "no event",
|
|
301
|
+
expectedExit: 0,
|
|
302
|
+
toolInput: {
|
|
303
|
+
method: "create",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "an empty JSON string",
|
|
308
|
+
toolInput: "{}",
|
|
309
|
+
expectedExit: 0,
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
|
|
299
313
|
const protectedPaths = [
|
|
300
314
|
".agents/rules/code-comments.md",
|
|
301
315
|
".agents/rules.json",
|
|
@@ -317,6 +331,62 @@ const protectedPaths = [
|
|
|
317
331
|
"src/../.husky/pre-commit",
|
|
318
332
|
];
|
|
319
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Create a repository on a branch, with one commit per file after an empty commit on `main`.
|
|
336
|
+
* @param branch - The branch to check out.
|
|
337
|
+
* @param committedFiles - Files to commit on the branch, one commit each.
|
|
338
|
+
* @returns The repository directory.
|
|
339
|
+
*/
|
|
340
|
+
function createRepository(branch: string, committedFiles: string[]): string {
|
|
341
|
+
const directory = mkdtempSync(path.join(tmpdir(), "agent-gates-"));
|
|
342
|
+
|
|
343
|
+
const runGit = (...gitArguments: string[]): void => {
|
|
344
|
+
execFileSync(
|
|
345
|
+
"git",
|
|
346
|
+
[
|
|
347
|
+
"-C",
|
|
348
|
+
directory,
|
|
349
|
+
"-c",
|
|
350
|
+
"user.name=Gate Check",
|
|
351
|
+
"-c",
|
|
352
|
+
"user.email=gate-check@example.com",
|
|
353
|
+
"-c",
|
|
354
|
+
"commit.gpgsign=false",
|
|
355
|
+
...gitArguments,
|
|
356
|
+
],
|
|
357
|
+
{
|
|
358
|
+
env: childEnvironment,
|
|
359
|
+
stdio: "ignore",
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
runGit("init", "--initial-branch=main");
|
|
365
|
+
runGit("commit", "--allow-empty", "--message", "initial");
|
|
366
|
+
runGit("checkout", "-b", branch);
|
|
367
|
+
|
|
368
|
+
for (const file of committedFiles) {
|
|
369
|
+
writeFileSync(path.join(directory, path.basename(file)), "");
|
|
370
|
+
runGit("add", "--all");
|
|
371
|
+
runGit("commit", "--message", `add ${file}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return directory;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const fixBranch = "fix/login-error";
|
|
378
|
+
const featureBranch = "feat/login";
|
|
379
|
+
const testFileName = "login.test.ts";
|
|
380
|
+
const fixBranchWithTest = createRepository(fixBranch, [testFileName]);
|
|
381
|
+
const fixBranchWithoutTest = createRepository(fixBranch, ["login.ts"]);
|
|
382
|
+
const featureBranchWithTest = createRepository(featureBranch, [testFileName]);
|
|
383
|
+
const markerRepository = createRepository(featureBranch, []);
|
|
384
|
+
|
|
385
|
+
writeFileSync(path.join(markerRepository, ".git", "bug-fix-in-progress"), "");
|
|
386
|
+
|
|
387
|
+
const testFile = `src/${testFileName}`;
|
|
388
|
+
const outsideRepository = mkdtempSync(path.join(tmpdir(), "agent-gates-outside-"));
|
|
389
|
+
|
|
320
390
|
const cases: GateCase[] = [
|
|
321
391
|
...blockedCommands.flatMap(command => [
|
|
322
392
|
{
|
|
@@ -338,29 +408,6 @@ const cases: GateCase[] = [
|
|
|
338
408
|
name: `Claude Bash: ${command}`,
|
|
339
409
|
},
|
|
340
410
|
]),
|
|
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
411
|
...allowedCommands.map(command => ({
|
|
365
412
|
gate: shellGate,
|
|
366
413
|
input: {
|
|
@@ -390,18 +437,29 @@ const cases: GateCase[] = [
|
|
|
390
437
|
name: `Claude production: ${command}`,
|
|
391
438
|
},
|
|
392
439
|
]),
|
|
393
|
-
{
|
|
440
|
+
...approvalLiftsGate.map(releaseApproval => ({
|
|
394
441
|
gate: productionGate,
|
|
395
|
-
releaseApproval
|
|
396
|
-
expectedPermission: "allow",
|
|
397
|
-
name: "production with RELEASE_APPROVAL: railway up -e prod",
|
|
398
|
-
expectedExit: 0,
|
|
442
|
+
releaseApproval,
|
|
399
443
|
input: {
|
|
400
444
|
tool_input: {
|
|
401
|
-
command:
|
|
445
|
+
command: railwayProductionCommand,
|
|
402
446
|
},
|
|
403
447
|
},
|
|
404
|
-
|
|
448
|
+
expectedPermission: "allow" as const,
|
|
449
|
+
name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
|
|
450
|
+
expectedExit: 0 as const,
|
|
451
|
+
})),
|
|
452
|
+
...approvalKeepsGate.map(releaseApproval => ({
|
|
453
|
+
gate: productionGate,
|
|
454
|
+
releaseApproval,
|
|
455
|
+
input: {
|
|
456
|
+
tool_input: {
|
|
457
|
+
command: railwayProductionCommand,
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
|
|
461
|
+
expectedExit: 2 as const,
|
|
462
|
+
})),
|
|
405
463
|
...nonProductionCommands.map(command => ({
|
|
406
464
|
gate: productionGate,
|
|
407
465
|
input: {
|
|
@@ -430,63 +488,6 @@ const cases: GateCase[] = [
|
|
|
430
488
|
expectedPermission: "allow" as const,
|
|
431
489
|
name: `MCP: ${tool}`,
|
|
432
490
|
})),
|
|
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
491
|
...protectedPaths.map(file => ({
|
|
491
492
|
gate: constitutionGate,
|
|
492
493
|
input: {
|
|
@@ -497,6 +498,15 @@ const cases: GateCase[] = [
|
|
|
497
498
|
expectedExit: 2 as const,
|
|
498
499
|
name: `constitution: ${file}`,
|
|
499
500
|
})),
|
|
501
|
+
...reviewWriteCases.map(({expectedExit, name, toolInput}) => ({
|
|
502
|
+
gate: mcpGate,
|
|
503
|
+
input: {
|
|
504
|
+
tool_input: toolInput,
|
|
505
|
+
tool_name: "mcp__github__pull_request_review_write",
|
|
506
|
+
},
|
|
507
|
+
expectedExit: expectedExit as 0 | 2,
|
|
508
|
+
name: `MCP: pull_request_review_write with ${name}`,
|
|
509
|
+
})),
|
|
500
510
|
{
|
|
501
511
|
gate: constitutionGate,
|
|
502
512
|
name: "constitution: src file",
|
|
@@ -507,36 +517,51 @@ const cases: GateCase[] = [
|
|
|
507
517
|
},
|
|
508
518
|
},
|
|
509
519
|
},
|
|
520
|
+
...[
|
|
521
|
+
{
|
|
522
|
+
projectDirectory: fixBranchWithTest,
|
|
523
|
+
name: "fix branch with a committed test",
|
|
524
|
+
expectedExit: 2,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
projectDirectory: fixBranchWithoutTest,
|
|
528
|
+
name: "fix branch without a committed test",
|
|
529
|
+
expectedExit: 0,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
projectDirectory: featureBranchWithTest,
|
|
533
|
+
name: "other branch with a committed test",
|
|
534
|
+
expectedExit: 0,
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
projectDirectory: markerRepository,
|
|
538
|
+
name: "bug-fix-in-progress marker",
|
|
539
|
+
expectedExit: 2,
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
projectDirectory: outsideRepository,
|
|
543
|
+
name: "directory outside a Git repository",
|
|
544
|
+
expectedExit: 0,
|
|
545
|
+
},
|
|
546
|
+
].map(({expectedExit, name, projectDirectory}) => ({
|
|
547
|
+
gate: testEditGate,
|
|
548
|
+
projectDirectory,
|
|
549
|
+
input: {
|
|
550
|
+
tool_input: {
|
|
551
|
+
file_path: testFile,
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
expectedExit: expectedExit as 0 | 2,
|
|
555
|
+
name: `test edit: ${name}`,
|
|
556
|
+
})),
|
|
510
557
|
];
|
|
511
558
|
|
|
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
559
|
const failures = cases.filter(gateCase => {
|
|
535
560
|
const result = spawnSync(gateCase.gate, {
|
|
536
561
|
env: {
|
|
537
|
-
...
|
|
538
|
-
CLAUDE_PROJECT_DIR: process.cwd(),
|
|
539
|
-
CURSOR_PROJECT_DIR: process.cwd(),
|
|
562
|
+
...childEnvironment,
|
|
563
|
+
CLAUDE_PROJECT_DIR: gateCase.projectDirectory ?? process.cwd(),
|
|
564
|
+
CURSOR_PROJECT_DIR: gateCase.projectDirectory ?? process.cwd(),
|
|
540
565
|
RELEASE_APPROVAL: gateCase.releaseApproval,
|
|
541
566
|
},
|
|
542
567
|
input: JSON.stringify(gateCase.input),
|
|
@@ -544,11 +569,25 @@ const failures = cases.filter(gateCase => {
|
|
|
544
569
|
|
|
545
570
|
const isPermissionWrong =
|
|
546
571
|
gateCase.expectedPermission !== undefined &&
|
|
547
|
-
|
|
572
|
+
(JSON.parse(result.stdout.toString()) as {permission?: string}).permission !==
|
|
573
|
+
gateCase.expectedPermission;
|
|
548
574
|
|
|
549
575
|
return result.status !== gateCase.expectedExit || isPermissionWrong;
|
|
550
576
|
});
|
|
551
577
|
|
|
578
|
+
for (const directory of [
|
|
579
|
+
fixBranchWithTest,
|
|
580
|
+
fixBranchWithoutTest,
|
|
581
|
+
featureBranchWithTest,
|
|
582
|
+
markerRepository,
|
|
583
|
+
outsideRepository,
|
|
584
|
+
]) {
|
|
585
|
+
rmSync(directory, {
|
|
586
|
+
recursive: true,
|
|
587
|
+
force: true,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
552
591
|
if (failures.length > 0) {
|
|
553
592
|
const failureLines = failures.map(gateCase => {
|
|
554
593
|
const permission =
|