@thedecipherist/claude-packs 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (827) hide show
  1. package/CHANGELOG.md +680 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -2
  4. package/bin/claude-packs.mjs +204 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +92 -0
  7. package/configurations/full-stack/config.json +152 -0
  8. package/configurations/go/config.json +96 -0
  9. package/configurations/node/config.json +100 -0
  10. package/configurations/python/config.json +97 -0
  11. package/configurations/synth-creator/config.json +148 -0
  12. package/global/hooks/gnu-shell.mjs +131 -0
  13. package/global/hooks/lib/program-read.mjs +196 -0
  14. package/global/hooks/lib/program-writes.mjs +177 -0
  15. package/global/hooks/lib/shell-writes.mjs +293 -0
  16. package/global/hooks/shell-traps.mjs +79 -0
  17. package/global/hooks/version-pin-guard.mjs +280 -0
  18. package/global/skills/latest-versions/SKILL.md +49 -0
  19. package/lib/backup.mjs +34 -0
  20. package/lib/claude-md.mjs +58 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +86 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +115 -0
  26. package/lib/fix-plan.mjs +71 -0
  27. package/lib/git-tokens.mjs +64 -0
  28. package/lib/global.mjs +125 -0
  29. package/lib/hook-proof.mjs +28 -0
  30. package/lib/hook-timing.mjs +42 -0
  31. package/lib/hover-gate.mjs +29 -0
  32. package/lib/kit-lock.mjs +149 -0
  33. package/lib/layout.mjs +75 -0
  34. package/lib/logs.mjs +69 -0
  35. package/lib/manifest.mjs +24 -0
  36. package/lib/new.mjs +49 -0
  37. package/lib/once-only-report.mjs +54 -0
  38. package/lib/pack-manifest.mjs +20 -0
  39. package/lib/packs.mjs +107 -0
  40. package/lib/planner.mjs +59 -0
  41. package/lib/repeats.mjs +65 -0
  42. package/lib/repo-weight.mjs +28 -0
  43. package/lib/rules.mjs +40 -0
  44. package/lib/scan-detect.mjs +94 -0
  45. package/lib/scan.mjs +168 -0
  46. package/lib/selftest-cache.mjs +39 -0
  47. package/lib/settings.mjs +138 -0
  48. package/lib/shape-cli.mjs +76 -0
  49. package/lib/shape.mjs +4 -0
  50. package/lib/suggest.mjs +18 -0
  51. package/lib/switches.mjs +44 -0
  52. package/lib/symmetry-doctor.mjs +26 -0
  53. package/lib/sync.mjs +130 -0
  54. package/package.json +34 -4
  55. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  56. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  57. package/packs/ai/checks/model-id-from-config.json +28 -0
  58. package/packs/ai/checks/model-output-validated.json +26 -0
  59. package/packs/ai/pack.json +38 -0
  60. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  61. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  62. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  63. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  64. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  65. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  66. package/packs/audio-core/checks/keep-werror.json +12 -0
  67. package/packs/audio-core/checks/no-fast-math.json +14 -0
  68. package/packs/audio-core/checks/no-locale-change.json +12 -0
  69. package/packs/audio-core/checks/no-march-native.json +14 -0
  70. package/packs/audio-core/clang-tidy +28 -0
  71. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  72. package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
  73. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  74. package/packs/audio-core/pack.json +187 -0
  75. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  76. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  77. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  78. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  79. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  80. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  81. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  82. package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
  83. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  84. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  85. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  86. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  87. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  88. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  89. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  90. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  91. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  92. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  93. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  94. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  95. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  96. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  97. package/packs/audio-core/rules/output-limiter.md +23 -0
  98. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  99. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  100. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  101. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  102. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  103. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  104. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  105. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  106. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  107. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  108. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  109. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  110. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  111. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  112. package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
  113. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  114. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  115. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  116. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
  117. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  118. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  119. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  120. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  121. package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
  122. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  123. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  124. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  125. package/packs/audio-core/tools/tone-report.mjs +47 -0
  126. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  127. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  128. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  129. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  130. package/packs/base/checks/one-walk-per-template.json +23 -0
  131. package/packs/base/checks/stub-reports-success.json +31 -0
  132. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  133. package/packs/base/hooks/brief-guard.mjs +96 -0
  134. package/packs/base/hooks/code-shape.mjs +95 -0
  135. package/packs/base/hooks/dispatch-once.mjs +87 -0
  136. package/packs/base/hooks/file-size-guard.mjs +61 -0
  137. package/packs/base/hooks/folder-law.mjs +74 -0
  138. package/packs/base/hooks/git-guard.mjs +289 -0
  139. package/packs/base/hooks/lesson-checks.mjs +174 -0
  140. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  141. package/packs/base/hooks/lib/code-shapes.mjs +137 -0
  142. package/packs/base/hooks/lib/content.mjs +14 -0
  143. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  144. package/packs/base/hooks/lib/dup-index.mjs +199 -0
  145. package/packs/base/hooks/lib/dup.mjs +127 -0
  146. package/packs/base/hooks/lib/elements.mjs +87 -0
  147. package/packs/base/hooks/lib/event-log.mjs +67 -0
  148. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  149. package/packs/base/hooks/lib/git-adds.mjs +20 -0
  150. package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
  151. package/packs/base/hooks/lib/glob.mjs +33 -0
  152. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  153. package/packs/base/hooks/lib/house.py +77 -0
  154. package/packs/base/hooks/lib/languages.mjs +66 -0
  155. package/packs/base/hooks/lib/layout.mjs +54 -0
  156. package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
  157. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  158. package/packs/base/hooks/lib/program-read.mjs +196 -0
  159. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  160. package/packs/base/hooks/lib/project.mjs +81 -0
  161. package/packs/base/hooks/lib/prose.mjs +11 -0
  162. package/packs/base/hooks/lib/pushed.mjs +201 -0
  163. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  164. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  165. package/packs/base/hooks/lib/selftest.mjs +45 -0
  166. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  167. package/packs/base/hooks/lib/shape.mjs +294 -0
  168. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  169. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  170. package/packs/base/hooks/lib/turn.mjs +81 -0
  171. package/packs/base/hooks/lib/writes.mjs +71 -0
  172. package/packs/base/hooks/making-check.mjs +243 -0
  173. package/packs/base/hooks/making.mjs +106 -0
  174. package/packs/base/hooks/once-only.mjs +150 -0
  175. package/packs/base/hooks/placement-law.mjs +84 -0
  176. package/packs/base/hooks/repo-litter.mjs +75 -0
  177. package/packs/base/hooks/reuse-hint.mjs +75 -0
  178. package/packs/base/hooks/run-hooks.mjs +132 -0
  179. package/packs/base/hooks/secrets-guard.mjs +99 -0
  180. package/packs/base/hooks/shape-law.mjs +133 -0
  181. package/packs/base/hooks/suite-once.mjs +80 -0
  182. package/packs/base/hooks/template-law.mjs +148 -0
  183. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  184. package/packs/base/pack.json +214 -0
  185. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  186. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  187. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  188. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  189. package/packs/base/rules/finished-work-lands.md +17 -0
  190. package/packs/base/rules/git-right-first-time.md +19 -0
  191. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  192. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  193. package/packs/base/rules/making-line-first.md +14 -0
  194. package/packs/base/rules/open-items-tagged.md +17 -0
  195. package/packs/base/rules/plan-and-logs.md +22 -0
  196. package/packs/base/rules/references-use-anchors.md +19 -0
  197. package/packs/base/rules/refusals-are-judged.md +18 -0
  198. package/packs/base/rules/reuse-before-writing.md +15 -0
  199. package/packs/base/rules/secrets-never-committed.md +16 -0
  200. package/packs/base/rules/skill-writing.md +23 -0
  201. package/packs/base/rules/split-before-the-limit.md +14 -0
  202. package/packs/base/rules/subagent-dispatch.md +26 -0
  203. package/packs/base/rules/test-only-what-changed.md +17 -0
  204. package/packs/base/rules/test-the-template-once.md +22 -0
  205. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  206. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  207. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  208. package/packs/base/skills/land-branch/SKILL.md +24 -0
  209. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  210. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  211. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  212. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  213. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  214. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  215. package/packs/base/tools/shape-symmetry.mjs +178 -0
  216. package/packs/base/tools/test-verdict.mjs +75 -0
  217. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  218. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  219. package/packs/ci/pack.json +21 -0
  220. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  221. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  222. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  223. package/packs/content-site/checks/img-has-size.json +20 -0
  224. package/packs/content-site/checks/page-head-complete.json +26 -0
  225. package/packs/content-site/pack.json +22 -0
  226. package/packs/content-site/rules/content-has-one-source.md +23 -0
  227. package/packs/content-site/rules/media-is-sized.md +24 -0
  228. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  229. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  230. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  231. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  232. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  233. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  234. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  235. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  236. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  237. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  238. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  239. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  240. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  241. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  242. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  243. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  244. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  245. package/packs/cpp/pack.json +22 -0
  246. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  247. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  248. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  249. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  250. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  251. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  252. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  253. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  254. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  255. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  256. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  257. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  258. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  259. package/packs/docker/checks/compose-image-pinned.json +14 -0
  260. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  261. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  262. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  263. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  264. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  265. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  266. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  267. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  268. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  269. package/packs/docker/checks/docker-exec-form.json +16 -0
  270. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  271. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  272. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  273. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  274. package/packs/docker/checks/health-checks-nothing.json +25 -0
  275. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  276. package/packs/docker/checks/no-latest-base.json +13 -0
  277. package/packs/docker/checks/no-secret-in-image.json +13 -0
  278. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  279. package/packs/docker/checks/node-env-before-install.json +13 -0
  280. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  281. package/packs/docker/pack.json +46 -0
  282. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  283. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  284. package/packs/docker/rules/docker-exec-form.md +19 -0
  285. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  286. package/packs/docker/rules/dockerfile-production.md +25 -0
  287. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  288. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  289. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  290. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  291. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  292. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  293. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  294. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  295. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  296. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  297. package/packs/dynamic-data/pack.json +23 -0
  298. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  299. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  300. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  301. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  302. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  303. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  304. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  305. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  306. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  307. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  308. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  309. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  310. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  311. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  312. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  313. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  314. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  315. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  316. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  317. package/packs/fastify/checks/one-error-body.json +26 -0
  318. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  319. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  320. package/packs/fastify/checks/sec-headers-present.json +28 -0
  321. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  322. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  323. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  324. package/packs/fastify/pack.json +41 -0
  325. package/packs/fastify/rules/credentials-and-input.md +23 -0
  326. package/packs/fastify/rules/security-one-switch.md +28 -0
  327. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  328. package/packs/fastify/rules/tests-use-inject.md +13 -0
  329. package/packs/fastify/rules/three-layers.md +20 -0
  330. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  331. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  332. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  333. package/packs/full-stack/pack.json +76 -0
  334. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  335. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  336. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  337. package/packs/go/pack.json +21 -0
  338. package/packs/go/rules/go-errors-are-handled.md +22 -0
  339. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  340. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  341. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  342. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  343. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  344. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  345. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  346. package/packs/gpu-painter/pack.json +91 -0
  347. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  348. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  349. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  350. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  351. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  352. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  353. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  354. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  355. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  356. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  357. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  358. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  359. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  360. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  361. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  362. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  363. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  364. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  365. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  366. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  367. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  368. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  369. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  370. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  371. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  372. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  373. package/packs/hook-proof/pack.json +38 -0
  374. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  375. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  376. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  377. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  378. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  379. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  380. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  381. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  382. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  383. package/packs/host-agent/pack.json +22 -0
  384. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  385. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  386. package/packs/html-ui/checks/external-link-rel.json +21 -0
  387. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  388. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  389. package/packs/html-ui/checks/no-inline-style.json +23 -0
  390. package/packs/html-ui/pack.json +33 -0
  391. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  392. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  393. package/packs/http-api/checks/api-path-versioned.json +31 -0
  394. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  395. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  396. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  397. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  398. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  399. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  400. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  401. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  402. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  403. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  404. package/packs/http-api/checks/guard-fails-open.json +28 -0
  405. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  406. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  407. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  408. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  409. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  410. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  411. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  412. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  413. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  414. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  415. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  416. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  417. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  418. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  419. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  420. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  421. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  422. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  423. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  424. package/packs/http-api/pack.json +45 -0
  425. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  426. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  427. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  428. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  429. package/packs/http-api/rules/health-and-correlation.md +17 -0
  430. package/packs/http-api/rules/one-error-contract.md +17 -0
  431. package/packs/http-api/rules/routes-are-thin.md +17 -0
  432. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  433. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  434. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  435. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  436. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  437. package/packs/instrument-shell/pack.json +22 -0
  438. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  439. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  440. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  441. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  442. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  443. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  444. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  445. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  446. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  447. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  448. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  449. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  450. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  451. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  452. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  453. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  454. package/packs/js-ts/checks/no-any.json +27 -0
  455. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  456. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  457. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  458. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  459. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  460. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  461. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  462. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  463. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  464. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  465. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  466. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  467. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  468. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  469. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  470. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  471. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  472. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  473. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  474. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  475. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  476. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  477. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  478. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  479. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  480. package/packs/js-ts/pack.json +84 -0
  481. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  482. package/packs/js-ts/rules/env-through-config.md +36 -0
  483. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  484. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  485. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  486. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  487. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  488. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  489. package/packs/js-ts/rules/no-any.md +34 -0
  490. package/packs/js-ts/rules/no-console.md +30 -0
  491. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  492. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  493. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  494. package/packs/js-ts/rules/one-logger.md +24 -0
  495. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  496. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  497. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  498. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  499. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  500. package/packs/js-ts/rules/small-functions.md +20 -0
  501. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  502. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  503. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  504. package/packs/js-ts/rules/typed-exports.md +27 -0
  505. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  506. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  507. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  508. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  509. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  510. package/packs/midi/checks/midi-realtime-first.json +27 -0
  511. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  512. package/packs/midi/checks/position-not-integrated.json +25 -0
  513. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  514. package/packs/midi/checks/sysex-length-literal.json +23 -0
  515. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  516. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  517. package/packs/midi/pack.json +22 -0
  518. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  519. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  520. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  521. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  522. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  523. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  524. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  525. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  526. package/packs/mongo/checks/bulk-over-many.json +28 -0
  527. package/packs/mongo/checks/created-at-immutable.json +25 -0
  528. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  529. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  530. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  531. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  532. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  533. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  534. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  535. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  536. package/packs/mongo/checks/no-find-reads.json +26 -0
  537. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  538. package/packs/mongo/checks/no-mongoose.json +23 -0
  539. package/packs/mongo/checks/no-negation-delete.json +26 -0
  540. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  541. package/packs/mongo/checks/objectid-equals.json +26 -0
  542. package/packs/mongo/checks/update-then-reread.json +24 -0
  543. package/packs/mongo/checks/watch-not-poll.json +25 -0
  544. package/packs/mongo/pack.json +52 -0
  545. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  546. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  547. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  548. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  549. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  550. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  551. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  552. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  553. package/packs/multi-tenant/pack.json +22 -0
  554. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  555. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  556. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  557. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  558. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  559. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  560. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  561. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  562. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  563. package/packs/nginx/pack.json +43 -0
  564. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  565. package/packs/nginx/rules/nginx-production.md +26 -0
  566. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  567. package/packs/python/checks/py-no-bare-except.json +18 -0
  568. package/packs/python/checks/py-no-eval.json +18 -0
  569. package/packs/python/checks/py-no-mutable-default.json +18 -0
  570. package/packs/python/checks/py-no-shell-true.json +18 -0
  571. package/packs/python/checks/py-no-star-import.json +18 -0
  572. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  573. package/packs/python/checks/py-none-by-is.json +18 -0
  574. package/packs/python/checks/py-open-with-with.json +18 -0
  575. package/packs/python/checks/py-requests-timeout.json +18 -0
  576. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  577. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  578. package/packs/python/pack.json +26 -0
  579. package/packs/python/rules/python-errors-and-resources.md +26 -0
  580. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  581. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  582. package/packs/react/checks/dialog-needs-role.json +28 -0
  583. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  584. package/packs/react/checks/icon-button-needs-name.json +24 -0
  585. package/packs/react/checks/label-needs-a-field.json +24 -0
  586. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  587. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  588. package/packs/react/pack.json +20 -0
  589. package/packs/react/rules/react-accessible-controls.md +20 -0
  590. package/packs/react/rules/react-state-and-effects.md +23 -0
  591. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  592. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  593. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  594. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  595. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  596. package/packs/reverse-engineering/pack.json +35 -0
  597. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  598. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  599. package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
  600. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  601. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  602. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  603. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  604. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  605. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  606. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  607. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
  608. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  609. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  610. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  611. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  612. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  613. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  614. package/packs/shell/checks/sh-mktemp.json +18 -0
  615. package/packs/shell/checks/sh-no-backticks.json +18 -0
  616. package/packs/shell/checks/sh-no-eval.json +18 -0
  617. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  618. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  619. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  620. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  621. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  622. package/packs/shell/checks/sh-strict-mode.json +20 -0
  623. package/packs/shell/pack.json +28 -0
  624. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  625. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  626. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  627. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  628. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  629. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  630. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  631. package/packs/shell-hooks/pack.json +36 -0
  632. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  633. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  634. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  635. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  636. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  637. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  638. package/packs/sound-design/pack.json +22 -0
  639. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  640. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  641. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  642. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  643. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  644. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  645. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  646. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  647. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  648. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  649. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  650. package/packs/stellix/checks/one-seeded-random.json +16 -0
  651. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  652. package/packs/stellix/checks/world-no-libm.json +15 -0
  653. package/packs/stellix/checks/world-no-unordered.json +10 -0
  654. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  655. package/packs/stellix/pack.json +61 -0
  656. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  657. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  658. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  659. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  660. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  661. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  662. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  663. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  664. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  665. package/packs/stored-text/pack.json +41 -0
  666. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  667. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  668. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  669. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  670. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  671. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  672. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  673. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  674. package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
  675. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  676. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  677. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  678. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  679. package/packs/synth-creator/pack.json +136 -0
  680. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  681. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  682. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  683. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  684. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  685. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  686. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  687. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
  688. package/packs/synth-creator/tools/freeze.mjs +36 -0
  689. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  690. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  691. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  692. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  693. package/packs/tui/checks/tui-width-is-state.json +33 -0
  694. package/packs/tui/pack.json +44 -0
  695. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  696. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  697. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  698. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  699. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  700. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  701. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  702. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  703. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  704. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  705. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  706. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  707. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  708. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  709. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  710. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  711. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  712. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  713. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  714. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  715. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  716. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  717. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  718. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  719. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  720. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  721. package/packs/ui-craft/pack.json +43 -0
  722. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  723. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  724. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  725. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  726. package/packs/ui-craft/rules/interface-copy.md +26 -0
  727. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  728. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  729. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  730. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  731. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  732. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  733. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  734. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  735. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  736. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  737. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  738. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  739. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  740. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  741. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  742. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  743. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  744. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  745. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  746. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  747. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  748. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  749. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  750. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  751. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  752. package/packs/web/checks/client-no-api-key.json +29 -0
  753. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  754. package/packs/web/checks/client-no-server-import.json +31 -0
  755. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  756. package/packs/web/checks/css-no-inline-style.json +30 -0
  757. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  758. package/packs/web/checks/key-from-index.json +23 -0
  759. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  760. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  761. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  762. package/packs/web/checks/rcss-button-cursor.json +30 -0
  763. package/packs/web/checks/rcss-no-100vw.json +21 -0
  764. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  765. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  766. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  767. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  768. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  769. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  770. package/packs/web/pack.json +73 -0
  771. package/packs/web/rules/accessibility.md +26 -0
  772. package/packs/web/rules/client-boundaries.md +23 -0
  773. package/packs/web/rules/production-build.md +22 -0
  774. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  775. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  776. package/packs/web/rules/tailwind-v4.md +23 -0
  777. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  778. package/packs/web/rules/web-performance.md +21 -0
  779. package/packs/web/skills/design-direction/SKILL.md +30 -0
  780. package/packs/web/skills/design-review/SKILL.md +28 -0
  781. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  782. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  783. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  784. package/packs/websocket/checks/no-token-in-query.json +26 -0
  785. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  786. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  787. package/packs/websocket/pack.json +21 -0
  788. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  789. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  790. package/packs/writing/checks/no-copula-dodging.json +18 -0
  791. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  792. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  793. package/packs/writing/checks/no-participial-opener.json +18 -0
  794. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  795. package/packs/writing/checks/no-slop-words.json +18 -0
  796. package/packs/writing/checks/no-stock-connectors.json +18 -0
  797. package/packs/writing/checks/no-time-or-date.json +27 -0
  798. package/packs/writing/checks/no-trailing-ing.json +18 -0
  799. package/packs/writing/checks/no-vague-attribution.json +18 -0
  800. package/packs/writing/pack.json +43 -0
  801. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  802. package/packs/writing/rules/cut-the-padding.md +20 -0
  803. package/packs/writing/rules/direct-voice.md +22 -0
  804. package/packs/writing/rules/no-cargo-cult.md +16 -0
  805. package/packs/writing/rules/no-scaffolding.md +19 -0
  806. package/packs/writing/rules/no-stock-language.md +27 -0
  807. package/packs/writing/rules/no-time-or-date.md +18 -0
  808. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  809. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  810. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  811. package/packs/writing/skills/humanize/SKILL.md +28 -0
  812. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  813. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  814. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  815. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  816. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  817. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  818. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  819. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  820. package/packs/zod/hooks/schemas-only.mjs +53 -0
  821. package/packs/zod/pack.json +44 -0
  822. package/packs/zod/rules/env-shapes-only.md +14 -0
  823. package/packs/zod/rules/zod-4-schemas.md +14 -0
  824. package/profiles/stellix.layout.json +315 -0
  825. package/profiles/sub37.layout.json +247 -0
  826. package/prompts/scan-review.md +40 -0
  827. package/prompts/survey.md +82 -0
package/lib/logs.mjs ADDED
@@ -0,0 +1,69 @@
1
+ // claude-packs logs: which hooks earn their place. Reads every log, rotated ones included, and joins each refusal to its verdict.
2
+ // logs per hook: calls, refusals, right, wrong, not judged, crashes, time
3
+ // logs refusals [--hook <h>] [--open] every refusal with its reason (--open: not judged yet)
4
+ // logs mark <id> right|wrong "<why>" the verdict, added to .claude/logs/verdicts.jsonl; nothing is rewritten
5
+ import { appendFileSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+
8
+ const DIR = join('.claude', 'logs');
9
+
10
+ function linesOf(dir, name) {
11
+ const logs = join(dir, DIR);
12
+ let files = [];
13
+ try { files = readdirSync(logs).filter((f) => f === `${name}.jsonl` || (f.startsWith(`${name}-`) && f.endsWith('.jsonl'))).sort(); } catch { return []; }
14
+ return files.flatMap((f) => readFileSync(join(logs, f), 'utf8').split('\n').filter(Boolean).flatMap((l) => { try { return [JSON.parse(l)]; } catch { return []; } }));
15
+ }
16
+
17
+ const verdictsOf = (dir) => new Map(linesOf(dir, 'verdicts').map((v) => [v.id, v]));
18
+ const middle = (list) => [...list].sort((a, b) => a - b)[Math.floor(list.length / 2)] ?? 0;
19
+
20
+ // A refusal followed by a pass on the same file in the same session: the writer changed something and went on.
21
+ function passedAfter(refusal, events) {
22
+ return events.some((e) => e.t > refusal.t && e.session === refusal.session && e.file && e.file === refusal.file && e.hooks.some((h) => h.hook === refusal.hook && h.code === 0));
23
+ }
24
+
25
+ export function logsSummary(dir) {
26
+ const events = linesOf(dir, 'events');
27
+ const refusals = linesOf(dir, 'refusals');
28
+ const verdicts = verdictsOf(dir);
29
+ const rows = new Map();
30
+ const row = (hook) => { if (!rows.has(hook)) rows.set(hook, { hook, calls: 0, refused: 0, crashed: 0, right: 0, wrong: 0, open: 0, passedAfter: 0, ms: [] }); return rows.get(hook); };
31
+ for (const e of events) for (const h of e.hooks ?? []) { const r = row(h.hook); r.calls += 1; r.ms.push(h.ms); }
32
+ for (const f of refusals) {
33
+ const r = row(f.hook);
34
+ if (f.code !== 2) { r.crashed += 1; continue; }
35
+ r.refused += 1;
36
+ const v = verdicts.get(f.id)?.verdict;
37
+ if (v === 'right') r.right += 1; else if (v === 'wrong') r.wrong += 1; else r.open += 1;
38
+ if (passedAfter(f, events)) r.passedAfter += 1;
39
+ }
40
+ return { first: events[0]?.t, last: events.at(-1)?.t, calls: events.length, rows: [...rows.values()].sort((a, b) => b.refused - a.refused || b.calls - a.calls) };
41
+ }
42
+
43
+ export function logsCommand(dir, rest, log = console.log) {
44
+ const [sub] = rest;
45
+ if (sub === 'mark') {
46
+ const [, id, verdict, ...why] = rest;
47
+ if (!id || !['right', 'wrong'].includes(verdict) || !why.length) throw new Error('use `logs mark <id> right|wrong "<why>"`');
48
+ if (!linesOf(dir, 'refusals').some((r) => r.id === id)) throw new Error(`no refusal ${id}: \`logs refusals\` lists them`);
49
+ mkdirSync(join(dir, DIR), { recursive: true });
50
+ appendFileSync(join(dir, DIR, 'verdicts.jsonl'), `${JSON.stringify({ id, verdict, why: why.join(' '), t: new Date().toISOString() })}\n`);
51
+ log(`${id}: ${verdict}`);
52
+ return 0;
53
+ }
54
+ if (sub === 'refusals') {
55
+ const hook = rest.includes('--hook') ? rest[rest.indexOf('--hook') + 1] : null;
56
+ const verdicts = verdictsOf(dir);
57
+ const list = linesOf(dir, 'refusals').filter((r) => (!hook || r.hook === hook) && (!rest.includes('--open') || !verdicts.has(r.id)));
58
+ for (const r of list) log(`${r.id} ${r.t} ${r.hook} ${verdicts.get(r.id)?.verdict ?? 'not judged'}\n ${r.file ?? r.command ?? r.tool ?? '-'}\n ${(r.reason ?? '').split('\n')[0]}`);
59
+ log(`${list.length} refusal(s)`);
60
+ return 0;
61
+ }
62
+ const s = logsSummary(dir);
63
+ if (!s.calls) { log('no calls logged yet (.claude/logs/ fills as the hooks run)'); return 0; }
64
+ log(`${s.calls} calls, ${s.first} to ${s.last}`);
65
+ log(`${'hook'.padEnd(24)}${'calls'.padStart(7)}${'refused'.padStart(8)}${'right'.padStart(6)}${'wrong'.padStart(6)}${'open'.padStart(6)}${'fixed'.padStart(6)}${'crash'.padStart(6)}${'ms'.padStart(6)}`);
66
+ for (const r of s.rows) log(`${r.hook.padEnd(24)}${String(r.calls).padStart(7)}${String(r.refused).padStart(8)}${String(r.right).padStart(6)}${String(r.wrong).padStart(6)}${String(r.open).padStart(6)}${String(r.passedAfter).padStart(6)}${String(r.crashed).padStart(6)}${String(middle(r.ms)).padStart(6)}`);
67
+ log('fixed: the same file passed the same hook later in that session. Judge the open ones: `logs refusals --open`, then `logs mark <id> right|wrong "<why>"`.');
68
+ return 0;
69
+ }
@@ -0,0 +1,24 @@
1
+ // Tracks every file the packs installed, with its hash, so updates never overwrite a file
2
+ // The user changed and files removed from a pack are cleaned up.
3
+ import { createHash } from 'node:crypto';
4
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ export const LOCK = 'claude-packs.lock.json';
8
+
9
+ export const hashFile = (path) => createHash('sha256').update(readFileSync(path)).digest('hex');
10
+
11
+ export function readLock(claudeDir) {
12
+ const p = join(claudeDir, LOCK);
13
+ return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : { packs: [], files: {} };
14
+ }
15
+
16
+ export function writeLock(claudeDir, lock) {
17
+ writeFileSync(join(claudeDir, LOCK), JSON.stringify(lock, null, 2) + '\n');
18
+ }
19
+
20
+ // A managed file is "modified" when it exists and no longer matches the hash we wrote.
21
+ export function isModified(projectDir, rel, lock) {
22
+ const abs = join(projectDir, rel);
23
+ return existsSync(abs) && lock.files[rel] !== undefined && hashFile(abs) !== lock.files[rel];
24
+ }
package/lib/new.mjs ADDED
@@ -0,0 +1,49 @@
1
+ // `claude-packs new <name>`: a new project on the house structure, so the rules fire from the first file (it was the
2
+ // global new-project skill; it is a kit command now, and the global part stays at three pieces).
3
+ // - the folder, the house files (CLAUDE.md, build.md, bugs.md, workflow-bugs.md, docs/versions.md, .gitignore),
4
+ // - a git repo: the scaffold is the first commit on main, and work goes on from a `work/` branch (the house branch flow),
5
+ // - the kit installed into it (`init` with the packs named, or a profile), then doctor.
6
+ // A synth or effects unit is made by synthkit's new_synth.py first; `init` (or `new` on an empty folder) follows on it.
7
+ import { execFileSync } from 'node:child_process';
8
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { doctor } from './doctor.mjs';
11
+ import { sync } from './sync.mjs';
12
+ import { ensureShape } from './shape-cli.mjs';
13
+
14
+ const today = () => new Date().toISOString().slice(0, 10);
15
+
16
+ function files(name) {
17
+ return {
18
+ 'CLAUDE.md': `# ${name}\n\nRead \`build.md\` at the start of every session; nothing is worked on unless it is IN PROGRESS there. Every bug is logged in \`bugs.md\` (and every workflow fault in \`workflow-bugs.md\`) before it is fixed. One branch per step (the \`build-step\` skill), never a commit on main.\n`,
19
+ 'build.md': `# ${name}: the build\n\n## Rules for this file\n\n- A step is ticked only when its "done when" was measured or seen in the opened app.\n- Every step is built on its own branch and merged when done; its start and finish are in the log.\n\n## Where we are\n\nStarted ${today()}.\n\n## In progress\n\n## Steps\n\n## Log\n`,
20
+ 'bugs.md': `# ${name}: bugs\n\nLogged first, fixed second. A fixed bug moves to Fixed with its cause, the fix, the date and the gate that catches it.\n\n## Open\n\n## Fixed\n`,
21
+ 'workflow-bugs.md': `# ${name}: workflow bugs\n\nA fault in the workflow (a hook, a tool, a test run) is logged here before it is fixed.\n\n## Open\n\n## Fixed\n`,
22
+ 'docs/versions.md': '| name | latest stable | checked | source | used | decision |\n|---|---|---|---|---|---|\n',
23
+ '.gitignore': 'build/\nout/\n.DS_Store\n',
24
+ };
25
+ }
26
+
27
+ export async function newProject(parent, name, { packs = ['audio-core'], seedLayout = null, config = null, log = console.log } = {}) {
28
+ if (!name || !/^[\w.-]+$/.test(name)) throw new Error('name the project: `npx --no-install @thedecipherist/claude-packs new <name>` (letters, digits, . _ -)');
29
+ const dir = join(parent, name);
30
+ if (existsSync(dir) && readdirSync(dir).length) throw new Error(`${dir} already exists and is not empty; use \`npx --no-install @thedecipherist/claude-packs init\` there`);
31
+ mkdirSync(join(dir, 'docs'), { recursive: true });
32
+ for (const [rel, text] of Object.entries(files(name))) writeFileSync(join(dir, rel), text);
33
+ const git = (...a) => execFileSync('git', a, { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
34
+ git('init', '-q', '-b', 'main');
35
+ await sync(dir, packs, { mkdirs: true, seedLayout, config, log });
36
+ ensureShape(dir); // the shape law: every concept is written down as ONE or MANY before it is built
37
+ try {
38
+ // by name: the house files, .claude/, and every file the kit installed elsewhere (nested skills, .clang-tidy)
39
+ const installed = Object.keys(JSON.parse(readFileSync(join(dir, '.claude', 'claude-packs.lock.json'), 'utf8')).files);
40
+ git('add', '--', ...Object.keys(files(name)), 'design/shape.json', '.claude', ...installed, ...(existsSync(join(dir, '.clang-tidy')) ? ['.clang-tidy'] : []));
41
+ git('commit', '-q', '-m', `${name}: the house structure and claude-packs`);
42
+ git('switch', '-q', '-c', 'work/start');
43
+ log(` git: the scaffold is the first commit on main; you are on work/start`);
44
+ } catch (e) {
45
+ log(` git: the scaffold is not committed (${String(e.stderr ?? e.message).trim().split('\n')[0]}); commit it, then work on a work/ branch`);
46
+ }
47
+ log(`New project: ${dir}`);
48
+ return { dir, code: await doctor(dir, log) };
49
+ }
@@ -0,0 +1,54 @@
1
+ // What doctor prints for the making and once-only hooks (0.9.0 items 6 and 9), and `claude-packs dup scan`.
2
+ // onceOnlyDoctor(projectDir, log) -> [problem] the making text's cost per turn; the repo's duplicate count, which can
3
+ // only go down: the lowest count seen is kept in .claude/state/duplicates.json and a higher one is a problem.
4
+ // dupScan(dir, log) -> exit code every duplicate, listed by file (both sides of each), and the count.
5
+ import { spawnSync } from 'node:child_process';
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { findJscpd, repoClones } from '../packs/base/hooks/lib/dup.mjs';
9
+ import { readShape } from '../packs/base/hooks/lib/shape.mjs';
10
+
11
+ const layoutOf = (dir) => { try { return JSON.parse(readFileSync(join(dir, '.claude', 'layout.json'), 'utf8')); } catch { return {}; } };
12
+
13
+ // The project's detector, else the kit's own, for this process and every selftest it starts.
14
+ export function useJscpd(dir) {
15
+ const found = findJscpd([dir]);
16
+ if (found) process.env.CLAUDE_PACKS_JSCPD ??= found;
17
+ return found;
18
+ }
19
+
20
+ export function onceOnlyDoctor(projectDir, log = console.log) {
21
+ const problems = [];
22
+ const hooks = join(projectDir, '.claude', 'hooks');
23
+ if (existsSync(join(hooks, 'making.mjs'))) {
24
+ const r = spawnSync(process.execPath, [join(hooks, 'making.mjs'), '--cost'], { encoding: 'utf8', env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir } });
25
+ log(` ${(r.stdout || `making: --cost failed (${r.stderr.split('\n')[0]})`).trim()}`);
26
+ }
27
+ if (!existsSync(join(hooks, 'once-only.mjs'))) return problems;
28
+ if (!useJscpd(projectDir)) return [`once-only has no detector: jscpd not found (an optional dependency of the kit; npm install it, or set CLAUDE_PACKS_JSCPD)`];
29
+ const r = repoClones(projectDir, { layout: layoutOf(projectDir), shape: readShape(projectDir) });
30
+ if (r.error) return [`once-only: ${r.error}`];
31
+ const n = r.clones.length;
32
+ const file = join(projectDir, '.claude', 'state', 'duplicates.json');
33
+ let st = null;
34
+ try { st = JSON.parse(readFileSync(file, 'utf8')); } catch { /* first count */ }
35
+ log(` duplicates: ${n} (jscpd, ${r.sources} sources, ${r.ms} ms; \`npx --no-install @thedecipherist/claude-packs dup scan\` lists them)${st ? `, lowest seen ${st.lowest}` : ''}`);
36
+ if (st && n > st.lowest) problems.push(`duplicates went up from ${st.lowest} to ${n}: the count only goes down (\`npx --no-install @thedecipherist/claude-packs dup scan\` lists them)`);
37
+ if (!st || n < st.lowest) {
38
+ mkdirSync(join(projectDir, '.claude', 'state'), { recursive: true });
39
+ writeFileSync(file, `${JSON.stringify({ lowest: n, at: new Date().toISOString() }, null, 2)}\n`);
40
+ }
41
+ return problems;
42
+ }
43
+
44
+ export function dupScan(dir, log = console.log) {
45
+ if (!useJscpd(dir)) { log('jscpd not found (an optional dependency of the kit; npm install it, or set CLAUDE_PACKS_JSCPD)'); return 2; }
46
+ const r = repoClones(dir, { layout: layoutOf(dir), shape: readShape(dir) });
47
+ if (r.error) { log(r.error); return 2; }
48
+ const byFile = new Map();
49
+ const add = (s, o, c) => { if (!byFile.has(s.file)) byFile.set(s.file, []); byFile.get(s.file).push(` ${s.start}-${s.end} same as ${o.file}:${o.start}-${o.end} (${c.lines} lines, ${c.tokens} tokens)`); };
50
+ for (const c of r.clones) { add(c.a, c.b, c); if (c.b.file !== c.a.file) add(c.b, c.a, c); }
51
+ for (const f of [...byFile.keys()].sort()) { log(f); for (const l of byFile.get(f).sort((x, y) => parseInt(x, 10) - parseInt(y, 10))) log(l); }
52
+ log(`${r.clones.length} duplicate(s) in ${byFile.size} file(s) (${r.sources} sources scanned; both sides inside copy trees left out)`);
53
+ return 0;
54
+ }
@@ -0,0 +1,20 @@
1
+ // A pack.json is a manifest like a package.json (0.19.0: it says stock or custom, has a git url, and who made it). A custom pack comes
2
+ // from somebody else's repository, so these fields are how a project knows what it is running: what it is, who made it, where it lives.
3
+ const TYPES = ['stock', 'custom'];
4
+ const SEMVER = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
5
+ const GIT_URL = /^(?:https?:\/\/|ssh:\/\/|file:\/\/|git@)\S+$/;
6
+
7
+ const FIELDS = {
8
+ version: (p) => SEMVER.test(p.version ?? '') || 'needs a version like 1.2.0',
9
+ description: (p) => Boolean(p.description?.trim()) || 'needs a description',
10
+ type: (p) => TYPES.includes(p.type) || `needs a type: ${TYPES.join(' or ')}`,
11
+ author: (p) => Boolean(String(p.author?.name ?? p.author ?? '').trim()) || 'needs an author: who made it',
12
+ license: (p) => Boolean(p.license?.trim()) || 'needs a license',
13
+ repository: (p) => GIT_URL.test(p.repository?.url ?? '') || 'needs repository.url: the git url it lives at',
14
+ };
15
+
16
+ // The problems of one manifest; `folder` is where it was found, and its name must say the same.
17
+ export function manifestProblems(pack, folder = pack.name) {
18
+ const said = Object.values(FIELDS).map((check) => check(pack)).filter((r) => r !== true).map((r) => `pack ${folder} ${r}`);
19
+ return pack.name === folder ? said : [`pack ${folder} says its name is ${pack.name}`, ...said];
20
+ }
package/lib/packs.mjs ADDED
@@ -0,0 +1,107 @@
1
+ // Reads the packs shipped in this npm package and plans which file goes where in a project.
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ export const PACKS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'packs');
7
+ const CLAUDE_DIRS = ['rules', 'checks', 'hooks', 'tools'];
8
+
9
+ // Packs from a team's own repository (lib/custom-packs.mjs): name -> the folder it was fetched to.
10
+ const CUSTOM = new Map();
11
+ export const registerPack = (name, folder) => CUSTOM.set(name, folder);
12
+ export const customPacks = () => [...CUSTOM.keys()];
13
+ export const packRoot = (name) => CUSTOM.get(name) ?? join(PACKS_DIR, name);
14
+
15
+ export function availablePacks() {
16
+ return [...readdirSync(PACKS_DIR).filter((d) => existsSync(join(PACKS_DIR, d, 'pack.json'))), ...CUSTOM.keys()];
17
+ }
18
+
19
+ export function readPack(name) {
20
+ const file = join(packRoot(name), 'pack.json');
21
+ if (!existsSync(file)) throw new Error(`unknown pack "${name}" (available: ${availablePacks().join(', ')})`);
22
+ const pack = JSON.parse(readFileSync(file, 'utf8'));
23
+ if (!CUSTOM.has(name)) return pack;
24
+ // A hook runs code on every tool call, so somebody else's hook is off until the project names it in "enable".
25
+ const hooks = join(packRoot(name), 'hooks');
26
+ return { ...pack, optIn: [...(pack.optIn ?? []), ...(existsSync(hooks) ? readdirSync(hooks).filter((f) => /\.(mjs|py)$/.test(f)).map((f) => `hooks/${f}`) : [])] };
27
+ }
28
+
29
+ // Python's byte-code caches are never pack files (a selftest run inside the package makes them).
30
+ const JUNK = /^(__pycache__|\.DS_Store)$|\.pyc$/;
31
+
32
+ function walk(dir) {
33
+ if (!existsSync(dir)) return [];
34
+ return readdirSync(dir).filter((entry) => !JUNK.test(entry)).flatMap((entry) => {
35
+ const abs = join(dir, entry);
36
+ return statSync(abs).isDirectory() ? walk(abs) : [abs];
37
+ });
38
+ }
39
+
40
+ // Returns [{ src, rel }] where rel is the destination relative to the project root.
41
+ // Skills go to the pack's own folder (lazy-loaded nested skills) when it declares one.
42
+ export function planFiles(name, layout) {
43
+ const pack = readPack(name);
44
+ const root = packRoot(name);
45
+ const plan = [];
46
+ // A rule that states a hook's law ("# hook: <file>" in its frontmatter) goes where its hook goes: held or left out with it (0.10.0).
47
+ const ruleHook = (src) => readFileSync(src, 'utf8').match(/^---\n(?:[^\n]*\n)*?# hook:\s*(\S+)\s*\n(?:[^\n]*\n)*?---/)?.[1];
48
+ const off = (src) => {
49
+ const rel = relative(root, src);
50
+ if (isDisabled(layout, rel)) return true;
51
+ const hook = /^rules[\\/][^\\/]+\.md$/.test(rel) && ruleHook(src);
52
+ return Boolean(hook) && isDisabled(layout, `hooks/${hook}`);
53
+ };
54
+ for (const dir of CLAUDE_DIRS) {
55
+ for (const src of walk(join(root, dir))) if (!off(src)) plan.push({ src, rel: join('.claude', relative(root, src)) });
56
+ }
57
+ const skillFolder = pack.skillsIn && layout.folders[pack.skillsIn];
58
+ const skillBase = skillFolder ? join(skillFolder.path, '.claude') : '.claude';
59
+ for (const src of walk(join(root, 'skills'))) if (!off(src)) plan.push({ src, rel: join(skillBase, relative(root, src)) });
60
+ const tidy = join(root, 'clang-tidy');
61
+ if (existsSync(tidy)) plan.push({ src: tidy, rel: '.clang-tidy', userOwned: true });
62
+ return plan;
63
+ }
64
+
65
+ // Every hook script any pack ships (node .mjs or python3 .py), by file name. settings.json entries
66
+ // that run one of these are the package's; every other hook entry is the project's own.
67
+ export function kitHookScripts() {
68
+ const out = new Set();
69
+ for (const name of availablePacks()) {
70
+ const dir = join(PACKS_DIR, name, 'hooks');
71
+ if (existsSync(dir)) for (const f of readdirSync(dir)) if (/\.(mjs|py)$/.test(f)) out.add(f);
72
+ }
73
+ return out;
74
+ }
75
+
76
+ // layout.disable lists pack items a project switches off because it already has its own:
77
+ // "checks/one-seeded-random.json", "hooks/no-shipped-images.mjs", "rules/x.md", "skills/gpu-capture".
78
+ export function isDisabled(layout, packRel) {
79
+ const p = packRel.replace(/\\/g, '/');
80
+ return (layout.disable ?? []).some((d) => p === d || p.startsWith(`${d.replace(/\/$/, '')}/`));
81
+ }
82
+
83
+ // A preset names a stack; `init node-api` installs exactly the packs that stack needs (v7 additions 0.11.0, v8 0.12.0). The
84
+ // configuration is then the smallest one that takes those packs (lib/configs.mjs inferConfig): `node` for the node
85
+ // presets, `synth-creator` for synth.
86
+ export const PRESETS = {
87
+ 'node-api': ['js-ts', 'http-api', 'mongo', 'fastify', 'ai', 'docker'],
88
+ 'node-ui': ['js-ts', 'http-api', 'mongo', 'fastify', 'html-ui', 'stored-text', 'ui-craft', 'docker'],
89
+ 'next-app': ['js-ts', 'http-api', 'react', 'mongo', 'web', 'stored-text', 'ui-craft', 'ai', 'docker'],
90
+ 'mcp-server': ['js-ts', 'mongo', 'ai', 'shell-hooks'],
91
+ 'node-cli': ['js-ts', 'tui'],
92
+ 'node-lib': ['js-ts'],
93
+ edge: ['nginx', 'docker'],
94
+ synth: ['cpp', 'audio-core'],
95
+ 'synth-rebuild': ['cpp', 'audio-core', 'reverse-engineering', 'midi', 'sound-design', 'instrument-shell'],
96
+ 'python-app': ['python', 'shell', 'docker'],
97
+ 'shell-tools': ['shell'],
98
+ 'go-app': ['go', 'shell', 'docker', 'ci'],
99
+ };
100
+
101
+ // Base always first, no duplicates, presets expanded, in the order given.
102
+ export function normalisePacks(names) {
103
+ const out = ['base'];
104
+ for (const n of names.flatMap((p) => (Object.hasOwn(PRESETS, p) ? PRESETS[p] : [p]))) if (!out.includes(n)) out.push(n);
105
+ for (const n of out) readPack(n);
106
+ return out;
107
+ }
@@ -0,0 +1,59 @@
1
+ // The planner-skills generator (0.10.0, STEP 3 item 5; KWB-35): one project-layout skill body per configuration, made
2
+ // from that configuration's layout template, its packs and its preamble, for a planner that briefs work before any project
3
+ // of that kind exists (or without reading one). `claude-packs planner-skills` prints them; `--out <dir>` writes
4
+ // <dir>/<configuration>/project-layout/SKILL.md.
5
+ import { mkdirSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { availableConfigs, itemsOf, planOf, readConfig } from './configs.mjs';
8
+
9
+ export function plannerSkill(name) {
10
+ const c = readConfig(name);
11
+ const folders = c.layout?.folders ?? {};
12
+ const where = (k) => (folders[k.in] ? `${folders[k.in].path}/` : k.in);
13
+ const items = itemsOf(planOf(c, [...c.packs.always, ...c.packs.default]));
14
+ return [
15
+ '---',
16
+ 'name: project-layout',
17
+ `description: Use when planning, briefing or placing work in a ${name} project: where a new file, folder or kind of thing goes, and which laws hold there.`,
18
+ '---',
19
+ `# Project layout: ${name}`,
20
+ '',
21
+ c.description,
22
+ '',
23
+ '## Folders',
24
+ '',
25
+ ...Object.values(folders).map((f) => `- \`${f.path}/\`: ${f.purpose}`),
26
+ '',
27
+ ...(Object.keys(c.layout?.kinds ?? {}).length ? ['## What goes where', '', ...Object.values(c.layout.kinds).map((k) => `- ${k.what} → \`${where(k)}\``), ''] : []),
28
+ '## What it installs',
29
+ '',
30
+ `- packs: ${c.packs.always.join(', ')}${c.packs.choose.length ? `; to choose: ${c.packs.choose.join(', ')}` : ''}`,
31
+ `- hooks: ${items.hooks.join(', ')}`,
32
+ `- skills: ${items.skills.join(', ') || 'none'}`,
33
+ `- rules: ${items.rules.length} (\`npx --no-install @thedecipherist/claude-packs configs\` lists the configurations)`,
34
+ '',
35
+ '## House rules',
36
+ '',
37
+ '<constraints>',
38
+ 'Always do:',
39
+ '- Put every kind of thing in its one place (the folders above); a new folder goes into `.claude/layout.json` first.',
40
+ ...c.preamble.always.map((l) => `- ${l}`),
41
+ '',
42
+ 'Never do:',
43
+ ...c.preamble.never.map((l) => `- ${l}`),
44
+ '</constraints>',
45
+ '',
46
+ ].join('\n');
47
+ }
48
+
49
+ export function plannerSkills(out = null, log = console.log) {
50
+ for (const name of availableConfigs()) {
51
+ const body = plannerSkill(name);
52
+ if (!out) { log(`==> ${name}/project-layout/SKILL.md\n${body}`); continue; }
53
+ const dir = join(out, name, 'project-layout');
54
+ mkdirSync(dir, { recursive: true });
55
+ writeFileSync(join(dir, 'SKILL.md'), body);
56
+ log(` wrote ${join(dir, 'SKILL.md')}`);
57
+ }
58
+ return 0;
59
+ }
@@ -0,0 +1,65 @@
1
+ // What a project repeats, for `scan`: every shape written by hand more than once, the copies of it, and what it should become. A run seen as
2
+ // many overlapping windows is reported once, from its first window.
3
+ import { extname } from 'node:path';
4
+ import { TEMPLATE, TESTS, copiesNeeded, shapesOf, skippedBy } from '../packs/base/hooks/lib/elements.mjs';
5
+ import { parametersOf } from '../packs/base/hooks/lib/code-shapes.mjs';
6
+ import { languageOf } from '../packs/base/hooks/lib/languages.mjs';
7
+ import { definesOf, fingerprint, nearCopy } from '../packs/base/hooks/lib/near-copies.mjs';
8
+ import { matchesAny } from '../packs/base/hooks/lib/glob.mjs';
9
+
10
+ const copiesOf = (group) => new Set(group.map((x) => `${x.rel}:${x.line}`)).size;
11
+ const oneName = (group) => new Set(group.map((x) => x.what)).size === 1;
12
+ const isRepeat = (g) => copiesOf(g) >= copiesNeeded(g[0], oneName(g)) && (g[0].kind !== 'setup' || !sameObject(g));
13
+ // One object set up twice in one file is one object.
14
+ const sameObject = (g) => new Set(g.map((x) => `${x.rel} ${x.what}`)).size < 2;
15
+
16
+ function firstWindows(groups) {
17
+ const seen = new Set(groups.filter((g) => g[0].kind === 'run').map((g) => g.map((x) => `${x.rel}:${x.at}`).join()));
18
+ return groups.filter((g) => g[0].kind !== 'run' || !seen.has(g.map((x) => `${x.rel}:${x.at - 1}`).join()));
19
+ }
20
+
21
+ export function repeatedShapes(texts, layout = {}) {
22
+ const by = new Map();
23
+ for (const [rel, text] of texts) {
24
+ if (matchesAny(rel, [...TESTS, ...skippedBy(layout)])) continue;
25
+ for (const s of shapesOf(text, extname(rel))) { if (!by.has(s.id)) by.set(s.id, []); by.get(s.id).push({ ...s, rel }); }
26
+ }
27
+ const source = new Map(texts);
28
+ return firstWindows([...by.values()].filter(isRepeat)).sort((a, b) => b.length * b[0].nodes - a.length * a[0].nodes).map((g) => {
29
+ const [a, b] = g;
30
+ return { kind: g[0].kind, what: g[0].what, copies: g.length, at: g.map((x) => `${x.rel}:${x.line}`), template: TEMPLATE[g[0].kind],
31
+ parameters: g[0].kind === 'run' ? parametersOf(a.id, source.get(a.rel), source.get(b.rel), extname(a.rel)).slice(0, 6) : [] };
32
+ });
33
+ }
34
+
35
+ export const repeatLine = (r) => ` - ${r.copies}x ${r.what}: ${r.at.slice(0, 4).join(', ')}${r.copies > 4 ? ', ...' : ''}. Make it ${r.template}${r.parameters.length ? ` (differs in: ${r.parameters.join(', ').replace(/`/g, "'")})` : ''}`;
36
+
37
+ const sourceTexts = (texts, layout) => texts.filter(([rel]) => languageOf(extname(rel)) && !matchesAny(rel, [...TESTS, ...skippedBy(layout)]));
38
+
39
+ // Files that hold most of another, in order, with other names. A pair is reported once.
40
+ export function nearCopies(texts, layout = {}) {
41
+ const all = sourceTexts(texts, layout).map(([file, text]) => ({ file, st: fingerprint(text, extname(file)), lang: languageOf(extname(file)) }));
42
+ const seen = new Set();
43
+ const out = [];
44
+ for (const x of all) {
45
+ const hit = nearCopy(x.st, all.filter((o) => o !== x && o.lang === x.lang));
46
+ if (!hit || seen.has(`${hit.file} ${x.file}`)) continue;
47
+ seen.add(`${x.file} ${hit.file}`);
48
+ out.push({ file: x.file, line: hit.line, other: hit.file, from: hit.from, to: hit.to, matched: hit.matched, of: hit.of });
49
+ }
50
+ return out.sort((a, b) => b.matched - a.matched);
51
+ }
52
+
53
+ // Compound names defined in two or three files: a helper made again. More files than that is a convention.
54
+ export function remadeNames(texts, layout = {}) {
55
+ const by = new Map();
56
+ for (const [rel, text] of sourceTexts(texts, layout)) {
57
+ for (const [name, line] of definesOf(text, extname(rel))) {
58
+ if (!/[a-z0-9][A-Z]|[a-z0-9]_[a-z0-9]/.test(name)) continue;
59
+ const key = `${languageOf(extname(rel)).name} ${name}`;
60
+ if (!by.has(key)) by.set(key, { name, at: [] });
61
+ by.get(key).at.push(`${rel}:${line}`);
62
+ }
63
+ }
64
+ return [...by.values()].filter((m) => m.at.length >= 2 && m.at.length <= 3 && new Set(m.at.map((a) => a.split(':')[0])).size >= 2);
65
+ }
@@ -0,0 +1,28 @@
1
+ // What a repo carries that is not source: its tracked size, files too big for git, and working screenshots in its root. For `scan`; the `repo-litter`
2
+ // hook stops new ones at `git add`.
3
+ import { statSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { BIG_MB, isRootImage } from '../packs/base/hooks/repo-litter.mjs';
6
+
7
+ const MB = 1024 * 1024;
8
+
9
+ // A build folder is known by what the build leaves in it, whatever it was called: `build-reso/` held 43% of one repo's tracked files.
10
+ const BUILD_MARK = /^(.*?\/)?(?:CMakeFiles\/|CMakeCache\.txt$)/;
11
+ export function buildFolders(files) {
12
+ const folders = new Set(files.map((f) => f.match(BUILD_MARK)).filter((m) => m?.[1]).map((m) => m[1]));
13
+ return [...folders].filter((d) => ![...folders].some((o) => o !== d && d.startsWith(o)));
14
+ }
15
+
16
+ export function repoWeight(dir, files) {
17
+ const sized = files.flatMap((file) => { try { return [{ file, bytes: statSync(join(dir, file)).size }]; } catch { return []; } });
18
+ const total = sized.reduce((n, f) => n + f.bytes, 0);
19
+ const big = sized.filter((f) => f.bytes > BIG_MB * MB).sort((a, b) => b.bytes - a.bytes).map((f) => ({ file: f.file, mb: Math.round(f.bytes / MB) }));
20
+ const built = buildFolders(files).map((folder) => ({ folder, files: files.filter((f) => f.startsWith(folder)).length }));
21
+ return { mb: Math.round(total / MB), big, rootImages: files.filter((f) => isRootImage(f, [])), built, files: files.length };
22
+ }
23
+
24
+ export const weightLines = (w) => [
25
+ `- ${w.mb} MB tracked; ${w.big.length} file(s) over ${BIG_MB} MB${w.big.length ? `: ${w.big.slice(0, 5).map((b) => `${b.file} (${b.mb} MB)`).join(', ')}` : ''}`,
26
+ ...w.built.map((b) => `- build output held in git: ${b.folder} (${b.files} of ${w.files} files), left out of this scan`),
27
+ ...(w.rootImages.length ? [`- ${w.rootImages.length} image(s) in the repo root, which look like working screenshots: ${w.rootImages.slice(0, 5).join(', ')}`] : []),
28
+ ];
package/lib/rules.mjs ADDED
@@ -0,0 +1,40 @@
1
+ // Regenerates every rule's `paths:` from its `# concepts:` line and the layout.
2
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { globsForConcepts as kitGlobs } from '../packs/base/hooks/lib/layout.mjs';
6
+
7
+ // A rule's text with its `paths:` written from its concepts and the layout; null for a rule
8
+ // without a `# concepts:` line, or (unless strict, which throws) whose concepts resolve to nothing.
9
+ export function renderRule(text, layout, globsForConcepts = kitGlobs, strict = false) {
10
+ const fm = text.match(/^---\n([\s\S]*?)\n---/);
11
+ const line = fm?.[1].match(/^# concepts:\s*(.+)$/m);
12
+ if (!fm || !line) return null;
13
+ const concepts = line[1].split(',').map((s) => s.trim()).filter(Boolean);
14
+ let globs;
15
+ try { globs = globsForConcepts(layout, concepts); } catch (e) { if (strict) throw e; return null; }
16
+ const head = ['---', `# concepts: ${concepts.join(', ')}`, 'paths:', ...globs.map((g) => ` - "${g}"`), '---'].join('\n');
17
+ return text.replace(fm[0], head);
18
+ }
19
+
20
+ async function layoutLib(projectDir) {
21
+ return import(pathToFileURL(join(projectDir, '.claude', 'hooks', 'lib', 'layout.mjs')).href);
22
+ }
23
+
24
+ export async function applyPaths(projectDir, { checkOnly = false, skip = new Set() } = {}) {
25
+ const { readLayout, globsForConcepts } = await layoutLib(projectDir);
26
+ const layout = readLayout(projectDir);
27
+ const dir = join(projectDir, '.claude', 'rules');
28
+ if (!existsSync(dir)) return { stale: 0 };
29
+ let stale = 0;
30
+ for (const file of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
31
+ if (skip.has(join('.claude', 'rules', file))) continue; // the user's file or a rule they edited
32
+ const text = readFileSync(join(dir, file), 'utf8');
33
+ const next = renderRule(text, layout, globsForConcepts, true);
34
+ if (next !== null && next !== text) {
35
+ stale += 1;
36
+ if (!checkOnly) writeFileSync(join(dir, file), next);
37
+ }
38
+ }
39
+ return { stale };
40
+ }
@@ -0,0 +1,94 @@
1
+ // What a project is, from its files and its dependencies: the configuration and the packs `scan` recommends, each with its evidence.
2
+ // One row per pack: a pack is recommended when its row finds something, and says what it found.
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { isContent } from '../packs/base/hooks/lib/content.mjs';
6
+ import { readConfig } from './configs.mjs';
7
+
8
+ const dep = (ctx, ...names) => names.find((n) => ctx.deps.has(n) || [...ctx.deps].some((d) => d.startsWith(`${n}/`)));
9
+ const file = (ctx, re) => ctx.files.find((f) => re.test(f));
10
+ const said = (hit, what) => (hit ? `${what}: ${hit}` : null);
11
+ // The first of a project's files with this name that holds this text: a name says too little where every project has the same files.
12
+ const MOST_READ = 40;
13
+ function holds(ctx, name, text) {
14
+ return ctx.files.filter((f) => name.test(f)).slice(0, MOST_READ).find((f) => { try { return text.test(readFileSync(join(ctx.dir, f), 'utf8')); } catch { return false; } });
15
+ }
16
+
17
+ const ROWS = {
18
+ 'js-ts': (c) => said(file(c, /\.(ts|tsx|js|jsx|mjs|cjs)$/), 'JS or TS source'),
19
+ mongo: (c) => said(dep(c, 'mongodb', 'mongoose'), 'dependency'),
20
+ fastify: (c) => said(dep(c, 'fastify'), 'dependency'),
21
+ ai: (c) => said(dep(c, '@anthropic-ai/sdk', 'openai', 'ai', '@ai-sdk'), 'dependency'),
22
+ docker: (c) => said(file(c, /(^|\/)(Dockerfile[^/]*|[\w.-]*compose[\w.-]*\.ya?ml)$/), 'file'),
23
+ 'shell-hooks': (c) => said(file(c, /(^|\/)hooks\/[^/]+\.(sh|mjs|js|cjs)$/), 'hook file'),
24
+ 'html-ui': (c) => said(file(c, /(^|\/)(ui|views|templates)\/[^/]+\.ts$|\.view\.ts$/), 'server-rendered UI'),
25
+ web: (c) => said(dep(c, 'next', 'react', 'vue', 'svelte') ?? file(c, /\.(tsx|jsx|vue|svelte|html)$/), 'web UI'),
26
+ tui: (c) => said(dep(c, 'ink', 'blessed', 'terminal-kit'), 'dependency'),
27
+ nginx: (c) => said(file(c, /(^|\/)nginx[^/]*\.conf(?:\.template)?$|\/nginx\/.+\.conf$/), 'file'),
28
+ 'stored-text': (c) => (ROWS.web(c) || ROWS['html-ui'](c)) && (ROWS.mongo(c) || dep(c, 'pg', 'mysql2', 'sqlite3', 'better-sqlite3', 'prisma', 'drizzle-orm')) ? 'a UI and a database' : null,
29
+ 'ui-craft': (c) => ROWS.web(c) ?? ROWS['html-ui'](c),
30
+ cpp: (c) => said(file(c, /\.(?:cc|cpp|cxx|hh|hpp|mm)$/), 'C++ source'),
31
+ python: (c) => said(file(c, /\.py$/), 'Python source'),
32
+ go: (c) => said(file(c, /\.go$/), 'Go source'),
33
+ midi: (c) => said(file(c, /\.(syx|mid)$|(^|\/)\w*(Midi|MIDI|Sysex|SysEx)\w*\.(h|hpp|cpp|cc)$/), 'MIDI file')
34
+ ?? said(holds(c, /\.(h|hpp|cpp|cc)$/, /\b(MidiMessage|isController|isNoteOn|getSysExData)\b/), 'MIDI handling'),
35
+ 'sound-design': (c) => said(file(c, /(^|\/)\w*(Voice|Oscillator|Filter|Reverb|Envelope|Lfo|LFO)\w*\.(h|hpp|cpp|cc)$/), 'DSP source'),
36
+ 'reverse-engineering': (c) => said(file(c, /(^|\/)[\w-]*(firmware|disasm|disassembl|x86dis|dis8051|reverse[-_]engineer)[\w-]*\.(md|py)$|(^|\/)[\w.-]+[-_]re\.md$|(^|\/)tools\/re\//i), 'reverse-engineering notes')
37
+ ?? said(holds(c, /(^|\/)(research|laws|provenance)\.md$/, /\[FW\]/), 'firmware findings'),
38
+ 'instrument-shell': (c) => said(file(c, /(^|\/)PluginEditor\.(h|cpp)$/), 'plugin editor'),
39
+ 'content-site': (c) => said(c.published.find((f) => /\.(md|mdx|html)$/.test(f)) ?? file(c, /(^|\/)sitemap\.xml$/), 'published page'),
40
+ 'dynamic-data': (c) => said(file(c, /(^|[/_-])(cms|entity[-_]?definitions?|field[-_]?configs?|page[-_]?builder|admin[-_]?generator)([/_.-]|$)/i), 'definition-driven file'),
41
+ ci: (c) => said(file(c, /^\.github\/workflows\/[^/]+\.ya?ml$/), 'workflow'),
42
+ 'http-api': (c) => said(dep(c, 'express', 'fastify', 'koa', 'hono', '@nestjs/core', 'next'), 'dependency') ?? said(file(c, /(^|\/)app\/api\/.*route\.ts$|(^|\/)routes\/[^/]+\.[jt]s$/), 'route file'),
43
+ react: (c) => said(dep(c, 'react'), 'dependency') ?? said(file(c, /\.[jt]sx$/), 'JSX source'),
44
+ websocket: (c) => said(dep(c, 'ws', 'socket.io', 'uWebSockets.js', '@fastify/websocket'), 'dependency'),
45
+ 'multi-tenant': (c) => said(file(c, /(^|[/_-])(tenants?|compan(?:y|ies)|organi[sz]ations?|workspaces?)([/_.-]|$)/i), 'tenant file'),
46
+ 'host-agent': (c) => said(dep(c, 'ssh2'), 'dependency') ?? said(file(c, /(^|\/)install[-_]agent|\.service$/), 'agent file'),
47
+ shell: (c) => said(file(c, /\.(?:sh|bash|zsh)$/), 'shell script'),
48
+ 'audio-core': (c) => said(file(c, /(^|\/)(JuceHeader\.h|.*\.jucer)$/) ?? file(c, /PluginProcessor\.(h|cpp)$/), 'JUCE file'),
49
+ 'gpu-painter': (c) => said(file(c, /\.wgsl$/), 'shader'),
50
+ };
51
+
52
+ // The configuration of the project: a synth; else the language most of its files are in; else base. full-stack is offered, never assumed: it fixes a folder layout.
53
+ const LEADS = [['cpp', /\.(?:c|cc|cpp|cxx|h|hh|hpp|mm)$/, 'C++ sources'], ['node', /\.(?:ts|tsx|js|jsx|mjs|cjs)$/, 'JS or TS sources'], ['python', /\.py$/, 'Python sources'], ['go', /\.go$/, 'Go sources']];
54
+
55
+ function configFor(ctx) {
56
+ if (ROWS['audio-core'](ctx)) return { name: 'synth-creator', why: 'JUCE sources' };
57
+ const [lead] = LEADS.map(([name, re, why]) => ({ name, why, n: ctx.files.filter((f) => re.test(f)).length })).sort((a, b) => b.n - a.n);
58
+ return lead.n ? lead : { name: 'base', why: 'no stack the kit has a configuration for' };
59
+ }
60
+
61
+ const INSTALLED = /(^|\/)\.claude\//;
62
+
63
+ // A project that names its configuration has decided; reading the files again can only disagree with it.
64
+ function namedConfig(projectDir) {
65
+ try {
66
+ const name = JSON.parse(readFileSync(join(projectDir, '.claude/layout.json'), 'utf8')).configuration;
67
+ return name && readConfig(name) ? { name, why: 'named in its layout.json' } : null;
68
+ } catch { return null; }
69
+ }
70
+
71
+ export function recommend(projectDir, files) {
72
+ const deps = new Set();
73
+ for (const f of files.filter((x) => /(^|\/)package\.json$/.test(x))) {
74
+ const p = join(projectDir, f);
75
+ if (!existsSync(p)) continue;
76
+ try {
77
+ const j = JSON.parse(readFileSync(p, 'utf8'));
78
+ for (const k of ['dependencies', 'devDependencies', 'peerDependencies']) for (const n of Object.keys(j[k] ?? {})) deps.add(n);
79
+ } catch { /* a package.json that does not parse says nothing */ }
80
+ }
81
+ const own = files.filter((f) => !INSTALLED.test(f)); // the kit's own files in a project say nothing about the project
82
+ const ctx = { deps, dir: projectDir, files: own.filter((f) => !isContent(f)), published: own.filter(isContent) };
83
+ const { name, why } = namedConfig(projectDir) ?? configFor(ctx);
84
+ const config = readConfig(name);
85
+ const packs = [];
86
+ const leftOut = [];
87
+ for (const pack of config.packs.choose) {
88
+ const found = ROWS[pack]?.(ctx);
89
+ if (found) packs.push({ pack, why: found });
90
+ else leftOut.push(pack);
91
+ }
92
+ const fullStack = dep(ctx, 'next') && dep(ctx, 'fastify');
93
+ return { config: name, why, always: config.packs.always, packs, leftOut, fullStack: Boolean(fullStack) };
94
+ }