@next-friday/create-app 5.0.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/templates/base/.agents/hooks/agent-mcp-gate.sh +22 -0
- package/templates/base/.agents/hooks/agent-shell-gate.sh +160 -5
- package/templates/base/.agents/rules/agent-owned-work.md +14 -1
- package/templates/base/.agents/skills/babysit-pr/SKILL.md +1 -1
- package/templates/base/.agents/skills/git-workflow/SKILL.md +1 -1
- package/templates/base/.agents/skills/write-intent/SKILL.md +6 -5
- package/templates/base/.agents/skills/write-spec/SKILL.md +2 -2
- package/templates/base/.claude/settings.json +13 -1
- package/templates/base/.claude/skills/babysit-pr/SKILL.md +1 -1
- package/templates/base/.claude/skills/git-workflow/SKILL.md +1 -1
- package/templates/base/.claude/skills/write-intent/SKILL.md +6 -5
- package/templates/base/.claude/skills/write-spec/SKILL.md +2 -2
- package/templates/base/.cursor/hooks.json +7 -0
- package/templates/base/.cursor/rules/agent-owned-work.mdc +15 -2
- package/templates/base/.cursor/skills/babysit-pr/SKILL.md +1 -1
- package/templates/base/.cursor/skills/git-workflow/SKILL.md +1 -1
- package/templates/base/.cursor/skills/write-intent/SKILL.md +6 -5
- package/templates/base/.cursor/skills/write-spec/SKILL.md +2 -2
- package/templates/base/SETUP.md +1 -1
- package/templates/base/scripts/check-agent-gates.ts +171 -4
- package/templates/nestjs/.agents/rules.json +1 -1
- package/templates/nestjs/AGENTS.md +2 -1
- package/templates/nextjs/.agents/rules.json +1 -1
- package/templates/nextjs/AGENTS.md +3 -2
- package/templates/nodejs/.agents/rules.json +1 -1
- package/templates/nodejs/AGENTS.md +2 -1
- package/templates/payload/.agents/rules.json +1 -1
- package/templates/payload/AGENTS.md +3 -2
- package/templates/turborepo/.agents/rules.json +1 -1
- 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": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Create a Next Friday project from a template.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"template",
|
|
9
9
|
"next-friday"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://github.com/next-friday/
|
|
11
|
+
"homepage": "https://github.com/next-friday/next-friday-create-app#readme",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/next-friday/
|
|
13
|
+
"url": "https://github.com/next-friday/next-friday-create-app/issues"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/next-friday/
|
|
17
|
+
"url": "git+https://github.com/next-friday/next-friday-create-app.git"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "Next Friday",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"bin": {
|
|
27
|
-
"
|
|
27
|
+
"next-friday-create-app": "./dist/main.mjs"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
10
|
-
3.
|
|
11
|
-
4.
|
|
12
|
-
5.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
10
|
-
3.
|
|
11
|
-
4.
|
|
12
|
-
5.
|
|
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
|
|
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.
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Agent-owned work: do
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
10
|
-
3.
|
|
11
|
-
4.
|
|
12
|
-
5.
|
|
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
|
|
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.
|
package/templates/base/SETUP.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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. **
|
|
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
|
|
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
|
|
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. **
|
|
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
|
|
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. **
|
|
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
|
|