@thedecipherist/claude-packs 0.0.0-stage → 1.1.0

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