@next-friday/create-app 0.0.0-stage → 0.1.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.
Files changed (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -2
  3. package/dist/main.mjs +414 -0
  4. package/package.json +84 -3
  5. package/templates/base/.agents/hooks/agent-shell-gate.sh +355 -0
  6. package/templates/base/.agents/hooks/agent-suppression-gate.sh +45 -0
  7. package/templates/base/.agents/rules/automation-owned-checks.md +5 -0
  8. package/templates/base/.agents/rules/code-comments.md +3 -0
  9. package/templates/base/.agents/rules/current-state-authority.md +17 -0
  10. package/templates/base/.agents/rules/demand-driven-configuration.md +7 -0
  11. package/templates/base/.agents/rules/dependency-security.md +5 -0
  12. package/templates/base/.agents/rules/deterministic-testing.md +15 -0
  13. package/templates/base/.agents/rules/diagnostics-and-error-handling.md +7 -0
  14. package/templates/base/.agents/rules/git-workflow.md +10 -0
  15. package/templates/base/.agents/rules/no-suppression.md +7 -0
  16. package/templates/base/.agents/rules/script-driven-changes.md +7 -0
  17. package/templates/base/.agents/rules/single-author-style.md +7 -0
  18. package/templates/base/.claude/rules/agents-guide.md +1 -0
  19. package/templates/base/.claude/rules/automation-owned-checks.md +1 -0
  20. package/templates/base/.claude/rules/code-comments.md +1 -0
  21. package/templates/base/.claude/rules/current-state-authority.md +1 -0
  22. package/templates/base/.claude/rules/demand-driven-configuration.md +1 -0
  23. package/templates/base/.claude/rules/dependency-security.md +12 -0
  24. package/templates/base/.claude/rules/diagnostics-and-error-handling.md +1 -0
  25. package/templates/base/.claude/rules/git-workflow.md +1 -0
  26. package/templates/base/.claude/rules/no-suppression.md +1 -0
  27. package/templates/base/.claude/rules/script-driven-changes.md +1 -0
  28. package/templates/base/.claude/rules/single-author-style.md +1 -0
  29. package/templates/base/.claude/settings.json +61 -0
  30. package/templates/base/.commitlintrc.json +3 -0
  31. package/templates/base/.cursor/BUGBOT.md +17 -0
  32. package/templates/base/.cursor/hooks/agent-constitution-gate.sh +32 -0
  33. package/templates/base/.cursor/hooks.json +26 -0
  34. package/templates/base/.cursor/rules/automation-owned-checks.mdc +10 -0
  35. package/templates/base/.cursor/rules/code-comments.mdc +8 -0
  36. package/templates/base/.cursor/rules/current-state-authority.mdc +22 -0
  37. package/templates/base/.cursor/rules/demand-driven-configuration.mdc +12 -0
  38. package/templates/base/.cursor/rules/dependency-security.mdc +11 -0
  39. package/templates/base/.cursor/rules/diagnostics-and-error-handling.mdc +12 -0
  40. package/templates/base/.cursor/rules/git-workflow.mdc +15 -0
  41. package/templates/base/.cursor/rules/no-suppression.mdc +12 -0
  42. package/templates/base/.cursor/rules/script-driven-changes.mdc +12 -0
  43. package/templates/base/.cursor/rules/single-author-style.mdc +12 -0
  44. package/templates/base/.editorconfig +13 -0
  45. package/templates/base/.gitattributes +1 -0
  46. package/templates/base/.github/CODEOWNERS +2 -0
  47. package/templates/base/.github/ISSUE_TEMPLATE/config.yml +1 -0
  48. package/templates/base/.github/dependabot.yml +77 -0
  49. package/templates/base/.github/file-filters.yml +19 -0
  50. package/templates/base/.github/labeler.yml +25 -0
  51. package/templates/base/.github/requirements/diff-cover.txt +213 -0
  52. package/templates/base/.github/workflows/deploy.yml +124 -0
  53. package/templates/base/.github/workflows/osv-scanner.yml +34 -0
  54. package/templates/base/.github/workflows/pr-triage.yml +29 -0
  55. package/templates/base/.github/workflows/secret-scan.yml +50 -0
  56. package/templates/base/.github/workflows/validate-pr-title.yml +48 -0
  57. package/templates/base/.github/workflows/zizmor.yml +62 -0
  58. package/templates/base/.github/zizmor.yml +4 -0
  59. package/templates/base/.gitleaks.toml +14 -0
  60. package/templates/base/.husky/commit-msg +3 -0
  61. package/templates/base/.husky/pre-commit +17 -0
  62. package/templates/base/.husky/pre-merge-commit +7 -0
  63. package/templates/base/.husky/pre-push +15 -0
  64. package/templates/base/.nvmrc +1 -0
  65. package/templates/base/SECURITY.md +15 -0
  66. package/templates/base/knip.json +3 -0
  67. package/templates/base/pnpm-workspace.yaml +17 -0
  68. package/templates/base/railway.json +9 -0
  69. package/templates/base/scripts/check-agent-gates.ts +252 -0
  70. package/templates/base/scripts/sync-agent-rules.ts +121 -0
  71. package/templates/nestjs/.agents/rules/typescript-first.md +7 -0
  72. package/templates/nestjs/.agents/rules.json +41 -0
  73. package/templates/nestjs/.claude/rules/deterministic-testing.md +20 -0
  74. package/templates/nestjs/.claude/rules/typescript-first.md +13 -0
  75. package/templates/nestjs/.cursor/rules/deterministic-testing.mdc +21 -0
  76. package/templates/nestjs/.cursor/rules/typescript-first.mdc +13 -0
  77. package/templates/nestjs/.dependency-cruiser.json +28 -0
  78. package/templates/nestjs/.dockerignore +9 -0
  79. package/templates/nestjs/.env.example +1 -0
  80. package/templates/nestjs/.github/ISSUE_TEMPLATE/bug.yml +66 -0
  81. package/templates/nestjs/.github/ISSUE_TEMPLATE/feature.yml +50 -0
  82. package/templates/nestjs/.github/pull_request_template.md +25 -0
  83. package/templates/nestjs/.github/workflows/ci.yml +181 -0
  84. package/templates/nestjs/.github/workflows/sast.yml +45 -0
  85. package/templates/nestjs/.lintstagedrc.json +10 -0
  86. package/templates/nestjs/.prettierignore +2 -0
  87. package/templates/nestjs/.prettierrc.json +1 -0
  88. package/templates/nestjs/AGENTS.md +8 -0
  89. package/templates/nestjs/CONTRIBUTING.md +57 -0
  90. package/templates/nestjs/Dockerfile +27 -0
  91. package/templates/nestjs/README.md +78 -0
  92. package/templates/nestjs/_gitignore +47 -0
  93. package/templates/nestjs/eslint.config.ts +7 -0
  94. package/templates/nestjs/nest-cli.json +8 -0
  95. package/templates/nestjs/package.json +81 -0
  96. package/templates/nestjs/src/app.controller.test.ts +23 -0
  97. package/templates/nestjs/src/app.controller.ts +13 -0
  98. package/templates/nestjs/src/app.module.ts +12 -0
  99. package/templates/nestjs/src/app.service.ts +10 -0
  100. package/templates/nestjs/src/health/health.controller.test.ts +17 -0
  101. package/templates/nestjs/src/health/health.controller.ts +14 -0
  102. package/templates/nestjs/src/health/health.module.ts +10 -0
  103. package/templates/nestjs/src/main.ts +25 -0
  104. package/templates/nestjs/src/port.test.ts +29 -0
  105. package/templates/nestjs/src/port.ts +20 -0
  106. package/templates/nestjs/test/app.e2e.test.ts +40 -0
  107. package/templates/nestjs/tsconfig.build.json +9 -0
  108. package/templates/nestjs/tsconfig.json +20 -0
  109. package/templates/nestjs/vitest.config.ts +13 -0
  110. package/templates/nestjs/vitest.e2e.config.ts +8 -0
  111. package/templates/nextjs/.agents/rules/typescript-first.md +7 -0
  112. package/templates/nextjs/.agents/rules.json +41 -0
  113. package/templates/nextjs/.claude/rules/deterministic-testing.md +21 -0
  114. package/templates/nextjs/.claude/rules/typescript-first.md +15 -0
  115. package/templates/nextjs/.cursor/rules/deterministic-testing.mdc +21 -0
  116. package/templates/nextjs/.cursor/rules/typescript-first.mdc +13 -0
  117. package/templates/nextjs/.dependency-cruiser.json +34 -0
  118. package/templates/nextjs/.dockerignore +10 -0
  119. package/templates/nextjs/.env.example +1 -0
  120. package/templates/nextjs/.github/ISSUE_TEMPLATE/bug.yml +66 -0
  121. package/templates/nextjs/.github/ISSUE_TEMPLATE/feature.yml +50 -0
  122. package/templates/nextjs/.github/pull_request_template.md +25 -0
  123. package/templates/nextjs/.github/workflows/ci.yml +187 -0
  124. package/templates/nextjs/.github/workflows/sast.yml +46 -0
  125. package/templates/nextjs/.lintstagedrc.json +10 -0
  126. package/templates/nextjs/.prettierignore +2 -0
  127. package/templates/nextjs/AGENTS.md +18 -0
  128. package/templates/nextjs/CONTRIBUTING.md +57 -0
  129. package/templates/nextjs/Dockerfile +23 -0
  130. package/templates/nextjs/README.md +74 -0
  131. package/templates/nextjs/_gitignore +51 -0
  132. package/templates/nextjs/app/favicon.ico +0 -0
  133. package/templates/nextjs/app/globals.css +26 -0
  134. package/templates/nextjs/app/health/route.test.ts +20 -0
  135. package/templates/nextjs/app/health/route.ts +23 -0
  136. package/templates/nextjs/app/layout.tsx +29 -0
  137. package/templates/nextjs/app/page.test.ts +11 -0
  138. package/templates/nextjs/app/page.tsx +8 -0
  139. package/templates/nextjs/eslint.config.ts +32 -0
  140. package/templates/nextjs/knip.json +4 -0
  141. package/templates/nextjs/next.config.ts +35 -0
  142. package/templates/nextjs/package.json +78 -0
  143. package/templates/nextjs/playwright.config.ts +27 -0
  144. package/templates/nextjs/postcss.config.mjs +7 -0
  145. package/templates/nextjs/prettier.config.ts +11 -0
  146. package/templates/nextjs/test/home.e2e.test.ts +11 -0
  147. package/templates/nextjs/tsconfig.json +33 -0
  148. package/templates/nextjs/vitest.config.ts +12 -0
  149. package/templates/nodejs/.agents/rules/typescript-first.md +7 -0
  150. package/templates/nodejs/.agents/rules.json +41 -0
  151. package/templates/nodejs/.claude/rules/deterministic-testing.md +20 -0
  152. package/templates/nodejs/.claude/rules/typescript-first.md +13 -0
  153. package/templates/nodejs/.cursor/rules/deterministic-testing.mdc +21 -0
  154. package/templates/nodejs/.cursor/rules/typescript-first.mdc +13 -0
  155. package/templates/nodejs/.dependency-cruiser.json +28 -0
  156. package/templates/nodejs/.dockerignore +8 -0
  157. package/templates/nodejs/.env.example +1 -0
  158. package/templates/nodejs/.github/ISSUE_TEMPLATE/bug.yml +66 -0
  159. package/templates/nodejs/.github/ISSUE_TEMPLATE/feature.yml +50 -0
  160. package/templates/nodejs/.github/pull_request_template.md +25 -0
  161. package/templates/nodejs/.github/workflows/ci.yml +179 -0
  162. package/templates/nodejs/.github/workflows/sast.yml +45 -0
  163. package/templates/nodejs/.lintstagedrc.json +10 -0
  164. package/templates/nodejs/.prettierignore +2 -0
  165. package/templates/nodejs/.prettierrc.json +1 -0
  166. package/templates/nodejs/AGENTS.md +8 -0
  167. package/templates/nodejs/CONTRIBUTING.md +56 -0
  168. package/templates/nodejs/Dockerfile +27 -0
  169. package/templates/nodejs/README.md +74 -0
  170. package/templates/nodejs/_gitignore +47 -0
  171. package/templates/nodejs/eslint.config.ts +7 -0
  172. package/templates/nodejs/package.json +66 -0
  173. package/templates/nodejs/src/main.ts +22 -0
  174. package/templates/nodejs/src/port.test.ts +30 -0
  175. package/templates/nodejs/src/port.ts +20 -0
  176. package/templates/nodejs/src/server.test.ts +60 -0
  177. package/templates/nodejs/src/server.ts +37 -0
  178. package/templates/nodejs/tsconfig.build.json +9 -0
  179. package/templates/nodejs/tsconfig.json +19 -0
  180. package/templates/nodejs/vitest.config.ts +12 -0
  181. package/templates/payload/.agents/rules/typescript-first.md +7 -0
  182. package/templates/payload/.agents/rules.json +41 -0
  183. package/templates/payload/.claude/rules/deterministic-testing.md +21 -0
  184. package/templates/payload/.claude/rules/typescript-first.md +15 -0
  185. package/templates/payload/.cursor/rules/deterministic-testing.mdc +21 -0
  186. package/templates/payload/.cursor/rules/typescript-first.mdc +13 -0
  187. package/templates/payload/.dependency-cruiser.json +34 -0
  188. package/templates/payload/.dockerignore +9 -0
  189. package/templates/payload/.editorconfig-checker.json +3 -0
  190. package/templates/payload/.env.example +4 -0
  191. package/templates/payload/.github/ISSUE_TEMPLATE/bug.yml +66 -0
  192. package/templates/payload/.github/ISSUE_TEMPLATE/feature.yml +50 -0
  193. package/templates/payload/.github/pull_request_template.md +25 -0
  194. package/templates/payload/.github/workflows/ci.yml +204 -0
  195. package/templates/payload/.github/workflows/sast.yml +46 -0
  196. package/templates/payload/.lintstagedrc.json +10 -0
  197. package/templates/payload/.prettierignore +5 -0
  198. package/templates/payload/.prettierrc.json +1 -0
  199. package/templates/payload/AGENTS.md +19 -0
  200. package/templates/payload/CONTRIBUTING.md +58 -0
  201. package/templates/payload/Dockerfile +26 -0
  202. package/templates/payload/README.md +98 -0
  203. package/templates/payload/_gitignore +52 -0
  204. package/templates/payload/eslint.config.ts +35 -0
  205. package/templates/payload/next.config.ts +43 -0
  206. package/templates/payload/package.json +86 -0
  207. package/templates/payload/playwright.config.ts +27 -0
  208. package/templates/payload/src/app/(frontend)/layout.tsx +18 -0
  209. package/templates/payload/src/app/(frontend)/page.test.ts +14 -0
  210. package/templates/payload/src/app/(frontend)/page.tsx +14 -0
  211. package/templates/payload/src/app/(frontend)/styles.css +15 -0
  212. package/templates/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx +24 -0
  213. package/templates/payload/src/app/(payload)/admin/[[...segments]]/page.tsx +24 -0
  214. package/templates/payload/src/app/(payload)/admin/importMap.ts +6 -0
  215. package/templates/payload/src/app/(payload)/api/[...slug]/route.ts +19 -0
  216. package/templates/payload/src/app/(payload)/api/graphql/route.ts +8 -0
  217. package/templates/payload/src/app/(payload)/api/graphql-playground/route.ts +7 -0
  218. package/templates/payload/src/app/(payload)/custom.scss +0 -0
  219. package/templates/payload/src/app/(payload)/layout.tsx +31 -0
  220. package/templates/payload/src/app/health/route.test.ts +20 -0
  221. package/templates/payload/src/app/health/route.ts +23 -0
  222. package/templates/payload/src/collections/media.test.ts +27 -0
  223. package/templates/payload/src/collections/media.ts +18 -0
  224. package/templates/payload/src/collections/users.test.ts +10 -0
  225. package/templates/payload/src/collections/users.ts +10 -0
  226. package/templates/payload/src/environment.test.ts +32 -0
  227. package/templates/payload/src/environment.ts +14 -0
  228. package/templates/payload/src/migrations/20261030_005055_initial.json +969 -0
  229. package/templates/payload/src/migrations/20261030_005055_initial.ts +134 -0
  230. package/templates/payload/src/migrations/index.ts +9 -0
  231. package/templates/payload/src/payload-types.ts +339 -0
  232. package/templates/payload/src/payload.config.ts +47 -0
  233. package/templates/payload/test/home.e2e.test.ts +16 -0
  234. package/templates/payload/tsconfig.json +34 -0
  235. package/templates/payload/vitest.config.ts +19 -0
@@ -0,0 +1,355 @@
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-shell-gate: %s\n' "$1" >&2
12
+ exit 2
13
+ }
14
+
15
+ 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
+ command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
17
+ input=$(cat)
18
+ command=$(jq -r '.command // .tool_input.command // empty' <<< "$input")
19
+ [[ -n $command ]] || allow
20
+
21
+ separator=$'\x1f'
22
+ redirect=$'\x1e'
23
+ protected_names='(\.husky|\.agents|\.cursor|\.claude/rules|\.claude/settings(\.local)?\.json|\.git/hooks|\.git/config|\.lintstagedrc\.json|\.gitleaks\.toml|\.commitlintrc\.json|\.prettierrc\.json|prettier\.config\.ts|eslint\.config\.ts|scripts/(sync-agent-rules|check-agent-gates)\.ts)'
24
+ protected="(^|[/=])$protected_names(/|$)"
25
+ policy="Hooks and agent policy files are company policy; stop and ask the user."
26
+ in_protected=0
27
+
28
+ # Split a command line into words as the shell does: quotes and backslashes are removed, and
29
+ # command substitutions stay inside their word and are also collected, so they are checked as
30
+ # command lines too. Separators become their own word, and a redirection target is prefixed, so
31
+ # each simple command is checked on its own.
32
+ lex() {
33
+ local text=$1 length=${#1} index=0 char word="" has_word=0 quote="" inner_quote="" depth=0 start=0 end is_target=0
34
+ words=()
35
+ substitutions=()
36
+
37
+ flush() {
38
+ if ((has_word)); then
39
+ if ((is_target)); then
40
+ words+=("$redirect$word")
41
+ is_target=0
42
+ else
43
+ words+=("$word")
44
+ fi
45
+ fi
46
+ word=""
47
+ has_word=0
48
+ }
49
+
50
+ while ((index < length)); do
51
+ char=${text:index:1}
52
+ if ((depth > 0)); then
53
+ word+=$char
54
+ if [[ -n $inner_quote ]]; then
55
+ if [[ $char == "$inner_quote" ]]; then
56
+ inner_quote=""
57
+ elif [[ $inner_quote == '"' && $char == '\' ]]; then
58
+ index=$((index + 1))
59
+ word+=${text:index:1}
60
+ fi
61
+ elif [[ $char == "'" || $char == '"' ]]; then
62
+ inner_quote=$char
63
+ elif [[ $char == '\' ]]; then
64
+ index=$((index + 1))
65
+ word+=${text:index:1}
66
+ elif [[ $char == '#' && ${text:index-1:1} == [[:space:]\;\&\|\(\)\<\>] ]]; then
67
+ while ((index + 1 < length)) && [[ ${text:index+1:1} != $'\n' ]]; do
68
+ index=$((index + 1))
69
+ done
70
+ elif [[ $char == '(' ]]; then
71
+ depth=$((depth + 1))
72
+ elif [[ $char == ')' ]]; then
73
+ depth=$((depth - 1))
74
+ if ((depth == 0)); then
75
+ substitutions+=("${text:start:index-start}")
76
+ fi
77
+ fi
78
+ elif [[ $quote != "'" && $char == '$' && ${text:index+1:1} == '(' ]]; then
79
+ word+='$('
80
+ index=$((index + 1))
81
+ start=$((index + 1))
82
+ depth=1
83
+ has_word=1
84
+ elif [[ $quote != "'" && $char == '`' ]]; then
85
+ end=$index
86
+ while ((end + 1 < length)) && [[ ${text:end+1:1} != '`' ]]; do
87
+ end=$((end + 1))
88
+ done
89
+ substitutions+=("${text:index+1:end-index}")
90
+ word+=${text:index:end-index+2}
91
+ index=$((end + 1))
92
+ has_word=1
93
+ elif [[ -n $quote ]]; then
94
+ if [[ $char == "$quote" ]]; then
95
+ quote=""
96
+ elif [[ $quote == '"' && $char == '\' ]]; then
97
+ index=$((index + 1))
98
+ word+=${text:index:1}
99
+ else
100
+ word+=$char
101
+ fi
102
+ else
103
+ case $char in
104
+ "'" | '"')
105
+ quote=$char
106
+ has_word=1
107
+ ;;
108
+ '\')
109
+ index=$((index + 1))
110
+ word+=${text:index:1}
111
+ has_word=1
112
+ ;;
113
+ ' ' | $'\t')
114
+ flush
115
+ ;;
116
+ ';' | '&' | '|' | $'\n' | '(' | ')')
117
+ flush
118
+ words+=("$separator")
119
+ ;;
120
+ '>')
121
+ flush
122
+ while [[ ${text:index+1:1} == [\>\|\&] ]]; do
123
+ index=$((index + 1))
124
+ done
125
+ is_target=1
126
+ ;;
127
+ *)
128
+ word+=$char
129
+ has_word=1
130
+ ;;
131
+ esac
132
+ fi
133
+ index=$((index + 1))
134
+ done
135
+ flush
136
+ return 0
137
+ }
138
+
139
+ writes_protected() {
140
+ block "Changing $1 from the shell is blocked. $policy"
141
+ }
142
+
143
+ check_config_key() {
144
+ local key
145
+ key=$(tr '[:upper:]' '[:lower:]' <<< "$1")
146
+ if [[ $key == *core.hookspath* ]]; then
147
+ block "Changing core.hooksPath is blocked. Stop and ask the user."
148
+ fi
149
+ if [[ $key == alias.* ]]; then
150
+ block "Defining git aliases is blocked, because an alias can skip hooks. Run the git command directly."
151
+ fi
152
+ if [[ $key == *'$'* || $key == *'`'* ]]; then
153
+ block "Write git config keys literally, without shell expansion."
154
+ fi
155
+ return 0
156
+ }
157
+
158
+ check_commit() {
159
+ local word cluster position skip_next=0
160
+ for word in "$@"; do
161
+ if ((skip_next)); then
162
+ skip_next=0
163
+ continue
164
+ fi
165
+ case $word in
166
+ --) break ;;
167
+ --message | --file | --reuse-message | --reedit-message | --template | --author | --date | --cleanup | --fixup | --squash | --trailer | --pathspec-from-file) skip_next=1 ;;
168
+ --*) ;;
169
+ -?*)
170
+ cluster=${word#-}
171
+ for ((position = 0; position < ${#cluster}; position++)); do
172
+ case ${cluster:position:1} in
173
+ n) block "Skipping git hooks with git commit -n is blocked. Fix the hook failure instead." ;;
174
+ m | F | C | c | t)
175
+ if ((position == ${#cluster} - 1)); then
176
+ skip_next=1
177
+ fi
178
+ break
179
+ ;;
180
+ S | u) break ;;
181
+ esac
182
+ done
183
+ ;;
184
+ esac
185
+ done
186
+ return 0
187
+ }
188
+
189
+ check_git() {
190
+ local -a args=(${@+"$@"})
191
+ local count=${#args[@]} index=0 word subcommand=""
192
+ ((count > 0)) || return 0
193
+ while ((index < count)); do
194
+ word=${args[index]}
195
+ case $word in
196
+ -C | --git-dir | --work-tree | --namespace | --super-prefix) index=$((index + 2)) ;;
197
+ -c | --config-env)
198
+ check_config_key "${args[index + 1]:-}"
199
+ index=$((index + 2))
200
+ ;;
201
+ --config-env=*)
202
+ check_config_key "${word#*=}"
203
+ index=$((index + 1))
204
+ ;;
205
+ -*) index=$((index + 1)) ;;
206
+ *)
207
+ subcommand=$word
208
+ break
209
+ ;;
210
+ esac
211
+ done
212
+ local -a rest=("${args[@]:index+1}")
213
+
214
+ # Git accepts any unambiguous prefix of a long option, so --no-veri works like --no-verify.
215
+ for word in ${rest[@]+"${rest[@]}"}; do
216
+ if [[ $word =~ ^--no-v(e(r(i(fy?)?)?)?)?(=|$) ]]; then
217
+ block "Skipping git hooks with --no-verify is blocked. Fix the hook failure instead."
218
+ fi
219
+ if [[ $word == -* && ($word == *'$'* || $word == *'`'*) ]]; then
220
+ block "Write git options literally, without shell expansion."
221
+ fi
222
+ done
223
+
224
+ case $subcommand in
225
+ commit) check_commit ${rest[@]+"${rest[@]}"} ;;
226
+ config)
227
+ for word in ${rest[@]+"${rest[@]}"}; do
228
+ [[ $word == -* ]] || check_config_key "$word"
229
+ done
230
+ ;;
231
+ commit-tree | update-ref) block "git commit-tree and git update-ref are blocked, because they create commits without hooks. Use git commit." ;;
232
+ checkout | restore | rm | mv | apply)
233
+ for word in ${rest[@]+"${rest[@]}"}; do
234
+ if [[ $word =~ $protected ]]; then
235
+ writes_protected "$word"
236
+ fi
237
+ done
238
+ ;;
239
+ esac
240
+ return 0
241
+ }
242
+
243
+ check_command() {
244
+ local -a args=()
245
+ local word name is_writer=0
246
+ for word in "$@"; do
247
+ if [[ $word == "$redirect"* ]]; then
248
+ word=${word#"$redirect"}
249
+ if ((in_protected)) || [[ $word =~ $protected ]]; then
250
+ writes_protected "$word"
251
+ fi
252
+ else
253
+ args+=("$word")
254
+ fi
255
+ done
256
+ ((${#args[@]} > 0)) || return 0
257
+
258
+ for word in "${args[@]}"; do
259
+ if [[ $word == HUSKY=0 ]]; then
260
+ block "HUSKY=0 is blocked. Fix the hook failure instead."
261
+ fi
262
+ if [[ $word =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] && [[ $(tr '[:upper:]' '[:lower:]' <<< "$word") == *core.hookspath* ]]; then
263
+ block "Changing core.hooksPath is blocked. Stop and ask the user."
264
+ fi
265
+ done
266
+
267
+ while ((${#args[@]} > 0)); do
268
+ word=${args[0]}
269
+ if [[ $word =~ ^[A-Za-z_][A-Za-z0-9_]*= || $word == -* ]]; then
270
+ args=("${args[@]:1}")
271
+ continue
272
+ fi
273
+ case ${word##*/} in
274
+ env | command | exec | nohup | time | nice | sudo | builtin) args=("${args[@]:1}") ;;
275
+ *) break ;;
276
+ esac
277
+ done
278
+ ((${#args[@]} > 0)) || return 0
279
+ name=${args[0]##*/}
280
+
281
+ case $name in
282
+ git) check_git "${args[@]:1}" ;;
283
+ gh)
284
+ if [[ ${args[1]:-} =~ ^(repo|issue|release)$ && ${args[2]:-} == delete ]]; then
285
+ block "gh ${args[1]} delete is blocked. Stop and ask the user."
286
+ fi
287
+ ;;
288
+ bash | sh | zsh | dash | ksh)
289
+ for ((index = 1; index < ${#args[@]}; index++)); do
290
+ if [[ ${args[index]} =~ ^-[a-zA-Z]*c[a-zA-Z]*$ ]]; then
291
+ gate_line "${args[index + 1]:-}"
292
+ break
293
+ fi
294
+ done
295
+ ;;
296
+ eval) gate_line "${args[*]:1}" ;;
297
+ cd | pushd)
298
+ if [[ ${args[1]:-} =~ $protected ]]; then
299
+ in_protected=1
300
+ fi
301
+ ;;
302
+ tee | rm | rmdir | mv | cp | ln | chmod | chown | truncate | install | unlink | rsync | dd | patch | sponge | shred) is_writer=1 ;;
303
+ sed | perl)
304
+ for word in "${args[@]:1}"; do
305
+ if [[ $word =~ ^-[a-zA-Z]*i || $word == --in-place* ]]; then
306
+ is_writer=1
307
+ fi
308
+ done
309
+ ;;
310
+ python | python3 | node | ruby | deno | bun)
311
+ if ((in_protected)); then
312
+ writes_protected "a protected directory"
313
+ fi
314
+ for ((index = 1; index < ${#args[@]}; index++)); do
315
+ if [[ ${args[index]} =~ ^-(c|e|p|E|-eval|-print)$ && ${args[index + 1]:-} =~ $protected_names ]]; then
316
+ writes_protected "${BASH_REMATCH[0]}"
317
+ fi
318
+ done
319
+ ;;
320
+ esac
321
+
322
+ if ((is_writer)); then
323
+ if ((in_protected)); then
324
+ writes_protected "a protected directory"
325
+ fi
326
+ for word in "${args[@]:1}"; do
327
+ if [[ $word =~ $protected ]]; then
328
+ writes_protected "$word"
329
+ fi
330
+ done
331
+ fi
332
+ return 0
333
+ }
334
+
335
+ gate_line() {
336
+ lex "$1"
337
+ local -a line=(${words[@]+"${words[@]}"}) nested=(${substitutions[@]+"${substitutions[@]}"}) current=()
338
+ local word
339
+ for word in ${nested[@]+"${nested[@]}"}; do
340
+ gate_line "$word"
341
+ done
342
+ for word in ${line[@]+"${line[@]}"}; do
343
+ if [[ $word == "$separator" ]]; then
344
+ check_command ${current[@]+"${current[@]}"}
345
+ current=()
346
+ else
347
+ current+=("$word")
348
+ fi
349
+ done
350
+ check_command ${current[@]+"${current[@]}"}
351
+ return 0
352
+ }
353
+
354
+ gate_line "$command"
355
+ allow
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ directives='eslint-disable|eslint-enable|@ts-ignore|@ts-expect-error|@ts-nocheck|zizmor: ?ignore|nosemgrep|gitleaks:allow|trufflehog:ignore|prettier-ignore|istanbul ignore|c8 ignore|v8 ignore|knip-ignore|NOSONAR|editorconfig-checker-disable|hadolint ignore|trivy:ignore|(^|[^[:alnum:]_])@public([^[:alnum:]_]|$)|(^|[^[:alnum:]_])noqa([^[:alnum:]_]|$)'
5
+ exempt='^(\.agents/rules/|\.agents/hooks/|\.claude/rules/|\.cursor/rules/|\.cursor/hooks/|scripts/check-agent-gates\.ts(:|$))'
6
+
7
+ allow() {
8
+ printf '%s\n' '{"permission":"allow"}'
9
+ exit 0
10
+ }
11
+
12
+ block() {
13
+ jq -n --arg message "$1" '{permission:"deny",user_message:$message,agent_message:$message}'
14
+ printf 'agent-suppression-gate: %s\n' "$1" >&2
15
+ exit 2
16
+ }
17
+
18
+ if [[ ${1:-} == --repo ]]; then
19
+ matches=$(git grep -nIE "$directives" -- . ":(exclude)pnpm-lock.yaml" | grep -vE "$exempt" || true)
20
+ if [[ -n $matches ]]; then
21
+ printf '%s\n' "$matches" >&2
22
+ printf 'suppressions:check: remove these suppression directives and fix the findings they hide.\n' >&2
23
+ exit 1
24
+ fi
25
+ exit 0
26
+ fi
27
+
28
+ trap 'block ".agents/hooks/agent-suppression-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
29
+ command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
30
+ input=$(cat)
31
+ path=$(jq -r '.tool_input.file_path // .tool_input.path // .tool_input.notebook_path // .file_path // empty' <<< "$input")
32
+ [[ -n $path ]] || allow
33
+
34
+ project=$(jq -r '.workspace_roots[0] // .cwd // empty' <<< "$input")
35
+ project=${CLAUDE_PROJECT_DIR:-${project:-$PWD}}
36
+ [[ $path == /* ]] || path=$project/$path
37
+ [[ $path == "$project"/* ]] || allow
38
+ relative=${path#"$project"/}
39
+ [[ ! $relative =~ (^|/)\.\.?(/|$) && $relative =~ $exempt ]] && allow
40
+
41
+ content=$(jq -r '[.tool_input.content, .tool_input.contents, .tool_input.new_string, .tool_input.new_source, (.tool_input.edits // [] | .[].new_string)] | map(select(. != null)) | join("\n")' <<< "$input")
42
+ if grep -qE "$directives" <<< "$content"; then
43
+ block "The edit to $relative adds a suppression directive. Fix what the tool reports instead; if you believe the finding is wrong, stop and ask the user."
44
+ fi
45
+ allow
@@ -0,0 +1,5 @@
1
+ # Owned Checks
2
+
3
+ A check is **owned** when `.husky/` or `.github/workflows/` runs it. Those files are the source of truth for what must pass.
4
+
5
+ Run any check by hand when you need its result to verify your work; the hooks and CI still run it on commit, push, and the pull request. Read their output and fix what it reports. Report a check as passed only from an observed result.
@@ -0,0 +1,3 @@
1
+ # Required Comments
2
+
3
+ Write a comment only where a configured tool requires or reads it, where `no-suppression.md` requires an accepted-risk record, or to give a reason the code cannot show, such as a platform constraint or a workaround. Names, types, tests, ADRs, issues, and pull requests carry intent. When you touch a file, remove comments no tool requires.
@@ -0,0 +1,17 @@
1
+ # Current State
2
+
3
+ For implementation decisions, use this order:
4
+
5
+ 1. Explicit current user requirements
6
+ 2. Project instructions on the affected path
7
+ 3. Current working tree
8
+ 4. Tests, contracts, schemas, and configuration
9
+ 5. Runtime behavior and logs
10
+ 6. Official upstream documentation
11
+ 7. Git history as evidence of what changed and why
12
+
13
+ Code absent from the working tree is removed. Restore it only when the user names that historical implementation, then verify it against current requirements.
14
+
15
+ Identify the owning project before investigating. Apply the more specific convention inside its scope.
16
+
17
+ _Completion criterion_: The change matches current requirements and the working tree. Historical code appears only when the user named it.
@@ -0,0 +1,7 @@
1
+ # Demand-Driven Configuration
2
+
3
+ Add or change a setting for an active requirement in that configuration's scope. Prefer the smallest config that satisfies the current contract. Reuse a repeated value when a local name makes it clearer. Remove settings no active path, test, or documented purpose reads.
4
+
5
+ Make paths, patterns, and ignores match files that exist in the intended project. Compose from shared definitions; keep each pattern in one source of truth.
6
+
7
+ Verify a setting through the tool's validation or effective-configuration output.
@@ -0,0 +1,5 @@
1
+ # Dependencies
2
+
3
+ Add a dependency when active code, tests, build tooling, or a documented contract uses it, and native or existing project capabilities do not. Assess cadence, license, advisories, compatibility, and transitives first. Place it in the manifest of the project that imports it, in the ecosystem category that matches its role.
4
+
5
+ Resolve a vulnerability that Dependabot or OSV-Scanner reports by upgrading the dependency. For a transitive dependency, add a pnpm override in `pnpm-workspace.yaml`. When no fix exists, record the accepted risk as `no-suppression.md` describes.
@@ -0,0 +1,15 @@
1
+ # Test Admission
2
+
3
+ Keep tests as small, deterministic protections for observable contracts.
4
+
5
+ Name the contract and the distinct defect a proposed test can detect before writing it. Admit a test for externally observable behavior, a public contract, a demonstrated regression, a non-trivial invariant, a meaningful failure mode, or an explicit repository verification requirement.
6
+
7
+ Choose the narrowest existing layer that owns that contract. Give each test one defect to detect. Add a case only when it catches a defect remaining cases miss. Treat coverage percentage as a measurement.
8
+
9
+ Search existing coverage first. Extend the file that already owns the contract. Create a file only when the contract has no owner or needs a different harness. Follow the nearest directory and naming pattern. Keep setup local until multiple tests share live runtime logic, then extract one helper.
10
+
11
+ Use the framework's standard assertions. Use an inline snippet unless filesystem paths, module resolution, or compiler services are the contract. Keep fixtures declarative and read-only.
12
+
13
+ When the user asks to run tests, run the narrowest owning file or filter.
14
+
15
+ _Completion criterion_: Every added or modified case maps to an admitted contract and a defect current cases would miss. Tests stay unchanged when no proposed case survives admission.
@@ -0,0 +1,7 @@
1
+ # Diagnostics
2
+
3
+ Resolve runtime failures at the source with the language's standard narrowing, validation, and error types. For missing third-party types, take upstream definitions or a narrow adapter in the established location.
4
+
5
+ Surface errors at the caller or process boundary with diagnostic context. Handle an error to add context, recover, or translate it at an application boundary. Retain the root cause through the language's exception mechanism.
6
+
7
+ Report unresolved type, declaration, or rule conflicts to the user. Findings follow `no-suppression.md`.
@@ -0,0 +1,10 @@
1
+ # Git Workflow
2
+
3
+ The repository uses trunk-based development.
4
+
5
+ - Branch from the latest `origin/main`, in a worktree or a new branch named `<type>/<short-description>`. `main` is the only long-lived branch. The Git hooks block commits and pushes on it, except the first commit and push that create `main` in a new repository.
6
+ - Write commit messages in the format that `@next-friday/commitlint-config-friday` defines. The `commit-msg` hook rejects anything else.
7
+ - 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.
8
+ - Fill the pull request body from `.github/pull_request_template.md`.
9
+ - 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.
10
+ - Rebase and `git push --force-with-lease` only branches you own.
@@ -0,0 +1,7 @@
1
+ # Findings
2
+
3
+ Fix what a linter, type checker, or scanner reports, including the replacement it suggests. Comments a tool requires follow `code-comments.md`.
4
+
5
+ Record an accepted risk only in that tool's central file, with an owner, a reason, and an expiry date: `.github/zizmor.yml` for zizmor, `osv-scanner.toml` for OSV-Scanner, `.gitleaksignore` for gitleaks, `.trivyignore` for Trivy, or `.hadolint.yaml` for hadolint. Create the file or key when the first accepted risk needs it. Do not add inline suppressions; `.agents/hooks/agent-suppression-gate.sh` defines the rejected directives.
6
+
7
+ When a finding looks wrong, stop and cite the tool's documentation to the user. Report a finding as fixed only when the tool passes, with any accepted risk in that central file.
@@ -0,0 +1,7 @@
1
+ # Scripts
2
+
3
+ When a change repeats one pattern across more than a few files, run a script. Derive facts from the toolchain (TypeScript compiler API, `git`), not by reading files by eye. Edit by hand when the change needs judgment in one place.
4
+
5
+ Commit a script when a reviewer needs to rerun it or when the work outlives the session, and add it as a `package.json` script so knip counts it as an entry. Keep any other one-off script in a temporary directory outside the repository, and remove a committed script when its workflow ends. Fail on unmapped input. Back up before a destructive run.
6
+
7
+ Print summaries: counts, first errors. Verify what owned checks omit, such as the running app.
@@ -0,0 +1,7 @@
1
+ # Sibling Style
2
+
3
+ Treat each application, package, or component as one author's work. Match representative siblings for layout, naming, exports, helpers, composition, errors, and abstraction. Use the language's standard idiom when no sibling exists. Name the convention when a conflict affects the design.
4
+
5
+ Keep execution flow collocated. Extract a helper when multiple callers share it. One term per concept; parallel symbols share grammatical shape. Comments follow `code-comments.md`.
6
+
7
+ Keep edits in the requested component, its call sites, and the workspace metadata it needs. Cross-project convention changes are in scope only when the task names them.
@@ -0,0 +1 @@
1
+ @../../AGENTS.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/automation-owned-checks.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/code-comments.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/current-state-authority.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/demand-driven-configuration.md
@@ -0,0 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "package.json"
4
+ - "pnpm-lock.yaml"
5
+ - "pnpm-workspace.yaml"
6
+ ---
7
+
8
+ # Dependencies
9
+
10
+ Add a dependency when active code, tests, build tooling, or a documented contract uses it, and native or existing project capabilities do not. Assess cadence, license, advisories, compatibility, and transitives first. Place it in the manifest of the project that imports it, in the ecosystem category that matches its role.
11
+
12
+ Resolve a vulnerability that Dependabot or OSV-Scanner reports by upgrading the dependency. For a transitive dependency, add a pnpm override in `pnpm-workspace.yaml`. When no fix exists, record the accepted risk as `no-suppression.md` describes.
@@ -0,0 +1 @@
1
+ @../../.agents/rules/diagnostics-and-error-handling.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/git-workflow.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/no-suppression.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/script-driven-changes.md
@@ -0,0 +1 @@
1
+ @../../.agents/rules/single-author-style.md
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "permissions": {
4
+ "deny": [
5
+ "Edit(./.agents/rules/**)",
6
+ "Edit(./.claude/rules/**)",
7
+ "Edit(./.cursor/rules/**)",
8
+ "Edit(./.commitlintrc.json)",
9
+ "Edit(./.prettierrc.json)",
10
+ "Edit(./prettier.config.ts)",
11
+ "Edit(./eslint.config.ts)",
12
+ "Edit(./.agents/rules.json)",
13
+ "Edit(./.agents/hooks/**)",
14
+ "Edit(./.claude/settings.json)",
15
+ "Edit(./.claude/settings.local.json)",
16
+ "Edit(./.cursor/hooks.json)",
17
+ "Edit(./.cursor/hooks/**)",
18
+ "Edit(./scripts/sync-agent-rules.ts)",
19
+ "Edit(./scripts/check-agent-gates.ts)",
20
+ "Edit(./.husky/**)",
21
+ "Edit(./.lintstagedrc.json)",
22
+ "Edit(./.gitleaks.toml)",
23
+ "Bash(gh repo delete *)",
24
+ "Bash(gh issue delete *)",
25
+ "Bash(gh release delete *)",
26
+ "Bash(git *--no-verify*)",
27
+ "Bash(git commit -n*)",
28
+ "Bash(git *core.hooksPath*)",
29
+ "Bash(git *core.hookspath*)",
30
+ "Bash(git commit-tree*)",
31
+ "Bash(git update-ref*)",
32
+ "Bash(*HUSKY=0*)"
33
+ ]
34
+ },
35
+ "hooks": {
36
+ "PreToolUse": [
37
+ {
38
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
39
+ "hooks": [
40
+ {
41
+ "type": "command",
42
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.agents/hooks/agent-suppression-gate.sh",
43
+ "timeout": 10,
44
+ "onFailure": "block"
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "matcher": "Bash",
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.agents/hooks/agent-shell-gate.sh",
54
+ "timeout": 10,
55
+ "onFailure": "block"
56
+ }
57
+ ]
58
+ }
59
+ ]
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@next-friday/commitlint-config-friday"]
3
+ }
@@ -0,0 +1,17 @@
1
+ # Review rules
2
+
3
+ Review pull requests against the project's agent guide and rules:
4
+
5
+ - [Agent guide](../AGENTS.md)
6
+ - [Owned Checks](../.agents/rules/automation-owned-checks.md)
7
+ - [Required Comments](../.agents/rules/code-comments.md)
8
+ - [Current State](../.agents/rules/current-state-authority.md)
9
+ - [Demand-Driven Configuration](../.agents/rules/demand-driven-configuration.md)
10
+ - [Dependencies](../.agents/rules/dependency-security.md)
11
+ - [Test Admission](../.agents/rules/deterministic-testing.md)
12
+ - [Diagnostics](../.agents/rules/diagnostics-and-error-handling.md)
13
+ - [Git Workflow](../.agents/rules/git-workflow.md)
14
+ - [Findings](../.agents/rules/no-suppression.md)
15
+ - [Scripts](../.agents/rules/script-driven-changes.md)
16
+ - [Sibling Style](../.agents/rules/single-author-style.md)
17
+ - [TypeScript-First](../.agents/rules/typescript-first.md)