@thedecipherist/claude-packs 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (890) hide show
  1. package/CHANGELOG.md +754 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -2
  4. package/bin/claude-packs.mjs +212 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +93 -0
  7. package/configurations/full-stack/config.json +153 -0
  8. package/configurations/go/config.json +98 -0
  9. package/configurations/node/config.json +102 -0
  10. package/configurations/python/config.json +99 -0
  11. package/configurations/rust/config.json +106 -0
  12. package/configurations/synth-creator/config.json +148 -0
  13. package/global/hooks/gnu-shell.mjs +131 -0
  14. package/global/hooks/lib/shell-writes.mjs +2 -0
  15. package/global/hooks/shell-traps.mjs +79 -0
  16. package/global/hooks/version-pin-guard.mjs +280 -0
  17. package/global/skills/latest-versions/SKILL.md +49 -0
  18. package/lib/backup.mjs +34 -0
  19. package/lib/build-queue.mjs +22 -0
  20. package/lib/claude-md.mjs +60 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +94 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +126 -0
  26. package/lib/fix-plan.mjs +72 -0
  27. package/lib/found-folders.mjs +25 -0
  28. package/lib/git-tokens.mjs +64 -0
  29. package/lib/global.mjs +125 -0
  30. package/lib/hook-proof.mjs +45 -0
  31. package/lib/hook-timing.mjs +42 -0
  32. package/lib/hover-gate.mjs +29 -0
  33. package/lib/kit-command.mjs +14 -0
  34. package/lib/kit-lock.mjs +149 -0
  35. package/lib/layout.mjs +99 -0
  36. package/lib/logs.mjs +109 -0
  37. package/lib/manifest.mjs +24 -0
  38. package/lib/new.mjs +49 -0
  39. package/lib/once-only-report.mjs +54 -0
  40. package/lib/pack-manifest.mjs +20 -0
  41. package/lib/packs.mjs +109 -0
  42. package/lib/planner.mjs +59 -0
  43. package/lib/repeats.mjs +65 -0
  44. package/lib/repo-weight.mjs +28 -0
  45. package/lib/rule-tests.mjs +42 -0
  46. package/lib/rules.mjs +40 -0
  47. package/lib/scan-detect.mjs +130 -0
  48. package/lib/scan.mjs +171 -0
  49. package/lib/selftest-cache.mjs +39 -0
  50. package/lib/settings.mjs +138 -0
  51. package/lib/shape-cli.mjs +76 -0
  52. package/lib/shape.mjs +4 -0
  53. package/lib/suggest.mjs +31 -0
  54. package/lib/switches.mjs +44 -0
  55. package/lib/symmetry-doctor.mjs +26 -0
  56. package/lib/sync.mjs +133 -0
  57. package/lib/workorder.mjs +26 -0
  58. package/package.json +34 -4
  59. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  60. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  61. package/packs/ai/checks/model-id-from-config.json +28 -0
  62. package/packs/ai/checks/model-output-validated.json +26 -0
  63. package/packs/ai/pack.json +38 -0
  64. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  65. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  66. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  67. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  68. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  69. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  70. package/packs/audio-core/checks/keep-werror.json +12 -0
  71. package/packs/audio-core/checks/no-fast-math.json +14 -0
  72. package/packs/audio-core/checks/no-locale-change.json +12 -0
  73. package/packs/audio-core/checks/no-march-native.json +14 -0
  74. package/packs/audio-core/clang-tidy +28 -0
  75. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  76. package/packs/audio-core/hooks/param-id-lock.mjs +172 -0
  77. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  78. package/packs/audio-core/pack.json +192 -0
  79. package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -0
  80. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  81. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  82. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  83. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  84. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  85. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  86. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  87. package/packs/audio-core/rules/cpu-worst-case-per-block.md +28 -0
  88. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  89. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  90. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  91. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  92. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  93. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  94. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  95. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  96. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  97. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  98. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  99. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  100. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  101. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  102. package/packs/audio-core/rules/output-limiter.md +23 -0
  103. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  104. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  105. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  106. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  107. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  108. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  109. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  110. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  111. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  112. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  113. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  114. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  115. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  116. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  117. package/packs/audio-core/rules/ui-matches-reference-look.md +23 -0
  118. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  119. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  120. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  121. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +35 -0
  122. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  123. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  124. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  125. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  126. package/packs/audio-core/skills/prove-control/SKILL.md +25 -0
  127. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  128. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  129. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  130. package/packs/audio-core/tools/tone-report.mjs +47 -0
  131. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  132. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  133. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  134. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  135. package/packs/base/checks/one-walk-per-template.json +23 -0
  136. package/packs/base/checks/stub-reports-success.json +31 -0
  137. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  138. package/packs/base/hooks/brief-guard.mjs +96 -0
  139. package/packs/base/hooks/code-shape.mjs +99 -0
  140. package/packs/base/hooks/dispatch-once.mjs +124 -0
  141. package/packs/base/hooks/file-size-guard.mjs +61 -0
  142. package/packs/base/hooks/folder-law.mjs +77 -0
  143. package/packs/base/hooks/git-guard.mjs +202 -0
  144. package/packs/base/hooks/lesson-checks.mjs +182 -0
  145. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  146. package/packs/base/hooks/lib/code-shapes.mjs +142 -0
  147. package/packs/base/hooks/lib/content.mjs +14 -0
  148. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  149. package/packs/base/hooks/lib/crash-policy.mjs +47 -0
  150. package/packs/base/hooks/lib/dup-index.mjs +204 -0
  151. package/packs/base/hooks/lib/dup.mjs +127 -0
  152. package/packs/base/hooks/lib/elements.mjs +87 -0
  153. package/packs/base/hooks/lib/event-log.mjs +68 -0
  154. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  155. package/packs/base/hooks/lib/git-adds.mjs +21 -0
  156. package/packs/base/hooks/lib/git-auth.mjs +44 -0
  157. package/packs/base/hooks/lib/git-guard-selftest.mjs +192 -0
  158. package/packs/base/hooks/lib/glob.mjs +33 -0
  159. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  160. package/packs/base/hooks/lib/house.py +77 -0
  161. package/packs/base/hooks/lib/languages.mjs +69 -0
  162. package/packs/base/hooks/lib/layout.mjs +58 -0
  163. package/packs/base/hooks/lib/making-check-selftest.mjs +112 -0
  164. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  165. package/packs/base/hooks/lib/program-read.mjs +199 -0
  166. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  167. package/packs/base/hooks/lib/project.mjs +81 -0
  168. package/packs/base/hooks/lib/prose.mjs +11 -0
  169. package/packs/base/hooks/lib/pushed.mjs +201 -0
  170. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  171. package/packs/base/hooks/lib/rust-scan.mjs +139 -0
  172. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  173. package/packs/base/hooks/lib/selftest.mjs +45 -0
  174. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  175. package/packs/base/hooks/lib/shape-lists.mjs +31 -0
  176. package/packs/base/hooks/lib/shape-prose.mjs +78 -0
  177. package/packs/base/hooks/lib/shape.mjs +273 -0
  178. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  179. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  180. package/packs/base/hooks/lib/turn.mjs +86 -0
  181. package/packs/base/hooks/lib/unread-writes.mjs +24 -0
  182. package/packs/base/hooks/lib/writes.mjs +71 -0
  183. package/packs/base/hooks/making-check.mjs +248 -0
  184. package/packs/base/hooks/making.mjs +106 -0
  185. package/packs/base/hooks/once-only.mjs +150 -0
  186. package/packs/base/hooks/placement-law.mjs +84 -0
  187. package/packs/base/hooks/repo-litter.mjs +75 -0
  188. package/packs/base/hooks/reuse-hint.mjs +75 -0
  189. package/packs/base/hooks/run-hooks.mjs +140 -0
  190. package/packs/base/hooks/secrets-guard.mjs +99 -0
  191. package/packs/base/hooks/shape-law.mjs +140 -0
  192. package/packs/base/hooks/suite-once.mjs +96 -0
  193. package/packs/base/hooks/template-law.mjs +148 -0
  194. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  195. package/packs/base/pack.json +214 -0
  196. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  197. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  198. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  199. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  200. package/packs/base/rules/finished-work-lands.md +17 -0
  201. package/packs/base/rules/git-right-first-time.md +19 -0
  202. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  203. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  204. package/packs/base/rules/making-line-first.md +14 -0
  205. package/packs/base/rules/open-items-tagged.md +17 -0
  206. package/packs/base/rules/plan-and-logs.md +22 -0
  207. package/packs/base/rules/references-use-anchors.md +19 -0
  208. package/packs/base/rules/refusals-are-judged.md +19 -0
  209. package/packs/base/rules/reuse-before-writing.md +15 -0
  210. package/packs/base/rules/secrets-never-committed.md +16 -0
  211. package/packs/base/rules/skill-writing.md +23 -0
  212. package/packs/base/rules/split-before-the-limit.md +14 -0
  213. package/packs/base/rules/subagent-dispatch.md +26 -0
  214. package/packs/base/rules/test-only-what-changed.md +17 -0
  215. package/packs/base/rules/test-the-template-once.md +23 -0
  216. package/packs/base/rules/unattended-runs.md +15 -0
  217. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  218. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  219. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  220. package/packs/base/skills/land-branch/SKILL.md +24 -0
  221. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  222. package/packs/base/skills/new-project/SKILL.md +23 -0
  223. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  224. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  225. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  226. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  227. package/packs/base/skills/write-a-workorder/SKILL.md +41 -0
  228. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  229. package/packs/base/tools/shape-symmetry.mjs +178 -0
  230. package/packs/base/tools/test-verdict.mjs +75 -0
  231. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  232. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  233. package/packs/ci/pack.json +21 -0
  234. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  235. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  236. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  237. package/packs/content-site/checks/img-has-size.json +20 -0
  238. package/packs/content-site/checks/page-head-complete.json +26 -0
  239. package/packs/content-site/pack.json +22 -0
  240. package/packs/content-site/rules/content-has-one-source.md +23 -0
  241. package/packs/content-site/rules/media-is-sized.md +24 -0
  242. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  243. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  244. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  245. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  246. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  247. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  248. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  249. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  250. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  251. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  252. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  253. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  254. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  255. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  256. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  257. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  258. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  259. package/packs/cpp/pack.json +22 -0
  260. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  261. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  262. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  263. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  264. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  265. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  266. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  267. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  268. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  269. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  270. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  271. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  272. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  273. package/packs/docker/checks/compose-image-pinned.json +14 -0
  274. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  275. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  276. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  277. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  278. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  279. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  280. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  281. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  282. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  283. package/packs/docker/checks/docker-exec-form.json +16 -0
  284. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  285. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  286. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  287. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  288. package/packs/docker/checks/health-checks-nothing.json +25 -0
  289. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  290. package/packs/docker/checks/no-latest-base.json +13 -0
  291. package/packs/docker/checks/no-secret-in-image.json +13 -0
  292. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  293. package/packs/docker/checks/node-env-before-install.json +13 -0
  294. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  295. package/packs/docker/pack.json +46 -0
  296. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  297. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  298. package/packs/docker/rules/docker-exec-form.md +19 -0
  299. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  300. package/packs/docker/rules/dockerfile-production.md +25 -0
  301. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  302. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  303. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  304. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  305. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  306. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  307. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  308. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  309. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  310. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  311. package/packs/dynamic-data/pack.json +23 -0
  312. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  313. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  314. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  315. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  316. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  317. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  318. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  319. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  320. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  321. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  322. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  323. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  324. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  325. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  326. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  327. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  328. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  329. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  330. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  331. package/packs/fastify/checks/one-error-body.json +26 -0
  332. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  333. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  334. package/packs/fastify/checks/sec-headers-present.json +28 -0
  335. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  336. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  337. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  338. package/packs/fastify/pack.json +41 -0
  339. package/packs/fastify/rules/credentials-and-input.md +23 -0
  340. package/packs/fastify/rules/security-one-switch.md +28 -0
  341. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  342. package/packs/fastify/rules/tests-use-inject.md +13 -0
  343. package/packs/fastify/rules/three-layers.md +20 -0
  344. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  345. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  346. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  347. package/packs/full-stack/pack.json +76 -0
  348. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  349. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  350. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  351. package/packs/go/pack.json +26 -0
  352. package/packs/go/rules/go-errors-are-handled.md +22 -0
  353. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  354. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  355. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  356. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  357. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  358. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  359. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  360. package/packs/gpu-painter/pack.json +91 -0
  361. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  362. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  363. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  364. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  365. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  366. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  367. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  368. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  369. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  370. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  371. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  372. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  373. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  374. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  375. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  376. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  377. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  378. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  379. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  380. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  381. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  382. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  383. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  384. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  385. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  386. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  387. package/packs/hook-proof/pack.json +38 -0
  388. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  389. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  390. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  391. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  392. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  393. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  394. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  395. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  396. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  397. package/packs/host-agent/pack.json +22 -0
  398. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  399. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  400. package/packs/html-ui/checks/external-link-rel.json +21 -0
  401. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  402. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  403. package/packs/html-ui/checks/no-inline-style.json +23 -0
  404. package/packs/html-ui/pack.json +33 -0
  405. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  406. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  407. package/packs/http-api/checks/api-path-versioned.json +31 -0
  408. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  409. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  410. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  411. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  412. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  413. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  414. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  415. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  416. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  417. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  418. package/packs/http-api/checks/guard-fails-open.json +28 -0
  419. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  420. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  421. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  422. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  423. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  424. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  425. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  426. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  427. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  428. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  429. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  430. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  431. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  432. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  433. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  434. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  435. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  436. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  437. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  438. package/packs/http-api/pack.json +45 -0
  439. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  440. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  441. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  442. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  443. package/packs/http-api/rules/health-and-correlation.md +17 -0
  444. package/packs/http-api/rules/one-error-contract.md +17 -0
  445. package/packs/http-api/rules/routes-are-thin.md +17 -0
  446. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  447. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  448. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  449. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  450. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  451. package/packs/instrument-shell/pack.json +22 -0
  452. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  453. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  454. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  455. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  456. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  457. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  458. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  459. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  460. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  461. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  462. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  463. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  464. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  465. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  466. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  467. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  468. package/packs/js-ts/checks/no-any.json +27 -0
  469. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  470. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  471. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  472. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  473. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  474. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  475. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  476. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  477. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  478. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  479. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  480. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  481. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  482. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  483. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  484. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  485. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  486. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  487. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  488. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  489. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  490. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  491. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  492. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  493. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  494. package/packs/js-ts/pack.json +84 -0
  495. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  496. package/packs/js-ts/rules/env-through-config.md +36 -0
  497. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  498. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  499. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  500. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  501. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  502. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  503. package/packs/js-ts/rules/no-any.md +34 -0
  504. package/packs/js-ts/rules/no-console.md +30 -0
  505. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  506. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  507. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  508. package/packs/js-ts/rules/one-logger.md +24 -0
  509. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  510. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  511. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  512. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  513. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  514. package/packs/js-ts/rules/small-functions.md +20 -0
  515. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  516. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  517. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  518. package/packs/js-ts/rules/typed-exports.md +27 -0
  519. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  520. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  521. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  522. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  523. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  524. package/packs/midi/checks/midi-realtime-first.json +27 -0
  525. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  526. package/packs/midi/checks/position-not-integrated.json +29 -0
  527. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  528. package/packs/midi/checks/sysex-length-literal.json +23 -0
  529. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  530. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  531. package/packs/midi/pack.json +22 -0
  532. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  533. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  534. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  535. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  536. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  537. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  538. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  539. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  540. package/packs/mongo/checks/bulk-over-many.json +28 -0
  541. package/packs/mongo/checks/created-at-immutable.json +25 -0
  542. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  543. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  544. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  545. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  546. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  547. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  548. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  549. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  550. package/packs/mongo/checks/no-find-reads.json +26 -0
  551. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  552. package/packs/mongo/checks/no-mongoose.json +23 -0
  553. package/packs/mongo/checks/no-negation-delete.json +26 -0
  554. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  555. package/packs/mongo/checks/objectid-equals.json +26 -0
  556. package/packs/mongo/checks/update-then-reread.json +24 -0
  557. package/packs/mongo/checks/watch-not-poll.json +25 -0
  558. package/packs/mongo/pack.json +52 -0
  559. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  560. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  561. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  562. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  563. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  564. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  565. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  566. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  567. package/packs/multi-tenant/pack.json +22 -0
  568. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  569. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  570. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  571. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  572. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  573. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  574. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  575. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  576. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  577. package/packs/nginx/pack.json +43 -0
  578. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  579. package/packs/nginx/rules/nginx-production.md +26 -0
  580. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  581. package/packs/postgres/checks/pg-bind-values.json +33 -0
  582. package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
  583. package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
  584. package/packs/postgres/checks/pg-index-concurrently.json +18 -0
  585. package/packs/postgres/checks/pg-no-float-money.json +19 -0
  586. package/packs/postgres/checks/pg-no-money.json +18 -0
  587. package/packs/postgres/checks/pg-no-required-column.json +18 -0
  588. package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
  589. package/packs/postgres/checks/pg-not-exists.json +18 -0
  590. package/packs/postgres/checks/pg-timestamptz.json +18 -0
  591. package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
  592. package/packs/postgres/pack.json +48 -0
  593. package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
  594. package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
  595. package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
  596. package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
  597. package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
  598. package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -0
  599. package/packs/python/checks/py-no-bare-except.json +18 -0
  600. package/packs/python/checks/py-no-eval.json +18 -0
  601. package/packs/python/checks/py-no-mutable-default.json +18 -0
  602. package/packs/python/checks/py-no-shell-true.json +18 -0
  603. package/packs/python/checks/py-no-star-import.json +18 -0
  604. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  605. package/packs/python/checks/py-none-by-is.json +18 -0
  606. package/packs/python/checks/py-open-with-with.json +18 -0
  607. package/packs/python/checks/py-requests-timeout.json +18 -0
  608. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  609. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  610. package/packs/python/pack.json +26 -0
  611. package/packs/python/rules/python-errors-and-resources.md +26 -0
  612. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  613. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  614. package/packs/react/checks/dialog-needs-role.json +28 -0
  615. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  616. package/packs/react/checks/icon-button-needs-name.json +24 -0
  617. package/packs/react/checks/label-needs-a-field.json +24 -0
  618. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  619. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  620. package/packs/react/pack.json +20 -0
  621. package/packs/react/rules/react-accessible-controls.md +20 -0
  622. package/packs/react/rules/react-state-and-effects.md +23 -0
  623. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  624. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  625. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  626. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  627. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  628. package/packs/reverse-engineering/pack.json +35 -0
  629. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  630. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  631. package/packs/reverse-engineering/rules/addresses-name-their-space.md +22 -0
  632. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  633. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  634. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  635. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  636. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  637. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  638. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  639. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +22 -0
  640. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  641. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  642. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  643. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  644. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  645. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  646. package/packs/rust/checks/rust-allow-needs-reason.json +16 -0
  647. package/packs/rust/checks/rust-blocking-in-async.json +32 -0
  648. package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
  649. package/packs/rust/checks/rust-static-mut.json +32 -0
  650. package/packs/rust/checks/rust-string-as-error.json +34 -0
  651. package/packs/rust/checks/rust-stub-left-in.json +32 -0
  652. package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
  653. package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
  654. package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
  655. package/packs/rust/clippy.toml +5 -0
  656. package/packs/rust/pack.json +27 -0
  657. package/packs/rust/rules/rust-async-never-blocks.md +20 -0
  658. package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
  659. package/packs/rust/rules/rust-errors-are-returned.md +22 -0
  660. package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
  661. package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
  662. package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
  663. package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
  664. package/packs/rust/skills/rust-port/SKILL.md +22 -0
  665. package/packs/rust/skills/rust-review/SKILL.md +24 -0
  666. package/packs/rust/tools/rust-lints.toml +19 -0
  667. package/packs/shell/checks/sh-mktemp.json +18 -0
  668. package/packs/shell/checks/sh-no-backticks.json +18 -0
  669. package/packs/shell/checks/sh-no-eval.json +18 -0
  670. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  671. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  672. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  673. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  674. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  675. package/packs/shell/checks/sh-strict-mode.json +20 -0
  676. package/packs/shell/pack.json +28 -0
  677. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  678. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  679. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  680. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  681. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  682. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  683. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  684. package/packs/shell-hooks/pack.json +36 -0
  685. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  686. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  687. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  688. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  689. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  690. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  691. package/packs/sound-design/pack.json +22 -0
  692. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  693. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  694. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  695. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  696. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  697. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  698. package/packs/sound-design/skills/bounce-a-plugin/SKILL.md +40 -0
  699. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  700. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  701. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  702. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  703. package/packs/sound-design/tools/vst3host/build.sh +73 -0
  704. package/packs/sound-design/tools/vst3host/host_events.h +101 -0
  705. package/packs/sound-design/tools/vst3host/options.h +172 -0
  706. package/packs/sound-design/tools/vst3host/render.cpp +245 -0
  707. package/packs/sound-design/tools/vst3host/session.h +241 -0
  708. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  709. package/packs/stellix/checks/one-seeded-random.json +16 -0
  710. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  711. package/packs/stellix/checks/world-no-libm.json +15 -0
  712. package/packs/stellix/checks/world-no-unordered.json +10 -0
  713. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  714. package/packs/stellix/pack.json +61 -0
  715. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  716. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  717. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  718. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  719. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  720. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  721. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  722. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  723. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  724. package/packs/stored-text/pack.json +41 -0
  725. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  726. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  727. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  728. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  729. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  730. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  731. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  732. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  733. package/packs/synth-creator/hooks/protect-vendor.mjs +175 -0
  734. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  735. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  736. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  737. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  738. package/packs/synth-creator/pack.json +136 -0
  739. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  740. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  741. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  742. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  743. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  744. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  745. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  746. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +21 -0
  747. package/packs/synth-creator/tools/buildq.mjs +74 -0
  748. package/packs/synth-creator/tools/freeze.mjs +36 -0
  749. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  750. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  751. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  752. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  753. package/packs/tui/checks/tui-width-is-state.json +33 -0
  754. package/packs/tui/pack.json +44 -0
  755. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  756. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  757. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  758. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  759. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  760. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  761. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  762. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  763. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  764. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  765. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  766. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  767. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  768. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  769. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  770. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  771. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  772. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  773. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  774. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  775. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  776. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  777. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  778. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  779. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  780. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  781. package/packs/ui-craft/pack.json +43 -0
  782. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  783. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  784. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  785. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  786. package/packs/ui-craft/rules/interface-copy.md +26 -0
  787. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  788. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  789. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  790. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  791. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  792. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  793. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  794. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  795. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  796. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  797. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  798. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  799. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  800. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  801. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  802. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  803. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  804. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  805. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  806. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  807. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  808. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  809. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  810. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  811. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  812. package/packs/web/checks/client-no-api-key.json +29 -0
  813. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  814. package/packs/web/checks/client-no-server-import.json +31 -0
  815. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  816. package/packs/web/checks/css-no-inline-style.json +30 -0
  817. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  818. package/packs/web/checks/key-from-index.json +23 -0
  819. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  820. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  821. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  822. package/packs/web/checks/rcss-button-cursor.json +30 -0
  823. package/packs/web/checks/rcss-no-100vw.json +21 -0
  824. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  825. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  826. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  827. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  828. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  829. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  830. package/packs/web/pack.json +73 -0
  831. package/packs/web/rules/accessibility.md +26 -0
  832. package/packs/web/rules/client-boundaries.md +23 -0
  833. package/packs/web/rules/production-build.md +22 -0
  834. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  835. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  836. package/packs/web/rules/tailwind-v4.md +23 -0
  837. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  838. package/packs/web/rules/web-performance.md +21 -0
  839. package/packs/web/skills/design-direction/SKILL.md +30 -0
  840. package/packs/web/skills/design-review/SKILL.md +28 -0
  841. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  842. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  843. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  844. package/packs/websocket/checks/no-token-in-query.json +26 -0
  845. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  846. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  847. package/packs/websocket/pack.json +21 -0
  848. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  849. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  850. package/packs/writing/checks/no-copula-dodging.json +18 -0
  851. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  852. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  853. package/packs/writing/checks/no-participial-opener.json +18 -0
  854. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  855. package/packs/writing/checks/no-slop-words.json +18 -0
  856. package/packs/writing/checks/no-stock-connectors.json +18 -0
  857. package/packs/writing/checks/no-time-or-date.json +27 -0
  858. package/packs/writing/checks/no-trailing-ing.json +18 -0
  859. package/packs/writing/checks/no-vague-attribution.json +18 -0
  860. package/packs/writing/pack.json +43 -0
  861. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  862. package/packs/writing/rules/cut-the-padding.md +20 -0
  863. package/packs/writing/rules/direct-voice.md +22 -0
  864. package/packs/writing/rules/no-cargo-cult.md +16 -0
  865. package/packs/writing/rules/no-scaffolding.md +19 -0
  866. package/packs/writing/rules/no-stock-language.md +27 -0
  867. package/packs/writing/rules/no-time-or-date.md +18 -0
  868. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  869. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  870. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  871. package/packs/writing/skills/humanize/SKILL.md +28 -0
  872. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  873. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  874. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  875. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  876. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  877. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  878. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  879. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  880. package/packs/zod/hooks/schemas-only.mjs +53 -0
  881. package/packs/zod/pack.json +44 -0
  882. package/packs/zod/rules/env-shapes-only.md +14 -0
  883. package/packs/zod/rules/zod-4-schemas.md +14 -0
  884. package/profiles/stellix.layout.json +315 -0
  885. package/profiles/sub37.layout.json +247 -0
  886. package/prompts/scan-review.md +40 -0
  887. package/prompts/survey.md +82 -0
  888. package/prompts/workorder/PROMPT.md +53 -0
  889. package/prompts/workorder/run.sh +36 -0
  890. package/prompts/workorder/workorder.md +59 -0
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ // version-pin-guard: refuses a new or changed version pin unless docs/versions.md has a row
3
+ // for that dependency checked within MAX_AGE days. Pairs with the latest-versions skill.
4
+ // PreToolUse on Write|Edit|MultiEdit|Bash. Exit 2 blocks; stderr goes back to Claude. Every route that writes a
5
+ // dependency file is judged (WB-69): the Write tool, an Edit or MultiEdit (its new_string taken literally), an install
6
+ // command, and the shell writing the file (>, >>, tee, sed -i, cp, mv, a heredoc; lib/shell-writes.mjs).
7
+ // `node version-pin-guard.mjs --selftest` proves every extractor and the age check.
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { basename, dirname, join, relative, resolve } from 'node:path';
10
+ import { afterText, shellWrites } from './lib/shell-writes.mjs';
11
+
12
+ const MAX_AGE = Number(process.env.VERSION_GUARD_DAYS ?? 14);
13
+ const DAY = 86_400_000;
14
+
15
+ // ---------- extractors: text -> Map(name -> version) ----------
16
+ function fromPackageJson(text) {
17
+ const out = new Map();
18
+ let json;
19
+ try { json = JSON.parse(text); } catch { return out; }
20
+ for (const key of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
21
+ for (const [n, v] of Object.entries(json[key] ?? {})) out.set(n, String(v));
22
+ }
23
+ if (json.packageManager) out.set(json.packageManager.split('@')[0], json.packageManager);
24
+ return out;
25
+ }
26
+
27
+ function fromCmake(text) {
28
+ const out = new Map();
29
+ for (const m of text.matchAll(/FetchContent_Declare\s*\(\s*([\w.-]+)([^)]*)\)/gi)) {
30
+ const v = m[2].match(/\b(?:GIT_TAG|URL)\s+("?)([^\s")]+)\1/i);
31
+ if (v) out.set(m[1], v[2]);
32
+ }
33
+ for (const m of text.matchAll(/CPMAddPackage\s*\(\s*"(?:gh:)?([\w./-]+)@([\w.+-]+)"/gi)) out.set(m[1].split('/').pop(), m[2]);
34
+ for (const m of text.matchAll(/ExternalProject_Add\s*\(\s*([\w.-]+)[^)]*?\bGIT_TAG\s+([^\s)]+)/gi)) out.set(m[1], m[2]);
35
+ return out;
36
+ }
37
+
38
+ function fromRequirements(text) {
39
+ const out = new Map();
40
+ for (const line of text.split('\n')) {
41
+ const m = line.replace(/#.*/, '').trim().match(/^([A-Za-z0-9_.\-\[\]]+)\s*(==|>=|~=|<=|>|<|===)\s*([^\s;,]+)/);
42
+ if (m) out.set(m[1].replace(/\[.*\]/, ''), `${m[2]}${m[3]}`);
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function fromPyproject(text) {
48
+ const out = new Map();
49
+ for (const m of text.matchAll(/["']([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(==|>=|~=|<=|>|<)\s*([^"',;\s]+)/g)) out.set(m[1], `${m[2]}${m[3]}`);
50
+ return out;
51
+ }
52
+
53
+ function fromCargo(text) {
54
+ const out = new Map();
55
+ let inDeps = false;
56
+ for (const line of text.split('\n')) {
57
+ const h = line.match(/^\s*\[([^\]]+)\]/);
58
+ if (h) { inDeps = /dependencies/.test(h[1]); continue; }
59
+ if (!inDeps) continue;
60
+ const m = line.match(/^\s*([\w-]+)\s*=\s*(?:"([^"]+)"|\{[^}]*version\s*=\s*"([^"]+)")/);
61
+ if (m) out.set(m[1], m[2] ?? m[3]);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ function fromGoMod(text) {
67
+ const out = new Map();
68
+ for (const m of text.matchAll(/^\s*(?:require\s+)?([\w.\-/]+\.[\w.\-/]+)\s+(v[\w.+-]+)/gm)) out.set(m[1], m[2]);
69
+ return out;
70
+ }
71
+
72
+ function fromDockerfile(text) {
73
+ const out = new Map();
74
+ for (const m of text.matchAll(/^\s*FROM\s+(?:--platform=\S+\s+)?([^\s:@]+)[:@](\S+)/gim)) out.set(m[1].split('/').pop(), m[2]);
75
+ return out;
76
+ }
77
+
78
+ function fromWorkflow(text) {
79
+ const out = new Map();
80
+ for (const m of text.matchAll(/uses:\s*["']?([\w.-]+\/[\w.-]+)(?:\/[\w./-]+)?@([\w.-]+)/g)) out.set(m[1], m[2]);
81
+ return out;
82
+ }
83
+
84
+ // The parser for each manifest: by exact file name, else by the first name pattern that fits, else a workflow by its path.
85
+ const BY_NAME = { 'package.json': fromPackageJson, 'CMakeLists.txt': fromCmake, 'pyproject.toml': fromPyproject, 'Cargo.toml': fromCargo, 'go.mod': fromGoMod };
86
+ const BY_PATTERN = [
87
+ [/\.cmake$/, fromCmake],
88
+ [/^requirements.*\.(txt|in)$/, fromRequirements],
89
+ [/^(Dockerfile|Containerfile)(\..+)?$|\.dockerfile$/i, fromDockerfile],
90
+ ];
91
+
92
+ export function extractorFor(file) {
93
+ const name = basename(file);
94
+ if (Object.hasOwn(BY_NAME, name)) return BY_NAME[name];
95
+ const hit = BY_PATTERN.find(([re]) => re.test(name));
96
+ if (hit) return hit[1];
97
+ return /\.github[\\/]workflows[\\/].+\.ya?ml$/.test(file) ? fromWorkflow : null;
98
+ }
99
+
100
+ // Pins in `after` that are new or different from `before`.
101
+ export function changedPins(extract, before, after) {
102
+ const old = extract(before);
103
+ return [...extract(after)].filter(([n, v]) => old.get(n) !== v).map(([n, v]) => ({ name: n, version: v }));
104
+ }
105
+
106
+ // A shell redirection is never a package: `2>&1`, `&>log`, `>/dev/null`, `> out.txt`, `< in`, and a
107
+ // trailing `&` (0.5.x read `2>&1` as a package named "2" at version "&1" and refused the command).
108
+ const REDIRECT = /(?:^|\s)(?:\d*|&)(?:>>?|<)(?:&\d+|&-|\s*[^\s&|;<>]+)?|\s&(?=\s|$)/g;
109
+
110
+ // A heredoc's body is text, never a command (KWB-3: a script fed to python3 <<'EOF' that held the words
111
+ // "npm install x" in a comment was read as forty installs).
112
+ const HEREDOC = /<<-?\s*(['"]?)([A-Za-z_][\w-]*)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g;
113
+ const PREFIX = /^(sudo|env|command|time|nohup|npx|[A-Za-z_]\w*=\S*)$/;
114
+
115
+ // Package names added by an install command (the install is the command itself, never a word an echo prints).
116
+ export function pinsFromCommand(cmd) {
117
+ const found = [];
118
+ for (const seg of cmd.replace(HEREDOC, ' ').split(/&&|\|\||;|\||\n/)) {
119
+ const words = seg.replace(REDIRECT, ' ').trim().split(/\s+/);
120
+ let lead = 0;
121
+ while (lead < words.length && PREFIX.test(words[lead])) lead += 1;
122
+ const w0 = words[lead] ?? '', w1 = words[lead + 1] ?? '';
123
+ const at = (/^(npm|pnpm|yarn|bun)$/.test(w0) && /^(i|install|add)$/.test(w1)) || (/^(pip|pip3|uv|poetry)$/.test(w0) && /^(install|add)$/.test(w1)) || (w0 === 'cargo' && w1 === 'add') || (w0 === 'go' && w1 === 'get') ? lead : -1;
124
+ if (at === -1) continue;
125
+ for (const w of words.slice(at + 2)) {
126
+ if (w.startsWith('-') || w === 'pip' || w.startsWith('.') || w.startsWith('/')) continue;
127
+ const m = w.match(/^(@?[^@=<>~\s]+)(?:[@=<>~]=?(.+))?$/);
128
+ if (m && !/^(-r|requirements)/.test(m[1])) found.push({ name: m[1], version: m[2] ?? '(unpinned)' });
129
+ }
130
+ }
131
+ return found;
132
+ }
133
+
134
+ // The folder an install in `cmd` runs in: a `cd <dir>` before it moves it (KWB-3: `cd <scratch repo> && npm install x`
135
+ // was judged by the session folder's docs/versions.md, so a pin in a repo that had its row was refused).
136
+ export function installDir(cmd, cwd) {
137
+ let here = cwd;
138
+ for (const seg of cmd.replace(HEREDOC, ' ').split(/&&|\|\||;|\||\n/)) {
139
+ const m = seg.trim().match(/^(?:cd|pushd)\s+(['"]?)([^'"\s]+)\1$/);
140
+ if (m) { here = resolve(here, m[2].replace(/^(~|\$HOME|\$\{HOME\})(?=\/|$)/, process.env.HOME ?? '~')); continue; }
141
+ if (pinsFromCommand(seg).length) return here;
142
+ }
143
+ return here;
144
+ }
145
+
146
+ // ---------- docs/versions.md ----------
147
+ export function checkedDates(versionsText) {
148
+ const dates = new Map();
149
+ for (const line of versionsText.split('\n')) {
150
+ if (!line.trim().startsWith('|')) continue;
151
+ const cells = line.split('|').map((c) => c.trim().replace(/`/g, '')).filter(Boolean);
152
+ const date = line.match(/\b(\d{4}-\d{2}-\d{2})\b/);
153
+ if (!cells.length || !date) continue;
154
+ const key = cells[0].toLowerCase();
155
+ const t = Date.parse(date[1]);
156
+ if (!dates.has(key) || dates.get(key) < t) dates.set(key, t);
157
+ }
158
+ return dates;
159
+ }
160
+
161
+ function lookup(dates, name) {
162
+ const n = name.toLowerCase();
163
+ for (const k of [n, n.split('/').pop(), n.replace(/^@[^/]+\//, '')]) if (dates.has(k)) return dates.get(k);
164
+ return undefined;
165
+ }
166
+
167
+ export function verdict(pins, dates, now = Date.now()) {
168
+ const problems = [];
169
+ for (const p of pins) {
170
+ const t = lookup(dates, p.name);
171
+ if (t === undefined) problems.push(`${p.name} ${p.version}: no row in docs/versions.md`);
172
+ else if (now - t > MAX_AGE * DAY) problems.push(`${p.name} ${p.version}: last checked ${new Date(t).toISOString().slice(0, 10)}, over ${MAX_AGE} days ago`);
173
+ }
174
+ return problems;
175
+ }
176
+
177
+ // The project the pin is written in: the nearest folder above it with docs/versions.md or .git
178
+ // (a file in a git worktree), so an agent worktree and a sibling project read their own table.
179
+ // CLAUDE_PROJECT_DIR is only the fallback, and for a worktree that carries no versions.md.
180
+ export function projectRoot(start, env = process.env.CLAUDE_PROJECT_DIR) {
181
+ let d = resolve(start);
182
+ let found = null;
183
+ for (;;) {
184
+ if (existsSync(join(d, '.git')) || existsSync(join(d, 'docs', 'versions.md'))) { found = d; break; }
185
+ const up = dirname(d);
186
+ if (up === d) break;
187
+ d = up;
188
+ }
189
+ if (!found) return env ?? resolve(start);
190
+ const has = (root) => existsSync(join(root, 'docs', 'versions.md'));
191
+ if (!has(found) && env && has(env) && !relative(resolve(env), found).startsWith('..')) return env;
192
+ return found;
193
+ }
194
+
195
+ // ---------- hook ----------
196
+ function newText(input, current) {
197
+ if (typeof input.content === 'string') return input.content;
198
+ const edits = input.edits ?? (input.old_string !== undefined ? [input] : []);
199
+ let text = current;
200
+ // literally: a new_string holding `$&` or `$1` was read as a replacement pattern (the Edit gap, WB-69)
201
+ for (const e of edits) text = e.replace_all ? text.split(e.old_string).join(e.new_string ?? '') : text.replace(e.old_string, () => e.new_string ?? '');
202
+ return text;
203
+ }
204
+
205
+ function main(payload) {
206
+ const input = payload.tool_input ?? {};
207
+ let pins = [];
208
+ let where;
209
+ if (payload.tool_name === 'Bash') {
210
+ pins = pinsFromCommand(input.command ?? '');
211
+ where = installDir(input.command ?? '', payload.cwd ?? process.cwd());
212
+ for (const w of shellWrites(input.command ?? '', where)) {
213
+ const extract = extractorFor(w.path);
214
+ if (!extract) continue;
215
+ const { before, after } = afterText(w);
216
+ if (after !== null) pins.push(...changedPins(extract, before, after));
217
+ where = dirname(w.path);
218
+ }
219
+ } else {
220
+ const file = input.file_path;
221
+ const extract = file && extractorFor(file);
222
+ if (!extract) return 0;
223
+ const before = existsSync(file) ? readFileSync(file, 'utf8') : '';
224
+ pins = changedPins(extract, before, newText(input, before));
225
+ where = dirname(file);
226
+ }
227
+ if (pins.length === 0) return 0;
228
+ const vfile = join(projectRoot(where), 'docs', 'versions.md');
229
+ const dates = existsSync(vfile) ? checkedDates(readFileSync(vfile, 'utf8')) : new Map();
230
+ const problems = verdict(pins, dates);
231
+ if (problems.length === 0) return 0;
232
+ process.stderr.write(`version-pin-guard: look up the newest release first (latest-versions skill).\n${problems.map((p) => `- ${p}`).join('\n')}\nAdd or refresh each row in ${vfile} (name | latest stable | YYYY-MM-DD | source URL | used | decision), then pin.\n`);
233
+ return 2;
234
+ }
235
+
236
+ function selftest() {
237
+ const eq = (a, b, what) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${what}: got ${JSON.stringify(a)}`); };
238
+ const names = (pins) => pins.map((p) => p.name);
239
+ eq(names(changedPins(fromPackageJson, '{"dependencies":{"a":"1"}}', '{"dependencies":{"a":"1","fastify":"^5.6.0"}}')), ['fastify'], 'package.json add');
240
+ eq(names(changedPins(fromPackageJson, '{"dependencies":{"a":"1"}}', '{"dependencies":{"a":"1"},"x":2}')), [], 'package.json unchanged');
241
+ eq([...fromCmake('FetchContent_Declare(JUCE\n GIT_REPOSITORY https://github.com/juce-framework/JUCE\n GIT_TAG 9.0.4)')], [['JUCE', '9.0.4']], 'cmake GIT_TAG');
242
+ eq([...fromCmake('CPMAddPackage("gh:fmtlib/fmt@11.2.0")')], [['fmt', '11.2.0']], 'cmake CPM');
243
+ eq([...fromRequirements('numpy==2.3.1 # x\n-r base.txt\n')], [['numpy', '==2.3.1']], 'requirements');
244
+ eq([...fromPyproject('dependencies = ["httpx>=0.28", "rich"]')], [['httpx', '>=0.28']], 'pyproject');
245
+ eq([...fromCargo('[package]\nversion = "0.1.0"\n[dependencies]\nserde = { version = "1.0.219", features = ["derive"] }\n')], [['serde', '1.0.219']], 'cargo');
246
+ eq([...fromGoMod('module x\nrequire (\n github.com/spf13/cobra v1.9.1\n)\n')], [['github.com/spf13/cobra', 'v1.9.1']], 'go.mod');
247
+ eq([...fromDockerfile('FROM --platform=linux/amd64 node:24-slim AS build\n')], [['node', '24-slim']], 'dockerfile');
248
+ eq([...fromWorkflow('- uses: actions/checkout@v5\n- uses: github/codeql-action/init@v3')], [['actions/checkout', 'v5'], ['github/codeql-action', 'v3']], 'workflow');
249
+ eq(names(pinsFromCommand('cd x && npm install -D typescript@5.9.3 zod')), ['typescript', 'zod'], 'npm install');
250
+ eq(names(pinsFromCommand('pip install --break-system-packages requests==2.32.5')), ['requests'], 'pip install');
251
+ eq(pinsFromCommand('npm install'), [], 'bare npm install');
252
+ eq(names(pinsFromCommand('npm install fastify 2>&1 | tail -5')), ['fastify'], 'install with 2>&1');
253
+ eq(names(pinsFromCommand('npm i zod > /tmp/npm.log 2>&1')), ['zod'], 'install with > log 2>&1');
254
+ eq(names(pinsFromCommand('pip install requests==2.32.5 &>/dev/null &')), ['requests'], 'install with &>/dev/null &');
255
+ eq(pinsFromCommand('npm install 2>&1'), [], 'bare install with 2>&1');
256
+ eq(pinsFromCommand("python3 - <<'EOF'\n# then npm install zod and pip install rich\nEOF"), [], 'a heredoc body is text');
257
+ eq(pinsFromCommand('echo run npm install zod first'), [], 'an echo that names an install');
258
+ eq(names(pinsFromCommand('sudo npm install -g zod@4')), ['zod'], 'install after a prefix');
259
+ eq(installDir('cd /p/other && npm install zod@4', '/p/here'), '/p/other', 'an install after cd runs there');
260
+ eq(installDir('npm install zod@4 && cd /p/other', '/p/here'), '/p/here', 'a cd after the install does not move it');
261
+ const now = Date.parse('2026-10-26');
262
+ const dates = checkedDates('| name | latest |\n|---|---|\n| fastify | 5.6.1 | 2026-10-20 | https://github.com/fastify/fastify/releases | 5.6.1 | use |\n| `JUCE` | 9.0.4 | 2026-09-01 | url | 8.0.4 | F4 |\n');
263
+ eq(verdict([{ name: 'fastify', version: '5.6.1' }], dates, now), [], 'fresh row passes');
264
+ eq(verdict([{ name: 'JUCE', version: '9.0.4' }], dates, now).length, 1, 'stale row blocks');
265
+ eq(verdict([{ name: 'zod', version: '4' }], dates, now).length, 1, 'missing row blocks');
266
+ eq(verdict([{ name: 'github.com/fastify', version: 'v1' }], dates, now), [], 'last path segment matches');
267
+ console.log('version-pin-guard selftest: OK (26 cases)');
268
+ return 0;
269
+ }
270
+
271
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname)) {
272
+ if (process.argv.includes('--selftest')) process.exit(selftest());
273
+ let raw = '';
274
+ process.stdin.on('data', (c) => { raw += c; });
275
+ process.stdin.on('end', () => {
276
+ let payload;
277
+ try { payload = JSON.parse(raw || '{}'); } catch { process.exit(0); }
278
+ process.exit(main(payload));
279
+ });
280
+ }
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: latest-versions
3
+ description: Use when starting a new project, or adding, choosing, pinning or upgrading any library, tool, compiler, runtime, SDK, GitHub Action or container image.
4
+ ---
5
+
6
+ # Latest versions
7
+
8
+ Your knowledge of versions is out of date by definition. Every version Stellix and every other project uses is looked up today, from the project's own release source, and written down before it is used.
9
+
10
+ <constraints>
11
+ Always do:
12
+ - Look up the current stable release before you choose, add, pin or upgrade anything: a library, framework, tool, compiler, runtime, SDK, GitHub Action or container base image.
13
+ - Take the version from a primary source, in this order:
14
+ 1. the project's GitHub releases or tags page;
15
+ 2. its official site or changelog;
16
+ 3. its registry page (npm, PyPI, crates.io, pkg.go.dev, Homebrew, Docker Hub).
17
+ Blog posts, tutorials and AI answers are not sources.
18
+ - Record every lookup in `docs/versions.md`, in the project being worked on, one row per dependency:
19
+ `| name | latest stable | date checked YYYY-MM-DD | source URL | version used | decision |`
20
+ - Use the latest stable release unless there is a stated reason not to. Write that reason in the decision column (for example "9.0.4 latest; staying on 8.0.4 until F4").
21
+ - Read the release notes between the version used and the latest. Name any breaking change in the decision column.
22
+ - Pin exact versions: a full tag, release or commit, never a range or `latest`, in build files (CMake `GIT_TAG`, Docker `FROM`, Actions `uses`). Lockfiles do the pinning for package managers.
23
+ - At the start of a new project, run this for the whole stack before the first build file is written.
24
+
25
+ Never do:
26
+ - Write a version number from memory, from an older project, or from an example in docs.
27
+ - Pick a pre-release, beta, RC or nightly unless the user asks for one.
28
+ - Treat a row older than 14 days as current. Look it up again and update the date.
29
+ - Edit a version pin before its row exists. The `version-pin-guard` hook refuses pins that `docs/versions.md` has not checked within 14 days.
30
+ </constraints>
31
+
32
+ ## How
33
+
34
+ 1. List what the change needs: each dependency, tool and image.
35
+ 2. For each one, fetch its releases page (WebFetch, or `gh release list -R owner/repo -L 5` / `gh api repos/owner/repo/releases/latest`). Note the newest non-pre-release and its date.
36
+ 3. Add or update its row in `docs/versions.md`. Create the file with this header if it is missing:
37
+
38
+ ```
39
+ # Versions
40
+ Checked by the latest-versions skill. One row per dependency; re-check after 14 days.
41
+
42
+ | name | latest stable | checked | source | used | decision |
43
+ |---|---|---|---|---|---|
44
+ ```
45
+
46
+ 4. Then write the pin.
47
+ 5. Tell the user, in one line per dependency, which version you chose and why, and flag any major jump.
48
+
49
+ The name in the row must be the name the pin uses: the npm package name, the CMake FetchContent name, the image name or the Action `owner/repo`. That's how the hook matches the pin to its row.
package/lib/backup.mjs ADDED
@@ -0,0 +1,34 @@
1
+ // Before every install or update, copies the files the package writes into (CLAUDE.md,
2
+ // settings.json, layout.json, the lock) to .claude/.claude-packs-backup/<timestamp>/.
3
+ // Keeps the last 5 backups.
4
+ import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ const DIR = join('.claude', '.claude-packs-backup');
8
+ const KEEP = 5;
9
+ const FILES = ['CLAUDE.md', '.claude/settings.json', '.claude/layout.json', '.claude/claude-packs.lock.json'];
10
+
11
+ export function backup(projectDir) {
12
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
13
+ const target = join(projectDir, DIR, stamp);
14
+ for (const rel of FILES) {
15
+ const src = join(projectDir, rel);
16
+ if (!existsSync(src)) continue;
17
+ mkdirSync(join(target, rel, '..'), { recursive: true });
18
+ copyFileSync(src, join(target, rel));
19
+ }
20
+ const all = existsSync(join(projectDir, DIR)) ? readdirSync(join(projectDir, DIR)).sort() : [];
21
+ for (const old of all.slice(0, Math.max(0, all.length - KEEP))) rmSync(join(projectDir, DIR, old), { recursive: true, force: true });
22
+ return join(DIR, stamp);
23
+ }
24
+
25
+ // Adds the backup folder, the hooks' run state (.claude/state: frozen list, last test log) and brief-guard's refusal counter
26
+ // (.claude/usage.log, 0.9.2) to .gitignore (appends; never rewrites the file).
27
+ export function ensureGitignore(projectDir) {
28
+ const file = join(projectDir, '.gitignore');
29
+ for (const line of [`${DIR.replace(/\\/g, '/')}/`, '.claude/state/', '.claude/usage.log', '.claude/hooks.log', '.claude/logs/']) {
30
+ const text = existsSync(file) ? readFileSync(file, 'utf8') : '';
31
+ if (text.split('\n').includes(line)) continue;
32
+ appendFileSync(file, `${text && !text.endsWith('\n') ? '\n' : ''}${line}\n`);
33
+ }
34
+ }
@@ -0,0 +1,22 @@
1
+ // KF-1: build-guard named a queue no pack shipped, and took any file called build.sh as a queue. doctor checks both ways it fails open: the tool is gone, or the project's
2
+ // wrapper queues nothing. A wrapper that holds the law itself says so: layout.json "guards": { "build": { "queue": "none: <reason>" } }.
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ const TOOL = '.claude/tools/buildq.mjs';
7
+
8
+ const wrappersOf = (commands) => [...new Set(Object.values(commands ?? {}).flatMap((c) => String(c).split(/\s+/)).filter((w) => /(^|\/)build\.sh$/.test(w)))];
9
+
10
+ export function buildQueueProblems(projectDir, layout) {
11
+ if (!existsSync(join(projectDir, '.claude/hooks/build-guard.mjs'))) return [];
12
+ const problems = [];
13
+ if (!existsSync(join(projectDir, TOOL))) problems.push(`build-guard sends every build to ${TOOL}, which is missing: run update`);
14
+ if (/^none:\s*\S/.test(layout.guards?.build?.queue ?? '')) return problems;
15
+ for (const rel of wrappersOf(layout.commands)) {
16
+ const file = join(projectDir, rel);
17
+ if (existsSync(file) && !readFileSync(file, 'utf8').includes('buildq')) {
18
+ problems.push(`${rel} does not call the queue, so build-guard passes it and no job waits. Call ${TOOL} in it, or set layout.json "guards": { "build": { "queue": "none: <reason>" } }`);
19
+ }
20
+ }
21
+ return problems;
22
+ }
@@ -0,0 +1,60 @@
1
+ // Writes the generated layout section into the project's CLAUDE.md, between markers.
2
+ // Everything outside the markers is the user's and is never touched.
3
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { noPackageLine } from './kit-command.mjs';
6
+
7
+ const START = '<!-- claude-packs:start -->';
8
+ const END = '<!-- claude-packs:end -->';
9
+
10
+ // The house rules after the folders are the configuration's preamble (configurations/<name>/config.json, 0.10.0).
11
+ function section(layout, packNames, preamble, notes = []) {
12
+ // Folders no kind points at are listed by purpose; the rest appear once, in the kinds list.
13
+ const used = new Set(Object.values(layout.kinds ?? {}).map((k) => k.in));
14
+ const folders = Object.entries(layout.folders)
15
+ .filter(([name]) => !used.has(name) && name !== 'src')
16
+ .sort(([, a], [, b]) => a.path.localeCompare(b.path))
17
+ .map(([, f]) => ` - \`${f.path}/\`: ${f.purpose}`);
18
+ return [
19
+ START,
20
+ '<!-- Generated by claude-packs from .claude/layout.json. Edit that file and run `npx --no-install @thedecipherist/claude-packs update`; edits here are replaced. -->',
21
+ '## Project layout',
22
+ '',
23
+ '<constraints>',
24
+ 'Always do:',
25
+ '- Put every kind of thing in its one place. The rules, checks and skills for it load only there:',
26
+ ...kindLines(layout),
27
+ ...(folders.length ? ['- The other folders:', ...folders] : []),
28
+ ...preamble.always.map((l) => `- ${l}`),
29
+ '',
30
+ 'Never do:',
31
+ ...preamble.never.map((l) => `- ${l}`),
32
+ '</constraints>',
33
+ '',
34
+ ...commandLines(layout),
35
+ ...notes,
36
+ `Installed packs: ${packNames.join(', ')}.`,
37
+ END,
38
+ ].join('\n');
39
+ }
40
+
41
+ function kindLines(layout) {
42
+ return Object.values(layout.kinds ?? {}).map((k) => ` - ${k.what} → \`${layout.folders?.[k.in] ? `${layout.folders[k.in].path}/` : k.in}\``);
43
+ }
44
+
45
+ function commandLines(layout) {
46
+ const entries = Object.entries(layout.commands ?? {});
47
+ if (entries.length === 0) return [];
48
+ return ['## Project commands', '', 'Skills and checks run these; use them, not the raw tools:', ...entries.map(([k, v]) => `- ${k}: \`${v}\``), ''];
49
+ }
50
+
51
+ export function writeClaudeMd(projectDir, layout, packNames, preamble) {
52
+ const file = join(projectDir, 'CLAUDE.md');
53
+ const block = section(layout, packNames, preamble, noPackageLine(projectDir));
54
+ let text = existsSync(file) ? readFileSync(file, 'utf8') : '';
55
+ const s = text.indexOf(START);
56
+ const e = text.indexOf(END);
57
+ if (s !== -1 && e > s) text = text.slice(0, s) + block + text.slice(e + END.length);
58
+ else text = `${text.trimEnd()}${text.trim() ? '\n\n' : ''}${block}\n`;
59
+ writeFileSync(file, text);
60
+ }
@@ -0,0 +1,36 @@
1
+ // The `configs` and `config <name>` commands, and doctor's configuration line (0.10.0, STEP 3 item 1).
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { allowedPacks, describeConfigs, describeSwitch, packsFor, readConfig, resolveConfig } from './configs.mjs';
5
+ import { doctor } from './doctor.mjs';
6
+ import { readLock } from './manifest.mjs';
7
+ import { sync } from './sync.mjs';
8
+
9
+ export { describeConfigs, readConfig };
10
+
11
+ const layoutOf = (dir) => {
12
+ const file = join(dir, '.claude', 'layout.json');
13
+ return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : null;
14
+ };
15
+
16
+ export const layoutConfiguration = (dir) => layoutOf(dir)?.configuration ?? null;
17
+
18
+ // Switch a project to configuration `name`: the packs it has that the new one takes stay; with none left, the new one's
19
+ // default packs. What changes is printed first; --dry-run stops there.
20
+ export async function switchConfig(dir, name, installed, { force = false, dryRun = false, log = console.log } = {}) {
21
+ if (!name) throw new Error('name the configuration: `config <name>` (`configs` lists them)');
22
+ if (installed.length === 0) throw new Error('nothing installed here; run `init --config <name>` first');
23
+ const to = readConfig(name);
24
+ const layout = layoutOf(dir) ?? { folders: {} };
25
+ const { config: from } = resolveConfig({ layout, packs: installed });
26
+ const fromSources = packsFor(from, installed);
27
+ const kept = installed.filter((p) => p === 'base' || (allowedPacks(to).includes(p) && !from.packs.always.includes(p)));
28
+ const chosen = kept.some((p) => p !== 'base') ? kept : [...kept, ...to.packs.default];
29
+ const toSources = packsFor(to, chosen);
30
+ const lines = describeSwitch(from, fromSources, to, toSources, { ...layout, disable: [...(layout.disable ?? [])] }, Object.keys(readLock(join(dir, '.claude')).files));
31
+ for (const l of lines) log(l);
32
+ if (dryRun) { log('dry run: nothing changed'); return 0; }
33
+ if (lines.at(-1) === ' no change' && layout.configuration === to.name) return 0;
34
+ await sync(dir, chosen, { force, mkdirs: true, config: name, log });
35
+ return doctor(dir, log, { installing: true });
36
+ }
@@ -0,0 +1,145 @@
1
+ // Configurations (0.10.0, STEP 3; KWB-31): a kind of project, as data. configurations/<name>/config.json holds
2
+ // "packs": { "always": [...], "choose": [...], "default": [...] } the packs it always installs (base first), the ones
3
+ // a project may add, and the ones `init --config <name>` takes when none are named
4
+ // "layout": { "folders", "concepts", "kinds" } the layout template: the folder structure of that kind of project
5
+ // "hookOrder": { "<event> <matcher>": ["a.mjs", ...] } the order its hooks run in, per settings entry (optional)
6
+ // "leaveOut": ["rules/x.md", "hooks/y.mjs", ...] pack items this configuration does not install (optional)
7
+ // "sourceFiles": "<regex>" what doctor's coverage counts as source (optional; default C, C++ and shaders)
8
+ // "preamble": { "always": [...], "never": [...] } the house rules of its CLAUDE.md block
9
+ // Its rules, hooks, checks and skills are its packs' (`configs` lists them). layout.json "configuration" names the one a
10
+ // project has; `update` applies it; an install from before 0.10.0 gets the smallest configuration that takes its packs.
11
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { customPacks, planFiles, readPack } from './packs.mjs';
15
+
16
+ export const CONFIGS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'configurations');
17
+
18
+ export function availableConfigs() {
19
+ return readdirSync(CONFIGS_DIR).filter((d) => existsSync(join(CONFIGS_DIR, d, 'config.json'))).sort();
20
+ }
21
+
22
+ export function readConfig(name) {
23
+ const file = join(CONFIGS_DIR, String(name), 'config.json');
24
+ if (!existsSync(file)) throw new Error(`unknown configuration "${name}" (available: ${availableConfigs().join(', ')})`);
25
+ return JSON.parse(readFileSync(file, 'utf8'));
26
+ }
27
+
28
+ // Every pack a configuration takes, always-installed first.
29
+ export const allowedPacks = (config) => [...config.packs.always, ...config.packs.choose, ...customPacks()]; // a team's own pack goes with any configuration
30
+
31
+ // The packs a project names, checked against its configuration: base first, then the configuration's own, then the chosen.
32
+ export function packsFor(config, requested) {
33
+ const allowed = allowedPacks(config);
34
+ const bad = requested.filter((p) => !allowed.includes(p));
35
+ if (bad.length) throw new Error(`${bad.join(', ')} ${bad.length > 1 ? 'are' : 'is'} not in configuration ${config.name} (it takes: ${allowed.join(', ')})`);
36
+ const out = [...config.packs.always];
37
+ for (const p of requested) if (!out.includes(p)) out.push(p);
38
+ return out;
39
+ }
40
+
41
+ // The packs written in the lock and named in CLAUDE.md: base and the chosen ones; a configuration's own packs come with it.
42
+ export const listedPacks = (config, sources) => sources.filter((p) => p === 'base' || !config.packs.always.includes(p));
43
+
44
+ // The configuration of an install that names none: the smallest one that takes all its packs.
45
+ export function inferConfig(packs) {
46
+ const fits = availableConfigs().map(readConfig).filter((c) => packs.every((p) => allowedPacks(c).includes(p)));
47
+ if (!fits.length) throw new Error(`no configuration takes the packs ${packs.join(', ')}; name one with --config`);
48
+ // The configuration that has a pack by default first, then one with no folder layout of its own, then the smallest.
49
+ const rank = (c) => [packs.some((p) => c.packs.default.includes(p)) ? 0 : 1, c.packs.always.length, allowedPacks(c).length];
50
+ return fits.sort((a, b) => rank(a).map((v, i) => v - rank(b)[i]).find(Boolean) ?? 0)[0];
51
+ }
52
+
53
+ // Which configuration an install uses: the one named now (--config), else layout.json's, else its packs'.
54
+ export function resolveConfig({ named = null, layout = {}, packs = [] } = {}) {
55
+ if (named) return { config: readConfig(named), how: 'named' };
56
+ if (layout.configuration) return { config: readConfig(layout.configuration), how: 'layout' };
57
+ return { config: inferConfig(packs), how: 'packs' };
58
+ }
59
+
60
+ // What a configuration installs with these packs: every file, by its place in the project, and the items by kind.
61
+ export function planOf(config, sources, layout = { folders: {}, disable: [] }) {
62
+ const optIn = sources.flatMap((s) => readPack(s).optIn ?? []).filter((i) => !(layout.enable ?? []).includes(i)); // opt-in: only when enabled
63
+ const lay = { ...layout, disable: [...(layout.disable ?? []), ...(config.leaveOut ?? []), ...optIn] };
64
+ const files = new Set();
65
+ for (const name of sources) for (const { rel } of planFiles(name, lay)) files.add(rel);
66
+ return files;
67
+ }
68
+
69
+ export function itemsOf(files) {
70
+ const by = { rules: [], hooks: [], checks: [], skills: [] };
71
+ for (const rel of files) {
72
+ const p = rel.replace(/\\/g, '/');
73
+ const skill = p.match(/(?:^|\/)\.claude\/skills\/([^/]+)\/SKILL\.md$/);
74
+ if (skill) by.skills.push(skill[1]);
75
+ else if (/^\.claude\/rules\/[^/]+\.md$/.test(p)) by.rules.push(p.split('/').pop());
76
+ else if (/^\.claude\/hooks\/[^/]+\.(mjs|py)$/.test(p)) by.hooks.push(p.split('/').pop());
77
+ else if (/^\.claude\/checks\/[^/]+\.json$/.test(p)) by.checks.push(p.split('/').pop());
78
+ }
79
+ for (const k of Object.keys(by)) by[k].sort();
80
+ return by;
81
+ }
82
+
83
+ export function describeConfigs(current = null) {
84
+ return availableConfigs().map((name) => {
85
+ const c = readConfig(name);
86
+ const all = [...c.packs.always, ...c.packs.choose];
87
+ const n = itemsOf(planOf(c, all));
88
+ const choose = c.packs.choose.length ? `; ${c.packs.choose.join(', ')} (to choose)` : '';
89
+ return [
90
+ `${name}${name === current ? ' (this project)' : ''}`,
91
+ ` ${c.description}`,
92
+ ` packs: ${c.packs.always.join(', ')} (always)${choose}${c.packs.default.length ? `; init takes ${c.packs.default.join(', ')}` : ''}`,
93
+ ` with every pack: rules ${n.rules.length}, hooks ${n.hooks.length}, checks ${n.checks.length}, skills ${n.skills.length}`,
94
+ ` folders: ${Object.values(c.layout?.folders ?? {}).map((f) => `${f.path}/`).join(' ') || '(its packs\')'}`,
95
+ ].join('\n');
96
+ }).join('\n\n');
97
+ }
98
+
99
+ // The lines `config <name>` prints: what the switch adds and takes away.
100
+ // `installed`: the files the project's lock names (what it has now); without it, what `from` would install.
101
+ export function describeSwitch(from, fromSources, to, toSources, layout, installed = null) {
102
+ const before = itemsOf(installed ? new Set(installed) : planOf(from, fromSources, layout));
103
+ const after = itemsOf(planOf(to, toSources, layout));
104
+ const lines = [` ${from.name} -> ${to.name}`];
105
+ const fromListed = listedPacks(from, fromSources), toListed = listedPacks(to, toSources);
106
+ const added = toListed.filter((p) => !fromListed.includes(p)), dropped = fromListed.filter((p) => !toListed.includes(p));
107
+ if (added.length) lines.push(` packs added: ${added.join(', ')}`);
108
+ if (dropped.length) lines.push(` packs dropped: ${dropped.join(', ')}`);
109
+ for (const kind of Object.keys(before)) {
110
+ const plus = after[kind].filter((x) => !before[kind].includes(x)), minus = before[kind].filter((x) => !after[kind].includes(x));
111
+ if (plus.length) lines.push(` ${kind} added (${plus.length}): ${plus.join(', ')}`);
112
+ if (minus.length) lines.push(` ${kind} removed (${minus.length}): ${minus.join(', ')}`);
113
+ }
114
+ if (JSON.stringify(from.preamble) !== JSON.stringify(to.preamble)) lines.push(' CLAUDE.md preamble: changes');
115
+ const missing = Object.values(to.layout?.folders ?? {}).filter((f) => !Object.values(layout.folders ?? {}).some((g) => g.path === f.path));
116
+ if (missing.length && !layout.complete) lines.push(` folders added from its template: ${missing.map((f) => `${f.path}/`).join(' ')}`);
117
+ return lines.length > 1 ? lines : [...lines, ' no change'];
118
+ }
119
+
120
+ // doctor's lines: the configuration installed, and what does not fit it.
121
+ export function configProblems(dir, lockPacks, log) {
122
+ const file = join(dir, '.claude', 'layout.json');
123
+ if (!existsSync(file)) return [];
124
+ const layout = JSON.parse(readFileSync(file, 'utf8'));
125
+ let config;
126
+ try {
127
+ config = resolveConfig({ layout, packs: lockPacks }).config;
128
+ } catch (e) {
129
+ return [layout.configuration ? `layout.json names configuration "${layout.configuration}", which this kit does not have (${e.message})` : e.message];
130
+ }
131
+ log(` configuration: ${config.name}${layout.configuration ? '' : ' (from its packs; `update` writes it into layout.json)'}`);
132
+ return lockPacks.filter((p) => !allowedPacks(config).includes(p)).map((p) => `pack ${p} is installed but configuration ${config.name} does not take it (\`config <name>\` switches; \`configs\` lists them)`);
133
+ }
134
+
135
+ // The configuration's "sourceFiles" as a RegExp, or undefined (coverage's default) when it names none or cannot be read.
136
+ export function sourceFilesOf(dir, lockPacks) {
137
+ try {
138
+ const file = join(dir, '.claude', 'layout.json');
139
+ const layout = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {};
140
+ const re = layout.sourceFiles ?? resolveConfig({ layout, packs: lockPacks }).config.sourceFiles;
141
+ return re ? new RegExp(re, 'i') : undefined;
142
+ } catch {
143
+ return undefined;
144
+ }
145
+ }