@next-friday/create-app 6.0.0 → 6.0.2

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/dist/main.mjs CHANGED
@@ -81,12 +81,15 @@ async function writeEnvironmentFile(directory) {
81
81
  */
82
82
  function initGit(directory) {
83
83
  if (existsSync(path.join(directory, ".git"))) return "existing";
84
+ const environment = projectGitEnvironment();
84
85
  if (spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
85
86
  cwd: directory,
87
+ env: environment,
86
88
  stdio: "ignore"
87
89
  }).status === 0) return "parent";
88
90
  return spawnSync("git", ["init", "--initial-branch=main"], {
89
91
  cwd: directory,
92
+ env: environment,
90
93
  stdio: "ignore"
91
94
  }).status === 0 ? "initialized" : "failed";
92
95
  }
@@ -99,9 +102,22 @@ function didInstallDependencies(directory) {
99
102
  return spawnSync("pnpm install", {
100
103
  cwd: directory,
101
104
  stdio: "inherit",
102
- shell: true
105
+ shell: true,
106
+ env: projectGitEnvironment()
103
107
  }).status === 0;
104
108
  }
109
+ /**
110
+ * The environment without the repository variables that `git rev-parse --local-env-vars` lists.
111
+ * An inherited `GIT_DIR`, as Git sets for its hooks, would point Git, and the install's Git hook
112
+ * setup, at another repository. `git -c` options stay, as Git keeps them for another repository.
113
+ * @returns Environment for commands that run Git in the project directory.
114
+ */
115
+ function projectGitEnvironment() {
116
+ const localVariables = new Set(spawnSync("git", ["rev-parse", "--local-env-vars"], { encoding: "utf8" }).stdout?.split("\n"));
117
+ localVariables.delete("GIT_CONFIG_PARAMETERS");
118
+ localVariables.delete("GIT_CONFIG_COUNT");
119
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !localVariables.has(name)));
120
+ }
105
121
  // src/project-name.ts
106
122
  const packageNamePattern = /^[a-z0-9][a-z0-9._-]*$/;
107
123
  const maxPackageNameLength = 214;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next-friday/create-app",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
4
4
  "description": "Create a Next Friday project from a template.",
5
5
  "keywords": [
6
6
  "create",
@@ -1,10 +1,48 @@
1
- #!/bin/bash
2
- # Production deploys require a named release authorization
3
- cmd=$(jq -r '.tool_input.command // .command' < /dev/stdin)
4
- if [[ "$cmd" == *"deploy"* && "$cmd" == *"production"* ]]; then
5
- if [ -z "$RELEASE_APPROVAL" ]; then
6
- echo "Production deploys need a release authorization." >&2
7
- exit 2 # exit 2 blocks the action; the message goes to Claude
8
- fi
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ allow() {
5
+ printf '%s\n' '{"permission":"allow"}'
6
+ exit 0
7
+ }
8
+
9
+ block() {
10
+ jq -n --arg message "$1" '{permission:"deny",user_message:$message,agent_message:$message}'
11
+ printf 'agent-production-gate: %s\n' "$1" >&2
12
+ exit 2
13
+ }
14
+
15
+ trap 'block ".agents/hooks/agent-production-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
16
+ command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
17
+ command=$(jq -r '.command // .tool_input.command // empty')
18
+ [[ -n $command && -z ${RELEASE_APPROVAL:-} ]] || allow
19
+
20
+ release_gate="A release manager runs prod deploys, commands against prod, and release tag pushes. Do not run this; report what is ready to promote, such as the commit SHA. If a gated command appears only inside a message, write the text to a file and pass it with git commit -F <file> or gh pr create --body-file <file>."
21
+
22
+ # The gate reads the command line as text, joining continued lines and dropping quotes,
23
+ # backslashes, and fd redirections, so wrappers such as bash -c, eval, or npx and split words such
24
+ # as pr''od do not hide the common forms. A dispatch input or Railway environment the gate cannot
25
+ # read (a variable, a command substitution, a file, or stdin) counts as prod. It is a convenience,
26
+ # not a security boundary: the prod environment's required reviewers and Railway credentials are.
27
+ text=${command//'\\'/}
28
+ text=${text//$'\\\n'/}
29
+ text=$(sed -E 's/([0-9]*[<>]&[0-9-]*|&>>?)//g' <<< "${text//[\"\'\\]/}")
30
+ simple=$'[^;&|\n]*'
31
+ prod="(prod([^[:alnum:]_-]|$)|[\$\`@])"
32
+ dispatch="(workflow[[:space:]]+run|/dispatches)"
33
+ environment_field="environment[]]*[=:][[:space:]]*$prod"
34
+ unreadable_inputs="--(json|input)([[:space:]=]|$)"
35
+ railway="(^|[^[:alnum:]_.-])(railway|@railway/cli(@[^[:space:]]*)?)([[:space:]]$simple)?"
36
+ git_push="(^|[^[:alnum:]_.-])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--(git-dir|work-tree|namespace|config-env)[[:space:]]+[^[:space:]]+|--?[^[:space:]]+))*[[:space:]]+push([[:space:]]$simple)?"
37
+ tag_ref="([[:space:]:+]((refs/)?tags/|v[0-9])|[[:space:]]--((follow-)?tags|mirror)([[:space:]=]|$))"
38
+
39
+ if [[ $text =~ $dispatch$simple($environment_field|$unreadable_inputs) || $text =~ $environment_field$simple$dispatch ]]; then
40
+ block "$release_gate"
9
41
  fi
10
- exit 0
42
+ if [[ $text =~ $railway[[:space:]](-[[:alpha:]]*e([[:space:]]+|=)?|--environment([[:space:]]+|=))$prod || $text =~ $railway[[:space:]](env|environment)([[:space:]]+[[:alpha:]]+)?[[:space:]]+$prod ]]; then
43
+ block "$release_gate"
44
+ fi
45
+ if [[ $text =~ $git_push$tag_ref || $(tr '[:upper:]' '[:lower:]' <<< "$text") == *push.followtags* ]]; then
46
+ block "$release_gate"
47
+ fi
48
+ allow
@@ -76,7 +76,8 @@
76
76
  {
77
77
  "type": "command",
78
78
  "command": "\"$CLAUDE_PROJECT_DIR\"/.agents/hooks/agent-production-gate.sh",
79
- "timeout": 10
79
+ "timeout": 10,
80
+ "onFailure": "block"
80
81
  }
81
82
  ]
82
83
  },
@@ -29,7 +29,8 @@
29
29
  },
30
30
  {
31
31
  "command": ".agents/hooks/agent-production-gate.sh",
32
- "timeout": 10
32
+ "timeout": 10,
33
+ "failClosed": true
33
34
  }
34
35
  ],
35
36
  "beforeMCPExecution": [
@@ -9,7 +9,7 @@ This project follows Anthropic's [AI-native SDLC Playbook](https://claude.com/re
9
9
  3. **Build:** the engineer starts in plan mode, approves the plan, and commits it as `plan.md`. A tech lead approves higher-risk plans.
10
10
  4. **Test:** the session runs **Verifying your work** in `AGENTS.md`.
11
11
  5. **Review:** Claude Code Review and Bugbot review by `REVIEW.md`. A code owner approves; the agent never does.
12
- 6. **Deploy:** `agent-production-gate.sh` gates production.
12
+ 6. **Deploy:** once `DEPLOY_ENABLED` is `true`, each push to `main` that passes CI deploys to `dev`, and the Deploy workflow promotes a commit to `uat` and then `prod`. `agent-production-gate.sh` blocks an agent's shell commands that deploy to `prod`, run a Railway command with `prod` as its environment, or push a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set.
13
13
  7. **Maintain:** larger work becomes a new `intent.md`.
14
14
 
15
15
  ## 1. First commit and push
@@ -94,7 +94,7 @@ Also replace the owner placeholder in `.github/zizmor.yml`.
94
94
  - **Sources of truth (engineering lead):** for each artifact, name one system as the source of truth: this repository, an existing tool such as Jira, or, as the minimum, a link both ways (the record id in the file and the commit SHA in the record).
95
95
  - **Higher risk (engineering lead):** decide which changes count as higher risk. A tech lead or architect approves their specs and plans.
96
96
  - **Policies (each policy owner, with an engineer):** write the policy in `.agents/skills/<area>-policy/SKILL.md` from the owner's source of truth, then run `pnpm rules:sync`. Test that the skill triggers: ask for the relevant task in different ways and confirm the skill loads each time. The policy owner approves every later change as its code owner.
97
- - **Production gate (engineering leadership, with change management and compliance):** list the approvals that must survive. A person then changes `.agents/hooks/agent-production-gate.sh` to match this project's production deploy command and approval; agent settings deny the file to agents.
97
+ - **Production gate (engineering leadership, with change management and compliance):** name the release managers and add them as required reviewers on the `prod` environment, as the README's deploy section describes. `.agents/hooks/agent-production-gate.sh` blocks an agent's shell commands that dispatch a `prod` deploy, run a Railway command with `prod` as its environment, or push a release tag. It does not read MCP tools. A release manager whose own agent session must do one of these starts the agent with `RELEASE_APPROVAL` set to the approval record, for example `RELEASE_APPROVAL=CHG-123 claude`.
98
98
  - **Review (tech lead):** replace `src/gen/` in `.agents/review.md` with this project's generated paths, then run `pnpm rules:sync`, which renders it to `REVIEW.md` for Claude Code Review. Once a month, rate the review findings and tune `.agents/review.md`.
99
99
  - **People without Git experience:** connect GitHub to Claude (claude.ai or Cowork), so Claude commits `intent.md` for them.
100
100
 
@@ -6,3 +6,18 @@ This folder is the intent home. Every change starts here as an `intent.md`, and
6
6
  - **Plan.** An idea, a ticket, or an incident becomes `intent.md`, written with the `write-intent` skill. The product owner reviews and corrects it before it is committed. Merging its pull request records the decision: `Status: accepted.` accepts it, and `Status: rejected.` rejects it.
7
7
  - **Design.** The `write-spec` skill turns the accepted `intent.md` into `spec.md`, committed alongside it. The product owner reviews the spec against the idea and resolves each area of concern with its policy owner. Accepting the spec starts the build.
8
8
  - **Build.** The engineer starts in plan mode with `intent.md` and `spec.md`, and commits the approved plan as `plan.md`. When implementation departs from the plan, `plan.md` changes in the same commit.
9
+
10
+ ## Which file holds what
11
+
12
+ The bullets above say who writes and who decides each file. What each file answers:
13
+
14
+ | File | Answers |
15
+ | :---------- | :--------------------------------------------------------------------------------------------------------- |
16
+ | `intent.md` | The problem, the outcome wanted, the users and systems it affects, its constraints, and its open questions |
17
+ | `spec.md` | What to build and how it is designed: requirements, design, and areas of concern |
18
+ | `plan.md` | How to make the change: the files that change, the order of the work, and the tests that prove it |
19
+
20
+ ## Where a technology choice goes
21
+
22
+ - **A choice already made**, such as a required framework or service, goes into the intent's **Constraints**. The spec designs within it.
23
+ - **A choice to weigh**, such as two libraries or two approaches, goes into the intent's **Proposed outcome** as a proposal or into its **Open questions**. The spec compares the options in its design, with sources, and the product owner accepts or corrects the choice in review.
@@ -6,12 +6,14 @@ type GateCase = {
6
6
  gate: string;
7
7
  input: Record<string, unknown>;
8
8
  name: string;
9
+ releaseApproval?: string;
9
10
  };
10
11
 
11
12
  const shellGate = ".agents/hooks/agent-shell-gate.sh";
12
13
  const suppressionGate = ".agents/hooks/agent-suppression-gate.sh";
13
14
  const constitutionGate = ".cursor/hooks/agent-constitution-gate.sh";
14
15
  const mcpGate = ".agents/hooks/agent-mcp-gate.sh";
16
+ const productionGate = ".agents/hooks/agent-production-gate.sh";
15
17
  const sourceFile = "src/example.ts";
16
18
 
17
19
  const blockedCommands = [
@@ -189,6 +191,101 @@ const allowedCommands = [
189
191
  'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
190
192
  ];
191
193
 
194
+ const productionCommands = [
195
+ "gh workflow run deploy.yml -f environment=prod -f commit_sha=abc",
196
+ "gh workflow run Deploy --field 'environment=prod'",
197
+ "gh workflow run deploy.yml -fenvironment=prod",
198
+ "gh workflow run deploy.yml --field=environment=prod",
199
+ 'gh workflow run deploy.yml -f "environment=prod"',
200
+ "gh workflow run deploy.yml -F environment=@environment.txt",
201
+ "gh workflow run deploy.yml --json",
202
+ "gh workflow run deploy.yml --json=true",
203
+ "for e in uat prod; do gh workflow run deploy.yml -f environment=$e -f commit_sha=abc; done",
204
+ "env=prod; gh workflow run deploy.yml -f environment=$env",
205
+ "gh workflow run deploy.yml -f environment=$(echo prod)",
206
+ 'gh workflow run deploy.yml -f "environment=$TARGET"',
207
+ "gh api -X POST repos/o/r/actions/workflows/deploy.yml/dispatches -f ref=main -f 'inputs[environment]=prod'",
208
+ "gh api repos/o/r/actions/workflows/deploy.yml/dispatches/ -f inputs[environment]=prod",
209
+ "gh api -f 'inputs[environment]=prod' repos/o/r/actions/workflows/deploy.yml/dispatches",
210
+ "gh api repos/o/r/actions/workflows/1/dispatches --input body.json",
211
+ "gh api repos/o/r/actions/workflows/1/dispatches -f inputs[environment]=$E",
212
+ "bash -c 'gh workflow run deploy.yml -f environment=prod'",
213
+ "railway up --ci --environment prod",
214
+ "railway up -e prod",
215
+ "railway up -e=prod",
216
+ "railway up -eprod",
217
+ "railway up -ce prod",
218
+ "railway up -ceprod",
219
+ "railway redeploy -ye prod",
220
+ "railway up -s api -e prod",
221
+ "railway up --environment=prod",
222
+ 'railway up -e "$ENV"',
223
+ "for e in uat prod; do railway up -e $e; done",
224
+ "railway env prod",
225
+ "railway env link prod",
226
+ "railway environment prod",
227
+ "railway environment delete prod --yes",
228
+ "railway link -e prod",
229
+ "railway logs -e prod",
230
+ "railway variables --set A=b -e prod",
231
+ "npx @railway/cli up -e prod",
232
+ "pnpm dlx @railway/cli up -e prod",
233
+ "/usr/local/bin/railway up -e prod",
234
+ "timeout 60 railway up -e prod",
235
+ "eval railway up -e prod",
236
+ "git push origin v1.0.0",
237
+ "git push origin HEAD:refs/tags/v1.0.0",
238
+ "git push --tags",
239
+ "git push --follow-tags origin main",
240
+ "git -C . push origin v2.3.4",
241
+ "npx @railway/cli@5.71.0 up -e prod",
242
+ "npx @railway/cli@latest up --environment prod",
243
+ "railway up -e pr''od",
244
+ "gh workflow run deploy.yml -f environment=pr''od",
245
+ "gh workflow run deploy.yml -f environment=p\\rod",
246
+ 'gh workflow run deploy.yml -f "envi"ronment=prod',
247
+ "git push origin 'refs/tags/*'",
248
+ 'git push origin "refs/tags/v*"',
249
+ "git push --mirror",
250
+ "git -c push.followTags=true push origin main",
251
+ "git config push.followTags true && git push",
252
+ "gh workflow run deploy.yml 2>&1 -f environment=prod",
253
+ "railway up 2>&1 -e prod",
254
+ "git push origin tags/v1.0.0",
255
+ "git --git-dir .git push origin v1.0.0",
256
+ "gh workflow run deploy.yml \\\n -f environment=prod \\\n -f commit_sha=abc",
257
+ "railway up \\\n --environment prod",
258
+ "git push \\\n origin v1.0.0",
259
+ "railway up &>/dev/null -e prod",
260
+ "git push &>/dev/null origin v1.0.0",
261
+ "cd a\\\\\ngit push origin v1.0.0",
262
+ ];
263
+
264
+ const nonProductionCommands = [
265
+ "gh workflow run deploy.yml -f environment=uat -f commit_sha=abc",
266
+ "gh api repos/o/r/actions/workflows/deploy.yml/dispatches -f 'inputs[environment]=uat'",
267
+ "railway up -e uat",
268
+ "railway up -e prod-eu",
269
+ "railway environment uat",
270
+ "railway status",
271
+ "gh workflow list",
272
+ "gh run list --workflow deploy.yml",
273
+ "gh api repos/o/r/environments/prod",
274
+ "git push -u origin feature/v2-ui",
275
+ "git push origin main",
276
+ 'git commit -m "release v1.0" && git push',
277
+ 'git commit -m "docs(x): deploy to production"',
278
+ 'gh pr create --title "ci(x): promote to prod" --body "run gh workflow run later"',
279
+ "echo railway",
280
+ "git tag v1.0.0",
281
+ 'git stash push -m "wip v2"',
282
+ 'git commit -m "fix(ci): push v2 images"',
283
+ 'git commit -m "fix: railway env uat and prod split"',
284
+ "git push -u origin HEAD",
285
+ "git push origin feature/push-v2",
286
+ "pnpm verify 2>&1 | tail && git push",
287
+ ];
288
+
192
289
  const mcpTools = {
193
290
  blocked: [
194
291
  "mcp__github__merge_pull_request",
@@ -273,6 +370,47 @@ const cases: GateCase[] = [
273
370
  expectedPermission: "allow" as const,
274
371
  name: `shell: ${command}`,
275
372
  })),
373
+ ...productionCommands.flatMap(command => [
374
+ {
375
+ gate: productionGate,
376
+ input: {
377
+ command,
378
+ },
379
+ expectedExit: 2 as const,
380
+ name: `Cursor production: ${command}`,
381
+ },
382
+ {
383
+ gate: productionGate,
384
+ input: {
385
+ tool_input: {
386
+ command,
387
+ },
388
+ },
389
+ expectedExit: 2 as const,
390
+ name: `Claude production: ${command}`,
391
+ },
392
+ ]),
393
+ {
394
+ gate: productionGate,
395
+ releaseApproval: "CHG-1",
396
+ expectedPermission: "allow",
397
+ name: "production with RELEASE_APPROVAL: railway up -e prod",
398
+ expectedExit: 0,
399
+ input: {
400
+ tool_input: {
401
+ command: "railway up -e prod",
402
+ },
403
+ },
404
+ },
405
+ ...nonProductionCommands.map(command => ({
406
+ gate: productionGate,
407
+ input: {
408
+ command,
409
+ },
410
+ expectedExit: 0 as const,
411
+ expectedPermission: "allow" as const,
412
+ name: `production: ${command}`,
413
+ })),
276
414
  ...mcpTools.blocked.map(tool => ({
277
415
  gate: mcpGate,
278
416
  input: {
@@ -399,6 +537,7 @@ const failures = cases.filter(gateCase => {
399
537
  ...process.env,
400
538
  CLAUDE_PROJECT_DIR: process.cwd(),
401
539
  CURSOR_PROJECT_DIR: process.cwd(),
540
+ RELEASE_APPROVAL: gateCase.releaseApproval,
402
541
  },
403
542
  input: JSON.stringify(gateCase.input),
404
543
  });
@@ -43,6 +43,7 @@ If a test fails, fix the code, not the test.
43
43
  When Claude makes a mistake twice, add the correction here.
44
44
 
45
45
  - **Agent shell gate**: `.agents/hooks/agent-shell-gate.sh` blocks a shell command that writes to a protected path such as `.agents`, `.cursor`, `.claude/rules`, or `.husky`. It also blocks merging or approving a pull request, approving a deployment, and gh aliases, and asks the person before an irreversible git command such as a force push or a hard reset. It is a convenience, not a security boundary: CI and code-owner review are the boundary.
46
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` (`gh workflow run` or a workflow dispatch with `environment=prod`), a Railway CLI command that names `prod` with `-e`, `--environment`, or `railway environment`, and a release tag push, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it. It is a convenience, not a security boundary: the `prod` environment's required reviewers and Railway credentials are the boundary.
46
47
 
47
48
  ## Related documentation
48
49
 
@@ -53,7 +53,7 @@ On Vercel, the zero-configuration NestJS preset takes precedence over the `Docke
53
53
  - the `RAILWAY_SERVICE` variable: the service name;
54
54
  - the `APP_URL` variable: the public `https://` URL.
55
55
 
56
- Limit `dev`, `uat`, and `prod` to the `main` branch and add required reviewers. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
56
+ Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
57
57
 
58
58
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
59
59
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
@@ -44,6 +44,7 @@ When Claude makes a mistake twice, add the correction here.
44
44
 
45
45
  - **First end-to-end run**: run `pnpm exec playwright install chromium` once. `pnpm test:e2e` starts the development server unless one is already running.
46
46
  - **Agent shell gate**: `.agents/hooks/agent-shell-gate.sh` blocks a shell command that writes to a protected path such as `.agents`, `.cursor`, `.claude/rules`, or `.husky`. It also blocks merging or approving a pull request, approving a deployment, and gh aliases, and asks the person before an irreversible git command such as a force push or a hard reset. It is a convenience, not a security boundary: CI and code-owner review are the boundary.
47
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` (`gh workflow run` or a workflow dispatch with `environment=prod`), a Railway CLI command that names `prod` with `-e`, `--environment`, or `railway environment`, and a release tag push, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it. It is a convenience, not a security boundary: the `prod` environment's required reviewers and Railway credentials are the boundary.
47
48
 
48
49
  ## Related documentation
49
50
 
@@ -49,7 +49,7 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
49
49
  - the `RAILWAY_SERVICE` variable: the service name;
50
50
  - the `APP_URL` variable: the public `https://` URL.
51
51
 
52
- Limit `dev`, `uat`, and `prod` to the `main` branch and add required reviewers. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
52
+ Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
53
53
 
54
54
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
55
55
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
@@ -41,6 +41,7 @@ If a test fails, fix the code, not the test.
41
41
  When Claude makes a mistake twice, add the correction here.
42
42
 
43
43
  - **Agent shell gate**: `.agents/hooks/agent-shell-gate.sh` blocks a shell command that writes to a protected path such as `.agents`, `.cursor`, `.claude/rules`, or `.husky`. It also blocks merging or approving a pull request, approving a deployment, and gh aliases, and asks the person before an irreversible git command such as a force push or a hard reset. It is a convenience, not a security boundary: CI and code-owner review are the boundary.
44
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` (`gh workflow run` or a workflow dispatch with `environment=prod`), a Railway CLI command that names `prod` with `-e`, `--environment`, or `railway environment`, and a release tag push, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it. It is a convenience, not a security boundary: the `prod` environment's required reviewers and Railway credentials are the boundary.
44
45
 
45
46
  ## Related documentation
46
47
 
@@ -49,7 +49,7 @@ docker run --env PORT=3000 --publish 3000:3000 app
49
49
  - the `RAILWAY_SERVICE` variable: the service name;
50
50
  - the `APP_URL` variable: the public `https://` URL.
51
51
 
52
- Limit `dev`, `uat`, and `prod` to the `main` branch and add required reviewers. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
52
+ Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
53
53
 
54
54
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
55
55
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
@@ -47,6 +47,7 @@ When Claude makes a mistake twice, add the correction here.
47
47
 
48
48
  - **Database for `pnpm dev` and end-to-end tests**: both need a PostgreSQL database in `DATABASE_URL` and `PAYLOAD_SECRET` in `.env`; the README lists every variable. Run `pnpm exec playwright install chromium` once before the first `pnpm test:e2e`.
49
49
  - **Agent shell gate**: `.agents/hooks/agent-shell-gate.sh` blocks a shell command that writes to a protected path such as `.agents`, `.cursor`, `.claude/rules`, or `.husky`. It also blocks merging or approving a pull request, approving a deployment, and gh aliases, and asks the person before an irreversible git command such as a force push or a hard reset. It is a convenience, not a security boundary: CI and code-owner review are the boundary.
50
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` (`gh workflow run` or a workflow dispatch with `environment=prod`), a Railway CLI command that names `prod` with `-e`, `--environment`, or `railway environment`, and a release tag push, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it. It is a convenience, not a security boundary: the `prod` environment's required reviewers and Railway credentials are the boundary.
50
51
 
51
52
  ## Related documentation
52
53
 
@@ -73,7 +73,7 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
73
73
  - the `RAILWAY_SERVICE` variable: the service name;
74
74
  - the `APP_URL` variable: the public `https://` URL.
75
75
 
76
- Limit `dev`, `uat`, and `prod` to the `main` branch and add required reviewers. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
76
+ Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
77
77
 
78
78
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
79
79
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
@@ -50,6 +50,7 @@ When Claude makes a mistake twice, add the correction here.
50
50
  - **Next.js docs live per app**: this Next.js version differs from your training data. Read the guide in `apps/app/node_modules/next/dist/docs/` or `apps/backoffice/node_modules/next/dist/docs/` before writing Next.js code; the workspace root has no `next` package.
51
51
  - **Database for `pnpm dev` and end-to-end tests**: the backoffice needs a PostgreSQL database in `DATABASE_URL` and `PAYLOAD_SECRET` in `.env`; the README lists every variable. Run `pnpm --filter ./apps/app exec playwright install chromium` once before the first `pnpm test:e2e`.
52
52
  - **Agent shell gate**: `.agents/hooks/agent-shell-gate.sh` blocks a shell command that writes to a protected path such as `.agents`, `.cursor`, `.claude/rules`, or `.husky`. It also blocks merging or approving a pull request, approving a deployment, and gh aliases, and asks the person before an irreversible git command such as a force push or a hard reset. It is a convenience, not a security boundary: CI and code-owner review are the boundary.
53
+ - **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` (`gh workflow run` or a workflow dispatch with `environment=prod`), a Railway CLI command that names `prod` with `-e`, `--environment`, or `railway environment`, and a release tag push, unless a release manager started the session with `RELEASE_APPROVAL` set. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it. It is a convenience, not a security boundary: the `prod` environment's required reviewers and Railway credentials are the boundary.
53
54
 
54
55
  ## Related documentation
55
56
 
@@ -79,7 +79,7 @@ On Vercel, deploy an app without its image. The Next.js preset takes precedence
79
79
  - the `APP_URL` variable: the public Next.js `https://` URL;
80
80
  - the `BACKOFFICE_URL` variable: the public backoffice `https://` URL.
81
81
 
82
- Limit `dev`, `uat`, and `prod` to the `main` branch and add required reviewers. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
82
+ Limit `dev`, `uat`, and `prod` to the `main` branch. Add the release managers as required reviewers on `prod` only; `dev` deploys on every push to `main`, so a reviewer there would hold each push. Environment secrets and variables in a private repository need GitHub Pro, Team, or Enterprise; required reviewers in a private repository need GitHub Enterprise.
83
83
 
84
84
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`. The workflow compares the commit with the last GitHub deployment to that environment and runs `railway up` only for services whose packages changed. A promotion through `uat` or `prod` uses the same comparison against the commit last deployed there, so several commits still ship every changed service. The first deploy to an environment ships both services.
85
85
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health` on each service it uploaded.