@thedecipherist/claude-packs 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (827) hide show
  1. package/CHANGELOG.md +680 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -2
  4. package/bin/claude-packs.mjs +204 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +92 -0
  7. package/configurations/full-stack/config.json +152 -0
  8. package/configurations/go/config.json +96 -0
  9. package/configurations/node/config.json +100 -0
  10. package/configurations/python/config.json +97 -0
  11. package/configurations/synth-creator/config.json +148 -0
  12. package/global/hooks/gnu-shell.mjs +131 -0
  13. package/global/hooks/lib/program-read.mjs +196 -0
  14. package/global/hooks/lib/program-writes.mjs +177 -0
  15. package/global/hooks/lib/shell-writes.mjs +293 -0
  16. package/global/hooks/shell-traps.mjs +79 -0
  17. package/global/hooks/version-pin-guard.mjs +280 -0
  18. package/global/skills/latest-versions/SKILL.md +49 -0
  19. package/lib/backup.mjs +34 -0
  20. package/lib/claude-md.mjs +58 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +86 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +115 -0
  26. package/lib/fix-plan.mjs +71 -0
  27. package/lib/git-tokens.mjs +64 -0
  28. package/lib/global.mjs +125 -0
  29. package/lib/hook-proof.mjs +28 -0
  30. package/lib/hook-timing.mjs +42 -0
  31. package/lib/hover-gate.mjs +29 -0
  32. package/lib/kit-lock.mjs +149 -0
  33. package/lib/layout.mjs +75 -0
  34. package/lib/logs.mjs +69 -0
  35. package/lib/manifest.mjs +24 -0
  36. package/lib/new.mjs +49 -0
  37. package/lib/once-only-report.mjs +54 -0
  38. package/lib/pack-manifest.mjs +20 -0
  39. package/lib/packs.mjs +107 -0
  40. package/lib/planner.mjs +59 -0
  41. package/lib/repeats.mjs +65 -0
  42. package/lib/repo-weight.mjs +28 -0
  43. package/lib/rules.mjs +40 -0
  44. package/lib/scan-detect.mjs +94 -0
  45. package/lib/scan.mjs +168 -0
  46. package/lib/selftest-cache.mjs +39 -0
  47. package/lib/settings.mjs +138 -0
  48. package/lib/shape-cli.mjs +76 -0
  49. package/lib/shape.mjs +4 -0
  50. package/lib/suggest.mjs +18 -0
  51. package/lib/switches.mjs +44 -0
  52. package/lib/symmetry-doctor.mjs +26 -0
  53. package/lib/sync.mjs +130 -0
  54. package/package.json +34 -4
  55. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  56. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  57. package/packs/ai/checks/model-id-from-config.json +28 -0
  58. package/packs/ai/checks/model-output-validated.json +26 -0
  59. package/packs/ai/pack.json +38 -0
  60. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  61. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  62. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  63. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  64. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  65. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  66. package/packs/audio-core/checks/keep-werror.json +12 -0
  67. package/packs/audio-core/checks/no-fast-math.json +14 -0
  68. package/packs/audio-core/checks/no-locale-change.json +12 -0
  69. package/packs/audio-core/checks/no-march-native.json +14 -0
  70. package/packs/audio-core/clang-tidy +28 -0
  71. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  72. package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
  73. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  74. package/packs/audio-core/pack.json +187 -0
  75. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  76. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  77. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  78. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  79. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  80. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  81. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  82. package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
  83. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  84. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  85. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  86. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  87. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  88. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  89. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  90. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  91. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  92. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  93. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  94. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  95. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  96. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  97. package/packs/audio-core/rules/output-limiter.md +23 -0
  98. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  99. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  100. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  101. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  102. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  103. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  104. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  105. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  106. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  107. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  108. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  109. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  110. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  111. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  112. package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
  113. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  114. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  115. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  116. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
  117. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  118. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  119. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  120. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  121. package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
  122. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  123. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  124. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  125. package/packs/audio-core/tools/tone-report.mjs +47 -0
  126. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  127. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  128. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  129. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  130. package/packs/base/checks/one-walk-per-template.json +23 -0
  131. package/packs/base/checks/stub-reports-success.json +31 -0
  132. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  133. package/packs/base/hooks/brief-guard.mjs +96 -0
  134. package/packs/base/hooks/code-shape.mjs +95 -0
  135. package/packs/base/hooks/dispatch-once.mjs +87 -0
  136. package/packs/base/hooks/file-size-guard.mjs +61 -0
  137. package/packs/base/hooks/folder-law.mjs +74 -0
  138. package/packs/base/hooks/git-guard.mjs +289 -0
  139. package/packs/base/hooks/lesson-checks.mjs +174 -0
  140. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  141. package/packs/base/hooks/lib/code-shapes.mjs +137 -0
  142. package/packs/base/hooks/lib/content.mjs +14 -0
  143. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  144. package/packs/base/hooks/lib/dup-index.mjs +199 -0
  145. package/packs/base/hooks/lib/dup.mjs +127 -0
  146. package/packs/base/hooks/lib/elements.mjs +87 -0
  147. package/packs/base/hooks/lib/event-log.mjs +67 -0
  148. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  149. package/packs/base/hooks/lib/git-adds.mjs +20 -0
  150. package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
  151. package/packs/base/hooks/lib/glob.mjs +33 -0
  152. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  153. package/packs/base/hooks/lib/house.py +77 -0
  154. package/packs/base/hooks/lib/languages.mjs +66 -0
  155. package/packs/base/hooks/lib/layout.mjs +54 -0
  156. package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
  157. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  158. package/packs/base/hooks/lib/program-read.mjs +196 -0
  159. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  160. package/packs/base/hooks/lib/project.mjs +81 -0
  161. package/packs/base/hooks/lib/prose.mjs +11 -0
  162. package/packs/base/hooks/lib/pushed.mjs +201 -0
  163. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  164. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  165. package/packs/base/hooks/lib/selftest.mjs +45 -0
  166. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  167. package/packs/base/hooks/lib/shape.mjs +294 -0
  168. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  169. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  170. package/packs/base/hooks/lib/turn.mjs +81 -0
  171. package/packs/base/hooks/lib/writes.mjs +71 -0
  172. package/packs/base/hooks/making-check.mjs +243 -0
  173. package/packs/base/hooks/making.mjs +106 -0
  174. package/packs/base/hooks/once-only.mjs +150 -0
  175. package/packs/base/hooks/placement-law.mjs +84 -0
  176. package/packs/base/hooks/repo-litter.mjs +75 -0
  177. package/packs/base/hooks/reuse-hint.mjs +75 -0
  178. package/packs/base/hooks/run-hooks.mjs +132 -0
  179. package/packs/base/hooks/secrets-guard.mjs +99 -0
  180. package/packs/base/hooks/shape-law.mjs +133 -0
  181. package/packs/base/hooks/suite-once.mjs +80 -0
  182. package/packs/base/hooks/template-law.mjs +148 -0
  183. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  184. package/packs/base/pack.json +214 -0
  185. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  186. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  187. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  188. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  189. package/packs/base/rules/finished-work-lands.md +17 -0
  190. package/packs/base/rules/git-right-first-time.md +19 -0
  191. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  192. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  193. package/packs/base/rules/making-line-first.md +14 -0
  194. package/packs/base/rules/open-items-tagged.md +17 -0
  195. package/packs/base/rules/plan-and-logs.md +22 -0
  196. package/packs/base/rules/references-use-anchors.md +19 -0
  197. package/packs/base/rules/refusals-are-judged.md +18 -0
  198. package/packs/base/rules/reuse-before-writing.md +15 -0
  199. package/packs/base/rules/secrets-never-committed.md +16 -0
  200. package/packs/base/rules/skill-writing.md +23 -0
  201. package/packs/base/rules/split-before-the-limit.md +14 -0
  202. package/packs/base/rules/subagent-dispatch.md +26 -0
  203. package/packs/base/rules/test-only-what-changed.md +17 -0
  204. package/packs/base/rules/test-the-template-once.md +22 -0
  205. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  206. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  207. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  208. package/packs/base/skills/land-branch/SKILL.md +24 -0
  209. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  210. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  211. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  212. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  213. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  214. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  215. package/packs/base/tools/shape-symmetry.mjs +178 -0
  216. package/packs/base/tools/test-verdict.mjs +75 -0
  217. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  218. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  219. package/packs/ci/pack.json +21 -0
  220. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  221. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  222. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  223. package/packs/content-site/checks/img-has-size.json +20 -0
  224. package/packs/content-site/checks/page-head-complete.json +26 -0
  225. package/packs/content-site/pack.json +22 -0
  226. package/packs/content-site/rules/content-has-one-source.md +23 -0
  227. package/packs/content-site/rules/media-is-sized.md +24 -0
  228. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  229. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  230. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  231. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  232. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  233. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  234. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  235. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  236. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  237. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  238. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  239. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  240. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  241. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  242. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  243. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  244. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  245. package/packs/cpp/pack.json +22 -0
  246. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  247. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  248. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  249. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  250. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  251. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  252. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  253. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  254. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  255. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  256. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  257. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  258. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  259. package/packs/docker/checks/compose-image-pinned.json +14 -0
  260. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  261. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  262. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  263. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  264. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  265. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  266. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  267. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  268. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  269. package/packs/docker/checks/docker-exec-form.json +16 -0
  270. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  271. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  272. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  273. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  274. package/packs/docker/checks/health-checks-nothing.json +25 -0
  275. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  276. package/packs/docker/checks/no-latest-base.json +13 -0
  277. package/packs/docker/checks/no-secret-in-image.json +13 -0
  278. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  279. package/packs/docker/checks/node-env-before-install.json +13 -0
  280. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  281. package/packs/docker/pack.json +46 -0
  282. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  283. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  284. package/packs/docker/rules/docker-exec-form.md +19 -0
  285. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  286. package/packs/docker/rules/dockerfile-production.md +25 -0
  287. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  288. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  289. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  290. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  291. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  292. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  293. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  294. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  295. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  296. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  297. package/packs/dynamic-data/pack.json +23 -0
  298. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  299. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  300. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  301. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  302. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  303. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  304. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  305. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  306. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  307. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  308. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  309. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  310. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  311. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  312. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  313. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  314. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  315. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  316. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  317. package/packs/fastify/checks/one-error-body.json +26 -0
  318. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  319. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  320. package/packs/fastify/checks/sec-headers-present.json +28 -0
  321. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  322. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  323. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  324. package/packs/fastify/pack.json +41 -0
  325. package/packs/fastify/rules/credentials-and-input.md +23 -0
  326. package/packs/fastify/rules/security-one-switch.md +28 -0
  327. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  328. package/packs/fastify/rules/tests-use-inject.md +13 -0
  329. package/packs/fastify/rules/three-layers.md +20 -0
  330. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  331. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  332. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  333. package/packs/full-stack/pack.json +76 -0
  334. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  335. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  336. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  337. package/packs/go/pack.json +21 -0
  338. package/packs/go/rules/go-errors-are-handled.md +22 -0
  339. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  340. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  341. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  342. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  343. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  344. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  345. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  346. package/packs/gpu-painter/pack.json +91 -0
  347. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  348. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  349. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  350. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  351. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  352. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  353. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  354. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  355. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  356. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  357. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  358. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  359. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  360. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  361. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  362. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  363. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  364. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  365. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  366. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  367. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  368. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  369. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  370. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  371. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  372. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  373. package/packs/hook-proof/pack.json +38 -0
  374. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  375. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  376. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  377. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  378. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  379. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  380. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  381. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  382. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  383. package/packs/host-agent/pack.json +22 -0
  384. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  385. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  386. package/packs/html-ui/checks/external-link-rel.json +21 -0
  387. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  388. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  389. package/packs/html-ui/checks/no-inline-style.json +23 -0
  390. package/packs/html-ui/pack.json +33 -0
  391. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  392. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  393. package/packs/http-api/checks/api-path-versioned.json +31 -0
  394. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  395. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  396. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  397. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  398. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  399. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  400. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  401. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  402. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  403. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  404. package/packs/http-api/checks/guard-fails-open.json +28 -0
  405. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  406. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  407. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  408. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  409. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  410. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  411. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  412. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  413. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  414. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  415. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  416. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  417. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  418. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  419. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  420. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  421. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  422. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  423. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  424. package/packs/http-api/pack.json +45 -0
  425. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  426. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  427. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  428. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  429. package/packs/http-api/rules/health-and-correlation.md +17 -0
  430. package/packs/http-api/rules/one-error-contract.md +17 -0
  431. package/packs/http-api/rules/routes-are-thin.md +17 -0
  432. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  433. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  434. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  435. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  436. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  437. package/packs/instrument-shell/pack.json +22 -0
  438. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  439. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  440. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  441. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  442. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  443. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  444. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  445. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  446. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  447. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  448. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  449. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  450. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  451. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  452. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  453. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  454. package/packs/js-ts/checks/no-any.json +27 -0
  455. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  456. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  457. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  458. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  459. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  460. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  461. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  462. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  463. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  464. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  465. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  466. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  467. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  468. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  469. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  470. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  471. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  472. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  473. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  474. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  475. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  476. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  477. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  478. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  479. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  480. package/packs/js-ts/pack.json +84 -0
  481. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  482. package/packs/js-ts/rules/env-through-config.md +36 -0
  483. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  484. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  485. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  486. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  487. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  488. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  489. package/packs/js-ts/rules/no-any.md +34 -0
  490. package/packs/js-ts/rules/no-console.md +30 -0
  491. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  492. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  493. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  494. package/packs/js-ts/rules/one-logger.md +24 -0
  495. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  496. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  497. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  498. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  499. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  500. package/packs/js-ts/rules/small-functions.md +20 -0
  501. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  502. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  503. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  504. package/packs/js-ts/rules/typed-exports.md +27 -0
  505. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  506. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  507. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  508. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  509. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  510. package/packs/midi/checks/midi-realtime-first.json +27 -0
  511. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  512. package/packs/midi/checks/position-not-integrated.json +25 -0
  513. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  514. package/packs/midi/checks/sysex-length-literal.json +23 -0
  515. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  516. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  517. package/packs/midi/pack.json +22 -0
  518. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  519. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  520. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  521. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  522. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  523. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  524. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  525. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  526. package/packs/mongo/checks/bulk-over-many.json +28 -0
  527. package/packs/mongo/checks/created-at-immutable.json +25 -0
  528. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  529. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  530. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  531. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  532. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  533. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  534. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  535. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  536. package/packs/mongo/checks/no-find-reads.json +26 -0
  537. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  538. package/packs/mongo/checks/no-mongoose.json +23 -0
  539. package/packs/mongo/checks/no-negation-delete.json +26 -0
  540. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  541. package/packs/mongo/checks/objectid-equals.json +26 -0
  542. package/packs/mongo/checks/update-then-reread.json +24 -0
  543. package/packs/mongo/checks/watch-not-poll.json +25 -0
  544. package/packs/mongo/pack.json +52 -0
  545. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  546. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  547. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  548. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  549. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  550. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  551. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  552. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  553. package/packs/multi-tenant/pack.json +22 -0
  554. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  555. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  556. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  557. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  558. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  559. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  560. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  561. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  562. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  563. package/packs/nginx/pack.json +43 -0
  564. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  565. package/packs/nginx/rules/nginx-production.md +26 -0
  566. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  567. package/packs/python/checks/py-no-bare-except.json +18 -0
  568. package/packs/python/checks/py-no-eval.json +18 -0
  569. package/packs/python/checks/py-no-mutable-default.json +18 -0
  570. package/packs/python/checks/py-no-shell-true.json +18 -0
  571. package/packs/python/checks/py-no-star-import.json +18 -0
  572. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  573. package/packs/python/checks/py-none-by-is.json +18 -0
  574. package/packs/python/checks/py-open-with-with.json +18 -0
  575. package/packs/python/checks/py-requests-timeout.json +18 -0
  576. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  577. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  578. package/packs/python/pack.json +26 -0
  579. package/packs/python/rules/python-errors-and-resources.md +26 -0
  580. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  581. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  582. package/packs/react/checks/dialog-needs-role.json +28 -0
  583. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  584. package/packs/react/checks/icon-button-needs-name.json +24 -0
  585. package/packs/react/checks/label-needs-a-field.json +24 -0
  586. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  587. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  588. package/packs/react/pack.json +20 -0
  589. package/packs/react/rules/react-accessible-controls.md +20 -0
  590. package/packs/react/rules/react-state-and-effects.md +23 -0
  591. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  592. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  593. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  594. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  595. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  596. package/packs/reverse-engineering/pack.json +35 -0
  597. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  598. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  599. package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
  600. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  601. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  602. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  603. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  604. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  605. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  606. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  607. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
  608. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  609. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  610. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  611. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  612. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  613. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  614. package/packs/shell/checks/sh-mktemp.json +18 -0
  615. package/packs/shell/checks/sh-no-backticks.json +18 -0
  616. package/packs/shell/checks/sh-no-eval.json +18 -0
  617. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  618. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  619. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  620. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  621. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  622. package/packs/shell/checks/sh-strict-mode.json +20 -0
  623. package/packs/shell/pack.json +28 -0
  624. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  625. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  626. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  627. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  628. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  629. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  630. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  631. package/packs/shell-hooks/pack.json +36 -0
  632. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  633. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  634. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  635. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  636. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  637. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  638. package/packs/sound-design/pack.json +22 -0
  639. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  640. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  641. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  642. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  643. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  644. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  645. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  646. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  647. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  648. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  649. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  650. package/packs/stellix/checks/one-seeded-random.json +16 -0
  651. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  652. package/packs/stellix/checks/world-no-libm.json +15 -0
  653. package/packs/stellix/checks/world-no-unordered.json +10 -0
  654. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  655. package/packs/stellix/pack.json +61 -0
  656. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  657. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  658. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  659. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  660. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  661. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  662. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  663. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  664. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  665. package/packs/stored-text/pack.json +41 -0
  666. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  667. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  668. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  669. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  670. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  671. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  672. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  673. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  674. package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
  675. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  676. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  677. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  678. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  679. package/packs/synth-creator/pack.json +136 -0
  680. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  681. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  682. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  683. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  684. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  685. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  686. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  687. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
  688. package/packs/synth-creator/tools/freeze.mjs +36 -0
  689. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  690. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  691. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  692. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  693. package/packs/tui/checks/tui-width-is-state.json +33 -0
  694. package/packs/tui/pack.json +44 -0
  695. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  696. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  697. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  698. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  699. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  700. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  701. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  702. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  703. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  704. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  705. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  706. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  707. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  708. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  709. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  710. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  711. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  712. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  713. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  714. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  715. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  716. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  717. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  718. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  719. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  720. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  721. package/packs/ui-craft/pack.json +43 -0
  722. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  723. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  724. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  725. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  726. package/packs/ui-craft/rules/interface-copy.md +26 -0
  727. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  728. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  729. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  730. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  731. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  732. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  733. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  734. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  735. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  736. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  737. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  738. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  739. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  740. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  741. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  742. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  743. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  744. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  745. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  746. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  747. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  748. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  749. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  750. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  751. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  752. package/packs/web/checks/client-no-api-key.json +29 -0
  753. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  754. package/packs/web/checks/client-no-server-import.json +31 -0
  755. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  756. package/packs/web/checks/css-no-inline-style.json +30 -0
  757. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  758. package/packs/web/checks/key-from-index.json +23 -0
  759. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  760. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  761. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  762. package/packs/web/checks/rcss-button-cursor.json +30 -0
  763. package/packs/web/checks/rcss-no-100vw.json +21 -0
  764. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  765. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  766. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  767. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  768. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  769. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  770. package/packs/web/pack.json +73 -0
  771. package/packs/web/rules/accessibility.md +26 -0
  772. package/packs/web/rules/client-boundaries.md +23 -0
  773. package/packs/web/rules/production-build.md +22 -0
  774. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  775. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  776. package/packs/web/rules/tailwind-v4.md +23 -0
  777. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  778. package/packs/web/rules/web-performance.md +21 -0
  779. package/packs/web/skills/design-direction/SKILL.md +30 -0
  780. package/packs/web/skills/design-review/SKILL.md +28 -0
  781. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  782. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  783. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  784. package/packs/websocket/checks/no-token-in-query.json +26 -0
  785. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  786. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  787. package/packs/websocket/pack.json +21 -0
  788. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  789. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  790. package/packs/writing/checks/no-copula-dodging.json +18 -0
  791. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  792. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  793. package/packs/writing/checks/no-participial-opener.json +18 -0
  794. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  795. package/packs/writing/checks/no-slop-words.json +18 -0
  796. package/packs/writing/checks/no-stock-connectors.json +18 -0
  797. package/packs/writing/checks/no-time-or-date.json +27 -0
  798. package/packs/writing/checks/no-trailing-ing.json +18 -0
  799. package/packs/writing/checks/no-vague-attribution.json +18 -0
  800. package/packs/writing/pack.json +43 -0
  801. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  802. package/packs/writing/rules/cut-the-padding.md +20 -0
  803. package/packs/writing/rules/direct-voice.md +22 -0
  804. package/packs/writing/rules/no-cargo-cult.md +16 -0
  805. package/packs/writing/rules/no-scaffolding.md +19 -0
  806. package/packs/writing/rules/no-stock-language.md +27 -0
  807. package/packs/writing/rules/no-time-or-date.md +18 -0
  808. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  809. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  810. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  811. package/packs/writing/skills/humanize/SKILL.md +28 -0
  812. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  813. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  814. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  815. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  816. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  817. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  818. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  819. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  820. package/packs/zod/hooks/schemas-only.mjs +53 -0
  821. package/packs/zod/pack.json +44 -0
  822. package/packs/zod/rules/env-shapes-only.md +14 -0
  823. package/packs/zod/rules/zod-4-schemas.md +14 -0
  824. package/profiles/stellix.layout.json +315 -0
  825. package/profiles/sub37.layout.json +247 -0
  826. package/prompts/scan-review.md +40 -0
  827. package/prompts/survey.md +82 -0
@@ -0,0 +1,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,58 @@
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
+
6
+ const START = '<!-- claude-packs:start -->';
7
+ const END = '<!-- claude-packs:end -->';
8
+
9
+ // The house rules after the folders are the configuration's preamble (configurations/<name>/config.json, 0.10.0).
10
+ function section(layout, packNames, preamble) {
11
+ // Folders no kind points at are listed by purpose; the rest appear once, in the kinds list.
12
+ const used = new Set(Object.values(layout.kinds ?? {}).map((k) => k.in));
13
+ const folders = Object.entries(layout.folders)
14
+ .filter(([name]) => !used.has(name) && name !== 'src')
15
+ .sort(([, a], [, b]) => a.path.localeCompare(b.path))
16
+ .map(([, f]) => ` - \`${f.path}/\`: ${f.purpose}`);
17
+ return [
18
+ START,
19
+ '<!-- Generated by claude-packs from .claude/layout.json. Edit that file and run `npx --no-install @thedecipherist/claude-packs update`; edits here are replaced. -->',
20
+ '## Project layout',
21
+ '',
22
+ '<constraints>',
23
+ 'Always do:',
24
+ '- Put every kind of thing in its one place. The rules, checks and skills for it load only there:',
25
+ ...kindLines(layout),
26
+ ...(folders.length ? ['- The other folders:', ...folders] : []),
27
+ ...preamble.always.map((l) => `- ${l}`),
28
+ '',
29
+ 'Never do:',
30
+ ...preamble.never.map((l) => `- ${l}`),
31
+ '</constraints>',
32
+ '',
33
+ ...commandLines(layout),
34
+ `Installed packs: ${packNames.join(', ')}.`,
35
+ END,
36
+ ].join('\n');
37
+ }
38
+
39
+ function kindLines(layout) {
40
+ return Object.values(layout.kinds ?? {}).map((k) => ` - ${k.what} → \`${layout.folders?.[k.in] ? `${layout.folders[k.in].path}/` : k.in}\``);
41
+ }
42
+
43
+ function commandLines(layout) {
44
+ const entries = Object.entries(layout.commands ?? {});
45
+ if (entries.length === 0) return [];
46
+ return ['## Project commands', '', 'Skills and checks run these; use them, not the raw tools:', ...entries.map(([k, v]) => `- ${k}: \`${v}\``), ''];
47
+ }
48
+
49
+ export function writeClaudeMd(projectDir, layout, packNames, preamble) {
50
+ const file = join(projectDir, 'CLAUDE.md');
51
+ const block = section(layout, packNames, preamble);
52
+ let text = existsSync(file) ? readFileSync(file, 'utf8') : '';
53
+ const s = text.indexOf(START);
54
+ const e = text.indexOf(END);
55
+ if (s !== -1 && e > s) text = text.slice(0, s) + block + text.slice(e + END.length);
56
+ else text = `${text.trimEnd()}${text.trim() ? '\n\n' : ''}${block}\n`;
57
+ writeFileSync(file, text);
58
+ }
@@ -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);
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
+ }
@@ -0,0 +1,86 @@
1
+ // "Do the rules actually fire?" Two numbers per project: rules whose paths match no file
2
+ // (the folder structure and the rules disagree) and source files no rule covers (code that
3
+ // grew outside the structure). doctor prints both; `doctor --strict` fails on either.
4
+ import { execFileSync } from 'node:child_process';
5
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
+ import { join, relative } from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+
9
+ const SKIP = /(^|\/)(\.git|node_modules|build|Builds|_deps|JUCE|JuceLibraryCode|cmake-build[^/]*)(\/|$)/;
10
+ // Inside any .claude/ only the kit's own files are walked: skills, rules and agents, which rules on
11
+ // the `claude` concept aim at (Sub37 WB-1: skipping .claude made those rules look dead in --strict).
12
+ // Worktrees, hooks, state and backups stay out, so a worktree's copy never counts as the project's.
13
+ const CLAUDE_KEEP = new Set(['skills', 'rules', 'agents']);
14
+ export const SOURCE = /\.(c|cc|cpp|cxx|h|hh|hpp|mm|m|wgsl|metal|hlsl)$/i;
15
+
16
+ // The files git would not ignore (tracked, and untracked but not ignored), relative to root; null
17
+ // when root is not inside a git work tree. Build output a project ignores (Stellix's out/**) is
18
+ // not source, wherever the build folder is and whatever it is called.
19
+ function gitVisible(root) {
20
+ const ls = (...a) => execFileSync('git', ['ls-files', '-z', ...a], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 256 * 1024 * 1024 }).split('\0').filter(Boolean);
21
+ try {
22
+ return { files: new Set(ls('--cached', '--others', '--exclude-standard')), dirs: new Set(ls('--others', '--ignored', '--exclude-standard', '--directory').filter((d) => d.endsWith('/'))) };
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ export function projectFiles(root) {
29
+ const git = gitVisible(root);
30
+ const visible = git?.files;
31
+ const out = [];
32
+ const walk = (dir) => {
33
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
34
+ const abs = join(dir, e.name);
35
+ const rel = relative(root, abs).replace(/\\/g, '/');
36
+ if (SKIP.test(rel)) continue;
37
+ if (e.isDirectory() && git?.dirs.has(`${rel}/`)) continue; // a whole ignored folder (build output): never walked
38
+ if (e.isDirectory() && dir.endsWith('/.claude') && !CLAUDE_KEEP.has(e.name)) continue;
39
+ if (e.isDirectory()) walk(abs);
40
+ else if (!visible || visible.has(rel)) out.push(rel);
41
+ }
42
+ };
43
+ walk(root);
44
+ return out;
45
+ }
46
+
47
+ // The project's own .claude/ files (its rules, skills, agents), which rules of the "claude" concept
48
+ // fire on; agent worktrees, backups and run state are not the project's.
49
+ function claudeFiles(projectDir) {
50
+ const out = [];
51
+ const walk = (dir) => {
52
+ if (!existsSync(dir)) return;
53
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
54
+ const rel = relative(projectDir, join(dir, e.name)).replace(/\\/g, '/');
55
+ if (/^\.claude\/(worktrees|\.claude-packs-backup|state)(\/|$)/.test(rel)) continue;
56
+ if (e.isDirectory()) walk(join(dir, e.name));
57
+ else out.push(rel);
58
+ }
59
+ };
60
+ walk(join(projectDir, '.claude'));
61
+ return out;
62
+ }
63
+
64
+ function rulePaths(claudeDir) {
65
+ const dir = join(claudeDir, 'rules');
66
+ if (!existsSync(dir)) return [];
67
+ return readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => {
68
+ const fm = readFileSync(join(dir, f), 'utf8').match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
69
+ return { rule: f, globs: [...fm.matchAll(/^\s*-\s*"([^"]+)"/gm)].map((m) => m[1]) };
70
+ });
71
+ }
72
+
73
+ // `source`: what counts as source (the configuration's "sourceFiles"; default C, C++ and shaders). KWB-32: a full-stack project's
74
+ // TypeScript was never counted, so a file outside every rule could not show.
75
+ export async function coverage(projectDir, source = SOURCE) {
76
+ const claudeDir = join(projectDir, '.claude');
77
+ const { matchesAny } = await import(pathToFileURL(join(claudeDir, 'hooks', 'lib', 'glob.mjs')).href);
78
+ const { readLayout } = await import(pathToFileURL(join(claudeDir, 'hooks', 'lib', 'layout.mjs')).href);
79
+ const all = projectFiles(projectDir);
80
+ const rules = rulePaths(claudeDir);
81
+ const reachable = [...all, ...claudeFiles(projectDir)];
82
+ const deadRules = rules.filter((r) => r.globs.length && !reachable.some((f) => matchesAny(f, r.globs))).map((r) => r.rule);
83
+ const protectedGlobs = readLayout(projectDir).protected ?? [];
84
+ const uncovered = all.filter((f) => source.test(f) && !matchesAny(f, protectedGlobs) && !rules.some((r) => matchesAny(f, r.globs)));
85
+ return { deadRules, uncovered, sourceCount: all.filter((f) => source.test(f)).length };
86
+ }
@@ -0,0 +1,93 @@
1
+ // Custom packs (0.21.0: someone makes a pack for their employer's internals and shares it with their team). A team keeps its packs
2
+ // in a small repository of its own, `packs/<name>/` (or a pack.json at the root), which `new-pack` scaffolds. A project adds one with
3
+ // `add <name> --from <git-url>`: the repository is fetched to a cache, never run, and the lock records its url and commit, so a teammate's
4
+ // checkout restores the same pack and `update` pulls the newest. Who can fetch it is the git host's business. Its hooks stay off until the
5
+ // project's "enable" names them (lib/packs.mjs): a hook runs code on every tool call.
6
+ import { execFileSync } from 'node:child_process';
7
+ import { createHash } from 'node:crypto';
8
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { dirname, join, resolve } from 'node:path';
11
+ import { readLock } from './manifest.mjs';
12
+ import { manifestProblems } from './pack-manifest.mjs';
13
+ import { readPack, registerPack } from './packs.mjs';
14
+
15
+ const cacheRoot = () => process.env.CLAUDE_PACKS_CACHE ?? join(homedir(), '.cache', 'claude-packs');
16
+ const git = (cwd, ...args) => execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
17
+
18
+ // The repository at a commit, or at its newest: -> { folder, commit }.
19
+ function fetchRepo(url, commit = null) {
20
+ if (url.startsWith('-')) throw new Error(`not a git url: ${url}`);
21
+ const folder = join(cacheRoot(), createHash('sha256').update(url).digest('hex').slice(0, 16));
22
+ if (existsSync(join(folder, '.git'))) git(folder, 'fetch', '--quiet', 'origin');
23
+ else {
24
+ mkdirSync(dirname(folder), { recursive: true });
25
+ git(cacheRoot(), 'clone', '--quiet', '--', url, folder);
26
+ }
27
+ git(folder, 'checkout', '--quiet', '--detach', commit ?? 'origin/HEAD');
28
+ return { folder, commit: git(folder, 'rev-parse', 'HEAD') };
29
+ }
30
+
31
+ function register(name, folder) {
32
+ const nested = join(folder, 'packs', name);
33
+ const root = existsSync(join(nested, 'pack.json')) ? nested : folder;
34
+ if (!existsSync(join(root, 'pack.json'))) throw new Error(`no pack "${name}" in that repository (looked for packs/${name}/pack.json and pack.json)`);
35
+ registerPack(name, root);
36
+ const pack = readPack(name);
37
+ const problems = [...manifestProblems(pack, name), ...(pack.type === 'custom' ? [] : [`pack ${name} says its type is ${pack.type}, not custom`])];
38
+ if (problems.length) throw new Error(problems.join('; '));
39
+ return pack;
40
+ }
41
+
42
+ export function addCustomPack(name, url) {
43
+ if (!name) throw new Error('name the pack: `add <name> --from <git-url>`');
44
+ const { folder, commit } = fetchRepo(url);
45
+ const pack = register(name, folder);
46
+ console.log(` ${name} ${pack.version} by ${pack.author} from ${url}`);
47
+ return { url, commit };
48
+ }
49
+
50
+ // The custom packs a project's lock names, at their commits, or at their newest for `update`. -> { name: { url, commit } }.
51
+ export function loadCustomPacks(projectDir, { latest = false } = {}) {
52
+ const out = {};
53
+ for (const [name, { url, commit }] of Object.entries(readLock(join(resolve(projectDir), '.claude')).sources ?? {})) {
54
+ try {
55
+ const got = fetchRepo(url, latest ? null : commit);
56
+ const pack = register(name, got.folder);
57
+ if (latest && got.commit !== commit) console.log(` ${name}: now ${pack.version} (${got.commit.slice(0, 7)})`);
58
+ out[name] = { url, commit: got.commit };
59
+ } catch (e) {
60
+ throw new Error(`custom pack ${name} could not be fetched from ${url}: ${String(e.stderr ?? e.message).trim().split('\n')[0]}. Check access to that repository.`);
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+
66
+ const gitConfig = (key) => { try { return git('.', 'config', key); } catch { return ''; } };
67
+
68
+ const FILES = (name, author, repo) => ({
69
+ 'README.md': `# ${name}\n\nOur own claude-packs. Each folder under \`packs/\` is one pack: rules, checks, hooks, skills.\n\nInstall in a project:\n\n\`\`\`\nnpx --no-install @thedecipherist/claude-packs add ${name} --from ${repo}\n\`\`\`\n\nAfter that, \`update\` pulls the newest. A pack's hooks stay off until the project names them in \`"enable"\` in \`.claude/layout.json\`.\n`,
70
+ [`packs/${name}/pack.json`]: `${JSON.stringify({ name, version: '0.1.0', description: `What ${name} is for.`, type: 'custom', author, license: 'UNLICENSED', homepage: repo, repository: { type: 'git', url: repo, directory: `packs/${name}` }, keywords: [], engines: { 'claude-packs': '>=0.21.0' }, folders: {}, concepts: {}, skillsIn: null, hooks: {} }, null, 2)}\n`,
71
+ [`packs/${name}/rules/${name}-example.md`]: '---\n# concepts: js\npaths:\n - "placeholder"\n---\n\n# State the law in one line\n\n<constraints>\nAlways do:\n- What the rule asks for.\n\nNever do:\n- What it refuses.\n</constraints>\n',
72
+ [`packs/${name}/checks/${name}-example.json`]: `${JSON.stringify({ id: `${name}-example`, why: 'Why this pattern is refused.', rule: `.claude/rules/${name}-example.md`, concepts: ['js'], globs: [], code: true, pattern: '\\bOLD_API_NAME\\b', bad: 'OLD_API_NAME();\n', good: 'newApiName();\n' }, null, 2)}\n`,
73
+ [`packs/${name}/skills/${name}-example/SKILL.md`]: `---\nname: ${name}-example\ndescription: Use when the situation this skill is for comes up.\n---\n\n# One job\n\n<constraints>\nAlways do:\n1. The first step.\n\nNever do:\n- What goes wrong without this skill.\n</constraints>\n`,
74
+ });
75
+
76
+ export function newPack(dir, name, { repo = null } = {}) {
77
+ if (!/^[a-z][a-z0-9-]*$/.test(name ?? '')) throw new Error('name the pack in lower case: `new-pack acme-internal`');
78
+ const root = join(dir, name);
79
+ if (existsSync(root)) throw new Error(`${root} already exists`);
80
+ const url = repo ?? `git@git.example.com:team/${name}.git`;
81
+ for (const [rel, text] of Object.entries(FILES(name, gitConfig('user.name') || 'Your team', url))) {
82
+ mkdirSync(dirname(join(root, rel)), { recursive: true });
83
+ writeFileSync(join(root, rel), text);
84
+ }
85
+ git(root, 'init', '--quiet', '--initial-branch', 'main');
86
+ git(root, 'add', '.');
87
+ git(root, '-c', 'user.name=claude-packs', '-c', 'user.email=claude-packs@localhost', 'commit', '--quiet', '-m', `${name}: the scaffold`);
88
+ console.log(`A new pack repository: ${root}`);
89
+ console.log(` 1. Edit packs/${name}/ (pack.json says who made it and its git url${repo ? '' : ': set repository.url'}).`);
90
+ console.log(` 2. Push it to your git host.`);
91
+ console.log(` 3. In a project: npx --no-install @thedecipherist/claude-packs add ${name} --from ${url}`);
92
+ return 0;
93
+ }
package/lib/doctor.mjs ADDED
@@ -0,0 +1,115 @@
1
+ // Checks a project's install: lock present, rule paths current, lesson checks pass their own
2
+ // examples, every layout folder exists, hooks registered, and which managed files were edited.
3
+ import { execFileSync, spawn } from 'node:child_process';
4
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { isModified, readLock } from './manifest.mjs';
7
+ import { applyPaths } from './rules.mjs';
8
+ import { coverage } from './coverage.mjs';
9
+ import { tokenProblems } from './git-tokens.mjs';
10
+ import { shapeProblems } from './shape-cli.mjs';
11
+ import { symmetryProblems } from './symmetry-doctor.mjs';
12
+ import { onceOnlyDoctor, useJscpd } from './once-only-report.mjs';
13
+ import { proved, remember, selftestKey, sharedHash } from './selftest-cache.mjs';
14
+ import { timingProblems } from './hook-timing.mjs';
15
+ import { configProblems, sourceFilesOf } from './configs.mjs';
16
+ import { hookProofProblems } from './hook-proof.mjs';
17
+ import { manifestProblems } from './pack-manifest.mjs';
18
+ import { readPack } from './packs.mjs';
19
+ import { hoverGateProblems, pageGateProblems } from './hover-gate.mjs';
20
+
21
+ const SELFTEST_JOBS = Number(process.env.CLAUDE_PACKS_SELFTEST_JOBS ?? 4);
22
+ const SELFTEST_S = Number(process.env.CLAUDE_PACKS_SELFTEST_S ?? 180);
23
+
24
+ export async function doctor(projectDir, log = console.log, { strict = false, fresh = false } = {}) {
25
+ const claudeDir = join(projectDir, '.claude');
26
+ const lock = readLock(claudeDir);
27
+ const problems = [];
28
+ if (lock.packs.length === 0) problems.push('no packs installed (run `npx --no-install @thedecipherist/claude-packs init <packs>`)');
29
+ else problems.push(...configProblems(projectDir, lock.packs, log)); // the configuration installed, and a pack it does not take (0.10.0)
30
+
31
+ const theirs = new Set(
32
+ readdirSync(join(claudeDir, 'rules'))
33
+ .map((f) => join('.claude', 'rules', f))
34
+ .filter((rel) => lock.files[rel] === undefined || isModified(projectDir, rel, lock)),
35
+ );
36
+ const { stale } = await applyPaths(projectDir, { checkOnly: true, skip: theirs });
37
+ if (stale) problems.push(`${stale} rule(s) have paths out of date with layout.json (run update)`);
38
+
39
+ const layout = JSON.parse(readFileSync(join(claudeDir, 'layout.json'), 'utf8'));
40
+ for (const f of Object.values(layout.folders)) if (!existsSync(join(projectDir, f.path))) problems.push(`layout folder missing: ${f.path}/`);
41
+
42
+ const settings = existsSync(join(claudeDir, 'settings.json')) ? readFileSync(join(claudeDir, 'settings.json'), 'utf8') : '';
43
+ if (!settings.includes('.claude/hooks/lesson-checks.mjs')) problems.push('hooks not registered in .claude/settings.json');
44
+
45
+ try {
46
+ execFileSync('node', [join(claudeDir, 'hooks', 'lesson-checks.mjs'), '--selftest'], {
47
+ env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
48
+ stdio: 'pipe',
49
+ });
50
+ } catch (e) {
51
+ problems.push(`lesson checks fail their own examples:\n${String(e.stderr)}`);
52
+ }
53
+
54
+ useJscpd(projectDir); // once-only's and making-check's selftests need the detector (0.9.0)
55
+ // Every hook proves its own law (Sub37 WB-4), all at once; a hook without a selftest is noted.
56
+ const hooks = readdirSync(join(claudeDir, 'hooks')).filter((f) => /\.(mjs|py)$/.test(f) && f !== 'lesson-checks.mjs').sort();
57
+ // Run SELFTEST_JOBS at a time with SELFTEST_S each: all at once with 60 s each, the heavy ones (git repos and worktrees) were killed
58
+ // under load and read as failing, with no output (the kit's own suite failed a different few each run while agents built).
59
+ // A selftest that passed is not run again while what it ran on is the same (lib/selftest-cache.mjs, KWB-41).
60
+ const shared = sharedHash(claudeDir, JSON.parse(readFileSync(join(claudeDir, 'layout.json'), 'utf8')).guards);
61
+ const one = (hook) => {
62
+ const file = join(claudeDir, 'hooks', hook);
63
+ if (!readFileSync(file, 'utf8').includes('--selftest')) return Promise.resolve({ hook, missing: true });
64
+ const key = selftestKey(file, shared);
65
+ if (!fresh && proved(key)) return Promise.resolve({ hook, code: 0, unchanged: true });
66
+ return new Promise((done) => {
67
+ const child = spawn(hook.endsWith('.py') ? 'python3' : 'node', [file, '--selftest'], { env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir, PYTHONDONTWRITEBYTECODE: '1' }, stdio: ['ignore', 'pipe', 'pipe'] });
68
+ let out = '', timedOut = false;
69
+ const timer = setTimeout(() => { timedOut = true; child.kill(); }, SELFTEST_S * 1000);
70
+ child.stdout.on('data', (d) => { out += d; });
71
+ child.stderr.on('data', (d) => { out += d; });
72
+ child.on('error', (e) => { clearTimeout(timer); done({ hook, code: 1, err: e.message }); });
73
+ child.on('close', (code) => { clearTimeout(timer); if (code === 0 && !timedOut) remember(key); done({ hook, code, timedOut, err: out.trim().split('\n').slice(-4).join('\n') }); });
74
+ });
75
+ };
76
+ const results = [];
77
+ for (let i = 0; i < hooks.length; i += SELFTEST_JOBS) results.push(...await Promise.all(hooks.slice(i, i + SELFTEST_JOBS).map(one)));
78
+ const same = results.filter((r) => r.unchanged).length;
79
+ if (same) log(` selftests: ${same} of ${results.length} unchanged, not run again (doctor --fresh runs them all)`);
80
+ for (const r of results) {
81
+ if (r.missing) log(` note: ${r.hook} has no --selftest`);
82
+ else if (r.timedOut) problems.push(`${r.hook}'s selftest timed out after ${SELFTEST_S} s (the machine is loaded, or the selftest hangs)`);
83
+ else if (r.code !== 0) problems.push(`${r.hook} fails its selftest${r.err ? `:\n${r.err}` : ''}`);
84
+ }
85
+ problems.push(...symmetryProblems(projectDir, log));
86
+ for (const rel of Object.keys(lock.files).filter((f) => f.endsWith('SKILL.md'))) {
87
+ const abs = join(projectDir, rel);
88
+ if (!existsSync(abs)) continue;
89
+ const text = readFileSync(abs, 'utf8');
90
+ if (text.length > 8000) problems.push(`${rel} is ${text.length} characters (keep skills under 8,000; compaction cuts at 20,000)`);
91
+ if (!/^description:\s*Use /m.test(text)) problems.push(`${rel}: description must start with "Use when…" and list triggers only`);
92
+ }
93
+
94
+ for (const name of lock.packs) problems.push(...manifestProblems(readPack(name), name));
95
+ problems.push(...await hookProofProblems(projectDir, settings)); // a guard that fails open fails silently (0.18.0)
96
+ problems.push(...await pageGateProblems(projectDir, layout)); // UI code is proved rendered (0.17.0)
97
+ problems.push(...await hoverGateProblems(projectDir, layout)); // UI code needs a declared hover gate (0.10.1, Stellix B-261)
98
+ problems.push(...tokenProblems(projectDir, layout.siblings ?? []));
99
+ problems.push(...shapeProblems(projectDir, lock.packs)); // the shape law: a synth has design/shape.json, and its rows keep the contract
100
+ problems.push(...onceOnlyDoctor(projectDir, log)); // making's cost per turn, the duplicate count (0.9.0)
101
+ problems.push(...timingProblems(projectDir, log)); // each hook's median and worst from .claude/hooks.log; a median over 500 ms is red (KWB-24)
102
+
103
+ const cov = await coverage(projectDir, sourceFilesOf(projectDir, lock.packs)); // the configuration says what is source (KWB-32)
104
+ if (cov.sourceCount > 0) {
105
+ const say = (msg) => (strict ? problems.push(msg) : log(` note: ${msg}`));
106
+ if (cov.deadRules.length) say(`${cov.deadRules.length} rule(s) match no file yet, so they never fire: ${cov.deadRules.slice(0, 8).join(', ')}${cov.deadRules.length > 8 ? ', …' : ''}`);
107
+ if (cov.uncovered.length) say(`${cov.uncovered.length} source file(s) no rule covers (outside the structure?): ${cov.uncovered.slice(0, 8).join(', ')}${cov.uncovered.length > 8 ? ', …' : ''}`);
108
+ }
109
+
110
+ const edited = Object.keys(lock.files).filter((rel) => isModified(projectDir, rel, lock));
111
+ for (const rel of edited) log(` note: you edited ${rel} (updates keep your version)`);
112
+ for (const p of problems) log(` PROBLEM: ${p}`);
113
+ log(problems.length ? `${problems.length} problem(s)` : `OK: ${lock.packs.join(', ')}`);
114
+ return problems.length ? 1 : 0;
115
+ }