@thedecipherist/claude-packs 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (890) hide show
  1. package/CHANGELOG.md +754 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -2
  4. package/bin/claude-packs.mjs +212 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +93 -0
  7. package/configurations/full-stack/config.json +153 -0
  8. package/configurations/go/config.json +98 -0
  9. package/configurations/node/config.json +102 -0
  10. package/configurations/python/config.json +99 -0
  11. package/configurations/rust/config.json +106 -0
  12. package/configurations/synth-creator/config.json +148 -0
  13. package/global/hooks/gnu-shell.mjs +131 -0
  14. package/global/hooks/lib/shell-writes.mjs +2 -0
  15. package/global/hooks/shell-traps.mjs +79 -0
  16. package/global/hooks/version-pin-guard.mjs +280 -0
  17. package/global/skills/latest-versions/SKILL.md +49 -0
  18. package/lib/backup.mjs +34 -0
  19. package/lib/build-queue.mjs +22 -0
  20. package/lib/claude-md.mjs +60 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +94 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +126 -0
  26. package/lib/fix-plan.mjs +72 -0
  27. package/lib/found-folders.mjs +25 -0
  28. package/lib/git-tokens.mjs +64 -0
  29. package/lib/global.mjs +125 -0
  30. package/lib/hook-proof.mjs +45 -0
  31. package/lib/hook-timing.mjs +42 -0
  32. package/lib/hover-gate.mjs +29 -0
  33. package/lib/kit-command.mjs +14 -0
  34. package/lib/kit-lock.mjs +149 -0
  35. package/lib/layout.mjs +99 -0
  36. package/lib/logs.mjs +109 -0
  37. package/lib/manifest.mjs +24 -0
  38. package/lib/new.mjs +49 -0
  39. package/lib/once-only-report.mjs +54 -0
  40. package/lib/pack-manifest.mjs +20 -0
  41. package/lib/packs.mjs +109 -0
  42. package/lib/planner.mjs +59 -0
  43. package/lib/repeats.mjs +65 -0
  44. package/lib/repo-weight.mjs +28 -0
  45. package/lib/rule-tests.mjs +42 -0
  46. package/lib/rules.mjs +40 -0
  47. package/lib/scan-detect.mjs +130 -0
  48. package/lib/scan.mjs +171 -0
  49. package/lib/selftest-cache.mjs +39 -0
  50. package/lib/settings.mjs +138 -0
  51. package/lib/shape-cli.mjs +76 -0
  52. package/lib/shape.mjs +4 -0
  53. package/lib/suggest.mjs +31 -0
  54. package/lib/switches.mjs +44 -0
  55. package/lib/symmetry-doctor.mjs +26 -0
  56. package/lib/sync.mjs +133 -0
  57. package/lib/workorder.mjs +26 -0
  58. package/package.json +34 -4
  59. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  60. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  61. package/packs/ai/checks/model-id-from-config.json +28 -0
  62. package/packs/ai/checks/model-output-validated.json +26 -0
  63. package/packs/ai/pack.json +38 -0
  64. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  65. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  66. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  67. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  68. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  69. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  70. package/packs/audio-core/checks/keep-werror.json +12 -0
  71. package/packs/audio-core/checks/no-fast-math.json +14 -0
  72. package/packs/audio-core/checks/no-locale-change.json +12 -0
  73. package/packs/audio-core/checks/no-march-native.json +14 -0
  74. package/packs/audio-core/clang-tidy +28 -0
  75. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  76. package/packs/audio-core/hooks/param-id-lock.mjs +172 -0
  77. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  78. package/packs/audio-core/pack.json +192 -0
  79. package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -0
  80. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  81. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  82. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  83. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  84. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  85. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  86. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  87. package/packs/audio-core/rules/cpu-worst-case-per-block.md +28 -0
  88. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  89. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  90. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  91. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  92. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  93. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  94. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  95. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  96. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  97. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  98. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  99. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  100. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  101. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  102. package/packs/audio-core/rules/output-limiter.md +23 -0
  103. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  104. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  105. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  106. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  107. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  108. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  109. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  110. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  111. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  112. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  113. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  114. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  115. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  116. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  117. package/packs/audio-core/rules/ui-matches-reference-look.md +23 -0
  118. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  119. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  120. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  121. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +35 -0
  122. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  123. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  124. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  125. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  126. package/packs/audio-core/skills/prove-control/SKILL.md +25 -0
  127. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  128. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  129. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  130. package/packs/audio-core/tools/tone-report.mjs +47 -0
  131. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  132. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  133. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  134. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  135. package/packs/base/checks/one-walk-per-template.json +23 -0
  136. package/packs/base/checks/stub-reports-success.json +31 -0
  137. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  138. package/packs/base/hooks/brief-guard.mjs +96 -0
  139. package/packs/base/hooks/code-shape.mjs +99 -0
  140. package/packs/base/hooks/dispatch-once.mjs +124 -0
  141. package/packs/base/hooks/file-size-guard.mjs +61 -0
  142. package/packs/base/hooks/folder-law.mjs +77 -0
  143. package/packs/base/hooks/git-guard.mjs +202 -0
  144. package/packs/base/hooks/lesson-checks.mjs +182 -0
  145. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  146. package/packs/base/hooks/lib/code-shapes.mjs +142 -0
  147. package/packs/base/hooks/lib/content.mjs +14 -0
  148. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  149. package/packs/base/hooks/lib/crash-policy.mjs +47 -0
  150. package/packs/base/hooks/lib/dup-index.mjs +204 -0
  151. package/packs/base/hooks/lib/dup.mjs +127 -0
  152. package/packs/base/hooks/lib/elements.mjs +87 -0
  153. package/packs/base/hooks/lib/event-log.mjs +68 -0
  154. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  155. package/packs/base/hooks/lib/git-adds.mjs +21 -0
  156. package/packs/base/hooks/lib/git-auth.mjs +44 -0
  157. package/packs/base/hooks/lib/git-guard-selftest.mjs +192 -0
  158. package/packs/base/hooks/lib/glob.mjs +33 -0
  159. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  160. package/packs/base/hooks/lib/house.py +77 -0
  161. package/packs/base/hooks/lib/languages.mjs +69 -0
  162. package/packs/base/hooks/lib/layout.mjs +58 -0
  163. package/packs/base/hooks/lib/making-check-selftest.mjs +112 -0
  164. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  165. package/packs/base/hooks/lib/program-read.mjs +199 -0
  166. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  167. package/packs/base/hooks/lib/project.mjs +81 -0
  168. package/packs/base/hooks/lib/prose.mjs +11 -0
  169. package/packs/base/hooks/lib/pushed.mjs +201 -0
  170. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  171. package/packs/base/hooks/lib/rust-scan.mjs +139 -0
  172. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  173. package/packs/base/hooks/lib/selftest.mjs +45 -0
  174. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  175. package/packs/base/hooks/lib/shape-lists.mjs +31 -0
  176. package/packs/base/hooks/lib/shape-prose.mjs +78 -0
  177. package/packs/base/hooks/lib/shape.mjs +273 -0
  178. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  179. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  180. package/packs/base/hooks/lib/turn.mjs +86 -0
  181. package/packs/base/hooks/lib/unread-writes.mjs +24 -0
  182. package/packs/base/hooks/lib/writes.mjs +71 -0
  183. package/packs/base/hooks/making-check.mjs +248 -0
  184. package/packs/base/hooks/making.mjs +106 -0
  185. package/packs/base/hooks/once-only.mjs +150 -0
  186. package/packs/base/hooks/placement-law.mjs +84 -0
  187. package/packs/base/hooks/repo-litter.mjs +75 -0
  188. package/packs/base/hooks/reuse-hint.mjs +75 -0
  189. package/packs/base/hooks/run-hooks.mjs +140 -0
  190. package/packs/base/hooks/secrets-guard.mjs +99 -0
  191. package/packs/base/hooks/shape-law.mjs +140 -0
  192. package/packs/base/hooks/suite-once.mjs +96 -0
  193. package/packs/base/hooks/template-law.mjs +148 -0
  194. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  195. package/packs/base/pack.json +214 -0
  196. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  197. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  198. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  199. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  200. package/packs/base/rules/finished-work-lands.md +17 -0
  201. package/packs/base/rules/git-right-first-time.md +19 -0
  202. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  203. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  204. package/packs/base/rules/making-line-first.md +14 -0
  205. package/packs/base/rules/open-items-tagged.md +17 -0
  206. package/packs/base/rules/plan-and-logs.md +22 -0
  207. package/packs/base/rules/references-use-anchors.md +19 -0
  208. package/packs/base/rules/refusals-are-judged.md +19 -0
  209. package/packs/base/rules/reuse-before-writing.md +15 -0
  210. package/packs/base/rules/secrets-never-committed.md +16 -0
  211. package/packs/base/rules/skill-writing.md +23 -0
  212. package/packs/base/rules/split-before-the-limit.md +14 -0
  213. package/packs/base/rules/subagent-dispatch.md +26 -0
  214. package/packs/base/rules/test-only-what-changed.md +17 -0
  215. package/packs/base/rules/test-the-template-once.md +23 -0
  216. package/packs/base/rules/unattended-runs.md +15 -0
  217. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  218. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  219. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  220. package/packs/base/skills/land-branch/SKILL.md +24 -0
  221. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  222. package/packs/base/skills/new-project/SKILL.md +23 -0
  223. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  224. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  225. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  226. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  227. package/packs/base/skills/write-a-workorder/SKILL.md +41 -0
  228. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  229. package/packs/base/tools/shape-symmetry.mjs +178 -0
  230. package/packs/base/tools/test-verdict.mjs +75 -0
  231. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  232. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  233. package/packs/ci/pack.json +21 -0
  234. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  235. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  236. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  237. package/packs/content-site/checks/img-has-size.json +20 -0
  238. package/packs/content-site/checks/page-head-complete.json +26 -0
  239. package/packs/content-site/pack.json +22 -0
  240. package/packs/content-site/rules/content-has-one-source.md +23 -0
  241. package/packs/content-site/rules/media-is-sized.md +24 -0
  242. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  243. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  244. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  245. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  246. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  247. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  248. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  249. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  250. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  251. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  252. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  253. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  254. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  255. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  256. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  257. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  258. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  259. package/packs/cpp/pack.json +22 -0
  260. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  261. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  262. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  263. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  264. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  265. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  266. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  267. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  268. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  269. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  270. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  271. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  272. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  273. package/packs/docker/checks/compose-image-pinned.json +14 -0
  274. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  275. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  276. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  277. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  278. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  279. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  280. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  281. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  282. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  283. package/packs/docker/checks/docker-exec-form.json +16 -0
  284. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  285. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  286. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  287. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  288. package/packs/docker/checks/health-checks-nothing.json +25 -0
  289. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  290. package/packs/docker/checks/no-latest-base.json +13 -0
  291. package/packs/docker/checks/no-secret-in-image.json +13 -0
  292. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  293. package/packs/docker/checks/node-env-before-install.json +13 -0
  294. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  295. package/packs/docker/pack.json +46 -0
  296. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  297. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  298. package/packs/docker/rules/docker-exec-form.md +19 -0
  299. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  300. package/packs/docker/rules/dockerfile-production.md +25 -0
  301. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  302. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  303. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  304. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  305. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  306. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  307. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  308. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  309. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  310. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  311. package/packs/dynamic-data/pack.json +23 -0
  312. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  313. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  314. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  315. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  316. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  317. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  318. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  319. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  320. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  321. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  322. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  323. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  324. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  325. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  326. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  327. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  328. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  329. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  330. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  331. package/packs/fastify/checks/one-error-body.json +26 -0
  332. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  333. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  334. package/packs/fastify/checks/sec-headers-present.json +28 -0
  335. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  336. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  337. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  338. package/packs/fastify/pack.json +41 -0
  339. package/packs/fastify/rules/credentials-and-input.md +23 -0
  340. package/packs/fastify/rules/security-one-switch.md +28 -0
  341. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  342. package/packs/fastify/rules/tests-use-inject.md +13 -0
  343. package/packs/fastify/rules/three-layers.md +20 -0
  344. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  345. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  346. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  347. package/packs/full-stack/pack.json +76 -0
  348. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  349. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  350. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  351. package/packs/go/pack.json +26 -0
  352. package/packs/go/rules/go-errors-are-handled.md +22 -0
  353. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  354. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  355. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  356. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  357. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  358. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  359. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  360. package/packs/gpu-painter/pack.json +91 -0
  361. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  362. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  363. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  364. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  365. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  366. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  367. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  368. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  369. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  370. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  371. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  372. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  373. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  374. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  375. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  376. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  377. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  378. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  379. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  380. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  381. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  382. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  383. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  384. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  385. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  386. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  387. package/packs/hook-proof/pack.json +38 -0
  388. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  389. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  390. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  391. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  392. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  393. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  394. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  395. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  396. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  397. package/packs/host-agent/pack.json +22 -0
  398. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  399. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  400. package/packs/html-ui/checks/external-link-rel.json +21 -0
  401. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  402. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  403. package/packs/html-ui/checks/no-inline-style.json +23 -0
  404. package/packs/html-ui/pack.json +33 -0
  405. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  406. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  407. package/packs/http-api/checks/api-path-versioned.json +31 -0
  408. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  409. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  410. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  411. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  412. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  413. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  414. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  415. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  416. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  417. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  418. package/packs/http-api/checks/guard-fails-open.json +28 -0
  419. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  420. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  421. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  422. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  423. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  424. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  425. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  426. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  427. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  428. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  429. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  430. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  431. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  432. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  433. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  434. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  435. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  436. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  437. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  438. package/packs/http-api/pack.json +45 -0
  439. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  440. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  441. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  442. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  443. package/packs/http-api/rules/health-and-correlation.md +17 -0
  444. package/packs/http-api/rules/one-error-contract.md +17 -0
  445. package/packs/http-api/rules/routes-are-thin.md +17 -0
  446. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  447. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  448. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  449. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  450. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  451. package/packs/instrument-shell/pack.json +22 -0
  452. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  453. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  454. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  455. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  456. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  457. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  458. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  459. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  460. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  461. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  462. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  463. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  464. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  465. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  466. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  467. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  468. package/packs/js-ts/checks/no-any.json +27 -0
  469. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  470. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  471. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  472. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  473. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  474. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  475. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  476. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  477. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  478. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  479. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  480. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  481. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  482. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  483. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  484. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  485. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  486. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  487. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  488. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  489. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  490. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  491. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  492. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  493. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  494. package/packs/js-ts/pack.json +84 -0
  495. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  496. package/packs/js-ts/rules/env-through-config.md +36 -0
  497. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  498. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  499. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  500. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  501. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  502. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  503. package/packs/js-ts/rules/no-any.md +34 -0
  504. package/packs/js-ts/rules/no-console.md +30 -0
  505. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  506. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  507. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  508. package/packs/js-ts/rules/one-logger.md +24 -0
  509. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  510. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  511. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  512. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  513. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  514. package/packs/js-ts/rules/small-functions.md +20 -0
  515. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  516. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  517. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  518. package/packs/js-ts/rules/typed-exports.md +27 -0
  519. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  520. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  521. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  522. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  523. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  524. package/packs/midi/checks/midi-realtime-first.json +27 -0
  525. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  526. package/packs/midi/checks/position-not-integrated.json +29 -0
  527. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  528. package/packs/midi/checks/sysex-length-literal.json +23 -0
  529. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  530. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  531. package/packs/midi/pack.json +22 -0
  532. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  533. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  534. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  535. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  536. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  537. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  538. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  539. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  540. package/packs/mongo/checks/bulk-over-many.json +28 -0
  541. package/packs/mongo/checks/created-at-immutable.json +25 -0
  542. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  543. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  544. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  545. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  546. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  547. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  548. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  549. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  550. package/packs/mongo/checks/no-find-reads.json +26 -0
  551. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  552. package/packs/mongo/checks/no-mongoose.json +23 -0
  553. package/packs/mongo/checks/no-negation-delete.json +26 -0
  554. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  555. package/packs/mongo/checks/objectid-equals.json +26 -0
  556. package/packs/mongo/checks/update-then-reread.json +24 -0
  557. package/packs/mongo/checks/watch-not-poll.json +25 -0
  558. package/packs/mongo/pack.json +52 -0
  559. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  560. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  561. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  562. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  563. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  564. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  565. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  566. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  567. package/packs/multi-tenant/pack.json +22 -0
  568. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  569. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  570. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  571. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  572. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  573. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  574. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  575. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  576. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  577. package/packs/nginx/pack.json +43 -0
  578. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  579. package/packs/nginx/rules/nginx-production.md +26 -0
  580. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  581. package/packs/postgres/checks/pg-bind-values.json +33 -0
  582. package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
  583. package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
  584. package/packs/postgres/checks/pg-index-concurrently.json +18 -0
  585. package/packs/postgres/checks/pg-no-float-money.json +19 -0
  586. package/packs/postgres/checks/pg-no-money.json +18 -0
  587. package/packs/postgres/checks/pg-no-required-column.json +18 -0
  588. package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
  589. package/packs/postgres/checks/pg-not-exists.json +18 -0
  590. package/packs/postgres/checks/pg-timestamptz.json +18 -0
  591. package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
  592. package/packs/postgres/pack.json +48 -0
  593. package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
  594. package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
  595. package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
  596. package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
  597. package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
  598. package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -0
  599. package/packs/python/checks/py-no-bare-except.json +18 -0
  600. package/packs/python/checks/py-no-eval.json +18 -0
  601. package/packs/python/checks/py-no-mutable-default.json +18 -0
  602. package/packs/python/checks/py-no-shell-true.json +18 -0
  603. package/packs/python/checks/py-no-star-import.json +18 -0
  604. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  605. package/packs/python/checks/py-none-by-is.json +18 -0
  606. package/packs/python/checks/py-open-with-with.json +18 -0
  607. package/packs/python/checks/py-requests-timeout.json +18 -0
  608. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  609. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  610. package/packs/python/pack.json +26 -0
  611. package/packs/python/rules/python-errors-and-resources.md +26 -0
  612. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  613. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  614. package/packs/react/checks/dialog-needs-role.json +28 -0
  615. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  616. package/packs/react/checks/icon-button-needs-name.json +24 -0
  617. package/packs/react/checks/label-needs-a-field.json +24 -0
  618. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  619. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  620. package/packs/react/pack.json +20 -0
  621. package/packs/react/rules/react-accessible-controls.md +20 -0
  622. package/packs/react/rules/react-state-and-effects.md +23 -0
  623. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  624. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  625. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  626. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  627. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  628. package/packs/reverse-engineering/pack.json +35 -0
  629. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  630. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  631. package/packs/reverse-engineering/rules/addresses-name-their-space.md +22 -0
  632. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  633. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  634. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  635. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  636. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  637. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  638. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  639. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +22 -0
  640. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  641. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  642. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  643. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  644. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  645. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  646. package/packs/rust/checks/rust-allow-needs-reason.json +16 -0
  647. package/packs/rust/checks/rust-blocking-in-async.json +32 -0
  648. package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
  649. package/packs/rust/checks/rust-static-mut.json +32 -0
  650. package/packs/rust/checks/rust-string-as-error.json +34 -0
  651. package/packs/rust/checks/rust-stub-left-in.json +32 -0
  652. package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
  653. package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
  654. package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
  655. package/packs/rust/clippy.toml +5 -0
  656. package/packs/rust/pack.json +27 -0
  657. package/packs/rust/rules/rust-async-never-blocks.md +20 -0
  658. package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
  659. package/packs/rust/rules/rust-errors-are-returned.md +22 -0
  660. package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
  661. package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
  662. package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
  663. package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
  664. package/packs/rust/skills/rust-port/SKILL.md +22 -0
  665. package/packs/rust/skills/rust-review/SKILL.md +24 -0
  666. package/packs/rust/tools/rust-lints.toml +19 -0
  667. package/packs/shell/checks/sh-mktemp.json +18 -0
  668. package/packs/shell/checks/sh-no-backticks.json +18 -0
  669. package/packs/shell/checks/sh-no-eval.json +18 -0
  670. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  671. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  672. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  673. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  674. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  675. package/packs/shell/checks/sh-strict-mode.json +20 -0
  676. package/packs/shell/pack.json +28 -0
  677. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  678. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  679. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  680. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  681. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  682. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  683. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  684. package/packs/shell-hooks/pack.json +36 -0
  685. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  686. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  687. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  688. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  689. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  690. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  691. package/packs/sound-design/pack.json +22 -0
  692. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  693. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  694. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  695. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  696. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  697. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  698. package/packs/sound-design/skills/bounce-a-plugin/SKILL.md +40 -0
  699. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  700. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  701. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  702. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  703. package/packs/sound-design/tools/vst3host/build.sh +73 -0
  704. package/packs/sound-design/tools/vst3host/host_events.h +101 -0
  705. package/packs/sound-design/tools/vst3host/options.h +172 -0
  706. package/packs/sound-design/tools/vst3host/render.cpp +245 -0
  707. package/packs/sound-design/tools/vst3host/session.h +241 -0
  708. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  709. package/packs/stellix/checks/one-seeded-random.json +16 -0
  710. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  711. package/packs/stellix/checks/world-no-libm.json +15 -0
  712. package/packs/stellix/checks/world-no-unordered.json +10 -0
  713. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  714. package/packs/stellix/pack.json +61 -0
  715. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  716. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  717. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  718. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  719. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  720. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  721. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  722. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  723. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  724. package/packs/stored-text/pack.json +41 -0
  725. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  726. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  727. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  728. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  729. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  730. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  731. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  732. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  733. package/packs/synth-creator/hooks/protect-vendor.mjs +175 -0
  734. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  735. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  736. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  737. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  738. package/packs/synth-creator/pack.json +136 -0
  739. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  740. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  741. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  742. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  743. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  744. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  745. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  746. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +21 -0
  747. package/packs/synth-creator/tools/buildq.mjs +74 -0
  748. package/packs/synth-creator/tools/freeze.mjs +36 -0
  749. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  750. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  751. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  752. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  753. package/packs/tui/checks/tui-width-is-state.json +33 -0
  754. package/packs/tui/pack.json +44 -0
  755. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  756. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  757. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  758. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  759. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  760. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  761. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  762. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  763. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  764. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  765. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  766. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  767. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  768. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  769. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  770. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  771. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  772. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  773. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  774. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  775. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  776. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  777. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  778. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  779. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  780. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  781. package/packs/ui-craft/pack.json +43 -0
  782. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  783. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  784. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  785. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  786. package/packs/ui-craft/rules/interface-copy.md +26 -0
  787. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  788. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  789. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  790. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  791. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  792. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  793. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  794. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  795. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  796. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  797. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  798. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  799. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  800. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  801. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  802. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  803. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  804. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  805. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  806. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  807. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  808. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  809. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  810. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  811. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  812. package/packs/web/checks/client-no-api-key.json +29 -0
  813. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  814. package/packs/web/checks/client-no-server-import.json +31 -0
  815. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  816. package/packs/web/checks/css-no-inline-style.json +30 -0
  817. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  818. package/packs/web/checks/key-from-index.json +23 -0
  819. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  820. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  821. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  822. package/packs/web/checks/rcss-button-cursor.json +30 -0
  823. package/packs/web/checks/rcss-no-100vw.json +21 -0
  824. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  825. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  826. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  827. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  828. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  829. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  830. package/packs/web/pack.json +73 -0
  831. package/packs/web/rules/accessibility.md +26 -0
  832. package/packs/web/rules/client-boundaries.md +23 -0
  833. package/packs/web/rules/production-build.md +22 -0
  834. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  835. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  836. package/packs/web/rules/tailwind-v4.md +23 -0
  837. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  838. package/packs/web/rules/web-performance.md +21 -0
  839. package/packs/web/skills/design-direction/SKILL.md +30 -0
  840. package/packs/web/skills/design-review/SKILL.md +28 -0
  841. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  842. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  843. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  844. package/packs/websocket/checks/no-token-in-query.json +26 -0
  845. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  846. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  847. package/packs/websocket/pack.json +21 -0
  848. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  849. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  850. package/packs/writing/checks/no-copula-dodging.json +18 -0
  851. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  852. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  853. package/packs/writing/checks/no-participial-opener.json +18 -0
  854. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  855. package/packs/writing/checks/no-slop-words.json +18 -0
  856. package/packs/writing/checks/no-stock-connectors.json +18 -0
  857. package/packs/writing/checks/no-time-or-date.json +27 -0
  858. package/packs/writing/checks/no-trailing-ing.json +18 -0
  859. package/packs/writing/checks/no-vague-attribution.json +18 -0
  860. package/packs/writing/pack.json +43 -0
  861. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  862. package/packs/writing/rules/cut-the-padding.md +20 -0
  863. package/packs/writing/rules/direct-voice.md +22 -0
  864. package/packs/writing/rules/no-cargo-cult.md +16 -0
  865. package/packs/writing/rules/no-scaffolding.md +19 -0
  866. package/packs/writing/rules/no-stock-language.md +27 -0
  867. package/packs/writing/rules/no-time-or-date.md +18 -0
  868. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  869. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  870. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  871. package/packs/writing/skills/humanize/SKILL.md +28 -0
  872. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  873. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  874. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  875. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  876. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  877. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  878. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  879. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  880. package/packs/zod/hooks/schemas-only.mjs +53 -0
  881. package/packs/zod/pack.json +44 -0
  882. package/packs/zod/rules/env-shapes-only.md +14 -0
  883. package/packs/zod/rules/zod-4-schemas.md +14 -0
  884. package/profiles/stellix.layout.json +315 -0
  885. package/profiles/sub37.layout.json +247 -0
  886. package/prompts/scan-review.md +40 -0
  887. package/prompts/survey.md +82 -0
  888. package/prompts/workorder/PROMPT.md +53 -0
  889. package/prompts/workorder/run.sh +36 -0
  890. package/prompts/workorder/workorder.md +59 -0
@@ -0,0 +1,86 @@
1
+ // turn: what the session did since the user's last prompt, read from the session's transcript (a JSONL file: one entry
2
+ // per line; an assistant entry's content holds text and tool_use blocks, a user entry's holds the prompt or tool results).
3
+ // lastTurn(path) -> { text, uses: [{ name, input, cwd }] } uses: every tool call of the turn that was not refused
4
+ // writesOfTurn(turn) -> [{ path, text, tool }] every file a call wrote and the text it put there
5
+ // (Write's content, Edit's and MultiEdit's new strings, NotebookEdit's source, the shell's known content; null
6
+ // when it cannot be known, e.g. a sed -i), by the shared write reader for the shell.
7
+ // A prompt starts a turn; tool results, hook feedback, task notifications and the system's own lines do not, so a turn
8
+ // sent back by a Stop hook is judged again with everything it wrote before.
9
+ import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
10
+ import { resolve } from 'node:path';
11
+ import { shellWrites } from './shell-writes.mjs';
12
+
13
+ const NOT_A_PROMPT = /^\s*(?:<|\[|Stop hook feedback|SubagentStop hook feedback|making-check|Caveat:|This session is being continued)/;
14
+
15
+ function promptText(e) {
16
+ if (e.type !== 'user' || e.isMeta || e.isSidechain) return null;
17
+ const c = e.message?.content;
18
+ if (typeof c === 'string') return c;
19
+ if (!Array.isArray(c) || c.some((b) => b?.type === 'tool_result')) return null;
20
+ const t = c.filter((b) => b?.type === 'text').map((b) => b.text ?? '').join('\n');
21
+ return t || null;
22
+ }
23
+
24
+ // The transcript's last entries, read from its end: the last TAIL bytes, doubled until they hold the turn's prompt (KWB-24: a
25
+ // long session's transcript is hundreds of MB, and reading all of it at every Stop took seconds). -> { entries, start }
26
+ const TAIL = 1 << 20;
27
+ function tailEntries(path) {
28
+ let fd;
29
+ try { fd = openSync(path, 'r'); } catch { return { entries: [], start: 0 }; }
30
+ try {
31
+ const size = fstatSync(fd).size;
32
+ for (let want = TAIL; ; want *= 4) {
33
+ const from = Math.max(0, size - want);
34
+ const buf = Buffer.alloc(size - from);
35
+ readSync(fd, buf, 0, buf.length, from);
36
+ const lines = buf.toString('utf8').split('\n');
37
+ if (from > 0) lines.shift(); // a line cut by the start of the window
38
+ const entries = [];
39
+ for (const l of lines) { if (!l.trim()) continue; try { entries.push(JSON.parse(l)); } catch { /* a torn last line */ } }
40
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
41
+ const t = promptText(entries[i]);
42
+ if (t !== null && !NOT_A_PROMPT.test(t)) return { entries, start: i + 1 };
43
+ }
44
+ if (from === 0) return { entries, start: 0 };
45
+ }
46
+ } finally {
47
+ closeSync(fd);
48
+ }
49
+ }
50
+
51
+ export function lastTurn(path) {
52
+ const { entries, start } = tailEntries(path);
53
+ const refused = new Set();
54
+ const uses = [];
55
+ const text = [];
56
+ for (const e of entries.slice(start)) {
57
+ if (e.isSidechain) continue;
58
+ const content = Array.isArray(e.message?.content) ? e.message.content : [];
59
+ if (e.type === 'user') for (const b of content) if (b?.type === 'tool_result' && b.is_error) refused.add(b.tool_use_id);
60
+ if (e.type !== 'assistant') continue;
61
+ for (const b of content) {
62
+ if (b?.type === 'text' && b.text) text.push(b.text);
63
+ if (b?.type === 'tool_use') uses.push({ id: b.id, name: b.name, input: b.input ?? {}, cwd: e.cwd });
64
+ }
65
+ }
66
+ return { text: text.join('\n'), uses: uses.filter((u) => !refused.has(u.id)) };
67
+ }
68
+
69
+ // A landing ends a step. Compaction is no prompt, so one turn can hold a whole run, and its last step's making line says nothing of the steps before: the
70
+ // MPX 500 was sent back for files written hours earlier, at a landing (KF-34). What a turn landed is over; only the writes after its last landing are judged.
71
+ const LANDING = /(?:^|[;&|\n])\s*git\s+(?:-C\s+\S+\s+)?merge\s+(?:\S+\s+)*?--ff-only\b/;
72
+ const sinceLanding = (uses) => uses.slice(uses.findLastIndex((u) => u.name === 'Bash' && LANDING.test(u.input.command ?? '')) + 1);
73
+
74
+ export function writesOfTurn(turn, cwd = process.cwd()) {
75
+ const out = [];
76
+ for (const u of sinceLanding(turn.uses)) {
77
+ const i = u.input;
78
+ const at = (p) => resolve(u.cwd ?? cwd, p);
79
+ if (u.name === 'Write' && i.file_path) out.push({ path: at(i.file_path), text: i.content ?? '', tool: u.name });
80
+ else if (u.name === 'Edit' && i.file_path) out.push({ path: at(i.file_path), text: i.new_string ?? '', tool: u.name });
81
+ else if (u.name === 'MultiEdit' && i.file_path) out.push({ path: at(i.file_path), text: (i.edits ?? []).map((e) => e.new_string ?? '').join('\n'), tool: u.name });
82
+ else if (u.name === 'NotebookEdit' && i.notebook_path) out.push({ path: at(i.notebook_path), text: i.new_source ?? '', tool: u.name });
83
+ else if (u.name === 'Bash') for (const w of shellWrites(i.command ?? '', u.cwd ?? cwd)) if (w.op !== 'delete') out.push({ path: w.path, text: w.content ?? (w.sed ? w.sed.scripts.join('\n') : null), tool: 'Bash' });
84
+ }
85
+ return out;
86
+ }
@@ -0,0 +1,24 @@
1
+ // unread-writes: an inline program can write a source file in a way no hook can read, and every content law lets an unknown text through.
2
+ // The MPX 500's edits through a local helper passed no law, and nothing said so (KF-40). When a command passed, this names what it wrote
3
+ // unread, where the author sees it, so a pass is never taken for a judgement.
4
+ // unreadWrites(ev, root) -> '' | JSON for the hook's stdout
5
+ import { extname, relative } from 'node:path';
6
+ import { languageOf } from './languages.mjs';
7
+ import { insideProject } from './project.mjs';
8
+ import { writesOf } from './writes.mjs';
9
+
10
+ const INLINE = /\b(?:python[\d.]*|pypy3?|node|nodejs|bun|perl|ruby)\b[^\n|;&]*(?:\s-[A-Za-z]*[ceE]\s|\s-\s|<<)/;
11
+ const CONTENT_OPS = new Set(['write', 'edit', 'append']);
12
+
13
+ export function unreadWrites(ev, root) {
14
+ const command = ev.tool_input?.command;
15
+ if (ev.tool_name !== 'Bash' || ev.hook_event_name !== 'PreToolUse' || !INLINE.test(command ?? '')) return '';
16
+ const cwd = ev.cwd ?? root;
17
+ const unread = writesOf(ev, cwd).filter((w) => w.after === null && CONTENT_OPS.has(w.op) && languageOf(extname(w.path)) && insideProject(w.path, cwd)).map((w) => relative(cwd, w.path));
18
+ if (!unread.length) return '';
19
+ const files = unread.join(', ');
20
+ return JSON.stringify({
21
+ systemMessage: `not judged: this command writes ${files} in a way the hooks could not read`,
22
+ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: `The hooks could not read what this command writes to ${files}, so no content law judged it. Use the Edit or Write tool for source files, where every law applies.` },
23
+ });
24
+ }
@@ -0,0 +1,71 @@
1
+ // writes: every file one tool call writes, whichever tool it is (WB-69: close the route, not just the
2
+ // brief). A guard that refuses a write judges these, so Write, Edit, MultiEdit, NotebookEdit and the
3
+ // shell (>, >>, tee, sed -i, cp, mv, a heredoc into a file, an inline program's own writes (KWB-8), ...) are one route each into the same check.
4
+ // writesOf(payload, base) -> [{ path, op, before, after }]
5
+ // path: absolute; op: write, edit, append, copy, move, link, truncate, touch, delete (see shell-writes.mjs)
6
+ // before: the file's text now ('' when it does not exist); after: its text once the call has run, or null
7
+ // when that cannot be known (a perl -i, an inline program's write of text it builds). A guard that judges paths needs only path;
8
+ // one that judges content judges after, and lets an unknown after through.
9
+ import { relative, resolve } from 'node:path';
10
+ import { checkoutRoot, commandLeavesProject, insideProject } from './project.mjs';
11
+ import { computeEdit } from './apply-edit.mjs';
12
+ import { afterText, readText, shellWrites } from './shell-writes.mjs';
13
+
14
+ export const WRITE_TOOLS = ['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'Bash'];
15
+
16
+ export function writesOf(payload, base = process.cwd()) {
17
+ const input = payload.tool_input ?? {};
18
+ const tool = payload.tool_name;
19
+ if (tool === 'Write' || tool === 'Edit' || tool === 'MultiEdit') {
20
+ if (!input.file_path) return [];
21
+ const path = resolve(base, input.file_path);
22
+ const r = computeEdit(tool, { ...input, file_path: path });
23
+ return [{ path, op: tool === 'Write' ? 'write' : 'edit', before: r?.before ?? readText(path) ?? '', after: r?.after ?? null }];
24
+ }
25
+ if (tool === 'NotebookEdit') {
26
+ if (!input.notebook_path) return [];
27
+ const path = resolve(base, input.notebook_path);
28
+ const before = readText(path) ?? '';
29
+ const src = input.new_source ?? '';
30
+ const after = input.edit_mode === 'delete' ? before : `${before}${before && !before.endsWith('\n') ? '\n' : ''}${src}${src.endsWith('\n') ? '' : '\n'}`;
31
+ return [{ path, op: 'edit', before, after }];
32
+ }
33
+ if (tool === 'Bash') {
34
+ // several writes to one file in one command (`sed -i A f && sed -i B f`) are one change: each reads what the one before it left,
35
+ // and the file is judged once, from what it was to what the last one leaves (KWB-13: each was read against the file on disk, and
36
+ // the two different results were judged as two copies of the file)
37
+ const byPath = new Map();
38
+ for (const w of shellWrites(input.command ?? '', payload.cwd ?? base)) {
39
+ const prev = byPath.get(w.path);
40
+ const r = prev ? (prev.after === null ? { after: null } : afterText(w, prev.after)) : afterText(w);
41
+ byPath.set(w.path, prev ? { ...prev, op: w.op === 'delete' ? 'delete' : prev.op, after: r.after } : { path: w.path, op: w.op, ...r });
42
+ }
43
+ return [...byPath.values()];
44
+ }
45
+ return [];
46
+ }
47
+
48
+ export function readPayload(text) {
49
+ try { return JSON.parse(text); } catch { return null; }
50
+ }
51
+
52
+ // One guard's frame (0.10.0): read the event, judge its shell command (`command(text, cwd)`, unless it leaves the project) and
53
+ // every write inside the project (`write(w, rel)`, rel from the file's own checkout); the first finding refuses, exit 2.
54
+ // A finding that starts with "BLOCKED" is written as it is, any other as "BLOCKED by <name>: <finding>".
55
+ export function judgeEvent(text, { name, command = null, write = null }) {
56
+ const payload = readPayload(text);
57
+ if (!payload) return 0;
58
+ const cwd = payload.cwd ?? process.cwd();
59
+ const refuse = (msg) => { process.stderr.write(msg.startsWith('BLOCKED') ? msg : `BLOCKED by ${name}: ${msg}\n`); return 2; };
60
+ const cmd = payload.tool_input?.command ?? '';
61
+ if (command && payload.tool_name === 'Bash' && !commandLeavesProject(cmd)) {
62
+ const msg = command(cmd, cwd);
63
+ if (msg) return refuse(msg);
64
+ }
65
+ if (write) for (const w of writesOf(payload, cwd)) {
66
+ if (!insideProject(w.path, cwd)) continue;
67
+ const msg = write(w, relative(checkoutRoot(w.path), w.path));
68
+ if (msg) return refuse(msg);
69
+ }
70
+ return 0;
71
+ }
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ // making-check: a turn ends only when what it wrote is what it said it was making (claude-packs 0.9.0 item 7; KWB-1).
3
+ // A Stop hook. It reads the turn from the transcript (lib/turn.mjs: every file written by Write, Edit, MultiEdit,
4
+ // NotebookEdit or the shell, refused calls left out) and sends the turn back (exit 2) naming what is missing when:
5
+ // - the turn wrote code or docs that touch a concept of design/shape.json and build.md's In progress entry for this
6
+ // step has no `making:` line, or the line does not name that concept;
7
+ // - the making line names a concept that has no row in design/shape.json, or the turn wrote source with no manifest;
8
+ // - a source file the turn wrote repeats code the repo already has (once-only's detector, lib/dup.mjs, on the lines
9
+ // the working tree adds against HEAD);
10
+ // - THE OVERRIDE RULE: the turn's text says it did something other than what it was told, and cites no law. Told X
11
+ // and a law file (design/shape.json, docs/*-spec.md, docs/laws.md) says Y: do Y, log the brief as the bug, cite the
12
+ // line. No law covers it: ask; overnight, decide and write it under Decisions, and cite that entry. There is no
13
+ // third case where Claude's own judgement overrides an instruction.
14
+ // After three send-backs with the same findings the turn ends and the findings go to the user (no endless loop).
15
+ // node making-check.mjs --selftest
16
+ import { createHash } from 'node:crypto';
17
+ import { spawnSync } from 'node:child_process';
18
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
19
+ import { tmpdir } from 'node:os';
20
+ import { join, relative, resolve } from 'node:path';
21
+ import { copyGlobs, excludeGlobs, FIX, isGenerated, isSource, real } from './lib/dup.mjs';
22
+ import { judgeWrites } from './lib/dup-index.mjs';
23
+ import { matchesAny } from './lib/glob.mjs';
24
+ import { readLayout } from './lib/layout.mjs';
25
+ import { checkoutRoot, claudeRoot, insideProject, ROOT } from './lib/project.mjs';
26
+ import { readShape } from './lib/shape.mjs';
27
+ import { lastTurn, writesOfTurn } from './lib/turn.mjs';
28
+ import { asHook, isMain } from './lib/hook-log.mjs';
29
+
30
+ const MAX_SENDS = 3;
31
+ const NOT_CONCEPTS = new Set(['none', 'no', '-', 'n/a', 'one', 'many', 'ids']);
32
+ // the folder kinds a layout marks as code; a layout that marks none: any source file outside its docs folders
33
+ const CODE_KINDS = new Set(['code', 'engine', 'ui', 'params', 'tests']);
34
+ const NOT_CODE = ['build/**', 'inbox/**', 'docs/**', '**/*.md', '**/*.txt', '**/*.json'];
35
+ function isCode(relPath, layout) {
36
+ if (!(isSource(relPath) || /\.ipynb$/i.test(relPath)) || matchesAny(relPath, NOT_CODE)) return false;
37
+ const folders = Object.values(layout?.folders ?? {});
38
+ const docs = folders.filter((f) => f.kind === 'docs' || /^docs?\b/i.test(f.purpose ?? '')).map((f) => `${f.path}/**`);
39
+ if (matchesAny(relPath, docs)) return false;
40
+ const code = folders.filter((f) => CODE_KINDS.has(f.kind)).map((f) => `${f.path}/**`);
41
+ return !code.length || matchesAny(relPath, code);
42
+ }
43
+ const PLAN = ['build.md', '**/build.md', 'bugs.md', 'workflow-bugs.md', 'CHANGELOG.md', 'design/shape.json', '.claude/**'];
44
+ const LAW = /(?:design\/)?shape\.json|(?:docs\/)?[\w.-]*-spec\.md|(?:docs\/)?laws\.md/g;
45
+ const LOCATOR = /:\d+\b|#[\w-]+|\{#[\w-]+\}|§\s*\d[\d.]*|\bL-?\d+\b|\b(?:section|line|row|rule|law)\s+["'`]?[\w.-]+|"[\w -]{2,40}"\s+row/i;
46
+ // The turn says it did something other than it was told.
47
+ const OVERRIDE = [
48
+ /\b(?:instead of|rather than|not)\s+(?:what\s+)?(?:the\s+)?(?:brief|prompt|order|instruction|request|work ?order|lead|you)\s+(?:asked|said|told|wanted|named|specified)/i,
49
+ /\b(?:the\s+)?(?:brief|prompt|order|instruction|request|work ?order|lead)\s+(?:said|asked|told|wanted|named|specified)\b[^.\n]{0,160}\b(?:but|however|instead)\b/i,
50
+ /\byou\s+(?:said|asked|told me|wanted)\b[^.\n]{0,160}\b(?:but I|however I|instead I|I instead)\b(?!['’]d\b|\s+(?:would|think|recommend|suggest|expect|believe)\b)/i, // a view is not an action (KWB-47)
51
+ /\bI\s+(?:deviated|departed|diverged)\s+from\b/i,
52
+ /\b(?:overrode|overriding|override[sd]?)\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order)/i,
53
+ /\bcontrary\s+to\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order)/i,
54
+ /\b(?:did\s+not|didn't)\s+(?:follow|do\s+what)\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order|you)/i,
55
+ ];
56
+
57
+ const splitName = (name) => name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([A-Za-z])(\d)/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
58
+ // each identifier's parts, and the identifier whole: a concept named editPage is the word editpage, not the words edit and page (KF-19, KF-39)
59
+ const partsOf = (text) => new Set((text.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []).flatMap((w) => [...splitName(w), w.toLowerCase()]));
60
+ const rowOf = (c) => c?.row ?? c ?? {};
61
+ const wordsOf = (name, c) => [name.toLowerCase(), ...(c?.words ?? rowOf(c).words ?? []).map((w) => String(w).toLowerCase())];
62
+
63
+ // The concepts a text touches: a word of the concept as a word or a part of an identifier, a code pattern, a per-instance
64
+ // array, the generator.
65
+ export function conceptsIn(text, shape) {
66
+ const parts = partsOf(text);
67
+ const out = new Set();
68
+ for (const [name, c] of Object.entries(shape?.concepts ?? {})) {
69
+ const row = rowOf(c);
70
+ const gen = String(row.generator ?? '').match(/^\s*([A-Za-z_]\w*)\s*\(/)?.[1];
71
+ const hit = wordsOf(name, c).some((w) => parts.has(w))
72
+ || (row.patterns ?? []).some((p) => { try { return new RegExp(p).test(text); } catch { return false; } })
73
+ || (row.arrays ?? []).some((a) => new RegExp(`\\b${a}\\b`).test(text))
74
+ || (gen && new RegExp(`\\b${gen}\\s*\\(`).test(text));
75
+ if (hit) out.add(name);
76
+ }
77
+ return out;
78
+ }
79
+
80
+ // The concepts listed after "concepts:" on a making line that the manifest does not know.
81
+ export function unknownConcepts(making, shape) {
82
+ const known = new Set(Object.entries(shape?.concepts ?? {}).flatMap(([n, c]) => wordsOf(n, c)));
83
+ const out = [];
84
+ // bracketed text goes first, nested brackets too, so a ";" inside a shape ("slot (many, 4; slotId(i, field))") does not end the list (KWB-17)
85
+ let flat = making;
86
+ for (let prev; prev !== flat;) { prev = flat; flat = flat.replace(/\([^()]*\)/g, ' '); }
87
+ // the list runs to `reuse:` or the line's end, and a shape word after a concept ("editKnob, many") is what the rule asks for (KF-24)
88
+ for (const m of flat.matchAll(/\bconcepts?\s*:\s*(.*?)(?=\breuse\s*:|\||$)/gim)) {
89
+ for (const item of m[1].split(/[,;]/)) {
90
+ const w = item.trim().split(/\s+/)[0]?.toLowerCase().replace(/[^a-z0-9_-]/g, '');
91
+ if (w && !NOT_CONCEPTS.has(w) && !known.has(w)) out.push(w);
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+
97
+ // The checkout's branch from its HEAD file (a worktree's .git file names its git dir): no git process (KWB-24: on a loaded
98
+ // machine each one took half a second)
99
+ function branchOf(root) {
100
+ try {
101
+ let dir = join(root, '.git');
102
+ const dotGit = readFileSync(dir, 'utf8').match(/^gitdir:\s*(.+)$/m); // a worktree; a folder throws and is read below
103
+ if (dotGit) dir = resolve(root, dotGit[1].trim());
104
+ return headBranch(dir);
105
+ } catch { return headBranch(join(root, '.git')); }
106
+ }
107
+ const headBranch = (gitDir) => { try { return readFileSync(join(gitDir, 'HEAD'), 'utf8').match(/^ref:\s*refs\/heads\/(.+)$/m)?.[1].trim() ?? 'HEAD'; } catch { return ''; } };
108
+
109
+ // The making lines of build.md's In progress section: those on this branch's entry, else every one there.
110
+ export function makingLines(root) {
111
+ const file = [join(root, 'build.md'), join(ROOT, 'build.md')].find((f) => existsSync(f));
112
+ if (!file) return [];
113
+ const text = readFileSync(file, 'utf8');
114
+ const at = text.search(/^##\s+In progress\b/im);
115
+ if (at < 0) return [];
116
+ const rest = text.slice(at).split('\n');
117
+ const section = [];
118
+ for (const l of rest.slice(1)) { if (/^##\s/.test(l)) break; section.push(l); }
119
+ const lines = section.filter((l) => /\bmaking:/i.test(l)).map((l) => l.slice(l.search(/\bmaking:/i)));
120
+ const branch = branchOf(root);
121
+ const mine = branch && branch !== 'HEAD' ? section.filter((l) => /\bmaking:/i.test(l) && (l.includes(branch) || l.includes(branch.split('/').pop()))).map((l) => l.slice(l.search(/\bmaking:/i))) : [];
122
+ return mine.length ? mine : lines;
123
+ }
124
+
125
+ // Added line ranges of each file against HEAD (the whole file when it is new or there is no git): two git processes for all
126
+ // the files of a checkout, not two per file. -> Map(file -> [[lo, hi]])
127
+ function addedRanges(root, files) {
128
+ const rels = files.map((f) => relative(root, f));
129
+ const out = new Map();
130
+ const tracked = new Set((spawnSync('git', ['-C', root, 'ls-files', '--', ...rels], { encoding: 'utf8' }).stdout ?? '').split('\n').filter(Boolean));
131
+ const diff = tracked.size ? spawnSync('git', ['-C', root, 'diff', '--relative', '-U0', 'HEAD', '--', ...rels.filter((r) => tracked.has(r))], { encoding: 'utf8' }).stdout ?? '' : '';
132
+ let cur = null;
133
+ for (const line of diff.split('\n')) {
134
+ const f = line.match(/^\+\+\+ b\/(.+)$/);
135
+ if (f) { cur = f[1]; continue; }
136
+ const h = line.match(/^@@ -\S+ \+(\d+)(?:,(\d+))? @@/);
137
+ if (h && cur) { const r = [Number(h[1]), Number(h[1]) + Number(h[2] ?? 1) - 1]; if (r[1] >= r[0]) out.set(cur, [...(out.get(cur) ?? []), r]); }
138
+ }
139
+ const lines = (f) => { try { return readFileSync(f, 'utf8').split('\n').length; } catch { return 0; } };
140
+ return new Map(files.map((f, i) => [f, tracked.has(rels[i]) ? out.get(rels[i]) ?? [] : lines(f) ? [[1, lines(f)]] : []]));
141
+ }
142
+
143
+ export function check(event, cwd = event.cwd ?? process.cwd()) {
144
+ const path = event.hook_event_name === 'SubagentStop' ? event.agent_transcript_path ?? event.transcript_path : event.transcript_path;
145
+ const turn = lastTurn(path ?? '');
146
+ const text = `${turn.text}\n${event.last_assistant_message ?? ''}`;
147
+ const writes = writesOfTurn(turn, cwd).filter((w) => insideProject(w.path));
148
+ const problems = [];
149
+ const rel = (p) => relative(checkoutRoot(p), p).replace(/\\/g, '/');
150
+ // Each write is judged against its own checkout (a worktree's own build.md, manifest and layout), never the session's folder:
151
+ // a lead in the main checkout writing in a worktree has its making line in that worktree's build.md (Stellix WB-86).
152
+ const byRoot = new Map();
153
+ for (const w of writes) { const r = checkoutRoot(w.path); if (!byRoot.has(r)) byRoot.set(r, []); byRoot.get(r).push(w); }
154
+ if (!byRoot.size) byRoot.set(checkoutRoot(join(cwd, 'x')), []);
155
+ const judged = [];
156
+ let shape = null, root = null, layout = null;
157
+ for (const [r, ws] of byRoot) {
158
+ const lay = readLayout(r);
159
+ const sh = readShape(r) ?? readShape(ROOT);
160
+ const mine = ws.filter((w) => !matchesAny(rel(w.path), [...PLAN, ...excludeGlobs(lay, sh)]));
161
+ // once-only (below) judges the checkout the source went to: the first checkout that holds a judged source
162
+ if (!root || (mine.some((w) => isSource(w.path)) && !judged.some((w) => isSource(w.path)))) { root = r; layout = lay; shape = sh; }
163
+ judged.push(...mine);
164
+
165
+ // 1. the concepts written, the making line, the manifest
166
+ if (!sh && mine.some((w) => isSource(w.path))) {
167
+ problems.push(`no design/shape.json, and this turn wrote source (${[...new Set(mine.filter((w) => isSource(w.path)).map((w) => rel(w.path)))].slice(0, 4).join(', ')}): write the manifest first, every concept one or many, its generator, its selectors`);
168
+ }
169
+ if (sh && !sh.error && mine.length) {
170
+ const written = new Map();
171
+ // only code is judged for its concepts: a note, a brief, a doc or a log names concepts in words ("Status line"), and that is no
172
+ // undeclared use (KWB-19)
173
+ for (const w of mine) if (isCode(rel(w.path), lay)) for (const c of conceptsIn(w.text ?? '', sh)) if (!written.has(c)) written.set(c, rel(w.path));
174
+ const making = makingLines(r).join('\n');
175
+ const wroteSource = mine.filter((w) => isSource(w.path)).map((w) => rel(w.path));
176
+ // a turn that writes source answers "what are you making" first, even before the manifest knows the concept
177
+ if (!making && (written.size || wroteSource.length)) problems.push(`build.md's In progress entry for this step has no "making:" line, and this turn wrote ${written.size ? [...written].map(([c, f]) => `${c} (${f})`).join(', ') : [...new Set(wroteSource)].slice(0, 4).join(', ')}: write the line (what you are making; concepts: each with its shape; reuse: the one function, where it lives)`);
178
+ else if (making) {
179
+ const named = conceptsIn(making, sh);
180
+ for (const [c, f] of written) if (!named.has(c)) problems.push(`the making line does not name ${c}, which this turn wrote (${f}): add it with its shape from design/shape.json, or take the write back`);
181
+ for (const u of unknownConcepts(making, sh)) problems.push(`the making line names "${u}", which has no row in design/shape.json: add the row first (one or many, its generator, its selectors)`);
182
+ }
183
+ }
184
+ }
185
+
186
+ // 2. once-only on the source the turn wrote. The tools' writes passed once-only itself when it is installed; running its detector again on them at
187
+ // every Stop cost over the kit's own 500 ms line (KF-33). The shell's writes stay: much of what they write is unknown at write time.
188
+ const atWrite = existsSync(join(claudeRoot(join(cwd, 'x')), '.claude/hooks/once-only.mjs'));
189
+ const sources = [...new Set(judged.filter((w) => isSource(w.path) && existsSync(w.path) && !(atWrite && w.tool !== 'Bash')).map((w) => w.path))]
190
+ .filter((p) => !matchesAny(rel(p), copyGlobs(layout)) && !isGenerated(readFileSync(p, 'utf8')));
191
+ if (sources.length) {
192
+ // once-only's index finds the candidates, jscpd confirms on those files alone (lib/dup-index.mjs; KWB-24: no whole-repo scan)
193
+ const added = addedRanges(root, sources);
194
+ const writes = sources.map((p) => ({ path: p, after: readFileSync(p, 'utf8'), ranges: added.get(p) })).filter((w) => w.ranges.length);
195
+ const r = writes.length ? judgeWrites(root, writes, { layout, shape }) : { clones: [] };
196
+ if (r.error) problems.push(`once-only could not judge this turn's writes: ${r.error}`);
197
+ const base = real(root);
198
+ const show = (s) => `${relative(base, s.file)}:${s.start}-${s.end}`;
199
+ const found = new Set();
200
+ for (const c of r.clones) found.add(`${show(c.at)} repeats ${show(c.other)} (${c.lines} lines)`);
201
+ if (found.size) problems.push(`this turn's writes did not pass once-only: ${[...found].slice(0, 5).join('; ')}. ${FIX}`);
202
+ }
203
+
204
+ // 3. the override rule
205
+ const claims = text.replace(/"[^"\n]*"|`[^`\n]*`/g, '""'); // a quotation reports words, it claims nothing (KWB-47)
206
+ const said = OVERRIDE.map((re) => claims.match(re)).find(Boolean);
207
+ if (said) {
208
+ const cites = text.split('\n').some((l) => [...l.matchAll(LAW)].some((m) => LOCATOR.test(l.slice(Math.max(0, m.index - 60), m.index + m[0].length + 60))));
209
+ const decided = /\bDecisions?\b/.test(text) && writes.some((w) => /decisions/i.test(w.path) || (existsSync(w.path) && /^#+\s*Decisions\b/im.test(readFileSync(w.path, 'utf8'))));
210
+ const logged = writes.some((w) => /(?:^|\/)(?:workflow-)?bugs\.md$/.test(w.path)) || /\b(?:K?WB|B)-\d+\b/.test(text);
211
+ const quote = `"${said[0].slice(0, 120)}"`;
212
+ if (!cites && !decided) problems.push(`the turn says it did something other than it was told (${quote}) and cites no law. Told X and a law file says Y: do Y, log the brief as the bug, cite the line (design/shape.json "<concept>", docs/<name>-spec.md {#anchor}, docs/laws.md L<n>). No law covers it: ask; overnight, decide and write it under Decisions and cite that entry. Claude's own judgement never overrides an instruction`);
213
+ else if (cites && !decided && !logged) problems.push(`the turn overrode what it was told by a law (${quote}) but did not log the brief as the bug: log it in bugs.md or workflow-bugs.md and name its number`);
214
+ }
215
+ return problems;
216
+ }
217
+
218
+ // Three send-backs with the same findings end the turn: the findings go to the user instead of looping.
219
+ function guard(event, problems) {
220
+ const file = join(process.env.TMPDIR ?? tmpdir(), `making-check-${String(event.session_id ?? 'session').replace(/[^\w-]/g, '')}.json`);
221
+ let st = {};
222
+ try { st = JSON.parse(readFileSync(file, 'utf8')); } catch { /* first */ }
223
+ const hash = createHash('sha1').update(problems.join('\n')).digest('hex');
224
+ // consecutive stops with the same findings count whether or not the host marks them as a continuation (stop_hook_active):
225
+ // a host that delivers each send-back as a new turn reset the count to 1 every time, and the limit was never reached (KWB-7)
226
+ const count = problems.length && st.hash === hash ? (st.count ?? 0) + 1 : problems.length ? 1 : 0;
227
+ try { writeFileSync(file, JSON.stringify({ hash, count })); } catch { /* the check still runs */ }
228
+ return count > MAX_SENDS;
229
+ }
230
+
231
+ async function main(text) {
232
+ let event;
233
+ try { event = JSON.parse(text); } catch { return 0; }
234
+ if (event.hook_event_name && !['Stop', 'SubagentStop'].includes(event.hook_event_name)) return 0;
235
+ const problems = check(event);
236
+ if (!problems.length) { guard(event, []); return 0; }
237
+ if (guard(event, problems)) {
238
+ process.stdout.write(JSON.stringify({ systemMessage: `making-check sent this turn back ${MAX_SENDS} times with the same findings; it ends here for you to decide:\n- ${problems.join('\n- ')}` }));
239
+ return 0;
240
+ }
241
+ process.stderr.write(`making-check sent this turn back:\n- ${problems.join('\n- ')}\n`);
242
+ return 2;
243
+ }
244
+
245
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
246
+ if (isMain(import.meta.url)) {
247
+ process.exit(process.argv.includes('--selftest') ? await (await import('./lib/making-check-selftest.mjs')).selftest(import.meta.url) : await asHook('making-check', main));
248
+ }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ // making: before anything is built, what is being made and the shape of every concept it touches (claude-packs 0.9.0
3
+ // item 6; KWB-1). A UserPromptSubmit hook: the same short text goes in front of every prompt, under 80 words, exactly
4
+ // The user's content: what are you making; each concept you touch and its shape from design/shape.json; a new concept gets its
5
+ // row first; a brief naming an instance is wrong by definition; used more than once, one function, parameterized, and
6
+ // where it lives. The answer is a `making:` line in the step's In progress entry in build.md (making-check reads it).
7
+ // When the prompt itself names an instance of a many concept, one line more: "you named X: building for all <shape>, X
8
+ // is the reproduction". Nothing else is added.
9
+ // node making.mjs --cost the text's size per turn (doctor prints it)
10
+ // node making.mjs --selftest
11
+ import { readFileSync, rmSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { checkoutRoot, ROOT } from './lib/project.mjs';
14
+ import { scratchProject } from './lib/selftest.mjs';
15
+ import { findInstanceLiterals, readShape } from './lib/shape.mjs';
16
+ import { asHook, isMain } from './lib/hook-log.mjs';
17
+
18
+ export const TEXT = "making: before the first write, answer in build.md's In progress entry for this step, one line: " +
19
+ '`making: <what you are making>; concepts: <each you touch, its shape from design/shape.json>; reuse: <function, where it lives>`. ' +
20
+ 'A new concept gets its shape.json row first. A brief naming an instance is wrong by definition. Used more than once? ' +
21
+ 'One function, parameterized; cite where it lives.';
22
+ export const MAX_WORDS = 80;
23
+
24
+ export const words = (t) => t.split(/\s+/).filter(Boolean).length;
25
+
26
+ // A token estimate, stated as one: word pieces of up to 6 letters, each digit run of up to 3 and each mark one token
27
+ // each (English in a BPE tokenizer runs about 1.3 tokens a word); never the model's own count.
28
+ export function tokens(t) {
29
+ let n = 0;
30
+ for (const p of t.match(/[A-Za-z]+|\d+|[^\sA-Za-z\d]/g) ?? []) n += /^[A-Za-z]/.test(p) ? Math.ceil(p.length / 6) : /^\d/.test(p) ? Math.ceil(p.length / 3) : 1;
31
+ return n;
32
+ }
33
+
34
+ const plural = (c, name) => (c?.plural instanceof Set && c.plural.size ? [...c.plural][0] : (c?.words ?? []).find((w) => w === `${name}s` || w === `${name}es`)) ?? `${name}s`;
35
+
36
+ // The echo line, or '' when the prompt names no instance of a many concept. One line, whatever the prompt holds.
37
+ export function echo(prompt, shape) {
38
+ if (!shape || !prompt) return '';
39
+ const named = [];
40
+ const seen = new Set();
41
+ for (const f of findInstanceLiterals(prompt, 'prompt.md', shape)) {
42
+ const c = shape.concepts?.[f.concept];
43
+ if ((c?.shape ?? c?.row?.shape) !== 'many' || seen.has(f.concept) || f.instance === 'each') continue;
44
+ seen.add(f.concept);
45
+ const x = /^\d+$/.test(f.instance) ? `${f.concept} ${f.instance}` : `${f.instance} ${f.concept}`;
46
+ const n = c.count ?? c.row?.count;
47
+ named.push(`${x}: building for all ${plural(c, f.concept)} (many${n ? `, ${n}` : ''}), ${x} is the reproduction`);
48
+ }
49
+ return named.length ? `you named ${named.slice(0, 2).join('; ')}` : '';
50
+ }
51
+
52
+ export function inject(prompt, shape) {
53
+ const line = echo(prompt, shape);
54
+ return line && words(`${TEXT} ${line}`) <= MAX_WORDS ? `${TEXT}\n${line}` : line ? `${TEXT}\n${line.split('; ')[0]}` : TEXT;
55
+ }
56
+
57
+ export const cost = () => {
58
+ const one = `${TEXT}\nyou named slot 2: building for all slots (many, 4), slot 2 is the reproduction`;
59
+ return `making: ${words(TEXT)} words, about ${tokens(TEXT)} tokens added to every prompt (${words(one)} words, about ${tokens(one)} tokens when it echoes a named instance); ` +
60
+ 'an estimate (word pieces of up to 6 letters, each mark one token), not the model\'s tokenizer';
61
+ };
62
+
63
+ function selftest() {
64
+ const dir = scratchProject({ 'design/shape.json': JSON.stringify({
65
+ concepts: {
66
+ slot: { shape: 'many', count: 4, generator: 'slotId(i, field)', words: ['slot', 'slots'], selectors: { selectedSlot: 'the slot the player has open' }, constants: { initSlot: { instance: 1, reason: 'INIT plays one saw' } } },
67
+ mixer: { shape: 'one', id: 'mixer', words: ['mixer'] },
68
+ },
69
+ exclude: [],
70
+ }) });
71
+ const shape = readShape(dir);
72
+ rmSync(dir, { recursive: true, force: true });
73
+ const cases = [
74
+ ['the text is under 80 words', words(TEXT) < MAX_WORDS],
75
+ ['the text with one echo line is still under 80 words', words(inject('drive slot 2 louder', shape)) <= MAX_WORDS],
76
+ ['the text asks every item of the order', ['making:', 'concept', 'shape', 'design/shape.json', 'row first', 'brief naming an instance', 'wrong by definition', 'more than once', 'one function', 'parameterized', 'where it lives', 'In progress', 'build.md'].every((k) => TEXT.toLowerCase().includes(k.toLowerCase()))],
77
+ ['a prompt naming slot 2 gets the echo', inject('the PAD in slot 2 is too quiet, fix it', shape).endsWith('you named slot 2: building for all slots (many, 4), slot 2 is the reproduction')],
78
+ ['a prompt naming no instance gets the text alone', inject('make the reverb darker on every slot', shape) === TEXT],
79
+ ['an example line is not an echo', inject('seen in: slot 3, build it for all slots', shape) === TEXT],
80
+ ['a selector is not an echo', inject('the selectedSlot loses its state after a load', shape) === TEXT],
81
+ ['a one concept is never echoed as many', inject('the mixer 1 meter', shape).split('\n').length <= 2 && !inject('the mixer 1 meter', shape).includes('all mixers')],
82
+ ['no manifest: the text alone', inject('drive slot 2', null) === TEXT],
83
+ ['the cost is measured and stated as an estimate', /\d+ words, about \d+ tokens/.test(cost()) && cost().includes('estimate')],
84
+ ];
85
+ for (const [name, ok] of cases) if (!ok) console.error(`making selftest FAILED: ${name}`);
86
+ const bad = cases.filter(([, ok]) => !ok).length;
87
+ if (!bad) console.log(`making selftest: OK (${cases.length} cases)`);
88
+ return bad ? 1 : 0;
89
+ }
90
+
91
+ function main(text) {
92
+ let event = {};
93
+ try { event = JSON.parse(text || '{}'); } catch { /* the text still goes */ }
94
+ if (event.hook_event_name && event.hook_event_name !== 'UserPromptSubmit') return 0;
95
+ const root = event.cwd ? checkoutRoot(join(event.cwd, 'prompt')) : ROOT;
96
+ let shape = null;
97
+ try { shape = readShape(root) ?? readShape(ROOT); } catch { /* the text still goes */ }
98
+ process.stdout.write(`${inject(event.prompt ?? '', shape)}\n`);
99
+ return 0;
100
+ }
101
+
102
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
103
+ if (isMain(import.meta.url)) {
104
+ if (process.argv.includes('--cost')) { console.log(cost()); process.exit(0); }
105
+ process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('making', main));
106
+ }