@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,94 @@
|
|
|
1
|
+
// Minimal C++ scanner: blanks comments and string/char literals (keeping offsets and
|
|
2
|
+
// newlines intact), then finds audio-thread function definitions and their bodies.
|
|
3
|
+
|
|
4
|
+
export const AUDIO_FUNCTIONS = new Set([
|
|
5
|
+
'processBlock',
|
|
6
|
+
'processBlockBypassed',
|
|
7
|
+
'renderNextBlock',
|
|
8
|
+
'processSample',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const NOT_FUNCTIONS = new Set(['if', 'for', 'while', 'switch', 'catch', 'return', 'sizeof', 'alignof', 'decltype']);
|
|
12
|
+
|
|
13
|
+
// Text allowed between a definition's ")" and its "{": qualifiers, trailing return type, attributes.
|
|
14
|
+
// Parentheses are not allowed (except `noexcept(...)`, stripped first), so a call inside
|
|
15
|
+
// `if (x.processSample (s)) {` is never mistaken for a definition.
|
|
16
|
+
const TRAILER = /^[\s\w:<>,&*\[\]\-]*$/;
|
|
17
|
+
const NOEXCEPT_ARG = /noexcept\s*\([^()]*\)/g;
|
|
18
|
+
|
|
19
|
+
// keepStrings: blank comments only, so a check can read string literals in code but not prose
|
|
20
|
+
export function blankCode(src, keepStrings = false) {
|
|
21
|
+
let out = '';
|
|
22
|
+
let i = 0;
|
|
23
|
+
const blank = (s) => s.replace(/[^\n]/g, ' ');
|
|
24
|
+
while (i < src.length) {
|
|
25
|
+
const two = src.slice(i, i + 2);
|
|
26
|
+
if (two === '//') {
|
|
27
|
+
const end = src.indexOf('\n', i);
|
|
28
|
+
const stop = end === -1 ? src.length : end;
|
|
29
|
+
out += blank(src.slice(i, stop));
|
|
30
|
+
i = stop;
|
|
31
|
+
} else if (two === '/*') {
|
|
32
|
+
const end = src.indexOf('*/', i + 2);
|
|
33
|
+
const stop = end === -1 ? src.length : end + 2;
|
|
34
|
+
out += blank(src.slice(i, stop));
|
|
35
|
+
i = stop;
|
|
36
|
+
} else if (src[i] === '"' || src[i] === "'" || src[i] === '`') {
|
|
37
|
+
// A string is walked either way, so a // inside one is no comment. A template string can span lines and stays as written.
|
|
38
|
+
const q = src[i];
|
|
39
|
+
let j = i + 1;
|
|
40
|
+
while (j < src.length && src[j] !== q && (src[j] !== '\n' || q === '`')) j += src[j] === '\\' ? 2 : 1;
|
|
41
|
+
const inner = src.slice(i + 1, j);
|
|
42
|
+
out += q + (keepStrings || q === '`' ? inner : blank(inner)) + (j < src.length ? src[j] : '');
|
|
43
|
+
i = j + 1;
|
|
44
|
+
} else {
|
|
45
|
+
out += src[i];
|
|
46
|
+
i += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function matchClose(code, openIdx, open, close) {
|
|
53
|
+
let depth = 0;
|
|
54
|
+
for (let i = openIdx; i < code.length; i += 1) {
|
|
55
|
+
if (code[i] === open) depth += 1;
|
|
56
|
+
else if (code[i] === close) {
|
|
57
|
+
depth -= 1;
|
|
58
|
+
if (depth === 0) return i;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return -1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Returns [{ name, start, end }] where start..end is the body including braces.
|
|
65
|
+
export function findAudioBodies(code) {
|
|
66
|
+
const bodies = [];
|
|
67
|
+
const nameRe = /\b([A-Za-z_]\w*)\s*\(/g;
|
|
68
|
+
let m;
|
|
69
|
+
while ((m = nameRe.exec(code)) !== null) {
|
|
70
|
+
const name = m[1];
|
|
71
|
+
if (NOT_FUNCTIONS.has(name)) continue;
|
|
72
|
+
const parenOpen = m.index + m[0].length - 1;
|
|
73
|
+
const parenClose = matchClose(code, parenOpen, '(', ')');
|
|
74
|
+
if (parenClose === -1) continue;
|
|
75
|
+
const brace = code.indexOf('{', parenClose);
|
|
76
|
+
const semi = code.indexOf(';', parenClose);
|
|
77
|
+
if (brace === -1 || (semi !== -1 && semi < brace)) continue;
|
|
78
|
+
const trailer = code.slice(parenClose + 1, brace).replace(NOEXCEPT_ARG, 'noexcept');
|
|
79
|
+
if (!TRAILER.test(trailer)) continue;
|
|
80
|
+
const isAudio = AUDIO_FUNCTIONS.has(name) || /\bnonblocking\b/.test(trailer);
|
|
81
|
+
if (!isAudio) continue;
|
|
82
|
+
const end = matchClose(code, brace, '{', '}');
|
|
83
|
+
if (end === -1) continue;
|
|
84
|
+
bodies.push({ name, start: brace, end });
|
|
85
|
+
nameRe.lastIndex = end;
|
|
86
|
+
}
|
|
87
|
+
return bodies;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function lineAt(src, index) {
|
|
91
|
+
let line = 1;
|
|
92
|
+
for (let i = 0; i < index; i += 1) if (src[i] === '\n') line += 1;
|
|
93
|
+
return line;
|
|
94
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// dup-index: once-only's detector at write time (KWB-24). jscpd read the whole repo on every source write; this keeps an
|
|
2
|
+
// index instead, built once and updated for the files that changed since, so a write costs one walk of the folders (a stat
|
|
3
|
+
// of each source file, no reading) and a lookup, never a rescan.
|
|
4
|
+
// The index: per source file (lib/dup.mjs: SOURCE, its excludes, the same files jscpd judged), the hash of every window of
|
|
5
|
+
// WINDOW tokens (comments and blank space left out, as jscpd's mild mode) and the line each token is on; kept in
|
|
6
|
+
// $TMPDIR/claude-packs-dup-index/<checkout>.json, keyed by modification time and size. A file that changed is read again;
|
|
7
|
+
// one that is gone leaves.
|
|
8
|
+
// A candidate clone: the windows of the new text that are also in one other file, chained in order along one diagonal (a
|
|
9
|
+
// line changed inside a copy moves it by a few tokens; a gap of up to LIMITS.gap lines), covering LOOSE.tokens tokens over
|
|
10
|
+
// LOOSE.lines lines on either side, a little under jscpd's LIMITS (lib/dup.mjs) so the candidates hold every clone jscpd
|
|
11
|
+
// finds (measured on a C++ project's 752 sources: all 55 files jscpd flags are among them). A window found in more than COMMON
|
|
12
|
+
// places is boilerplate and never starts a match.
|
|
13
|
+
// loadIndex(root, { layout, shape }) -> { root, files: { rel: { h, l } }, built, read } (read: files tokenized this call)
|
|
14
|
+
// clonesIn(index, writes, { ignore }) -> [{ at, other, lines, tokens }] writes: [{ path, after, range | ranges }] (the changed
|
|
15
|
+
// lines, [lo, hi] each; one write per path), sides { file, start, end }
|
|
16
|
+
// judgeWrites(root, writes, { layout, shape, ignore }) -> { clones, error, index }: the index finds the candidates (it is
|
|
17
|
+
// looser than jscpd: a superset, measured on a C++ project's tree), then jscpd confirms on those files alone, so a write is
|
|
18
|
+
// refused when jscpd over the repo would refuse it, and a write with no candidate never starts jscpd.
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { basename, join, relative } from 'node:path';
|
|
23
|
+
import { clonesOf, excludeGlobs, LIMITS, real, scan, SOURCE } from './dup.mjs';
|
|
24
|
+
import { matchesAny } from './glob.mjs';
|
|
25
|
+
|
|
26
|
+
export const WINDOW = 20;
|
|
27
|
+
const COMMON = 40;
|
|
28
|
+
// A candidate is looser than jscpd's clone (it counts tokens and lines its own way: a block jscpd reads as 7 lines can be 5
|
|
29
|
+
// here), so nothing jscpd would refuse is missed; jscpd then judges the candidates (judgeWrites).
|
|
30
|
+
const LOOSE = { tokens: Math.round(LIMITS.tokens * 0.8), lines: LIMITS.lines - 2 };
|
|
31
|
+
const MAX_BYTES = 512 * 1024; // a table or a blob, not code anyone copies by hand
|
|
32
|
+
const VERSION = 1;
|
|
33
|
+
|
|
34
|
+
const C_LIKE = /\/\/[^\n]*|\/\*[\s\S]*?\*\/|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`(?:\\.|[^`\\])*`|[A-Za-z_$][\w$]*|\d[\w.]*|\S/g;
|
|
35
|
+
const PY = /#[^\n]*|"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|[A-Za-z_]\w*|\d[\w.]*|\S/g;
|
|
36
|
+
|
|
37
|
+
// -> { t: [token], l: [line of each token] }
|
|
38
|
+
export function tokenize(text, path) {
|
|
39
|
+
const re = new RegExp((/\.py$/i.test(path) ? PY : C_LIKE).source, 'g');
|
|
40
|
+
const t = [];
|
|
41
|
+
const l = [];
|
|
42
|
+
let line = 1;
|
|
43
|
+
let at = 0;
|
|
44
|
+
for (const m of text.matchAll(re)) {
|
|
45
|
+
for (let i = at; i < m.index; i += 1) if (text.charCodeAt(i) === 10) line += 1;
|
|
46
|
+
const s = m[0];
|
|
47
|
+
const comment = s.startsWith('//') || s.startsWith('/*') || (s[0] === '#' && /\.py$/i.test(path));
|
|
48
|
+
if (!comment) { t.push(s); l.push(line); }
|
|
49
|
+
for (let i = 0; i < s.length; i += 1) if (s.charCodeAt(i) === 10) line += 1;
|
|
50
|
+
at = m.index + s.length;
|
|
51
|
+
}
|
|
52
|
+
return { t, l };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// FNV-1a over a window's tokens, one number per window start.
|
|
56
|
+
export function windows(t) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (let i = 0; i + WINDOW <= t.length; i += 1) {
|
|
59
|
+
let h = 0x811c9dc5;
|
|
60
|
+
for (let k = i; k < i + WINDOW; k += 1) {
|
|
61
|
+
const s = t[k];
|
|
62
|
+
for (let c = 0; c < s.length; c += 1) { h ^= s.charCodeAt(c); h = Math.imul(h, 0x01000193); }
|
|
63
|
+
h ^= 0x1f; h = Math.imul(h, 0x01000193);
|
|
64
|
+
}
|
|
65
|
+
out.push(h >>> 0);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fileOf = (root) => join(process.env.TMPDIR ?? tmpdir(), 'claude-packs-dup-index', `${createHash('sha1').update(real(root)).digest('hex').slice(0, 16)}.json`);
|
|
71
|
+
|
|
72
|
+
// Every source file under root that the index judges: { rel: { m, s } }. Folders an exclude covers are never entered.
|
|
73
|
+
function walk(root, ex) {
|
|
74
|
+
const out = {};
|
|
75
|
+
const go = (dir, rel) => {
|
|
76
|
+
let entries;
|
|
77
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
78
|
+
for (const e of entries) {
|
|
79
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
80
|
+
if (e.isDirectory()) { if (!matchesAny(`${r}/x`, ex) && !matchesAny(`${r}/x/x`, ex)) go(join(dir, e.name), r); continue; }
|
|
81
|
+
if (!e.isFile() || !SOURCE.test(e.name) || matchesAny(r, ex)) continue;
|
|
82
|
+
try { const st = statSync(join(dir, e.name)); if (st.size <= MAX_BYTES) out[r] = { m: st.mtimeMs, s: st.size }; } catch { /* gone */ }
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
go(root, '');
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function loadIndex(root, { layout = {}, shape = null } = {}) {
|
|
90
|
+
const file = fileOf(root);
|
|
91
|
+
let old = null;
|
|
92
|
+
try { old = JSON.parse(readFileSync(file, 'utf8')); } catch { /* built below */ }
|
|
93
|
+
if (old?.v !== VERSION || old.root !== real(root)) old = null;
|
|
94
|
+
const ex = excludeGlobs(layout, shape);
|
|
95
|
+
const now = walk(root, ex);
|
|
96
|
+
const files = {};
|
|
97
|
+
let read = 0;
|
|
98
|
+
for (const [rel, st] of Object.entries(now)) {
|
|
99
|
+
const was = old?.files[rel];
|
|
100
|
+
if (was && was.m === st.m && was.s === st.s) { files[rel] = was; continue; }
|
|
101
|
+
let text = '';
|
|
102
|
+
try { text = readFileSync(join(root, rel), 'utf8'); } catch { continue; }
|
|
103
|
+
const { t, l } = tokenize(text, rel);
|
|
104
|
+
files[rel] = { m: st.m, s: st.s, h: windows(t), l };
|
|
105
|
+
read += 1;
|
|
106
|
+
}
|
|
107
|
+
const changed = read > 0 || !old || Object.keys(old.files).length !== Object.keys(files).length;
|
|
108
|
+
if (changed) {
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(join(file, '..'), { recursive: true });
|
|
111
|
+
const tmp = `${file}.${process.pid}`;
|
|
112
|
+
writeFileSync(tmp, JSON.stringify({ v: VERSION, root: real(root), files }));
|
|
113
|
+
renameSync(tmp, file);
|
|
114
|
+
} catch { /* judged all the same */ }
|
|
115
|
+
}
|
|
116
|
+
return { root: real(root), files, built: !old, read };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The clones the writes add: each write's windows on its changed lines, against every other file (the index, minus the
|
|
120
|
+
// written files and `ignore`; each other write of the call as it will be; the write itself elsewhere in its own text).
|
|
121
|
+
export function clonesIn(index, writes, { ignore = [] } = {}) {
|
|
122
|
+
const skip = new Set([...ignore, ...writes.map((w) => w.path)].map((p) => relative(index.root, real(p))));
|
|
123
|
+
const texts = writes.map((w) => { const { t, l } = tokenize(w.after, w.path); return { w, l, h: windows(t), n: t.length }; });
|
|
124
|
+
const found = [];
|
|
125
|
+
for (const q of texts) {
|
|
126
|
+
const ranges = q.w.ranges ?? [q.w.range];
|
|
127
|
+
const want = new Map();
|
|
128
|
+
for (let i = 0; i < q.h.length; i += 1) {
|
|
129
|
+
if (!ranges.some(([lo, hi]) => q.l[i] <= hi && q.l[i + WINDOW - 1] >= lo)) continue;
|
|
130
|
+
if (!want.has(q.h[i])) want.set(q.h[i], []);
|
|
131
|
+
want.get(q.h[i]).push(i);
|
|
132
|
+
}
|
|
133
|
+
if (!want.size) continue;
|
|
134
|
+
const others = [];
|
|
135
|
+
for (const [rel, f] of Object.entries(index.files)) if (!skip.has(rel)) others.push({ file: join(index.root, rel), h: f.h, l: f.l });
|
|
136
|
+
for (const o of texts) others.push({ file: real(o.w.path), h: o.h, l: o.l, self: o === q });
|
|
137
|
+
const pairs = new Map(); // other -> [[i, j]]
|
|
138
|
+
const seen = new Map();
|
|
139
|
+
for (const o of others) {
|
|
140
|
+
for (let j = 0; j < o.h.length; j += 1) {
|
|
141
|
+
const is = want.get(o.h[j]);
|
|
142
|
+
if (!is) continue;
|
|
143
|
+
seen.set(o.h[j], (seen.get(o.h[j]) ?? 0) + 1);
|
|
144
|
+
for (const i of is) if (!o.self || Math.abs(i - j) >= WINDOW) { if (!pairs.has(o)) pairs.set(o, []); pairs.get(o).push([i, j]); }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const [o, ps] of pairs) {
|
|
148
|
+
const real_ = ps.filter(([i]) => (seen.get(q.h[i]) ?? 0) <= COMMON).sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
149
|
+
for (const c of chains(real_, q.l)) {
|
|
150
|
+
// either side's lines (jscpd counts a block written on 3 long lines that repeats one of 7 lines)
|
|
151
|
+
const lines = Math.max(q.l[c.end - 1] - q.l[c.start] + 1, o.l[c.jhi - 1] - o.l[c.jlo] + 1);
|
|
152
|
+
if (c.covered < LOOSE.tokens || lines < LOOSE.lines) continue;
|
|
153
|
+
found.push({ at: { file: real(q.w.path), start: q.l[c.start], end: q.l[c.end - 1] }, other: { file: o.file, start: o.l[c.jlo], end: o.l[c.jhi - 1] }, lines, tokens: c.covered });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return found;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Matched window pairs [i (new), j (other)], sorted by i, chained along a diagonal: a change of a few tokens inside a copy
|
|
161
|
+
// keeps it one chain; a gap of more than LIMITS.gap lines in the new text ends it.
|
|
162
|
+
function chains(pairs, lines) {
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const [i, j] of pairs) {
|
|
165
|
+
const d = j - i;
|
|
166
|
+
const c = out.find((x) => Math.abs(x.d - d) <= 12 && i >= x.lastI && lines[i] <= lines[x.end - 1] + LIMITS.gap + 1);
|
|
167
|
+
if (!c) { out.push({ start: i, end: i + WINDOW, covered: WINDOW, d, lastI: i, jlo: j, jhi: j + WINDOW }); continue; }
|
|
168
|
+
if (i === c.lastI && d === c.d) continue;
|
|
169
|
+
c.covered += Math.max(0, i + WINDOW - Math.max(i, c.end));
|
|
170
|
+
c.end = Math.max(c.end, i + WINDOW);
|
|
171
|
+
c.d = d;
|
|
172
|
+
c.lastI = i;
|
|
173
|
+
c.jlo = Math.min(c.jlo, j);
|
|
174
|
+
c.jhi = Math.max(c.jhi, j + WINDOW);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function judgeWrites(root, writes, { layout = {}, shape = null, ignore = [] } = {}) {
|
|
180
|
+
const index = loadIndex(root, { layout, shape });
|
|
181
|
+
const maybe = clonesIn(index, writes, { ignore });
|
|
182
|
+
if (!maybe.length) return { clones: [], index };
|
|
183
|
+
const tmp = mkdtempSync(join(process.env.TMPDIR ?? tmpdir(), 'once-only-new-'));
|
|
184
|
+
try {
|
|
185
|
+
const extra = writes.map((w, i) => {
|
|
186
|
+
const path = join(tmp, String(i), basename(w.path));
|
|
187
|
+
mkdirSync(join(tmp, String(i)), { recursive: true });
|
|
188
|
+
writeFileSync(path, w.after);
|
|
189
|
+
return { path, as: w.path };
|
|
190
|
+
});
|
|
191
|
+
const written = new Set(writes.map((w) => real(w.path)));
|
|
192
|
+
const paths = [...new Set(maybe.map((c) => c.other.file))].filter((f) => !written.has(real(f)));
|
|
193
|
+
const r = scan(root, { extra, ignore, layout, shape, paths });
|
|
194
|
+
if (r.error) return { clones: [], error: r.error, index };
|
|
195
|
+
return { clones: writes.flatMap((w) => (w.ranges ?? [w.range]).flatMap((range) => clonesOf(r.clones, w.path, range))), index };
|
|
196
|
+
} finally {
|
|
197
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// dup: the one copy-paste reader (once-only at write time, making-check at the end of a turn, doctor's count and
|
|
2
|
+
// `claude-packs dup scan`). Code that exists once is called, never copied: a near-duplicate of a block already in the repo
|
|
3
|
+
// is refused with where the first one lives (0.9.0 item 9).
|
|
4
|
+
//
|
|
5
|
+
// The detector is jscpd (5.4.2, an optional dependency of the kit: a Rust binary). Measured on a C++ repo of 580 C++,
|
|
6
|
+
// Python and JS sources: 0.3 s for the whole tree, so it runs at write time; a planted copy of a function with one line
|
|
7
|
+
// changed is found as one near-miss clone (--max-gap-lines). Threshold: 50 tokens and 6 lines, a gap of up to 2 lines.
|
|
8
|
+
// findJscpd(starts) -> path | null CLAUDE_PACKS_JSCPD, then node_modules/.bin/jscpd above each start, then PATH
|
|
9
|
+
// scan(root, { extra, layout, shape, paths }) -> { clones, ms, error } paths: only these files (once-only's confirmation), else the tree
|
|
10
|
+
// extra: [{ path, as }] files outside the tree judged as if they were `as` (the text a write would leave), `as`
|
|
11
|
+
// itself left out of the scan; ignore: more paths left out (a file a move takes away). Clone sides are
|
|
12
|
+
// { file, start, end }, `file` the real path in the repo (`as` for extra).
|
|
13
|
+
// changedLines(before, after) -> [lo, hi] | null the lines of after that differ (1-based), null when none do
|
|
14
|
+
// isSource(rel), isGenerated(text), excludeGlobs(layout, shape)
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { matchesAny } from './glob.mjs';
|
|
21
|
+
|
|
22
|
+
export const LIMITS = { tokens: 50, lines: 6, gap: 2 };
|
|
23
|
+
export const SOURCE = /\.(c|cc|cpp|cxx|h|hh|hpp|hxx|inl|mm|m|py|js|mjs|cjs|jsx|ts|tsx)$/i;
|
|
24
|
+
const FORMATS = 'c,c-header,cpp,cpp-header,objectivec,python,javascript,jsx,typescript,tsx';
|
|
25
|
+
// A header and a source are one language: a function copied from a .cpp into a .h is a copy (jscpd compares within a format otherwise).
|
|
26
|
+
const CROSS = 'c,c-header,cpp,cpp-header,objectivec;javascript,jsx,typescript,tsx';
|
|
27
|
+
// Generated files, resources and vendored or built code are never judged (and never a match).
|
|
28
|
+
export const DEFAULT_EXCLUDE = [
|
|
29
|
+
'.git/**', '.claude/**', 'node_modules/**', '**/node_modules/**', 'third_party/**', '**/third_party/**', 'JUCE/**', '**/JUCE/**',
|
|
30
|
+
'out/**', 'build/**', '**/resources/**', '**/Resources/**', '**/generated/**', '**/*.generated.*', '**/*_generated.*', '**/BinaryData.*',
|
|
31
|
+
];
|
|
32
|
+
export const FIX = 'call it, or extract one function that both call, parameterized over what differs';
|
|
33
|
+
|
|
34
|
+
export const isSource = (rel) => SOURCE.test(rel);
|
|
35
|
+
export const isGenerated = (text) => /\b(?:auto-?generated|generated by|do not edit|DO NOT EDIT|@generated)\b/i.test((text ?? '').split('\n').slice(0, 6).join('\n'));
|
|
36
|
+
|
|
37
|
+
export function excludeGlobs(layout = {}, shape = null) {
|
|
38
|
+
const out = [...DEFAULT_EXCLUDE];
|
|
39
|
+
const build = layout.buildOutput;
|
|
40
|
+
for (const g of Array.isArray(build) ? build : build ? [build] : []) out.push(g);
|
|
41
|
+
for (const g of shape?.exclude ?? []) out.push(g);
|
|
42
|
+
out.push('design/shape.json');
|
|
43
|
+
return [...new Set(out)].filter((g) => !g.includes(',')); // jscpd takes one comma-separated list
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The folders of kind "copy" (another project's code, copied unchanged): a match there is real, but a clone with both
|
|
47
|
+
// sides inside copies is the other project's, never counted here.
|
|
48
|
+
export const copyGlobs = (layout = {}) => Object.values(layout.folders ?? {}).filter((f) => f.kind === 'copy').map((f) => `${f.path.replace(/\/+$/, '')}/**`);
|
|
49
|
+
|
|
50
|
+
export function findJscpd(starts = []) {
|
|
51
|
+
const env = process.env.CLAUDE_PACKS_JSCPD;
|
|
52
|
+
if (env && existsSync(env)) return env;
|
|
53
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
for (const s of [...starts, here]) {
|
|
55
|
+
let d = resolve(s);
|
|
56
|
+
for (;;) {
|
|
57
|
+
for (const c of ['node_modules/.bin/jscpd', 'node_modules/@thedecipherist/claude-packs/node_modules/.bin/jscpd']) if (existsSync(join(d, c))) return join(d, c);
|
|
58
|
+
const up = dirname(d);
|
|
59
|
+
if (up === d) break;
|
|
60
|
+
d = up;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const p of (process.env.PATH ?? '').split(delimiter)) if (p && existsSync(join(p, 'jscpd'))) return join(p, 'jscpd');
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function changedLines(before, after) {
|
|
68
|
+
if (after === null || after === undefined || before === after) return null;
|
|
69
|
+
const a = (before ?? '').split('\n');
|
|
70
|
+
const b = after.split('\n');
|
|
71
|
+
let top = 0;
|
|
72
|
+
while (top < a.length && top < b.length && a[top] === b[top]) top += 1;
|
|
73
|
+
let tail = 0;
|
|
74
|
+
while (tail < a.length - top && tail < b.length - top && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail += 1;
|
|
75
|
+
const lo = top + 1;
|
|
76
|
+
const hi = b.length - tail;
|
|
77
|
+
return hi >= lo ? [lo, hi] : null; // a pure deletion adds no line
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Real paths: /var and /private/var are one folder on a Mac, and jscpd prints the real one.
|
|
81
|
+
export const real = (p) => { try { return realpathSync(p); } catch { const d = dirname(resolve(p)); return d === resolve(p) ? d : join(real(d), resolve(p).slice(d.length + 1)); } };
|
|
82
|
+
|
|
83
|
+
export function scan(root, { extra = [], ignore: alsoIgnore = [], layout = {}, shape = null, jscpd = null, timeoutMs = 8000, paths = null } = {}) {
|
|
84
|
+
const bin = jscpd ?? findJscpd([root]);
|
|
85
|
+
if (!bin) return { clones: [], error: 'jscpd not found (it is an optional dependency of @thedecipherist/claude-packs: npm install in the project, or set CLAUDE_PACKS_JSCPD)' };
|
|
86
|
+
const out = mkdtempSync(join(process.env.TMPDIR ?? tmpdir(), 'once-only-'));
|
|
87
|
+
const abs = real(root);
|
|
88
|
+
const ignore = [...excludeGlobs(layout, shape).map((g) => `${abs}/${g}`), ...extra.map((x) => real(x.as)), ...alsoIgnore.map(real)];
|
|
89
|
+
const args = ['-a', '-s', '-k', String(LIMITS.tokens), '-l', String(LIMITS.lines), '--max-gap-lines', String(LIMITS.gap), '--exit-code', '0',
|
|
90
|
+
'-f', FORMATS, '-r', 'json', '-o', out, '--cross-formats', CROSS, '-i', ignore.join(','), ...(paths ?? [abs]), ...extra.map((x) => x.path)];
|
|
91
|
+
const t0 = Date.now();
|
|
92
|
+
const r = spawnSync(bin, args, { encoding: 'utf8', timeout: timeoutMs, cwd: abs });
|
|
93
|
+
const ms = Date.now() - t0;
|
|
94
|
+
let report = null;
|
|
95
|
+
try { report = JSON.parse(readFileSync(join(out, 'jscpd-report.json'), 'utf8')); } catch { /* read below */ }
|
|
96
|
+
rmSync(out, { recursive: true, force: true });
|
|
97
|
+
if (!report) return { clones: [], ms, error: `jscpd gave no report (exit ${r.status}${r.signal ? `, ${r.signal}` : ''}): ${(r.stderr || r.error?.message || '').split('\n')[0]}` };
|
|
98
|
+
const back = new Map(extra.map((x) => [real(x.path), real(x.as)]));
|
|
99
|
+
const side = (f) => { const p = real(resolve(abs, f.name)); return { file: back.get(p) ?? p, start: f.start, end: f.end }; };
|
|
100
|
+
const clones = (report.duplicates ?? []).map((d) => ({ a: side(d.firstFile), b: side(d.secondFile), lines: d.lines, tokens: d.tokens }));
|
|
101
|
+
return { clones, ms, sources: report.statistics?.total?.sources ?? 0 };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The clones a block adds: one side in `file` overlapping [lo, hi], the other side anywhere (another copy in the same
|
|
105
|
+
// file included). Returns [{ at: {file,start,end}, other: {file,start,end}, lines, tokens }].
|
|
106
|
+
export function clonesOf(clones, file, [lo, hi]) {
|
|
107
|
+
const f = real(file);
|
|
108
|
+
const hits = (s) => s.file === f && s.start <= hi && s.end >= lo;
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const c of clones) {
|
|
111
|
+
if (hits(c.a)) out.push({ at: c.a, other: c.b, lines: c.lines, tokens: c.tokens });
|
|
112
|
+
else if (hits(c.b)) out.push({ at: c.b, other: c.a, lines: c.lines, tokens: c.tokens });
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Every clone of a repo, both sides inside copy trees left out. `rel` makes the paths relative for printing.
|
|
118
|
+
export function repoClones(root, { layout = {}, shape = null, jscpd = null } = {}) {
|
|
119
|
+
const r = scan(root, { layout, shape, jscpd, timeoutMs: 120000 });
|
|
120
|
+
const copies = copyGlobs(layout);
|
|
121
|
+
const base = real(root);
|
|
122
|
+
const rel = (p) => (p.startsWith(`${base}/`) ? p.slice(base.length + 1) : p);
|
|
123
|
+
const clones = r.clones
|
|
124
|
+
.map((c) => ({ ...c, a: { ...c.a, file: rel(c.a.file) }, b: { ...c.b, file: rel(c.b.file) } }))
|
|
125
|
+
.filter((c) => !(matchesAny(c.a.file, copies) && matchesAny(c.b.file, copies)));
|
|
126
|
+
return { ...r, clones };
|
|
127
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Elements built by hand (0.23.0: anything "component" used more than once is a template, in html, c++ and apps). once-only compares
|
|
2
|
+
// code as written, so two controls that differ in their names and text look different to it. Here names and text are stripped, and what is
|
|
3
|
+
// left is the element's shape: copies of one element have one shape.
|
|
4
|
+
// markup an element and its children: tags, attribute names, nesting (HTML, JSX, Vue, Svelte, template strings)
|
|
5
|
+
// code setups, runs of statements and tables written as code (lib/code-shapes.mjs)
|
|
6
|
+
// Using a template is never a shape: a capitalised tag (<Knob ... />) is a component, and a list from .map() appears once.
|
|
7
|
+
// shapesOf(text, ext) -> [{ id, kind, line, what }] codeFiles(root, skipGlobs) -> [rel]
|
|
8
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
9
|
+
import { extname, join, relative } from 'node:path';
|
|
10
|
+
import { codeShapes } from './code-shapes.mjs';
|
|
11
|
+
import { matchesAny } from './glob.mjs';
|
|
12
|
+
|
|
13
|
+
export const CODE = new Set(['.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.mm', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx']);
|
|
14
|
+
export const MARKUP = new Set(['.html', '.htm', '.vue', '.svelte', '.tsx', '.jsx', '.ts', '.js', '.mjs']);
|
|
15
|
+
export const SKIP = /(^|\/)(\.git|node_modules|build|Builds|dist|out|target|_deps|JUCE|JuceLibraryCode|third_party|vendor|Pods|venv|\.venv|site-packages|__pycache__|\.tox|\.next|coverage|cmake-build[^/]*|\.claude)(\/|$)/;
|
|
16
|
+
export const TESTS = ['tests/**', 'test/**', '**/tests/**', '**/test/**', '**/*.test.*', '**/*.spec.*', '**/*_test.*'];
|
|
17
|
+
|
|
18
|
+
const VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);
|
|
19
|
+
const TAG = /<(\/?)([A-Za-z][\w.:-]*)((?:[^<>{}"'`]|"[^"]*"|'[^']*'|`[^`]*`|\{(?:[^{}]|\{[^{}]*\})*\})*?)(\/?)>/g;
|
|
20
|
+
const ATTR = /([A-Za-z_:@][\w:.-]*)(?==|\s|$)/g;
|
|
21
|
+
const MIN_NODES = 4; // the element, and at least three more tags or attributes: a bare <li> is not a component
|
|
22
|
+
|
|
23
|
+
const attrsOf = (raw) => [...raw.replace(/"[^"]*"|'[^']*'|`[^`]*`|\{(?:[^{}]|\{[^{}]*\})*\}/g, ' ').matchAll(ATTR)].map((m) => m[1]).sort();
|
|
24
|
+
const lineOf = (text, index) => text.slice(0, index).split('\n').length;
|
|
25
|
+
|
|
26
|
+
// Every plain element with its children, as one string. An unclosed or unbalanced tag ends the walk for that branch, never the file.
|
|
27
|
+
function markupShapes(text) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const stack = [];
|
|
30
|
+
for (const m of text.matchAll(TAG)) {
|
|
31
|
+
const [, close, tag, attrs, self] = m;
|
|
32
|
+
if (close) {
|
|
33
|
+
const at = stack.map((e) => e.tag).lastIndexOf(tag);
|
|
34
|
+
if (at === -1) continue;
|
|
35
|
+
stack.length = at + 1;
|
|
36
|
+
const el = stack.pop();
|
|
37
|
+
const shape = `${el.tag}[${el.attrs.join(',')}](${el.kids.join('')})`;
|
|
38
|
+
stack.at(-1)?.kids.push(shape);
|
|
39
|
+
if (stack.at(-1)) stack.at(-1).nodes += el.nodes;
|
|
40
|
+
if (/^[a-z]/.test(el.tag) && el.nodes >= MIN_NODES && el.kids.length >= 1) out.push({ id: `m:${shape}`, kind: 'markup', line: lineOf(text, el.index), what: `<${el.tag}>`, nodes: el.nodes });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const names = attrsOf(attrs);
|
|
44
|
+
if (self || VOID.has(tag.toLowerCase())) {
|
|
45
|
+
const top = stack.at(-1);
|
|
46
|
+
if (top) { top.kids.push(`${tag}[${names.join(',')}]`); top.nodes += 1 + names.length; }
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
stack.push({ tag, attrs: names, kids: [], nodes: 1 + names.length, index: m.index });
|
|
50
|
+
}
|
|
51
|
+
// Keep the largest: the parts of a repeated element repeat too.
|
|
52
|
+
return out.filter((s) => !out.some((o) => o !== s && o.id.length > s.id.length && o.id.includes(s.id.slice(2))));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function shapesOf(text, ext) {
|
|
56
|
+
return [...(MARKUP.has(ext) ? markupShapes(text) : []), ...codeShapes(text, ext)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A short setup needs a third copy to be a pattern (two members; three on one name, the same habit in two files). Rows are a fault on their own.
|
|
60
|
+
export const copiesNeeded = (shape, oneName = false) => (shape.kind === 'rows' ? 1 : shape.kind === 'setup' && (shape.nodes < 3 || (shape.nodes === 3 && oneName)) ? 3 : 2);
|
|
61
|
+
|
|
62
|
+
// What the law leaves alone: `protected` globs, folders of kind copy (a tree kept the same as its source on purpose) and `guards.templates.exclude`.
|
|
63
|
+
export const skippedBy = (layout) => [...(layout.protected ?? []), ...(layout.guards?.templates?.exclude ?? []),
|
|
64
|
+
...Object.values(layout.folders ?? {}).filter((f) => f.kind === 'copy').map((f) => `${f.path}/**`)];
|
|
65
|
+
|
|
66
|
+
// What each kind of repeat becomes: the refusal and the scan say it, so a repeat is never only named.
|
|
67
|
+
export const TEMPLATE = {
|
|
68
|
+
markup: 'one component, with the differences passed in',
|
|
69
|
+
setup: 'one factory function: it builds the object from its arguments',
|
|
70
|
+
run: 'one function, with the differences as parameters',
|
|
71
|
+
rows: 'one table of the arguments, and one loop',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function codeFiles(root, skipGlobs = [], exts = CODE) {
|
|
75
|
+
const out = [];
|
|
76
|
+
const walk = (dir) => {
|
|
77
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
78
|
+
const abs = join(dir, e.name);
|
|
79
|
+
const rel = relative(root, abs).replace(/\\/g, '/');
|
|
80
|
+
if (SKIP.test(rel) || matchesAny(rel, skipGlobs)) continue;
|
|
81
|
+
if (e.isDirectory()) walk(abs);
|
|
82
|
+
else if (exts.has(extname(e.name)) && statSync(abs).size < 400_000) out.push(rel);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
walk(root);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The detail behind hooks.log. Every call is one line of .claude/logs/events.jsonl: who, where, what was attempted, and each hook's time, exit code and reason. Every refusal
|
|
2
|
+
// and crash is also a line of .claude/logs/refusals.jsonl, with an id, the branch and commit, the kit's version and the hash of the hook that decided, so it can be judged
|
|
3
|
+
// later: `claude-packs logs mark <id> right|wrong "<why>"`. Bug logs only record the refusals somebody complained about; this records them all. Secrets are masked. File
|
|
4
|
+
// contents are not kept, only their size. Logs rotate and are never cleared.
|
|
5
|
+
// logCall(root, text, [{ hook, ms, code, reason, out }], append, total)
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
9
|
+
import { maskSecrets } from './secret-patterns.mjs';
|
|
10
|
+
|
|
11
|
+
export const EVENTS = join('.claude', 'logs', 'events.jsonl');
|
|
12
|
+
export const REFUSALS = join('.claude', 'logs', 'refusals.jsonl');
|
|
13
|
+
const KEEP = { command: 4000, prompt: 1000, reason: 4000, out: 500 };
|
|
14
|
+
|
|
15
|
+
const cut = (text, n) => { const t = maskSecrets(text ?? ''); return t.length > n ? `${t.slice(0, n)}... [${t.length} chars]` : t; };
|
|
16
|
+
|
|
17
|
+
// The branch and commit of a checkout, from its files: no process is started on the hot path. A worktree's .git is a file.
|
|
18
|
+
export function headOf(cwd) {
|
|
19
|
+
try {
|
|
20
|
+
let git = join(cwd, '.git');
|
|
21
|
+
if (statSync(git).isFile()) git = resolve(cwd, readFileSync(git, 'utf8').replace('gitdir:', '').trim());
|
|
22
|
+
const head = readFileSync(join(git, 'HEAD'), 'utf8').trim();
|
|
23
|
+
const ref = head.match(/^ref: (.+)$/)?.[1];
|
|
24
|
+
if (!ref) return { commit: head.slice(0, 12) };
|
|
25
|
+
const common = existsSync(join(git, 'commondir')) ? resolve(git, readFileSync(join(git, 'commondir'), 'utf8').trim()) : git;
|
|
26
|
+
let commit;
|
|
27
|
+
try { commit = readFileSync(join(common, ref), 'utf8').trim().slice(0, 12); } catch { /* packed, or no commit yet */ }
|
|
28
|
+
return { branch: ref.replace('refs/heads/', ''), commit };
|
|
29
|
+
} catch { return {}; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// What the call tried to do, from the event itself.
|
|
33
|
+
function attempt(ev) {
|
|
34
|
+
const i = ev.tool_input ?? {};
|
|
35
|
+
const body = i.content ?? i.new_string ?? i.new_source;
|
|
36
|
+
return {
|
|
37
|
+
...(i.file_path || i.notebook_path ? { file: i.file_path ?? i.notebook_path } : {}),
|
|
38
|
+
...(typeof body === 'string' ? { bytes: body.length } : {}),
|
|
39
|
+
...(i.edits ? { edits: i.edits.length } : {}),
|
|
40
|
+
...(i.command ? { command: cut(i.command, KEEP.command) } : {}),
|
|
41
|
+
...(i.description ? { description: cut(i.description, 200) } : {}),
|
|
42
|
+
...(ev.prompt ? { prompt: cut(ev.prompt, KEEP.prompt) } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function lawOf(root, hook) {
|
|
47
|
+
try {
|
|
48
|
+
const lock = JSON.parse(readFileSync(join(root, '.claude/claude-packs.lock.json'), 'utf8'));
|
|
49
|
+
return { kit: lock.kit, hookHash: lock.files?.[`.claude/hooks/${hook}.mjs`]?.slice(0, 12) };
|
|
50
|
+
} catch { return {}; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function logCall(root, text, hooks, append, total) {
|
|
54
|
+
try {
|
|
55
|
+
let ev = {};
|
|
56
|
+
try { ev = JSON.parse(text) ?? {}; } catch { /* a line with no event is still a line */ }
|
|
57
|
+
const t = new Date().toISOString();
|
|
58
|
+
const where = { session: ev.session_id, agent: ev.agent_id ?? ev.agent_type, cwd: ev.cwd, event: ev.hook_event_name, tool: ev.tool_name ?? ev.source, mode: ev.permission_mode };
|
|
59
|
+
const what = attempt(ev);
|
|
60
|
+
const judged = hooks.map((h) => ({ hook: h.hook, ms: Math.round(h.ms), code: h.code, ...(h.code !== 0 && h.reason ? { reason: cut(h.reason, KEEP.reason) } : {}), ...(h.out ? { out: cut(h.out, KEEP.out) } : {}) }));
|
|
61
|
+
append(root, EVENTS, `${JSON.stringify({ t, ...where, ...what, ...(total ? { ms: Math.round(total) } : {}), hooks: judged })}\n`);
|
|
62
|
+
for (const h of judged.filter((x) => x.code !== 0)) {
|
|
63
|
+
const id = createHash('sha1').update(`${t}${h.hook}${h.reason ?? ''}`).digest('hex').slice(0, 8);
|
|
64
|
+
append(root, REFUSALS, `${JSON.stringify({ id, t, hook: h.hook, code: h.code, reason: h.reason, ...where, ...what, ...headOf(ev.cwd ?? root), ...lawOf(root, h.hook) })}\n`);
|
|
65
|
+
}
|
|
66
|
+
} catch { /* the log never stops a hook */ }
|
|
67
|
+
}
|