@thedecipherist/claude-packs 0.0.0-stage → 1.0.0

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