@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
package/lib/scan.mjs
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// claude-packs scan (0.16.0: a project owner runs it before choosing to install anything). A real install's review found bugs that
|
|
2
|
+
// "could have been super bad on production", so the scan is that review, on a copy:
|
|
3
|
+
// 1. the project's files are copied to a temp folder (what git tracks or would track; never .env, keys, node_modules or build output),
|
|
4
|
+
// so the project itself, its .git included, is never written to;
|
|
5
|
+
// 2. the recommended packs are installed into the copy (lib/scan-detect.mjs says which, with the evidence);
|
|
6
|
+
// 3. every check runs over every file, and the secret patterns, the line limit and coverage with them;
|
|
7
|
+
// 4. the report and every hit go beside the copy. A hit is a candidate, not a bug: a real install's run had 189 real of 302 counted.
|
|
8
|
+
// The bugs that hurt most are the ones no check sees, so the report ends with the review step: `scan --prompt` (prompts/scan-review.md).
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { copyFileSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join, relative } from 'node:path';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import { isContent } from '../packs/base/hooks/lib/content.mjs';
|
|
15
|
+
import { readLayout } from '../packs/base/hooks/lib/layout.mjs';
|
|
16
|
+
import { isSecretFile } from '../packs/base/hooks/secrets-guard.mjs';
|
|
17
|
+
import { sourceFilesOf } from './configs.mjs';
|
|
18
|
+
import { SOURCE, coverage } from './coverage.mjs';
|
|
19
|
+
import { onceOnlyDoctor } from './once-only-report.mjs';
|
|
20
|
+
import { PACKS_DIR, availablePacks } from './packs.mjs';
|
|
21
|
+
import { nearCopies, remadeNames, repeatLine, repeatedShapes } from './repeats.mjs';
|
|
22
|
+
import { rememberScan } from './fix-plan.mjs';
|
|
23
|
+
import { repoWeight, weightLines } from './repo-weight.mjs';
|
|
24
|
+
import { recommend } from './scan-detect.mjs';
|
|
25
|
+
import { sync } from './sync.mjs';
|
|
26
|
+
|
|
27
|
+
const SKIP = /(^|\/)(\.git|node_modules|dist|build|out|coverage|\.next|\.claude|_deps|JUCE|cmake-build[^/]*)(\/|$)/;
|
|
28
|
+
const MAX_BYTES = 1_000_000;
|
|
29
|
+
const SHOWN = 5;
|
|
30
|
+
const TEXT = 'public text';
|
|
31
|
+
const KINDS = [
|
|
32
|
+
['security', /\b(?:secrets?|tokens?|xss|injection|credentials?|passwords?|csrf|redirects?|sanitize\w*|escape[sd]?|untrusted|helmet|cookies?)\b/i],
|
|
33
|
+
['failures and lost data', /\b(?:swallow\w*|lost|silently|crash\w*|shutdown|unhandled\w*|delete\w*|drops?|transaction\w*|floating|never stops?)\b/i],
|
|
34
|
+
['quality', /./],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function walk(root, dir = root) {
|
|
38
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
39
|
+
const abs = join(dir, e.name);
|
|
40
|
+
if (SKIP.test(relative(root, abs))) return [];
|
|
41
|
+
return e.isDirectory() ? walk(root, abs) : [relative(root, abs).replace(/\\/g, '/')];
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function filesOf(dir) {
|
|
46
|
+
try {
|
|
47
|
+
return execFileSync('git', ['-C', dir, 'ls-files', '-co', '--exclude-standard', '-z'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).split('\0').filter(Boolean);
|
|
48
|
+
} catch {
|
|
49
|
+
return walk(dir);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const copyable = (dir, rel) => {
|
|
54
|
+
if (SKIP.test(rel) || isSecretFile(rel)) return false;
|
|
55
|
+
try { const s = lstatSync(join(dir, rel)); return s.isFile() && s.size <= MAX_BYTES; } catch { return false; }
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function snapshot(dir, files) {
|
|
59
|
+
const copy = mkdtempSync(join(tmpdir(), 'claude-packs-scan-'));
|
|
60
|
+
for (const rel of files) {
|
|
61
|
+
mkdirSync(dirname(join(copy, rel)), { recursive: true });
|
|
62
|
+
copyFileSync(join(dir, rel), join(copy, rel));
|
|
63
|
+
}
|
|
64
|
+
return copy;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// A line as it may be shown: long tokens masked, so a report can be passed around.
|
|
68
|
+
const shown = (text) => text.replace(/[A-Za-z0-9_+/=-]{24,}/g, '[masked]').slice(0, 110);
|
|
69
|
+
const packOf = (file) => availablePacks().find((p) => { try { return lstatSync(join(PACKS_DIR, p, 'checks', file)).isFile(); } catch { return false; } }) ?? '?';
|
|
70
|
+
|
|
71
|
+
async function runChecks(copy, files) {
|
|
72
|
+
const hook = (name) => import(pathToFileURL(join(copy, '.claude/hooks', name)).href);
|
|
73
|
+
const { loadChecks, hitsOf } = await hook('lesson-checks.mjs');
|
|
74
|
+
const { matchesAny } = await hook('lib/glob.mjs');
|
|
75
|
+
const { SECRETS } = await hook('secrets-guard.mjs');
|
|
76
|
+
const texts = files.map((rel) => [rel, readFileSync(join(copy, rel), 'utf8')]).filter(([, t]) => !t.includes('\0'));
|
|
77
|
+
const results = [];
|
|
78
|
+
let samples = 0;
|
|
79
|
+
for (const c of loadChecks(copy)) {
|
|
80
|
+
const hits = texts.filter(([rel]) => matchesAny(rel, c.globs) && !matchesAny(rel, c.exclude ?? []))
|
|
81
|
+
.flatMap(([rel, text]) => hitsOf(c, text).map((h) => ({ file: rel, line: h.line, text: shown(h.text) })));
|
|
82
|
+
const pack = packOf(c.file);
|
|
83
|
+
const own = pack === 'writing' ? hits : hits.filter((h) => !isContent(h.file));
|
|
84
|
+
samples += hits.length - own.length;
|
|
85
|
+
if (own.length) results.push({ id: c.id, why: c.why, rule: c.rule, pack, kind: pack === 'writing' ? TEXT : c.kind ?? KINDS.find(([, re]) => re.test(`${c.id} ${c.why}`))[0], hits: own });
|
|
86
|
+
}
|
|
87
|
+
const secrets = texts.flatMap(([rel, text]) => SECRETS.flatMap(([name, re]) => [...text.matchAll(re)].map((m) => ({ file: rel, line: text.slice(0, m.index).split('\n').length, name }))));
|
|
88
|
+
return { results, secrets, texts, samples };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function report({ dir, copy, pick, results, secrets, skipped, longFiles, cov, dups, handBuilt, near, remade, samples, weight, count }) {
|
|
92
|
+
const code = results.filter((r) => r.kind !== TEXT);
|
|
93
|
+
const total = code.reduce((n, r) => n + r.hits.length, 0);
|
|
94
|
+
const inText = results.filter((r) => r.kind === TEXT).reduce((n, r) => n + r.hits.length, 0);
|
|
95
|
+
const packs = [...pick.always, ...pick.packs.map((p) => p.pack)];
|
|
96
|
+
const out = ['# claude-packs scan', '', `${dir}: ${count} files read, on a copy. Nothing in the project was changed.`, ''];
|
|
97
|
+
const inTests = (s) => /(^|\/)(tests?|__tests__|fixtures?)\/|\.(test|spec)\./.test(s.file) || isContent(s.file);
|
|
98
|
+
const live = secrets.filter((s) => !inTests(s));
|
|
99
|
+
if (live.length) out.push(`**Look at this first: ${live.length} possible secret(s) in the code, listed below. Any that is real is leaked: rotate it.**`, '');
|
|
100
|
+
out.push(`${total} check hits in ${new Set(code.flatMap((r) => r.hits.map((h) => h.file))).size} files${inText ? `, and ${inText} in public text, listed last` : ''}${samples ? `. ${samples} more in content folders are samples, left out` : ''}. A hit is a candidate, not a confirmed bug: someone has to read it. The worst bugs are usually the ones no check can see, so the review step at the end matters more than this list.`, '');
|
|
101
|
+
out.push('## What to install', '', `Configuration \`${pick.config}\` (${pick.why}), packs: ${packs.join(', ')}.`, '');
|
|
102
|
+
for (const p of pick.packs) out.push(`- \`${p.pack}\`: ${p.why}`);
|
|
103
|
+
if (pick.leftOut.length) out.push('', `Left out, nothing found for them: ${pick.leftOut.join(', ')}.`);
|
|
104
|
+
if (pick.fullStack) out.push('', 'Next.js and Fastify are both here: the `full-stack` configuration may fit, but it fixes a folder layout and has fewer checks. Compare before choosing.');
|
|
105
|
+
out.push('', '```', `npx --no-install @thedecipherist/claude-packs init ${pick.packs.map((p) => p.pack).join(' ')} --config ${pick.config}`, '```', '');
|
|
106
|
+
if (secrets.length) {
|
|
107
|
+
out.push('## Secrets', '', 'Values are never shown. Secret-shaped text in test files and content folders is usually an example, and is listed apart.', '');
|
|
108
|
+
for (const s of live) out.push(`- ${s.file}:${s.line}: ${s.name}`);
|
|
109
|
+
const planted = secrets.filter(inTests);
|
|
110
|
+
if (planted.length) out.push('', `In test files and content folders (${planted.length}):`, ...planted.map((s) => `- ${s.file}:${s.line}: ${s.name}`));
|
|
111
|
+
out.push('');
|
|
112
|
+
}
|
|
113
|
+
out.push('## Check hits', '', 'Grouped by a keyword match on what each check is for, so the groups are rough.', '');
|
|
114
|
+
for (const kind of [...KINDS.map(([k]) => k), TEXT]) {
|
|
115
|
+
const rows = results.filter((r) => r.kind === kind).sort((a, b) => b.hits.length - a.hits.length);
|
|
116
|
+
if (!rows.length) continue;
|
|
117
|
+
out.push(`### ${kind}`, '');
|
|
118
|
+
for (const r of rows) {
|
|
119
|
+
out.push(`**${r.id}** (${r.pack}): ${r.hits.length}. ${r.why}`, '');
|
|
120
|
+
for (const h of r.hits.slice(0, SHOWN)) out.push(`- ${h.file}:${h.line}: \`${h.text.replace(/`/g, "'")}\``);
|
|
121
|
+
if (r.hits.length > SHOWN) out.push(`- and ${r.hits.length - SHOWN} more (all in claude-packs-scan.json)`);
|
|
122
|
+
out.push('');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
out.push('## Structure', '', `- ${longFiles.length} source file(s) over 300 lines${longFiles.length ? `: ${longFiles.slice(0, 8).join(', ')}` : ''}`, `- ${dups}`, `- ${handBuilt.length} repeat(s) written by hand, each of which should be one template${handBuilt.length ? ':' : ''}`, ...handBuilt.slice(0, 12).map(repeatLine), `- ${near.length} near cop(ies), the same code with lines changed${near.length ? ':' : ''}`, ...near.slice(0, 8).map((n) => ` - ${n.file}:${n.line} ~ ${n.other}:${n.from}-${n.to} (${n.matched} of ${n.of} statements)`), `- ${remade.length} function(s) made again under the same name${remade.length ? ':' : ''}`, ...remade.slice(0, 8).map((m) => ` - ${m.name}: ${m.at.join(', ')}`));
|
|
126
|
+
out.push(...weightLines(weight));
|
|
127
|
+
out.push(`- ${cov.uncovered.length} source file(s) no rule covers yet; the layout is fitted to the project at install`, '');
|
|
128
|
+
if (skipped.length) out.push(`Not read: ${skipped.length} file(s), secret files, symlinks or over 1 MB. ${skipped.filter(isSecretFile).length} of them are secret files, left out on purpose.`, '');
|
|
129
|
+
out.push('## Next step', '', 'The checks find patterns. For runaway loops, security holes between files and work that disappears silently, run the review on the copy:', '', '```', `cd ${copy}`, 'npx --no-install @thedecipherist/claude-packs scan --prompt # give what it prints to Claude Code here', '```', '', `The copy holds your code: delete ${copy} when you are done.`, '');
|
|
130
|
+
return out.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function scan(projectDir, { out = null, log = console.log } = {}) {
|
|
134
|
+
const listed = filesOf(projectDir);
|
|
135
|
+
const weight = repoWeight(projectDir, listed);
|
|
136
|
+
const built = weight.built.map((b) => b.folder);
|
|
137
|
+
const files = listed.filter((rel) => copyable(projectDir, rel) && !built.some((folder) => rel.startsWith(folder)));
|
|
138
|
+
const pick = recommend(projectDir, files);
|
|
139
|
+
const copy = snapshot(projectDir, files);
|
|
140
|
+
await sync(copy, pick.packs.map((p) => p.pack), { config: pick.config, mkdirs: true, log: () => {} });
|
|
141
|
+
const { results, secrets, texts: all, samples } = await runChecks(copy, files);
|
|
142
|
+
const texts = all.filter(([rel]) => !isContent(rel));
|
|
143
|
+
const source = sourceFilesOf(copy, [...pick.always, ...pick.packs.map((p) => p.pack)]) ?? SOURCE; // base and synths name none
|
|
144
|
+
const cov = await coverage(copy, source);
|
|
145
|
+
const longFiles = texts.filter(([rel, t]) => source.test(rel) && t.split('\n').length - 1 > 300).map(([rel]) => rel);
|
|
146
|
+
const said = [];
|
|
147
|
+
onceOnlyDoctor(copy, (l) => said.push(l.trim()));
|
|
148
|
+
const layout = readLayout(projectDir);
|
|
149
|
+
const handBuilt = repeatedShapes(texts, layout);
|
|
150
|
+
const near = nearCopies(texts, layout);
|
|
151
|
+
const remade = remadeNames(texts, layout);
|
|
152
|
+
const dups = said.find((l) => l.startsWith('duplicates')) ?? 'duplicates: not measured (jscpd not found)';
|
|
153
|
+
const skipped = listed.filter((rel) => !files.includes(rel));
|
|
154
|
+
const text = report({ dir: projectDir, copy, pick, results, secrets, skipped, longFiles, cov, dups, handBuilt, near, remade, samples, weight, count: files.length });
|
|
155
|
+
const file = out ?? join(copy, 'claude-packs-scan.md');
|
|
156
|
+
writeFileSync(file, text);
|
|
157
|
+
const json = join(copy, 'claude-packs-scan.json');
|
|
158
|
+
writeFileSync(json, `${JSON.stringify({ pick, results, secrets, repeats: handBuilt, near, remade, longFiles, weight }, null, 2)}\n`);
|
|
159
|
+
rememberScan(projectDir, json);
|
|
160
|
+
log(`Scanned ${files.length} files on a copy; the project was not changed.`);
|
|
161
|
+
log(` ${pick.config}: ${[...pick.always, ...pick.packs.map((p) => p.pack)].join(', ')}`);
|
|
162
|
+
const inCode = secrets.filter((s) => !isContent(s.file)).length;
|
|
163
|
+
log(` ${results.filter((r) => r.kind !== TEXT).reduce((n, r) => n + r.hits.length, 0)} check hits, ${inCode} possible secret(s)${samples ? `; ${samples} sample hits in content folders left out` : ''}`);
|
|
164
|
+
log(`Report: ${file}`);
|
|
165
|
+
log(`Copy: ${copy} (delete it when you are done; it holds your code)`);
|
|
166
|
+
log('Next: `fix` turns this into a work order for Claude; `init --auto` installs what it suggests.');
|
|
167
|
+
return { file, copy, pick, results, secrets };
|
|
168
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// doctor's memory of proved hooks (0.13.0, KWB-41): a hook's selftest proves the hook, so a hook that has not changed has nothing new
|
|
2
|
+
// to prove. One install took 0.1 s and its doctor 7.4 s, all of it hooks proving what they had already proved, on every init,
|
|
3
|
+
// update and test. A selftest that PASSED is remembered by the hash of what it ran: the hook, every file of hooks/lib, the runner,
|
|
4
|
+
// node's version, the platform, the detector and the project's guards. Change any of those and it runs again; a failure is never
|
|
5
|
+
// remembered; `doctor --fresh` runs them all. The memory is a folder of empty files, safe to delete.
|
|
6
|
+
// CLAUDE_PACKS_SELFTEST_CACHE: the folder (default: claude-packs-selftests in the system temp folder)
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
const folder = () => process.env.CLAUDE_PACKS_SELFTEST_CACHE ?? join(tmpdir(), 'claude-packs-selftests');
|
|
13
|
+
|
|
14
|
+
function filesUnder(dir) {
|
|
15
|
+
if (!existsSync(dir)) return [];
|
|
16
|
+
return readdirSync(dir).filter((e) => e !== '__pycache__' && !e.endsWith('.pyc')).sort().flatMap((e) => {
|
|
17
|
+
const abs = join(dir, e);
|
|
18
|
+
return statSync(abs).isDirectory() ? filesUnder(abs) : [abs];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// What every selftest of this install runs on, hashed once: the shared libs, the runner, the machine, the project's guards.
|
|
23
|
+
export function sharedHash(claudeDir, guards = {}) {
|
|
24
|
+
const h = createHash('sha256');
|
|
25
|
+
for (const f of [...filesUnder(join(claudeDir, 'hooks', 'lib')), join(claudeDir, 'hooks', 'run-hooks.mjs')]) if (existsSync(f)) h.update(readFileSync(f));
|
|
26
|
+
h.update(`${process.version}\0${process.platform}\0${process.env.CLAUDE_PACKS_JSCPD ?? ''}\0${JSON.stringify(guards)}`);
|
|
27
|
+
return h.digest('hex');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const selftestKey = (file, shared) => createHash('sha256').update(shared).update(readFileSync(file)).digest('hex');
|
|
31
|
+
export const proved = (key) => existsSync(join(folder(), key));
|
|
32
|
+
|
|
33
|
+
// Never throws: a memory that cannot be written only means the selftest runs again next time.
|
|
34
|
+
export function remember(key) {
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(folder(), { recursive: true });
|
|
37
|
+
writeFileSync(join(folder(), key), '');
|
|
38
|
+
} catch { /* nothing to remember with */ }
|
|
39
|
+
}
|
package/lib/settings.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Merges the packs' hooks into .claude/settings.json. Hooks this package owns (a command that runs
|
|
2
|
+
// a script some pack ships, or one the last install put in .claude/hooks/) are rebuilt from the
|
|
3
|
+
// installed packs on every run; every other hook entry, the project's own, stays as it is.
|
|
4
|
+
// Kit hooks are node (.mjs); browser-guard.py is the one Python hook left (see README).
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { isDisabled, kitHookScripts, readPack } from './packs.mjs';
|
|
8
|
+
|
|
9
|
+
const SCRIPT = /\.claude\/hooks\/([\w-]+\.(?:mjs|py))\b/;
|
|
10
|
+
// Hooks a project carried by hand that the kit has taken over elsewhere: copy-tree-guard is merged into
|
|
11
|
+
// protect-vendor; shell-traps and gnu-shell are global by design (`claude-packs global add`); git-guard, build-guard,
|
|
12
|
+
// time-guard, user-asks and idle-wake were Python and are Node now. A project entry for one only runs it twice, or runs
|
|
13
|
+
// a file the project no longer has. Their entries go, and update says so.
|
|
14
|
+
// user-asks had a person's name until 0.25.0. A project's settings and `enable` list may still say it.
|
|
15
|
+
export const OLD_ASKS = 'tims-asks.mjs';
|
|
16
|
+
export const FORERUNNERS = ['git-guard.py', 'build-guard.py', 'time-guard.py', OLD_ASKS.replace('.mjs', '.py'), 'idle-wake.py'];
|
|
17
|
+
export const RETIRED = ['copy-tree-guard.py', 'shell-traps.py', 'gnu-shell.py', 'shell-traps.mjs', 'gnu-shell.mjs', OLD_ASKS, ...FORERUNNERS];
|
|
18
|
+
|
|
19
|
+
// One node process per settings entry, not one per hook (KWB-24): a group's kit node hooks become one run-hooks.mjs command
|
|
20
|
+
// that names them (`node ".../run-hooks.mjs" .claude/hooks/a.mjs .claude/hooks/b.mjs`), in their order, at the first one's
|
|
21
|
+
// place, its timeout their sum (60 s at most). An async hook (idle-wake waits for minutes) keeps its own entry, as does
|
|
22
|
+
// every hook that is not the kit's node hook (browser-guard.py, the project's own).
|
|
23
|
+
export const RUNNER = 'run-hooks.mjs';
|
|
24
|
+
const NODE_HOOK = /^node "\$CLAUDE_PROJECT_DIR\/\.claude\/hooks\/([\w-]+\.mjs)"$/;
|
|
25
|
+
// Only a hook written for the runner (it exports hook(text)) is folded in; any other node hook keeps its own entry: the kit
|
|
26
|
+
// repo's own kit-lock was folded in, failed there as exit 1 and never refused (KWB-26).
|
|
27
|
+
const HOOK_EXPORT = /export\s+(?:const|let|function|async\s+function)\s+hook\b|export\s*\{[^}]*\bhook\b[^}]*\}/;
|
|
28
|
+
export const runnable = (claudeDir) => (name) => { try { return HOOK_EXPORT.test(readFileSync(join(claudeDir, 'hooks', name), 'utf8')); } catch { return false; } };
|
|
29
|
+
export function throughRunner(hooks, canRun = () => true) {
|
|
30
|
+
const runs = (h) => !h.async && !h.asyncRewake && NODE_HOOK.test(h.command ?? '') && h.command.match(NODE_HOOK)[1] !== RUNNER && canRun(h.command.match(NODE_HOOK)[1]);
|
|
31
|
+
const kit = hooks.filter(runs);
|
|
32
|
+
if (!kit.length) return hooks;
|
|
33
|
+
const at = hooks.indexOf(kit[0]);
|
|
34
|
+
const runner = {
|
|
35
|
+
type: 'command',
|
|
36
|
+
command: `node "$CLAUDE_PROJECT_DIR/.claude/hooks/${RUNNER}" ${kit.map((h) => `.claude/hooks/${h.command.match(NODE_HOOK)[1]}`).join(' ')}`,
|
|
37
|
+
timeout: Math.min(60, kit.reduce((a, h) => a + (h.timeout ?? 10), 0)),
|
|
38
|
+
};
|
|
39
|
+
const rest = hooks.filter((h) => !runs(h));
|
|
40
|
+
rest.splice(Math.min(at, rest.length), 0, runner);
|
|
41
|
+
return rest;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A runner command back into one entry per hook it names, so the project's own hooks in it survive the rebuild below (the
|
|
45
|
+
// runner itself is the kit's, so its whole command was dropped, and the project's kit-lock with it: KWB-26).
|
|
46
|
+
const RUNS = new RegExp(`^node "\\$CLAUDE_PROJECT_DIR/\\.claude/hooks/${RUNNER.replace('.', '\\.')}"((?:\\s+\\S+)+)$`);
|
|
47
|
+
export function unfold(hooks) {
|
|
48
|
+
return hooks.flatMap((h) => {
|
|
49
|
+
const m = (h.command ?? '').match(RUNS);
|
|
50
|
+
if (!m) return [h];
|
|
51
|
+
return m[1].trim().split(/\s+/).map((p) => ({ type: 'command', command: `node "$CLAUDE_PROJECT_DIR/.claude/hooks/${p.split('/').pop()}"`, timeout: 15 }));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function readSettings(claudeDir) {
|
|
56
|
+
const file = join(claudeDir, 'settings.json');
|
|
57
|
+
if (!existsSync(file)) return {};
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
throw new Error(`.claude/settings.json is not valid JSON (${e.message}); fix it first, nothing was changed`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A configuration's "hookOrder" ({ "<event> <matcher>": [script, ...] }; no matcher: "<event>") puts the kit hooks of an entry
|
|
66
|
+
// in that order, in the places kit hooks had there; the project's own hooks keep theirs; a script it does not list keeps its
|
|
67
|
+
// pack order after the listed ones (0.10.0: moving a hook into another pack does not reorder a project's settings).
|
|
68
|
+
export function orderHooks(hooks, order, isOurs) {
|
|
69
|
+
if (!order?.length) return hooks;
|
|
70
|
+
const at = hooks.map((h, i) => (isOurs(h.command) ? i : -1)).filter((i) => i >= 0);
|
|
71
|
+
const rank = (h) => { const i = order.indexOf(h.command.match(SCRIPT)?.[1]); return i < 0 ? order.length : i; };
|
|
72
|
+
const sorted = at.map((i) => hooks[i]).map((h, n) => ({ h, n })).sort((a, b) => rank(a.h) - rank(b.h) || a.n - b.n).map((x) => x.h);
|
|
73
|
+
const out = [...hooks];
|
|
74
|
+
at.forEach((i, n) => { out[i] = sorted[n]; });
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// `previous`: the paths the last install managed (its lock), so a hook a pack dropped is removed too.
|
|
79
|
+
export function syncSettings(claudeDir, packNames, layout = {}, previous = [], hookOrder = {}) {
|
|
80
|
+
const file = join(claudeDir, 'settings.json');
|
|
81
|
+
const settings = readSettings(claudeDir);
|
|
82
|
+
settings.hooks ??= {};
|
|
83
|
+
const ours = new Set([...kitHookScripts(), ...RETIRED]);
|
|
84
|
+
for (const rel of previous) {
|
|
85
|
+
const m = rel.replace(/\\/g, '/').match(/^\.claude\/hooks\/([\w-]+\.(?:mjs|py))$/);
|
|
86
|
+
if (m) ours.add(m[1]);
|
|
87
|
+
}
|
|
88
|
+
const isOurs = (command = '') => ours.has(command.match(SCRIPT)?.[1]);
|
|
89
|
+
|
|
90
|
+
for (const groups of Object.values(settings.hooks)) for (const g of groups) g.hooks = unfold(g.hooks ?? []);
|
|
91
|
+
const retired = new Set();
|
|
92
|
+
for (const groups of Object.values(settings.hooks)) for (const g of groups) for (const h of g.hooks ?? []) {
|
|
93
|
+
const s = h.command?.match(SCRIPT)?.[1];
|
|
94
|
+
if (RETIRED.includes(s)) retired.add(s);
|
|
95
|
+
}
|
|
96
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
97
|
+
settings.hooks[event] = settings.hooks[event]
|
|
98
|
+
.map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !isOurs(h.command)) }))
|
|
99
|
+
.filter((g) => g.hooks.length > 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const name of packNames) {
|
|
103
|
+
for (const [event, groups] of Object.entries(readPack(name).hooks ?? {})) {
|
|
104
|
+
settings.hooks[event] ??= [];
|
|
105
|
+
for (const group of groups) {
|
|
106
|
+
let target = settings.hooks[event].find((g) => g.matcher === group.matcher);
|
|
107
|
+
if (!target) settings.hooks[event].push((target = { matcher: group.matcher, hooks: [] }));
|
|
108
|
+
for (const h of group.hooks) {
|
|
109
|
+
const script = h.command.match(SCRIPT)?.[1];
|
|
110
|
+
if (script && isDisabled(layout, `hooks/${script}`)) continue;
|
|
111
|
+
if (!target.hooks.some((x) => x.command === h.command)) target.hooks.push(h);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const [event, groups] of Object.entries(settings.hooks)) for (const g of groups) g.hooks = orderHooks(g.hooks, hookOrder?.[g.matcher === undefined ? event : `${event} ${g.matcher}`], isOurs);
|
|
117
|
+
if (!isDisabled(layout, `hooks/${RUNNER}`)) for (const groups of Object.values(settings.hooks)) for (const g of groups) g.hooks = throughRunner(g.hooks, runnable(claudeDir));
|
|
118
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
119
|
+
settings.hooks[event] = settings.hooks[event].filter((g) => g.hooks.length > 0);
|
|
120
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
121
|
+
}
|
|
122
|
+
// the events in the order the configuration's hookOrder names them (a fresh install of 0.10.0 writes them as 0.9.x did)
|
|
123
|
+
const events = [...new Set(Object.keys(hookOrder ?? {}).map((k) => k.split(' ')[0]))];
|
|
124
|
+
if (events.length) {
|
|
125
|
+
const rank = (e) => (events.includes(e) ? events.indexOf(e) : events.length);
|
|
126
|
+
settings.hooks = Object.fromEntries(Object.entries(settings.hooks).map((x, n) => [x, n]).sort(([[a], i], [[b], j]) => rank(a) - rank(b) || i - j).map(([x]) => x));
|
|
127
|
+
}
|
|
128
|
+
// layout.siblings: the folders Claude may read and edit besides this project (e.g. "../" for
|
|
129
|
+
// every sibling project), so rolling a lesson out to other projects never stalls on access.
|
|
130
|
+
// Added only; entries the user put there themselves are never removed.
|
|
131
|
+
if (Array.isArray(layout.siblings) && layout.siblings.length) {
|
|
132
|
+
settings.permissions ??= {};
|
|
133
|
+
const dirs = (settings.permissions.additionalDirectories ??= []);
|
|
134
|
+
for (const d of layout.siblings) if (!dirs.includes(d)) dirs.push(d);
|
|
135
|
+
}
|
|
136
|
+
writeFileSync(file, JSON.stringify(settings, null, 2) + '\n');
|
|
137
|
+
return { retired: [...retired] };
|
|
138
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The kit's side of the shape law (0.9.0, KWB-1): the empty manifest `new` and `init` write, what doctor fails, and
|
|
2
|
+
// `claude-packs shape scan`: every instance literal already in a repo, by file and concept, with a count.
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, relative } from 'node:path';
|
|
6
|
+
import { matchesAny } from '../packs/base/hooks/lib/glob.mjs';
|
|
7
|
+
import { readPack } from './packs.mjs';
|
|
8
|
+
import { EMPTY_SHAPE, SHAPE_FILE, findInstanceLiterals, findSelectorDefs, findUnknownTypes, formatFinding, isExcluded, readShape, validateShape } from './shape.mjs';
|
|
9
|
+
|
|
10
|
+
export const isSynth = (packs) => packs.some((p) => { try { return readPack(p).synth === true; } catch { return false; } });
|
|
11
|
+
|
|
12
|
+
// Writes design/shape.json with no concepts when there is none; returns true when it wrote one.
|
|
13
|
+
export function ensureShape(dir) {
|
|
14
|
+
const file = join(dir, SHAPE_FILE);
|
|
15
|
+
if (existsSync(file)) return false;
|
|
16
|
+
mkdirSync(join(dir, 'design'), { recursive: true });
|
|
17
|
+
writeFileSync(file, `${JSON.stringify(EMPTY_SHAPE, null, 2)}\n`);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function shapeProblems(dir, packs) {
|
|
22
|
+
if (!existsSync(join(dir, SHAPE_FILE))) {
|
|
23
|
+
return isSynth(packs) ? [`no ${SHAPE_FILE}: a synth writes down, before it builds, whether each concept is ONE or MANY (its generator, selectors and declared constants); start from \`{"concepts": {}, "exclude": []}\` and add a row per concept`] : [];
|
|
24
|
+
}
|
|
25
|
+
return validateShape(readShape(dir));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SKIP = ['.git/**', '**/node_modules/**', '.claude/worktrees/**', '.claude/.claude-packs-backup/**', '.claude/claude-packs.lock.json', '.claude/usage.log', '.claude/hooks.log'];
|
|
29
|
+
|
|
30
|
+
function files(dir) {
|
|
31
|
+
try {
|
|
32
|
+
const out = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], { cwd: dir, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
33
|
+
return out.split('\0').filter(Boolean);
|
|
34
|
+
} catch {
|
|
35
|
+
const walk = (d) => readdirSync(join(dir, d)).flatMap((e) => {
|
|
36
|
+
const rel = d ? `${d}/${e}` : e;
|
|
37
|
+
if (matchesAny(rel, SKIP) || matchesAny(`${rel}/x`, SKIP)) return [];
|
|
38
|
+
return statSync(join(dir, rel)).isDirectory() ? walk(rel) : [rel];
|
|
39
|
+
});
|
|
40
|
+
return walk('');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Every finding in the repo, code, tests, docs and briefs alike; the files the kit installed are the kit's, not the repo's.
|
|
45
|
+
export function scanRepo(dir, shape = readShape(dir)) {
|
|
46
|
+
if (!shape) throw new Error(`no ${SHAPE_FILE} in ${dir}`);
|
|
47
|
+
if (shape.error) throw new Error(shape.error);
|
|
48
|
+
let kit = new Set();
|
|
49
|
+
try { kit = new Set(Object.keys(JSON.parse(readFileSync(join(dir, '.claude/claude-packs.lock.json'), 'utf8')).files)); } catch { /* not installed */ }
|
|
50
|
+
const findings = [];
|
|
51
|
+
for (const rel of files(dir)) {
|
|
52
|
+
if (kit.has(rel) || matchesAny(rel, SKIP) || isExcluded(rel, shape)) continue;
|
|
53
|
+
let text;
|
|
54
|
+
try { if (statSync(join(dir, rel)).size > 4 * 1024 * 1024) continue; text = readFileSync(join(dir, rel), 'utf8'); } catch { continue; }
|
|
55
|
+
if (text.includes('\0')) continue;
|
|
56
|
+
for (const f of [...findInstanceLiterals(text, rel, shape), ...findSelectorDefs(text, rel, shape), ...findUnknownTypes(text, rel, shape)]) findings.push({ ...f, file: rel });
|
|
57
|
+
}
|
|
58
|
+
return findings;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function shapeCommand(sub, dir, log = console.log) {
|
|
62
|
+
if (sub !== 'scan') throw new Error('use `npx --no-install @thedecipherist/claude-packs shape scan [--dir D]`');
|
|
63
|
+
const findings = scanRepo(dir);
|
|
64
|
+
const byConcept = new Map();
|
|
65
|
+
for (const f of findings) {
|
|
66
|
+
log(formatFinding(f, f.file).split('\n')[0] + (f.kind ? '' : ` [${f.why}]`));
|
|
67
|
+
const k = f.concept ?? 'new type (no row)';
|
|
68
|
+
const e = byConcept.get(k) ?? { n: 0, files: new Set() };
|
|
69
|
+
e.n += 1; e.files.add(f.file);
|
|
70
|
+
byConcept.set(k, e);
|
|
71
|
+
}
|
|
72
|
+
log('');
|
|
73
|
+
for (const [k, e] of byConcept) log(`${k}: ${e.n} finding(s) in ${e.files.size} file(s)`);
|
|
74
|
+
log(findings.length ? `${findings.length} finding(s) in ${relative(process.cwd(), dir) || '.'}: each is an example, a rule or a constant, or it is fixed` : 'no findings');
|
|
75
|
+
return findings.length ? 1 : 0;
|
|
76
|
+
}
|
package/lib/shape.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The shape law's one reader lives with the hooks (packs/base/hooks/lib/shape.mjs), because every project gets it
|
|
2
|
+
// installed beside them; the kit's own commands (doctor, `shape scan`, new) read it through this file.
|
|
3
|
+
export * from '../packs/base/hooks/lib/shape.mjs';
|
|
4
|
+
export { findSelectorDefs, findUnknownTypes } from '../packs/base/hooks/lib/shape-code.mjs';
|
package/lib/suggest.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// claude-packs suggest: which packs this project needs, read from its own files, so nobody picks them by hand. `init --auto` installs exactly this.
|
|
2
|
+
// Fast and read-only: it reads file names and package.json, nothing else. `scan` goes further and runs the checks on a copy.
|
|
3
|
+
import { filesOf } from './scan.mjs';
|
|
4
|
+
import { recommend } from './scan-detect.mjs';
|
|
5
|
+
|
|
6
|
+
export const suggestionOf = (dir) => recommend(dir, filesOf(dir));
|
|
7
|
+
|
|
8
|
+
export function suggest(dir, { log = console.log } = {}) {
|
|
9
|
+
const pick = suggestionOf(dir);
|
|
10
|
+
log(`configuration: ${pick.config} (${pick.why})`);
|
|
11
|
+
log(`always: ${pick.always.join(', ')}`);
|
|
12
|
+
if (pick.packs.length) log('suggested:');
|
|
13
|
+
for (const p of pick.packs) log(` ${p.pack.padEnd(14)} ${p.why}`);
|
|
14
|
+
if (pick.leftOut.length) log(`left out, nothing here needs them: ${pick.leftOut.join(', ')}`);
|
|
15
|
+
if (pick.fullStack) log('this looks like the full-stack configuration: `init --config full-stack` fixes its folder layout, so it is offered and never assumed');
|
|
16
|
+
log('\nInstall exactly this: npx --no-install @thedecipherist/claude-packs init --auto');
|
|
17
|
+
return pick;
|
|
18
|
+
}
|
package/lib/switches.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// What an install switches on. A hook or a lesson check is behaviour: it refuses work. So a new one never
|
|
2
|
+
// switches on in a project that did not choose it:
|
|
3
|
+
// - a pack the project is choosing now (init, add) brings all its hooks and checks, except its opt-in ones
|
|
4
|
+
// (pack.json "optIn", e.g. user-asks and idle-wake), which wait for the project's "enable" list;
|
|
5
|
+
// - a pack the project already had brings a NEW hook or check (one it never had: not in the lock, not on disk, not
|
|
6
|
+
// run by its settings.json) only when layout.json "enable" names it ("hooks/git-guard.mjs", "checks/x.json");
|
|
7
|
+
// - what the project already had stays on.
|
|
8
|
+
// `update` prints both lists before it changes anything.
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { join, relative } from 'node:path';
|
|
11
|
+
import { packRoot, planFiles, readPack } from './packs.mjs';
|
|
12
|
+
import { OLD_ASKS, readSettings } from './settings.mjs';
|
|
13
|
+
|
|
14
|
+
const SWITCH = /^(hooks\/[\w-]+\.(mjs|py)|checks\/[\w-]+\.json)$/;
|
|
15
|
+
const renamed = (d) => (d === `hooks/${OLD_ASKS}` ? 'hooks/user-asks.mjs' : d);
|
|
16
|
+
const listed = (list, packRel) => (list ?? []).map(renamed).some((d) => packRel === d || packRel.startsWith(`${d.replace(/\/$/, '')}/`));
|
|
17
|
+
|
|
18
|
+
export function switches(projectDir, packNames, layout, lock) {
|
|
19
|
+
const hooks = JSON.stringify(readSettings(join(projectDir, '.claude')).hooks ?? {});
|
|
20
|
+
const on = [];
|
|
21
|
+
const held = [];
|
|
22
|
+
for (const name of packNames) {
|
|
23
|
+
const optIn = readPack(name).optIn ?? [];
|
|
24
|
+
const onByDefault = readPack(name).onByDefault ?? []; // the law (pack.json "onByDefault"): on in every project, new or updated
|
|
25
|
+
for (const { src, rel } of planFiles(name, layout)) {
|
|
26
|
+
const packRel = relative(packRoot(name), src).replace(/\\/g, '/');
|
|
27
|
+
if (!SWITCH.test(packRel)) continue;
|
|
28
|
+
// A hook counts as had under either of its names: a Node hook's Python forerunner (git-guard.py) is the same hook.
|
|
29
|
+
const names = packRel.startsWith('hooks/') ? [packRel, packRel.replace(/\.(mjs|py)$/, '.py'), packRel.replace(/\.(mjs|py)$/, '.mjs')] : [packRel];
|
|
30
|
+
if (packRel === 'hooks/user-asks.mjs') names.push(`hooks/${OLD_ASKS}`, `hooks/${OLD_ASKS.replace('.mjs', '.py')}`);
|
|
31
|
+
const had = names.some((n) => lock.files[`.claude/${n}`] !== undefined || existsSync(join(projectDir, '.claude', n)) ||
|
|
32
|
+
(n.startsWith('hooks/') && hooks.includes(`.claude/${n}`))) || lock.files[rel] !== undefined;
|
|
33
|
+
if (had) continue;
|
|
34
|
+
const chosen = names.some((n) => listed(layout.enable, n)) || onByDefault.includes(packRel) || (!lock.packs.includes(name) && !optIn.includes(packRel));
|
|
35
|
+
(chosen ? on : held).push(packRel);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { on, held };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function reportSwitches({ on, held }, log) {
|
|
42
|
+
if (on.length) log(` switching on (new to this project): ${on.join(', ')}`);
|
|
43
|
+
if (held.length) log(` held back, not switched on (name them in "enable" in .claude/layout.json to switch them on): ${held.join(', ')}`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// doctor's part for the generated gate shape_symmetry: the installed tool proves itself (its selftest plants a missing member
|
|
2
|
+
// and a swap difference), and every many concept in design/shape.json without a symmetry block is named (its generated test
|
|
3
|
+
// is red until it has one).
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export function symmetryProblems(projectDir, log = console.log) {
|
|
9
|
+
const problems = [];
|
|
10
|
+
const tool = join(projectDir, '.claude', 'tools', 'shape-symmetry.mjs');
|
|
11
|
+
if (existsSync(tool)) {
|
|
12
|
+
try {
|
|
13
|
+
execFileSync('node', [tool, '--selftest'], { env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }, stdio: 'pipe' });
|
|
14
|
+
} catch (e) {
|
|
15
|
+
problems.push(`shape-symmetry.mjs fails its selftest:\n${String(e.stdout)}${String(e.stderr)}`.trim());
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const file = join(projectDir, 'design', 'shape.json');
|
|
19
|
+
if (existsSync(file)) {
|
|
20
|
+
let shape;
|
|
21
|
+
try { shape = JSON.parse(readFileSync(file, 'utf8')); } catch (e) { return [...problems, `design/shape.json is not JSON: ${e.message}`]; }
|
|
22
|
+
const bare = Object.entries(shape.concepts ?? {}).filter(([, c]) => c.shape === 'many' && !(c.symmetry?.members && c.symmetry?.swap)).map(([n]) => n);
|
|
23
|
+
if (bare.length) log(` note: many concept(s) with no symmetry commands, so shape_symmetry is red for them: ${bare.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
return problems;
|
|
26
|
+
}
|