@thedecipherist/claude-packs 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +680 -0
- package/LICENSE +21 -0
- package/README.md +316 -2
- package/bin/claude-packs.mjs +204 -0
- package/configurations/base/config.json +85 -0
- package/configurations/cpp/config.json +92 -0
- package/configurations/full-stack/config.json +152 -0
- package/configurations/go/config.json +96 -0
- package/configurations/node/config.json +100 -0
- package/configurations/python/config.json +97 -0
- package/configurations/synth-creator/config.json +148 -0
- package/global/hooks/gnu-shell.mjs +131 -0
- package/global/hooks/lib/program-read.mjs +196 -0
- package/global/hooks/lib/program-writes.mjs +177 -0
- package/global/hooks/lib/shell-writes.mjs +293 -0
- package/global/hooks/shell-traps.mjs +79 -0
- package/global/hooks/version-pin-guard.mjs +280 -0
- package/global/skills/latest-versions/SKILL.md +49 -0
- package/lib/backup.mjs +34 -0
- package/lib/claude-md.mjs +58 -0
- package/lib/config-cli.mjs +36 -0
- package/lib/configs.mjs +145 -0
- package/lib/coverage.mjs +86 -0
- package/lib/custom-packs.mjs +93 -0
- package/lib/doctor.mjs +115 -0
- package/lib/fix-plan.mjs +71 -0
- package/lib/git-tokens.mjs +64 -0
- package/lib/global.mjs +125 -0
- package/lib/hook-proof.mjs +28 -0
- package/lib/hook-timing.mjs +42 -0
- package/lib/hover-gate.mjs +29 -0
- package/lib/kit-lock.mjs +149 -0
- package/lib/layout.mjs +75 -0
- package/lib/logs.mjs +69 -0
- package/lib/manifest.mjs +24 -0
- package/lib/new.mjs +49 -0
- package/lib/once-only-report.mjs +54 -0
- package/lib/pack-manifest.mjs +20 -0
- package/lib/packs.mjs +107 -0
- package/lib/planner.mjs +59 -0
- package/lib/repeats.mjs +65 -0
- package/lib/repo-weight.mjs +28 -0
- package/lib/rules.mjs +40 -0
- package/lib/scan-detect.mjs +94 -0
- package/lib/scan.mjs +168 -0
- package/lib/selftest-cache.mjs +39 -0
- package/lib/settings.mjs +138 -0
- package/lib/shape-cli.mjs +76 -0
- package/lib/shape.mjs +4 -0
- package/lib/suggest.mjs +18 -0
- package/lib/switches.mjs +44 -0
- package/lib/symmetry-doctor.mjs +26 -0
- package/lib/sync.mjs +130 -0
- package/package.json +34 -4
- package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
- package/packs/ai/checks/model-call-needs-boundary.json +29 -0
- package/packs/ai/checks/model-id-from-config.json +28 -0
- package/packs/ai/checks/model-output-validated.json +26 -0
- package/packs/ai/pack.json +38 -0
- package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
- package/packs/ai/rules/llm-pipeline-security.md +26 -0
- package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
- package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
- package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
- package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
- package/packs/audio-core/checks/keep-werror.json +12 -0
- package/packs/audio-core/checks/no-fast-math.json +14 -0
- package/packs/audio-core/checks/no-locale-change.json +12 -0
- package/packs/audio-core/checks/no-march-native.json +14 -0
- package/packs/audio-core/clang-tidy +28 -0
- package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
- package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
- package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
- package/packs/audio-core/pack.json +187 -0
- package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
- package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
- package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
- package/packs/audio-core/rules/build-flags-safe.md +20 -0
- package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
- package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
- package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
- package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
- package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
- package/packs/audio-core/rules/discrete-params-snap.md +19 -0
- package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
- package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
- package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
- package/packs/audio-core/rules/house-control-behaviours.md +34 -0
- package/packs/audio-core/rules/math-domain-guards.md +19 -0
- package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
- package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
- package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
- package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
- package/packs/audio-core/rules/one-formula-per-value.md +20 -0
- package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
- package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
- package/packs/audio-core/rules/output-limiter.md +23 -0
- package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
- package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
- package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
- package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
- package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
- package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
- package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
- package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
- package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
- package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
- package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
- package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
- package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
- package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
- package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
- package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
- package/packs/audio-core/rules/voice-steal-fades.md +21 -0
- package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
- package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
- package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
- package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
- package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
- package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
- package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
- package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
- package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
- package/packs/audio-core/tools/lib/wav.mjs +41 -0
- package/packs/audio-core/tools/tone-report.mjs +47 -0
- package/packs/base/checks/field-map-over-if-runs.json +25 -0
- package/packs/base/checks/lookup-over-if-chains.json +25 -0
- package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
- package/packs/base/checks/no-code-after-midline-comment.json +12 -0
- package/packs/base/checks/one-walk-per-template.json +23 -0
- package/packs/base/checks/stub-reports-success.json +31 -0
- package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
- package/packs/base/hooks/brief-guard.mjs +96 -0
- package/packs/base/hooks/code-shape.mjs +95 -0
- package/packs/base/hooks/dispatch-once.mjs +87 -0
- package/packs/base/hooks/file-size-guard.mjs +61 -0
- package/packs/base/hooks/folder-law.mjs +74 -0
- package/packs/base/hooks/git-guard.mjs +289 -0
- package/packs/base/hooks/lesson-checks.mjs +174 -0
- package/packs/base/hooks/lib/apply-edit.mjs +20 -0
- package/packs/base/hooks/lib/code-shapes.mjs +137 -0
- package/packs/base/hooks/lib/content.mjs +14 -0
- package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
- package/packs/base/hooks/lib/dup-index.mjs +199 -0
- package/packs/base/hooks/lib/dup.mjs +127 -0
- package/packs/base/hooks/lib/elements.mjs +87 -0
- package/packs/base/hooks/lib/event-log.mjs +67 -0
- package/packs/base/hooks/lib/function-shapes.mjs +90 -0
- package/packs/base/hooks/lib/git-adds.mjs +20 -0
- package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
- package/packs/base/hooks/lib/glob.mjs +33 -0
- package/packs/base/hooks/lib/hook-log.mjs +100 -0
- package/packs/base/hooks/lib/house.py +77 -0
- package/packs/base/hooks/lib/languages.mjs +66 -0
- package/packs/base/hooks/lib/layout.mjs +54 -0
- package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
- package/packs/base/hooks/lib/near-copies.mjs +76 -0
- package/packs/base/hooks/lib/program-read.mjs +196 -0
- package/packs/base/hooks/lib/program-writes.mjs +177 -0
- package/packs/base/hooks/lib/project.mjs +81 -0
- package/packs/base/hooks/lib/prose.mjs +11 -0
- package/packs/base/hooks/lib/pushed.mjs +201 -0
- package/packs/base/hooks/lib/repo-index.mjs +52 -0
- package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
- package/packs/base/hooks/lib/selftest.mjs +45 -0
- package/packs/base/hooks/lib/shape-code.mjs +126 -0
- package/packs/base/hooks/lib/shape.mjs +294 -0
- package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
- package/packs/base/hooks/lib/shell-writes.mjs +293 -0
- package/packs/base/hooks/lib/turn.mjs +81 -0
- package/packs/base/hooks/lib/writes.mjs +71 -0
- package/packs/base/hooks/making-check.mjs +243 -0
- package/packs/base/hooks/making.mjs +106 -0
- package/packs/base/hooks/once-only.mjs +150 -0
- package/packs/base/hooks/placement-law.mjs +84 -0
- package/packs/base/hooks/repo-litter.mjs +75 -0
- package/packs/base/hooks/reuse-hint.mjs +75 -0
- package/packs/base/hooks/run-hooks.mjs +132 -0
- package/packs/base/hooks/secrets-guard.mjs +99 -0
- package/packs/base/hooks/shape-law.mjs +133 -0
- package/packs/base/hooks/suite-once.mjs +80 -0
- package/packs/base/hooks/template-law.mjs +148 -0
- package/packs/base/hooks/untrusted-boundary.mjs +53 -0
- package/packs/base/pack.json +214 -0
- package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
- package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
- package/packs/base/rules/bug-fix-starts-red.md +25 -0
- package/packs/base/rules/field-map-over-if-runs.md +47 -0
- package/packs/base/rules/finished-work-lands.md +17 -0
- package/packs/base/rules/git-right-first-time.md +19 -0
- package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
- package/packs/base/rules/lookup-over-if-chains.md +49 -0
- package/packs/base/rules/making-line-first.md +14 -0
- package/packs/base/rules/open-items-tagged.md +17 -0
- package/packs/base/rules/plan-and-logs.md +22 -0
- package/packs/base/rules/references-use-anchors.md +19 -0
- package/packs/base/rules/refusals-are-judged.md +18 -0
- package/packs/base/rules/reuse-before-writing.md +15 -0
- package/packs/base/rules/secrets-never-committed.md +16 -0
- package/packs/base/rules/skill-writing.md +23 -0
- package/packs/base/rules/split-before-the-limit.md +14 -0
- package/packs/base/rules/subagent-dispatch.md +26 -0
- package/packs/base/rules/test-only-what-changed.md +17 -0
- package/packs/base/rules/test-the-template-once.md +22 -0
- package/packs/base/rules/untrusted-content-boundary.md +15 -0
- package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
- package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
- package/packs/base/skills/land-branch/SKILL.md +24 -0
- package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
- package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
- package/packs/base/skills/pick-tests/SKILL.md +20 -0
- package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
- package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
- package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
- package/packs/base/tools/shape-symmetry.mjs +178 -0
- package/packs/base/tools/test-verdict.mjs +75 -0
- package/packs/ci/checks/ci-permissions-stated.json +18 -0
- package/packs/ci/checks/ci-tools-pinned.json +14 -0
- package/packs/ci/pack.json +21 -0
- package/packs/ci/rules/ci-covers-what-ships.md +20 -0
- package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
- package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
- package/packs/content-site/checks/img-has-size.json +20 -0
- package/packs/content-site/checks/page-head-complete.json +26 -0
- package/packs/content-site/pack.json +22 -0
- package/packs/content-site/rules/content-has-one-source.md +23 -0
- package/packs/content-site/rules/media-is-sized.md +24 -0
- package/packs/content-site/rules/page-head-is-complete.md +26 -0
- package/packs/content-site/rules/public-forms-are-defended.md +27 -0
- package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
- package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
- package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
- package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
- package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
- package/packs/cpp/checks/cpp-no-atoi.json +16 -0
- package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
- package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
- package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
- package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
- package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
- package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
- package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
- package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
- package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
- package/packs/cpp/pack.json +22 -0
- package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
- package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
- package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
- package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
- package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
- package/packs/cpp/rules/cpp-headers-declare.md +22 -0
- package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
- package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
- package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
- package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
- package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
- package/packs/docker/checks/alpine-with-native-build.json +13 -0
- package/packs/docker/checks/compose-db-needs-auth.json +14 -0
- package/packs/docker/checks/compose-image-pinned.json +14 -0
- package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
- package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
- package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
- package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
- package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
- package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
- package/packs/docker/checks/compose-service-needs-init.json +11 -0
- package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
- package/packs/docker/checks/copy-source-before-deps.json +13 -0
- package/packs/docker/checks/docker-exec-form.json +16 -0
- package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
- package/packs/docker/checks/dockerfile-needs-user.json +21 -0
- package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
- package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
- package/packs/docker/checks/health-checks-nothing.json +25 -0
- package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
- package/packs/docker/checks/no-latest-base.json +13 -0
- package/packs/docker/checks/no-secret-in-image.json +13 -0
- package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
- package/packs/docker/checks/node-env-before-install.json +13 -0
- package/packs/docker/checks/npm-ci-not-install.json +13 -0
- package/packs/docker/pack.json +46 -0
- package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
- package/packs/docker/rules/compose-swarm-safe.md +26 -0
- package/packs/docker/rules/docker-exec-form.md +19 -0
- package/packs/docker/rules/dockerfile-multistage.md +13 -0
- package/packs/docker/rules/dockerfile-production.md +25 -0
- package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
- package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
- package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
- package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
- package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
- package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
- package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
- package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
- package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
- package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
- package/packs/dynamic-data/pack.json +23 -0
- package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
- package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
- package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
- package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
- package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
- package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
- package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
- package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
- package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
- package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
- package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
- package/packs/fastify/checks/api-routes-versioned.json +28 -0
- package/packs/fastify/checks/cookie-host-prefix.json +24 -0
- package/packs/fastify/checks/hijack-needs-try.json +28 -0
- package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
- package/packs/fastify/checks/no-db-in-route-file.json +28 -0
- package/packs/fastify/checks/no-err-message-to-client.json +24 -0
- package/packs/fastify/checks/no-regexp-from-input.json +24 -0
- package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
- package/packs/fastify/checks/one-error-body.json +26 -0
- package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
- package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
- package/packs/fastify/checks/sec-headers-present.json +28 -0
- package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
- package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
- package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
- package/packs/fastify/pack.json +41 -0
- package/packs/fastify/rules/credentials-and-input.md +23 -0
- package/packs/fastify/rules/security-one-switch.md +28 -0
- package/packs/fastify/rules/streams-and-hijacks.md +22 -0
- package/packs/fastify/rules/tests-use-inject.md +13 -0
- package/packs/fastify/rules/three-layers.md +20 -0
- package/packs/fastify/rules/trust-config-not-headers.md +24 -0
- package/packs/full-stack/checks/no-express-no-vite.json +23 -0
- package/packs/full-stack/hooks/stack-guard.mjs +90 -0
- package/packs/full-stack/pack.json +76 -0
- package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
- package/packs/go/checks/go-exec-result-ignored.json +15 -0
- package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
- package/packs/go/pack.json +21 -0
- package/packs/go/rules/go-errors-are-handled.md +22 -0
- package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
- package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
- package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
- package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
- package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
- package/packs/gpu-painter/checks/no-opengl.json +13 -0
- package/packs/gpu-painter/hooks/browser-guard.py +196 -0
- package/packs/gpu-painter/pack.json +91 -0
- package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
- package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
- package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
- package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
- package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
- package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
- package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
- package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
- package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
- package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
- package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
- package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
- package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
- package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
- package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
- package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
- package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
- package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
- package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
- package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
- package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
- package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
- package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
- package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
- package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
- package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
- package/packs/hook-proof/pack.json +38 -0
- package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
- package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
- package/packs/host-agent/checks/file-path-from-param.json +27 -0
- package/packs/host-agent/checks/integrity-check-optional.json +27 -0
- package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
- package/packs/host-agent/checks/secret-on-argv.json +28 -0
- package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
- package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
- package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
- package/packs/host-agent/pack.json +22 -0
- package/packs/host-agent/rules/root-on-other-machines.md +27 -0
- package/packs/html-ui/checks/email-html-escapes.json +25 -0
- package/packs/html-ui/checks/external-link-rel.json +21 -0
- package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
- package/packs/html-ui/checks/no-inline-client-script.json +21 -0
- package/packs/html-ui/checks/no-inline-style.json +23 -0
- package/packs/html-ui/pack.json +33 -0
- package/packs/html-ui/rules/email-html-escapes.md +19 -0
- package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
- package/packs/http-api/checks/api-path-versioned.json +31 -0
- package/packs/http-api/checks/authz-from-request-body.json +26 -0
- package/packs/http-api/checks/client-calls-a-version.json +28 -0
- package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
- package/packs/http-api/checks/cors-needs-origin.json +26 -0
- package/packs/http-api/checks/csp-not-switched-off.json +28 -0
- package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
- package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
- package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
- package/packs/http-api/checks/failure-is-not-empty.json +28 -0
- package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
- package/packs/http-api/checks/guard-fails-open.json +28 -0
- package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
- package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
- package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
- package/packs/http-api/checks/next-api-route-versioned.json +47 -0
- package/packs/http-api/checks/no-security-off-switch.json +28 -0
- package/packs/http-api/checks/oauth-state-signed.json +26 -0
- package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
- package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
- package/packs/http-api/checks/queue-action-from-client.json +26 -0
- package/packs/http-api/checks/rate-limit-one-module.json +28 -0
- package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
- package/packs/http-api/checks/route-body-needs-schema.json +31 -0
- package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
- package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
- package/packs/http-api/checks/secret-in-job-record.json +27 -0
- package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
- package/packs/http-api/checks/short-code-needs-limit.json +26 -0
- package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
- package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
- package/packs/http-api/pack.json +45 -0
- package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
- package/packs/http-api/rules/apis-are-versioned.md +27 -0
- package/packs/http-api/rules/credentials-handled-safely.md +22 -0
- package/packs/http-api/rules/handlers-own-the-work.md +17 -0
- package/packs/http-api/rules/health-and-correlation.md +17 -0
- package/packs/http-api/rules/one-error-contract.md +17 -0
- package/packs/http-api/rules/routes-are-thin.md +17 -0
- package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
- package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
- package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
- package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
- package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
- package/packs/instrument-shell/pack.json +22 -0
- package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
- package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
- package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
- package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
- package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
- package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
- package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
- package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
- package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
- package/packs/js-ts/checks/child-process-error-handler.json +28 -0
- package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
- package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
- package/packs/js-ts/checks/job-only-in-memory.json +28 -0
- package/packs/js-ts/checks/json-only-through-helper.json +29 -0
- package/packs/js-ts/checks/line-under-200-chars.json +34 -0
- package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
- package/packs/js-ts/checks/no-any.json +27 -0
- package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
- package/packs/js-ts/checks/no-console-in-app.json +31 -0
- package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
- package/packs/js-ts/checks/no-inline-binary.json +20 -0
- package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
- package/packs/js-ts/checks/no-local-sleep.json +25 -0
- package/packs/js-ts/checks/no-nested-ternary.json +20 -0
- package/packs/js-ts/checks/no-secret-in-log.json +24 -0
- package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
- package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
- package/packs/js-ts/checks/no-ts-enum.json +20 -0
- package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
- package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
- package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
- package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
- package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
- package/packs/js-ts/checks/server-close-awaited.json +27 -0
- package/packs/js-ts/checks/session-user-typed-once.json +24 -0
- package/packs/js-ts/checks/switch-is-a-table.json +24 -0
- package/packs/js-ts/checks/test-imports-the-source.json +19 -0
- package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
- package/packs/js-ts/checks/tsconfig-flags.json +21 -0
- package/packs/js-ts/checks/void-needs-catch.json +24 -0
- package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
- package/packs/js-ts/checks/zod-v4-formats.json +23 -0
- package/packs/js-ts/pack.json +84 -0
- package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
- package/packs/js-ts/rules/env-through-config.md +36 -0
- package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
- package/packs/js-ts/rules/files-under-300-lines.md +18 -0
- package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
- package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
- package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
- package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
- package/packs/js-ts/rules/no-any.md +34 -0
- package/packs/js-ts/rules/no-console.md +30 -0
- package/packs/js-ts/rules/no-floating-promises.md +31 -0
- package/packs/js-ts/rules/no-param-mutation.md +28 -0
- package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
- package/packs/js-ts/rules/one-logger.md +24 -0
- package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
- package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
- package/packs/js-ts/rules/process-lifecycle.md +47 -0
- package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
- package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
- package/packs/js-ts/rules/small-functions.md +20 -0
- package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
- package/packs/js-ts/rules/tests-can-fail.md +19 -0
- package/packs/js-ts/rules/tsconfig-strict.md +32 -0
- package/packs/js-ts/rules/typed-exports.md +27 -0
- package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
- package/packs/js-ts/rules/unions-over-enums.md +29 -0
- package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
- package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
- package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
- package/packs/midi/checks/midi-realtime-first.json +27 -0
- package/packs/midi/checks/no-tempo-writeback.json +23 -0
- package/packs/midi/checks/position-not-integrated.json +25 -0
- package/packs/midi/checks/stub-playhead-advances.json +27 -0
- package/packs/midi/checks/sysex-length-literal.json +23 -0
- package/packs/midi/checks/sysex-not-on-audio.json +24 -0
- package/packs/midi/checks/sysex-survives-realtime.json +27 -0
- package/packs/midi/pack.json +22 -0
- package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
- package/packs/midi/rules/imports-count-every-loss.md +25 -0
- package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
- package/packs/midi/rules/sync-is-a-mode.md +29 -0
- package/packs/midi/rules/sysex-is-a-record.md +29 -0
- package/packs/midi/skills/clock-probe/SKILL.md +23 -0
- package/packs/midi/skills/clock-probe/clock-law.md +417 -0
- package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
- package/packs/mongo/checks/bulk-over-many.json +28 -0
- package/packs/mongo/checks/created-at-immutable.json +25 -0
- package/packs/mongo/checks/driver-outside-gateway.json +24 -0
- package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
- package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
- package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
- package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
- package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
- package/packs/mongo/checks/no-drop-in-code.json +27 -0
- package/packs/mongo/checks/no-empty-deletemany.json +26 -0
- package/packs/mongo/checks/no-find-reads.json +26 -0
- package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
- package/packs/mongo/checks/no-mongoose.json +23 -0
- package/packs/mongo/checks/no-negation-delete.json +26 -0
- package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
- package/packs/mongo/checks/objectid-equals.json +26 -0
- package/packs/mongo/checks/update-then-reread.json +24 -0
- package/packs/mongo/checks/watch-not-poll.json +25 -0
- package/packs/mongo/pack.json +52 -0
- package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
- package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
- package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
- package/packs/mongo/rules/one-client-one-pool.md +32 -0
- package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
- package/packs/mongo/rules/writes-are-atomic.md +27 -0
- package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
- package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
- package/packs/multi-tenant/pack.json +22 -0
- package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
- package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
- package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
- package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
- package/packs/nginx/checks/nginx-headers-always.json +15 -0
- package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
- package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
- package/packs/nginx/checks/nginx-no-crlf.json +17 -0
- package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
- package/packs/nginx/pack.json +43 -0
- package/packs/nginx/rules/modsecurity-tuning.md +22 -0
- package/packs/nginx/rules/nginx-production.md +26 -0
- package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
- package/packs/python/checks/py-no-bare-except.json +18 -0
- package/packs/python/checks/py-no-eval.json +18 -0
- package/packs/python/checks/py-no-mutable-default.json +18 -0
- package/packs/python/checks/py-no-shell-true.json +18 -0
- package/packs/python/checks/py-no-star-import.json +18 -0
- package/packs/python/checks/py-no-swallowed-exception.json +18 -0
- package/packs/python/checks/py-none-by-is.json +18 -0
- package/packs/python/checks/py-open-with-with.json +18 -0
- package/packs/python/checks/py-requests-timeout.json +18 -0
- package/packs/python/checks/py-subprocess-output-checked.json +18 -0
- package/packs/python/checks/py-yaml-safe-load.json +18 -0
- package/packs/python/pack.json +26 -0
- package/packs/python/rules/python-errors-and-resources.md +26 -0
- package/packs/python/rules/python-repeats-become-templates.md +27 -0
- package/packs/python/skills/python-extract-template/SKILL.md +35 -0
- package/packs/react/checks/dialog-needs-role.json +28 -0
- package/packs/react/checks/effect-not-for-derived-state.json +24 -0
- package/packs/react/checks/icon-button-needs-name.json +24 -0
- package/packs/react/checks/label-needs-a-field.json +24 -0
- package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
- package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
- package/packs/react/pack.json +20 -0
- package/packs/react/rules/react-accessible-controls.md +20 -0
- package/packs/react/rules/react-state-and-effects.md +23 -0
- package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
- package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
- package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
- package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
- package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
- package/packs/reverse-engineering/pack.json +35 -0
- package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
- package/packs/reverse-engineering/rules/account-every-term.md +20 -0
- package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
- package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
- package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
- package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
- package/packs/reverse-engineering/rules/names-before-code.md +19 -0
- package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
- package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
- package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
- package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
- package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
- package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
- package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
- package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
- package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
- package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
- package/packs/shell/checks/sh-mktemp.json +18 -0
- package/packs/shell/checks/sh-no-backticks.json +18 -0
- package/packs/shell/checks/sh-no-eval.json +18 -0
- package/packs/shell/checks/sh-no-parse-ls.json +18 -0
- package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
- package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
- package/packs/shell/checks/sh-quote-arguments.json +18 -0
- package/packs/shell/checks/sh-rm-guarded.json +18 -0
- package/packs/shell/checks/sh-strict-mode.json +20 -0
- package/packs/shell/pack.json +28 -0
- package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
- package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
- package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
- package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
- package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
- package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
- package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
- package/packs/shell-hooks/pack.json +36 -0
- package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
- package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
- package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
- package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
- package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
- package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
- package/packs/sound-design/pack.json +22 -0
- package/packs/sound-design/rules/algorithms-are-data.md +26 -0
- package/packs/sound-design/rules/meter-before-engine.md +27 -0
- package/packs/sound-design/rules/neutral-is-exact.md +26 -0
- package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
- package/packs/sound-design/rules/unreachable-is-said.md +25 -0
- package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
- package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
- package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
- package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
- package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
- package/packs/stellix/checks/no-keyboard-focus.json +12 -0
- package/packs/stellix/checks/one-seeded-random.json +16 -0
- package/packs/stellix/checks/world-never-includes-painter.json +12 -0
- package/packs/stellix/checks/world-no-libm.json +15 -0
- package/packs/stellix/checks/world-no-unordered.json +10 -0
- package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
- package/packs/stellix/pack.json +61 -0
- package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
- package/packs/stellix/rules/stellix-reset-generated.md +24 -0
- package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
- package/packs/stellix/rules/world-continuous-collision.md +26 -0
- package/packs/stellix/rules/world-fp-determinism.md +28 -0
- package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
- package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
- package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
- package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
- package/packs/stored-text/pack.json +41 -0
- package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
- package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
- package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
- package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
- package/packs/synth-creator/hooks/build-guard.mjs +232 -0
- package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
- package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
- package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
- package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
- package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
- package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
- package/packs/synth-creator/hooks/time-guard.mjs +292 -0
- package/packs/synth-creator/hooks/user-asks.mjs +165 -0
- package/packs/synth-creator/pack.json +136 -0
- package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
- package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
- package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
- package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
- package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
- package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
- package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
- package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
- package/packs/synth-creator/tools/freeze.mjs +36 -0
- package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
- package/packs/tui/checks/tui-gates-on-tty.json +25 -0
- package/packs/tui/checks/tui-no-ris-reset.json +22 -0
- package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
- package/packs/tui/checks/tui-width-is-state.json +33 -0
- package/packs/tui/pack.json +44 -0
- package/packs/tui/rules/tui-resize-and-screen.md +25 -0
- package/packs/tui/skills/build-tui/SKILL.md +21 -0
- package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
- package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
- package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
- package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
- package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
- package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
- package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
- package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
- package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
- package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
- package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
- package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
- package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
- package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
- package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
- package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
- package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
- package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
- package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
- package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
- package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
- package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
- package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
- package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
- package/packs/ui-craft/pack.json +43 -0
- package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
- package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
- package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
- package/packs/ui-craft/rules/forms-never-fight.md +22 -0
- package/packs/ui-craft/rules/interface-copy.md +26 -0
- package/packs/ui-craft/rules/motion-is-rare.md +31 -0
- package/packs/ui-craft/rules/no-generated-look.md +37 -0
- package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
- package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
- package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
- package/packs/ui-craft/rules/pointer-feedback.md +28 -0
- package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
- package/packs/ui-craft/rules/survives-real-data.md +23 -0
- package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
- package/packs/ui-craft/rules/type-space-colour.md +26 -0
- package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
- package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
- package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
- package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
- package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
- package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
- package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
- package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
- package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
- package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
- package/packs/ui-craft/tools/page-measure.mjs +41 -0
- package/packs/web/checks/a11y-img-needs-alt.json +26 -0
- package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
- package/packs/web/checks/a11y-no-clickable-div.json +26 -0
- package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
- package/packs/web/checks/client-no-api-key.json +29 -0
- package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
- package/packs/web/checks/client-no-server-import.json +31 -0
- package/packs/web/checks/client-no-window-location-nav.json +28 -0
- package/packs/web/checks/css-no-inline-style.json +30 -0
- package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
- package/packs/web/checks/key-from-index.json +23 -0
- package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
- package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
- package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
- package/packs/web/checks/rcss-button-cursor.json +30 -0
- package/packs/web/checks/rcss-no-100vw.json +21 -0
- package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
- package/packs/web/checks/rcss-viewport-meta.json +28 -0
- package/packs/web/checks/secret-not-in-web-storage.json +25 -0
- package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
- package/packs/web/checks/wp-hero-not-lazy.json +27 -0
- package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
- package/packs/web/pack.json +73 -0
- package/packs/web/rules/accessibility.md +26 -0
- package/packs/web/rules/client-boundaries.md +23 -0
- package/packs/web/rules/production-build.md +22 -0
- package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
- package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
- package/packs/web/rules/tailwind-v4.md +23 -0
- package/packs/web/rules/ui-primitives-one-module.md +30 -0
- package/packs/web/rules/web-performance.md +21 -0
- package/packs/web/skills/design-direction/SKILL.md +30 -0
- package/packs/web/skills/design-review/SKILL.md +28 -0
- package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
- package/packs/web/skills/preflight-build/SKILL.md +23 -0
- package/packs/web/skills/seo-audit/SKILL.md +22 -0
- package/packs/websocket/checks/no-token-in-query.json +26 -0
- package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
- package/packs/websocket/checks/ws-identity-from-message.json +26 -0
- package/packs/websocket/pack.json +21 -0
- package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
- package/packs/writing/checks/ascii-no-em-dash.json +17 -0
- package/packs/writing/checks/no-copula-dodging.json +18 -0
- package/packs/writing/checks/no-dramatic-colon.json +18 -0
- package/packs/writing/checks/no-not-x-but-y.json +18 -0
- package/packs/writing/checks/no-participial-opener.json +18 -0
- package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
- package/packs/writing/checks/no-slop-words.json +18 -0
- package/packs/writing/checks/no-stock-connectors.json +18 -0
- package/packs/writing/checks/no-time-or-date.json +27 -0
- package/packs/writing/checks/no-trailing-ing.json +18 -0
- package/packs/writing/checks/no-vague-attribution.json +18 -0
- package/packs/writing/pack.json +43 -0
- package/packs/writing/rules/ascii-no-em-dash.md +21 -0
- package/packs/writing/rules/cut-the-padding.md +20 -0
- package/packs/writing/rules/direct-voice.md +22 -0
- package/packs/writing/rules/no-cargo-cult.md +16 -0
- package/packs/writing/rules/no-scaffolding.md +19 -0
- package/packs/writing/rules/no-stock-language.md +27 -0
- package/packs/writing/rules/no-time-or-date.md +18 -0
- package/packs/writing/rules/rhythm-like-talk.md +20 -0
- package/packs/writing/rules/write-from-your-own-corner.md +20 -0
- package/packs/writing/skills/argument-post/SKILL.md +28 -0
- package/packs/writing/skills/humanize/SKILL.md +28 -0
- package/packs/writing/skills/launch-post/SKILL.md +38 -0
- package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
- package/packs/writing/skills/post-voice/SKILL.md +22 -0
- package/packs/writing/skills/reddit-post/SKILL.md +63 -0
- package/packs/writing/skills/reddit-title/SKILL.md +23 -0
- package/packs/writing/skills/reply-moves/SKILL.md +40 -0
- package/packs/writing/skills/reply-stance/SKILL.md +23 -0
- package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
- package/packs/zod/hooks/schemas-only.mjs +53 -0
- package/packs/zod/pack.json +44 -0
- package/packs/zod/rules/env-shapes-only.md +14 -0
- package/packs/zod/rules/zod-4-schemas.md +14 -0
- package/profiles/stellix.layout.json +315 -0
- package/profiles/sub37.layout.json +247 -0
- package/prompts/scan-review.md +40 -0
- package/prompts/survey.md +82 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// making-check: a turn ends only when what it wrote is what it said it was making (claude-packs 0.9.0 item 7; KWB-1).
|
|
3
|
+
// A Stop hook. It reads the turn from the transcript (lib/turn.mjs: every file written by Write, Edit, MultiEdit,
|
|
4
|
+
// NotebookEdit or the shell, refused calls left out) and sends the turn back (exit 2) naming what is missing when:
|
|
5
|
+
// - the turn wrote code or docs that touch a concept of design/shape.json and build.md's In progress entry for this
|
|
6
|
+
// step has no `making:` line, or the line does not name that concept;
|
|
7
|
+
// - the making line names a concept that has no row in design/shape.json, or the turn wrote source with no manifest;
|
|
8
|
+
// - a source file the turn wrote repeats code the repo already has (once-only's detector, lib/dup.mjs, on the lines
|
|
9
|
+
// the working tree adds against HEAD);
|
|
10
|
+
// - THE OVERRIDE RULE: the turn's text says it did something other than what it was told, and cites no law. Told X
|
|
11
|
+
// and a law file (design/shape.json, docs/*-spec.md, docs/laws.md) says Y: do Y, log the brief as the bug, cite the
|
|
12
|
+
// line. No law covers it: ask; overnight, decide and write it under Decisions, and cite that entry. There is no
|
|
13
|
+
// third case where Claude's own judgement overrides an instruction.
|
|
14
|
+
// After three send-backs with the same findings the turn ends and the findings go to the user (no endless loop).
|
|
15
|
+
// node making-check.mjs --selftest
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join, relative, resolve } from 'node:path';
|
|
21
|
+
import { copyGlobs, excludeGlobs, FIX, isGenerated, isSource, real } from './lib/dup.mjs';
|
|
22
|
+
import { judgeWrites } from './lib/dup-index.mjs';
|
|
23
|
+
import { matchesAny } from './lib/glob.mjs';
|
|
24
|
+
import { readLayout } from './lib/layout.mjs';
|
|
25
|
+
import { checkoutRoot, insideProject, ROOT } from './lib/project.mjs';
|
|
26
|
+
import { readShape } from './lib/shape.mjs';
|
|
27
|
+
import { lastTurn, writesOfTurn } from './lib/turn.mjs';
|
|
28
|
+
import { asHook, isMain } from './lib/hook-log.mjs';
|
|
29
|
+
|
|
30
|
+
const MAX_SENDS = 3;
|
|
31
|
+
// the folder kinds a layout marks as code; a layout that marks none: any source file outside its docs folders
|
|
32
|
+
const CODE_KINDS = new Set(['code', 'engine', 'ui', 'params', 'tests']);
|
|
33
|
+
const NOT_CODE = ['build/**', 'inbox/**', 'docs/**', '**/*.md', '**/*.txt', '**/*.json'];
|
|
34
|
+
function isCode(relPath, layout) {
|
|
35
|
+
if (!(isSource(relPath) || /\.ipynb$/i.test(relPath)) || matchesAny(relPath, NOT_CODE)) return false;
|
|
36
|
+
const folders = Object.values(layout?.folders ?? {});
|
|
37
|
+
const docs = folders.filter((f) => f.kind === 'docs' || /^docs?\b/i.test(f.purpose ?? '')).map((f) => `${f.path}/**`);
|
|
38
|
+
if (matchesAny(relPath, docs)) return false;
|
|
39
|
+
const code = folders.filter((f) => CODE_KINDS.has(f.kind)).map((f) => `${f.path}/**`);
|
|
40
|
+
return !code.length || matchesAny(relPath, code);
|
|
41
|
+
}
|
|
42
|
+
const PLAN = ['build.md', '**/build.md', 'bugs.md', 'workflow-bugs.md', 'CHANGELOG.md', 'design/shape.json', '.claude/**'];
|
|
43
|
+
const LAW = /(?:design\/)?shape\.json|(?:docs\/)?[\w.-]*-spec\.md|(?:docs\/)?laws\.md/g;
|
|
44
|
+
const LOCATOR = /:\d+\b|#[\w-]+|\{#[\w-]+\}|§\s*\d[\d.]*|\bL-?\d+\b|\b(?:section|line|row|rule|law)\s+["'`]?[\w.-]+|"[\w -]{2,40}"\s+row/i;
|
|
45
|
+
// The turn says it did something other than it was told.
|
|
46
|
+
const OVERRIDE = [
|
|
47
|
+
/\b(?:instead of|rather than|not)\s+(?:what\s+)?(?:the\s+)?(?:brief|prompt|order|instruction|request|work ?order|lead|you)\s+(?:asked|said|told|wanted|named|specified)/i,
|
|
48
|
+
/\b(?:the\s+)?(?:brief|prompt|order|instruction|request|work ?order|lead)\s+(?:said|asked|told|wanted|named|specified)\b[^.\n]{0,160}\b(?:but|however|instead)\b/i,
|
|
49
|
+
/\byou\s+(?:said|asked|told me|wanted)\b[^.\n]{0,160}\b(?:but I|however I|instead I|I instead)\b(?!['’]d\b|\s+(?:would|think|recommend|suggest|expect|believe)\b)/i, // a view is not an action (KWB-47)
|
|
50
|
+
/\bI\s+(?:deviated|departed|diverged)\s+from\b/i,
|
|
51
|
+
/\b(?:overrode|overriding|override[sd]?)\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order)/i,
|
|
52
|
+
/\bcontrary\s+to\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order)/i,
|
|
53
|
+
/\b(?:did\s+not|didn't)\s+(?:follow|do\s+what)\s+(?:the\s+|your\s+)?(?:brief|instruction|order|prompt|request|work ?order|you)/i,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const splitName = (name) => name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([A-Za-z])(\d)/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
57
|
+
const partsOf = (text) => new Set((text.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []).flatMap(splitName));
|
|
58
|
+
const rowOf = (c) => c?.row ?? c ?? {};
|
|
59
|
+
const wordsOf = (name, c) => [name.toLowerCase(), ...(c?.words ?? rowOf(c).words ?? []).map((w) => String(w).toLowerCase())];
|
|
60
|
+
|
|
61
|
+
// The concepts a text touches: a word of the concept as a word or a part of an identifier, a code pattern, a per-instance
|
|
62
|
+
// array, the generator.
|
|
63
|
+
export function conceptsIn(text, shape) {
|
|
64
|
+
const parts = partsOf(text);
|
|
65
|
+
const out = new Set();
|
|
66
|
+
for (const [name, c] of Object.entries(shape?.concepts ?? {})) {
|
|
67
|
+
const row = rowOf(c);
|
|
68
|
+
const gen = String(row.generator ?? '').match(/^\s*([A-Za-z_]\w*)\s*\(/)?.[1];
|
|
69
|
+
const hit = wordsOf(name, c).some((w) => parts.has(w))
|
|
70
|
+
|| (row.patterns ?? []).some((p) => { try { return new RegExp(p).test(text); } catch { return false; } })
|
|
71
|
+
|| (row.arrays ?? []).some((a) => new RegExp(`\\b${a}\\b`).test(text))
|
|
72
|
+
|| (gen && new RegExp(`\\b${gen}\\s*\\(`).test(text));
|
|
73
|
+
if (hit) out.add(name);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The concepts listed after "concepts:" on a making line that the manifest does not know.
|
|
79
|
+
export function unknownConcepts(making, shape) {
|
|
80
|
+
const known = new Set(Object.entries(shape?.concepts ?? {}).flatMap(([n, c]) => wordsOf(n, c)));
|
|
81
|
+
const out = [];
|
|
82
|
+
// bracketed text goes first, nested brackets too, so a ";" inside a shape ("slot (many, 4; slotId(i, field))") does not end the list (KWB-17)
|
|
83
|
+
let flat = making;
|
|
84
|
+
for (let prev; prev !== flat;) { prev = flat; flat = flat.replace(/\([^()]*\)/g, ' '); }
|
|
85
|
+
for (const m of flat.matchAll(/\bconcepts?\s*:\s*([^;|]+)/gi)) {
|
|
86
|
+
for (const item of m[1].split(',')) {
|
|
87
|
+
const w = item.trim().split(/\s+/)[0]?.toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
88
|
+
if (w && !['none', 'no', '-', 'n/a'].includes(w) && !known.has(w)) out.push(w);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The checkout's branch from its HEAD file (a worktree's .git file names its git dir): no git process (KWB-24: on a loaded
|
|
95
|
+
// machine each one took half a second)
|
|
96
|
+
function branchOf(root) {
|
|
97
|
+
try {
|
|
98
|
+
let dir = join(root, '.git');
|
|
99
|
+
const dotGit = readFileSync(dir, 'utf8').match(/^gitdir:\s*(.+)$/m); // a worktree; a folder throws and is read below
|
|
100
|
+
if (dotGit) dir = resolve(root, dotGit[1].trim());
|
|
101
|
+
return headBranch(dir);
|
|
102
|
+
} catch { return headBranch(join(root, '.git')); }
|
|
103
|
+
}
|
|
104
|
+
const headBranch = (gitDir) => { try { return readFileSync(join(gitDir, 'HEAD'), 'utf8').match(/^ref:\s*refs\/heads\/(.+)$/m)?.[1].trim() ?? 'HEAD'; } catch { return ''; } };
|
|
105
|
+
|
|
106
|
+
// The making lines of build.md's In progress section: those on this branch's entry, else every one there.
|
|
107
|
+
export function makingLines(root) {
|
|
108
|
+
const file = [join(root, 'build.md'), join(ROOT, 'build.md')].find((f) => existsSync(f));
|
|
109
|
+
if (!file) return [];
|
|
110
|
+
const text = readFileSync(file, 'utf8');
|
|
111
|
+
const at = text.search(/^##\s+In progress\b/im);
|
|
112
|
+
if (at < 0) return [];
|
|
113
|
+
const rest = text.slice(at).split('\n');
|
|
114
|
+
const section = [];
|
|
115
|
+
for (const l of rest.slice(1)) { if (/^##\s/.test(l)) break; section.push(l); }
|
|
116
|
+
const lines = section.filter((l) => /\bmaking:/i.test(l)).map((l) => l.slice(l.search(/\bmaking:/i)));
|
|
117
|
+
const branch = branchOf(root);
|
|
118
|
+
const mine = branch && branch !== 'HEAD' ? section.filter((l) => /\bmaking:/i.test(l) && (l.includes(branch) || l.includes(branch.split('/').pop()))).map((l) => l.slice(l.search(/\bmaking:/i))) : [];
|
|
119
|
+
return mine.length ? mine : lines;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Added line ranges of each file against HEAD (the whole file when it is new or there is no git): two git processes for all
|
|
123
|
+
// the files of a checkout, not two per file. -> Map(file -> [[lo, hi]])
|
|
124
|
+
function addedRanges(root, files) {
|
|
125
|
+
const rels = files.map((f) => relative(root, f));
|
|
126
|
+
const out = new Map();
|
|
127
|
+
const tracked = new Set((spawnSync('git', ['-C', root, 'ls-files', '--', ...rels], { encoding: 'utf8' }).stdout ?? '').split('\n').filter(Boolean));
|
|
128
|
+
const diff = tracked.size ? spawnSync('git', ['-C', root, 'diff', '--relative', '-U0', 'HEAD', '--', ...rels.filter((r) => tracked.has(r))], { encoding: 'utf8' }).stdout ?? '' : '';
|
|
129
|
+
let cur = null;
|
|
130
|
+
for (const line of diff.split('\n')) {
|
|
131
|
+
const f = line.match(/^\+\+\+ b\/(.+)$/);
|
|
132
|
+
if (f) { cur = f[1]; continue; }
|
|
133
|
+
const h = line.match(/^@@ -\S+ \+(\d+)(?:,(\d+))? @@/);
|
|
134
|
+
if (h && cur) { const r = [Number(h[1]), Number(h[1]) + Number(h[2] ?? 1) - 1]; if (r[1] >= r[0]) out.set(cur, [...(out.get(cur) ?? []), r]); }
|
|
135
|
+
}
|
|
136
|
+
const lines = (f) => { try { return readFileSync(f, 'utf8').split('\n').length; } catch { return 0; } };
|
|
137
|
+
return new Map(files.map((f, i) => [f, tracked.has(rels[i]) ? out.get(rels[i]) ?? [] : lines(f) ? [[1, lines(f)]] : []]));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function check(event, cwd = event.cwd ?? process.cwd()) {
|
|
141
|
+
const path = event.hook_event_name === 'SubagentStop' ? event.agent_transcript_path ?? event.transcript_path : event.transcript_path;
|
|
142
|
+
const turn = lastTurn(path ?? '');
|
|
143
|
+
const text = `${turn.text}\n${event.last_assistant_message ?? ''}`;
|
|
144
|
+
const writes = writesOfTurn(turn, cwd).filter((w) => insideProject(w.path));
|
|
145
|
+
const problems = [];
|
|
146
|
+
const rel = (p) => relative(checkoutRoot(p), p).replace(/\\/g, '/');
|
|
147
|
+
// Each write is judged against its own checkout (a worktree's own build.md, manifest and layout), never the session's folder:
|
|
148
|
+
// a lead in the main checkout writing in a worktree has its making line in that worktree's build.md (Stellix WB-86).
|
|
149
|
+
const byRoot = new Map();
|
|
150
|
+
for (const w of writes) { const r = checkoutRoot(w.path); if (!byRoot.has(r)) byRoot.set(r, []); byRoot.get(r).push(w); }
|
|
151
|
+
if (!byRoot.size) byRoot.set(checkoutRoot(join(cwd, 'x')), []);
|
|
152
|
+
const judged = [];
|
|
153
|
+
let shape = null, root = null, layout = null;
|
|
154
|
+
for (const [r, ws] of byRoot) {
|
|
155
|
+
const lay = readLayout(r);
|
|
156
|
+
const sh = readShape(r) ?? readShape(ROOT);
|
|
157
|
+
const mine = ws.filter((w) => !matchesAny(rel(w.path), [...PLAN, ...excludeGlobs(lay, sh)]));
|
|
158
|
+
// once-only (below) judges the checkout the source went to: the first checkout that holds a judged source
|
|
159
|
+
if (!root || (mine.some((w) => isSource(w.path)) && !judged.some((w) => isSource(w.path)))) { root = r; layout = lay; shape = sh; }
|
|
160
|
+
judged.push(...mine);
|
|
161
|
+
|
|
162
|
+
// 1. the concepts written, the making line, the manifest
|
|
163
|
+
if (!sh && mine.some((w) => isSource(w.path))) {
|
|
164
|
+
problems.push(`no design/shape.json, and this turn wrote source (${[...new Set(mine.filter((w) => isSource(w.path)).map((w) => rel(w.path)))].slice(0, 4).join(', ')}): write the manifest first, every concept one or many, its generator, its selectors`);
|
|
165
|
+
}
|
|
166
|
+
if (sh && !sh.error && mine.length) {
|
|
167
|
+
const written = new Map();
|
|
168
|
+
// only code is judged for its concepts: a note, a brief, a doc or a log names concepts in words ("Status line"), and that is no
|
|
169
|
+
// undeclared use (KWB-19)
|
|
170
|
+
for (const w of mine) if (isCode(rel(w.path), lay)) for (const c of conceptsIn(w.text ?? '', sh)) if (!written.has(c)) written.set(c, rel(w.path));
|
|
171
|
+
const making = makingLines(r).join('\n');
|
|
172
|
+
const wroteSource = mine.filter((w) => isSource(w.path)).map((w) => rel(w.path));
|
|
173
|
+
// a turn that writes source answers "what are you making" first, even before the manifest knows the concept
|
|
174
|
+
if (!making && (written.size || wroteSource.length)) problems.push(`build.md's In progress entry for this step has no "making:" line, and this turn wrote ${written.size ? [...written].map(([c, f]) => `${c} (${f})`).join(', ') : [...new Set(wroteSource)].slice(0, 4).join(', ')}: write the line (what you are making; concepts: each with its shape; reuse: the one function, where it lives)`);
|
|
175
|
+
else if (making) {
|
|
176
|
+
const named = conceptsIn(making, sh);
|
|
177
|
+
for (const [c, f] of written) if (!named.has(c)) problems.push(`the making line does not name ${c}, which this turn wrote (${f}): add it with its shape from design/shape.json, or take the write back`);
|
|
178
|
+
for (const u of unknownConcepts(making, sh)) problems.push(`the making line names "${u}", which has no row in design/shape.json: add the row first (one or many, its generator, its selectors)`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. once-only on the source the turn wrote
|
|
184
|
+
const sources = [...new Set(judged.filter((w) => isSource(w.path) && existsSync(w.path)).map((w) => w.path))]
|
|
185
|
+
.filter((p) => !matchesAny(rel(p), copyGlobs(layout)) && !isGenerated(readFileSync(p, 'utf8')));
|
|
186
|
+
if (sources.length) {
|
|
187
|
+
// once-only's index finds the candidates, jscpd confirms on those files alone (lib/dup-index.mjs; KWB-24: no whole-repo scan)
|
|
188
|
+
const added = addedRanges(root, sources);
|
|
189
|
+
const writes = sources.map((p) => ({ path: p, after: readFileSync(p, 'utf8'), ranges: added.get(p) })).filter((w) => w.ranges.length);
|
|
190
|
+
const r = writes.length ? judgeWrites(root, writes, { layout, shape }) : { clones: [] };
|
|
191
|
+
if (r.error) problems.push(`once-only could not judge this turn's writes: ${r.error}`);
|
|
192
|
+
const base = real(root);
|
|
193
|
+
const show = (s) => `${relative(base, s.file)}:${s.start}-${s.end}`;
|
|
194
|
+
const found = new Set();
|
|
195
|
+
for (const c of r.clones) found.add(`${show(c.at)} repeats ${show(c.other)} (${c.lines} lines)`);
|
|
196
|
+
if (found.size) problems.push(`this turn's writes did not pass once-only: ${[...found].slice(0, 5).join('; ')}. ${FIX}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 3. the override rule
|
|
200
|
+
const claims = text.replace(/"[^"\n]*"|`[^`\n]*`/g, '""'); // a quotation reports words, it claims nothing (KWB-47)
|
|
201
|
+
const said = OVERRIDE.map((re) => claims.match(re)).find(Boolean);
|
|
202
|
+
if (said) {
|
|
203
|
+
const cites = text.split('\n').some((l) => [...l.matchAll(LAW)].some((m) => LOCATOR.test(l.slice(Math.max(0, m.index - 60), m.index + m[0].length + 60))));
|
|
204
|
+
const decided = /\bDecisions?\b/.test(text) && writes.some((w) => /decisions/i.test(w.path) || (existsSync(w.path) && /^#+\s*Decisions\b/im.test(readFileSync(w.path, 'utf8'))));
|
|
205
|
+
const logged = writes.some((w) => /(?:^|\/)(?:workflow-)?bugs\.md$/.test(w.path)) || /\b(?:K?WB|B)-\d+\b/.test(text);
|
|
206
|
+
const quote = `"${said[0].slice(0, 120)}"`;
|
|
207
|
+
if (!cites && !decided) problems.push(`the turn says it did something other than it was told (${quote}) and cites no law. Told X and a law file says Y: do Y, log the brief as the bug, cite the line (design/shape.json "<concept>", docs/<name>-spec.md {#anchor}, docs/laws.md L<n>). No law covers it: ask; overnight, decide and write it under Decisions and cite that entry. Claude's own judgement never overrides an instruction`);
|
|
208
|
+
else if (cites && !decided && !logged) problems.push(`the turn overrode what it was told by a law (${quote}) but did not log the brief as the bug: log it in bugs.md or workflow-bugs.md and name its number`);
|
|
209
|
+
}
|
|
210
|
+
return problems;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Three send-backs with the same findings end the turn: the findings go to the user instead of looping.
|
|
214
|
+
function guard(event, problems) {
|
|
215
|
+
const file = join(process.env.TMPDIR ?? tmpdir(), `making-check-${String(event.session_id ?? 'session').replace(/[^\w-]/g, '')}.json`);
|
|
216
|
+
let st = {};
|
|
217
|
+
try { st = JSON.parse(readFileSync(file, 'utf8')); } catch { /* first */ }
|
|
218
|
+
const hash = createHash('sha1').update(problems.join('\n')).digest('hex');
|
|
219
|
+
// consecutive stops with the same findings count whether or not the host marks them as a continuation (stop_hook_active):
|
|
220
|
+
// a host that delivers each send-back as a new turn reset the count to 1 every time, and the limit was never reached (KWB-7)
|
|
221
|
+
const count = problems.length && st.hash === hash ? (st.count ?? 0) + 1 : problems.length ? 1 : 0;
|
|
222
|
+
try { writeFileSync(file, JSON.stringify({ hash, count })); } catch { /* the check still runs */ }
|
|
223
|
+
return count > MAX_SENDS;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function main(text) {
|
|
227
|
+
let event;
|
|
228
|
+
try { event = JSON.parse(text); } catch { return 0; }
|
|
229
|
+
if (event.hook_event_name && !['Stop', 'SubagentStop'].includes(event.hook_event_name)) return 0;
|
|
230
|
+
const problems = check(event);
|
|
231
|
+
if (!problems.length) { guard(event, []); return 0; }
|
|
232
|
+
if (guard(event, problems)) {
|
|
233
|
+
process.stdout.write(JSON.stringify({ systemMessage: `making-check sent this turn back ${MAX_SENDS} times with the same findings; it ends here for you to decide:\n- ${problems.join('\n- ')}` }));
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
process.stderr.write(`making-check sent this turn back:\n- ${problems.join('\n- ')}\n`);
|
|
237
|
+
return 2;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
|
|
241
|
+
if (isMain(import.meta.url)) {
|
|
242
|
+
process.exit(process.argv.includes('--selftest') ? await (await import('./lib/making-check-selftest.mjs')).selftest(import.meta.url) : await asHook('making-check', main));
|
|
243
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// making: before anything is built, what is being made and the shape of every concept it touches (claude-packs 0.9.0
|
|
3
|
+
// item 6; KWB-1). A UserPromptSubmit hook: the same short text goes in front of every prompt, under 80 words, exactly
|
|
4
|
+
// The user's content: what are you making; each concept you touch and its shape from design/shape.json; a new concept gets its
|
|
5
|
+
// row first; a brief naming an instance is wrong by definition; used more than once, one function, parameterized, and
|
|
6
|
+
// where it lives. The answer is a `making:` line in the step's In progress entry in build.md (making-check reads it).
|
|
7
|
+
// When the prompt itself names an instance of a many concept, one line more: "you named X: building for all <shape>, X
|
|
8
|
+
// is the reproduction". Nothing else is added.
|
|
9
|
+
// node making.mjs --cost the text's size per turn (doctor prints it)
|
|
10
|
+
// node making.mjs --selftest
|
|
11
|
+
import { readFileSync, rmSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { checkoutRoot, ROOT } from './lib/project.mjs';
|
|
14
|
+
import { scratchProject } from './lib/selftest.mjs';
|
|
15
|
+
import { findInstanceLiterals, readShape } from './lib/shape.mjs';
|
|
16
|
+
import { asHook, isMain } from './lib/hook-log.mjs';
|
|
17
|
+
|
|
18
|
+
export const TEXT = "making: before the first write, answer in build.md's In progress entry for this step, one line: " +
|
|
19
|
+
'`making: <what you are making>; concepts: <each you touch, its shape from design/shape.json>; reuse: <function, where it lives>`. ' +
|
|
20
|
+
'A new concept gets its shape.json row first. A brief naming an instance is wrong by definition. Used more than once? ' +
|
|
21
|
+
'One function, parameterized; cite where it lives.';
|
|
22
|
+
export const MAX_WORDS = 80;
|
|
23
|
+
|
|
24
|
+
export const words = (t) => t.split(/\s+/).filter(Boolean).length;
|
|
25
|
+
|
|
26
|
+
// A token estimate, stated as one: word pieces of up to 6 letters, each digit run of up to 3 and each mark one token
|
|
27
|
+
// each (English in a BPE tokenizer runs about 1.3 tokens a word); never the model's own count.
|
|
28
|
+
export function tokens(t) {
|
|
29
|
+
let n = 0;
|
|
30
|
+
for (const p of t.match(/[A-Za-z]+|\d+|[^\sA-Za-z\d]/g) ?? []) n += /^[A-Za-z]/.test(p) ? Math.ceil(p.length / 6) : /^\d/.test(p) ? Math.ceil(p.length / 3) : 1;
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const plural = (c, name) => (c?.plural instanceof Set && c.plural.size ? [...c.plural][0] : (c?.words ?? []).find((w) => w === `${name}s` || w === `${name}es`)) ?? `${name}s`;
|
|
35
|
+
|
|
36
|
+
// The echo line, or '' when the prompt names no instance of a many concept. One line, whatever the prompt holds.
|
|
37
|
+
export function echo(prompt, shape) {
|
|
38
|
+
if (!shape || !prompt) return '';
|
|
39
|
+
const named = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const f of findInstanceLiterals(prompt, 'prompt.md', shape)) {
|
|
42
|
+
const c = shape.concepts?.[f.concept];
|
|
43
|
+
if ((c?.shape ?? c?.row?.shape) !== 'many' || seen.has(f.concept) || f.instance === 'each') continue;
|
|
44
|
+
seen.add(f.concept);
|
|
45
|
+
const x = /^\d+$/.test(f.instance) ? `${f.concept} ${f.instance}` : `${f.instance} ${f.concept}`;
|
|
46
|
+
const n = c.count ?? c.row?.count;
|
|
47
|
+
named.push(`${x}: building for all ${plural(c, f.concept)} (many${n ? `, ${n}` : ''}), ${x} is the reproduction`);
|
|
48
|
+
}
|
|
49
|
+
return named.length ? `you named ${named.slice(0, 2).join('; ')}` : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function inject(prompt, shape) {
|
|
53
|
+
const line = echo(prompt, shape);
|
|
54
|
+
return line && words(`${TEXT} ${line}`) <= MAX_WORDS ? `${TEXT}\n${line}` : line ? `${TEXT}\n${line.split('; ')[0]}` : TEXT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const cost = () => {
|
|
58
|
+
const one = `${TEXT}\nyou named slot 2: building for all slots (many, 4), slot 2 is the reproduction`;
|
|
59
|
+
return `making: ${words(TEXT)} words, about ${tokens(TEXT)} tokens added to every prompt (${words(one)} words, about ${tokens(one)} tokens when it echoes a named instance); ` +
|
|
60
|
+
'an estimate (word pieces of up to 6 letters, each mark one token), not the model\'s tokenizer';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function selftest() {
|
|
64
|
+
const dir = scratchProject({ 'design/shape.json': JSON.stringify({
|
|
65
|
+
concepts: {
|
|
66
|
+
slot: { shape: 'many', count: 4, generator: 'slotId(i, field)', words: ['slot', 'slots'], selectors: { selectedSlot: 'the slot the player has open' }, constants: { initSlot: { instance: 1, reason: 'INIT plays one saw' } } },
|
|
67
|
+
mixer: { shape: 'one', id: 'mixer', words: ['mixer'] },
|
|
68
|
+
},
|
|
69
|
+
exclude: [],
|
|
70
|
+
}) });
|
|
71
|
+
const shape = readShape(dir);
|
|
72
|
+
rmSync(dir, { recursive: true, force: true });
|
|
73
|
+
const cases = [
|
|
74
|
+
['the text is under 80 words', words(TEXT) < MAX_WORDS],
|
|
75
|
+
['the text with one echo line is still under 80 words', words(inject('drive slot 2 louder', shape)) <= MAX_WORDS],
|
|
76
|
+
['the text asks every item of the order', ['making:', 'concept', 'shape', 'design/shape.json', 'row first', 'brief naming an instance', 'wrong by definition', 'more than once', 'one function', 'parameterized', 'where it lives', 'In progress', 'build.md'].every((k) => TEXT.toLowerCase().includes(k.toLowerCase()))],
|
|
77
|
+
['a prompt naming slot 2 gets the echo', inject('the PAD in slot 2 is too quiet, fix it', shape).endsWith('you named slot 2: building for all slots (many, 4), slot 2 is the reproduction')],
|
|
78
|
+
['a prompt naming no instance gets the text alone', inject('make the reverb darker on every slot', shape) === TEXT],
|
|
79
|
+
['an example line is not an echo', inject('seen in: slot 3, build it for all slots', shape) === TEXT],
|
|
80
|
+
['a selector is not an echo', inject('the selectedSlot loses its state after a load', shape) === TEXT],
|
|
81
|
+
['a one concept is never echoed as many', inject('the mixer 1 meter', shape).split('\n').length <= 2 && !inject('the mixer 1 meter', shape).includes('all mixers')],
|
|
82
|
+
['no manifest: the text alone', inject('drive slot 2', null) === TEXT],
|
|
83
|
+
['the cost is measured and stated as an estimate', /\d+ words, about \d+ tokens/.test(cost()) && cost().includes('estimate')],
|
|
84
|
+
];
|
|
85
|
+
for (const [name, ok] of cases) if (!ok) console.error(`making selftest FAILED: ${name}`);
|
|
86
|
+
const bad = cases.filter(([, ok]) => !ok).length;
|
|
87
|
+
if (!bad) console.log(`making selftest: OK (${cases.length} cases)`);
|
|
88
|
+
return bad ? 1 : 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function main(text) {
|
|
92
|
+
let event = {};
|
|
93
|
+
try { event = JSON.parse(text || '{}'); } catch { /* the text still goes */ }
|
|
94
|
+
if (event.hook_event_name && event.hook_event_name !== 'UserPromptSubmit') return 0;
|
|
95
|
+
const root = event.cwd ? checkoutRoot(join(event.cwd, 'prompt')) : ROOT;
|
|
96
|
+
let shape = null;
|
|
97
|
+
try { shape = readShape(root) ?? readShape(ROOT); } catch { /* the text still goes */ }
|
|
98
|
+
process.stdout.write(`${inject(event.prompt ?? '', shape)}\n`);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
|
|
103
|
+
if (isMain(import.meta.url)) {
|
|
104
|
+
if (process.argv.includes('--cost')) { console.log(cost()); process.exit(0); }
|
|
105
|
+
process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('making', main));
|
|
106
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// once-only: code that exists once is called, never copied (claude-packs 0.9.0 item 9; KWB-1). Before a source write
|
|
3
|
+
// lands (C++, Python, JS), the lines it adds are compared with the whole repo through an index kept between calls
|
|
4
|
+
// (lib/dup-index.mjs, KWB-24: jscpd's whole-repo scan on every write was too slow; only files that changed are read again),
|
|
5
|
+
// at jscpd's thresholds (lib/dup.mjs: 50 tokens and 6 lines, a gap of up to 2 lines, so a copy with one line changed is
|
|
6
|
+
// still a copy). doctor's count and `claude-packs dup scan` still run jscpd over the whole repo. A near-duplicate is refused with the
|
|
7
|
+
// existing location and the fix: call it, or extract one function that both call, parameterized over what differs.
|
|
8
|
+
// Generated files, resources, vendored and built code, copy trees and design/shape.json "exclude" are never judged.
|
|
9
|
+
// Every write route (lib/writes.mjs): Write, Edit, MultiEdit, NotebookEdit and the shell's >, >>, tee, sed -i, cp, mv,
|
|
10
|
+
// a heredoc. The file is judged as it would be after the write, its old text left out, so editing a file never
|
|
11
|
+
// matches its own old lines.
|
|
12
|
+
// node once-only.mjs --selftest
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join, relative } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { changedLines, copyGlobs, excludeGlobs, FIX, findJscpd, isGenerated, isSource, real } from './lib/dup.mjs';
|
|
18
|
+
import { judgeWrites } from './lib/dup-index.mjs';
|
|
19
|
+
import { matchesAny } from './lib/glob.mjs';
|
|
20
|
+
import { readLayout } from './lib/layout.mjs';
|
|
21
|
+
import { checkoutRoot, claudeRoot, insideProject } from './lib/project.mjs';
|
|
22
|
+
import { bashOf, editOf, runHook, scratchProject, verdicts, writeOf } from './lib/selftest.mjs';
|
|
23
|
+
import { readShape } from './lib/shape.mjs';
|
|
24
|
+
import { readPayload, writesOf } from './lib/writes.mjs';
|
|
25
|
+
import { isContent } from './lib/content.mjs';
|
|
26
|
+
import { asHook, isMain } from './lib/hook-log.mjs';
|
|
27
|
+
|
|
28
|
+
const HOW = `Code that exists once is called, never copied: ${FIX}. A function written for a many concept takes the instance as a parameter.\n` +
|
|
29
|
+
'Moving or extracting code: take the old copy out in the same write (one Write or MultiEdit), or take it out first.';
|
|
30
|
+
|
|
31
|
+
// [{ root, layout, shape, w, range }] for every source write the call makes that once-only judges.
|
|
32
|
+
function judged(payload) {
|
|
33
|
+
const out = [];
|
|
34
|
+
const gone = [];
|
|
35
|
+
for (const w of writesOf(payload, payload.cwd ?? process.cwd())) {
|
|
36
|
+
if (!insideProject(w.path)) continue;
|
|
37
|
+
if (w.op === 'delete') { gone.push(w.path); continue; }
|
|
38
|
+
if (w.after === null || w.after === undefined || !isSource(w.path) || isGenerated(w.after)) continue;
|
|
39
|
+
const root = checkoutRoot(w.path);
|
|
40
|
+
const rel = relative(root, w.path).replace(/\\/g, '/');
|
|
41
|
+
const layout = readLayout(claudeRoot(w.path));
|
|
42
|
+
const shape = readShape(root);
|
|
43
|
+
if (matchesAny(rel, [...excludeGlobs(layout, shape), ...copyGlobs(layout)]) || isContent(rel, layout)) continue;
|
|
44
|
+
const range = changedLines(w.before, w.after);
|
|
45
|
+
if (range) out.push({ root, layout, shape, w, rel, range });
|
|
46
|
+
}
|
|
47
|
+
return { list: out, gone };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function judge(payload) {
|
|
51
|
+
const { list, gone } = judged(payload);
|
|
52
|
+
if (!list.length) return { code: 0 };
|
|
53
|
+
const found = [];
|
|
54
|
+
// each checkout written to: its index finds the candidates (built once, then only changed files are read again), jscpd
|
|
55
|
+
// confirms on those files alone (lib/dup-index.mjs judgeWrites)
|
|
56
|
+
for (const root of new Set(list.map((j) => j.root))) {
|
|
57
|
+
const mine = list.filter((j) => j.root === root);
|
|
58
|
+
const r = judgeWrites(root, mine.map((j) => ({ path: j.w.path, after: j.w.after, range: j.range })), { layout: mine[0].layout, shape: mine[0].shape, ignore: gone });
|
|
59
|
+
if (r.error) return { code: 0, note: `once-only could not judge: ${r.error}` }; // doctor shows a missing detector
|
|
60
|
+
const base = real(root);
|
|
61
|
+
const show = (s) => `${relative(base, s.file)}:${s.start}-${s.end}`;
|
|
62
|
+
for (const c of r.clones) found.push(`${show(c.at)} repeats ${show(c.other)} (${c.lines} lines, ${c.tokens} tokens)`);
|
|
63
|
+
}
|
|
64
|
+
if (!found.length) return { code: 0 };
|
|
65
|
+
const listed = [...new Set(found)];
|
|
66
|
+
return { code: 2, message: `BLOCKED by once-only: this write repeats code the repo already has.\n${listed.slice(0, 6).map((f) => ` ${f}`).join('\n')}${listed.length > 6 ? `\n ... and ${listed.length - 6} more` : ''}\n${HOW}` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const FN = (name, k = '2.0f') => `float ${name}::process (float x) noexcept
|
|
70
|
+
{
|
|
71
|
+
const float g = std::tan (kPi * cutoff / sampleRate);
|
|
72
|
+
const float k = ${k} - 2.0f * resonance;
|
|
73
|
+
const float a1 = 1.0f / (1.0f + g * (g + k));
|
|
74
|
+
const float a2 = g * a1;
|
|
75
|
+
const float a3 = g * a2;
|
|
76
|
+
const float v3 = x - ic2eq;
|
|
77
|
+
const float v1 = a1 * ic1eq + a2 * v3;
|
|
78
|
+
const float v2 = ic2eq + a2 * ic1eq + a3 * v3;
|
|
79
|
+
ic1eq = 2.0f * v1 - ic1eq;
|
|
80
|
+
ic2eq = 2.0f * v2 - ic2eq;
|
|
81
|
+
return mode == 0 ? v2 : mode == 1 ? v1 : x - k * v1 - v2;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
const PY = (name, scale = '0.5') => `def ${name}(frames, rate):
|
|
85
|
+
out = []
|
|
86
|
+
for i, f in enumerate(frames):
|
|
87
|
+
level = sum(abs(s) for s in f) / max(len(f), 1)
|
|
88
|
+
peak = max((abs(s) for s in f), default=0.0)
|
|
89
|
+
crest = peak / level if level > 0 else 0.0
|
|
90
|
+
out.append({"i": i, "t": i / rate, "level": level * ${scale}, "peak": peak, "crest": crest})
|
|
91
|
+
return [o for o in out if o["level"] > 0.001 and o["crest"] < 30.0]
|
|
92
|
+
`;
|
|
93
|
+
const JS = (name, pad = 2) => `export function ${name}(rows, widths) {
|
|
94
|
+
const cells = rows.map((r) => r.map((c, i) => String(c).padEnd(widths[i] ?? 0)));
|
|
95
|
+
const lines = cells.map((r) => r.join(' | ').trimEnd());
|
|
96
|
+
const rule = widths.map((w) => '-'.repeat(Math.max(w, 3))).join('-|-');
|
|
97
|
+
const head = lines.shift() ?? '';
|
|
98
|
+
const body = lines.filter((l) => l.length > 0).map((l) => \` \${l}\`);
|
|
99
|
+
return [head, rule, ...body].join('\\n').padEnd(${pad});
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
function selftest() {
|
|
104
|
+
const hook = fileURLToPath(import.meta.url);
|
|
105
|
+
if (!findJscpd([process.cwd()])) {
|
|
106
|
+
console.error('once-only selftest FAILED: jscpd not found (npm install the kit with its optional dependency, or set CLAUDE_PACKS_JSCPD)');
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
const dir = scratchProject({
|
|
110
|
+
'src/core/Filter.cpp': `#include "Filter.h"\n\n${FN('Filter')}`,
|
|
111
|
+
'tools/meter.py': `import math\n\n${PY('meter')}`,
|
|
112
|
+
'tools/table.mjs': `// table\n${JS('table')}`,
|
|
113
|
+
'design/shape.json': JSON.stringify({ concepts: {}, exclude: ['src/gen/**'] }),
|
|
114
|
+
});
|
|
115
|
+
const at = (f) => join(dir, f);
|
|
116
|
+
const w = (f, text) => runHook(hook, dir, writeOf(at(f), text));
|
|
117
|
+
const sh = (cmd) => runHook(hook, dir, bashOf(cmd, dir));
|
|
118
|
+
const filter = readFileSync(at('src/core/Filter.cpp'), 'utf8');
|
|
119
|
+
return verdicts('once-only', [
|
|
120
|
+
['a copy of a function with one line changed, in a new file (Write)', w('src/core/Filter2.cpp', `#include "Filter2.h"\n\n${FN('Filter2', '1.5f')}`), 2],
|
|
121
|
+
['a copy with its middle line changed (the two halves are one near-miss clone)', w('src/core/Mid.cpp', FN('Mid').replace('const float a2 = g * a1;', 'const float a2 = 0.5f * g * a1;')), 2],
|
|
122
|
+
['a copy put into a header (.h and .cpp are one language)', w('src/core/Inline.h', `#pragma once\n\n${FN('Inline', '1.7f')}`), 2],
|
|
123
|
+
['the same code calling the function instead', w('src/core/Mixer.cpp', '#include "Filter.h"\n\nfloat Mixer::render (float x) noexcept { return filter.process (x) * gain; }\n'), 0],
|
|
124
|
+
['a near-copy added to the same file (Edit)', runHook(hook, dir, editOf(at('src/core/Filter.cpp'), '#include "Filter.h"\n', `#include "Filter.h"\n\n${FN('Shelf', '1.0f')}`)), 2],
|
|
125
|
+
['a heredoc that writes a copy (Bash)', sh(`cat > src/core/Copy.cpp <<'EOF'\n${FN('Copy', '0.5f')}EOF`), 2],
|
|
126
|
+
['cp of a source file (Bash)', sh('cp src/core/Filter.cpp src/core/Again.cpp'), 2],
|
|
127
|
+
['mv of a source file: its old place is gone, nothing is repeated', sh('mv src/core/Filter.cpp src/core/Moved.cpp'), 0],
|
|
128
|
+
['a Python copy with one value changed', w('tools/meter2.py', `import math\n\n${PY('meter2', '0.25')}`), 2],
|
|
129
|
+
['a JS copy with one value changed', w('tools/table2.mjs', `// table 2\n${JS('table2', 4)}`), 2],
|
|
130
|
+
['editing a function in place (its own old text is not a match)', w('src/core/Filter.cpp', filter.replace('2.0f - 2.0f * resonance', '1.8f - 2.0f * resonance')), 0],
|
|
131
|
+
['two sed -i edits of one file in one command (KWB-13: each was read against the disk, two versions judged as copies)', sh("sed -i 's/2.0f - 2.0f/1.9f - 2.0f/' src/core/Filter.cpp && sed -i '1a // tuned' src/core/Filter.cpp"), 0],
|
|
132
|
+
['moving a function within its file in one write', w('src/core/Filter.cpp', `${FN('Filter')}\n#include "Filter.h"\n`), 0],
|
|
133
|
+
['a shape.json "exclude" path', w('src/gen/Filter3.cpp', FN('Filter3', '1.1f')), 0],
|
|
134
|
+
['a generated file', w('src/core/Tables.cpp', `// generated by tools/make_tables.py: do not edit\n${FN('Tables', '1.2f')}`), 0],
|
|
135
|
+
['resources', w('src/Resources/Filter4.cpp', FN('Filter4', '1.3f')), 0],
|
|
136
|
+
['a short block of under 6 lines repeated', w('src/core/Small.cpp', 'float Small::process (float x) noexcept\n{\n return x * gain;\n}\n'), 0],
|
|
137
|
+
['a file outside the project', runHook(hook, dir, writeOf(join(tmpdir(), 'other-project-x', 'Filter5.cpp'), FN('Filter5', '1.4f'))), 0],
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function main(text) {
|
|
142
|
+
const payload = readPayload(text);
|
|
143
|
+
if (!payload) return 0;
|
|
144
|
+
const r = judge(payload);
|
|
145
|
+
if (r.code === 2) process.stderr.write(`${r.message}\n`);
|
|
146
|
+
return r.code;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
|
|
150
|
+
if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('once-only', main));
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse (Write|Edit|MultiEdit|NotebookEdit|Bash; every route, lib/writes.mjs): every kind of thing goes in its one place, so the rules for
|
|
3
|
+
// it load. layout.kinds says what goes where; a kind may name file patterns ("**/*.wgsl") and a
|
|
4
|
+
// content pattern (processBlock, wgpu calls, paint(Graphics)). A file of that kind, or an edit that
|
|
5
|
+
// ADDS that content, outside the kind's concept folders is refused. Content is read in C, C++ and shader files, or in the
|
|
6
|
+
// kind's own "extensions" (a TypeScript kind: [".ts", ".tsx"]; KWB-36). `--selftest` proves it.
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { extname, relative } from 'node:path';
|
|
9
|
+
import { writesOf } from './lib/writes.mjs';
|
|
10
|
+
import { matchesAny } from './lib/glob.mjs';
|
|
11
|
+
import { conceptGlobs, readLayout } from './lib/layout.mjs';
|
|
12
|
+
import { insideProject, checkoutRoot, claudeRoot } from './lib/project.mjs';
|
|
13
|
+
import { verdicts } from './lib/selftest.mjs';
|
|
14
|
+
import { asHook, isMain } from './lib/hook-log.mjs';
|
|
15
|
+
|
|
16
|
+
const ROOT = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
17
|
+
const CODE = new Set(['.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.mm', '.m', '.wgsl', '.metal', '.hlsl']);
|
|
18
|
+
|
|
19
|
+
const home = (layout, kind) => (kind.concepts ?? []).flatMap((c) => conceptGlobs(layout, c) ?? []);
|
|
20
|
+
const where = (layout, kind) => layout.folders?.[kind.in]?.path ?? kind.in;
|
|
21
|
+
|
|
22
|
+
export function misplaced(layout, rel, before, after) {
|
|
23
|
+
for (const [name, kind] of Object.entries(layout.kinds ?? {})) {
|
|
24
|
+
const globs = home(layout, kind);
|
|
25
|
+
if (globs.length === 0 || matchesAny(rel, globs)) continue;
|
|
26
|
+
const byName = kind.files && matchesAny(rel, kind.files) && before === '';
|
|
27
|
+
let byContent = false;
|
|
28
|
+
if (kind.content && (kind.extensions ? kind.extensions.includes(extname(rel)) : CODE.has(extname(rel)))) {
|
|
29
|
+
const re = new RegExp(kind.content);
|
|
30
|
+
byContent = re.test(after) && !re.test(before);
|
|
31
|
+
}
|
|
32
|
+
if (byName || byContent) return { name, kind, where: where(layout, kind), globs };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function main(text) {
|
|
38
|
+
let payload;
|
|
39
|
+
try { payload = JSON.parse(text); } catch { return 0; }
|
|
40
|
+
for (const w of writesOf(payload)) {
|
|
41
|
+
if (w.op === 'delete' || !insideProject(w.path)) continue;
|
|
42
|
+
const rel = relative(checkoutRoot(w.path), w.path).replace(/\\/g, '/');
|
|
43
|
+
// an unknown result (a program's output) is judged by name only
|
|
44
|
+
const hit = misplaced(readLayout(claudeRoot(w.path)), rel, w.before, w.after ?? w.before);
|
|
45
|
+
if (hit) return refuse(rel, hit);
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function refuse(rel, hit) {
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
`BLOCKED by placement-law: ${rel} holds ${hit.kind.what} (kind "${hit.name}").\n` +
|
|
53
|
+
`That goes in ${hit.where}/, where its rules load (${hit.globs.join(', ')}).\n` +
|
|
54
|
+
'Put it there instead. If it truly belongs elsewhere, ask the user to change .claude/layout.json, then `npx --no-install @thedecipherist/claude-packs update`.\n',
|
|
55
|
+
);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function selftest() {
|
|
60
|
+
const layout = {
|
|
61
|
+
folders: { audio: { path: 'src/audio' }, painter: { path: 'src/painter' }, tests: { path: 'tests' }, adapters: { path: 'api/adapters' } },
|
|
62
|
+
concepts: { audio: ['audio'], painter: ['painter'], tests: ['tests'], adapters: ['adapters'] },
|
|
63
|
+
kinds: {
|
|
64
|
+
'audio-entry': { what: 'processBlock', in: 'audio', concepts: ['audio', 'tests'], content: '\\bprocessBlock\\s*\\(' },
|
|
65
|
+
shader: { what: 'WGSL', in: 'painter', concepts: ['painter'], files: ['**/*.wgsl'] },
|
|
66
|
+
'db-gateway': { what: 'the database driver', in: 'adapters', concepts: ['adapters'], content: "from\\s+'mongodb'", extensions: ['.ts'] },
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const cases = [
|
|
70
|
+
['processBlock added outside audio', misplaced(layout, 'src/app/Helper.cpp', '', 'void processBlock (A& b) {}') !== null],
|
|
71
|
+
['processBlock in audio is fine', misplaced(layout, 'src/audio/Engine.cpp', '', 'void processBlock (A& b) {}') === null],
|
|
72
|
+
['processBlock already there is not re-judged', misplaced(layout, 'src/app/Old.cpp', 'processBlock (x);', 'processBlock (x); y();') === null],
|
|
73
|
+
['a new shader outside painter', misplaced(layout, 'src/app/glow.wgsl', '', 'fn main() {}') !== null],
|
|
74
|
+
['a shader in painter is fine', misplaced(layout, 'src/painter/glow.wgsl', '', 'fn main() {}') === null],
|
|
75
|
+
['a comment in docs is not code', misplaced(layout, 'docs/notes.md', '', 'processBlock (x)') === null],
|
|
76
|
+
['a kind with its own extensions: the driver in a handler (KWB-36)', misplaced(layout, 'api/handlers/user.ts', '', "import { MongoClient } from 'mongodb';") !== null],
|
|
77
|
+
['the driver in the adapters is fine', misplaced(layout, 'api/adapters/db.ts', '', "import { MongoClient } from 'mongodb';") === null],
|
|
78
|
+
['a kind with extensions leaves other files alone', misplaced(layout, 'api/handlers/notes.md', '', "from 'mongodb'") === null],
|
|
79
|
+
];
|
|
80
|
+
return verdicts('placement-law', cases.map(([label, ok]) => [label, ok, true]));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
|
|
84
|
+
if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('placement-law', main));
|