@thedecipherist/claude-packs 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (890) hide show
  1. package/CHANGELOG.md +754 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -2
  4. package/bin/claude-packs.mjs +212 -0
  5. package/configurations/base/config.json +85 -0
  6. package/configurations/cpp/config.json +93 -0
  7. package/configurations/full-stack/config.json +153 -0
  8. package/configurations/go/config.json +98 -0
  9. package/configurations/node/config.json +102 -0
  10. package/configurations/python/config.json +99 -0
  11. package/configurations/rust/config.json +106 -0
  12. package/configurations/synth-creator/config.json +148 -0
  13. package/global/hooks/gnu-shell.mjs +131 -0
  14. package/global/hooks/lib/shell-writes.mjs +2 -0
  15. package/global/hooks/shell-traps.mjs +79 -0
  16. package/global/hooks/version-pin-guard.mjs +280 -0
  17. package/global/skills/latest-versions/SKILL.md +49 -0
  18. package/lib/backup.mjs +34 -0
  19. package/lib/build-queue.mjs +22 -0
  20. package/lib/claude-md.mjs +60 -0
  21. package/lib/config-cli.mjs +36 -0
  22. package/lib/configs.mjs +145 -0
  23. package/lib/coverage.mjs +94 -0
  24. package/lib/custom-packs.mjs +93 -0
  25. package/lib/doctor.mjs +126 -0
  26. package/lib/fix-plan.mjs +72 -0
  27. package/lib/found-folders.mjs +25 -0
  28. package/lib/git-tokens.mjs +64 -0
  29. package/lib/global.mjs +125 -0
  30. package/lib/hook-proof.mjs +45 -0
  31. package/lib/hook-timing.mjs +42 -0
  32. package/lib/hover-gate.mjs +29 -0
  33. package/lib/kit-command.mjs +14 -0
  34. package/lib/kit-lock.mjs +149 -0
  35. package/lib/layout.mjs +99 -0
  36. package/lib/logs.mjs +109 -0
  37. package/lib/manifest.mjs +24 -0
  38. package/lib/new.mjs +49 -0
  39. package/lib/once-only-report.mjs +54 -0
  40. package/lib/pack-manifest.mjs +20 -0
  41. package/lib/packs.mjs +109 -0
  42. package/lib/planner.mjs +59 -0
  43. package/lib/repeats.mjs +65 -0
  44. package/lib/repo-weight.mjs +28 -0
  45. package/lib/rule-tests.mjs +42 -0
  46. package/lib/rules.mjs +40 -0
  47. package/lib/scan-detect.mjs +130 -0
  48. package/lib/scan.mjs +171 -0
  49. package/lib/selftest-cache.mjs +39 -0
  50. package/lib/settings.mjs +138 -0
  51. package/lib/shape-cli.mjs +76 -0
  52. package/lib/shape.mjs +4 -0
  53. package/lib/suggest.mjs +31 -0
  54. package/lib/switches.mjs +44 -0
  55. package/lib/symmetry-doctor.mjs +26 -0
  56. package/lib/sync.mjs +133 -0
  57. package/lib/workorder.mjs +26 -0
  58. package/package.json +34 -4
  59. package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
  60. package/packs/ai/checks/model-call-needs-boundary.json +29 -0
  61. package/packs/ai/checks/model-id-from-config.json +28 -0
  62. package/packs/ai/checks/model-output-validated.json +26 -0
  63. package/packs/ai/pack.json +38 -0
  64. package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
  65. package/packs/ai/rules/llm-pipeline-security.md +26 -0
  66. package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
  67. package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
  68. package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
  69. package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
  70. package/packs/audio-core/checks/keep-werror.json +12 -0
  71. package/packs/audio-core/checks/no-fast-math.json +14 -0
  72. package/packs/audio-core/checks/no-locale-change.json +12 -0
  73. package/packs/audio-core/checks/no-march-native.json +14 -0
  74. package/packs/audio-core/clang-tidy +28 -0
  75. package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
  76. package/packs/audio-core/hooks/param-id-lock.mjs +172 -0
  77. package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
  78. package/packs/audio-core/pack.json +192 -0
  79. package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -0
  80. package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
  81. package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
  82. package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
  83. package/packs/audio-core/rules/build-flags-safe.md +20 -0
  84. package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
  85. package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
  86. package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
  87. package/packs/audio-core/rules/cpu-worst-case-per-block.md +28 -0
  88. package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
  89. package/packs/audio-core/rules/discrete-params-snap.md +19 -0
  90. package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
  91. package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
  92. package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
  93. package/packs/audio-core/rules/house-control-behaviours.md +34 -0
  94. package/packs/audio-core/rules/math-domain-guards.md +19 -0
  95. package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
  96. package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
  97. package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
  98. package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
  99. package/packs/audio-core/rules/one-formula-per-value.md +20 -0
  100. package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
  101. package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
  102. package/packs/audio-core/rules/output-limiter.md +23 -0
  103. package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
  104. package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
  105. package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
  106. package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
  107. package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
  108. package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
  109. package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
  110. package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
  111. package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
  112. package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
  113. package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
  114. package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
  115. package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
  116. package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
  117. package/packs/audio-core/rules/ui-matches-reference-look.md +23 -0
  118. package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
  119. package/packs/audio-core/rules/voice-steal-fades.md +21 -0
  120. package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
  121. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +35 -0
  122. package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
  123. package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
  124. package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
  125. package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
  126. package/packs/audio-core/skills/prove-control/SKILL.md +25 -0
  127. package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
  128. package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
  129. package/packs/audio-core/tools/lib/wav.mjs +41 -0
  130. package/packs/audio-core/tools/tone-report.mjs +47 -0
  131. package/packs/base/checks/field-map-over-if-runs.json +25 -0
  132. package/packs/base/checks/lookup-over-if-chains.json +25 -0
  133. package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
  134. package/packs/base/checks/no-code-after-midline-comment.json +12 -0
  135. package/packs/base/checks/one-walk-per-template.json +23 -0
  136. package/packs/base/checks/stub-reports-success.json +31 -0
  137. package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
  138. package/packs/base/hooks/brief-guard.mjs +96 -0
  139. package/packs/base/hooks/code-shape.mjs +99 -0
  140. package/packs/base/hooks/dispatch-once.mjs +124 -0
  141. package/packs/base/hooks/file-size-guard.mjs +61 -0
  142. package/packs/base/hooks/folder-law.mjs +77 -0
  143. package/packs/base/hooks/git-guard.mjs +202 -0
  144. package/packs/base/hooks/lesson-checks.mjs +182 -0
  145. package/packs/base/hooks/lib/apply-edit.mjs +20 -0
  146. package/packs/base/hooks/lib/code-shapes.mjs +142 -0
  147. package/packs/base/hooks/lib/content.mjs +14 -0
  148. package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
  149. package/packs/base/hooks/lib/crash-policy.mjs +47 -0
  150. package/packs/base/hooks/lib/dup-index.mjs +204 -0
  151. package/packs/base/hooks/lib/dup.mjs +127 -0
  152. package/packs/base/hooks/lib/elements.mjs +87 -0
  153. package/packs/base/hooks/lib/event-log.mjs +68 -0
  154. package/packs/base/hooks/lib/function-shapes.mjs +90 -0
  155. package/packs/base/hooks/lib/git-adds.mjs +21 -0
  156. package/packs/base/hooks/lib/git-auth.mjs +44 -0
  157. package/packs/base/hooks/lib/git-guard-selftest.mjs +192 -0
  158. package/packs/base/hooks/lib/glob.mjs +33 -0
  159. package/packs/base/hooks/lib/hook-log.mjs +100 -0
  160. package/packs/base/hooks/lib/house.py +77 -0
  161. package/packs/base/hooks/lib/languages.mjs +69 -0
  162. package/packs/base/hooks/lib/layout.mjs +58 -0
  163. package/packs/base/hooks/lib/making-check-selftest.mjs +112 -0
  164. package/packs/base/hooks/lib/near-copies.mjs +76 -0
  165. package/packs/base/hooks/lib/program-read.mjs +199 -0
  166. package/packs/base/hooks/lib/program-writes.mjs +177 -0
  167. package/packs/base/hooks/lib/project.mjs +81 -0
  168. package/packs/base/hooks/lib/prose.mjs +11 -0
  169. package/packs/base/hooks/lib/pushed.mjs +201 -0
  170. package/packs/base/hooks/lib/repo-index.mjs +52 -0
  171. package/packs/base/hooks/lib/rust-scan.mjs +139 -0
  172. package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
  173. package/packs/base/hooks/lib/selftest.mjs +45 -0
  174. package/packs/base/hooks/lib/shape-code.mjs +126 -0
  175. package/packs/base/hooks/lib/shape-lists.mjs +31 -0
  176. package/packs/base/hooks/lib/shape-prose.mjs +78 -0
  177. package/packs/base/hooks/lib/shape.mjs +273 -0
  178. package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
  179. package/packs/base/hooks/lib/shell-writes.mjs +293 -0
  180. package/packs/base/hooks/lib/turn.mjs +86 -0
  181. package/packs/base/hooks/lib/unread-writes.mjs +24 -0
  182. package/packs/base/hooks/lib/writes.mjs +71 -0
  183. package/packs/base/hooks/making-check.mjs +248 -0
  184. package/packs/base/hooks/making.mjs +106 -0
  185. package/packs/base/hooks/once-only.mjs +150 -0
  186. package/packs/base/hooks/placement-law.mjs +84 -0
  187. package/packs/base/hooks/repo-litter.mjs +75 -0
  188. package/packs/base/hooks/reuse-hint.mjs +75 -0
  189. package/packs/base/hooks/run-hooks.mjs +140 -0
  190. package/packs/base/hooks/secrets-guard.mjs +99 -0
  191. package/packs/base/hooks/shape-law.mjs +140 -0
  192. package/packs/base/hooks/suite-once.mjs +96 -0
  193. package/packs/base/hooks/template-law.mjs +148 -0
  194. package/packs/base/hooks/untrusted-boundary.mjs +53 -0
  195. package/packs/base/pack.json +214 -0
  196. package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
  197. package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
  198. package/packs/base/rules/bug-fix-starts-red.md +25 -0
  199. package/packs/base/rules/field-map-over-if-runs.md +47 -0
  200. package/packs/base/rules/finished-work-lands.md +17 -0
  201. package/packs/base/rules/git-right-first-time.md +19 -0
  202. package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
  203. package/packs/base/rules/lookup-over-if-chains.md +49 -0
  204. package/packs/base/rules/making-line-first.md +14 -0
  205. package/packs/base/rules/open-items-tagged.md +17 -0
  206. package/packs/base/rules/plan-and-logs.md +22 -0
  207. package/packs/base/rules/references-use-anchors.md +19 -0
  208. package/packs/base/rules/refusals-are-judged.md +19 -0
  209. package/packs/base/rules/reuse-before-writing.md +15 -0
  210. package/packs/base/rules/secrets-never-committed.md +16 -0
  211. package/packs/base/rules/skill-writing.md +23 -0
  212. package/packs/base/rules/split-before-the-limit.md +14 -0
  213. package/packs/base/rules/subagent-dispatch.md +26 -0
  214. package/packs/base/rules/test-only-what-changed.md +17 -0
  215. package/packs/base/rules/test-the-template-once.md +23 -0
  216. package/packs/base/rules/unattended-runs.md +15 -0
  217. package/packs/base/rules/untrusted-content-boundary.md +15 -0
  218. package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
  219. package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
  220. package/packs/base/skills/land-branch/SKILL.md +24 -0
  221. package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
  222. package/packs/base/skills/new-project/SKILL.md +23 -0
  223. package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
  224. package/packs/base/skills/pick-tests/SKILL.md +20 -0
  225. package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
  226. package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
  227. package/packs/base/skills/write-a-workorder/SKILL.md +41 -0
  228. package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
  229. package/packs/base/tools/shape-symmetry.mjs +178 -0
  230. package/packs/base/tools/test-verdict.mjs +75 -0
  231. package/packs/ci/checks/ci-permissions-stated.json +18 -0
  232. package/packs/ci/checks/ci-tools-pinned.json +14 -0
  233. package/packs/ci/pack.json +21 -0
  234. package/packs/ci/rules/ci-covers-what-ships.md +20 -0
  235. package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
  236. package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
  237. package/packs/content-site/checks/img-has-size.json +20 -0
  238. package/packs/content-site/checks/page-head-complete.json +26 -0
  239. package/packs/content-site/pack.json +22 -0
  240. package/packs/content-site/rules/content-has-one-source.md +23 -0
  241. package/packs/content-site/rules/media-is-sized.md +24 -0
  242. package/packs/content-site/rules/page-head-is-complete.md +26 -0
  243. package/packs/content-site/rules/public-forms-are-defended.md +27 -0
  244. package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
  245. package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
  246. package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
  247. package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
  248. package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
  249. package/packs/cpp/checks/cpp-no-atoi.json +16 -0
  250. package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
  251. package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
  252. package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
  253. package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
  254. package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
  255. package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
  256. package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
  257. package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
  258. package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
  259. package/packs/cpp/pack.json +22 -0
  260. package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
  261. package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
  262. package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
  263. package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
  264. package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
  265. package/packs/cpp/rules/cpp-headers-declare.md +22 -0
  266. package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
  267. package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
  268. package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
  269. package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
  270. package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
  271. package/packs/docker/checks/alpine-with-native-build.json +13 -0
  272. package/packs/docker/checks/compose-db-needs-auth.json +14 -0
  273. package/packs/docker/checks/compose-image-pinned.json +14 -0
  274. package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
  275. package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
  276. package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
  277. package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
  278. package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
  279. package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
  280. package/packs/docker/checks/compose-service-needs-init.json +11 -0
  281. package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
  282. package/packs/docker/checks/copy-source-before-deps.json +13 -0
  283. package/packs/docker/checks/docker-exec-form.json +16 -0
  284. package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
  285. package/packs/docker/checks/dockerfile-needs-user.json +21 -0
  286. package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
  287. package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
  288. package/packs/docker/checks/health-checks-nothing.json +25 -0
  289. package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
  290. package/packs/docker/checks/no-latest-base.json +13 -0
  291. package/packs/docker/checks/no-secret-in-image.json +13 -0
  292. package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
  293. package/packs/docker/checks/node-env-before-install.json +13 -0
  294. package/packs/docker/checks/npm-ci-not-install.json +13 -0
  295. package/packs/docker/pack.json +46 -0
  296. package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
  297. package/packs/docker/rules/compose-swarm-safe.md +26 -0
  298. package/packs/docker/rules/docker-exec-form.md +19 -0
  299. package/packs/docker/rules/dockerfile-multistage.md +13 -0
  300. package/packs/docker/rules/dockerfile-production.md +25 -0
  301. package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
  302. package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
  303. package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
  304. package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
  305. package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
  306. package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
  307. package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
  308. package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
  309. package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
  310. package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
  311. package/packs/dynamic-data/pack.json +23 -0
  312. package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
  313. package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
  314. package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
  315. package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
  316. package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
  317. package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
  318. package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
  319. package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
  320. package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
  321. package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
  322. package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
  323. package/packs/fastify/checks/api-routes-versioned.json +28 -0
  324. package/packs/fastify/checks/cookie-host-prefix.json +24 -0
  325. package/packs/fastify/checks/hijack-needs-try.json +28 -0
  326. package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
  327. package/packs/fastify/checks/no-db-in-route-file.json +28 -0
  328. package/packs/fastify/checks/no-err-message-to-client.json +24 -0
  329. package/packs/fastify/checks/no-regexp-from-input.json +24 -0
  330. package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
  331. package/packs/fastify/checks/one-error-body.json +26 -0
  332. package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
  333. package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
  334. package/packs/fastify/checks/sec-headers-present.json +28 -0
  335. package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
  336. package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
  337. package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
  338. package/packs/fastify/pack.json +41 -0
  339. package/packs/fastify/rules/credentials-and-input.md +23 -0
  340. package/packs/fastify/rules/security-one-switch.md +28 -0
  341. package/packs/fastify/rules/streams-and-hijacks.md +22 -0
  342. package/packs/fastify/rules/tests-use-inject.md +13 -0
  343. package/packs/fastify/rules/three-layers.md +20 -0
  344. package/packs/fastify/rules/trust-config-not-headers.md +24 -0
  345. package/packs/full-stack/checks/no-express-no-vite.json +23 -0
  346. package/packs/full-stack/hooks/stack-guard.mjs +90 -0
  347. package/packs/full-stack/pack.json +76 -0
  348. package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
  349. package/packs/go/checks/go-exec-result-ignored.json +15 -0
  350. package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
  351. package/packs/go/pack.json +26 -0
  352. package/packs/go/rules/go-errors-are-handled.md +22 -0
  353. package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
  354. package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
  355. package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
  356. package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
  357. package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
  358. package/packs/gpu-painter/checks/no-opengl.json +13 -0
  359. package/packs/gpu-painter/hooks/browser-guard.py +196 -0
  360. package/packs/gpu-painter/pack.json +91 -0
  361. package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
  362. package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
  363. package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
  364. package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
  365. package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
  366. package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
  367. package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
  368. package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
  369. package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
  370. package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
  371. package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
  372. package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
  373. package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
  374. package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
  375. package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
  376. package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
  377. package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
  378. package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
  379. package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
  380. package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
  381. package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
  382. package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
  383. package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
  384. package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
  385. package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
  386. package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
  387. package/packs/hook-proof/pack.json +38 -0
  388. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
  389. package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
  390. package/packs/host-agent/checks/file-path-from-param.json +27 -0
  391. package/packs/host-agent/checks/integrity-check-optional.json +27 -0
  392. package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
  393. package/packs/host-agent/checks/secret-on-argv.json +28 -0
  394. package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
  395. package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
  396. package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
  397. package/packs/host-agent/pack.json +22 -0
  398. package/packs/host-agent/rules/root-on-other-machines.md +27 -0
  399. package/packs/html-ui/checks/email-html-escapes.json +25 -0
  400. package/packs/html-ui/checks/external-link-rel.json +21 -0
  401. package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
  402. package/packs/html-ui/checks/no-inline-client-script.json +21 -0
  403. package/packs/html-ui/checks/no-inline-style.json +23 -0
  404. package/packs/html-ui/pack.json +33 -0
  405. package/packs/html-ui/rules/email-html-escapes.md +19 -0
  406. package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
  407. package/packs/http-api/checks/api-path-versioned.json +31 -0
  408. package/packs/http-api/checks/authz-from-request-body.json +26 -0
  409. package/packs/http-api/checks/client-calls-a-version.json +28 -0
  410. package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
  411. package/packs/http-api/checks/cors-needs-origin.json +26 -0
  412. package/packs/http-api/checks/csp-not-switched-off.json +28 -0
  413. package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
  414. package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
  415. package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
  416. package/packs/http-api/checks/failure-is-not-empty.json +28 -0
  417. package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
  418. package/packs/http-api/checks/guard-fails-open.json +28 -0
  419. package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
  420. package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
  421. package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
  422. package/packs/http-api/checks/next-api-route-versioned.json +47 -0
  423. package/packs/http-api/checks/no-security-off-switch.json +28 -0
  424. package/packs/http-api/checks/oauth-state-signed.json +26 -0
  425. package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
  426. package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
  427. package/packs/http-api/checks/queue-action-from-client.json +26 -0
  428. package/packs/http-api/checks/rate-limit-one-module.json +28 -0
  429. package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
  430. package/packs/http-api/checks/route-body-needs-schema.json +31 -0
  431. package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
  432. package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
  433. package/packs/http-api/checks/secret-in-job-record.json +27 -0
  434. package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
  435. package/packs/http-api/checks/short-code-needs-limit.json +26 -0
  436. package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
  437. package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
  438. package/packs/http-api/pack.json +45 -0
  439. package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
  440. package/packs/http-api/rules/apis-are-versioned.md +27 -0
  441. package/packs/http-api/rules/credentials-handled-safely.md +22 -0
  442. package/packs/http-api/rules/handlers-own-the-work.md +17 -0
  443. package/packs/http-api/rules/health-and-correlation.md +17 -0
  444. package/packs/http-api/rules/one-error-contract.md +17 -0
  445. package/packs/http-api/rules/routes-are-thin.md +17 -0
  446. package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
  447. package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
  448. package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
  449. package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
  450. package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
  451. package/packs/instrument-shell/pack.json +22 -0
  452. package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
  453. package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
  454. package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
  455. package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
  456. package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
  457. package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
  458. package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
  459. package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
  460. package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
  461. package/packs/js-ts/checks/child-process-error-handler.json +28 -0
  462. package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
  463. package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
  464. package/packs/js-ts/checks/job-only-in-memory.json +28 -0
  465. package/packs/js-ts/checks/json-only-through-helper.json +29 -0
  466. package/packs/js-ts/checks/line-under-200-chars.json +34 -0
  467. package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
  468. package/packs/js-ts/checks/no-any.json +27 -0
  469. package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
  470. package/packs/js-ts/checks/no-console-in-app.json +31 -0
  471. package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
  472. package/packs/js-ts/checks/no-inline-binary.json +20 -0
  473. package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
  474. package/packs/js-ts/checks/no-local-sleep.json +25 -0
  475. package/packs/js-ts/checks/no-nested-ternary.json +20 -0
  476. package/packs/js-ts/checks/no-secret-in-log.json +24 -0
  477. package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
  478. package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
  479. package/packs/js-ts/checks/no-ts-enum.json +20 -0
  480. package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
  481. package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
  482. package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
  483. package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
  484. package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
  485. package/packs/js-ts/checks/server-close-awaited.json +27 -0
  486. package/packs/js-ts/checks/session-user-typed-once.json +24 -0
  487. package/packs/js-ts/checks/switch-is-a-table.json +24 -0
  488. package/packs/js-ts/checks/test-imports-the-source.json +19 -0
  489. package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
  490. package/packs/js-ts/checks/tsconfig-flags.json +21 -0
  491. package/packs/js-ts/checks/void-needs-catch.json +24 -0
  492. package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
  493. package/packs/js-ts/checks/zod-v4-formats.json +23 -0
  494. package/packs/js-ts/pack.json +84 -0
  495. package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
  496. package/packs/js-ts/rules/env-through-config.md +36 -0
  497. package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
  498. package/packs/js-ts/rules/files-under-300-lines.md +18 -0
  499. package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
  500. package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
  501. package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
  502. package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
  503. package/packs/js-ts/rules/no-any.md +34 -0
  504. package/packs/js-ts/rules/no-console.md +30 -0
  505. package/packs/js-ts/rules/no-floating-promises.md +31 -0
  506. package/packs/js-ts/rules/no-param-mutation.md +28 -0
  507. package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
  508. package/packs/js-ts/rules/one-logger.md +24 -0
  509. package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
  510. package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
  511. package/packs/js-ts/rules/process-lifecycle.md +47 -0
  512. package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
  513. package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
  514. package/packs/js-ts/rules/small-functions.md +20 -0
  515. package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
  516. package/packs/js-ts/rules/tests-can-fail.md +19 -0
  517. package/packs/js-ts/rules/tsconfig-strict.md +32 -0
  518. package/packs/js-ts/rules/typed-exports.md +27 -0
  519. package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
  520. package/packs/js-ts/rules/unions-over-enums.md +29 -0
  521. package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
  522. package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
  523. package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
  524. package/packs/midi/checks/midi-realtime-first.json +27 -0
  525. package/packs/midi/checks/no-tempo-writeback.json +23 -0
  526. package/packs/midi/checks/position-not-integrated.json +29 -0
  527. package/packs/midi/checks/stub-playhead-advances.json +27 -0
  528. package/packs/midi/checks/sysex-length-literal.json +23 -0
  529. package/packs/midi/checks/sysex-not-on-audio.json +24 -0
  530. package/packs/midi/checks/sysex-survives-realtime.json +27 -0
  531. package/packs/midi/pack.json +22 -0
  532. package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
  533. package/packs/midi/rules/imports-count-every-loss.md +25 -0
  534. package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
  535. package/packs/midi/rules/sync-is-a-mode.md +29 -0
  536. package/packs/midi/rules/sysex-is-a-record.md +29 -0
  537. package/packs/midi/skills/clock-probe/SKILL.md +23 -0
  538. package/packs/midi/skills/clock-probe/clock-law.md +417 -0
  539. package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
  540. package/packs/mongo/checks/bulk-over-many.json +28 -0
  541. package/packs/mongo/checks/created-at-immutable.json +25 -0
  542. package/packs/mongo/checks/driver-outside-gateway.json +24 -0
  543. package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
  544. package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
  545. package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
  546. package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
  547. package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
  548. package/packs/mongo/checks/no-drop-in-code.json +27 -0
  549. package/packs/mongo/checks/no-empty-deletemany.json +26 -0
  550. package/packs/mongo/checks/no-find-reads.json +26 -0
  551. package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
  552. package/packs/mongo/checks/no-mongoose.json +23 -0
  553. package/packs/mongo/checks/no-negation-delete.json +26 -0
  554. package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
  555. package/packs/mongo/checks/objectid-equals.json +26 -0
  556. package/packs/mongo/checks/update-then-reread.json +24 -0
  557. package/packs/mongo/checks/watch-not-poll.json +25 -0
  558. package/packs/mongo/pack.json +52 -0
  559. package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
  560. package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
  561. package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
  562. package/packs/mongo/rules/one-client-one-pool.md +32 -0
  563. package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
  564. package/packs/mongo/rules/writes-are-atomic.md +27 -0
  565. package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
  566. package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
  567. package/packs/multi-tenant/pack.json +22 -0
  568. package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
  569. package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
  570. package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
  571. package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
  572. package/packs/nginx/checks/nginx-headers-always.json +15 -0
  573. package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
  574. package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
  575. package/packs/nginx/checks/nginx-no-crlf.json +17 -0
  576. package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
  577. package/packs/nginx/pack.json +43 -0
  578. package/packs/nginx/rules/modsecurity-tuning.md +22 -0
  579. package/packs/nginx/rules/nginx-production.md +26 -0
  580. package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
  581. package/packs/postgres/checks/pg-bind-values.json +33 -0
  582. package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
  583. package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
  584. package/packs/postgres/checks/pg-index-concurrently.json +18 -0
  585. package/packs/postgres/checks/pg-no-float-money.json +19 -0
  586. package/packs/postgres/checks/pg-no-money.json +18 -0
  587. package/packs/postgres/checks/pg-no-required-column.json +18 -0
  588. package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
  589. package/packs/postgres/checks/pg-not-exists.json +18 -0
  590. package/packs/postgres/checks/pg-timestamptz.json +18 -0
  591. package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
  592. package/packs/postgres/pack.json +48 -0
  593. package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
  594. package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
  595. package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
  596. package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
  597. package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
  598. package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -0
  599. package/packs/python/checks/py-no-bare-except.json +18 -0
  600. package/packs/python/checks/py-no-eval.json +18 -0
  601. package/packs/python/checks/py-no-mutable-default.json +18 -0
  602. package/packs/python/checks/py-no-shell-true.json +18 -0
  603. package/packs/python/checks/py-no-star-import.json +18 -0
  604. package/packs/python/checks/py-no-swallowed-exception.json +18 -0
  605. package/packs/python/checks/py-none-by-is.json +18 -0
  606. package/packs/python/checks/py-open-with-with.json +18 -0
  607. package/packs/python/checks/py-requests-timeout.json +18 -0
  608. package/packs/python/checks/py-subprocess-output-checked.json +18 -0
  609. package/packs/python/checks/py-yaml-safe-load.json +18 -0
  610. package/packs/python/pack.json +26 -0
  611. package/packs/python/rules/python-errors-and-resources.md +26 -0
  612. package/packs/python/rules/python-repeats-become-templates.md +27 -0
  613. package/packs/python/skills/python-extract-template/SKILL.md +35 -0
  614. package/packs/react/checks/dialog-needs-role.json +28 -0
  615. package/packs/react/checks/effect-not-for-derived-state.json +24 -0
  616. package/packs/react/checks/icon-button-needs-name.json +24 -0
  617. package/packs/react/checks/label-needs-a-field.json +24 -0
  618. package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
  619. package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
  620. package/packs/react/pack.json +20 -0
  621. package/packs/react/rules/react-accessible-controls.md +20 -0
  622. package/packs/react/rules/react-state-and-effects.md +23 -0
  623. package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
  624. package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
  625. package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
  626. package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
  627. package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
  628. package/packs/reverse-engineering/pack.json +35 -0
  629. package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
  630. package/packs/reverse-engineering/rules/account-every-term.md +20 -0
  631. package/packs/reverse-engineering/rules/addresses-name-their-space.md +22 -0
  632. package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
  633. package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
  634. package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
  635. package/packs/reverse-engineering/rules/names-before-code.md +19 -0
  636. package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
  637. package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
  638. package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
  639. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +22 -0
  640. package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
  641. package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
  642. package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
  643. package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
  644. package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
  645. package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
  646. package/packs/rust/checks/rust-allow-needs-reason.json +16 -0
  647. package/packs/rust/checks/rust-blocking-in-async.json +32 -0
  648. package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
  649. package/packs/rust/checks/rust-static-mut.json +32 -0
  650. package/packs/rust/checks/rust-string-as-error.json +34 -0
  651. package/packs/rust/checks/rust-stub-left-in.json +32 -0
  652. package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
  653. package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
  654. package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
  655. package/packs/rust/clippy.toml +5 -0
  656. package/packs/rust/pack.json +27 -0
  657. package/packs/rust/rules/rust-async-never-blocks.md +20 -0
  658. package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
  659. package/packs/rust/rules/rust-errors-are-returned.md +22 -0
  660. package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
  661. package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
  662. package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
  663. package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
  664. package/packs/rust/skills/rust-port/SKILL.md +22 -0
  665. package/packs/rust/skills/rust-review/SKILL.md +24 -0
  666. package/packs/rust/tools/rust-lints.toml +19 -0
  667. package/packs/shell/checks/sh-mktemp.json +18 -0
  668. package/packs/shell/checks/sh-no-backticks.json +18 -0
  669. package/packs/shell/checks/sh-no-eval.json +18 -0
  670. package/packs/shell/checks/sh-no-parse-ls.json +18 -0
  671. package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
  672. package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
  673. package/packs/shell/checks/sh-quote-arguments.json +18 -0
  674. package/packs/shell/checks/sh-rm-guarded.json +18 -0
  675. package/packs/shell/checks/sh-strict-mode.json +20 -0
  676. package/packs/shell/pack.json +28 -0
  677. package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
  678. package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
  679. package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
  680. package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
  681. package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
  682. package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
  683. package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
  684. package/packs/shell-hooks/pack.json +36 -0
  685. package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
  686. package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
  687. package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
  688. package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
  689. package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
  690. package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
  691. package/packs/sound-design/pack.json +22 -0
  692. package/packs/sound-design/rules/algorithms-are-data.md +26 -0
  693. package/packs/sound-design/rules/meter-before-engine.md +27 -0
  694. package/packs/sound-design/rules/neutral-is-exact.md +26 -0
  695. package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
  696. package/packs/sound-design/rules/unreachable-is-said.md +25 -0
  697. package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
  698. package/packs/sound-design/skills/bounce-a-plugin/SKILL.md +40 -0
  699. package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
  700. package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
  701. package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
  702. package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
  703. package/packs/sound-design/tools/vst3host/build.sh +73 -0
  704. package/packs/sound-design/tools/vst3host/host_events.h +101 -0
  705. package/packs/sound-design/tools/vst3host/options.h +172 -0
  706. package/packs/sound-design/tools/vst3host/render.cpp +245 -0
  707. package/packs/sound-design/tools/vst3host/session.h +241 -0
  708. package/packs/stellix/checks/no-keyboard-focus.json +12 -0
  709. package/packs/stellix/checks/one-seeded-random.json +16 -0
  710. package/packs/stellix/checks/world-never-includes-painter.json +12 -0
  711. package/packs/stellix/checks/world-no-libm.json +15 -0
  712. package/packs/stellix/checks/world-no-unordered.json +10 -0
  713. package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
  714. package/packs/stellix/pack.json +61 -0
  715. package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
  716. package/packs/stellix/rules/stellix-reset-generated.md +24 -0
  717. package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
  718. package/packs/stellix/rules/world-continuous-collision.md +26 -0
  719. package/packs/stellix/rules/world-fp-determinism.md +28 -0
  720. package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
  721. package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
  722. package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
  723. package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
  724. package/packs/stored-text/pack.json +41 -0
  725. package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
  726. package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
  727. package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
  728. package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
  729. package/packs/synth-creator/hooks/build-guard.mjs +232 -0
  730. package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
  731. package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
  732. package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
  733. package/packs/synth-creator/hooks/protect-vendor.mjs +175 -0
  734. package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
  735. package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
  736. package/packs/synth-creator/hooks/time-guard.mjs +292 -0
  737. package/packs/synth-creator/hooks/user-asks.mjs +165 -0
  738. package/packs/synth-creator/pack.json +136 -0
  739. package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
  740. package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
  741. package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
  742. package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
  743. package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
  744. package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
  745. package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
  746. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +21 -0
  747. package/packs/synth-creator/tools/buildq.mjs +74 -0
  748. package/packs/synth-creator/tools/freeze.mjs +36 -0
  749. package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
  750. package/packs/tui/checks/tui-gates-on-tty.json +25 -0
  751. package/packs/tui/checks/tui-no-ris-reset.json +22 -0
  752. package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
  753. package/packs/tui/checks/tui-width-is-state.json +33 -0
  754. package/packs/tui/pack.json +44 -0
  755. package/packs/tui/rules/tui-resize-and-screen.md +25 -0
  756. package/packs/tui/skills/build-tui/SKILL.md +21 -0
  757. package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
  758. package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
  759. package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
  760. package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
  761. package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
  762. package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
  763. package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
  764. package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
  765. package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
  766. package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
  767. package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
  768. package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
  769. package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
  770. package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
  771. package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
  772. package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
  773. package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
  774. package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
  775. package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
  776. package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
  777. package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
  778. package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
  779. package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
  780. package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
  781. package/packs/ui-craft/pack.json +43 -0
  782. package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
  783. package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
  784. package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
  785. package/packs/ui-craft/rules/forms-never-fight.md +22 -0
  786. package/packs/ui-craft/rules/interface-copy.md +26 -0
  787. package/packs/ui-craft/rules/motion-is-rare.md +31 -0
  788. package/packs/ui-craft/rules/no-generated-look.md +37 -0
  789. package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
  790. package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
  791. package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
  792. package/packs/ui-craft/rules/pointer-feedback.md +28 -0
  793. package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
  794. package/packs/ui-craft/rules/survives-real-data.md +23 -0
  795. package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
  796. package/packs/ui-craft/rules/type-space-colour.md +26 -0
  797. package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
  798. package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
  799. package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
  800. package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
  801. package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
  802. package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
  803. package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
  804. package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
  805. package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
  806. package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
  807. package/packs/ui-craft/tools/page-measure.mjs +41 -0
  808. package/packs/web/checks/a11y-img-needs-alt.json +26 -0
  809. package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
  810. package/packs/web/checks/a11y-no-clickable-div.json +26 -0
  811. package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
  812. package/packs/web/checks/client-no-api-key.json +29 -0
  813. package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
  814. package/packs/web/checks/client-no-server-import.json +31 -0
  815. package/packs/web/checks/client-no-window-location-nav.json +28 -0
  816. package/packs/web/checks/css-no-inline-style.json +30 -0
  817. package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
  818. package/packs/web/checks/key-from-index.json +23 -0
  819. package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
  820. package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
  821. package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
  822. package/packs/web/checks/rcss-button-cursor.json +30 -0
  823. package/packs/web/checks/rcss-no-100vw.json +21 -0
  824. package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
  825. package/packs/web/checks/rcss-viewport-meta.json +28 -0
  826. package/packs/web/checks/secret-not-in-web-storage.json +25 -0
  827. package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
  828. package/packs/web/checks/wp-hero-not-lazy.json +27 -0
  829. package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
  830. package/packs/web/pack.json +73 -0
  831. package/packs/web/rules/accessibility.md +26 -0
  832. package/packs/web/rules/client-boundaries.md +23 -0
  833. package/packs/web/rules/production-build.md +22 -0
  834. package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
  835. package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
  836. package/packs/web/rules/tailwind-v4.md +23 -0
  837. package/packs/web/rules/ui-primitives-one-module.md +30 -0
  838. package/packs/web/rules/web-performance.md +21 -0
  839. package/packs/web/skills/design-direction/SKILL.md +30 -0
  840. package/packs/web/skills/design-review/SKILL.md +28 -0
  841. package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
  842. package/packs/web/skills/preflight-build/SKILL.md +23 -0
  843. package/packs/web/skills/seo-audit/SKILL.md +22 -0
  844. package/packs/websocket/checks/no-token-in-query.json +26 -0
  845. package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
  846. package/packs/websocket/checks/ws-identity-from-message.json +26 -0
  847. package/packs/websocket/pack.json +21 -0
  848. package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
  849. package/packs/writing/checks/ascii-no-em-dash.json +17 -0
  850. package/packs/writing/checks/no-copula-dodging.json +18 -0
  851. package/packs/writing/checks/no-dramatic-colon.json +18 -0
  852. package/packs/writing/checks/no-not-x-but-y.json +18 -0
  853. package/packs/writing/checks/no-participial-opener.json +18 -0
  854. package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
  855. package/packs/writing/checks/no-slop-words.json +18 -0
  856. package/packs/writing/checks/no-stock-connectors.json +18 -0
  857. package/packs/writing/checks/no-time-or-date.json +27 -0
  858. package/packs/writing/checks/no-trailing-ing.json +18 -0
  859. package/packs/writing/checks/no-vague-attribution.json +18 -0
  860. package/packs/writing/pack.json +43 -0
  861. package/packs/writing/rules/ascii-no-em-dash.md +21 -0
  862. package/packs/writing/rules/cut-the-padding.md +20 -0
  863. package/packs/writing/rules/direct-voice.md +22 -0
  864. package/packs/writing/rules/no-cargo-cult.md +16 -0
  865. package/packs/writing/rules/no-scaffolding.md +19 -0
  866. package/packs/writing/rules/no-stock-language.md +27 -0
  867. package/packs/writing/rules/no-time-or-date.md +18 -0
  868. package/packs/writing/rules/rhythm-like-talk.md +20 -0
  869. package/packs/writing/rules/write-from-your-own-corner.md +20 -0
  870. package/packs/writing/skills/argument-post/SKILL.md +28 -0
  871. package/packs/writing/skills/humanize/SKILL.md +28 -0
  872. package/packs/writing/skills/launch-post/SKILL.md +38 -0
  873. package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
  874. package/packs/writing/skills/post-voice/SKILL.md +22 -0
  875. package/packs/writing/skills/reddit-post/SKILL.md +63 -0
  876. package/packs/writing/skills/reddit-title/SKILL.md +23 -0
  877. package/packs/writing/skills/reply-moves/SKILL.md +40 -0
  878. package/packs/writing/skills/reply-stance/SKILL.md +23 -0
  879. package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
  880. package/packs/zod/hooks/schemas-only.mjs +53 -0
  881. package/packs/zod/pack.json +44 -0
  882. package/packs/zod/rules/env-shapes-only.md +14 -0
  883. package/packs/zod/rules/zod-4-schemas.md +14 -0
  884. package/profiles/stellix.layout.json +315 -0
  885. package/profiles/sub37.layout.json +247 -0
  886. package/prompts/scan-review.md +40 -0
  887. package/prompts/survey.md +82 -0
  888. package/prompts/workorder/PROMPT.md +53 -0
  889. package/prompts/workorder/run.sh +36 -0
  890. package/prompts/workorder/workorder.md +59 -0
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ // time-guard: nothing is slow without someone being told (after a night of two-hour panel exports that
3
+ // every agent, and the session watching them, took for normal).
4
+ //
5
+ // PreToolUse + PostToolUse on every tool, for the session and every agent it starts. After a call it tells the caller
6
+ // (exit 2 on PostToolUse: the call has run, nothing is blocked, the words reach the agent) when:
7
+ // 1. THE CALL ITSELF TOOK LONG: more than SLOW_CALL_MIN minutes (a question to the user never counts);
8
+ // 2. SOMETHING ON THE MACHINE IS SLOW, whoever started it (looked at once a minute at most): a panel export older than
9
+ // EXPORT_MIN, more than CHROME_MAX headless Chromes or one older than CHROME_MIN, a build older than BUILD_MIN, a ctest
10
+ // run older than CTEST_MIN, a probe older than PROBE_MIN (guards.time.probePattern says what a probe is), a wait
11
+ // loop older than WAIT_LOOP_MIN;
12
+ // 3. AN AGENT'S WORKTREE HAS NOT MOVED: work unsaved for SAVE_MIN, no commit for QUIET_MIN, or two agents in one;
13
+ // 4. THE CONTROLLER sees every worktree holding work unsaved for long, every SCAN_MIN minutes;
14
+ // and before a call it REFUSES a wait that can never end: `until ! pgrep -f "<words>"`, whose pgrep finds the loop's own
15
+ // command line (a heredoc body is text, not a loop). A finding is said again only after REPEAT_MIN minutes.
16
+ // Project values: .claude/layout.json "guards": { "time": { "probePattern", "saveCommand" } }.
17
+ // node time-guard.mjs --selftest
18
+ import { execFileSync, spawn } from 'node:child_process';
19
+ import { mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
20
+ import { homedir, tmpdir } from 'node:os';
21
+ import { basename, join, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { readLayout } from './lib/layout.mjs';
24
+ import { asHook, isMain } from './lib/hook-log.mjs';
25
+
26
+ export const DEFAULTS = {
27
+ probePattern: '\\S*/\\w*(?:Probe|_probe)\\b', // the probe binary itself, never a shell that names one
28
+ saveCommand: 'git add <paths> && git commit -m "<note>" -- <paths>',
29
+ };
30
+ const CFG = { ...DEFAULTS };
31
+ const SLOW_CALL_MIN = 10, EXPORT_MIN = 5, CHROME_MAX = 6, CHROME_MIN = 5, BUILD_MIN = 30, CTEST_MIN = 45, PROBE_MIN = 20;
32
+ const QUIET_MIN = 60, SAVE_MIN = 30, SCAN_MIN = 15, WAIT_LOOP_MIN = 60, REPEAT_MIN = 10, LOOK_EVERY_S = 60;
33
+
34
+ const projectRoot = resolve(process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replace(/\/\.claude\/worktrees\/[^/]+(\/.*)?$/, '');
35
+ const STATE = join(process.env.TMPDIR ?? tmpdir(), `time-guard-${basename(projectRoot).replace(/[^\w.-]/g, '-') || 'project'}`);
36
+ const sh = (cmd, args, cwd) => { try { return execFileSync(cmd, args, { cwd, encoding: 'utf8', timeout: 10000, stdio: ['ignore', 'pipe', 'ignore'] }); } catch { return null; } };
37
+
38
+ export function etimeS(s) {
39
+ const t = s.trim();
40
+ const dash = t.lastIndexOf('-');
41
+ const days = dash >= 0 ? Number(t.slice(0, dash)) : 0;
42
+ const parts = (dash >= 0 ? t.slice(dash + 1) : t).split(':').map(Number);
43
+ while (parts.length < 3) parts.unshift(0);
44
+ return days * 86400 + parts[0] * 3600 + parts[1] * 60 + parts[2];
45
+ }
46
+
47
+ function processes() {
48
+ const out = sh('ps', ['-axo', 'pid=,etime=,command=']) ?? '';
49
+ return out.split('\n').map((l) => l.match(/^\s*(\d+)\s+(\S+)\s+(.*)$/)).filter(Boolean).map((m) => [Number(m[1]), etimeS(m[2]), m[3]]);
50
+ }
51
+
52
+ export function machineFindings(rows) {
53
+ const f = [];
54
+ const mins = (s) => s / 60;
55
+ const headless = rows.filter((r) => /Google Chrome(?: for Testing)?(?:\.app\/Contents\/MacOS\/\S+)?\s.*--headless/.test(r[2]) && !r[2].includes('Helper'));
56
+ if (headless.length > CHROME_MAX) {
57
+ f.push(['chrome-count', `${headless.length} headless Chromes are running at once (more than ${CHROME_MAX}): something is starting one per picture again. ` +
58
+ 'Find it (ps -axo pid,etime,command | grep -- --headless) and stop it; Chrome is started only by kit/synthpanel-creator/src/onechrome.js, for a whole batch']);
59
+ }
60
+ for (const [pid, age] of headless) {
61
+ if (mins(age) > CHROME_MIN) f.push([`chrome-old-${pid}`, `a headless Chrome (pid ${pid}) has run ${mins(age).toFixed(0)} min: it is stuck or leaked. Find who started it and why it never finished; stop it by its pid`]);
62
+ }
63
+ const probe = new RegExp(`^(?:${CFG.probePattern})`);
64
+ for (const [pid, age, cmd] of rows) {
65
+ const m = mins(age).toFixed(0);
66
+ if (/node \S*synthpanel\S*\.js export-native/.test(cmd) && mins(age) > EXPORT_MIN) {
67
+ f.push([`export-${pid}`, `a panel export (pid ${pid}) has run ${m} min; one takes seconds since B-382 (one Chrome, a cache of every picture). It is stuck: find why before waiting longer`]);
68
+ } else if (/^\S*ctest\b/.test(cmd) && mins(age) > CTEST_MIN) {
69
+ f.push([`ctest-${pid}`, `a ctest run (pid ${pid}) has run ${m} min (more than ${CTEST_MIN}): find the slow test (ctest prints each as it ends) instead of waiting for all of them`]);
70
+ } else if (/^\S*(?:cmake\s+--build|\/make|make|ninja)\b/.test(cmd) && mins(age) > BUILD_MIN) { // the build itself, not a shell naming it
71
+ f.push([`build-${pid}`, `a build (pid ${pid}) has run ${m} min (more than ${BUILD_MIN}): look at the load (uptime) and what else is building`]);
72
+ } else if (probe.test(cmd) && mins(age) > PROBE_MIN) {
73
+ f.push([`probe-${pid}`, `a probe (pid ${pid}: ${cmd.split(/\s+/)[0].split('/').pop()}) has run ${m} min (more than ${PROBE_MIN}): every probe has a time limit; find what it is waiting on`]);
74
+ } else if (/\buntil\b.*;\s*do\s+sleep\b/.test(cmd) && mins(age) > WAIT_LOOP_MIN) {
75
+ f.push([`waitloop-${pid}`, `a wait loop (pid ${pid}) has run ${m} min: whatever it waits for is finished or dead. Stop it (kill ${pid}); wait on a background command's own notification instead of a loop`]);
76
+ }
77
+ }
78
+ return f;
79
+ }
80
+
81
+ function lastCommitMin(cwd) {
82
+ const out = sh('git', ['log', '-1', '--format=%ct'], cwd)?.trim();
83
+ return out ? (Date.now() / 1000 - Number(out)) / 60 : null;
84
+ }
85
+
86
+ // Minutes since the OLDEST change not yet committed in a worktree (build folders left out); null when it is clean.
87
+ function unsavedMin(wt) {
88
+ const out = sh('git', ['--no-optional-locks', '-C', wt, 'status', '--porcelain', '--untracked-files=all']);
89
+ if (out === null) return null;
90
+ let oldest = null;
91
+ for (const line of out.split('\n')) {
92
+ const f = line.slice(3).trim().replace(/^"|"$/g, '');
93
+ if (!f || /^(build|listen\/|screenshots\/)/.test(f) || f.includes('/build')) continue;
94
+ try { const t = statSync(join(wt, f)).mtimeMs / 1000; oldest = oldest === null ? t : Math.min(oldest, t); } catch { /* gone */ }
95
+ }
96
+ return oldest === null ? null : (Date.now() / 1000 - oldest) / 60;
97
+ }
98
+
99
+ function listWorktrees() {
100
+ const root = join(projectRoot, '.claude', 'worktrees');
101
+ try { return readdirSync(root).sort().map((d) => join(root, d)).filter((d) => { try { statSync(join(d, '.git')); return true; } catch { return false; } }); } catch { return []; }
102
+ }
103
+
104
+ // Is this agent still running, by the cap hook's count? Unknown state counts as running (the safe side).
105
+ const CLAIM_LAPSE_MIN = 15;
106
+
107
+ function agentRunning(aid) {
108
+ let st;
109
+ try { st = JSON.parse(readFileSync(join(homedir(), '.claude/agent-cap-state.json'), 'utf8')); } catch { return true; }
110
+ const sessions = Object.values(st).filter((s) => s && typeof s === 'object');
111
+ if (sessions.some((s) => aid in (s.running ?? {}))) return true;
112
+ return !sessions.some((s) => aid in (s.known ?? {}));
113
+ }
114
+
115
+ function sayOnce(key, msg, now, st) {
116
+ if (now - (st[key] ?? 0) < REPEAT_MIN * 60) return null;
117
+ st[key] = now;
118
+ return msg;
119
+ }
120
+
121
+ export function run(event, { now = Date.now() / 1000, rows = null, commitMin = null, state = null, worktrees = null, late = null } = {}) {
122
+ const st = state ?? loadState();
123
+ const said = [];
124
+ const tid = event.tool_use_id ?? '';
125
+ const input = event.tool_input ?? {};
126
+ if (event.hook_event_name === 'PreToolUse') {
127
+ let cmd = event.tool_name === 'Bash' ? String(input.command ?? '') : '';
128
+ cmd = cmd.replace(/<<-?\s*['"]?(\w+)['"]?[^\n]*\n[\s\S]*?\n\s*\1\s*(?:\n|$)/g, '\n'); // a heredoc body is text, not a loop
129
+ if (/until\s+!\s*pgrep\s+-[a-z]*f\s+(['"])(?!\[)/.test(cmd)) {
130
+ return ['REFUSED: `until ! pgrep -f "..."` never ends: pgrep -f finds the loop\'s own command line, which holds the same words. Run the work ' +
131
+ "with run_in_background and wait for its notification; or, if you must poll, make the pattern unable to match itself: pgrep -f '[T]abulaFaceProbe --x'"];
132
+ }
133
+ if (tid) (st.starts ??= {})[tid] = now;
134
+ if (!state) saveState(st);
135
+ return [];
136
+ }
137
+ // 1. the call itself
138
+ const started = tid ? st.starts?.[tid] : undefined;
139
+ if (tid && st.starts) delete st.starts[tid];
140
+ if (started !== undefined && event.tool_name !== 'AskUserQuestion' && (now - started) / 60 > SLOW_CALL_MIN) {
141
+ said.push(`this call took ${((now - started) / 60).toFixed(0)} min (more than ${SLOW_CALL_MIN}): \`${String(input.command ?? event.tool_name ?? '').slice(0, 80)}\`. Find what made it slow before running it again; never wait on it as normal`);
142
+ }
143
+ // 2. the machine, once a minute at most
144
+ if (now - (st.looked ?? 0) >= LOOK_EVERY_S || rows !== null) {
145
+ st.looked = now;
146
+ for (const [key, msg] of machineFindings(rows ?? processes())) { const m = sayOnce(key, msg, now, st); if (m) said.push(m); }
147
+ }
148
+ // 3. each agent's own worktree, learned from the commands it runs (.claude/worktrees/<name>)
149
+ const who = event.agent_id ?? 'main';
150
+ if (who !== 'main') (st.seen ??= {})[who] = now; // when each agent last ran anything: a finished agent's claim lapses (below)
151
+ const text = `${input.command ?? ''} ${input.file_path ?? ''}`;
152
+ for (const m of text.matchAll(/(\/[^\s'"]*?\/\.claude\/worktrees\/([\w.-]+))/g)) {
153
+ if (who === 'main') continue;
154
+ st.owner ??= {};
155
+ let prev = st.owner[m[2]];
156
+ // a claim lapses with its agent: known stopped, or quiet for CLAIM_LAPSE_MIN (the agent-cap state that said "stopped" is no
157
+ // longer written, so without the quiet rule a finished agent kept its worktree for ever and no successor could reuse its build)
158
+ if (prev && prev !== who && (!agentRunning(prev) || now - (st.seen?.[prev] ?? 0) > CLAIM_LAPSE_MIN * 60)) prev = null;
159
+ if (prev && prev !== who) {
160
+ const s = sayOnce(`shared-${m[2]}`, `the worktree .claude/worktrees/${m[2]} is already used by another agent (${prev}). Each agent works only in its own: ` +
161
+ 'make yours (git worktree add --detach .claude/worktrees/<yours> main) and move your files there', now, st);
162
+ if (s) said.push(s);
163
+ } else {
164
+ st.owner[m[2]] = who;
165
+ (st.wt ??= {})[who] = m[1];
166
+ }
167
+ }
168
+ const mine = who !== 'main' ? st.wt?.[who] : null;
169
+ // its git status at most once a minute (KWB-24: two git commands on every call of every agent)
170
+ if (mine && (rows !== null || now - (st.checked?.[mine] ?? 0) >= LOOK_EVERY_S)) {
171
+ if (rows === null) (st.checked ??= {})[mine] = now;
172
+ const u = rows === null ? unsavedMin(mine) : null; // a selftest case: never git on a made-up path
173
+ const cmn = commitMin ?? lastCommitMin(mine);
174
+ if (u !== null && u > SAVE_MIN) {
175
+ const s = sayOnce(`unsaved-${mine}`, `your worktree (${mine.split('/').pop()}) holds work unsaved for ${u.toFixed(0)} min (more than ${SAVE_MIN}). Save it now: ${CFG.saveCommand}`, now, st);
176
+ if (s) said.push(s);
177
+ } else if (cmn !== null && cmn > QUIET_MIN) {
178
+ const s = sayOnce(`quiet-${mine}`, `your worktree's last commit was ${cmn.toFixed(0)} min ago (more than ${QUIET_MIN}). If you are waiting on something slow, find why it is slow; if the work moved, save it`, now, st);
179
+ if (s) said.push(s);
180
+ }
181
+ }
182
+ // 4. the controller sees every worktree: a git status in each runs in its own process, never in the hook (KWB-24: a synth's
183
+ // 125 worktrees took over a minute, the hook was killed at its timeout before it saved `scanned`, so every call scanned again)
184
+ const scan = worktrees ?? (rows !== null ? {} : null);
185
+ const tell = (list) => list.length && said.push(`worktrees holding work unsaved for more than ${SAVE_MIN} min: ${list.join(', ')}. Ask their agents to save (${CFG.saveCommand}), or find why they stall`);
186
+ if (who === 'main' && (now - (st.scanned ?? 0) >= SCAN_MIN * 60 || scan !== null)) {
187
+ st.scanned = now;
188
+ if (scan) tell(lateOf(scan));
189
+ else { saveState(st); scanLater(); }
190
+ }
191
+ const found = late ?? (rows === null ? readLate() : null);
192
+ if (who === 'main' && found && found.at > (st.lateTold ?? 0)) { st.lateTold = found.at; tell(found.list); }
193
+ st.starts = Object.fromEntries(Object.entries(st.starts ?? {}).filter(([, v]) => now - v < 86400));
194
+ if (!state) saveState(st);
195
+ return said;
196
+ }
197
+
198
+ const lateOf = (unsaved) => Object.entries(unsaved).filter(([, u]) => u !== null && u > SAVE_MIN).map(([wt, u]) => `${wt.split('/').pop()} (${u.toFixed(0)} min)`);
199
+ const LATE = join(STATE, 'late.json');
200
+ function readLate() { try { return JSON.parse(readFileSync(LATE, 'utf8')); } catch { return null; } }
201
+ function scanLater() { try { spawn(process.execPath, [fileURLToPath(import.meta.url), '--scan-worktrees'], { cwd: projectRoot, detached: true, stdio: 'ignore', env: process.env }).unref(); } catch { /* next time */ } }
202
+ function scanNow() {
203
+ const list = lateOf(Object.fromEntries(listWorktrees().map((wt) => [wt, unsavedMin(wt)])));
204
+ try { mkdirSync(STATE, { recursive: true }); writeFileSync(`${LATE}.${process.pid}`, JSON.stringify({ at: Date.now() / 1000, list })); renameSync(`${LATE}.${process.pid}`, LATE); } catch { /* next time */ }
205
+ return 0;
206
+ }
207
+
208
+ function loadState() { try { return JSON.parse(readFileSync(join(STATE, 'state.json'), 'utf8')); } catch { return {}; } }
209
+ function saveState(st) {
210
+ try {
211
+ mkdirSync(STATE, { recursive: true });
212
+ const tmp = join(STATE, `state.${process.pid}.json`);
213
+ writeFileSync(tmp, JSON.stringify(st));
214
+ renameSync(tmp, join(STATE, 'state.json'));
215
+ } catch { /* the guard never stops work by failing itself */ }
216
+ }
217
+
218
+ function selftest() {
219
+ let bad = 0;
220
+ const expect = (name, said, want) => { if ((said.length > 0) !== want) { bad += 1; console.error(`FAIL ${name}: ${said.length ? 'said' : 'quiet'}`); } };
221
+ const G = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --headless=new --disable-gpu';
222
+ const post = (extra = {}) => ({ hook_event_name: 'PostToolUse', ...extra });
223
+ const at = (rows, extra = {}) => ({ now: 5000, state: {}, rows, commitMin: 1, ...extra });
224
+ const st = {};
225
+ run({ hook_event_name: 'PreToolUse', tool_use_id: 'a' }, { now: 1000, state: st, rows: [], commitMin: 1 });
226
+ expect('a call of 12 min', run(post({ tool_use_id: 'a', tool_input: { command: 'bash tools/build-panel.sh' } }), { now: 1720, state: st, rows: [], commitMin: 1 }), true);
227
+ run({ hook_event_name: 'PreToolUse', tool_use_id: 'b' }, { now: 2000, state: st, rows: [], commitMin: 1 });
228
+ expect('a call of 2 min', run(post({ tool_use_id: 'b' }), { now: 2120, state: st, rows: [], commitMin: 1 }), false);
229
+ const exp = 'node /x/kit/synthpanel-creator/bin/synthpanel.js export-native design/export-spec.json';
230
+ expect('an export of 7 min', run(post(), at([[1, 420, exp]])), true);
231
+ expect('an export of 30 s', run(post(), at([[1, 30, exp]])), false);
232
+ expect('12 headless Chromes', run(post(), at(Array.from({ length: 12 }, (_, i) => [i, 10, G]))), true);
233
+ expect('2 headless Chromes of 10 s', run(post(), at([[0, 10, G], [1, 10, G]])), false);
234
+ expect('a headless Chrome of a day', run(post(), at([[9, 93600, G]])), true);
235
+ expect('a ctest of an hour', run(post(), at([[3, 3600, 'ctest --test-dir build -j4']])), true);
236
+ expect('a probe of 25 min', run(post(), at([[4, 1500, '/x/build/SynthFaceProbe_artefacts/RelWithDebInfo/SynthFaceProbe --press-all']])), true);
237
+ const A = (command, who = 'a1') => post({ agent_id: who, tool_input: { command } });
238
+ expect("an agent's worktree quiet for 90 min", run(A('cd /x/.claude/worktrees/k2 && make'), at([], { commitMin: 90 })), true);
239
+ const s2 = {};
240
+ run(A('cd /x/.claude/worktrees/k2 && make'), { now: 5000, state: s2, rows: [], commitMin: 90 });
241
+ expect('the same said again a minute later', run(A('ls'), { now: 5060, state: s2, rows: [], commitMin: 90 }), false);
242
+ const s3 = {};
243
+ run(A('cd /x/.claude/worktrees/onechrome && ls', 'main-ish'), { now: 5000, state: s3, rows: [], commitMin: 1 });
244
+ expect('a second agent in the same worktree', run(A('cd /x/.claude/worktrees/onechrome && vi', 'a2'), { now: 5001, state: s3, rows: [], commitMin: 1 }), true);
245
+ const s4 = {};
246
+ run(A('cd /x/.claude/worktrees/handed && ls', 'first'), { now: 5000, state: s4, rows: [], commitMin: 1 });
247
+ expect('a successor after the first agent has been quiet 16 min takes the worktree', run(A('cd /x/.claude/worktrees/handed && ls', 'next'), { now: 5000 + 16 * 60, state: s4, rows: [], commitMin: 1 }), false);
248
+ expect('and the first agent coming back finds it taken', run(A('cd /x/.claude/worktrees/handed && ls', 'first'), { now: 5000 + 17 * 60, state: s4, rows: [], commitMin: 1 }), true);
249
+ expect('the controller told of a worktree unsaved for 45 min', run(post(), at([], { worktrees: { '/x/.claude/worktrees/guide': 45 } })), true);
250
+ expect('the controller, nothing unsaved', run(post(), at([], { worktrees: { '/x/.claude/worktrees/guide': 5 } })), false);
251
+ const told = {};
252
+ const found = { at: 4000, list: ['guide (45 min)'] };
253
+ expect('the background scan\'s finding is told', run(post(), at([], { state: told, late: found })), true);
254
+ expect('and told once', run(post(), { ...at([], { state: told, late: found }), now: 5100 }), false);
255
+ const pre = (command) => run({ hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: { command } }, { now: 5000, state: {} });
256
+ const loop = ['until', '!', 'pgrep', '-f', '"cmake --build"', '>/dev/null;', 'do', 'sleep', '5;', 'done'].join(' ');
257
+ expect('the endless wait loop inside a heredoc (text for a program) passes', pre(`python3 - <<'EOF'\nprint('${loop}')\nEOF\necho ok`), false);
258
+ expect('the same loop after a heredoc is still refused', pre(`cat > a.txt <<EOF\nx\nEOF\n${loop}`), true);
259
+ expect('REFUSED: the endless wait loop', pre(loop), true);
260
+ expect('a pgrep that cannot match itself passes', pre(loop.replace('"cmake --build"', "'[c]make --build'")), false);
261
+ expect('a shell that names a build is not a build', run(post(), at([[8, 3600, '/bin/zsh -c cmake --build build -j6 && ctest']])), false);
262
+ expect('a build of 40 min', run(post(), at([[8, 2400, 'cmake --build build -j6']])), true);
263
+ expect('a shell that names a probe is not a probe', run(post(), at([[9, 3000, '/bin/zsh -c ./build/SynthFaceProbe_artefacts/RelWithDebInfo/SynthFaceProbe --walk x']])), false);
264
+ expect('a quiet machine', run(post(), at([[5, 4000, '/bin/zsh'], [6, 20, 'cmake --build build -j4']], { commitMin: 5, worktrees: {} })), false);
265
+ const wait = "/bin/zsh -c eval 'until grep -q exit /tmp/x.log; do sleep 15; done'";
266
+ expect('a wait loop of a day', run(post(), at([[7, 90000, wait]])), true);
267
+ expect('a wait loop of 5 min', run(post(), at([[7, 300, wait]])), false);
268
+ expect('a question answered after 12 min is not a slow call (WB-22)', run(post({ tool_name: 'AskUserQuestion', tool_use_id: 'q1' }), { now: 5720, state: { starts: { q1: 5000 } }, rows: [] }), false);
269
+ expect('a Bash call of 12 min still is', run(post({ tool_name: 'Bash', tool_use_id: 'b1', tool_input: { command: 'x' } }), { now: 5720, state: { starts: { b1: 5000 } }, rows: [] }), true);
270
+ expect('a snake-case probe of 25 min', run(post(), at([[4, 1500, '/x/out/probes/engines/synth_engines_probe --only FIRESAW']])), true);
271
+ CFG.probePattern = '\\S*/bench_\\w+'; // a project's own probe pattern (guards.time.probePattern)
272
+ expect("a project's own probe pattern: its bench of 25 min", run(post(), at([[4, 1500, '/x/out/bench_voices --all']])), true);
273
+ expect("a project's own probe pattern: another program is no probe", run(post(), at([[4, 1500, '/x/out/synth_engines_probe']])), false);
274
+ Object.assign(CFG, DEFAULTS);
275
+ if (etimeS('1-02:03:04') !== 93784 || etimeS('05:06') !== 306) { bad += 1; console.error('FAIL ps etime'); }
276
+ console.log(`time-guard selftest: ${bad ? `${bad} wrong` : 'OK'}`);
277
+ return bad ? 1 : 0;
278
+ }
279
+
280
+ function main(text) {
281
+ let event;
282
+ try { event = JSON.parse(text); } catch { return 0; }
283
+ Object.assign(CFG, DEFAULTS, readLayout(projectRoot).guards?.time ?? {});
284
+ let said;
285
+ try { said = run(event); } catch { return 0; } // the guard never stops work by failing itself
286
+ if (!said.length) return 0;
287
+ process.stderr.write(`time-guard: ${said.join('\n time-guard: ')}\n`);
288
+ return 2;
289
+ }
290
+
291
+ export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
292
+ if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : process.argv.includes('--scan-worktrees') ? scanNow() : await asHook('time-guard', main));
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ // user-asks: everything the user asked, back in front of the session, so nothing discussed is forgotten (2026-10-11:
3
+ // "Sometimes you 'forget' certain things we had discussed ... maybe you also need to check memory at this time?").
4
+ //
5
+ // Every message the user sends is in the session's transcript: a turn of their own (type `user`, their text) or a message sent
6
+ // while the session was working (a `queue-operation` enqueue). This reads them back, with their times, two ways:
7
+ // - a SessionStart hook on `compact`: their asks of the last HOURS hours go back into the context, word for word;
8
+ // - idle-wake.mjs: the wake after their silence carries the same list.
9
+ // THE LEDGER: each ask has a status and its evidence, beside the project's own memory folder
10
+ // (~/.claude/projects/<the project's path, every character not a letter or digit made a dash>/asks-ledger.json), outside
11
+ // git, so compaction cannot touch it. Only OPEN asks come back word for word; done or answered ones are a count; needs-user
12
+ // and planned ones are one line each. Marking takes evidence (a commit, a bug number, a build.md step, or where it is planned):
13
+ // node user-asks.mjs --transcript <path> [--hours 24] [--all]
14
+ // node user-asks.mjs --mark <id> done|answered|planned|needs-user|not-an-ask "<evidence>"
15
+ // node user-asks.mjs --selftest
16
+ import { createHash } from 'node:crypto';
17
+ import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { homedir, tmpdir } from 'node:os';
19
+ import { dirname, join, resolve } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { asHook, isMain } from './lib/hook-log.mjs';
22
+
23
+ export const HOURS = Number(process.env.TIMS_ASKS_HOURS ?? 24);
24
+ const STATUSES = new Set(['done', 'answered', 'planned', 'needs-user', 'not-an-ask', 'open']);
25
+ const QUIET = new Set(['done', 'answered', 'not-an-ask']);
26
+ const HEAD = (h, n) => `TIM'S ASKS from the last ${h.toFixed(0)} hours (${n} messages). Compaction may have dropped some of them from what you remember; ` +
27
+ 'the ledger below holds which are done, with evidence. Every OPEN one is work: check it is not already done (then mark it with the evidence), ' +
28
+ 'finish it, or if it needs the user say so in one line and mark it needs-user. Do not wait to be reminded.';
29
+
30
+ const mainRoot = (p) => p.replace(/\/\.claude\/worktrees\/[^/]+(\/.*)?$/, '');
31
+ export const ledgerFor = (root) => join(homedir(), '.claude', 'projects', mainRoot(resolve(root)).replace(/[^A-Za-z0-9]/g, '-'), 'asks-ledger.json');
32
+ export const LEDGER = process.env.TIMS_ASKS_LEDGER || ledgerFor(process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
33
+
34
+ // The user's own words in a transcript entry, or null (a tool's result, a hook's or the system's text, an agent's report).
35
+ function textOf(e) {
36
+ if (e.type === 'queue-operation' && e.operation === 'enqueue') return typeof e.content === 'string' ? e.content : null;
37
+ if (e.type === 'user' && !e.isMeta && !e.isSidechain) {
38
+ let c = e.message?.content;
39
+ if (Array.isArray(c)) c = c.filter((b) => b && b.type === 'text').map((b) => b.text ?? '').join('\n');
40
+ return typeof c === 'string' ? c : null;
41
+ }
42
+ return null;
43
+ }
44
+ const isSystem = (t) => { const s = t.trimStart(); return !s || /^(<|\[SYSTEM|\[Subagent|idle-wake:|This session is being continued|Caveat:)/.test(s); };
45
+ const squash = (t) => t.split(/\s+/).filter(Boolean).join(' ');
46
+
47
+ export function asks(transcript, hours = HOURS, now = Date.now() / 1000) {
48
+ let text;
49
+ try { text = readFileSync(transcript, 'utf8'); } catch { return []; }
50
+ const out = [];
51
+ const seen = new Set();
52
+ for (const line of text.split('\n')) {
53
+ if (!line.includes('"queue-operation"') && !line.includes('"type":"user"') && !line.includes('"type": "user"')) continue;
54
+ let e;
55
+ try { e = JSON.parse(line); } catch { continue; }
56
+ const t = textOf(e);
57
+ if (!t || isSystem(t)) continue;
58
+ const when = e.timestamp ? Date.parse(e.timestamp) / 1000 : null;
59
+ if (when !== null && !Number.isNaN(when) && now - when > hours * 3600) continue;
60
+ const key = squash(t);
61
+ if (seen.has(key)) continue;
62
+ seen.add(key);
63
+ out.push([Number.isNaN(when) ? null : when, t.trim()]);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ export const askId = (t) => createHash('sha1').update(squash(t)).digest('hex').slice(0, 8);
69
+ export function loadLedger(path = LEDGER) { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return {}; } }
70
+
71
+ export function mark(id, status, evidence = '', path = LEDGER) {
72
+ if (!STATUSES.has(status)) throw new Error(`status is one of ${[...STATUSES].sort().join(', ')}`);
73
+ if (status !== 'open' && !evidence.trim()) throw new Error('evidence is required: a commit, a bug number, a build.md step, or where it is planned');
74
+ const led = loadLedger(path);
75
+ led[id] = { status, evidence: evidence.trim(), marked: new Date().toISOString().slice(0, 19) };
76
+ mkdirSync(dirname(path), { recursive: true });
77
+ writeFileSync(`${path}.tmp`, JSON.stringify(led, null, 1));
78
+ renameSync(`${path}.tmp`, path);
79
+ }
80
+
81
+ export function render(items, hours = HOURS, ledger = null, every = false) {
82
+ if (!items.length) return '';
83
+ const led = ledger ?? loadLedger();
84
+ const stamp = (w) => (w ? new Date(w * 1000).toISOString().slice(5, 16).replace('T', ' ') : ' ? ');
85
+ const rows = items.map(([w, t]) => { const id = askId(t); return { w, t, id, st: led[id]?.status ?? 'open', ev: led[id]?.evidence ?? '' }; });
86
+ const open = rows.filter((r) => r.st === 'open');
87
+ const waits = rows.filter((r) => r.st === 'needs-user' || r.st === 'planned');
88
+ const quiet = rows.filter((r) => QUIET.has(r.st));
89
+ const lines = [HEAD(hours, items.length),
90
+ `LEDGER (${LEDGER}): ${quiet.length} done or answered, ${waits.length} waiting on the user or planned, ${open.length} OPEN. Only the open ones are work; ` +
91
+ 'do not redo a done one. When one is done, mark it with its evidence: node .claude/hooks/user-asks.mjs --mark <id> done "<commit / bug / step>" ' +
92
+ '(or answered, planned, needs-user, not-an-ask).'];
93
+ if (open.length) { lines.push('OPEN, word for word:'); for (const r of open) lines.push(`- ${r.id} [${stamp(r.w)}] ${squash(r.t).slice(0, 1200)}`); }
94
+ if (waits.length) { lines.push('WAITING (one line each):'); for (const r of waits) lines.push(`- ${r.id} [${stamp(r.w)}] ${r.st}: ${r.ev.slice(0, 160)} <- ${squash(r.t).slice(0, 90)}`); }
95
+ if (every && quiet.length) { lines.push('DONE / ANSWERED:'); for (const r of quiet) lines.push(`- ${r.id} [${stamp(r.w)}] ${r.st}: ${r.ev.slice(0, 120)} <- ${squash(r.t).slice(0, 90)}`); }
96
+ return lines.join('\n');
97
+ }
98
+
99
+ function selftest() {
100
+ const now = Date.now() / 1000;
101
+ const iso = (dt) => new Date((now - dt) * 1000).toISOString();
102
+ const rows = [
103
+ { type: 'user', timestamp: iso(3600), message: { content: 'add 20 80s bass arps' } },
104
+ { type: 'queue-operation', operation: 'enqueue', timestamp: iso(1800), content: 'and make a hook for chrome' },
105
+ { type: 'queue-operation', operation: 'remove', timestamp: iso(1799), content: 'and make a hook for chrome' },
106
+ { type: 'user', timestamp: iso(1700), message: { content: [{ type: 'tool_result', content: 'ok' }] } },
107
+ { type: 'user', timestamp: iso(1600), message: { content: '<task-notification>done</task-notification>' } },
108
+ { type: 'user', timestamp: iso(1500), isMeta: true, message: { content: 'a meta line' } },
109
+ { type: 'user', timestamp: iso(90000), message: { content: 'an ask from two days ago' } },
110
+ { type: 'user', timestamp: iso(60), message: { content: [{ type: 'text', text: 'is it correct the agents still run?' }] } },
111
+ { type: 'assistant', timestamp: iso(30), message: { content: "Next I'd build P4." } },
112
+ ];
113
+ const t = join(tmpdir(), `user-asks-${process.pid}.jsonl`);
114
+ writeFileSync(t, rows.map((r) => JSON.stringify(r)).join('\n'));
115
+ const items = asks(t, 24, now);
116
+ rmSync(t, { force: true });
117
+ const led = `${t}.ledger.json`;
118
+ mark(askId('add 20 80s bass arps'), 'done', 'commit abc123', led);
119
+ const out = render(items, 24, loadLedger(led));
120
+ rmSync(led, { force: true });
121
+ const checks = [
122
+ ['a done ask is counted, not listed; the open ones are listed word for word', out.includes('1 done or answered') && out.includes('2 OPEN') && !out.includes('add 20 80s bass arps') && out.includes('and make a hook for chrome')],
123
+ ['done without evidence is refused', (() => { try { mark('x', 'done', '', led); return false; } catch { return true; } })()],
124
+ ["the user's own words, mid-turn ones too, no tool results, system text, meta lines, their old asks or the assistant",
125
+ JSON.stringify(items.map(([, x]) => x)) === JSON.stringify(['add 20 80s bass arps', 'and make a hook for chrome', 'is it correct the agents still run?'])],
126
+ ["the ledger is the project's own, beside its memory, and a worktree's is its project's",
127
+ ledgerFor('/Users/dev/projects/my_synth') === join(homedir(), '.claude/projects/-Users-dev-projects-my-synth/asks-ledger.json') &&
128
+ ledgerFor('/Users/dev/projects/my_synth/.claude/worktrees/agent-x') === ledgerFor('/Users/dev/projects/my_synth')],
129
+ ];
130
+ for (const [name, ok] of checks) if (!ok) console.error(`FAIL ${name}`);
131
+ const bad = checks.filter(([, ok]) => !ok).length;
132
+ console.log(`user-asks selftest: ${bad ? 'FAIL' : 'OK'}`);
133
+ return bad ? 1 : 0;
134
+ }
135
+
136
+ function main(argv) {
137
+ if (argv.includes('--selftest')) return selftest();
138
+ const i = argv.indexOf('--mark');
139
+ if (i >= 0) {
140
+ try { mark(argv[i + 1], argv[i + 2], argv[i + 3] ?? ''); } catch (e) { console.error(e.message); return 1; }
141
+ console.log(`marked ${argv[i + 1]} ${argv[i + 2]}`);
142
+ return 0;
143
+ }
144
+ const ti = argv.indexOf('--transcript');
145
+ if (ti >= 0) {
146
+ const h = argv.includes('--hours') ? Number(argv[argv.indexOf('--hours') + 1]) : HOURS;
147
+ console.log(render(asks(argv[ti + 1], h), h, null, argv.includes('--all')));
148
+ return 0;
149
+ }
150
+ // A HOOK (SessionStart on compact): what it prints is added to the session's context
151
+ return asHook('user-asks', hookMain);
152
+ }
153
+
154
+ function hookMain(input) {
155
+ let event;
156
+ try { event = JSON.parse(input); } catch { return 0; }
157
+ const text = render(asks(event.transcript_path ?? ''));
158
+ if (text) console.log(text);
159
+ return 0;
160
+ }
161
+ export const hook = hookMain; // run-hooks.mjs calls it with the event's text (KWB-24)
162
+
163
+ // Run as a hook or a command (not imported by idle-wake). Real paths: /var and /private/var are one folder on a Mac.
164
+ const real = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
165
+ if (process.argv[1] && real(process.argv[1]) === real(fileURLToPath(import.meta.url))) process.exit(await main(process.argv.slice(2)));
@@ -0,0 +1,136 @@
1
+ {
2
+ "name": "synth-creator",
3
+ "version": "1.0.0",
4
+ "description": "The synth-creator configuration's own laws, installed with it and never by another: the build queue (build-guard), the time watch, the spec lock, frozen fixes, read-only vendored code and copy trees, staging by name, the session watches (idle-wake, user-asks, build-sweep), and the rules and skill a synth build lives by.",
5
+ "type": "stock",
6
+ "author": "claude-packs",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/TheDecipherist/claude-packs",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/TheDecipherist/claude-packs.git",
12
+ "directory": "packs/synth-creator"
13
+ },
14
+ "keywords": [
15
+ "synth",
16
+ "build"
17
+ ],
18
+ "engines": {
19
+ "claude-packs": ">=0.19.0"
20
+ },
21
+ "configurationPack": true,
22
+ "folders": {},
23
+ "concepts": {},
24
+ "skillsIn": null,
25
+ "hooks": {
26
+ "PreToolUse": [
27
+ {
28
+ "matcher": "Write|Edit|MultiEdit|NotebookEdit|Bash",
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/frozen-files.mjs\"",
33
+ "timeout": 10
34
+ },
35
+ {
36
+ "type": "command",
37
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-vendor.mjs\"",
38
+ "timeout": 10
39
+ },
40
+ {
41
+ "type": "command",
42
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/spec-lock.mjs\"",
43
+ "timeout": 10
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "matcher": "Bash",
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/stage-guard.mjs\"",
53
+ "timeout": 10
54
+ },
55
+ {
56
+ "type": "command",
57
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/build-guard.mjs\"",
58
+ "timeout": 15
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "matcher": "*",
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/time-guard.mjs\"",
68
+ "timeout": 15
69
+ }
70
+ ]
71
+ }
72
+ ],
73
+ "PostToolUse": [
74
+ {
75
+ "matcher": "*",
76
+ "hooks": [
77
+ {
78
+ "type": "command",
79
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/time-guard.mjs\"",
80
+ "timeout": 15
81
+ }
82
+ ]
83
+ }
84
+ ],
85
+ "Stop": [
86
+ {
87
+ "hooks": [
88
+ {
89
+ "type": "command",
90
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/idle-wake.mjs\"",
91
+ "asyncRewake": true,
92
+ "timeout": 1000
93
+ }
94
+ ]
95
+ }
96
+ ],
97
+ "SessionStart": [
98
+ {
99
+ "matcher": "compact",
100
+ "hooks": [
101
+ {
102
+ "type": "command",
103
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/user-asks.mjs\"",
104
+ "timeout": 15
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ "hooks": [
110
+ {
111
+ "type": "command",
112
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/build-sweep.mjs\"",
113
+ "timeout": 15
114
+ }
115
+ ]
116
+ }
117
+ ],
118
+ "SubagentStop": [
119
+ {
120
+ "hooks": [
121
+ {
122
+ "type": "command",
123
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/build-sweep.mjs\"",
124
+ "timeout": 15
125
+ }
126
+ ]
127
+ }
128
+ ]
129
+ },
130
+ "optIn": [
131
+ "hooks/user-asks.mjs",
132
+ "hooks/idle-wake.mjs",
133
+ "hooks/build-sweep.mjs",
134
+ "checks/ui-drawn-in-code.json"
135
+ ]
136
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ # concepts: copy-trees
3
+ paths:
4
+ - "placeholder"
5
+ ---
6
+
7
+ # Copy trees: copied, pinned, read-only
8
+
9
+ A copy tree is a folder of kind `"copy"` in `.claude/layout.json`: another project's code, copied unchanged and proved
10
+ identical (the `copy-and-prove` skill). This rule loads in every copy tree and in `layout.json`, where one is declared.
11
+
12
+ <constraints>
13
+ Always do:
14
+ - Treat every file here as a copy of another project's file at a named commit. It changes only through the project's copy tools (`guards.copy.tools` in `layout.json`): from its source, unchanged, or as a pinned change the user said yes to and null tests proved.
15
+ - Give every file its origin before it comes in (repo, path, commit, SHA-256, licence row). The licence row in `docs/licences.md` comes first.
16
+ - Prove the copy before anything uses it: the source's own render fingerprinted, the copy IDENTICAL, one coefficient changed in the copy seen DIFFERENT.
17
+ - Fix the code that uses the copy. If the copy itself must change, ask the user and pin the change.
18
+ - Start a new copy tree only in `.claude/layout.json`, as a folder with `"kind": "copy"`. The guard, this rule and the checks then cover it with no other change.
19
+ - Change a file that measured identical only after it is promoted out of the copy tree into the project's own code (a folder of kind `"own"`), with its origin record kept.
20
+
21
+ Never do:
22
+ - Edit, write, move or delete a file here by hand or by shell. The `protect-vendor` hook refuses it; only git and the sanctioned copy tools get through.
23
+ - Put a file here without its origin.
24
+ - Copy hardware, brand or machine names into anything that reaches the product: a program name, a label, a string in the binary.
25
+ </constraints>
@@ -0,0 +1,20 @@
1
+ ---
2
+ # concepts: cpp, tests
3
+ paths:
4
+ - "placeholder"
5
+ ---
6
+
7
+ # Debug by measuring, one hypothesis at a time
8
+
9
+ <constraints>
10
+ Always do:
11
+ - Reproduce first, then measure: render the case offline, read the numbers (`measure-tone`, a probe, a sanitizer, a log), and only then form a cause.
12
+ - State one falsifiable hypothesis ("the click is the filter state reset at voice steal, because the click lines up with sample N of the steal") and test it with one change.
13
+ - Treat "the host is wrong", "it's JUCE", "it's the driver" as a hypothesis to disprove with a minimal probe, never as the answer. Assume our code is wrong until a probe proves otherwise.
14
+ - After three fixes that didn't work, stop changing code. Report what was tried, what each measurement showed, and whether the design itself is the problem.
15
+
16
+ Never do:
17
+ - Change several things at once, then listen.
18
+ - Tweak constants until it "sounds right" without a measurement before and after.
19
+ - Start a fourth fix attempt on the same symptom without telling the user.
20
+ </constraints>