@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,90 @@
|
|
|
1
|
+
// Every named function in a text, with how much it holds (0.25.0). A line limit cannot see a 140-statement function written 70 lines long, or a
|
|
2
|
+
// function that takes nine positional arguments.
|
|
3
|
+
// functionsOf(text, ext) -> [{ name, line, end, statements, params }]
|
|
4
|
+
import { statementsOf } from './code-shapes.mjs';
|
|
5
|
+
import { languageOf } from './languages.mjs';
|
|
6
|
+
|
|
7
|
+
const CONTROL = new Set(['if', 'for', 'while', 'switch', 'catch', 'return', 'sizeof', 'function', 'with', 'elif', 'do', 'foreach', 'until', 'unless']);
|
|
8
|
+
const BY_INDENT = new Set(['python', 'ruby', 'lua']);
|
|
9
|
+
|
|
10
|
+
// Index of the bracket that opens the one closing at `close`, or -1.
|
|
11
|
+
function openerOf(code, close) {
|
|
12
|
+
let depth = 0;
|
|
13
|
+
for (let i = close; i >= 0; i -= 1) {
|
|
14
|
+
if (code[i] === ')') depth += 1;
|
|
15
|
+
else if (code[i] === '(' && (depth -= 1) === 0) return i;
|
|
16
|
+
}
|
|
17
|
+
return -1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function paramsIn(inner) {
|
|
21
|
+
let depth = 0;
|
|
22
|
+
let commas = 0;
|
|
23
|
+
for (const c of inner) {
|
|
24
|
+
if ('(<[{'.includes(c)) depth += 1;
|
|
25
|
+
else if (')>]}'.includes(c)) depth -= 1;
|
|
26
|
+
else if (c === ',' && depth === 0) commas += 1;
|
|
27
|
+
}
|
|
28
|
+
const self = /^\s*(?:self|cls|&?(?:mut\s+)?self)\s*(?:,|$)/.test(inner) ? 1 : 0;
|
|
29
|
+
return inner.trim() ? commas + 1 - self : 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `name (params) ... {`: the head before a body's brace, or null when it is a block, a call or a type.
|
|
33
|
+
function headOf(code, brace) {
|
|
34
|
+
const head = code.slice(Math.max(0, brace - 400), brace);
|
|
35
|
+
const close = head.search(/\)(?:\s*(?:const|noexcept|override|final|mutable|throws\s+[\w, ]+|->\s*[^{;]+|:\s*[\w<>[\]., |?]+))*\s*$/);
|
|
36
|
+
if (close < 0) return null;
|
|
37
|
+
const at = brace - head.length + close;
|
|
38
|
+
const open = openerOf(code, at);
|
|
39
|
+
if (open < 0) return null;
|
|
40
|
+
const arrow = code.slice(0, open).match(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?$/);
|
|
41
|
+
const name = arrow?.[1] ?? code.slice(Math.max(0, open - 80), open).match(/([A-Za-z_$][\w$]*)\s*(?:<[^<>()]*>)?\s*$/)?.[1];
|
|
42
|
+
return !name || CONTROL.has(name) ? null : { name, open, params: paramsIn(code.slice(open + 1, at)) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function byBraces(code) {
|
|
46
|
+
const out = [];
|
|
47
|
+
const stack = [];
|
|
48
|
+
for (let i = 0; i < code.length; i += 1) {
|
|
49
|
+
if (code[i] === '{') stack.push(i);
|
|
50
|
+
else if (code[i] === '}' && stack.length) {
|
|
51
|
+
const a = stack.pop();
|
|
52
|
+
const head = headOf(code, a);
|
|
53
|
+
if (head) out.push({ ...head, from: a, to: i });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function byIndent(code, starts) {
|
|
60
|
+
const lines = code.split('\n');
|
|
61
|
+
const out = [];
|
|
62
|
+
lines.forEach((l, i) => {
|
|
63
|
+
const m = l.match(/^(\s*)(?:async\s+)?(?:local\s+)?(?:def|function)\s+([A-Za-z_][\w.:?!]*)\s*(?:\(([^)]*)\)?)?/);
|
|
64
|
+
if (!m) return;
|
|
65
|
+
let end = i;
|
|
66
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
67
|
+
if (!lines[j].trim()) continue;
|
|
68
|
+
if (lines[j].match(/^\s*/)[0].length <= m[1].length) break;
|
|
69
|
+
end = j;
|
|
70
|
+
}
|
|
71
|
+
out.push({ name: m[2], from: starts[i], to: starts[end], params: paramsIn(m[3] ?? '') });
|
|
72
|
+
});
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function functionsOf(text, ext) {
|
|
77
|
+
const lang = languageOf(ext);
|
|
78
|
+
if (!lang) return [];
|
|
79
|
+
const code = lang.blank(text);
|
|
80
|
+
const starts = [0];
|
|
81
|
+
for (let i = code.indexOf('\n'); i !== -1; i = code.indexOf('\n', i + 1)) starts.push(i + 1);
|
|
82
|
+
const lineOf = (offset) => { let n = 0; while (n + 1 < starts.length && starts[n + 1] <= offset) n += 1; return n + 1; };
|
|
83
|
+
const list = statementsOf(text, lang);
|
|
84
|
+
const found = BY_INDENT.has(lang.name) ? byIndent(code, starts) : byBraces(code);
|
|
85
|
+
return found.map((f) => {
|
|
86
|
+
const line = lineOf(f.from);
|
|
87
|
+
const end = lineOf(f.to);
|
|
88
|
+
return { name: f.name, line, end, params: f.params, statements: list.filter((s) => s.line >= line && s.line <= end).length };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The `git add` calls in a shell command, with the folder each runs in and its arguments. secrets-guard and repo-litter both judge these.
|
|
2
|
+
// gitAdds(command, cwd) -> [{ dir, args }]
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export function gitAdds(command, cwd) {
|
|
6
|
+
const body = command.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, '');
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const part of body.split(/&&|\|\||;|\|/)) {
|
|
9
|
+
const w = part.trim().split(/\s+/);
|
|
10
|
+
if (w[0] !== 'git') continue;
|
|
11
|
+
let at = 1;
|
|
12
|
+
let dir = cwd;
|
|
13
|
+
while (at < w.length && w[at].startsWith('-')) {
|
|
14
|
+
if (w[at] === '-C') dir = resolve(dir, w[at + 1] ?? '.');
|
|
15
|
+
at += ['-C', '-c'].includes(w[at]) ? 2 : 1;
|
|
16
|
+
}
|
|
17
|
+
if (w[at] === 'add') out.push({ dir, args: w.slice(at + 1) });
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// git-guard's selftest for the pushed laws (KWB-26), on a real repo whose "GitHub" is a bare repo beside it. The hook runs
|
|
2
|
+
// the way Claude Code runs it (through run-hooks.mjs, the event on stdin, CLAUDE_PROJECT_DIR set):
|
|
3
|
+
// a commit on a branch whose earlier commits are not on the remote is refused; pushed, it passes; a branch's first
|
|
4
|
+
// commit passes; a push earlier in the same command counts; the remote out of reach lets it through with a warning;
|
|
5
|
+
// Stop is refused while a local branch is ahead of the remote or missing there, and passes once all are pushed;
|
|
6
|
+
// the refusals name the push and never offer deleting a branch; three identical Stop refusals end the session.
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { git, put } from './selftest.mjs';
|
|
13
|
+
import { remoteHeads } from './pushed.mjs';
|
|
14
|
+
|
|
15
|
+
const RUNNER = fileURLToPath(new URL('../run-hooks.mjs', import.meta.url));
|
|
16
|
+
|
|
17
|
+
export function pushedCases(hookFile) {
|
|
18
|
+
const top = realpathSync(mkdtempSync(join(process.env.TMPDIR ?? tmpdir(), 'git-guard-pushed-')));
|
|
19
|
+
const dir = join(top, 'repo');
|
|
20
|
+
const hub = join(top, 'hub.git');
|
|
21
|
+
const session = `selftest-${process.pid}-${Date.now()}`;
|
|
22
|
+
put(join(dir, '.claude/layout.json'), JSON.stringify({ folders: {}, guards: { git: { house: true } } }));
|
|
23
|
+
git(top, 'init', '-q', '--bare', '-b', 'main', hub);
|
|
24
|
+
git(top, 'init', '-q', '-b', 'main', dir);
|
|
25
|
+
git(dir, 'remote', 'add', 'origin', hub);
|
|
26
|
+
const commit = (msg) => { writeFileSync(join(dir, `${msg}.txt`), msg); git(dir, 'add', `${msg}.txt`); git(dir, 'commit', '-q', '-m', msg); };
|
|
27
|
+
const push = (...b) => git(dir, 'push', '-q', 'origin', ...b);
|
|
28
|
+
const run = (payload) => spawnSync(process.execPath, [RUNNER, hookFile], {
|
|
29
|
+
cwd: dir, encoding: 'utf8', input: JSON.stringify({ session_id: session, cwd: dir, ...payload }),
|
|
30
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: dir },
|
|
31
|
+
});
|
|
32
|
+
const bash = (command) => run({ hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: { command } });
|
|
33
|
+
const stop = () => run({ hook_event_name: 'Stop', stop_hook_active: false });
|
|
34
|
+
const cases = [];
|
|
35
|
+
const C = "git commit -m 'next' -- a.txt";
|
|
36
|
+
|
|
37
|
+
commit('first');
|
|
38
|
+
push('main');
|
|
39
|
+
git(dir, 'switch', '-q', '-c', 'work/x');
|
|
40
|
+
cases.push(['a new branch\'s first commit passes', bash(C).status, 0]);
|
|
41
|
+
commit('one');
|
|
42
|
+
const r1 = bash(C);
|
|
43
|
+
cases.push(['an earlier commit never pushed: the next commit is refused', r1.status, 2]);
|
|
44
|
+
cases.push(['the refusal names the commit and the push with the token named', /\bone\b/.test(r1.stderr) && /git push https:\/\/x-access-token:\$PERSONAL_GITHUB_TOKEN@github\.com\/\S+ work\/x/.test(r1.stderr), true]);
|
|
45
|
+
cases.push(['the refusal never offers deleting a branch', /branch -[dD]|--delete|delete the branch/.test(r1.stderr), false]);
|
|
46
|
+
cases.push(['a push earlier in the same command counts', bash(`source ~/.env && git push https://x-access-token:$PERSONAL_GITHUB_TOKEN@github.com/HouseOwner/repo.git work/x && ${C}`).status, 0]);
|
|
47
|
+
push('work/x');
|
|
48
|
+
cases.push(['pushed seconds after a refusal (the cache is fresh): the commit passes', bash(C).status, 0]);
|
|
49
|
+
commit('two');
|
|
50
|
+
cases.push(['a pushed branch one commit ahead: refused', bash(`cd ${dir} && ${C}`).status, 2]);
|
|
51
|
+
cases.push(['git -C into the repo is judged there', bash(`git -C ${dir} commit -m x -- a.txt`).status, 2]);
|
|
52
|
+
const lone = join(top, 'lone');
|
|
53
|
+
git(top, 'init', '-q', '-b', 'work/z', lone);
|
|
54
|
+
git(lone, 'commit', '-q', '--allow-empty', '-m', 'a');
|
|
55
|
+
git(lone, 'commit', '-q', '--allow-empty', '-m', 'b');
|
|
56
|
+
cases.push(['a repo with no remote has nothing to push to: passes', bash(`git -C ${lone} commit --allow-empty -m c`).status, 0]);
|
|
57
|
+
// a repo that pushes by URL only names its GitHub repo in the layout (guards.git.repo): judged against it
|
|
58
|
+
put(join(lone, '.claude/layout.json'), JSON.stringify({ folders: {}, guards: { git: { house: true, repo: hub } } }));
|
|
59
|
+
const byUrl = spawnSync(process.execPath, [RUNNER, hookFile], { cwd: lone, encoding: 'utf8', input: JSON.stringify({ session_id: session, cwd: lone, hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'git commit --allow-empty -m c' } }), env: { ...process.env, CLAUDE_PROJECT_DIR: lone } });
|
|
60
|
+
cases.push(['no remote, guards.git.repo named: its unpushed commits refuse the next commit', byUrl.status, 2]);
|
|
61
|
+
|
|
62
|
+
// KWB-48: another repo with no remote, the project's layout naming the project's own repo: nothing to compare with
|
|
63
|
+
const other = join(top, 'other');
|
|
64
|
+
git(top, 'init', '-q', '-b', 'work/o', other);
|
|
65
|
+
git(other, 'commit', '-q', '--allow-empty', '-m', 'a');
|
|
66
|
+
git(other, 'commit', '-q', '--allow-empty', '-m', 'b');
|
|
67
|
+
const foreign = spawnSync(process.execPath, [RUNNER, hookFile], { cwd: other, encoding: 'utf8', input: JSON.stringify({ session_id: session, cwd: other, hook_event_name: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'git commit --allow-empty -m c' } }), env: { ...process.env, CLAUDE_PROJECT_DIR: lone } });
|
|
68
|
+
cases.push(["a repo with no remote is not judged against the project's guards.git.repo", foreign.status, 0]);
|
|
69
|
+
// Stop: work/x is ahead, work/y was never pushed
|
|
70
|
+
git(dir, 'switch', '-q', '-c', 'work/y');
|
|
71
|
+
const s1 = stop();
|
|
72
|
+
cases.push(['Stop with a branch ahead and one missing: refused', s1.status, 2]);
|
|
73
|
+
cases.push(['the Stop refusal lists both with the push', /work\/x.*1 commit/.test(s1.stderr) && /work\/y.*not on/.test(s1.stderr) && /git push https:\/\/x-access-token:\$PERSONAL_GITHUB_TOKEN@\S+ work\/x work\/y/.test(s1.stderr), true]);
|
|
74
|
+
cases.push(['the Stop refusal never offers deleting a branch', /branch -[dD]|--delete|delete the branch/.test(s1.stderr), false]);
|
|
75
|
+
cases.push(['the same refusal again: still refused', stop().status, 2]);
|
|
76
|
+
stop();
|
|
77
|
+
const s4 = stop();
|
|
78
|
+
cases.push(['the fourth identical refusal ends the session, the list goes to the user', s4.status === 0 && /work\/y/.test(s4.stdout), true]);
|
|
79
|
+
push('work/x', 'work/y');
|
|
80
|
+
cases.push(['every branch pushed: Stop passes', stop().status, 0]);
|
|
81
|
+
const h = remoteHeads(dir, 'origin');
|
|
82
|
+
cases.push(['a look the last one clears makes no network call (the cache)', h.cached === true && h.heads.get('work/y') !== undefined, true]);
|
|
83
|
+
|
|
84
|
+
// the remote out of reach: the work goes ahead with a warning
|
|
85
|
+
commit('three');
|
|
86
|
+
git(dir, 'remote', 'set-url', 'origin', join(top, 'gone.git'));
|
|
87
|
+
const off = bash(C);
|
|
88
|
+
cases.push(['GitHub out of reach: the commit goes ahead', off.status, 0]);
|
|
89
|
+
cases.push(['and the user is told it was not checked', /could not be reached/.test(off.stdout), true]);
|
|
90
|
+
cases.push(['GitHub out of reach: Stop goes ahead with a warning', stop().status, 0]);
|
|
91
|
+
|
|
92
|
+
// the house laws off: nothing judged
|
|
93
|
+
git(dir, 'remote', 'set-url', 'origin', hub);
|
|
94
|
+
put(join(dir, '.claude/layout.json'), JSON.stringify({ folders: {} }));
|
|
95
|
+
cases.push(['house laws off: an unpushed commit is not judged', bash(C).status, 0]);
|
|
96
|
+
cases.push(['house laws off: Stop is not judged', stop().status, 0]);
|
|
97
|
+
rmSync(top, { recursive: true, force: true });
|
|
98
|
+
return cases;
|
|
99
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Tiny glob matcher (no dependencies): supports **, *, ? and {a,b}. Paths use "/".
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
const ONE_CHAR = { '*': '[^/]*', '?': '[^/]' }; // a lone * stays inside one folder; ? is one character
|
|
4
|
+
|
|
5
|
+
export function globToRegExp(glob) {
|
|
6
|
+
if (cache.has(glob)) return cache.get(glob);
|
|
7
|
+
let re = '';
|
|
8
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
9
|
+
const c = glob[i];
|
|
10
|
+
if (c === '*' && glob[i + 1] === '*') {
|
|
11
|
+
const slash = glob[i + 2] === '/';
|
|
12
|
+
re += slash ? '(?:.*/)?' : '.*';
|
|
13
|
+
i += slash ? 2 : 1;
|
|
14
|
+
} else if (c === '{') {
|
|
15
|
+
const end = glob.indexOf('}', i);
|
|
16
|
+
re += `(?:${glob.slice(i + 1, end).split(',').map(escape).join('|')})`;
|
|
17
|
+
i = end;
|
|
18
|
+
} else re += Object.hasOwn(ONE_CHAR, c) ? ONE_CHAR[c] : escape(c);
|
|
19
|
+
}
|
|
20
|
+
const compiled = new RegExp(`^${re}$`);
|
|
21
|
+
cache.set(glob, compiled);
|
|
22
|
+
return compiled;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function escape(s) {
|
|
26
|
+
return s.replace(/[.+^$()|[\]\\]/g, '\\$&');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Match against a project-relative path; a leading "**/" also matches files at the root.
|
|
30
|
+
export function matchesAny(relPath, globs) {
|
|
31
|
+
const p = relPath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
32
|
+
return globs.some((g) => globToRegExp(g).test(p));
|
|
33
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// hook-log: every hook's run time, one line per call in the project's .claude/hooks.log (KWB-24: The user saw 17 hooks take 48 s on
|
|
2
|
+
// one rm and nothing said which). A line: ISO time, event, hook, ms, exit code, tool, tab-separated. The node hooks write it
|
|
3
|
+
// through this file (the runner for the hooks it runs, asHook for a hook run on its own); the python ones through
|
|
4
|
+
// lib/house.py's log_hook, the same line. doctor reads it (median and worst per hook over its last 100 calls).
|
|
5
|
+
// Nothing is ever cleared. Past LOG_MAX_BYTES a log moves whole to .claude/logs/ under a dated name and a new one starts (appendRotated).
|
|
6
|
+
// Until a project knows which hooks earn their place it needs all of its history, so no rotated file is deleted.
|
|
7
|
+
// lib/event-log.mjs keeps the detail: every call, and every refusal with its reason.
|
|
8
|
+
// asHook(name, fn): a hook run as its own process: stdin to fn(text), the time since the process began, logged; the exit code
|
|
9
|
+
// isMain(import.meta.url): true when this module is the program node was started with (never when the runner imports it)
|
|
10
|
+
// logHook(root, { event, hook, ms, code, tool }) readHookLog(root) -> [{ time, event, hook, ms, code, tool }]
|
|
11
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync } from 'node:fs';
|
|
12
|
+
import { basename, extname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { logCall } from './event-log.mjs';
|
|
15
|
+
import { readLayout } from './layout.mjs';
|
|
16
|
+
|
|
17
|
+
export const LOG = join('.claude', 'hooks.log');
|
|
18
|
+
export const LOG_MAX_BYTES = 1024 * 1024;
|
|
19
|
+
export const LOGS = join('.claude', 'logs');
|
|
20
|
+
|
|
21
|
+
const limits = new Map();
|
|
22
|
+
const maxBytes = (root) => {
|
|
23
|
+
if (!limits.has(root)) limits.set(root, (readLayout(root).guards?.logs?.maxKb ?? LOG_MAX_BYTES / 1024) * 1024);
|
|
24
|
+
return limits.get(root);
|
|
25
|
+
};
|
|
26
|
+
const stamp = () => new Date().toISOString().replace(/[-:]/g, '').replace('T', '-').slice(0, 15);
|
|
27
|
+
|
|
28
|
+
// Moves a full log to .claude/logs/<name>-<time><ext>. A second writer finding it gone just carries on: nothing is lost.
|
|
29
|
+
function rotate(root, file) {
|
|
30
|
+
const ext = extname(file);
|
|
31
|
+
const name = basename(file, ext);
|
|
32
|
+
const dir = join(root, LOGS);
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
let to = join(dir, `${name}-${stamp()}${ext}`);
|
|
35
|
+
for (let n = 1; existsSync(to); n += 1) to = join(dir, `${name}-${stamp()}-${n}${ext}`);
|
|
36
|
+
renameSync(file, to);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// One or more lines onto a log, rotating it when full. Never throws, never creates .claude: a hook is never stopped by its own log.
|
|
40
|
+
export function appendRotated(root, rel, text) {
|
|
41
|
+
try {
|
|
42
|
+
if (!existsSync(join(root, '.claude'))) return;
|
|
43
|
+
const file = join(root, rel);
|
|
44
|
+
mkdirSync(join(file, '..'), { recursive: true });
|
|
45
|
+
appendFileSync(file, text);
|
|
46
|
+
if (statSync(file).size > maxBytes(root)) rotate(root, file);
|
|
47
|
+
} catch { /* the log never stops a hook */ }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const real = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
|
|
51
|
+
export const isMain = (url) => Boolean(process.argv[1]) && real(process.argv[1]) === real(fileURLToPath(url));
|
|
52
|
+
export const projectRoot = () => resolve(process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
|
|
53
|
+
|
|
54
|
+
export function logHook(root, { event = '-', hook, ms, code, tool = '-' }) {
|
|
55
|
+
const clean = (v) => String(v ?? '-').replace(/[\t\n]/g, ' ') || '-';
|
|
56
|
+
appendRotated(root, LOG, `${new Date().toISOString()}\t${clean(event)}\t${clean(hook)}\t${Math.round(ms)}\t${code ?? '-'}\t${clean(tool)}\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// One line per refused concept in .claude/usage.log: time, hook, concept (the shape hooks count what they refuse).
|
|
60
|
+
export function logRefusal(root, hook, concepts) {
|
|
61
|
+
try {
|
|
62
|
+
mkdirSync(join(root, '.claude'), { recursive: true });
|
|
63
|
+
const t = new Date().toISOString();
|
|
64
|
+
appendRotated(root, join('.claude', 'usage.log'), [...new Set(concepts)].map((c) => `${t}\t${hook}\t${c}\n`).join(''));
|
|
65
|
+
} catch { /* the count is a report, never a reason to fail the hook */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The live log, or with `{ rotated: true }` every line it ever held (doctor's crash report: a crash is not over because its line moved).
|
|
69
|
+
export function readHookLog(root, { rotated = false } = {}) {
|
|
70
|
+
const old = rotated ? rotatedHookLogs(root) : [];
|
|
71
|
+
const text = [...old, join(root, LOG)].map((f) => { try { return readFileSync(f, 'utf8'); } catch { return ''; } }).join('\n');
|
|
72
|
+
return text.split('\n').filter(Boolean).map((l) => l.split('\t')).filter((c) => c.length >= 5 && /^\d+$/.test(c[3]))
|
|
73
|
+
.map(([time, event, hook, ms, code, tool]) => ({ time, event, hook, ms: Number(ms), code, tool: tool ?? '-' }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function rotatedHookLogs(root) {
|
|
77
|
+
try { return readdirSync(join(root, LOGS)).filter((f) => /^hooks-.*\.log$/.test(f)).sort().map((f) => join(root, LOGS, f)); } catch { return []; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const eventOf = (text) => { try { const p = JSON.parse(text); return { event: p.hook_event_name, tool: p.tool_name ?? p.source }; } catch { return {}; } };
|
|
81
|
+
|
|
82
|
+
// A hook run on its own: the time is from the process's start (node's own start-up included, which is what a hook costs).
|
|
83
|
+
export async function asHook(name, fn) {
|
|
84
|
+
let text = '';
|
|
85
|
+
try { text = readFileSync(0, 'utf8'); } catch { /* no stdin */ }
|
|
86
|
+
const said = [];
|
|
87
|
+
const write = process.stderr.write.bind(process.stderr);
|
|
88
|
+
process.stderr.write = (chunk, ...rest) => { said.push(String(chunk)); return write(chunk, ...rest); };
|
|
89
|
+
let code;
|
|
90
|
+
try {
|
|
91
|
+
code = await fn(text);
|
|
92
|
+
} finally {
|
|
93
|
+
process.stderr.write = write;
|
|
94
|
+
const hook = name ?? basename(process.argv[1] ?? '-').replace(/\.\w+$/, '');
|
|
95
|
+
const root = projectRoot();
|
|
96
|
+
logHook(root, { ...eventOf(text), hook, ms: performance.now(), code: code ?? 1 });
|
|
97
|
+
logCall(root, text, [{ hook, ms: performance.now(), code: code ?? 1, reason: said.join('') }], appendRotated);
|
|
98
|
+
}
|
|
99
|
+
return code ?? 0;
|
|
100
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""house: what the python hooks share: the project root, its .claude/layout.json and a guard's settings.
|
|
2
|
+
|
|
3
|
+
A hook imports it with its own folder's lib/ on sys.path and byte-code writing off, so no __pycache__ appears in a project:
|
|
4
|
+
|
|
5
|
+
sys.dont_write_bytecode = True; sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib"))
|
|
6
|
+
import house
|
|
7
|
+
|
|
8
|
+
A project's values for a guard live in layout.json under "guards": { "<guard>": { ... } }; a key it does not set takes the
|
|
9
|
+
hook's default, so a project with no "guards" gets the house defaults.
|
|
10
|
+
"""
|
|
11
|
+
import json, os, re, time
|
|
12
|
+
|
|
13
|
+
LOG = os.path.join(".claude", "hooks.log")
|
|
14
|
+
LOG_MAX_BYTES = 1024 * 1024
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def project_root():
|
|
18
|
+
"""The project the session runs in (CLAUDE_PROJECT_DIR), else the current folder."""
|
|
19
|
+
return os.path.abspath(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main_root(path):
|
|
23
|
+
"""A path inside <project>/.claude/worktrees/<name>/ belongs to <project>; any other path is returned as it is."""
|
|
24
|
+
m = re.match(r"^(.*?)/\.claude/worktrees/[^/]+(?:/|$)", path.replace(os.sep, "/"))
|
|
25
|
+
return m.group(1) if m else path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def layout(root=None):
|
|
29
|
+
try:
|
|
30
|
+
with open(os.path.join(root or project_root(), ".claude", "layout.json"), encoding="utf-8") as f:
|
|
31
|
+
return json.load(f)
|
|
32
|
+
except (OSError, ValueError):
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def guard(name, defaults, root=None):
|
|
37
|
+
"""The guard's settings: its defaults, overridden key by key by layout.json's guards.<name>."""
|
|
38
|
+
got = (layout(root).get("guards") or {}).get(name) or {}
|
|
39
|
+
return {**defaults, **got}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def shell_writes(command, cwd):
|
|
43
|
+
"""The files a shell command writes, with their text before and after: [{path, op, before, after}] (after None when it
|
|
44
|
+
cannot be known). One reader of the shell for every hook: lib/shell-writes.mjs, run by node (WB-69)."""
|
|
45
|
+
import subprocess
|
|
46
|
+
lib = os.path.join(os.path.dirname(os.path.abspath(__file__)), "shell-writes.mjs")
|
|
47
|
+
try:
|
|
48
|
+
r = subprocess.run(["node", lib, "--json"], input=json.dumps({"command": command, "cwd": cwd}), capture_output=True, text=True, timeout=15)
|
|
49
|
+
return json.loads(r.stdout) if r.returncode == 0 and r.stdout.strip() else []
|
|
50
|
+
except Exception:
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def folders_of_kind(lay, kind):
|
|
55
|
+
"""The paths of every layout folder of one kind ("copy": a read-only copy of another project's code)."""
|
|
56
|
+
return [f["path"].rstrip("/") for f in (lay.get("folders") or {}).values() if isinstance(f, dict) and f.get("kind") == kind]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def log_hook(hook, event, ms, code, tool="-", root=None):
|
|
60
|
+
"""One timing line per call in <project>/.claude/hooks.log, the same line lib/hook-log.mjs writes (KWB-24): ISO time,
|
|
61
|
+
event, hook, ms, exit code, tool, tab-separated; past LOG_MAX_BYTES it moves whole to .claude/logs/, never cut. Never raises,
|
|
62
|
+
never creates .claude. ms is from the hook's own start (python's start-up before it is not counted)."""
|
|
63
|
+
try:
|
|
64
|
+
base = root or project_root()
|
|
65
|
+
if not os.path.isdir(os.path.join(base, ".claude")):
|
|
66
|
+
return
|
|
67
|
+
path = os.path.join(base, LOG)
|
|
68
|
+
clean = lambda v: (str(v if v is not None else "-").replace("\t", " ").replace("\n", " ") or "-")
|
|
69
|
+
now = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) + ".%03dZ" % int((time.time() % 1) * 1000)
|
|
70
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
71
|
+
f.write(f"{now}\t{clean(event)}\t{clean(hook)}\t{round(ms)}\t{code}\t{clean(tool)}\n")
|
|
72
|
+
if os.path.getsize(path) > LOG_MAX_BYTES:
|
|
73
|
+
logs = os.path.join(base, ".claude", "logs")
|
|
74
|
+
os.makedirs(logs, exist_ok=True)
|
|
75
|
+
os.rename(path, os.path.join(logs, "hooks-" + time.strftime("%Y%m%d-%H%M%S", time.gmtime()) + ".log"))
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// One row per language, for the statement reader (0.25.0: every language, because AI writes the same thing over and over). A row says how
|
|
2
|
+
// to blank a file's comments and strings, where a statement ends, what is not code, and how a function is defined. A new language is one row.
|
|
3
|
+
// languageOf(ext) -> the row, or null ALL_CODE: every extension the table reads
|
|
4
|
+
import { blankCode } from './cpp-scan.mjs';
|
|
5
|
+
|
|
6
|
+
const spaces = (s) => s.replace(/[^\n]/g, ' ');
|
|
7
|
+
|
|
8
|
+
// A blanker for a language with no blanker of its own: comments go, strings keep their quotes so they stay one token, at the same offsets.
|
|
9
|
+
function blanker({ line = [], block = [], quotes = ['"', "'"], raw = [], hashWord = false, heredoc = false }) {
|
|
10
|
+
const opener = (src, i) => raw.find((q) => src.startsWith(q, i));
|
|
11
|
+
return (src) => {
|
|
12
|
+
let out = '';
|
|
13
|
+
let i = 0;
|
|
14
|
+
const skipTo = (stop, keepQuotes) => {
|
|
15
|
+
const body = src.slice(i, stop);
|
|
16
|
+
out += keepQuotes && body.length >= 2 ? `"${spaces(body.slice(1, -1))}"` : spaces(body);
|
|
17
|
+
i = stop;
|
|
18
|
+
};
|
|
19
|
+
while (i < src.length) {
|
|
20
|
+
const here = src[i];
|
|
21
|
+
const pair = block.find(([a]) => src.startsWith(a, i));
|
|
22
|
+
const atWord = !hashWord || i === 0 || /\s/.test(src[i - 1]);
|
|
23
|
+
const q = opener(src, i);
|
|
24
|
+
const doc = heredoc && src.startsWith('<<', i) ? src.slice(i).match(/^<<-?\s*['"]?([A-Za-z_]\w*)['"]?[^\n]*\n/) : null;
|
|
25
|
+
if (pair) { const e = src.indexOf(pair[1], i + pair[0].length); skipTo(e === -1 ? src.length : e + pair[1].length, false); }
|
|
26
|
+
else if (line.some((c) => src.startsWith(c, i)) && atWord) { const e = src.indexOf('\n', i); skipTo(e === -1 ? src.length : e, false); }
|
|
27
|
+
else if (q) { const e = src.indexOf(q, i + q.length); skipTo(e === -1 ? src.length : e + q.length, true); }
|
|
28
|
+
else if (doc) {
|
|
29
|
+
const end = src.slice(i).search(new RegExp(`\\n[ \\t]*${doc[1]}[ \\t]*(?:\\n|$)`));
|
|
30
|
+
out += src.slice(i, i + doc[0].length); i += doc[0].length;
|
|
31
|
+
skipTo(end === -1 ? src.length : src.indexOf('\n', i + end - doc[0].length + 1), false);
|
|
32
|
+
} else if (quotes.includes(here)) {
|
|
33
|
+
let j = i + 1;
|
|
34
|
+
while (j < src.length && src[j] !== here && (src[j] !== '\n' || heredoc)) j += src[j] === '\\' ? 2 : 1;
|
|
35
|
+
skipTo(Math.min(j + 1, src.length), true);
|
|
36
|
+
} else { out += here; i += 1; }
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const SLASHES = { line: ['//'], block: [['/*', '*/']] };
|
|
43
|
+
const lineStart = (words) => new RegExp(`^[ \\t]*(?:${words})\\b.*$`, 'gm');
|
|
44
|
+
|
|
45
|
+
export const LANGUAGES = [
|
|
46
|
+
{ name: 'c', exts: ['.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.mm'], blank: blankCode, notCode: /^[ \t]*(?:#(?:.*\\\n)*.*|using\b.*)$/gm, minTokens: 60,
|
|
47
|
+
define: /^(?:[A-Za-z_][\w:<>,*& ]*[\s*&])?([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{/gm },
|
|
48
|
+
{ name: 'js', exts: ['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.mts', '.cts'], blank: blankCode, lines: true, notCode: /^[ \t]*import\b[^;]*;?$/gm, minTokens: 60,
|
|
49
|
+
define: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function\*?\s+([A-Za-z_$][\w$]*)|(?:const|let)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>|class\s+([A-Za-z_$][\w$]*))/gm },
|
|
50
|
+
{ name: 'python', exts: ['.py', '.pyi'], blank: blanker({ line: ['#'], raw: ['"""', "'''"] }), lines: true, notCode: /^[ \t]*(?:from\s+\S+\s+)?import\b.*$/gm, minTokens: 45,
|
|
51
|
+
define: /^(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/gm },
|
|
52
|
+
{ name: 'shell', exts: ['.sh', '.bash', '.zsh', '.ksh'], blank: blanker({ line: ['#'], hashWord: true, heredoc: true }), lines: true, commands: true, minTokens: 30,
|
|
53
|
+
token: /"\s*"|'\s*'|[^\s;|&()<>{}]+|[;|&()<>{}]/g, notCode: /^[ \t]*(?:\.|source|set|shopt|trap|cd)\s.*$/gm, define: /^(?:function\s+)?([A-Za-z_][\w-]*)\s*(?:\(\s*\))?\s*\{/gm },
|
|
54
|
+
{ name: 'go', exts: ['.go'], blank: blanker({ ...SLASHES, raw: ['`'] }), lines: true, notCode: /^(?:package\b.*|import\s*\([^)]*\)|import\b.*)$/gm, minTokens: 50, define: /^func\s+([A-Za-z_]\w*)/gm },
|
|
55
|
+
{ name: 'rust', exts: ['.rs'], blank: blanker({ ...SLASHES, quotes: ['"'] }), notCode: lineStart('use|mod|extern|pub use'), minTokens: 60, define: /^(?:pub(?:\([a-z]+\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/gm },
|
|
56
|
+
{ name: 'java', exts: ['.java', '.cs', '.scala', '.dart'], blank: blanker(SLASHES), notCode: lineStart('import|package|using|namespace'), minTokens: 60,
|
|
57
|
+
define: /^[ \t]*(?:public|private|protected|internal|static|final|async|override|virtual|\s)+[\w<>[\],\s]+\s([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:throws [\w, ]+)?\s*\{/gm },
|
|
58
|
+
{ name: 'kotlin', exts: ['.kt', '.kts', '.swift'], blank: blanker({ ...SLASHES, raw: ['"""'] }), lines: true, notCode: lineStart('import|package'), minTokens: 50, define: /^(?:(?:public|private|internal)\s+)?(?:fun|func)\s+([A-Za-z_]\w*)/gm },
|
|
59
|
+
{ name: 'ruby', exts: ['.rb', '.rake'], blank: blanker({ line: ['#'], block: [['=begin', '=end']] }), lines: true, notCode: lineStart('require|require_relative|include'), minTokens: 45, define: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[?!]?)/gm },
|
|
60
|
+
{ name: 'php', exts: ['.php'], blank: blanker({ line: ['//', '#'], block: [['/*', '*/']] }), notCode: lineStart('use|namespace|require|include'), minTokens: 60, define: /^\s*(?:public\s+|private\s+|protected\s+|static\s+)*function\s+([A-Za-z_]\w*)/gm },
|
|
61
|
+
{ name: 'lua', exts: ['.lua'], blank: blanker({ line: ['--'], block: [['--[[', ']]']] }), lines: true, notCode: /^$^/gm, minTokens: 45, define: /^(?:local\s+)?function\s+([A-Za-z_][\w.:]*)/gm },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const BY_EXT = new Map(LANGUAGES.flatMap((l) => l.exts.map((e) => [e, l])));
|
|
65
|
+
export const languageOf = (ext) => BY_EXT.get(ext) ?? null;
|
|
66
|
+
export const ALL_CODE = new Set(BY_EXT.keys());
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// .claude/layout.json is the one source of truth for where things live:
|
|
2
|
+
// { "folders": { name: { "path", "purpose" } }, "concepts": { concept: [folderName | glob, ...] } }
|
|
3
|
+
// Rules, lesson checks, the folder-law hook and the CLAUDE.md layout section all read it.
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export function readLayout(root) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(join(root, '.claude', 'layout.json'), 'utf8'));
|
|
10
|
+
} catch {
|
|
11
|
+
return { folders: {}, concepts: {} };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// A concept entry is a folder name (expands to "<path>/**"), "kind:<k>" (every folder whose
|
|
16
|
+
// "kind" is k, e.g. "kind:copy" for the copy trees; none is no glob), or a raw glob.
|
|
17
|
+
export function conceptGlobs(layout, concept) {
|
|
18
|
+
const entries = layout.concepts?.[concept];
|
|
19
|
+
if (!entries) return null;
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const e of entries) {
|
|
22
|
+
const folder = layout.folders?.[e];
|
|
23
|
+
const globs = folder ? [`${folder.path}/**`] : e.startsWith('kind:') ? foldersOfKind(layout, e.slice(5)).map((p) => `${p}/**`) : [e];
|
|
24
|
+
for (const glob of globs) if (!out.includes(glob)) out.push(glob);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The paths of every layout folder of one kind ("copy": read-only copies of another project's code).
|
|
30
|
+
export function foldersOfKind(layout, kind) {
|
|
31
|
+
return Object.values(layout.folders ?? {}).filter((f) => f.kind === kind).map((f) => f.path.replace(/\/+$/, ''));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Concepts from packs that aren't installed (e.g. "world" in a project without the stellix pack)
|
|
35
|
+
// are skipped; a rule must still resolve to at least one glob.
|
|
36
|
+
export function globsForConcepts(layout, concepts) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const c of concepts) for (const g of conceptGlobs(layout, c) ?? []) if (!out.includes(g)) out.push(g);
|
|
39
|
+
if (out.length === 0) throw new Error(`none of the concepts [${concepts.join(', ')}] exist in .claude/layout.json`);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Folders that may hold source code: every folder except docs/tools, deepest first.
|
|
44
|
+
export function codeFolders(layout) {
|
|
45
|
+
return Object.entries(layout.folders ?? {})
|
|
46
|
+
.filter(([name]) => !['docs', 'tools', 'src'].includes(name))
|
|
47
|
+
.map(([, f]) => f.path)
|
|
48
|
+
.sort((a, b) => b.length - a.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// "@alias" entries in a check's exclude/globs resolve to layout.files[alias]; other entries pass through.
|
|
52
|
+
export function resolveAliases(layout, entries = []) {
|
|
53
|
+
return entries.map((e) => (e.startsWith('@') ? layout.files?.[e.slice(1)] ?? `__missing_alias_${e.slice(1)}__` : e));
|
|
54
|
+
}
|