@thedecipherist/claude-packs 0.0.0-stage → 1.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.
Files changed (827) hide show
  1. package/CHANGELOG.md +680 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -2
  4. package/bin/claude-packs.mjs +204 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +92 -0
  7. package/configurations/full-stack/config.json +152 -0
  8. package/configurations/go/config.json +96 -0
  9. package/configurations/node/config.json +100 -0
  10. package/configurations/python/config.json +97 -0
  11. package/configurations/synth-creator/config.json +148 -0
  12. package/global/hooks/gnu-shell.mjs +131 -0
  13. package/global/hooks/lib/program-read.mjs +196 -0
  14. package/global/hooks/lib/program-writes.mjs +177 -0
  15. package/global/hooks/lib/shell-writes.mjs +293 -0
  16. package/global/hooks/shell-traps.mjs +79 -0
  17. package/global/hooks/version-pin-guard.mjs +280 -0
  18. package/global/skills/latest-versions/SKILL.md +49 -0
  19. package/lib/backup.mjs +34 -0
  20. package/lib/claude-md.mjs +58 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +86 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +115 -0
  26. package/lib/fix-plan.mjs +71 -0
  27. package/lib/git-tokens.mjs +64 -0
  28. package/lib/global.mjs +125 -0
  29. package/lib/hook-proof.mjs +28 -0
  30. package/lib/hook-timing.mjs +42 -0
  31. package/lib/hover-gate.mjs +29 -0
  32. package/lib/kit-lock.mjs +149 -0
  33. package/lib/layout.mjs +75 -0
  34. package/lib/logs.mjs +69 -0
  35. package/lib/manifest.mjs +24 -0
  36. package/lib/new.mjs +49 -0
  37. package/lib/once-only-report.mjs +54 -0
  38. package/lib/pack-manifest.mjs +20 -0
  39. package/lib/packs.mjs +107 -0
  40. package/lib/planner.mjs +59 -0
  41. package/lib/repeats.mjs +65 -0
  42. package/lib/repo-weight.mjs +28 -0
  43. package/lib/rules.mjs +40 -0
  44. package/lib/scan-detect.mjs +94 -0
  45. package/lib/scan.mjs +168 -0
  46. package/lib/selftest-cache.mjs +39 -0
  47. package/lib/settings.mjs +138 -0
  48. package/lib/shape-cli.mjs +76 -0
  49. package/lib/shape.mjs +4 -0
  50. package/lib/suggest.mjs +18 -0
  51. package/lib/switches.mjs +44 -0
  52. package/lib/symmetry-doctor.mjs +26 -0
  53. package/lib/sync.mjs +130 -0
  54. package/package.json +34 -4
  55. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  56. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  57. package/packs/ai/checks/model-id-from-config.json +28 -0
  58. package/packs/ai/checks/model-output-validated.json +26 -0
  59. package/packs/ai/pack.json +38 -0
  60. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  61. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  62. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  63. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  64. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  65. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  66. package/packs/audio-core/checks/keep-werror.json +12 -0
  67. package/packs/audio-core/checks/no-fast-math.json +14 -0
  68. package/packs/audio-core/checks/no-locale-change.json +12 -0
  69. package/packs/audio-core/checks/no-march-native.json +14 -0
  70. package/packs/audio-core/clang-tidy +28 -0
  71. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  72. package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
  73. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  74. package/packs/audio-core/pack.json +187 -0
  75. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  76. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  77. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  78. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  79. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  80. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  81. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  82. package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
  83. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  84. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  85. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  86. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  87. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  88. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  89. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  90. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  91. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  92. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  93. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  94. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  95. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  96. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  97. package/packs/audio-core/rules/output-limiter.md +23 -0
  98. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  99. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  100. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  101. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  102. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  103. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  104. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  105. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  106. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  107. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  108. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  109. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  110. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  111. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  112. package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
  113. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  114. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  115. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  116. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
  117. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  118. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  119. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  120. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  121. package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
  122. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  123. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  124. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  125. package/packs/audio-core/tools/tone-report.mjs +47 -0
  126. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  127. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  128. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  129. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  130. package/packs/base/checks/one-walk-per-template.json +23 -0
  131. package/packs/base/checks/stub-reports-success.json +31 -0
  132. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  133. package/packs/base/hooks/brief-guard.mjs +96 -0
  134. package/packs/base/hooks/code-shape.mjs +95 -0
  135. package/packs/base/hooks/dispatch-once.mjs +87 -0
  136. package/packs/base/hooks/file-size-guard.mjs +61 -0
  137. package/packs/base/hooks/folder-law.mjs +74 -0
  138. package/packs/base/hooks/git-guard.mjs +289 -0
  139. package/packs/base/hooks/lesson-checks.mjs +174 -0
  140. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  141. package/packs/base/hooks/lib/code-shapes.mjs +137 -0
  142. package/packs/base/hooks/lib/content.mjs +14 -0
  143. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  144. package/packs/base/hooks/lib/dup-index.mjs +199 -0
  145. package/packs/base/hooks/lib/dup.mjs +127 -0
  146. package/packs/base/hooks/lib/elements.mjs +87 -0
  147. package/packs/base/hooks/lib/event-log.mjs +67 -0
  148. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  149. package/packs/base/hooks/lib/git-adds.mjs +20 -0
  150. package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
  151. package/packs/base/hooks/lib/glob.mjs +33 -0
  152. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  153. package/packs/base/hooks/lib/house.py +77 -0
  154. package/packs/base/hooks/lib/languages.mjs +66 -0
  155. package/packs/base/hooks/lib/layout.mjs +54 -0
  156. package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
  157. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  158. package/packs/base/hooks/lib/program-read.mjs +196 -0
  159. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  160. package/packs/base/hooks/lib/project.mjs +81 -0
  161. package/packs/base/hooks/lib/prose.mjs +11 -0
  162. package/packs/base/hooks/lib/pushed.mjs +201 -0
  163. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  164. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  165. package/packs/base/hooks/lib/selftest.mjs +45 -0
  166. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  167. package/packs/base/hooks/lib/shape.mjs +294 -0
  168. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  169. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  170. package/packs/base/hooks/lib/turn.mjs +81 -0
  171. package/packs/base/hooks/lib/writes.mjs +71 -0
  172. package/packs/base/hooks/making-check.mjs +243 -0
  173. package/packs/base/hooks/making.mjs +106 -0
  174. package/packs/base/hooks/once-only.mjs +150 -0
  175. package/packs/base/hooks/placement-law.mjs +84 -0
  176. package/packs/base/hooks/repo-litter.mjs +75 -0
  177. package/packs/base/hooks/reuse-hint.mjs +75 -0
  178. package/packs/base/hooks/run-hooks.mjs +132 -0
  179. package/packs/base/hooks/secrets-guard.mjs +99 -0
  180. package/packs/base/hooks/shape-law.mjs +133 -0
  181. package/packs/base/hooks/suite-once.mjs +80 -0
  182. package/packs/base/hooks/template-law.mjs +148 -0
  183. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  184. package/packs/base/pack.json +214 -0
  185. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  186. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  187. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  188. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  189. package/packs/base/rules/finished-work-lands.md +17 -0
  190. package/packs/base/rules/git-right-first-time.md +19 -0
  191. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  192. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  193. package/packs/base/rules/making-line-first.md +14 -0
  194. package/packs/base/rules/open-items-tagged.md +17 -0
  195. package/packs/base/rules/plan-and-logs.md +22 -0
  196. package/packs/base/rules/references-use-anchors.md +19 -0
  197. package/packs/base/rules/refusals-are-judged.md +18 -0
  198. package/packs/base/rules/reuse-before-writing.md +15 -0
  199. package/packs/base/rules/secrets-never-committed.md +16 -0
  200. package/packs/base/rules/skill-writing.md +23 -0
  201. package/packs/base/rules/split-before-the-limit.md +14 -0
  202. package/packs/base/rules/subagent-dispatch.md +26 -0
  203. package/packs/base/rules/test-only-what-changed.md +17 -0
  204. package/packs/base/rules/test-the-template-once.md +22 -0
  205. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  206. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  207. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  208. package/packs/base/skills/land-branch/SKILL.md +24 -0
  209. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  210. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  211. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  212. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  213. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  214. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  215. package/packs/base/tools/shape-symmetry.mjs +178 -0
  216. package/packs/base/tools/test-verdict.mjs +75 -0
  217. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  218. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  219. package/packs/ci/pack.json +21 -0
  220. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  221. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  222. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  223. package/packs/content-site/checks/img-has-size.json +20 -0
  224. package/packs/content-site/checks/page-head-complete.json +26 -0
  225. package/packs/content-site/pack.json +22 -0
  226. package/packs/content-site/rules/content-has-one-source.md +23 -0
  227. package/packs/content-site/rules/media-is-sized.md +24 -0
  228. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  229. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  230. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  231. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  232. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  233. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  234. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  235. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  236. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  237. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  238. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  239. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  240. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  241. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  242. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  243. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  244. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  245. package/packs/cpp/pack.json +22 -0
  246. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  247. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  248. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  249. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  250. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  251. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  252. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  253. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  254. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  255. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  256. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  257. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  258. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  259. package/packs/docker/checks/compose-image-pinned.json +14 -0
  260. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  261. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  262. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  263. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  264. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  265. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  266. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  267. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  268. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  269. package/packs/docker/checks/docker-exec-form.json +16 -0
  270. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  271. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  272. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  273. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  274. package/packs/docker/checks/health-checks-nothing.json +25 -0
  275. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  276. package/packs/docker/checks/no-latest-base.json +13 -0
  277. package/packs/docker/checks/no-secret-in-image.json +13 -0
  278. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  279. package/packs/docker/checks/node-env-before-install.json +13 -0
  280. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  281. package/packs/docker/pack.json +46 -0
  282. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  283. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  284. package/packs/docker/rules/docker-exec-form.md +19 -0
  285. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  286. package/packs/docker/rules/dockerfile-production.md +25 -0
  287. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  288. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  289. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  290. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  291. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  292. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  293. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  294. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  295. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  296. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  297. package/packs/dynamic-data/pack.json +23 -0
  298. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  299. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  300. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  301. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  302. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  303. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  304. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  305. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  306. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  307. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  308. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  309. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  310. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  311. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  312. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  313. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  314. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  315. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  316. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  317. package/packs/fastify/checks/one-error-body.json +26 -0
  318. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  319. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  320. package/packs/fastify/checks/sec-headers-present.json +28 -0
  321. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  322. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  323. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  324. package/packs/fastify/pack.json +41 -0
  325. package/packs/fastify/rules/credentials-and-input.md +23 -0
  326. package/packs/fastify/rules/security-one-switch.md +28 -0
  327. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  328. package/packs/fastify/rules/tests-use-inject.md +13 -0
  329. package/packs/fastify/rules/three-layers.md +20 -0
  330. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  331. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  332. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  333. package/packs/full-stack/pack.json +76 -0
  334. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  335. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  336. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  337. package/packs/go/pack.json +21 -0
  338. package/packs/go/rules/go-errors-are-handled.md +22 -0
  339. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  340. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  341. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  342. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  343. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  344. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  345. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  346. package/packs/gpu-painter/pack.json +91 -0
  347. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  348. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  349. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  350. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  351. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  352. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  353. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  354. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  355. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  356. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  357. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  358. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  359. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  360. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  361. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  362. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  363. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  364. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  365. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  366. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  367. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  368. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  369. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  370. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  371. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  372. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  373. package/packs/hook-proof/pack.json +38 -0
  374. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  375. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  376. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  377. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  378. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  379. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  380. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  381. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  382. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  383. package/packs/host-agent/pack.json +22 -0
  384. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  385. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  386. package/packs/html-ui/checks/external-link-rel.json +21 -0
  387. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  388. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  389. package/packs/html-ui/checks/no-inline-style.json +23 -0
  390. package/packs/html-ui/pack.json +33 -0
  391. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  392. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  393. package/packs/http-api/checks/api-path-versioned.json +31 -0
  394. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  395. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  396. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  397. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  398. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  399. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  400. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  401. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  402. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  403. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  404. package/packs/http-api/checks/guard-fails-open.json +28 -0
  405. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  406. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  407. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  408. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  409. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  410. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  411. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  412. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  413. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  414. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  415. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  416. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  417. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  418. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  419. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  420. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  421. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  422. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  423. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  424. package/packs/http-api/pack.json +45 -0
  425. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  426. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  427. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  428. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  429. package/packs/http-api/rules/health-and-correlation.md +17 -0
  430. package/packs/http-api/rules/one-error-contract.md +17 -0
  431. package/packs/http-api/rules/routes-are-thin.md +17 -0
  432. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  433. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  434. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  435. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  436. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  437. package/packs/instrument-shell/pack.json +22 -0
  438. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  439. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  440. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  441. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  442. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  443. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  444. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  445. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  446. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  447. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  448. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  449. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  450. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  451. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  452. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  453. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  454. package/packs/js-ts/checks/no-any.json +27 -0
  455. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  456. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  457. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  458. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  459. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  460. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  461. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  462. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  463. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  464. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  465. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  466. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  467. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  468. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  469. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  470. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  471. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  472. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  473. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  474. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  475. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  476. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  477. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  478. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  479. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  480. package/packs/js-ts/pack.json +84 -0
  481. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  482. package/packs/js-ts/rules/env-through-config.md +36 -0
  483. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  484. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  485. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  486. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  487. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  488. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  489. package/packs/js-ts/rules/no-any.md +34 -0
  490. package/packs/js-ts/rules/no-console.md +30 -0
  491. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  492. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  493. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  494. package/packs/js-ts/rules/one-logger.md +24 -0
  495. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  496. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  497. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  498. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  499. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  500. package/packs/js-ts/rules/small-functions.md +20 -0
  501. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  502. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  503. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  504. package/packs/js-ts/rules/typed-exports.md +27 -0
  505. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  506. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  507. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  508. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  509. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  510. package/packs/midi/checks/midi-realtime-first.json +27 -0
  511. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  512. package/packs/midi/checks/position-not-integrated.json +25 -0
  513. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  514. package/packs/midi/checks/sysex-length-literal.json +23 -0
  515. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  516. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  517. package/packs/midi/pack.json +22 -0
  518. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  519. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  520. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  521. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  522. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  523. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  524. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  525. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  526. package/packs/mongo/checks/bulk-over-many.json +28 -0
  527. package/packs/mongo/checks/created-at-immutable.json +25 -0
  528. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  529. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  530. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  531. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  532. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  533. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  534. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  535. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  536. package/packs/mongo/checks/no-find-reads.json +26 -0
  537. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  538. package/packs/mongo/checks/no-mongoose.json +23 -0
  539. package/packs/mongo/checks/no-negation-delete.json +26 -0
  540. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  541. package/packs/mongo/checks/objectid-equals.json +26 -0
  542. package/packs/mongo/checks/update-then-reread.json +24 -0
  543. package/packs/mongo/checks/watch-not-poll.json +25 -0
  544. package/packs/mongo/pack.json +52 -0
  545. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  546. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  547. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  548. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  549. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  550. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  551. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  552. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  553. package/packs/multi-tenant/pack.json +22 -0
  554. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  555. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  556. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  557. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  558. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  559. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  560. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  561. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  562. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  563. package/packs/nginx/pack.json +43 -0
  564. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  565. package/packs/nginx/rules/nginx-production.md +26 -0
  566. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  567. package/packs/python/checks/py-no-bare-except.json +18 -0
  568. package/packs/python/checks/py-no-eval.json +18 -0
  569. package/packs/python/checks/py-no-mutable-default.json +18 -0
  570. package/packs/python/checks/py-no-shell-true.json +18 -0
  571. package/packs/python/checks/py-no-star-import.json +18 -0
  572. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  573. package/packs/python/checks/py-none-by-is.json +18 -0
  574. package/packs/python/checks/py-open-with-with.json +18 -0
  575. package/packs/python/checks/py-requests-timeout.json +18 -0
  576. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  577. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  578. package/packs/python/pack.json +26 -0
  579. package/packs/python/rules/python-errors-and-resources.md +26 -0
  580. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  581. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  582. package/packs/react/checks/dialog-needs-role.json +28 -0
  583. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  584. package/packs/react/checks/icon-button-needs-name.json +24 -0
  585. package/packs/react/checks/label-needs-a-field.json +24 -0
  586. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  587. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  588. package/packs/react/pack.json +20 -0
  589. package/packs/react/rules/react-accessible-controls.md +20 -0
  590. package/packs/react/rules/react-state-and-effects.md +23 -0
  591. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  592. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  593. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  594. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  595. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  596. package/packs/reverse-engineering/pack.json +35 -0
  597. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  598. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  599. package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
  600. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  601. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  602. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  603. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  604. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  605. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  606. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  607. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
  608. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  609. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  610. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  611. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  612. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  613. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  614. package/packs/shell/checks/sh-mktemp.json +18 -0
  615. package/packs/shell/checks/sh-no-backticks.json +18 -0
  616. package/packs/shell/checks/sh-no-eval.json +18 -0
  617. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  618. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  619. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  620. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  621. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  622. package/packs/shell/checks/sh-strict-mode.json +20 -0
  623. package/packs/shell/pack.json +28 -0
  624. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  625. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  626. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  627. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  628. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  629. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  630. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  631. package/packs/shell-hooks/pack.json +36 -0
  632. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  633. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  634. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  635. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  636. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  637. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  638. package/packs/sound-design/pack.json +22 -0
  639. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  640. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  641. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  642. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  643. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  644. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  645. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  646. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  647. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  648. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  649. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  650. package/packs/stellix/checks/one-seeded-random.json +16 -0
  651. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  652. package/packs/stellix/checks/world-no-libm.json +15 -0
  653. package/packs/stellix/checks/world-no-unordered.json +10 -0
  654. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  655. package/packs/stellix/pack.json +61 -0
  656. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  657. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  658. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  659. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  660. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  661. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  662. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  663. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  664. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  665. package/packs/stored-text/pack.json +41 -0
  666. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  667. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  668. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  669. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  670. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  671. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  672. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  673. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  674. package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
  675. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  676. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  677. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  678. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  679. package/packs/synth-creator/pack.json +136 -0
  680. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  681. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  682. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  683. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  684. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  685. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  686. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  687. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
  688. package/packs/synth-creator/tools/freeze.mjs +36 -0
  689. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  690. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  691. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  692. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  693. package/packs/tui/checks/tui-width-is-state.json +33 -0
  694. package/packs/tui/pack.json +44 -0
  695. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  696. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  697. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  698. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  699. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  700. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  701. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  702. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  703. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  704. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  705. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  706. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  707. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  708. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  709. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  710. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  711. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  712. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  713. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  714. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  715. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  716. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  717. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  718. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  719. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  720. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  721. package/packs/ui-craft/pack.json +43 -0
  722. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  723. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  724. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  725. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  726. package/packs/ui-craft/rules/interface-copy.md +26 -0
  727. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  728. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  729. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  730. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  731. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  732. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  733. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  734. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  735. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  736. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  737. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  738. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  739. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  740. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  741. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  742. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  743. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  744. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  745. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  746. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  747. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  748. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  749. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  750. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  751. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  752. package/packs/web/checks/client-no-api-key.json +29 -0
  753. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  754. package/packs/web/checks/client-no-server-import.json +31 -0
  755. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  756. package/packs/web/checks/css-no-inline-style.json +30 -0
  757. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  758. package/packs/web/checks/key-from-index.json +23 -0
  759. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  760. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  761. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  762. package/packs/web/checks/rcss-button-cursor.json +30 -0
  763. package/packs/web/checks/rcss-no-100vw.json +21 -0
  764. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  765. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  766. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  767. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  768. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  769. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  770. package/packs/web/pack.json +73 -0
  771. package/packs/web/rules/accessibility.md +26 -0
  772. package/packs/web/rules/client-boundaries.md +23 -0
  773. package/packs/web/rules/production-build.md +22 -0
  774. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  775. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  776. package/packs/web/rules/tailwind-v4.md +23 -0
  777. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  778. package/packs/web/rules/web-performance.md +21 -0
  779. package/packs/web/skills/design-direction/SKILL.md +30 -0
  780. package/packs/web/skills/design-review/SKILL.md +28 -0
  781. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  782. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  783. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  784. package/packs/websocket/checks/no-token-in-query.json +26 -0
  785. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  786. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  787. package/packs/websocket/pack.json +21 -0
  788. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  789. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  790. package/packs/writing/checks/no-copula-dodging.json +18 -0
  791. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  792. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  793. package/packs/writing/checks/no-participial-opener.json +18 -0
  794. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  795. package/packs/writing/checks/no-slop-words.json +18 -0
  796. package/packs/writing/checks/no-stock-connectors.json +18 -0
  797. package/packs/writing/checks/no-time-or-date.json +27 -0
  798. package/packs/writing/checks/no-trailing-ing.json +18 -0
  799. package/packs/writing/checks/no-vague-attribution.json +18 -0
  800. package/packs/writing/pack.json +43 -0
  801. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  802. package/packs/writing/rules/cut-the-padding.md +20 -0
  803. package/packs/writing/rules/direct-voice.md +22 -0
  804. package/packs/writing/rules/no-cargo-cult.md +16 -0
  805. package/packs/writing/rules/no-scaffolding.md +19 -0
  806. package/packs/writing/rules/no-stock-language.md +27 -0
  807. package/packs/writing/rules/no-time-or-date.md +18 -0
  808. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  809. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  810. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  811. package/packs/writing/skills/humanize/SKILL.md +28 -0
  812. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  813. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  814. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  815. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  816. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  817. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  818. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  819. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  820. package/packs/zod/hooks/schemas-only.mjs +53 -0
  821. package/packs/zod/pack.json +44 -0
  822. package/packs/zod/rules/env-shapes-only.md +14 -0
  823. package/packs/zod/rules/zod-4-schemas.md +14 -0
  824. package/profiles/stellix.layout.json +315 -0
  825. package/profiles/sub37.layout.json +247 -0
  826. package/prompts/scan-review.md +40 -0
  827. package/prompts/survey.md +82 -0
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: render-stored-text
3
+ description: Use when a page shows text from a database, when markdown, code or line endings look wrong on a page, or when stored-text checks refuse a write.
4
+ ---
5
+
6
+ # Build and prove the one renderer for stored text
7
+
8
+ <constraints>
9
+ Always do:
10
+ 1. Look for `stored-text.*` first. If it exists, call it.
11
+ 2. Otherwise create it, choosing the libraries with `latest-versions`. Next.js or React: one `<StoredText source={...} />` server component (a markdown component with sanitize and highlight plugins), so no `dangerouslySetInnerHTML` is needed. Server-rendered templates: `renderStored(text): string`, inserted with the template's explicit raw form.
12
+ 3. Keep the steps in order: `text.replace(/\r\n?/g, '\n')`; markdown with raw HTML off; highlight; sanitize last, allowing the highlighter's classes and spans, and no `style`, `script` or event attribute. Links get `rel="noopener noreferrer"`, and only `http`, `https` and `mailto` survive.
13
+ 4. For a tool's or terminal's output, give the renderer a second mode: a fenced plain block, where each line keeps only what follows its last `\r`.
14
+ 5. Give the code block its look in the stylesheet: a highlight theme from the design tokens, `overflow-x: auto`, and `white-space: pre-wrap` for prose that keeps its breaks.
15
+ 6. Prove it with one fixture row, in a test and on the rendered page:
16
+ - `a\r\nb\rc` shows three lines and no stray character;
17
+ - `**bold**` and a list render as markup;
18
+ - a ```` ```ts ```` block has token spans, and a ```` ```nosuchlang ```` block is plain and escaped;
19
+ - `<script>alert(1)</script>`, `<img src=x onerror=alert(1)>` and `[x](javascript:alert(1))` come out harmless.
20
+
21
+ Never do:
22
+ - Add a second path for "just this one page".
23
+ - Call a page done without opening it with the fixture row.
24
+ </constraints>
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "timing-from-synthcore",
3
+ "why": "Tempo, MIDI clock and transport are written once, in synthcore. A unit that reads the play head or counts clock bytes itself is a second clock, and two clocks drift. Go through core::CoreHost.",
4
+ "rule": ".claude/rules/timing-from-synthcore.md",
5
+ "globs": [
6
+ "**/*.cpp",
7
+ "**/*.cc",
8
+ "**/*.h",
9
+ "**/*.hpp",
10
+ "**/*.mm"
11
+ ],
12
+ "exclude": [
13
+ "**/tests/**",
14
+ "**/test/**",
15
+ "**/probes/**",
16
+ "**/build/**",
17
+ "**/_deps/**",
18
+ "**/JUCE/**"
19
+ ],
20
+ "pattern": "\\bgetPlayHead\\s*\\(\\s*\\)\\s*(?:->|\\.)|\\bisMidiClock\\s*\\(",
21
+ "code": true,
22
+ "bad": "const auto pos = getPlayHead()->getPosition();",
23
+ "good": "host.readTransport (getPlayHead(), midi, n);"
24
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "ui-drawn-in-code",
3
+ "why": "Nothing is pre-rendered: the UI is drawn in code, so it scales to any size and a control changes in one place. No images, sprites or embedded assets.",
4
+ "globs": [
5
+ "**/*.cpp",
6
+ "**/*.cc",
7
+ "**/*.h",
8
+ "**/*.hpp",
9
+ "**/*.mm"
10
+ ],
11
+ "exclude": [
12
+ "**/tests/**",
13
+ "**/test/**",
14
+ "**/probes/**",
15
+ "**/build/**",
16
+ "**/_deps/**",
17
+ "**/JUCE/**"
18
+ ],
19
+ "pattern": "\\bdrawImage(?:At|Within|Transformed)?\\s*\\(|\\bImageCache::|\\bBinaryData::|\\bImageFileFormat::",
20
+ "code": true,
21
+ "bad": "g.drawImageAt (ImageCache::getFromMemory (BinaryData::knob_png, BinaryData::knob_pngSize), 0, 0);",
22
+ "good": "g.fillEllipse (bounds.reduced (2.0f));"
23
+ }
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ // build-guard (PreToolUse, Bash): no heavy job takes the whole machine.
3
+ //
4
+ // "nothing should be able to take the entire system". Five agents each compiling with -j18 at once put
5
+ // ~90 jobs on 18 cores and every build took hours. Every compile and test run goes through the machine-wide queue
6
+ // ~/.claude/bin/buildq (two at a time). This refuses (exit 2) a heavy command started any other way, a polling loop, an
7
+ // agent's build or test run of everything, and any build from an agent started as `KIND: light`
8
+ // (~/.claude/agent-cap-state.json keeps the kinds).
9
+ //
10
+ // Allowed: a command run as `buildq <cmd>` (or `~/.claude/bin/buildq <cmd>`), or through a project's own wrapper that
11
+ // calls buildq (for example `bash tools/build.sh ...`, the `build` and `test` commands in .claude/layout.json).
12
+ // Configuring (`cmake -B build`) is light and allowed. Text that only MENTIONS a command (inside a heredoc, a quoted
13
+ // string, a commit message) is never read as running it. A project with its own PreToolUse dispatcher
14
+ // (.claude/hooks/pre.js) is left to it.
15
+ //
16
+ // node build-guard.mjs --selftest
17
+ import { execFileSync } from 'node:child_process';
18
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';
19
+ import { homedir, tmpdir } from 'node:os';
20
+ import { isAbsolute, join } from 'node:path';
21
+ import { asHook, isMain } from './lib/hook-log.mjs';
22
+
23
+ const WRAPPERS = ['buildq', 'build.sh'];
24
+ const base = (p) => p.split('/').pop();
25
+
26
+ export function heavy(w) {
27
+ const exe = base(w[0]);
28
+ // A PROGRAM RUN STRAIGHT FROM A BUILD FOLDER (a probe, a prototype, a test binary): one bank render took ~8 cores
29
+ if (/(^|\/)(build[^/]*|[^/]*_artefacts)\//.test(w[0]) || /^[A-Z]\w*(Probe|Proto|Tests?)$/.test(exe)) return true;
30
+ if (exe === 'cmake') return w.includes('--build');
31
+ if (exe === 'ninja' && w.slice(1).some((x) => ['-t', '--version', '-n'].includes(x) || x.startsWith('-t'))) return false; // a question to the build graph
32
+ if (exe === 'ctest' && w.slice(1).some((x) => ['--version', '-N', '--show-only'].includes(x) || x.startsWith('--show-only'))) return false; // listing tests
33
+ if (['ctest', 'ninja', 'xcodebuild', 'msbuild', 'gradle', 'gradlew', 'mvn', 'make', 'gmake'].includes(exe)) return true;
34
+ if (['cargo', 'swift', 'go', 'bazel', 'bazelisk'].includes(exe)) return w.length > 1 && ['build', 'test', 'bench', 'run', 'install'].includes(w[1]);
35
+ return false;
36
+ }
37
+
38
+ // Heredoc bodies are text written to a file or fed to a program, never commands of this shell.
39
+ export function stripHeredocs(cmd) {
40
+ const out = [];
41
+ const lines = cmd.split('\n');
42
+ for (let i = 0; i < lines.length; i += 1) {
43
+ out.push(lines[i]);
44
+ const m = lines[i].match(/<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/);
45
+ if (m) { i += 1; while (i < lines.length && lines[i].trim() !== m[1]) i += 1; }
46
+ }
47
+ return out.join('\n');
48
+ }
49
+
50
+ const blankQuotes = (s) => s.replace(/'[^']*'|"(?:\\.|[^"\\])*"/g, "''");
51
+ // Quoted text (a commit note, a bug title) keeps its length but loses its words, so a note naming the test runner is not a test run
52
+ const maskQuotes = (s) => s.replace(/'[^']*'|"(?:\\.|[^"\\])*"/g, (q) => q[0] + ' '.repeat(q.length - 2) + q[q.length - 1]);
53
+ const words = (part) => part.replace(/(^|\s)#.*$/, '').trim().split(/\s+/).filter(Boolean);
54
+
55
+ export function offending(cmd) {
56
+ for (const part of blankQuotes(stripHeredocs(cmd)).split(/&&|\|\||;|\||\n|\(|\)|`|\$\(/)) {
57
+ let w = words(part);
58
+ if ((w.length >= 2 && ['command', 'type', 'which', 'whence', 'hash'].includes(w[0]) && ['-v', '-V', '-p', '-a'].includes(w[1])) || ['which', 'type', 'whence'].includes(w[0])) continue;
59
+ while (w.length && (/^[A-Za-z_][A-Za-z0-9_]*=/.test(w[0]) || ['env', 'time', 'nice', 'caffeinate', 'exec', 'nohup', 'command', 'sudo'].includes(w[0]))) {
60
+ w = w.slice(1);
61
+ while (w.length && w[0].startsWith('-')) w = w.slice(1);
62
+ }
63
+ if (!w.length) continue;
64
+ if (WRAPPERS.includes(base(w[0])) || (['bash', 'sh', 'zsh'].includes(w[0]) && w.length > 1 && WRAPPERS.includes(base(w[1])))) continue;
65
+ if (heavy(w)) return w.slice(0, 3).join(' ');
66
+ }
67
+ return null;
68
+ }
69
+
70
+ // A sleep loop or a long sleep, in the commands this shell runs.
71
+ export function polling(cmd) {
72
+ const body = blankQuotes(stripHeredocs(cmd));
73
+ if (/\b(until|while)\b[^\n]*\bdo\b[\s\S]*\bsleep\b/.test(body) || /\b(until|while)\b[\s\S]*;\s*do\b[\s\S]*\bsleep\b/.test(body)) return 'a loop that sleeps';
74
+ for (const m of body.matchAll(/(?:^|[;&|(\n]|\bdo\b|\bthen\b)\s*sleep\s+([0-9.]+)/g)) if (Number(m[1]) > 5) return `sleep ${m[1]}`;
75
+ if (/for\s+\w+\s+in\s+\$\(seq[^)]*\)[\s\S]*\bsleep\b/.test(body)) return 'a loop that sleeps';
76
+ if (/\btail\s+(-\S*\s+)*-[a-zA-Z]*[fF]\b/.test(body)) return 'tail -f on a log (watching, not working)';
77
+ return null;
78
+ }
79
+
80
+ // Opening an .app or running the binary inside one (kept for the agent-jobs rule; the refusal itself is off: The user,
81
+ // 2026-10-11, agents may open the app; the fault was one agent doing it on repeat).
82
+ export function launchesApp(cmd) {
83
+ return /(^|[;&|(\s])open\s+(-[a-zA-Z]+\s+)*[^\s;&|]*\.app\b|(^|[;&|(\s])open\s+-a\s|\.app\/Contents\/MacOS\//.test(blankQuotes(stripHeredocs(cmd)));
84
+ }
85
+
86
+ // A build of everything, or a test run with no -R or one matching more than 30 tests. null when the scope is named.
87
+ export function tooWide(cmd, cwd = process.cwd(), countTests = ctestCount) {
88
+ const body = stripHeredocs(cmd);
89
+ const bare = maskQuotes(body);
90
+ const cd = body.match(/cd\s+(\S+)\s*&&/)?.[1];
91
+ const from = cd ? cd.replace(/^~(?=\/|$)/, homedir()) : cwd;
92
+ if (/(cmake\s+--build|tools\/build\.sh\s+build)\b/.test(bare)) {
93
+ // `build.sh build <one word>`: a build folder (`build.sh build <dir> [target ...]`) builds everything; a target name
94
+ // (`build.sh build [target ...]`) builds one (KWB-14: one target was read as a folder)
95
+ const one = body.match(/tools\/build\.sh\s+build\s+(\S+)\s*($|[;&|])/)?.[1];
96
+ if (/--target\s+all\b/.test(body) || (/cmake\s+--build/.test(body) && !body.includes('--target')) || (one && isFolder(one, from))) {
97
+ return 'a build of every target is refused.';
98
+ }
99
+ }
100
+ const m = bare.match(/(?:\bctest\b|tools\/build\.sh\s+test)([^;&|\n]*)/);
101
+ if (m) {
102
+ const end = m.index + m[0].length;
103
+ const args = body.slice(end - m[1].length, end);
104
+ if (args.includes('--full-gate') || args.includes('--changed')) return null;
105
+ const r = args.match(/-R\s+('([^']*)'|"([^"]*)"|(\S+))/);
106
+ if (!r) return 'a test run with no -R pattern runs the whole suite.';
107
+ const pat = r[2] ?? r[3] ?? r[4] ?? '';
108
+ const tdir = args.match(/--test-dir\s+(\S+)/)?.[1] ?? 'build';
109
+ const n = countTests(isAbsolute(tdir) ? tdir : join(from, tdir), pat);
110
+ if (n > 30) return `the -R pattern '${pat}' matches ${n} tests.`;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function isFolder(word, from) {
116
+ const p = word.replace(/^~(?=\/|$)/, homedir());
117
+ try { return word.includes('/') || statSync(isAbsolute(p) ? p : join(from, p)).isDirectory(); } catch { return false; }
118
+ }
119
+
120
+ function ctestCount(dir, pat) {
121
+ try {
122
+ const out = execFileSync('ctest', ['--test-dir', dir, '-N', '-R', pat], { encoding: 'utf8', timeout: 20000, stdio: ['ignore', 'pipe', 'ignore'] });
123
+ return Number(out.match(/Total Tests:\s*(\d+)/)?.[1] ?? 0);
124
+ } catch {
125
+ return 0;
126
+ }
127
+ }
128
+
129
+ function lightAgent(ev) {
130
+ try {
131
+ const st = JSON.parse(readFileSync(join(homedir(), '.claude/agent-cap-state.json'), 'utf8'));
132
+ return st[ev.session_id ?? 'default']?.kinds?.[ev.agent_id] === 'light';
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ function refuse(msg) {
139
+ process.stderr.write(`build-guard: ${msg}\n`);
140
+ return 2;
141
+ }
142
+
143
+ function main(text) {
144
+ let ev;
145
+ try { ev = JSON.parse(text); } catch { return 0; }
146
+ const project = process.env.CLAUDE_PROJECT_DIR ?? ev.cwd ?? '';
147
+ if (project && existsSync(join(project, '.claude', 'hooks', 'pre.js'))) return 0; // a project's own dispatcher wins
148
+ if (ev.tool_name !== 'Bash') return 0;
149
+ const cmd = String(ev.tool_input?.command ?? '');
150
+ const aid = ev.agent_id;
151
+ // AN AGENT BUILDS AND TESTS ONLY WHAT ITS CHANGE NEEDS (one agent ran all 425 tests on 1 core)
152
+ if (aid) {
153
+ const scope = tooWide(cmd, ev.cwd ?? process.cwd());
154
+ if (scope) {
155
+ return refuse(`${scope} Build only the targets your change needs (\`--target X --target Y\`), and run only its tests (\`-R 'pattern'\`, at most 30). ` +
156
+ 'The whole suite runs once, as the gate before landing, never while working.');
157
+ }
158
+ }
159
+ // NO POLLING (agents ran `until grep -q done log; do sleep 10` for minutes on end)
160
+ const poll = polling(cmd);
161
+ if (poll) {
162
+ return refuse(`polling refused (${poll}). Waiting in a sleep loop hides what you are doing and burns turns. Run the job in the FOREGROUND ` +
163
+ 'through the queue (buildq / tools/build.sh wait for a slot themselves; the Bash tool allows up to 10 minutes), or start it with ' +
164
+ 'run_in_background and you are notified when it ends.');
165
+ }
166
+ // A light agent may RUN an already-built program through the queue (a render), never compile or run a test suite
167
+ if (aid && /(cmake\s+--build|\bctest\b|\bmake\b|\bninja\b|xcodebuild|tools\/build\.sh\s+(build|test))/.test(maskQuotes(stripHeredocs(cmd))) && lightAgent(ev)) {
168
+ return refuse('this agent was started as KIND: light, and light agents do not build or run tests (at most 3 build agents run at once, ' +
169
+ '). Do the light part, and say in your report what needs building.');
170
+ }
171
+ const what = offending(cmd);
172
+ if (!what) return 0;
173
+ return refuse(`\`${what}\` straight from the shell is refused. Nothing may take the whole machine. Run it through the ` +
174
+ `machine-wide build queue, which runs two at a time and waits for a free slot by itself:\n buildq ${what} ... ` +
175
+ '(~/.claude/bin/buildq; buildq --status shows the slots)\nor the project\'s own wrapper if it has one (the build and test commands in ' +
176
+ '.claude/layout.json). Do not wrap it in a loop.');
177
+ }
178
+
179
+ function selftest() {
180
+ const bad = [];
181
+ const check = (name, fn, cases) => { for (const [cmd, want] of cases) if ((fn(cmd) !== null && fn(cmd) !== false) !== want) bad.push(`${name}: ${cmd.replace(/\n/g, ' / ')}`); };
182
+ check('offending', offending, [
183
+ ['cmake --build build --target X -j18', true], ['cd x && cmake --build build', true], ['ctest --test-dir build', true],
184
+ ['make -j8', true], ['ninja -C out', true], ['cargo build --release', true], ['xcodebuild -scheme A', true],
185
+ ['(cd w && ctest -j6)', true], ['FOO=1 time make', true],
186
+ ['buildq cmake --build build -j18', false], ['~/.claude/bin/buildq ctest --test-dir b', false],
187
+ ['bash tools/build.sh build build Synth_Standalone', false], ['cmake -B build -DX=1', false],
188
+ ['./build/SynthBankProbe_artefacts/RelWithDebInfo/SynthBankProbe --starter', true],
189
+ ['cd w && build/SynthFaceProbe_artefacts/RelWithDebInfo/SynthFaceProbe --pad', true],
190
+ ['buildq ./build/SynthBankProbe_artefacts/RelWithDebInfo/SynthBankProbe --starter', false],
191
+ ['ls build/', false], ['command -v ninja', false], ['ninja -C build -t inputs X', false], ['/opt/homebrew/bin/ninja --version', false],
192
+ ['ninja -C build X', true], ['ctest --test-dir build -N', false], ['ctest --test-dir build --show-only=json-v1', false], ['ctest --version', false],
193
+ ['which ninja', false], ['cargo fmt', false], ['go vet ./...', false], ["grep -n 'cmake --build' f.md", false], ["git commit -m 'make it'", false],
194
+ ["cat > f.py <<'EOF'\ncases = [(\"cmake --build b\", True)]\nmake -j8\nEOF\npython3 f.py", false],
195
+ ['python3 - <<EOF\nctest --test-dir b\nEOF', false], ['echo "run ctest later"', false],
196
+ ]);
197
+ check('polling', polling, [
198
+ ["until grep -qE 'done' log; do sleep 10; done", true], ['for i in $(seq 1 55); do grep -q x f && break; sleep 20; done', true],
199
+ ['while true; do\n sleep 15\ndone', true], ['sleep 180', true], ['sleep 2 && ls', false], ['echo sleep 60', false],
200
+ ["cat > f.sh <<'EOF'\nwhile true; do sleep 9; done\nEOF", false], ["grep -n 'sleep 30' x.sh", false],
201
+ ['timeout 585 tail -f /tmp/x.log', true], ['tail -F build.log', true], ['tail -20 build.log', false], ['tail -n 5 f', false],
202
+ ]);
203
+ check('launchesApp', launchesApp, [
204
+ ['open build/Synth_artefacts/RelWithDebInfo/Standalone/Synth.app', true], ['open -a Synth', true],
205
+ ['python3 tools/record_app_probe.py build/x/Synth.app/Contents/MacOS/Synth', true],
206
+ ['cd w && build/T.app/Contents/MacOS/Synth --record /tmp/x.mov &', true],
207
+ ['ls build/Synth_artefacts', false], ['open docs/report.md', false], ["echo 'open X.app'", false],
208
+ ]);
209
+ const dir = mkdtempSync(join(process.env.TMPDIR ?? tmpdir(), 'build-guard-'));
210
+ mkdirSync(join(dir, 'build'));
211
+ const wide = (cmd) => tooWide(cmd, dir, (_, pat) => (pat === 'record' ? 4 : 99));
212
+ check('tooWide', wide, [
213
+ ['cmake --build build --target all -j6', true], ['cd w && cmake --build build -j6', true], ['bash tools/build.sh build build', true],
214
+ // one word after build: a folder that exists (or a path) builds everything; a target name builds one (WB-90/95)
215
+ ['bash tools/build.sh build synth_block_probe', false], ['bash tools/build.sh build out/', true],
216
+ ['bash tools/build.sh build synth_slots_probe && echo ok', false],
217
+ ['bash tools/build.sh build build SynthFaceProbe Synth_Standalone', false], ['cmake --build build --target SynthBankProbe -j5', false],
218
+ ['bash tools/build.sh test build --output-on-failure', true], ['ctest --test-dir build -j3', true],
219
+ ["bash tools/build.sh test build -R 'record' --output-on-failure", false], ["ctest --test-dir build -R 'gate'", true],
220
+ // a note or title that names the test runner is not a test run; a real bare run after a quoted note still is
221
+ ['python3 tools/step.py save "the look gate hung inside ctest"', false], ["python3 tools/next_number.py wb --take 'ctest holds the turn'", false],
222
+ ['git commit -m "ctest: fix"', false], ['python3 tools/step.py save "note" && bash tools/build.sh test build', true],
223
+ ['bash tools/build.sh test build --full-gate', false], ['bash tools/build.sh test build --changed', false],
224
+ ]);
225
+ rmSync(dir, { recursive: true, force: true });
226
+ for (const b of bad) console.error(`build-guard selftest FAILED: ${b}`);
227
+ if (!bad.length) console.log('build-guard selftest: OK');
228
+ return bad.length ? 1 : 0;
229
+ }
230
+
231
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
232
+ if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('build-guard', main));
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ // build-sweep (SessionStart, SubagentStop): an agent worktree's build output is removed once the worktree has gone quiet.
3
+ //
4
+ // The user, 2026-11-01: "we should make [something] in claude-packs that automatically removes old build artifacts that are no
5
+ // longer needed". Every agent works in its own worktree under .claude/worktrees and builds the whole project there (3 to 15 GB);
6
+ // nothing removed them after the agent's work was merged, and two projects filled a 2 TB disk (283 GB in 88 worktrees and
7
+ // 642 GB in 178), until every command failed with ENOSPC.
8
+ //
9
+ // What goes: only the build output (layout.json "buildOutput", default build/**) of a worktree under .claude/worktrees that
10
+ // has been quiet for QUIET_MIN: no source file at depth 3 or less changed, and nothing written at the top of its build folder
11
+ // (.ninja_log, CMakeCache.txt: a build in progress writes there). Never the main checkout's build, never a source file, never a
12
+ // worktree itself (it may hold uncommitted work), never the worktree with the newest build (the one most likely being played),
13
+ // never a worktree named in layout.json "keepWorktrees". A build can always be made again; that is why only the build goes.
14
+ // At SessionStart it also says when the disk is low, so the next agent's build does not fill it.
15
+ // node build-sweep.mjs the hook (reads the event on stdin; sweeps in the background)
16
+ // node build-sweep.mjs --now [--dry-run] sweep this project now and say what went
17
+ // node build-sweep.mjs --selftest
18
+ import { spawn, execFileSync } from 'node:child_process';
19
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, statfsSync, utimesSync, writeFileSync } from 'node:fs';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { asHook, isMain } from './lib/hook-log.mjs';
24
+
25
+ const QUIET_MIN = Number(process.env.BUILD_SWEEP_QUIET_MIN ?? 120);
26
+ const LOW_GB = Number(process.env.BUILD_SWEEP_LOW_GB ?? 40);
27
+
28
+ /** the top-level folder name patterns of the build output, from layout.json "buildOutput" (default build/**) */
29
+ /** worktrees a project never sweeps (layout.json "keepWorktrees": names under .claude/worktrees, e.g. the build the user plays) */
30
+ export function keptNames(root) {
31
+ try { return JSON.parse(readFileSync(join(root, '.claude/layout.json'), 'utf8')).keepWorktrees ?? []; } catch { return []; }
32
+ }
33
+
34
+ export function buildNames(root) {
35
+ let globs = ['build/**'];
36
+ try { globs = JSON.parse(readFileSync(join(root, '.claude/layout.json'), 'utf8')).buildOutput ?? globs; } catch { /* no layout: the default */ }
37
+ return globs.map((g) => g.split('/')[0]).filter((n) => n && n !== '**')
38
+ .map((n) => new RegExp(`^${n.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.')}$`));
39
+ }
40
+
41
+ // the newest change in a worktree: its source files to depth 3 (build folders and .git left out), and the top of each build folder
42
+ function lastChange(dir, isBuild) {
43
+ let newest = 0;
44
+ const walk = (d, depth) => {
45
+ let entries = [];
46
+ try { entries = readdirSync(d, { withFileTypes: true }); } catch { return; }
47
+ for (const e of entries) {
48
+ const p = join(d, e.name);
49
+ if (depth === 0 && isBuild(e.name)) {
50
+ if (!e.isDirectory()) continue;
51
+ try { for (const f of readdirSync(p)) newest = Math.max(newest, statSync(join(p, f)).mtimeMs); } catch { /* gone */ }
52
+ continue;
53
+ }
54
+ if (e.name === '.git' || e.name === 'node_modules') continue;
55
+ try { newest = Math.max(newest, statSync(p).mtimeMs); } catch { continue; }
56
+ if (e.isDirectory() && depth < 2) walk(p, depth + 1);
57
+ }
58
+ };
59
+ walk(dir, 0);
60
+ return newest;
61
+ }
62
+
63
+ function sizeGb(p) {
64
+ try { return Number(execFileSync('du', ['-sk', p], { encoding: 'utf8', timeout: 120000 }).split('\t')[0]) / 1048576; } catch { return 0; }
65
+ }
66
+
67
+ /** sweep a project's quiet worktrees; returns [{worktree, folder, gb}] of what went (or would go, with dryRun) */
68
+ export function sweep(root, { now = Date.now(), quietMin = QUIET_MIN, dryRun = false, measure = sizeGb } = {}) {
69
+ const wts = join(root, '.claude/worktrees');
70
+ if (!existsSync(wts)) return [];
71
+ const names = buildNames(root);
72
+ const isBuild = (n) => names.some((r) => r.test(n));
73
+ const gone = [], keep = new Set(keptNames(root));
74
+ // the newest finished build (of the quiet worktrees) is always kept: the one the user is most likely to open (a sweep once removed
75
+ // the build being played); a worktree still being worked on is kept anyway
76
+ const built = readdirSync(wts, { withFileTypes: true }).filter((w) => w.isDirectory()).map((w) => {
77
+ const dir = join(wts, w.name); let t = 0;
78
+ for (const b of readdirSync(dir, { withFileTypes: true })) if (b.isDirectory() && isBuild(b.name)) try { t = Math.max(t, statSync(join(dir, b.name)).mtimeMs, ...readdirSync(join(dir, b.name)).map((f) => statSync(join(dir, b.name, f)).mtimeMs)); } catch { /* gone */ }
79
+ return { name: w.name, t };
80
+ }).filter((w) => w.t > 0 && now - lastChange(join(wts, w.name), isBuild) >= quietMin * 60000).sort((a, b) => b.t - a.t);
81
+ if (built.length) keep.add(built[0].name);
82
+ for (const w of readdirSync(wts, { withFileTypes: true })) {
83
+ if (!w.isDirectory() || keep.has(w.name)) continue;
84
+ const dir = join(wts, w.name);
85
+ if (now - lastChange(dir, isBuild) < quietMin * 60000) continue;
86
+ for (const b of readdirSync(dir, { withFileTypes: true })) {
87
+ if (!b.isDirectory() || !isBuild(b.name)) continue;
88
+ const p = join(dir, b.name);
89
+ gone.push({ worktree: w.name, folder: b.name, gb: measure(p) });
90
+ if (!dryRun) rmSync(p, { recursive: true, force: true });
91
+ }
92
+ }
93
+ return gone;
94
+ }
95
+
96
+ function freeGb(root) {
97
+ try { const s = statfsSync(root); return (s.bavail * s.bsize) / 1e9; } catch { return Infinity; }
98
+ }
99
+
100
+ function main(text) {
101
+ const root = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
102
+ if (process.argv.includes('--now')) {
103
+ const dry = process.argv.includes('--dry-run');
104
+ const gone = sweep(root, { dryRun: dry });
105
+ for (const g of gone) console.log(`${dry ? 'would remove' : 'removed'} .claude/worktrees/${g.worktree}/${g.folder} (${g.gb.toFixed(1)} GB)`);
106
+ console.log(`build-sweep: ${gone.length} build folder(s), ${gone.reduce((a, g) => a + g.gb, 0).toFixed(1)} GB ${dry ? 'would go' : 'freed'}; ${freeGb(root).toFixed(0)} GB free`);
107
+ return 0;
108
+ }
109
+ let event = {};
110
+ try { event = JSON.parse(text || '{}'); } catch { /* no event */ }
111
+ // the sweep runs on its own, so a session's start or an agent's stop never waits on a large delete
112
+ const child = spawn(process.execPath, [fileURLToPath(import.meta.url), '--now'], { cwd: root, detached: true, stdio: 'ignore', env: process.env });
113
+ child.unref();
114
+ const free = freeGb(root);
115
+ if (event.hook_event_name === 'SessionStart' && free < LOW_GB) {
116
+ console.log(`build-sweep: the disk has ${free.toFixed(0)} GB free (under ${LOW_GB}). Quiet agent worktrees' builds are being removed; ` +
117
+ 'if it stays low, see what else holds the space before starting agents that build.');
118
+ }
119
+ return 0;
120
+ }
121
+
122
+ function selftest() {
123
+ const root = mkdtempSync(join(tmpdir(), 'build-sweep-'));
124
+ const put = (p, t) => { mkdirSync(join(root, p, '..'), { recursive: true }); writeFileSync(join(root, p), 'x'); utimesSync(join(root, p), t / 1000, t / 1000); };
125
+ const now = Date.now(), old = now - 5 * 3600e3, fresh = now - 10 * 60e3;
126
+ mkdirSync(join(root, '.claude'), { recursive: true });
127
+ writeFileSync(join(root, '.claude/layout.json'), JSON.stringify({ buildOutput: ['out/**', 'build-*/**'], keepWorktrees: ['night'] }));
128
+ put('out/app.o', old); // the main checkout's build: never swept
129
+ put('.claude/worktrees/done/src/a.cpp', old); put('.claude/worktrees/done/out/x.o', old); put('.claude/worktrees/done/build-proof/y.o', old);
130
+ put('.claude/worktrees/editing/src/a.cpp', fresh); put('.claude/worktrees/editing/out/x.o', old);
131
+ put('.claude/worktrees/building/src/a.cpp', old); put('.claude/worktrees/building/out/.ninja_log', fresh);
132
+ put('.claude/worktrees/done/outside/z.txt', old); // a folder that only looks like a build name
133
+ put('.claude/worktrees/night/src/a.cpp', old - 3600e3); put('.claude/worktrees/night/out/app.o', old - 3600e3); // named in keepWorktrees
134
+ put('.claude/worktrees/latest/src/a.cpp', old); put('.claude/worktrees/latest/out/app.o', old + 60e3); // the newest build, quiet
135
+ for (const d of ['done', 'editing', 'building', 'night', 'latest']) for (const p of [join(root, '.claude/worktrees', d), join(root, '.claude/worktrees', d, 'src')]) utimesSync(p, old / 1000, old / 1000);
136
+ for (const p of ['out', 'build-proof', 'outside']) if (existsSync(join(root, '.claude/worktrees/done', p))) utimesSync(join(root, '.claude/worktrees/done', p), old / 1000, old / 1000);
137
+ utimesSync(join(root, '.claude/worktrees/editing/out'), old / 1000, old / 1000);
138
+ utimesSync(join(root, '.claude/worktrees/building/out'), old / 1000, old / 1000);
139
+ utimesSync(join(root, '.claude/worktrees/night/out'), (old - 3600e3) / 1000, (old - 3600e3) / 1000);
140
+ utimesSync(join(root, '.claude/worktrees/latest/out'), (old + 60e3) / 1000, (old + 60e3) / 1000);
141
+ const dry = sweep(root, { now, measure: () => 0, dryRun: true });
142
+ const dryKept = existsSync(join(root, '.claude/worktrees/done/out'));
143
+ const gone = sweep(root, { now, measure: () => 0 });
144
+ const has = (p) => existsSync(join(root, p));
145
+ const cases = [
146
+ ['a dry run names the two folders and removes nothing', dry.length === 2 && dryKept, JSON.stringify(dry)],
147
+ ['a quiet worktree loses its out/', !has('.claude/worktrees/done/out')],
148
+ ['and its build-proof/ (a build-* pattern)', !has('.claude/worktrees/done/build-proof')],
149
+ ['but keeps its sources', has('.claude/worktrees/done/src/a.cpp')],
150
+ ['and a folder that only starts like a build name', has('.claude/worktrees/done/outside/z.txt')],
151
+ ['a worktree whose sources changed 10 min ago keeps its build', has('.claude/worktrees/editing/out/x.o')],
152
+ ['a worktree whose build is running keeps it', has('.claude/worktrees/building/out/.ninja_log')],
153
+ ['the main checkout keeps its build', has('out/app.o')],
154
+ ['a worktree named in keepWorktrees keeps its build', has('.claude/worktrees/night/out/app.o')],
155
+ ['the newest build is kept though quiet', has('.claude/worktrees/latest/out/app.o')],
156
+ ['exactly two folders went', gone.length === 2],
157
+ ];
158
+ rmSync(root, { recursive: true, force: true });
159
+ const bad = cases.filter(([, ok]) => !ok);
160
+ for (const [what, , why] of bad) console.log(`build-sweep selftest FAILED: ${what}${why ? ' ' + why : ''}`);
161
+ console.log(bad.length ? `build-sweep selftest: FAIL (${bad.length} of ${cases.length})` : `build-sweep selftest: OK (${cases.length} cases)`);
162
+ return bad.length ? 1 : 0;
163
+ }
164
+
165
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
166
+ if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : process.argv.includes('--now') ? main('') : await asHook('build-sweep', main));
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse (Write|Edit|MultiEdit|NotebookEdit|Bash): files on the frozen list cannot change, by any route
3
+ // (a tool, or the shell writing, copying, moving or deleting them; lib/writes.mjs).
4
+ // While a fix is in progress, the test that proves it, the golden renders and the sound
5
+ // fingerprints are frozen, so the fix can't be "made to pass" by moving the target.
6
+ // The list lives in .claude/state/frozen.json and is set by `node .claude/tools/freeze.mjs`.
7
+ // `--selftest` proves it.
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { dirname, join, relative } from 'node:path';
10
+ import { matchesAny } from './lib/glob.mjs';
11
+ import { insideProject, checkoutRoot } from './lib/project.mjs';
12
+ import { writesOf } from './lib/writes.mjs';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { bashOf, editOf, runHook, scratchProject, verdicts } from './lib/selftest.mjs';
15
+ import { asHook, isMain } from './lib/hook-log.mjs';
16
+
17
+ const ROOT = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
18
+ const STATE = join(ROOT, '.claude', 'state', 'frozen.json');
19
+
20
+ function main(text) {
21
+ if (!existsSync(STATE)) return 0;
22
+ let payload;
23
+ let state;
24
+ try {
25
+ payload = JSON.parse(text);
26
+ state = JSON.parse(readFileSync(STATE, 'utf8'));
27
+ } catch {
28
+ return 0;
29
+ }
30
+ if (!Array.isArray(state.files) || state.files.length === 0) return 0;
31
+ const hit = writesOf(payload, payload.cwd ?? process.cwd()).map((w) => w.path).filter((f) => insideProject(f)).map((f) => relative(checkoutRoot(f), f))
32
+ .find((rel) => rel === '.claude/state/frozen.json' || matchesAny(rel, state.files));
33
+ if (!hit) return 0;
34
+ const rel = hit;
35
+ process.stderr.write(
36
+ `BLOCKED by frozen-files: ${rel} is frozen (${state.reason ?? 'a fix is in progress'}).\n` +
37
+ 'Fix the code, not the test or the golden file. Unfreeze (node .claude/tools/freeze.mjs off) only when the\n' +
38
+ 'fix is green, or after the user agrees the target itself is wrong.\n',
39
+ );
40
+ return 2;
41
+ }
42
+
43
+ function selftest() {
44
+ const hook = fileURLToPath(import.meta.url);
45
+ const frozen = JSON.stringify({ files: ['tests/bug_7*', 'goldens/**'], reason: 'bug 7' });
46
+ const dir = scratchProject({ '.claude/state/frozen.json': frozen });
47
+ const idle = scratchProject();
48
+ const e = (root, file) => runHook(hook, root, editOf(join(root, file)));
49
+ return verdicts('frozen-files', [
50
+ ['the test that proves the fix', e(dir, 'tests/bug_7_test.cpp'), 2],
51
+ ['a golden render', e(dir, 'goldens/pad.wav'), 2],
52
+ ['the code being fixed', e(dir, 'src/dsp/Svf.cpp'), 0],
53
+ ['the frozen list itself', e(dir, '.claude/state/frozen.json'), 2],
54
+ ['nothing frozen', e(idle, 'tests/bug_7_test.cpp'), 0],
55
+ ['another project is not ours', runHook(hook, dir, editOf(join(dirname(dir), 'elsewhere/tests/bug_7.cpp'))), 0],
56
+ ['the shell writing the frozen test', runHook(hook, dir, bashOf("cat > tests/bug_7_test.cpp <<'EOF'\nEXPECT (true);\nEOF", dir)), 2],
57
+ ['the shell deleting a golden', runHook(hook, dir, bashOf('rm -f goldens/pad.wav', dir)), 2],
58
+ ['the shell writing the code', runHook(hook, dir, bashOf('echo x >> src/dsp/Svf.cpp', dir)), 0],
59
+ ]);
60
+ }
61
+
62
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
63
+ if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('frozen-files', main));
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // idle-wake: a proposed next step is started when the user has gone quiet ("If I havent said anything for
3
+ // 15min I am probably in bed. And unless there is no logical reason for you to not start the next thing ... then you
4
+ // obviously should start"; later: "maybe you also need to check memory at this time?").
5
+ //
6
+ // A Stop hook with `asyncRewake`: it runs in the background after the main session ends ANY turn, so the turn ends and
7
+ // The user can type. It watches the session's transcript; anything written to it (the user typing, an agent reporting, the session
8
+ // working) ends the watch quietly. After WAIT_MIN minutes of silence it exits 2, which wakes the session with the message
9
+ // below and EVERY ASK the user made in the last day, word for word (user-asks.mjs). A turn that ends with the words NOTHING OPEN
10
+ // starts no watch, so a finished night does not wake itself every 15 minutes.
11
+ // node idle-wake.mjs --selftest
12
+ import { appendFileSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs';
13
+ import { readFileSync } from 'node:fs';
14
+ import { tmpdir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { asks, render } from './user-asks.mjs';
17
+
18
+ const WAIT_MIN = Number(process.env.IDLE_WAKE_MIN ?? 15);
19
+ const LOOK_S = Number(process.env.IDLE_WAKE_LOOK_S ?? 10);
20
+
21
+ const PROPOSES = new RegExp([
22
+ "\\bnext,?\\s+(?:I(?:'d|\\s+would|\\s+will|'ll)|is|up|step|on\\s+my\\s+list|comes?)\\b",
23
+ '\\b(?:shall|should|can|may)\\s+I\\b', '\\b(?:want|like)\\s+me\\s+to\\b', '\\bwould\\s+you\\s+like\\b', '\\bdo\\s+you\\s+want\\b',
24
+ "\\bI(?:'d|\\s+would)?\\s+(?:recommend|suggest|propose)\\b", '\\bmy\\s+recommendation\\b',
25
+ "\\bI'll\\s+(?:start|begin|continue|carry\\s+on|pick\\s+up|move\\s+on)\\b", "\\bthen\\s+I(?:'ll|\\s+will)\\b", '\\bready\\s+to\\s+(?:start|begin|go)\\b',
26
+ ].join('|'), 'i');
27
+
28
+ const WAKE = (m, what) => `idle-wake: The user has said nothing for ${m.toFixed(0)} minutes since you ${what}. They are probably away. First go through their asks ` +
29
+ "below and build.md's In progress: anything not done and proved is open work. Then, unless there is a logical reason not to (it needs their eyes, " +
30
+ 'ears or hands, it is destructive or outward-facing, or it waits on something unfinished), START the next thing NOW, so what was started is ' +
31
+ 'finished when they are back. If there is such a reason, say it in one line and take the next thing instead. If truly nothing is left that you ' +
32
+ 'can do, end your turn with the words NOTHING OPEN.';
33
+
34
+ export function proposes(text) {
35
+ if (!text) return false;
36
+ const tail = text.trim().slice(-1500);
37
+ return PROPOSES.test(tail) || tail.trimEnd().endsWith('?');
38
+ }
39
+
40
+ const sizeOf = (path) => { try { const s = statSync(path); return `${s.size}:${s.mtimeMs}`; } catch { return null; } };
41
+ const nap = (s) => new Promise((done) => setTimeout(done, s * 1000));
42
+
43
+ // [0, ''] when there is nothing to do or activity was seen; [2, words] after silence for waitS.
44
+ export async function watch(event, waitS, lookS, sleep = nap) {
45
+ if (event.hook_event_name !== undefined && event.hook_event_name !== 'Stop') return [0, ''];
46
+ const msg = event.last_assistant_message ?? '';
47
+ if (msg.includes('NOTHING OPEN')) return [0, ''];
48
+ const path = event.transcript_path;
49
+ if (!path) return [0, ''];
50
+ const first = sizeOf(path);
51
+ for (let waited = 0; waited < waitS; waited += lookS) {
52
+ await sleep(lookS);
53
+ if (sizeOf(path) !== first) return [0, ''];
54
+ }
55
+ let words = WAKE(waitS / 60, proposes(msg) ? 'proposed or asked about a next step' : 'ended a turn');
56
+ try { const listed = render(asks(path)); if (listed) words += `\n\n${listed}`; } catch { /* the wake still goes */ }
57
+ return [2, words];
58
+ }
59
+
60
+ async function selftest() {
61
+ let bad = 0;
62
+ const expect = (name, got, want) => { if (got !== want) { bad += 1; console.error(`FAIL ${name}: exit ${got}`); } };
63
+ const d = mkdtempSync(join(tmpdir(), 'idle-wake-'));
64
+ const t = join(d, 't.jsonl');
65
+ writeFileSync(t, '{}\n');
66
+ const ev = (m) => ({ hook_event_name: 'Stop', transcript_path: t, last_assistant_message: m });
67
+ const still = async () => {};
68
+ expect("silence after 'Next I'd build P4'", (await watch(ev("Done. Next I'd build P4 PAD SETUP."), 3, 1, still))[0], 2);
69
+ expect("silence after 'Shall I start the bank?'", (await watch(ev('All green. Shall I start the bank?'), 3, 1, still))[0], 2);
70
+ expect("silence after 'My recommendation'", (await watch(ev('My recommendation: the cache first.'), 3, 1, still))[0], 2);
71
+ expect('the user types during the wait', (await watch(ev("Next I'd build P4."), 3, 1, async () => appendFileSync(t, '{"type":"user"}\n')))[0], 0);
72
+ expect('silence after a plain report (every turn arms)', (await watch(ev('K10 is landed as fb67cbb3. 46 of 46 green.'), 3, 1, still))[0], 2);
73
+ expect('a turn ending NOTHING OPEN', (await watch(ev('All asks done and proved. NOTHING OPEN'), 3, 1, still))[0], 0);
74
+ expect('another hook event', (await watch({ hook_event_name: 'PostToolUse', transcript_path: t, last_assistant_message: 'Shall I?' }, 3, 1, still))[0], 0);
75
+ if (!proposes('Shall I go on?') || proposes('Landed.')) { bad += 1; console.error('FAIL proposes'); }
76
+ rmSync(d, { recursive: true, force: true });
77
+ console.log(`idle-wake selftest: ${bad ? `${bad} wrong` : 'OK'}`);
78
+ return bad ? 1 : 0;
79
+ }
80
+
81
+ async function main() {
82
+ if (process.argv.includes('--selftest')) return selftest();
83
+ let event;
84
+ try { event = JSON.parse(readFileSync(0, 'utf8')); } catch { return 0; }
85
+ const [code, words] = await watch(event, WAIT_MIN * 60, LOOK_S);
86
+ if (code === 2) process.stderr.write(`${words}\n`);
87
+ return code;
88
+ }
89
+
90
+ process.exit(await main());