@next-friday/create-app 5.0.2 → 5.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/templates/base/.agents/hooks/agent-mcp-gate.sh +22 -0
  3. package/templates/base/.agents/hooks/agent-shell-gate.sh +160 -5
  4. package/templates/base/.agents/rules/agent-owned-work.md +14 -1
  5. package/templates/base/.agents/skills/babysit-pr/SKILL.md +1 -1
  6. package/templates/base/.agents/skills/git-workflow/SKILL.md +1 -1
  7. package/templates/base/.agents/skills/write-intent/SKILL.md +6 -5
  8. package/templates/base/.agents/skills/write-spec/SKILL.md +2 -2
  9. package/templates/base/.claude/settings.json +13 -1
  10. package/templates/base/.claude/skills/babysit-pr/SKILL.md +1 -1
  11. package/templates/base/.claude/skills/git-workflow/SKILL.md +1 -1
  12. package/templates/base/.claude/skills/write-intent/SKILL.md +6 -5
  13. package/templates/base/.claude/skills/write-spec/SKILL.md +2 -2
  14. package/templates/base/.cursor/hooks.json +7 -0
  15. package/templates/base/.cursor/rules/agent-owned-work.mdc +15 -2
  16. package/templates/base/.cursor/skills/babysit-pr/SKILL.md +1 -1
  17. package/templates/base/.cursor/skills/git-workflow/SKILL.md +1 -1
  18. package/templates/base/.cursor/skills/write-intent/SKILL.md +6 -5
  19. package/templates/base/.cursor/skills/write-spec/SKILL.md +2 -2
  20. package/templates/base/SETUP.md +1 -1
  21. package/templates/base/scripts/check-agent-gates.ts +171 -4
  22. package/templates/nestjs/.agents/rules.json +1 -1
  23. package/templates/nestjs/AGENTS.md +2 -1
  24. package/templates/nextjs/.agents/rules.json +1 -1
  25. package/templates/nextjs/AGENTS.md +3 -2
  26. package/templates/nodejs/.agents/rules.json +1 -1
  27. package/templates/nodejs/AGENTS.md +2 -1
  28. package/templates/payload/.agents/rules.json +1 -1
  29. package/templates/payload/AGENTS.md +3 -2
  30. package/templates/turborepo/.agents/rules.json +1 -1
  31. package/templates/turborepo/AGENTS.md +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next-friday/create-app",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "Create a Next Friday project from a template.",
5
5
  "keywords": [
6
6
  "create",
@@ -0,0 +1,22 @@
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-mcp-gate: %s\n' "$1" >&2
12
+ exit 2
13
+ }
14
+
15
+ trap 'block ".agents/hooks/agent-mcp-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
+ input=$(cat)
18
+ tool=$(jq -r '.tool_name // empty' <<< "$input")
19
+ if [[ $tool =~ (merge_pull_request|enable_pr_auto_merge|pull_request_review_write)$ ]]; then
20
+ block "A code owner approves a pull request and a person merges it. Do not approve or merge; report that the pull request waits on that review."
21
+ fi
22
+ allow
@@ -12,6 +12,13 @@ block() {
12
12
  exit 2
13
13
  }
14
14
 
15
+ irreversible=""
16
+
17
+ # Record the first irreversible command, so the gate asks the person once a block has been ruled out.
18
+ pause() {
19
+ [[ -n $irreversible ]] || irreversible=$1
20
+ }
21
+
15
22
  trap 'block ".agents/hooks/agent-shell-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
16
23
  command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
17
24
  input=$(cat)
@@ -23,6 +30,7 @@ redirect=$'\x1e'
23
30
  protected_names='(\.husky|\.agents|\.cursor|\.claude/rules|\.claude/settings(\.local)?\.json|\.git/hooks|\.git/config(\.worktree)?|\.lintstagedrc\.json|\.gitleaks\.toml|\.commitlintrc\.json|\.prettierrc\.json|prettier\.config\.ts|eslint\.config\.ts|scripts/(sync-agent-rules|check-agent-gates)\.ts)'
24
31
  protected="(^|[/=])$protected_names(/|$)"
25
32
  policy="Hooks and agent policy files are company policy; stop and ask the user."
33
+ human_gate="A code owner approves a pull request and a person merges it. Do not approve or merge; report that the pull request waits on that review."
26
34
  in_protected=0
27
35
 
28
36
  # Split a command line into words as the shell does: quotes and backslashes are removed, and
@@ -239,16 +247,143 @@ check_git() {
239
247
  ;;
240
248
  commit-tree | update-ref) block "git commit-tree and git update-ref are blocked, because they create commits without hooks. Use git commit." ;;
241
249
  checkout | restore | rm | mv | apply)
250
+ local staged=0 worktree=0
242
251
  for word in ${rest[@]+"${rest[@]}"}; do
243
252
  if [[ $word =~ $protected ]]; then
244
253
  writes_protected "$word"
245
254
  fi
255
+ [[ $word == --staged || $word =~ ^-[a-zA-Z]*S ]] && staged=1
256
+ [[ $word == --worktree || $word =~ ^-[a-zA-Z]*W ]] && worktree=1
246
257
  done
258
+ if [[ $subcommand =~ ^(checkout|restore)$ ]] && ! ((staged == 1 && worktree == 0)); then
259
+ for word in ${rest[@]+"${rest[@]}"}; do
260
+ case $word in
261
+ . | ./ | :/) pause "git $subcommand $word discards uncommitted changes." ;;
262
+ --force) [[ $subcommand == checkout ]] && pause "git checkout --force discards uncommitted changes." ;;
263
+ --*) ;;
264
+ -*f*) [[ $subcommand == checkout ]] && pause "git checkout $word discards uncommitted changes." ;;
265
+ esac
266
+ done
267
+ fi
268
+ ;;
269
+ switch)
270
+ for word in ${rest[@]+"${rest[@]}"}; do
271
+ case $word in
272
+ --discard-changes | --force | -f) pause "git switch $word discards uncommitted changes." ;;
273
+ esac
274
+ done
275
+ ;;
276
+ push)
277
+ local dry_run=0
278
+ for word in ${rest[@]+"${rest[@]}"}; do
279
+ [[ $word == --dry-run || $word =~ ^-[a-zA-Z]*n[a-zA-Z]*$ ]] && dry_run=1
280
+ done
281
+ if ((dry_run == 0)); then
282
+ for word in ${rest[@]+"${rest[@]}"}; do
283
+ case $word in
284
+ --force | --force=* | --mirror | --delete | --prune) pause "git push $word overwrites or deletes work on the remote." ;;
285
+ --*) ;;
286
+ -*[fd]*) pause "git push $word overwrites or deletes work on the remote." ;;
287
+ +* | :*) pause "git push $word overwrites or deletes work on the remote." ;;
288
+ esac
289
+ done
290
+ fi
291
+ ;;
292
+ reset)
293
+ for word in ${rest[@]+"${rest[@]}"}; do
294
+ [[ $word == --hard || $word == --merge ]] && pause "git reset $word discards uncommitted changes."
295
+ done
296
+ ;;
297
+ clean)
298
+ local dry_run=0
299
+ for word in ${rest[@]+"${rest[@]}"}; do
300
+ [[ $word == --dry-run || $word =~ ^-[a-zA-Z]*n ]] && dry_run=1
301
+ done
302
+ ((dry_run == 1)) || pause "git clean deletes untracked files."
303
+ ;;
304
+ branch)
305
+ local delete=0 force=0
306
+ for word in ${rest[@]+"${rest[@]}"}; do
307
+ case $word in
308
+ --delete) delete=1 ;;
309
+ --force) force=1 ;;
310
+ --*) ;;
311
+ -*D*) delete=1 force=1 ;;
312
+ -*)
313
+ [[ $word == *d* ]] && delete=1
314
+ [[ $word == *f* ]] && force=1
315
+ ;;
316
+ esac
317
+ done
318
+ ((delete == 1 && force == 1)) && pause "Force-deleting a branch loses commits that no other branch holds."
319
+ ;;
320
+ stash)
321
+ [[ ${rest[0]:-} =~ ^(drop|clear)$ ]] && pause "git stash ${rest[0]} deletes stashed work."
322
+ ;;
323
+ worktree)
324
+ if [[ ${rest[0]:-} == remove ]]; then
325
+ for word in "${rest[@]:1}"; do
326
+ [[ $word == --force || $word == -f ]] && pause "git worktree remove $word deletes uncommitted work in that worktree."
327
+ done
328
+ fi
247
329
  ;;
248
330
  esac
249
331
  return 0
250
332
  }
251
333
 
334
+ # Merging and approving are human gates. Read gh's words with their flags and flag values set aside.
335
+ check_gh() {
336
+ local -a args=(${@+"$@"}) positional=()
337
+ local count=${#args[@]} index=0 word
338
+ while ((index < count)); do
339
+ word=${args[index]}
340
+ case $word in
341
+ -R | --repo | --hostname | -b | --body | -F | --body-file | -t | --title | -B | --base | -H | --head | -X | --method | -f | --field | --raw-field | -H | --header | -q | --jq | --template | --input)
342
+ index=$((index + 2))
343
+ continue
344
+ ;;
345
+ -*) ;;
346
+ *) positional+=("$word") ;;
347
+ esac
348
+ index=$((index + 1))
349
+ done
350
+ local first=${positional[0]:-} second=${positional[1]:-}
351
+ if [[ $first =~ ^(repo|issue|release)$ && $second == delete ]]; then
352
+ block "gh $first delete is blocked. Stop and ask the user."
353
+ fi
354
+ [[ $first == alias && $second =~ ^(set|import)$ ]] && block "gh aliases are blocked, because an alias can hide a merge or an approval. Run the gh command directly."
355
+ [[ $first == pr && $second == merge ]] && block "$human_gate"
356
+ if [[ $first == pr && $second == review ]]; then
357
+ index=0
358
+ while ((index < count)); do
359
+ word=${args[index]}
360
+ case $word in
361
+ -b | --body | -F | --body-file | -R | --repo) index=$((index + 1)) ;;
362
+ --approve | --approve=*) block "$human_gate" ;;
363
+ --*) ;;
364
+ -*a*) block "$human_gate" ;;
365
+ esac
366
+ index=$((index + 1))
367
+ done
368
+ fi
369
+ if [[ $first == api ]]; then
370
+ local joined=" ${args[*]} "
371
+ if [[ $joined =~ pulls/[^/[:space:]]+/merge/?[[:space:]?] || $joined =~ repos/[^/[:space:]]+/[^/[:space:]]+/merges[/[:space:]?] || $joined == *mergePullRequest* || $joined == *enablePullRequestAutoMerge* || $joined == *enqueuePullRequest* || $joined =~ =[\"\']?APPROVE([^A-Za-z_]|$) || $joined =~ event[\"\']?:[[:space:]]*[\"\']?APPROVE ]]; then
372
+ block "$human_gate"
373
+ fi
374
+ if [[ $joined == *pending_deployments* && $joined =~ state=[\"\']?approved ]]; then
375
+ block "A release manager approves a deployment. Do not approve it; report that the deployment waits on that approval."
376
+ fi
377
+ if [[ $joined =~ (query|event|state)=@ ]]; then
378
+ block "gh api reading a query, a review event, or a deployment state from a file is blocked, because the gate cannot read it. Pass the value inline."
379
+ fi
380
+ if [[ $joined =~ [[:space:]]--input([[:space:]]|=) && ($joined =~ [[:space:]]graphql[[:space:]] || $joined =~ pulls/[^/[:space:]]+/(reviews|merge) || $joined == *pending_deployments*) ]]; then
381
+ block "gh api reading this request body from a file or stdin is blocked, because the gate cannot read it. Pass the fields inline."
382
+ fi
383
+ fi
384
+ return 0
385
+ }
386
+
252
387
  check_command() {
253
388
  local -a args=()
254
389
  local word name is_writer=0
@@ -288,17 +423,28 @@ check_command() {
288
423
  done
289
424
  ((${#args[@]} > 0)) || return 0
290
425
  name=${args[0]##*/}
426
+ # A wrapper such as timeout, xargs, or find -exec runs the command named after it.
427
+ case $name in
428
+ git | gh | bash | sh | zsh | dash | ksh | eval) ;;
429
+ *)
430
+ for ((index = 1; index < ${#args[@]}; index++)); do
431
+ case ${args[index]##*/} in
432
+ git | gh | bash | sh | zsh | dash | ksh | eval)
433
+ check_command "${args[@]:index}"
434
+ break
435
+ ;;
436
+ esac
437
+ done
438
+ ;;
439
+ esac
291
440
 
292
441
  case $name in
293
442
  git) check_git "${args[@]:1}" ;;
294
- gh)
295
- if [[ ${args[1]:-} =~ ^(repo|issue|release)$ && ${args[2]:-} == delete ]]; then
296
- block "gh ${args[1]} delete is blocked. Stop and ask the user."
297
- fi
298
- ;;
443
+ gh) check_gh "${args[@]:1}" ;;
299
444
  bash | sh | zsh | dash | ksh)
300
445
  for ((index = 1; index < ${#args[@]}; index++)); do
301
446
  if [[ ${args[index]} =~ ^-[a-zA-Z]*c[a-zA-Z]*$ ]]; then
447
+ [[ ${args[index + 1]:-} == -- ]] && index=$((index + 1))
302
448
  gate_line "${args[index + 1]:-}"
303
449
  break
304
450
  fi
@@ -363,4 +509,13 @@ gate_line() {
363
509
  }
364
510
 
365
511
  gate_line "$command"
512
+ if [[ -n $irreversible ]]; then
513
+ message="$irreversible It cannot be undone, so ask the person before running it."
514
+ if [[ $(jq -r '.hook_event_name // empty' <<< "$input") == PreToolUse ]]; then
515
+ jq -n --arg message "$message" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:$message}}'
516
+ else
517
+ jq -n --arg message "$message" '{permission:"ask",user_message:$message,agent_message:$message}'
518
+ fi
519
+ exit 0
520
+ fi
366
521
  allow
@@ -1,3 +1,16 @@
1
1
  # Agent-Owned Work
2
2
 
3
- Do the work that an agent, a script, or an API can do; do not hand it to the user. Before you ask the user, check the code, data, docs, and tools that already exist. Ask only for what no one else can provide, such as payment, legal identity, or a business fact recorded nowhere. Constitution edits follow `constitution-changes.md`.
3
+ Find facts yourself, and do every step an agent, a script, or an API can do; never ask a person to do it.
4
+
5
+ Work starts only when a person asks for it or a trigger runs it. Answer a question or a statement without turning it into work. Once a task starts, carry it to the next human gate without asking.
6
+
7
+ The human gates:
8
+
9
+ - The originator or the product owner corrects an intent before it is committed.
10
+ - The product owner accepts an intent or a spec.
11
+ - An engineer approves a plan.
12
+ - A code owner approves a pull request, and a person merges it.
13
+ - A release manager approves a production release.
14
+ - A service owner triages a finding.
15
+
16
+ A decision that no file and no earlier answer settles belongs to a person: write it into the artifact as a proposal they correct at the next gate, or ask them when the work cannot continue without it. Pause before an irreversible action, such as a force push other than `--force-with-lease` on your own branch, a hard reset, or deleting work. Constitution edits follow `constitution-changes.md`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: babysit-pr
3
- description: Drive a pull request you opened until it waits only on code owner approval. Use when asked to babysit, watch, or finish a pull request.
3
+ description: Drive a pull request you opened until it waits only on code owner approval. Use right after you open a pull request, or when a person asks you to babysit one.
4
4
  ---
5
5
 
6
6
  # Babysit a pull request
@@ -11,5 +11,5 @@ The repository uses trunk-based development.
11
11
  - Write commit messages in the format that `@next-friday/commitlint-config-friday` defines. The `commit-msg` hook rejects anything else.
12
12
  - When work ends in a pull request, push its branch and open the pull request ready for review without asking. CI skips drafts. Work that does not end in one, such as an investigation or a prototype, opens none.
13
13
  - Fill the pull request body from `.github/pull_request_template.md`.
14
- - After you open a pull request, report its URL and continue. Drive it to merge-ready only when the user asks, and watch the checks with `gh pr checks --watch`. Merge only when the user asks to merge, land, or ship. Merge with `--squash`, bottom-up for a stack.
14
+ - After you open a pull request, report its URL and drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Never approve or merge it: a code owner approves, and a person merges with `--squash`, bottom-up for a stack.
15
15
  - Rebase and `git push --force-with-lease` only branches you own.
@@ -1,15 +1,16 @@
1
1
  ---
2
2
  name: write-intent
3
- description: Write intent.md, the proto-spec that starts a change. Use whenever someone describes an idea, a problem, or a change they want, before a spec or code exists.
3
+ description: Write intent.md, the proto-spec that starts a change, by interviewing the originator. Run it with /write-intent.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # Write intent.md
7
8
 
8
9
  1. Let the originator describe the problem in their own words: what they cannot do today, who is affected, what better looks like, and what is out of scope.
9
- 2. Brainstorm until the idea is concrete. Ask the questions an analyst would ask: scope, users, constraints, and what success looks like.
10
- 3. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below.
11
- 4. Ask the originator to correct anything you misunderstood.
12
- 5. Commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
10
+ 2. Interview them in rounds until no decision is left to guess. Each round, ask every question whose prerequisites are already settled, numbered, each with your recommended answer, worded so "yes" accepts it. Then wait for the answers, and build the next round on them. Aim at the problem (who, how often, and a number with its source), the outcome as the user would see it, the constraints, and what is out of scope.
11
+ 3. Find facts yourself: read the code, the docs, and the existing `intent/` folders before you ask, and never ask what a file can tell you. Put only decisions to the originator. When they cannot answer one yet, record it as an open question instead of guessing.
12
+ 4. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below. Show the whole draft, and ask whether it matches what they meant. Revise it until they say it does.
13
+ 5. When the originator confirms the draft, commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
13
14
 
14
15
  ## Template
15
16
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: write-spec
3
- description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when an intent.md has been accepted and the change needs its spec.
3
+ description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when a person or a trigger asks for the spec of an accepted intent.md.
4
4
  ---
5
5
 
6
6
  # Write spec.md
@@ -9,4 +9,4 @@ Read the `intent.md` the user names, and follow this prompt:
9
9
 
10
10
  > Read the attached intent.md and produce a requirements and design spec for integrating it into our existing codebase. Apply the skills available to you so the plan conforms to our brand guidelines, security policies and UX standards. Document the spec fully as spec.md, ready to hand to the engineering team. Describe clearly any areas of concern, especially where you cannot satisfy contradicting policies.
11
11
 
12
- Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward.
12
+ Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward. Commit it and open a pull request; the product owner accepts the spec by merging it.
@@ -41,7 +41,8 @@
41
41
  "Bash(git *core.hookspath*)",
42
42
  "Bash(git commit-tree*)",
43
43
  "Bash(git update-ref*)",
44
- "Bash(*HUSKY=0*)"
44
+ "Bash(*HUSKY=0*)",
45
+ "Bash(gh pr merge*)"
45
46
  ]
46
47
  },
47
48
  "hooks": {
@@ -78,6 +79,17 @@
78
79
  "timeout": 10
79
80
  }
80
81
  ]
82
+ },
83
+ {
84
+ "matcher": "mcp__.*__(merge_pull_request|enable_pr_auto_merge|pull_request_review_write)",
85
+ "hooks": [
86
+ {
87
+ "type": "command",
88
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.agents/hooks/agent-mcp-gate.sh",
89
+ "timeout": 10,
90
+ "onFailure": "block"
91
+ }
92
+ ]
81
93
  }
82
94
  ],
83
95
  "PostToolUse": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: babysit-pr
3
- description: Drive a pull request you opened until it waits only on code owner approval. Use when asked to babysit, watch, or finish a pull request.
3
+ description: Drive a pull request you opened until it waits only on code owner approval. Use right after you open a pull request, or when a person asks you to babysit one.
4
4
  ---
5
5
 
6
6
  # Babysit a pull request
@@ -11,5 +11,5 @@ The repository uses trunk-based development.
11
11
  - Write commit messages in the format that `@next-friday/commitlint-config-friday` defines. The `commit-msg` hook rejects anything else.
12
12
  - When work ends in a pull request, push its branch and open the pull request ready for review without asking. CI skips drafts. Work that does not end in one, such as an investigation or a prototype, opens none.
13
13
  - Fill the pull request body from `.github/pull_request_template.md`.
14
- - After you open a pull request, report its URL and continue. Drive it to merge-ready only when the user asks, and watch the checks with `gh pr checks --watch`. Merge only when the user asks to merge, land, or ship. Merge with `--squash`, bottom-up for a stack.
14
+ - After you open a pull request, report its URL and drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Never approve or merge it: a code owner approves, and a person merges with `--squash`, bottom-up for a stack.
15
15
  - Rebase and `git push --force-with-lease` only branches you own.
@@ -1,15 +1,16 @@
1
1
  ---
2
2
  name: write-intent
3
- description: Write intent.md, the proto-spec that starts a change. Use whenever someone describes an idea, a problem, or a change they want, before a spec or code exists.
3
+ description: Write intent.md, the proto-spec that starts a change, by interviewing the originator. Run it with /write-intent.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # Write intent.md
7
8
 
8
9
  1. Let the originator describe the problem in their own words: what they cannot do today, who is affected, what better looks like, and what is out of scope.
9
- 2. Brainstorm until the idea is concrete. Ask the questions an analyst would ask: scope, users, constraints, and what success looks like.
10
- 3. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below.
11
- 4. Ask the originator to correct anything you misunderstood.
12
- 5. Commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
10
+ 2. Interview them in rounds until no decision is left to guess. Each round, ask every question whose prerequisites are already settled, numbered, each with your recommended answer, worded so "yes" accepts it. Then wait for the answers, and build the next round on them. Aim at the problem (who, how often, and a number with its source), the outcome as the user would see it, the constraints, and what is out of scope.
11
+ 3. Find facts yourself: read the code, the docs, and the existing `intent/` folders before you ask, and never ask what a file can tell you. Put only decisions to the originator. When they cannot answer one yet, record it as an open question instead of guessing.
12
+ 4. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below. Show the whole draft, and ask whether it matches what they meant. Revise it until they say it does.
13
+ 5. When the originator confirms the draft, commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
13
14
 
14
15
  ## Template
15
16
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: write-spec
3
- description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when an intent.md has been accepted and the change needs its spec.
3
+ description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when a person or a trigger asks for the spec of an accepted intent.md.
4
4
  ---
5
5
 
6
6
  # Write spec.md
@@ -9,4 +9,4 @@ Read the `intent.md` the user names, and follow this prompt:
9
9
 
10
10
  > Read the attached intent.md and produce a requirements and design spec for integrating it into our existing codebase. Apply the skills available to you so the plan conforms to our brand guidelines, security policies and UX standards. Document the spec fully as spec.md, ready to hand to the engineering team. Describe clearly any areas of concern, especially where you cannot satisfy contradicting policies.
11
11
 
12
- Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward.
12
+ Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward. Commit it and open a pull request; the product owner accepts the spec by merging it.
@@ -32,6 +32,13 @@
32
32
  "timeout": 10
33
33
  }
34
34
  ],
35
+ "beforeMCPExecution": [
36
+ {
37
+ "command": ".agents/hooks/agent-mcp-gate.sh",
38
+ "timeout": 10,
39
+ "failClosed": true
40
+ }
41
+ ],
35
42
  "afterFileEdit": [
36
43
  {
37
44
  "command": ".agents/hooks/agent-format-after-edit.sh"
@@ -1,8 +1,21 @@
1
1
  ---
2
- description: "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
2
+ description: "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
3
3
  alwaysApply: true
4
4
  ---
5
5
 
6
6
  # Agent-Owned Work
7
7
 
8
- Do the work that an agent, a script, or an API can do; do not hand it to the user. Before you ask the user, check the code, data, docs, and tools that already exist. Ask only for what no one else can provide, such as payment, legal identity, or a business fact recorded nowhere. Constitution edits follow `constitution-changes.md`.
8
+ Find facts yourself, and do every step an agent, a script, or an API can do; never ask a person to do it.
9
+
10
+ Work starts only when a person asks for it or a trigger runs it. Answer a question or a statement without turning it into work. Once a task starts, carry it to the next human gate without asking.
11
+
12
+ The human gates:
13
+
14
+ - The originator or the product owner corrects an intent before it is committed.
15
+ - The product owner accepts an intent or a spec.
16
+ - An engineer approves a plan.
17
+ - A code owner approves a pull request, and a person merges it.
18
+ - A release manager approves a production release.
19
+ - A service owner triages a finding.
20
+
21
+ A decision that no file and no earlier answer settles belongs to a person: write it into the artifact as a proposal they correct at the next gate, or ask them when the work cannot continue without it. Pause before an irreversible action, such as a force push other than `--force-with-lease` on your own branch, a hard reset, or deleting work. Constitution edits follow `constitution-changes.md`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: babysit-pr
3
- description: Drive a pull request you opened until it waits only on code owner approval. Use when asked to babysit, watch, or finish a pull request.
3
+ description: Drive a pull request you opened until it waits only on code owner approval. Use right after you open a pull request, or when a person asks you to babysit one.
4
4
  ---
5
5
 
6
6
  # Babysit a pull request
@@ -11,5 +11,5 @@ The repository uses trunk-based development.
11
11
  - Write commit messages in the format that `@next-friday/commitlint-config-friday` defines. The `commit-msg` hook rejects anything else.
12
12
  - When work ends in a pull request, push its branch and open the pull request ready for review without asking. CI skips drafts. Work that does not end in one, such as an investigation or a prototype, opens none.
13
13
  - Fill the pull request body from `.github/pull_request_template.md`.
14
- - After you open a pull request, report its URL and continue. Drive it to merge-ready only when the user asks, and watch the checks with `gh pr checks --watch`. Merge only when the user asks to merge, land, or ship. Merge with `--squash`, bottom-up for a stack.
14
+ - After you open a pull request, report its URL and drive it until it is green and waits only on code owner approval, as `babysit-pr` describes. Never approve or merge it: a code owner approves, and a person merges with `--squash`, bottom-up for a stack.
15
15
  - Rebase and `git push --force-with-lease` only branches you own.
@@ -1,15 +1,16 @@
1
1
  ---
2
2
  name: write-intent
3
- description: Write intent.md, the proto-spec that starts a change. Use whenever someone describes an idea, a problem, or a change they want, before a spec or code exists.
3
+ description: Write intent.md, the proto-spec that starts a change, by interviewing the originator. Run it with /write-intent.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # Write intent.md
7
8
 
8
9
  1. Let the originator describe the problem in their own words: what they cannot do today, who is affected, what better looks like, and what is out of scope.
9
- 2. Brainstorm until the idea is concrete. Ask the questions an analyst would ask: scope, users, constraints, and what success looks like.
10
- 3. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below.
11
- 4. Ask the originator to correct anything you misunderstood.
12
- 5. Commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
10
+ 2. Interview them in rounds until no decision is left to guess. Each round, ask every question whose prerequisites are already settled, numbered, each with your recommended answer, worded so "yes" accepts it. Then wait for the answers, and build the next round on them. Aim at the problem (who, how often, and a number with its source), the outcome as the user would see it, the constraints, and what is out of scope.
11
+ 3. Find facts yourself: read the code, the docs, and the existing `intent/` folders before you ask, and never ask what a file can tell you. Put only decisions to the originator. When they cannot answer one yet, record it as an open question instead of guessing.
12
+ 4. Write the result as `intent/<change>/intent.md`, where `<change>` is a short kebab-case name, using the template below. Show the whole draft, and ask whether it matches what they meant. Revise it until they say it does.
13
+ 5. When the originator confirms the draft, commit `intent.md` and open a pull request. The product owner accepts it by merging the pull request, or rejects it by setting `Status: rejected.` and merging.
13
14
 
14
15
  ## Template
15
16
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: write-spec
3
- description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when an intent.md has been accepted and the change needs its spec.
3
+ description: Produce spec.md, the requirements and design spec, from an accepted intent.md. Use when a person or a trigger asks for the spec of an accepted intent.md.
4
4
  ---
5
5
 
6
6
  # Write spec.md
@@ -9,4 +9,4 @@ Read the `intent.md` the user names, and follow this prompt:
9
9
 
10
10
  > Read the attached intent.md and produce a requirements and design spec for integrating it into our existing codebase. Apply the skills available to you so the plan conforms to our brand guidelines, security policies and UX standards. Document the spec fully as spec.md, ready to hand to the engineering team. Describe clearly any areas of concern, especially where you cannot satisfy contradicting policies.
11
11
 
12
- Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward.
12
+ Write `spec.md` alongside that `intent.md`, in the same folder. Answer each open question from `intent.md`, or carry it forward. Commit it and open a pull request; the product owner accepts the spec by merging it.
@@ -4,7 +4,7 @@ This project follows Anthropic's [AI-native SDLC Playbook](https://claude.com/re
4
4
 
5
5
  ## How a change flows
6
6
 
7
- 1. **Plan:** an idea, a ticket, or an incident becomes `intent/<change>/intent.md` (`write-intent` skill). The product owner merges it with `Status: accepted.` or `Status: rejected.`
7
+ 1. **Plan:** the originator of an idea, a ticket, or an incident runs `/write-intent`, which interviews them and writes `intent/<change>/intent.md`. The product owner merges it with `Status: accepted.` or `Status: rejected.`
8
8
  2. **Design:** `spec.md` is written from the accepted intent with the policy skills loaded (`write-spec` skill). The product owner resolves concerns with the policy owners.
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`.
@@ -2,6 +2,7 @@ import {spawnSync} from "node:child_process";
2
2
 
3
3
  type GateCase = {
4
4
  expectedExit: 0 | 2;
5
+ expectedPermission?: "allow" | "ask";
5
6
  gate: string;
6
7
  input: Record<string, unknown>;
7
8
  name: string;
@@ -10,6 +11,7 @@ type GateCase = {
10
11
  const shellGate = ".agents/hooks/agent-shell-gate.sh";
11
12
  const suppressionGate = ".agents/hooks/agent-suppression-gate.sh";
12
13
  const constitutionGate = ".cursor/hooks/agent-constitution-gate.sh";
14
+ const mcpGate = ".agents/hooks/agent-mcp-gate.sh";
13
15
  const sourceFile = "src/example.ts";
14
16
 
15
17
  const blockedCommands = [
@@ -80,6 +82,68 @@ const blockedCommands = [
80
82
  "while true; do git commit -n -m x; done",
81
83
  "if git commit -n -m x; then true; fi",
82
84
  "printf x > .git/config.worktree",
85
+ "gh pr merge 1 --squash",
86
+ "gh pr merge 1 --auto",
87
+ "gh -R next-friday/example pr merge 1",
88
+ "gh pr -R next-friday/example merge 1",
89
+ "timeout 5 gh pr merge 1",
90
+ "nice -n 5 gh pr merge 1",
91
+ "xargs gh pr merge",
92
+ "gh pr review 1 --approve",
93
+ "gh pr review 1 --approve=true",
94
+ "gh pr review 1 -a",
95
+ "gh -R next-friday/example pr review 1 -a",
96
+ "gh alias set m 'pr merge'",
97
+ "gh api repos/next-friday/example/pulls/1/merge -X PUT",
98
+ "gh api graphql -f query='mutation { mergePullRequest(input: {}) { clientMutationId } }'",
99
+ "gh api graphql -f query=@query.graphql",
100
+ "gh api repos/next-friday/example/pulls/1/reviews -f event=APPROVE",
101
+ "gh api -X POST repos/next-friday/example/actions/runs/1/pending_deployments -f state=approved",
102
+ "gh pr review 1 -ca",
103
+ "gh alias import aliases.yml",
104
+ "gh api graphql -Fquery=@query.graphql",
105
+ "gh api graphql --input query.json",
106
+ "gh api -X POST repos/next-friday/example/pulls/1/reviews --input review.json",
107
+ "gh api repos/next-friday/example/actions/runs/1/pending_deployments -F state=@state.txt",
108
+ "gh api graphql -f query='mutation { enablePullRequestAutoMerge(input: {}) { clientMutationId } }'",
109
+ "gh api graphql -f query='mutation { enqueuePullRequest(input: {}) { clientMutationId } }'",
110
+ "gh api graphql -f query='mutation($e: PullRequestReviewEvent!) { addPullRequestReview(input: {event: $e}) { clientMutationId } }' -f e=APPROVE",
111
+ "gh api -X POST repos/next-friday/example/merges -f base=main -f head=feature",
112
+ "gh api -X PUT repos/next-friday/example/pulls/1/merge/",
113
+ "timeout 5 bash -c 'gh pr merge 1'",
114
+ "find . -exec sh -c 'gh pr merge 1' ;",
115
+ "sudo -u me bash -c 'gh pr merge 1'",
116
+ "bash -c -- 'gh pr merge 1'",
117
+ ];
118
+
119
+ const irreversibleCommands = [
120
+ "git push --force",
121
+ "git push -f origin x",
122
+ "git push origin x -f",
123
+ "git push -uf origin x",
124
+ "git push origin +x",
125
+ "git push origin :x",
126
+ "git push -d origin x",
127
+ "git push --delete origin x",
128
+ "git push --mirror",
129
+ "git reset --hard HEAD~1",
130
+ "git -C . reset --hard",
131
+ "git clean -fd",
132
+ "git clean -xdf",
133
+ "git branch -D x",
134
+ "git branch -df x",
135
+ "git branch --delete --force x",
136
+ "git checkout .",
137
+ "git checkout -- .",
138
+ "git checkout -f main",
139
+ "git restore .",
140
+ "git restore --worktree .",
141
+ "git switch --discard-changes main",
142
+ "git stash drop",
143
+ "git worktree remove --force x",
144
+ "git restore :/",
145
+ "timeout 5 git push --force",
146
+ "xargs git reset --hard",
83
147
  ];
84
148
 
85
149
  const allowedCommands = [
@@ -104,8 +168,37 @@ const allowedCommands = [
104
168
  'gh pr create --title "ci(x): remove HUSKY=0 usage"',
105
169
  "git push -n",
106
170
  "node scripts/check-agent-gates.ts",
171
+ "git push -u origin x",
172
+ "git push --dry-run --force",
173
+ "git push --force-with-lease origin x",
174
+ 'gh pr create --title "fix(x): y" --body z',
175
+ "gh pr comment 1 --body x",
176
+ 'gh pr review 1 -c -b "fix -a flag"',
177
+ 'gh api repos/next-friday/example/issues/1/comments -f body="Please APPROVE"',
178
+ "gh api repos/next-friday/example/pulls/1",
179
+ "gh pr checks 1 --watch",
180
+ "git branch -d x",
181
+ "git checkout feature",
182
+ "git checkout .changeset/x.md",
183
+ "git restore --staged .",
184
+ "git clean -n",
185
+ "git reset --soft HEAD~1",
186
+ "git stash pop",
187
+ "timeout 60 pnpm test",
188
+ 'gh api graphql -f query="query { viewer { login } }"',
189
+ 'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
107
190
  ];
108
191
 
192
+ const mcpTools = {
193
+ blocked: [
194
+ "mcp__github__merge_pull_request",
195
+ "github__merge_pull_request",
196
+ "github__enable_pr_auto_merge",
197
+ "github__pull_request_review_write",
198
+ ],
199
+ allowed: ["github__create_pull_request", "github__add_issue_comment"],
200
+ };
201
+
109
202
  const protectedPaths = [
110
203
  ".agents/rules/code-comments.md",
111
204
  ".agents/rules.json",
@@ -148,14 +241,57 @@ const cases: GateCase[] = [
148
241
  name: `Claude Bash: ${command}`,
149
242
  },
150
243
  ]),
244
+ ...irreversibleCommands.flatMap(command => [
245
+ {
246
+ gate: shellGate,
247
+ input: {
248
+ command,
249
+ },
250
+ expectedExit: 0 as const,
251
+ expectedPermission: "ask" as const,
252
+ name: `Cursor shell asks first: ${command}`,
253
+ },
254
+ {
255
+ gate: shellGate,
256
+ input: {
257
+ hook_event_name: "PreToolUse",
258
+ tool_input: {
259
+ command,
260
+ },
261
+ },
262
+ expectedExit: 0 as const,
263
+ expectedPermission: "ask" as const,
264
+ name: `Claude Bash asks first: ${command}`,
265
+ },
266
+ ]),
151
267
  ...allowedCommands.map(command => ({
152
268
  gate: shellGate,
153
269
  input: {
154
270
  command,
155
271
  },
156
272
  expectedExit: 0 as const,
273
+ expectedPermission: "allow" as const,
157
274
  name: `shell: ${command}`,
158
275
  })),
276
+ ...mcpTools.blocked.map(tool => ({
277
+ gate: mcpGate,
278
+ input: {
279
+ tool_name: tool,
280
+ tool_input: "{}",
281
+ },
282
+ expectedExit: 2 as const,
283
+ name: `MCP: ${tool}`,
284
+ })),
285
+ ...mcpTools.allowed.map(tool => ({
286
+ gate: mcpGate,
287
+ input: {
288
+ tool_name: tool,
289
+ tool_input: "{}",
290
+ },
291
+ expectedExit: 0 as const,
292
+ expectedPermission: "allow" as const,
293
+ name: `MCP: ${tool}`,
294
+ })),
159
295
  {
160
296
  gate: suppressionGate,
161
297
  name: "suppression: eslint-disable",
@@ -235,6 +371,28 @@ const cases: GateCase[] = [
235
371
  },
236
372
  ];
237
373
 
374
+ /**
375
+ * Reads the permission a gate printed in the format its tool reads: Claude Code's
376
+ * `hookSpecificOutput` for a `PreToolUse` input, and Cursor's `permission` otherwise.
377
+ * @param gateCase - The case whose input decides the format.
378
+ * @param stdout - The gate's standard output.
379
+ * @returns The permission, or undefined when the output is not JSON.
380
+ */
381
+ function permissionOf(gateCase: GateCase, stdout: string): string | undefined {
382
+ try {
383
+ const output = JSON.parse(stdout) as {
384
+ hookSpecificOutput?: {permissionDecision?: string};
385
+ permission?: string;
386
+ };
387
+
388
+ return gateCase.input.hook_event_name === "PreToolUse" && gateCase.expectedPermission === "ask"
389
+ ? output.hookSpecificOutput?.permissionDecision
390
+ : output.permission;
391
+ } catch {
392
+ return undefined;
393
+ }
394
+ }
395
+
238
396
  const failures = cases.filter(gateCase => {
239
397
  const result = spawnSync(gateCase.gate, {
240
398
  env: {
@@ -245,13 +403,22 @@ const failures = cases.filter(gateCase => {
245
403
  input: JSON.stringify(gateCase.input),
246
404
  });
247
405
 
248
- return result.status !== gateCase.expectedExit;
406
+ const isPermissionWrong =
407
+ gateCase.expectedPermission !== undefined &&
408
+ permissionOf(gateCase, result.stdout.toString()) !== gateCase.expectedPermission;
409
+
410
+ return result.status !== gateCase.expectedExit || isPermissionWrong;
249
411
  });
250
412
 
251
413
  if (failures.length > 0) {
252
- const failureLines = failures.map(
253
- gateCase => `- ${gateCase.name} (expected exit ${gateCase.expectedExit})`,
254
- );
414
+ const failureLines = failures.map(gateCase => {
415
+ const permission =
416
+ gateCase.expectedPermission === undefined
417
+ ? ""
418
+ : ` and permission ${gateCase.expectedPermission}`;
419
+
420
+ return `- ${gateCase.name} (expected exit ${gateCase.expectedExit}${permission})`;
421
+ });
255
422
 
256
423
  process.stderr.write(`Agent gate checks failed:\n${failureLines.join("\n")}\n`);
257
424
  process.exitCode = 1;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "agent-owned-work": {
3
- "description": "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
3
+ "description": "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
4
4
  },
5
5
  "automation-owned-checks": {
6
6
  "description": "Owned checks: hooks and CI define what must pass; run checks by hand to verify"
@@ -36,12 +36,13 @@ If a test fails, fix the code, not the test.
36
36
  4. **Log through Nest's `Logger`.** It goes to the `ConsoleLogger` set in `src/main.ts`.
37
37
  5. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
38
38
  6. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
39
+ 7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
39
40
 
40
41
  ## Things Claude gets wrong
41
42
 
42
43
  When Claude makes a mistake twice, add the correction here.
43
44
 
44
- - **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 is a convenience, not a security boundary: CI and code-owner review are the boundary.
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.
45
46
 
46
47
  ## Related documentation
47
48
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "agent-owned-work": {
3
- "description": "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
3
+ "description": "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
4
4
  },
5
5
  "automation-owned-checks": {
6
6
  "description": "Owned checks: hooks and CI define what must pass; run checks by hand to verify"
@@ -32,17 +32,18 @@ If a test fails, fix the code, not the test.
32
32
 
33
33
  1. **Follow `.agents/rules/`.** `.agents/rules.json` gives each rule's scope; Claude Code and Cursor load them. After changing either, or a skill or subagent in `.agents/skills/` or `.agents/agents/`, run `pnpm rules:sync`; `pnpm check` fails on stale copies.
34
34
  2. **Put unit tests next to the code as `app/**/*.test.{ts,tsx}` and Playwright end-to-end tests in `test/` as `*.e2e.test.ts`.** Vitest collects only the first pattern.
35
- 3. **Ask the user before adding component tests.** They need a DOM test environment, and Vitest runs in Node.js.
35
+ 3. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
36
36
  4. **Read configuration from environment variables and fail when one is missing.** Document each variable in `.env.example`.
37
37
  5. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
38
38
  6. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
39
+ 7. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
39
40
 
40
41
  ## Things Claude gets wrong
41
42
 
42
43
  When Claude makes a mistake twice, add the correction here.
43
44
 
44
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.
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 is a convenience, not a security boundary: CI and code-owner review are the boundary.
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.
46
47
 
47
48
  ## Related documentation
48
49
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "agent-owned-work": {
3
- "description": "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
3
+ "description": "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
4
4
  },
5
5
  "automation-owned-checks": {
6
6
  "description": "Owned checks: hooks and CI define what must pass; run checks by hand to verify"
@@ -34,12 +34,13 @@ If a test fails, fix the code, not the test.
34
34
  3. **Read configuration from environment variables and fail when one is missing**, as `parsePort` does. Document each variable in `.env.example`; `pnpm start` and `pnpm start:dev` load `.env`.
35
35
  4. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
36
36
  5. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
37
+ 6. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
37
38
 
38
39
  ## Things Claude gets wrong
39
40
 
40
41
  When Claude makes a mistake twice, add the correction here.
41
42
 
42
- - **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 is a convenience, not a security boundary: CI and code-owner review are the boundary.
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.
43
44
 
44
45
  ## Related documentation
45
46
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "agent-owned-work": {
3
- "description": "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
3
+ "description": "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
4
4
  },
5
5
  "automation-owned-checks": {
6
6
  "description": "Owned checks: hooks and CI define what must pass; run checks by hand to verify"
@@ -35,17 +35,18 @@ If a test fails, fix the code, not the test.
35
35
  1. **Follow `.agents/rules/`.** `.agents/rules.json` gives each rule's scope; Claude Code and Cursor load them. After changing either, or a skill or subagent in `.agents/skills/` or `.agents/agents/`, run `pnpm rules:sync`; `pnpm check` fails on stale copies.
36
36
  2. **Change the schema only through collections, then regenerate and commit.** Payload generates `src/app/(payload)/`, `src/payload-types.ts`, and `src/migrations/`; run `pnpm generate:types`, `pnpm generate:importmap`, and `pnpm migrate:create <name>`. Do not edit those files by hand.
37
37
  3. **Put unit tests next to the code as `src/**/*.test.{ts,tsx}` and Playwright end-to-end tests in `test/` as `*.e2e.test.ts`.** Vitest collects only the first pattern.
38
- 4. **Ask the user before adding component tests.** They need a DOM test environment, and Vitest runs in Node.js.
38
+ 4. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
39
39
  5. **Read configuration through `requireEnvironment` in `src/environment.ts`.** Document each variable in `.env.example`.
40
40
  6. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
41
41
  7. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
42
+ 8. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
42
43
 
43
44
  ## Things Claude gets wrong
44
45
 
45
46
  When Claude makes a mistake twice, add the correction here.
46
47
 
47
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`.
48
- - **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 is a convenience, not a security boundary: CI and code-owner review are the boundary.
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.
49
50
 
50
51
  ## Related documentation
51
52
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "agent-owned-work": {
3
- "description": "Agent-owned work: do what an agent, script, or API can do instead of asking the user"
3
+ "description": "Agent-owned work: do every step an agent can do, start only when asked, and stop at the human gates"
4
4
  },
5
5
  "automation-owned-checks": {
6
6
  "description": "Owned checks: hooks and CI define what must pass; run checks by hand to verify"
@@ -37,10 +37,11 @@ If a test fails, fix the code, not the test.
37
37
  1. **Follow `.agents/rules/`.** `.agents/rules.json` gives each rule's scope; Claude Code and Cursor load them. After changing either, or a skill or subagent in `.agents/skills/` or `.agents/agents/`, run `pnpm rules:sync`; `pnpm check` fails on stale copies.
38
38
  2. **Change the backoffice schema only through collections, then regenerate and commit.** Payload generates `apps/backoffice/src/app/(payload)/`, `apps/backoffice/src/payload-types.ts`, and `apps/backoffice/src/migrations/`; run `pnpm generate:types`, `pnpm generate:importmap`, and `pnpm migrate:create <name>`. Do not edit those files by hand.
39
39
  3. **Put unit tests next to the code and Playwright end-to-end tests in `apps/app/test/` and `apps/backoffice/test/` as `*.e2e.test.ts`.** Vitest collects `apps/app/app/**/*.test.{ts,tsx}` and `apps/backoffice/src/**/*.test.{ts,tsx}`.
40
- 4. **Ask the user before adding component tests.** They need a DOM test environment, and Vitest runs in Node.js.
40
+ 4. **Component tests need a DOM test environment, and Vitest runs in Node.js.** Add that environment with the first component test.
41
41
  5. **Read backoffice configuration through `requireEnvironment` in `apps/backoffice/src/environment.ts`.** Document each variable in `.env.example`.
42
42
  6. **Start from the accepted spec, in plan mode.** Claude Code starts every session in plan mode (`.claude/settings.json`); in Cursor, switch to Plan Mode before the first edit. Give the plan the change's `intent/<change>/intent.md` and `spec.md`, and have it name the files that change, the order of the work, and the tests that prove it. Commit the approved plan as `intent/<change>/plan.md`. When implementation departs from the plan, update `plan.md` in the same commit.
43
43
  7. **Start a bug fix with a failing test.** Reproduce the bug as a test, run it, and confirm it fails for the reason you expect. Commit that test. Then create `.git/bug-fix-in-progress`, so `.agents/hooks/agent-test-edit-gate.sh` blocks test edits, and make the test pass without editing it. Delete `.git/bug-fix-in-progress` when the fix is done.
44
+ 8. **Start work only when a person asks or a trigger runs it, and stop only at the human gates, before an irreversible action, or for a decision only a person can make.** A question or a statement gets an answer, not a file, a commit, or a pull request. Once a task starts, carry it to the next gate without asking; `.agents/rules/agent-owned-work.md` lists the gates.
44
45
 
45
46
  ## Things Claude gets wrong
46
47
 
@@ -48,7 +49,7 @@ When Claude makes a mistake twice, add the correction here.
48
49
 
49
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.
50
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`.
51
- - **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 is a convenience, not a security boundary: CI and code-owner review are the boundary.
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.
52
53
 
53
54
  ## Related documentation
54
55