@thedecipherist/claude-packs 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (827) hide show
  1. package/CHANGELOG.md +680 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -2
  4. package/bin/claude-packs.mjs +204 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +92 -0
  7. package/configurations/full-stack/config.json +152 -0
  8. package/configurations/go/config.json +96 -0
  9. package/configurations/node/config.json +100 -0
  10. package/configurations/python/config.json +97 -0
  11. package/configurations/synth-creator/config.json +148 -0
  12. package/global/hooks/gnu-shell.mjs +131 -0
  13. package/global/hooks/lib/program-read.mjs +196 -0
  14. package/global/hooks/lib/program-writes.mjs +177 -0
  15. package/global/hooks/lib/shell-writes.mjs +293 -0
  16. package/global/hooks/shell-traps.mjs +79 -0
  17. package/global/hooks/version-pin-guard.mjs +280 -0
  18. package/global/skills/latest-versions/SKILL.md +49 -0
  19. package/lib/backup.mjs +34 -0
  20. package/lib/claude-md.mjs +58 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +86 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +115 -0
  26. package/lib/fix-plan.mjs +71 -0
  27. package/lib/git-tokens.mjs +64 -0
  28. package/lib/global.mjs +125 -0
  29. package/lib/hook-proof.mjs +28 -0
  30. package/lib/hook-timing.mjs +42 -0
  31. package/lib/hover-gate.mjs +29 -0
  32. package/lib/kit-lock.mjs +149 -0
  33. package/lib/layout.mjs +75 -0
  34. package/lib/logs.mjs +69 -0
  35. package/lib/manifest.mjs +24 -0
  36. package/lib/new.mjs +49 -0
  37. package/lib/once-only-report.mjs +54 -0
  38. package/lib/pack-manifest.mjs +20 -0
  39. package/lib/packs.mjs +107 -0
  40. package/lib/planner.mjs +59 -0
  41. package/lib/repeats.mjs +65 -0
  42. package/lib/repo-weight.mjs +28 -0
  43. package/lib/rules.mjs +40 -0
  44. package/lib/scan-detect.mjs +94 -0
  45. package/lib/scan.mjs +168 -0
  46. package/lib/selftest-cache.mjs +39 -0
  47. package/lib/settings.mjs +138 -0
  48. package/lib/shape-cli.mjs +76 -0
  49. package/lib/shape.mjs +4 -0
  50. package/lib/suggest.mjs +18 -0
  51. package/lib/switches.mjs +44 -0
  52. package/lib/symmetry-doctor.mjs +26 -0
  53. package/lib/sync.mjs +130 -0
  54. package/package.json +34 -4
  55. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  56. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  57. package/packs/ai/checks/model-id-from-config.json +28 -0
  58. package/packs/ai/checks/model-output-validated.json +26 -0
  59. package/packs/ai/pack.json +38 -0
  60. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  61. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  62. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  63. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  64. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  65. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  66. package/packs/audio-core/checks/keep-werror.json +12 -0
  67. package/packs/audio-core/checks/no-fast-math.json +14 -0
  68. package/packs/audio-core/checks/no-locale-change.json +12 -0
  69. package/packs/audio-core/checks/no-march-native.json +14 -0
  70. package/packs/audio-core/clang-tidy +28 -0
  71. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  72. package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
  73. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  74. package/packs/audio-core/pack.json +187 -0
  75. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  76. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  77. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  78. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  79. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  80. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  81. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  82. package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
  83. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  84. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  85. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  86. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  87. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  88. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  89. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  90. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  91. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  92. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  93. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  94. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  95. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  96. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  97. package/packs/audio-core/rules/output-limiter.md +23 -0
  98. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  99. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  100. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  101. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  102. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  103. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  104. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  105. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  106. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  107. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  108. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  109. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  110. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  111. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  112. package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
  113. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  114. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  115. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  116. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
  117. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  118. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  119. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  120. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  121. package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
  122. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  123. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  124. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  125. package/packs/audio-core/tools/tone-report.mjs +47 -0
  126. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  127. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  128. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  129. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  130. package/packs/base/checks/one-walk-per-template.json +23 -0
  131. package/packs/base/checks/stub-reports-success.json +31 -0
  132. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  133. package/packs/base/hooks/brief-guard.mjs +96 -0
  134. package/packs/base/hooks/code-shape.mjs +95 -0
  135. package/packs/base/hooks/dispatch-once.mjs +87 -0
  136. package/packs/base/hooks/file-size-guard.mjs +61 -0
  137. package/packs/base/hooks/folder-law.mjs +74 -0
  138. package/packs/base/hooks/git-guard.mjs +289 -0
  139. package/packs/base/hooks/lesson-checks.mjs +174 -0
  140. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  141. package/packs/base/hooks/lib/code-shapes.mjs +137 -0
  142. package/packs/base/hooks/lib/content.mjs +14 -0
  143. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  144. package/packs/base/hooks/lib/dup-index.mjs +199 -0
  145. package/packs/base/hooks/lib/dup.mjs +127 -0
  146. package/packs/base/hooks/lib/elements.mjs +87 -0
  147. package/packs/base/hooks/lib/event-log.mjs +67 -0
  148. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  149. package/packs/base/hooks/lib/git-adds.mjs +20 -0
  150. package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
  151. package/packs/base/hooks/lib/glob.mjs +33 -0
  152. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  153. package/packs/base/hooks/lib/house.py +77 -0
  154. package/packs/base/hooks/lib/languages.mjs +66 -0
  155. package/packs/base/hooks/lib/layout.mjs +54 -0
  156. package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
  157. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  158. package/packs/base/hooks/lib/program-read.mjs +196 -0
  159. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  160. package/packs/base/hooks/lib/project.mjs +81 -0
  161. package/packs/base/hooks/lib/prose.mjs +11 -0
  162. package/packs/base/hooks/lib/pushed.mjs +201 -0
  163. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  164. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  165. package/packs/base/hooks/lib/selftest.mjs +45 -0
  166. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  167. package/packs/base/hooks/lib/shape.mjs +294 -0
  168. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  169. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  170. package/packs/base/hooks/lib/turn.mjs +81 -0
  171. package/packs/base/hooks/lib/writes.mjs +71 -0
  172. package/packs/base/hooks/making-check.mjs +243 -0
  173. package/packs/base/hooks/making.mjs +106 -0
  174. package/packs/base/hooks/once-only.mjs +150 -0
  175. package/packs/base/hooks/placement-law.mjs +84 -0
  176. package/packs/base/hooks/repo-litter.mjs +75 -0
  177. package/packs/base/hooks/reuse-hint.mjs +75 -0
  178. package/packs/base/hooks/run-hooks.mjs +132 -0
  179. package/packs/base/hooks/secrets-guard.mjs +99 -0
  180. package/packs/base/hooks/shape-law.mjs +133 -0
  181. package/packs/base/hooks/suite-once.mjs +80 -0
  182. package/packs/base/hooks/template-law.mjs +148 -0
  183. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  184. package/packs/base/pack.json +214 -0
  185. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  186. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  187. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  188. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  189. package/packs/base/rules/finished-work-lands.md +17 -0
  190. package/packs/base/rules/git-right-first-time.md +19 -0
  191. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  192. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  193. package/packs/base/rules/making-line-first.md +14 -0
  194. package/packs/base/rules/open-items-tagged.md +17 -0
  195. package/packs/base/rules/plan-and-logs.md +22 -0
  196. package/packs/base/rules/references-use-anchors.md +19 -0
  197. package/packs/base/rules/refusals-are-judged.md +18 -0
  198. package/packs/base/rules/reuse-before-writing.md +15 -0
  199. package/packs/base/rules/secrets-never-committed.md +16 -0
  200. package/packs/base/rules/skill-writing.md +23 -0
  201. package/packs/base/rules/split-before-the-limit.md +14 -0
  202. package/packs/base/rules/subagent-dispatch.md +26 -0
  203. package/packs/base/rules/test-only-what-changed.md +17 -0
  204. package/packs/base/rules/test-the-template-once.md +22 -0
  205. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  206. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  207. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  208. package/packs/base/skills/land-branch/SKILL.md +24 -0
  209. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  210. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  211. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  212. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  213. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  214. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  215. package/packs/base/tools/shape-symmetry.mjs +178 -0
  216. package/packs/base/tools/test-verdict.mjs +75 -0
  217. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  218. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  219. package/packs/ci/pack.json +21 -0
  220. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  221. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  222. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  223. package/packs/content-site/checks/img-has-size.json +20 -0
  224. package/packs/content-site/checks/page-head-complete.json +26 -0
  225. package/packs/content-site/pack.json +22 -0
  226. package/packs/content-site/rules/content-has-one-source.md +23 -0
  227. package/packs/content-site/rules/media-is-sized.md +24 -0
  228. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  229. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  230. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  231. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  232. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  233. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  234. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  235. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  236. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  237. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  238. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  239. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  240. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  241. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  242. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  243. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  244. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  245. package/packs/cpp/pack.json +22 -0
  246. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  247. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  248. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  249. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  250. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  251. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  252. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  253. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  254. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  255. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  256. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  257. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  258. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  259. package/packs/docker/checks/compose-image-pinned.json +14 -0
  260. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  261. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  262. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  263. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  264. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  265. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  266. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  267. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  268. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  269. package/packs/docker/checks/docker-exec-form.json +16 -0
  270. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  271. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  272. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  273. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  274. package/packs/docker/checks/health-checks-nothing.json +25 -0
  275. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  276. package/packs/docker/checks/no-latest-base.json +13 -0
  277. package/packs/docker/checks/no-secret-in-image.json +13 -0
  278. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  279. package/packs/docker/checks/node-env-before-install.json +13 -0
  280. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  281. package/packs/docker/pack.json +46 -0
  282. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  283. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  284. package/packs/docker/rules/docker-exec-form.md +19 -0
  285. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  286. package/packs/docker/rules/dockerfile-production.md +25 -0
  287. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  288. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  289. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  290. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  291. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  292. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  293. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  294. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  295. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  296. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  297. package/packs/dynamic-data/pack.json +23 -0
  298. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  299. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  300. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  301. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  302. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  303. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  304. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  305. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  306. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  307. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  308. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  309. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  310. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  311. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  312. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  313. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  314. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  315. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  316. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  317. package/packs/fastify/checks/one-error-body.json +26 -0
  318. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  319. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  320. package/packs/fastify/checks/sec-headers-present.json +28 -0
  321. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  322. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  323. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  324. package/packs/fastify/pack.json +41 -0
  325. package/packs/fastify/rules/credentials-and-input.md +23 -0
  326. package/packs/fastify/rules/security-one-switch.md +28 -0
  327. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  328. package/packs/fastify/rules/tests-use-inject.md +13 -0
  329. package/packs/fastify/rules/three-layers.md +20 -0
  330. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  331. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  332. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  333. package/packs/full-stack/pack.json +76 -0
  334. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  335. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  336. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  337. package/packs/go/pack.json +21 -0
  338. package/packs/go/rules/go-errors-are-handled.md +22 -0
  339. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  340. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  341. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  342. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  343. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  344. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  345. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  346. package/packs/gpu-painter/pack.json +91 -0
  347. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  348. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  349. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  350. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  351. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  352. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  353. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  354. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  355. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  356. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  357. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  358. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  359. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  360. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  361. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  362. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  363. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  364. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  365. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  366. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  367. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  368. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  369. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  370. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  371. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  372. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  373. package/packs/hook-proof/pack.json +38 -0
  374. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  375. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  376. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  377. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  378. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  379. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  380. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  381. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  382. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  383. package/packs/host-agent/pack.json +22 -0
  384. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  385. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  386. package/packs/html-ui/checks/external-link-rel.json +21 -0
  387. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  388. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  389. package/packs/html-ui/checks/no-inline-style.json +23 -0
  390. package/packs/html-ui/pack.json +33 -0
  391. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  392. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  393. package/packs/http-api/checks/api-path-versioned.json +31 -0
  394. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  395. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  396. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  397. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  398. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  399. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  400. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  401. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  402. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  403. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  404. package/packs/http-api/checks/guard-fails-open.json +28 -0
  405. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  406. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  407. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  408. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  409. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  410. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  411. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  412. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  413. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  414. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  415. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  416. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  417. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  418. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  419. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  420. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  421. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  422. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  423. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  424. package/packs/http-api/pack.json +45 -0
  425. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  426. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  427. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  428. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  429. package/packs/http-api/rules/health-and-correlation.md +17 -0
  430. package/packs/http-api/rules/one-error-contract.md +17 -0
  431. package/packs/http-api/rules/routes-are-thin.md +17 -0
  432. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  433. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  434. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  435. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  436. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  437. package/packs/instrument-shell/pack.json +22 -0
  438. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  439. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  440. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  441. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  442. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  443. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  444. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  445. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  446. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  447. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  448. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  449. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  450. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  451. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  452. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  453. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  454. package/packs/js-ts/checks/no-any.json +27 -0
  455. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  456. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  457. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  458. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  459. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  460. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  461. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  462. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  463. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  464. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  465. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  466. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  467. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  468. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  469. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  470. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  471. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  472. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  473. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  474. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  475. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  476. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  477. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  478. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  479. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  480. package/packs/js-ts/pack.json +84 -0
  481. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  482. package/packs/js-ts/rules/env-through-config.md +36 -0
  483. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  484. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  485. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  486. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  487. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  488. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  489. package/packs/js-ts/rules/no-any.md +34 -0
  490. package/packs/js-ts/rules/no-console.md +30 -0
  491. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  492. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  493. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  494. package/packs/js-ts/rules/one-logger.md +24 -0
  495. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  496. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  497. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  498. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  499. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  500. package/packs/js-ts/rules/small-functions.md +20 -0
  501. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  502. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  503. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  504. package/packs/js-ts/rules/typed-exports.md +27 -0
  505. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  506. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  507. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  508. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  509. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  510. package/packs/midi/checks/midi-realtime-first.json +27 -0
  511. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  512. package/packs/midi/checks/position-not-integrated.json +25 -0
  513. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  514. package/packs/midi/checks/sysex-length-literal.json +23 -0
  515. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  516. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  517. package/packs/midi/pack.json +22 -0
  518. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  519. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  520. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  521. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  522. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  523. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  524. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  525. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  526. package/packs/mongo/checks/bulk-over-many.json +28 -0
  527. package/packs/mongo/checks/created-at-immutable.json +25 -0
  528. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  529. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  530. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  531. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  532. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  533. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  534. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  535. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  536. package/packs/mongo/checks/no-find-reads.json +26 -0
  537. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  538. package/packs/mongo/checks/no-mongoose.json +23 -0
  539. package/packs/mongo/checks/no-negation-delete.json +26 -0
  540. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  541. package/packs/mongo/checks/objectid-equals.json +26 -0
  542. package/packs/mongo/checks/update-then-reread.json +24 -0
  543. package/packs/mongo/checks/watch-not-poll.json +25 -0
  544. package/packs/mongo/pack.json +52 -0
  545. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  546. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  547. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  548. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  549. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  550. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  551. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  552. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  553. package/packs/multi-tenant/pack.json +22 -0
  554. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  555. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  556. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  557. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  558. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  559. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  560. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  561. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  562. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  563. package/packs/nginx/pack.json +43 -0
  564. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  565. package/packs/nginx/rules/nginx-production.md +26 -0
  566. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  567. package/packs/python/checks/py-no-bare-except.json +18 -0
  568. package/packs/python/checks/py-no-eval.json +18 -0
  569. package/packs/python/checks/py-no-mutable-default.json +18 -0
  570. package/packs/python/checks/py-no-shell-true.json +18 -0
  571. package/packs/python/checks/py-no-star-import.json +18 -0
  572. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  573. package/packs/python/checks/py-none-by-is.json +18 -0
  574. package/packs/python/checks/py-open-with-with.json +18 -0
  575. package/packs/python/checks/py-requests-timeout.json +18 -0
  576. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  577. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  578. package/packs/python/pack.json +26 -0
  579. package/packs/python/rules/python-errors-and-resources.md +26 -0
  580. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  581. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  582. package/packs/react/checks/dialog-needs-role.json +28 -0
  583. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  584. package/packs/react/checks/icon-button-needs-name.json +24 -0
  585. package/packs/react/checks/label-needs-a-field.json +24 -0
  586. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  587. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  588. package/packs/react/pack.json +20 -0
  589. package/packs/react/rules/react-accessible-controls.md +20 -0
  590. package/packs/react/rules/react-state-and-effects.md +23 -0
  591. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  592. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  593. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  594. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  595. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  596. package/packs/reverse-engineering/pack.json +35 -0
  597. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  598. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  599. package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
  600. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  601. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  602. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  603. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  604. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  605. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  606. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  607. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
  608. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  609. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  610. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  611. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  612. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  613. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  614. package/packs/shell/checks/sh-mktemp.json +18 -0
  615. package/packs/shell/checks/sh-no-backticks.json +18 -0
  616. package/packs/shell/checks/sh-no-eval.json +18 -0
  617. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  618. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  619. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  620. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  621. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  622. package/packs/shell/checks/sh-strict-mode.json +20 -0
  623. package/packs/shell/pack.json +28 -0
  624. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  625. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  626. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  627. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  628. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  629. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  630. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  631. package/packs/shell-hooks/pack.json +36 -0
  632. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  633. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  634. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  635. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  636. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  637. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  638. package/packs/sound-design/pack.json +22 -0
  639. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  640. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  641. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  642. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  643. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  644. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  645. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  646. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  647. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  648. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  649. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  650. package/packs/stellix/checks/one-seeded-random.json +16 -0
  651. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  652. package/packs/stellix/checks/world-no-libm.json +15 -0
  653. package/packs/stellix/checks/world-no-unordered.json +10 -0
  654. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  655. package/packs/stellix/pack.json +61 -0
  656. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  657. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  658. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  659. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  660. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  661. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  662. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  663. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  664. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  665. package/packs/stored-text/pack.json +41 -0
  666. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  667. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  668. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  669. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  670. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  671. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  672. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  673. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  674. package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
  675. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  676. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  677. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  678. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  679. package/packs/synth-creator/pack.json +136 -0
  680. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  681. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  682. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  683. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  684. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  685. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  686. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  687. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
  688. package/packs/synth-creator/tools/freeze.mjs +36 -0
  689. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  690. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  691. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  692. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  693. package/packs/tui/checks/tui-width-is-state.json +33 -0
  694. package/packs/tui/pack.json +44 -0
  695. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  696. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  697. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  698. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  699. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  700. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  701. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  702. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  703. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  704. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  705. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  706. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  707. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  708. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  709. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  710. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  711. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  712. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  713. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  714. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  715. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  716. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  717. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  718. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  719. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  720. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  721. package/packs/ui-craft/pack.json +43 -0
  722. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  723. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  724. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  725. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  726. package/packs/ui-craft/rules/interface-copy.md +26 -0
  727. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  728. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  729. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  730. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  731. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  732. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  733. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  734. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  735. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  736. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  737. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  738. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  739. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  740. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  741. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  742. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  743. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  744. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  745. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  746. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  747. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  748. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  749. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  750. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  751. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  752. package/packs/web/checks/client-no-api-key.json +29 -0
  753. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  754. package/packs/web/checks/client-no-server-import.json +31 -0
  755. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  756. package/packs/web/checks/css-no-inline-style.json +30 -0
  757. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  758. package/packs/web/checks/key-from-index.json +23 -0
  759. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  760. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  761. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  762. package/packs/web/checks/rcss-button-cursor.json +30 -0
  763. package/packs/web/checks/rcss-no-100vw.json +21 -0
  764. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  765. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  766. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  767. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  768. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  769. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  770. package/packs/web/pack.json +73 -0
  771. package/packs/web/rules/accessibility.md +26 -0
  772. package/packs/web/rules/client-boundaries.md +23 -0
  773. package/packs/web/rules/production-build.md +22 -0
  774. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  775. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  776. package/packs/web/rules/tailwind-v4.md +23 -0
  777. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  778. package/packs/web/rules/web-performance.md +21 -0
  779. package/packs/web/skills/design-direction/SKILL.md +30 -0
  780. package/packs/web/skills/design-review/SKILL.md +28 -0
  781. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  782. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  783. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  784. package/packs/websocket/checks/no-token-in-query.json +26 -0
  785. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  786. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  787. package/packs/websocket/pack.json +21 -0
  788. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  789. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  790. package/packs/writing/checks/no-copula-dodging.json +18 -0
  791. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  792. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  793. package/packs/writing/checks/no-participial-opener.json +18 -0
  794. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  795. package/packs/writing/checks/no-slop-words.json +18 -0
  796. package/packs/writing/checks/no-stock-connectors.json +18 -0
  797. package/packs/writing/checks/no-time-or-date.json +27 -0
  798. package/packs/writing/checks/no-trailing-ing.json +18 -0
  799. package/packs/writing/checks/no-vague-attribution.json +18 -0
  800. package/packs/writing/pack.json +43 -0
  801. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  802. package/packs/writing/rules/cut-the-padding.md +20 -0
  803. package/packs/writing/rules/direct-voice.md +22 -0
  804. package/packs/writing/rules/no-cargo-cult.md +16 -0
  805. package/packs/writing/rules/no-scaffolding.md +19 -0
  806. package/packs/writing/rules/no-stock-language.md +27 -0
  807. package/packs/writing/rules/no-time-or-date.md +18 -0
  808. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  809. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  810. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  811. package/packs/writing/skills/humanize/SKILL.md +28 -0
  812. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  813. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  814. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  815. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  816. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  817. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  818. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  819. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  820. package/packs/zod/hooks/schemas-only.mjs +53 -0
  821. package/packs/zod/pack.json +44 -0
  822. package/packs/zod/rules/env-shapes-only.md +14 -0
  823. package/packs/zod/rules/zod-4-schemas.md +14 -0
  824. package/profiles/stellix.layout.json +315 -0
  825. package/profiles/sub37.layout.json +247 -0
  826. package/prompts/scan-review.md +40 -0
  827. package/prompts/survey.md +82 -0
@@ -0,0 +1,109 @@
1
+ // making-check's planted faults: each a transcript of one turn in a throwaway git project, judged as Claude Code runs the
2
+ // Stop hook. Kept beside the hook so the hook itself stays short.
3
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { findJscpd } from './dup.mjs';
8
+ import { git, put, runHook, scratchProject, verdicts } from './selftest.mjs';
9
+
10
+ const FN = (name, k = '2.0f') => `float ${name} (float x, int slot) noexcept\n{\n const float g = std::tan (kPi * cutoff[slot] / sampleRate);\n const float k = ${k} - 2.0f * resonance[slot];\n` +
11
+ ' const float a1 = 1.0f / (1.0f + g * (g + k));\n const float a2 = g * a1;\n const float a3 = g * a2;\n const float v3 = x - ic2eq[slot];\n' +
12
+ ' const float v1 = a1 * ic1eq[slot] + a2 * v3;\n const float v2 = ic2eq[slot] + a2 * ic1eq[slot] + a3 * v3;\n ic1eq[slot] = 2.0f * v1 - ic1eq[slot];\n' +
13
+ ' ic2eq[slot] = 2.0f * v2 - ic2eq[slot];\n return v2;\n}\n';
14
+ const SHAPE = { concepts: { slot: { shape: 'many', count: 4, generator: 'slotId(i, field)', words: ['slot', 'slots'], selectors: { selectedSlot: 'the open slot' } }, mixer: { shape: 'one', id: 'mixer', words: ['mixer'] } }, exclude: ['src/gen/**'] };
15
+ const BUILD = (making) => `# x: the build\n\n## In progress\n\n| Started | Step | Task | State |\n|---|---|---|---|\n| 2026-11-01 10:00 EST | work/s1 | Slot filter${making ? `; ${making}` : ''} | started |\n\n## Steps\n`;
16
+
17
+ export function selftest(hookUrl) {
18
+ const hook = fileURLToPath(hookUrl);
19
+ if (!findJscpd([process.cwd()])) { console.error('making-check selftest FAILED: jscpd not found (once-only\'s detector)'); return 1; }
20
+ const dir = scratchProject({ 'design/shape.json': JSON.stringify(SHAPE), 'src/slot/filter.cpp': `#include "filter.h"\n\n${FN('slotFilter')}`, 'build.md': BUILD('') });
21
+ git(dir, 'init', '-q', '-b', 'work/s1');
22
+ git(dir, 'add', '-A');
23
+ git(dir, 'commit', '-qm', 'start');
24
+ const good = 'making: one filter per slot; concepts: slot (many, 4); reuse: slotFilter in src/slot/filter.cpp';
25
+ let n = 0;
26
+ const T = join(dir, 'transcript.jsonl');
27
+ const user = (text) => ({ type: 'user', message: { role: 'user', content: text } });
28
+ const use = (name, input, refused = false) => {
29
+ const id = `t${n += 1}`;
30
+ return [{ type: 'assistant', cwd: dir, message: { role: 'assistant', content: [{ type: 'tool_use', id, name, input }] } },
31
+ { type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: id, is_error: refused, content: refused ? 'BLOCKED' : 'ok' }] } }];
32
+ };
33
+ const say = (text) => ({ type: 'assistant', message: { content: [{ type: 'text', text }] } });
34
+ // every selftest run names its own sessions: the send-back counter lives in the temp folder, and a second run with the same names
35
+ // added to the first run's counts (0.9.3, KWB-7: the counter no longer resets between turns)
36
+ const run = `${process.pid}-${Date.now()}`;
37
+ const turn = (entries, { making = good, last = '', active = false, session = `s${n += 1}` } = {}) => {
38
+ session = `${run}-${session}`;
39
+ writeFileSync(join(dir, 'build.md'), BUILD(making));
40
+ writeFileSync(T, [user('build the slot filter'), ...entries.flat()].map((e) => JSON.stringify(e)).join('\n'));
41
+ return runHook(hook, dir, { hook_event_name: 'Stop', transcript_path: T, cwd: dir, session_id: session, stop_hook_active: active, last_assistant_message: last });
42
+ };
43
+ const at = (f) => join(dir, f);
44
+ const W = (f, content) => use('Write', { file_path: at(f), content });
45
+ // Three more turns of one session, each sent back: the last says what the limit did.
46
+ const sendBacks = (session, opts = {}) => { let got; for (let i = 0; i < 3; i += 1) got = turn([W('src/slot/gain.cpp', slotCode)], { making: '', session, ...opts }); return got; };
47
+ const slotCode = 'float slotGain (int slot) { return gains[slot]; }\n';
48
+ const mixerCode = 'float mixerLevel() { return mixer.level; }\n';
49
+ const landed = (f, text, fn) => { put(at(f), text); try { return fn(); } finally { rmSync(at(f), { force: true }); } };
50
+ const copy = `#include "filter.h"\n\n${FN('slotFilterB', '1.5f')}`;
51
+
52
+ const cases = [
53
+ ['slot code with a making line that names slot', turn([W('src/slot/gain.cpp', slotCode)]), 0],
54
+ ['slot code, no making line', turn([W('src/slot/gain.cpp', slotCode)], { making: '' }), 2],
55
+ ['the making line names mixer, the turn wrote slot', turn([W('src/slot/gain.cpp', slotCode)], { making: 'making: mixer meter; concepts: mixer (one)' }), 2],
56
+ ['the making line names lane, which has no manifest row', turn([W('src/slot/gain.cpp', slotCode)], { making: 'making: one filter per slot; concepts: slot (many, 4), lane (many); reuse: slotFilter in src/slot/filter.cpp' }), 2],
57
+ ['slot written by Edit, no making line', turn([use('Edit', { file_path: at('src/slot/filter.cpp'), old_string: 'x', new_string: slotCode })], { making: '' }), 2],
58
+ ['slot written by MultiEdit, no making line', turn([use('MultiEdit', { file_path: at('src/slot/filter.cpp'), edits: [{ old_string: 'x', new_string: slotCode }] })], { making: '' }), 2],
59
+ ['slot written by NotebookEdit, no making line', turn([use('NotebookEdit', { notebook_path: at('tools/look.ipynb'), new_source: 'slots = load()' })], { making: '' }), 2],
60
+ ['slot written by a shell heredoc, no making line', turn([use('Bash', { command: `cat > src/slot/gain.cpp <<'EOF'\n${slotCode}EOF` })], { making: '' }), 2],
61
+ ['slot written by sed -i, no making line', turn([use('Bash', { command: 'sed -i s/gain/slotGain/ src/slot/filter.cpp' })], { making: '' }), 2],
62
+ ['a refused write is not counted', turn([use('Write', { file_path: at('src/slot/gain.cpp'), content: slotCode }, true)], { making: '' }), 0],
63
+ ['a doc naming the mixer, no making line: docs are not judged (KWB-19)', turn([W('docs/mixer.md', 'The mixer sums the four buses.\n')], { making: '' }), 0],
64
+ ['plan and log files are not concepts written', turn([W('bugs.md', '| B-1 | slot 2 silent |\n')], { making: '' }), 0],
65
+ ['a turn that wrote nothing and claims nothing', turn([say('All green.')]), 0],
66
+ ['a write in another project is not judged', turn([use('Write', { file_path: join(tmpdir(), 'other-project-y', 'slot.cpp'), content: slotCode })], { making: '' }), 0],
67
+ ['source with no design/shape.json', (() => { rmSync(at('design/shape.json')); try { return turn([W('src/slot/gain.cpp', slotCode)]); } finally { put(at('design/shape.json'), JSON.stringify(SHAPE)); } })(), 2],
68
+ ['the turn wrote a copy of slotFilter with one line changed (once-only)', landed('src/slot/filter_b.cpp', copy, () => turn([W('src/slot/filter_b.cpp', copy)])), 2],
69
+ // a tracked file the turn added a copy to: its added lines come from one git diff of the checkout (KWB-24), the old lines
70
+ // never match themselves; the same file edited in place is no copy
71
+ ['the turn added a copy of slotFilter to a tracked file (Edit; git diff)', (() => {
72
+ const before = readFileSync(at('src/slot/filter.cpp'), 'utf8');
73
+ const add = `${before}\n${FN('slotFilterC', '1.25f')}`;
74
+ try { put(at('src/slot/filter.cpp'), add); return turn([use('Edit', { file_path: at('src/slot/filter.cpp'), old_string: 'x', new_string: FN('slotFilterC', '1.25f') })]); } finally { put(at('src/slot/filter.cpp'), before); }
75
+ })(), 2],
76
+ ['the turn changed one value of a tracked file in place', (() => {
77
+ const before = readFileSync(at('src/slot/filter.cpp'), 'utf8');
78
+ try { put(at('src/slot/filter.cpp'), before.replace('2.0f - 2.0f', '1.9f - 2.0f')); return turn([use('Edit', { file_path: at('src/slot/filter.cpp'), old_string: '2.0f', new_string: '1.9f' })]); } finally { put(at('src/slot/filter.cpp'), before); }
79
+ })(), 0],
80
+ ['the turn wrote code that calls slotFilter', landed('src/slot/level.cpp', 'float level (float x, int slot) { return slotFilter (x, slot); }\n', () => turn([W('src/slot/level.cpp', 'float level (float x, int slot) { return slotFilter (x, slot); }\n')])), 0],
81
+ ['a view is no override: an estimate that differs from the user\'s', turn([W('src/slot/gain.cpp', slotCode)], { last: 'You said it is small, but I would plan for hours. It is built as you asked.' }), 0],
82
+ ['a view is no override, in the short form', turn([W('src/slot/gain.cpp', slotCode)], { last: 'You said it is small, but I\'d plan for hours.' }), 0],
83
+ ['a quotation is no claim', turn([W('src/slot/gain.cpp', slotCode)], { last: 'The hook matched "you said it is small, but I built something else" in a log.' }), 0],
84
+ ['an override with no citation', turn([W('src/slot/gain.cpp', slotCode)], { last: 'The brief said to drive slot 2, but I built it for every slot instead.' }), 2],
85
+ ['an override citing docs/laws.md and logging the brief as the bug', turn([W('src/slot/gain.cpp', slotCode), W('bugs.md', '| B-9 | the brief named slot 2 |\n')], { last: 'The brief said to drive slot 2, but docs/laws.md L8 says every slot is one shape, so I built it for all; B-9 logs the brief.' }), 0],
86
+ ['an override citing design/shape.json without logging the brief', turn([W('src/slot/gain.cpp', slotCode)], { last: 'You asked for slot 2 only, but I built every slot instead: design/shape.json row "slot" is many(4).' }), 2],
87
+ ['an override decided overnight under Decisions', turn([W('src/slot/gain.cpp', slotCode), W('docs/decisions.md', '## Decisions\n\n- D-3: no law covered the fade; chose 20 ms.\n')], { last: 'Overnight: the order did not name a fade, but no law covers it, so I decided it under Decisions (D-3) rather than what the brief said.' }), 0],
88
+ ['the third send-back with the same findings ends the turn', (() => {
89
+ const s = 'loop';
90
+ turn([W('src/slot/gain.cpp', slotCode)], { making: '', session: s });
91
+ return sendBacks(s, { active: true });
92
+ })(), 0],
93
+ ['the limit holds when each send-back comes as a new turn (no stop_hook_active; KWB-7)', (() => {
94
+ const s = 'loop-new-turns';
95
+ turn([W('src/slot/gain.cpp', slotCode)], { making: '', session: s });
96
+ return sendBacks(s);
97
+ })(), 0],
98
+ ['a turn sent back is judged with what it wrote before the feedback', (() => {
99
+ const fb = { type: 'user', isMeta: true, message: { content: 'Stop hook feedback: making-check sent this turn back' } };
100
+ return turn([W('src/slot/gain.cpp', slotCode), [fb], [say('Done.')]], { making: '' });
101
+ })(), 2],
102
+ ['a ";" inside the brackets of a shape ("slot (many, 4; slotId(i, field))", KWB-17)', turn([W('src/slot/gain.cpp', slotCode)], { making: 'making: one filter per slot; concepts: slot (many, 4; slotId(i, field)), mixer (one); reuse: slotFilter in src/slot/filter.cpp' }), 0],
103
+ ['a note naming a concept the making line does not name (KWB-19)', turn([W('docs/notes.md', 'The mixer meter and its Status line.\n')]), 0],
104
+ ['a brief in a notes folder naming a concept (KWB-19)', turn([W('notes/brief.md', '# Brief: the mixer, every slot\n')]), 0],
105
+ ['source using a concept the making line does not name (KWB-19)', turn([W('src/mix/level.cpp', mixerCode)]), 2],
106
+ ['mixer code with a making line naming the mixer', turn([W('src/mix/level.cpp', mixerCode)], { making: 'making: the mixer level; concepts: mixer (one); reuse: none' }), 0],
107
+ ];
108
+ return verdicts('making-check', cases);
109
+ }
@@ -0,0 +1,76 @@
1
+ // What a write repeats when no stretch of it is an exact copy (0.25.0). AI rarely copies exactly: it writes the same thing again with a line
2
+ // changed here and there, or makes a function the repo already has. Both are judged from each file's statements and definitions:
3
+ // fingerprint(text, ext) -> [[hash, line]] the statements long enough to mean something
4
+ // definesOf(text, ext) -> [[name, line]] the functions and classes defined at the top level
5
+ // nearCopy(added, others) -> { file, from, to, line, matched, of } | null: the place holding most of `added`, in order
6
+ import { createHash } from 'node:crypto';
7
+ import { statementsOf } from './code-shapes.mjs';
8
+ import { languageOf } from './languages.mjs';
9
+
10
+ const MIN_STATEMENT = 8; // tokens: a shorter statement has one shape in any file
11
+ const MIN_MATCHED = 8; // statements in order, in one place
12
+ const MIN_SHARE = 0.7; // of the statements in that stretch of the write
13
+ const COMMON = 0.04; // of the files: a statement this common says nothing
14
+ const CANDIDATES = 3;
15
+
16
+ const hashOf = (s) => createHash('sha1').update(s).digest('hex').slice(0, 10);
17
+
18
+ export function fingerprint(text, ext) {
19
+ const lang = languageOf(ext);
20
+ if (!lang) return [];
21
+ return statementsOf(text, lang).filter((s) => s.tokens.length >= MIN_STATEMENT).map((s) => [hashOf(s.shape), s.line]);
22
+ }
23
+
24
+ export function definesOf(text, ext) {
25
+ const lang = languageOf(ext);
26
+ if (!lang?.define) return [];
27
+ const code = lang.blank(text);
28
+ return [...code.matchAll(lang.define)].map((m) => [m.slice(1).find(Boolean), code.slice(0, m.index).split('\n').length]).filter(([name]) => name);
29
+ }
30
+
31
+ // The longest common subsequence of two lists of hashes, as pairs of indexes.
32
+ function matchedPairs(a, b) {
33
+ const w = b.length + 1;
34
+ const t = new Uint16Array((a.length + 1) * w);
35
+ for (let i = a.length - 1; i >= 0; i -= 1) {
36
+ for (let j = b.length - 1; j >= 0; j -= 1) t[i * w + j] = a[i] === b[j] ? t[(i + 1) * w + j + 1] + 1 : Math.max(t[(i + 1) * w + j], t[i * w + j + 1]);
37
+ }
38
+ const out = [];
39
+ for (let i = 0, j = 0; i < a.length && j < b.length;) {
40
+ if (a[i] === b[j]) { out.push([i, j]); i += 1; j += 1; } else if (t[(i + 1) * w + j] >= t[i * w + j + 1]) i += 1; else j += 1;
41
+ }
42
+ return out;
43
+ }
44
+
45
+ // The densest stretch of pairs: most of the statements on both sides are matched.
46
+ function densest(pairs) {
47
+ let best = null;
48
+ for (let s = 0; s < pairs.length; s += 1) {
49
+ for (let e = pairs.length - 1; e >= s + MIN_MATCHED - 1; e -= 1) {
50
+ const n = e - s + 1;
51
+ const spanA = pairs[e][0] - pairs[s][0] + 1;
52
+ const spanB = pairs[e][1] - pairs[s][1] + 1;
53
+ if (n / spanA >= MIN_SHARE && n / spanB >= MIN_SHARE) { if (!best || n > best.n) best = { s, e, n, spanA }; break; }
54
+ }
55
+ }
56
+ return best;
57
+ }
58
+
59
+ // others: [{ file, st: [[hash, line]] }]. `added`: the statements this write adds, in order.
60
+ export function nearCopy(added, others) {
61
+ if (added.length < MIN_MATCHED) return null;
62
+ const seen = new Map();
63
+ for (const o of others) for (const h of new Set(o.st.map(([x]) => x))) seen.set(h, (seen.get(h) ?? 0) + 1);
64
+ const limit = Math.max(4, others.length * COMMON);
65
+ const rare = (list) => list.filter(([h]) => (seen.get(h) ?? 0) <= limit);
66
+ const mine = rare(added);
67
+ const wanted = new Set(mine.map(([h]) => h));
68
+ const ranked = others.map((o) => ({ o, hits: new Set(o.st.map(([h]) => h).filter((h) => wanted.has(h))).size })).filter((c) => c.hits >= MIN_MATCHED).sort((a, b) => b.hits - a.hits).slice(0, CANDIDATES);
69
+ for (const { o } of ranked) {
70
+ const theirs = rare(o.st);
71
+ const pairs = matchedPairs(mine.map(([h]) => h), theirs.map(([h]) => h));
72
+ const d = densest(pairs);
73
+ if (d) return { file: o.file, from: theirs[pairs[d.s][1]][1], to: theirs[pairs[d.e][1]][1], line: mine[pairs[d.s][0]][1], matched: d.n, of: d.spanA };
74
+ }
75
+ return null;
76
+ }
@@ -0,0 +1,196 @@
1
+ // program-read: a small reader of an inline program (python, node, perl, ruby) for program-writes.mjs (KWB-8). It never
2
+ // runs the program; it reads string literals, splits statements, and evaluates the simple expressions a file-writing
3
+ // program uses (a literal, a variable, Path(...) and joins, a file's text read, .replace chains, + of strings).
4
+ // scan(code, lang) -> { code, lits } code: the program with every literal replaced by \x01n\x02 and comments blanked;
5
+ // lits[n]: its text, or null when it interpolates (f-string, ${}, "$x")
6
+ // statements(code) -> [string] split at newlines and ; outside brackets
7
+ // splitArgs(text) -> [string] top-level comma split of a call's arguments
8
+ // closeOf(text, open) -> index the bracket that closes text[open]
9
+ // evaluate(expr, ctx) -> value | null value: { t: 'str'|'path', v }; ctx: { env, lang, at, readText }
10
+ const PAIRS = { '(': ')', '[': ']', '{': '}' };
11
+
12
+ function unescape(s, lang) {
13
+ return s.replace(/\\(\n|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|.)/gs, (m, e) => {
14
+ if (e === '\n') return '';
15
+ if (e[0] === 'x' && e.length === 3) return String.fromCharCode(parseInt(e.slice(1), 16));
16
+ if (e[0] === 'u' && e.length === 5) return String.fromCharCode(parseInt(e.slice(1), 16));
17
+ const map = { n: '\n', t: '\t', r: '\r', 0: '\0', '\\': '\\', "'": "'", '"': '"', '`': '`', $: '$' };
18
+ if (e in map) return map[e];
19
+ return lang === 'python' ? m : e;
20
+ });
21
+ }
22
+
23
+ export function scan(src, lang) {
24
+ const lits = [];
25
+ let out = '';
26
+ let i = 0;
27
+ const hash = lang !== 'node';
28
+ while (i < src.length) {
29
+ const c = src[i];
30
+ if ((hash && c === '#') || (!hash && c === '/' && src[i + 1] === '/')) {
31
+ while (i < src.length && src[i] !== '\n') i += 1;
32
+ continue;
33
+ }
34
+ if (!hash && c === '/' && src[i + 1] === '*') { const j = src.indexOf('*/', i + 2); i = j < 0 ? src.length : j + 2; continue; }
35
+ let pre = '';
36
+ if (lang === 'python') pre = src.slice(i).match(/^[rRbBuUfF]{0,2}(?=['"])/)?.[0] ?? '';
37
+ if (pre && /\w/.test(src[i - 1] ?? '')) pre = '';
38
+ const q0 = src[i + pre.length];
39
+ const quote = q0 === "'" || q0 === '"' || (q0 === '`' && lang === 'node') ? q0 : null;
40
+ if (!quote || (pre === '' && /\w/.test(src[i - 1] ?? '') && lang === 'python')) { out += c; i += 1; continue; }
41
+ const start = i + pre.length;
42
+ const triple = lang === 'python' && src.startsWith(quote.repeat(3), start);
43
+ const open = triple ? 3 : 1;
44
+ let j = start + open;
45
+ while (j < src.length && !(triple ? src.startsWith(quote.repeat(3), j) : src[j] === quote) && (triple || quote === '`' || src[j] !== '\n')) j += src[j] === '\\' ? 2 : 1;
46
+ const raw = src.slice(start + open, j);
47
+ i = Math.min(src.length, j + open);
48
+ const rawFlag = /[rR]/.test(pre);
49
+ let v;
50
+ if (/[fF]/.test(pre) && /[{}]/.test(raw)) v = null;
51
+ else if (quote === '`' && raw.includes('${')) v = null;
52
+ else if ((lang === 'perl' || lang === 'ruby') && quote === '"' && /[$@]|#\{/.test(raw)) v = null;
53
+ else if ((lang === 'perl' || lang === 'ruby') && quote === "'") v = raw.replace(/\\([\\'])/g, '$1');
54
+ else v = rawFlag ? raw : unescape(raw, lang);
55
+ out += `\x01${lits.length}\x02`;
56
+ lits.push(v);
57
+ }
58
+ return { code: out, lits };
59
+ }
60
+
61
+ export function closeOf(text, open) {
62
+ const want = PAIRS[text[open]];
63
+ let depth = 0;
64
+ for (let k = open; k < text.length; k += 1) {
65
+ if (PAIRS[text[k]]) depth += 1;
66
+ else if (')]}'.includes(text[k])) { depth -= 1; if (depth === 0) return text[k] === want ? k : -1; }
67
+ }
68
+ return -1;
69
+ }
70
+
71
+ function splitTop(text, seps) {
72
+ const out = [];
73
+ let depth = 0;
74
+ let cur = '';
75
+ for (const c of text) {
76
+ if (PAIRS[c]) depth += 1;
77
+ else if (')]}'.includes(c)) depth -= 1;
78
+ if (depth <= 0 && seps.includes(c)) { out.push(cur); cur = ''; } else cur += c;
79
+ }
80
+ out.push(cur);
81
+ return out;
82
+ }
83
+
84
+ export const statements = (code) => splitTop(code, '\n;').map((s) => s.trim()).filter(Boolean);
85
+ export const splitArgs = (text) => (text.trim() ? splitTop(text, ',').map((s) => s.trim()) : []);
86
+
87
+ // A postfix chain: a primary (a literal, a name, a parenthesised expression) and its .name, (args) and [index] steps.
88
+ function chain(expr) {
89
+ let k = 0;
90
+ let primary;
91
+ const lit = expr.match(/^\x01(\d+)\x02/);
92
+ const name = expr.match(/^[A-Za-z_$][\w$]*/);
93
+ if (lit) { primary = { lit: Number(lit[1]) }; k = lit[0].length; }
94
+ else if (expr[0] === '(') { const e = closeOf(expr, 0); if (e < 0) return null; primary = { group: expr.slice(1, e) }; k = e + 1; }
95
+ else if (name) { primary = { name: name[0] }; k = name[0].length; }
96
+ else return null;
97
+ const steps = [];
98
+ while (k < expr.length) {
99
+ const rest = expr.slice(k);
100
+ const dot = rest.match(/^\s*(?:\.|->|::)\s*([A-Za-z_$][\w$]*)/);
101
+ if (dot) { steps.push({ name: dot[1] }); k += dot[0].length; continue; }
102
+ const ws = rest.match(/^\s*/)[0].length;
103
+ if (rest[ws] === '(' || rest[ws] === '[') {
104
+ const e = closeOf(expr, k + ws);
105
+ if (e < 0) return null;
106
+ steps.push(rest[ws] === '(' ? { args: splitArgs(expr.slice(k + ws + 1, e)) } : { index: expr.slice(k + ws + 1, e) });
107
+ k = e + 1;
108
+ continue;
109
+ }
110
+ return null;
111
+ }
112
+ return { primary, steps };
113
+ }
114
+
115
+ const str = (v) => (v && (v.t === 'str' || v.t === 'path') ? v.v : null);
116
+ const joinPaths = (parts) => (parts.some((p) => p === null) ? null : parts.reduce((a, b) => (b.startsWith('/') ? b : a ? `${a.replace(/\/$/, '')}/${b}` : b), ''));
117
+ const PATH_FNS = /^(?:(?:pathlib\.)?(?:Pure)?(?:Posix)?Path|os\.path\.join|path\.join|path\.resolve|join|resolve|File\.join|os\.path\.expanduser|os\.path\.abspath|path\.normalize)$/;
118
+ const READS = /^(?:(?:fs|fsp|fs\.promises)\.)?readFileSync$|^File\.read$|^IO\.read$/;
119
+
120
+ export function evaluate(expr, ctx) {
121
+ const e = expr.trim().replace(/^await\s+/, '');
122
+ if (!e) return null;
123
+ const plus = splitTop(e, '+');
124
+ if (plus.length > 1 && ctx.lang !== 'perl') {
125
+ const vs = plus.map((p) => str(evaluate(p, ctx)));
126
+ return vs.some((v) => v === null) ? null : { t: 'str', v: vs.join('') };
127
+ }
128
+ if (ctx.lang === 'python') {
129
+ const slash = splitTop(e, '/');
130
+ if (slash.length > 1) {
131
+ const vs = slash.map((p) => evaluate(p, ctx));
132
+ return vs[0]?.t === 'path' ? { t: 'path', v: joinPaths(vs.map(str)) } : null;
133
+ }
134
+ }
135
+ const c = chain(e);
136
+ if (!c) return null;
137
+ // callee: the dotted name while the chain is still a plain name (os.path.join, p.read_text); recv: the value before its last dot
138
+ let cur = null;
139
+ let callee = null;
140
+ let recv = null;
141
+ let method = null;
142
+ if (c.primary.lit !== undefined) cur = ctx.lits[c.primary.lit] === null ? null : { t: 'str', v: ctx.lits[c.primary.lit] };
143
+ else if (c.primary.group !== undefined) cur = evaluate(c.primary.group, ctx);
144
+ else { callee = c.primary.name; cur = ctx.env.get(callee) ?? null; }
145
+ for (const s of c.steps) {
146
+ if (s.name !== undefined) {
147
+ if (callee !== null) { recv = cur; callee = `${callee}.${s.name}`; cur = ctx.env.get(callee) ?? null; } else { method = s.name; }
148
+ continue;
149
+ }
150
+ if (s.args) {
151
+ if (callee !== null) {
152
+ const last = callee.split('.').pop();
153
+ cur = callFn(callee, s.args, ctx) ?? (recv && callee.includes('.') ? callMethod(recv, last, s.args, ctx) : null);
154
+ callee = null;
155
+ } else cur = method && cur ? callMethod(cur, method, s.args, ctx) : null;
156
+ method = null;
157
+ recv = null;
158
+ continue;
159
+ }
160
+ cur = null; callee = null; method = null;
161
+ }
162
+ return method ? null : cur;
163
+ }
164
+
165
+ function callFn(name, args, ctx) {
166
+ const vs = args.filter((a) => !/^\w+\s*=[^=]/.test(a)).map((a) => evaluate(a, ctx));
167
+ if (/(?:^|\.)Path\.home$/.test(name)) return process.env.HOME ? { t: 'path', v: process.env.HOME } : null;
168
+ if (/(?:^|\.)Path\.cwd$|^process\.cwd$|^os\.getcwd$/.test(name)) return { t: 'path', v: ctx.at('.') };
169
+ if (PATH_FNS.test(name)) return vs.length ? { t: 'path', v: joinPaths(vs.map(str)) } : null;
170
+ if (READS.test(name) || /^(?:io\.|codecs\.)?open$/.test(name)) {
171
+ const p = str(vs[0]);
172
+ if (p === null) return null;
173
+ ctx.reads?.add(ctx.at(p));
174
+ return READS.test(name) ? { t: 'str', v: ctx.readText(ctx.at(p)) ?? '' } : { t: 'file', v: p };
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function callMethod(base, m, args, ctx) {
180
+ const vs = args.map((a) => evaluate(a, ctx));
181
+ if (['expanduser', 'resolve', 'absolute'].includes(m)) return base.t === 'path' ? base : null;
182
+ if (m === 'joinpath') return { t: 'path', v: joinPaths([base.v, ...vs.map(str)]) };
183
+ if ((m === 'read_text' && base.t === 'path') || (m === 'read' && base.t === 'file')) {
184
+ ctx.reads?.add(ctx.at(base.v));
185
+ return { t: 'str', v: ctx.readText(ctx.at(base.v)) ?? '' };
186
+ }
187
+ if ((m === 'replace' || m === 'replaceAll') && base.t === 'str') {
188
+ const [a, b] = vs.map(str);
189
+ if (a === null || b === null || /^\//.test(args[0] ?? '')) return null;
190
+ if (m === 'replace' && ctx.lang === 'node') return { t: 'str', v: base.v.replace(a, () => b) };
191
+ const n = vs[2] ? Number(str(vs[2]) ?? args[2]) : -1;
192
+ if (!(n < 0)) { let out = base.v; for (let k = 0; k < n && out.includes(a); k += 1) out = out.replace(a, () => b); return { t: 'str', v: out }; }
193
+ return { t: 'str', v: base.v.split(a).join(b) };
194
+ }
195
+ return null;
196
+ }
@@ -0,0 +1,177 @@
1
+ // program-writes: the files an inline program writes (KWB-8: `python3 - <<EOF ... open(p,'w').write(...)` got past every
2
+ // write-time hook; the shell reader saw redirects, tee, sed -i, cp and heredocs, not a program's own writes).
3
+ // programOf(exe, args, stdin) -> { lang, code } | null
4
+ // an interpreter given its program inline: python -c / python - or python with a heredoc on stdin; node -e / -p /
5
+ // node - or a heredoc; perl -e / -E (or a heredoc); ruby -e (or a heredoc). A script file (python3 tools/x.py,
6
+ // node tools/x.mjs, python3 -m mod) is null: it is the project's own tool, reviewed like code, not judged as a write.
7
+ // programWrites(program, at, readText) -> [{ path, op, content, from? }] (op and content as in shell-writes.mjs)
8
+ // a write is a call that opens or makes a file for writing: python open(p, 'w'|'a'|'x'|'r+'), Path.write_text /
9
+ // write_bytes / touch / unlink, shutil.copy* / move / copytree / rmtree, os.remove / rename / replace; node
10
+ // fs.writeFileSync / appendFileSync / copyFileSync / cpSync / renameSync / rmSync / unlinkSync / createWriteStream;
11
+ // perl open(FH, '>' ...), rename, unlink, File::Copy; ruby File.write / File.open(p, 'w') / FileUtils.*.
12
+ // A read (open(p), open(p, 'r'), read_text, readFileSync) is never a write. content is the new text where the
13
+ // program says it (a literal, or a file's text read and .replace'd, in a program with no loop or branch), else null.
14
+ // A write whose path the reader cannot work out makes every path the program names (its path-like literals, less
15
+ // the ones it only reads) a write of unknown content, so the path-based hooks still judge it.
16
+ import { basename, join } from 'node:path';
17
+ import { statSync } from 'node:fs';
18
+ import { closeOf, evaluate, scan, splitArgs, statements } from './program-read.mjs';
19
+
20
+ const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
21
+
22
+ export function programOf(exe, args, stdin) {
23
+ const lang = /^python(\d+(\.\d+)*)?$|^pypy3?$/.test(exe) ? 'python' : /^(node|nodejs|bun)$/.test(exe) ? 'node' : exe === 'perl' ? 'perl' : exe === 'ruby' ? 'ruby' : null;
24
+ if (!lang) return null;
25
+ const inline = { python: /^-[A-Za-z]*c$/, node: /^(-e|--eval|-p|--print|-pe)$/, perl: /^-[A-Za-z]*[eE]$/, ruby: /^-[A-Za-z]*e$/ }[lang];
26
+ const takesValue = { python: /^-[WXQ]$/, node: /^(-r|--require|--import|--loader|--experimental-loader|-C|--conditions)$/, perl: /^-[IMx]$/, ruby: /^-[Ir]$/ }[lang];
27
+ const code = [];
28
+ for (let k = 0; k < args.length; k += 1) {
29
+ const a = args[k];
30
+ if (inline.test(a)) { if (args[k + 1] !== undefined) code.push(args[k + 1]); k += 1; if (lang === 'python') break; continue; }
31
+ if (lang === 'node' && /^--(eval|print)=/.test(a)) { code.push(a.slice(a.indexOf('=') + 1)); continue; }
32
+ if (lang === 'python' && /^-[A-Za-z]*m$/.test(a)) return null;
33
+ if (takesValue.test(a)) { k += 1; continue; }
34
+ if (a.startsWith('-') && a !== '-') continue;
35
+ if (code.length) break;
36
+ return a === '-' && stdin !== null ? { lang, code: stdin } : null;
37
+ }
38
+ if (code.length) return { lang, code: code.join('\n') };
39
+ return stdin !== null && stdin !== undefined ? { lang, code: stdin } : null;
40
+ }
41
+
42
+ // The receiver of a method call: the postfix expression that ends just before text[dot].
43
+ function receiverBefore(text, dot) {
44
+ let k = dot - 1;
45
+ while (k >= 0) {
46
+ const c = text[k];
47
+ if (c === ')' || c === ']') {
48
+ const open = c === ')' ? '(' : '[';
49
+ let depth = 0;
50
+ for (; k >= 0; k -= 1) { if (text[k] === c) depth += 1; else if (text[k] === open && --depth === 0) break; }
51
+ k -= 1;
52
+ } else if (c === '\x02') k = text.lastIndexOf('\x01', k) - 1;
53
+ else if (/[\w$.]/.test(c)) k -= 1;
54
+ else break;
55
+ }
56
+ return text.slice(k + 1, dot);
57
+ }
58
+
59
+ const modeOp = (m) => (m === null ? 'write' : /^\+?>>|a/.test(m) ? 'append' : /^\+</.test(m) || /r\+/.test(m) ? 'edit' : /[wx>]|\+/.test(m) ? 'write' : null);
60
+
61
+ const SITES = {
62
+ python: /(?<![\w.])(?:io\.|codecs\.)?open\s*\(|\.(open|write_text|write_bytes|touch|unlink|rmdir)\s*\(|\bshutil\.(copy|copy2|copyfile|move|copytree|rmtree)\s*\(|\bos\.(remove|unlink|rename|replace)\s*\(/g,
63
+ node: /\b(writeFileSync|writeFile|appendFileSync|appendFile|copyFileSync|copyFile|cpSync|renameSync|rmSync|unlinkSync|createWriteStream|truncateSync|openSync|outputFileSync)\s*\(|\bBun\.(write)\s*\(/g,
64
+ perl: /\bopen\s*(?=[\s(])|\b(rename|unlink|copy|move)\s*\(/g,
65
+ ruby: /\b(?:File|IO)\.(write|binwrite|open|rename|delete|unlink)\s*\(|\bFileUtils\.(cp|copy|mv|move|rm|rm_f|rm_rf|touch|install)\s*\(/g,
66
+ };
67
+
68
+ const COPY = /^(copy|copy2|copyfile|copyFileSync|copyFile|cp|install)$/;
69
+ const MOVE = /^(move|rename|replace|renameSync|mv)$/;
70
+ const DELETE = /^(rmtree|remove|unlink|rmdir|rmSync|unlinkSync|delete|rm|rm_f|rm_rf)$/;
71
+
72
+ export function programWrites({ lang, code: src }, at, readText) {
73
+ const { code, lits } = scan(src, lang);
74
+ const stmts = statements(code);
75
+ const branchy = /\b(for|while|foreach|until|unless|lambda|function|each|map|if|elif|else|try|switch|case|def)\b|=>|\bdo\b/.test(code);
76
+ const ASSIGN = /^(?:(?:const|let|var|my|our)\s+)?([$@%]?[A-Za-z_][\w.]*)\s*(\+?)=(?!=)\s*([\s\S]+)$/;
77
+ const count = new Map();
78
+ for (const s of stmts) { const m = s.match(ASSIGN); if (m) count.set(m[1], (count.get(m[1]) ?? 0) + 1); }
79
+ const env = new Map();
80
+ const reads = new Set();
81
+ const ctx = { env, lits, lang, at, readText, reads };
82
+ const val = (e) => (e === undefined ? null : evaluate(e, ctx));
83
+ const pathOf = (e) => { const v = val(e); return v && (v.t === 'str' || v.t === 'path') && v.v ? at(v.v) : null; };
84
+ const textOf = (e) => { const v = val(e); return !branchy && v && v.t === 'str' ? v.v : null; };
85
+ const out = [];
86
+ const handles = new Map();
87
+ let unresolved = false;
88
+ const add = (path, op, content, extra = {}) => { if (path === null) unresolved = true; else out.push({ path, op, content, ...extra }); return out.at(-1); };
89
+ const kw = (args, name) => args.find((a) => new RegExp(`^${name}\\s*=`).test(a))?.replace(/^\w+\s*=\s*/, '');
90
+ const modeArg = (e) => (e === undefined ? undefined : val(e)?.t === 'str' ? val(e).v : null);
91
+
92
+ for (const s of stmts) {
93
+ for (const m of s.matchAll(SITES[lang])) {
94
+ const name = m.slice(1).find(Boolean) ?? 'open';
95
+ const paren = s.indexOf('(', m.index + m[0].length - 1);
96
+ let args;
97
+ let end;
98
+ if (lang === 'perl' && name === 'open' && s[paren] !== '(' ) {
99
+ const rest = s.slice(m.index + m[0].length).split(/\s+(?:or|\|\|)\s+|\s+or$/)[0];
100
+ args = splitArgs(rest.replace(/^\s*\(|\)\s*$/g, ''));
101
+ end = s.length;
102
+ } else {
103
+ end = closeOf(s, paren);
104
+ if (end < 0) continue;
105
+ args = splitArgs(s.slice(paren + 1, end));
106
+ }
107
+ const pos = args.filter((a) => !/^\w+\s*=[^=]/.test(a));
108
+ const method = m[0].startsWith('.');
109
+ const recv = method ? receiverBefore(s, m.index) : null;
110
+ let site = null;
111
+ if (lang === 'perl' && name === 'open') {
112
+ const mode = pos.length >= 3 ? modeArg(pos[1]) : modeArg(pos[1]);
113
+ if (pos.length >= 3) { const op = modeOp(mode); if (op) site = add(pathOf(pos[2]), op, null); } else if (mode !== null && mode !== undefined) {
114
+ const two = mode.match(/^\s*(\+?[<>]{1,2}|\|)?\s*(.*)$/);
115
+ const op = two[1] && two[1] !== '|' ? modeOp(two[1]) : null;
116
+ if (op) site = add(two[2] ? at(two[2]) : null, op, null);
117
+ } else if (pos[1] !== undefined) site = add(null, 'write', null);
118
+ } else if (name === 'open' || name === 'openSync') {
119
+ if (method && val(recv)?.t !== 'path') continue;
120
+ const target = method ? recv : pos[0];
121
+ const mode = modeArg(method ? pos[0] ?? kw(args, 'mode') : pos[1] ?? kw(args, 'mode'));
122
+ const op = mode === undefined ? null : modeOp(mode);
123
+ if (!op) { const p = pathOf(target); if (p) reads.add(p); continue; }
124
+ site = add(pathOf(target), op, op === 'edit' ? null : '');
125
+ const after = s.slice(end + 1);
126
+ const chained = after.match(/^\s*\.\s*write\s*\(/);
127
+ const as = after.match(/^\s*as\s+([A-Za-z_]\w*)/) ?? s.slice(0, m.index).match(/^([A-Za-z_]\w*)\s*=\s*$/);
128
+ if (site && chained) { const e = closeOf(after, after.indexOf('(')); site.content = e < 0 ? null : textOf(after.slice(after.indexOf('(') + 1, e)); }
129
+ else if (site && as) handles.set(as[1], { site, parts: [], ok: !branchy && site.content !== null, uses: 0 });
130
+ else if (site) site.content = null;
131
+ } else if (name === 'write_text' || name === 'write' || name === 'binwrite' || /^(writeFile|outputFile)/.test(name) || /^appendFile/.test(name)) {
132
+ const target = method ? recv : pos[0];
133
+ site = add(pathOf(target), /^append/.test(name) ? 'append' : 'write', name === 'binwrite' ? null : textOf(method ? pos[0] : pos[1]));
134
+ } else if (name === 'write_bytes' || name === 'createWriteStream' || name === 'truncateSync') site = add(pathOf(method ? recv : pos[0]), name === 'truncateSync' ? 'truncate' : 'write', null);
135
+ else if (name === 'touch') site = add(pathOf(method ? recv : pos[0]), 'touch', null);
136
+ else if (DELETE.test(name)) { if (method && val(recv)?.t !== 'path') continue; for (const a of method ? [recv] : pos.slice(0, lang === 'ruby' ? pos.length : 1)) add(pathOf(a), 'delete', null); }
137
+ else if (COPY.test(name) || MOVE.test(name) || /^(copytree|cpSync)$/.test(name)) {
138
+ if (method) continue;
139
+ const from = pathOf(pos[0]);
140
+ let to = pathOf(pos[1]);
141
+ if (to && from && isDir(to) && !/^(copytree|cpSync)$/.test(name)) to = join(to, basename(from));
142
+ const tree = /^(copytree|cpSync)$/.test(name);
143
+ add(to, MOVE.test(name) ? 'move' : 'copy', tree || !from ? null : readText(from), from ? { from } : {});
144
+ if (MOVE.test(name)) add(from, 'delete', null);
145
+ }
146
+ }
147
+ for (const [h, rec] of handles) {
148
+ for (const m of s.matchAll(new RegExp(`(?<![\\w.])${h.replace(/\$/g, '\\$')}\\b`, 'g'))) {
149
+ rec.uses += 1;
150
+ const after = s.slice(m.index + h.length);
151
+ const w = after.match(/^\s*\.\s*(write|close)\s*\(/);
152
+ if (!w) continue;
153
+ rec.uses -= 1;
154
+ if (w[1] === 'close') continue;
155
+ const open = m.index + h.length + after.indexOf('(');
156
+ const e = closeOf(s, open);
157
+ const t = e < 0 ? null : textOf(s.slice(open + 1, e));
158
+ if (t === null) rec.ok = false; else rec.parts.push(t);
159
+ }
160
+ }
161
+ const a = s.match(ASSIGN);
162
+ if (a) {
163
+ const v = branchy && count.get(a[1]) > 1 ? null : a[2] ? ((x, y) => (x?.t === 'str' && y?.t === 'str' ? { t: 'str', v: x.v + y.v } : null))(env.get(a[1]), val(a[3])) : val(a[3]);
164
+ if (v) env.set(a[1], v); else env.delete(a[1]);
165
+ }
166
+ }
167
+ for (const rec of handles.values()) rec.site.content = rec.ok && rec.uses <= 1 ? rec.parts.join('') : null;
168
+ if (unresolved) {
169
+ const named = new Set(out.map((w) => w.path));
170
+ for (const l of lits) {
171
+ if (l === null || l.length > 300 || !/^[~\w.@+\-/]+$/.test(l) || /^[-.]+$|^-|^\d+(\.\d+)*$/.test(l) || !(l.includes('/') || /\.[A-Za-z][A-Za-z0-9]{0,7}$/.test(l))) continue;
172
+ const p = at(l);
173
+ if (!named.has(p) && !reads.has(p)) { named.add(p); out.push({ path: p, op: 'write', content: null, named: true }); }
174
+ }
175
+ }
176
+ return out;
177
+ }
@@ -0,0 +1,81 @@
1
+ // Every project hook judges only this project's own files. Claude regularly edits sibling
2
+ // projects from one session (rolling a lesson out to every project), and those edits must never be
3
+ // blocked by this project's laws; the other project's own hooks govern it there.
4
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
5
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
6
+
7
+ export const ROOT = resolve(process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
8
+
9
+ export function insideProject(path, base = process.cwd()) {
10
+ if (!path) return false;
11
+ const rel = relative(ROOT, resolve(base, path));
12
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
13
+ }
14
+
15
+ // True when a shell command moves to another folder outside this project (cd, pushd, git -C,
16
+ // make -C), so its relative paths are not ours to judge.
17
+ export function commandLeavesProject(command = '') {
18
+ for (const m of command.matchAll(/(?:^|[;&|]\s*|\s)(?:cd|pushd)\s+(['"]?)([^\s;&|'"]+)\1/g)) if (!insideProject(m[2].replace(/^~(?=\/)/, process.env.HOME ?? '~'))) return true;
19
+ for (const m of command.matchAll(/\b(?:git|make|ninja|cmake)\s+(?:--build\s+)?-C\s+(['"]?)([^\s;&|'"]+)\1/g)) if (!insideProject(m[2])) return true;
20
+ return false;
21
+ }
22
+
23
+ // The checkout a file belongs to: the project itself, or a git worktree of it. Agents work in
24
+ // worktrees under .claude/worktrees/, and CLAUDE_PROJECT_DIR still points at the main checkout there,
25
+ // so judging a worktree file against ROOT made every path look misplaced (Stellix WB-41 and WB-55:
26
+ // agents routed new code into unrelated files for days). Every hook measures paths and reads
27
+ // layout.json from this root.
28
+ // Only a worktree of THIS repo counts: a `.git` file whose gitdir lies in this repo's worktrees/.
29
+ // Any other nested `.git` (FetchContent's JUCE clone under build/_deps, a vendored repo, a
30
+ // submodule) is part of the project and judged by it; stopping there let an edit inside
31
+ // build/_deps/juce-src escape protect-vendor (Sub37 WB-2).
32
+ const real = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
33
+
34
+ function commonGitDir(root) {
35
+ const dotGit = resolve(root, '.git');
36
+ try {
37
+ if (statSync(dotGit).isDirectory()) return real(dotGit);
38
+ const gitdir = resolve(root, readFileSync(dotGit, 'utf8').match(/^gitdir:\s*(.+)$/m)[1].trim());
39
+ const common = resolve(gitdir, existsSync(resolve(gitdir, 'commondir')) ? readFileSync(resolve(gitdir, 'commondir'), 'utf8').trim() : '.');
40
+ return real(common);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ export function isWorktreeOfProject(dir) {
47
+ const dotGit = resolve(dir, '.git');
48
+ try {
49
+ if (!statSync(dotGit).isFile()) return false;
50
+ const gitdir = real(resolve(dir, readFileSync(dotGit, 'utf8').match(/^gitdir:\s*(.+)$/m)[1].trim()));
51
+ const common = commonGitDir(ROOT);
52
+ return common !== null && gitdir.startsWith(`${common}/worktrees/`);
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ export function checkoutRoot(file) {
59
+ if (!file) return ROOT;
60
+ let dir = dirname(resolve(process.cwd(), file));
61
+ for (;;) {
62
+ if (dir === ROOT) return ROOT; // a project that is not a git repo is never judged from a parent repo
63
+ if (isWorktreeOfProject(dir)) return dir;
64
+ const up = dirname(dir);
65
+ if (up === dir) return ROOT;
66
+ dir = up;
67
+ }
68
+ }
69
+
70
+ // The folder whose .claude/ (layout.json, checks, locks) governs a file: its own checkout when that
71
+ // checkout carries a layout.json, else the main project. A worktree branch that moved a folder or
72
+ // added a check is judged by its own copy; a worktree without one still gets the main laws.
73
+ export function claudeRoot(file) {
74
+ const root = checkoutRoot(file);
75
+ return existsSync(resolve(root, '.claude', 'layout.json')) ? root : ROOT;
76
+ }
77
+
78
+ // A path relative to its own checkout, with forward slashes.
79
+ export function relInCheckout(file) {
80
+ return relative(checkoutRoot(file), resolve(process.cwd(), file)).replace(/\\/g, '/');
81
+ }