@thedecipherist/claude-packs 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +754 -0
- package/LICENSE +21 -0
- package/README.md +573 -2
- package/bin/claude-packs.mjs +212 -0
- package/configurations/base/config.json +85 -0
- package/configurations/cpp/config.json +93 -0
- package/configurations/full-stack/config.json +153 -0
- package/configurations/go/config.json +98 -0
- package/configurations/node/config.json +102 -0
- package/configurations/python/config.json +99 -0
- package/configurations/rust/config.json +106 -0
- package/configurations/synth-creator/config.json +148 -0
- package/global/hooks/gnu-shell.mjs +131 -0
- package/global/hooks/lib/shell-writes.mjs +2 -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/build-queue.mjs +22 -0
- package/lib/claude-md.mjs +60 -0
- package/lib/config-cli.mjs +36 -0
- package/lib/configs.mjs +145 -0
- package/lib/coverage.mjs +94 -0
- package/lib/custom-packs.mjs +93 -0
- package/lib/doctor.mjs +126 -0
- package/lib/fix-plan.mjs +72 -0
- package/lib/found-folders.mjs +25 -0
- package/lib/git-tokens.mjs +64 -0
- package/lib/global.mjs +125 -0
- package/lib/hook-proof.mjs +45 -0
- package/lib/hook-timing.mjs +42 -0
- package/lib/hover-gate.mjs +29 -0
- package/lib/kit-command.mjs +14 -0
- package/lib/kit-lock.mjs +149 -0
- package/lib/layout.mjs +99 -0
- package/lib/logs.mjs +109 -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 +109 -0
- package/lib/planner.mjs +59 -0
- package/lib/repeats.mjs +65 -0
- package/lib/repo-weight.mjs +28 -0
- package/lib/rule-tests.mjs +42 -0
- package/lib/rules.mjs +40 -0
- package/lib/scan-detect.mjs +130 -0
- package/lib/scan.mjs +171 -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 +31 -0
- package/lib/switches.mjs +44 -0
- package/lib/symmetry-doctor.mjs +26 -0
- package/lib/sync.mjs +133 -0
- package/lib/workorder.mjs +26 -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 +172 -0
- package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
- package/packs/audio-core/pack.json +192 -0
- package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -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 +28 -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 +23 -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 +35 -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 +25 -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 +99 -0
- package/packs/base/hooks/dispatch-once.mjs +124 -0
- package/packs/base/hooks/file-size-guard.mjs +61 -0
- package/packs/base/hooks/folder-law.mjs +77 -0
- package/packs/base/hooks/git-guard.mjs +202 -0
- package/packs/base/hooks/lesson-checks.mjs +182 -0
- package/packs/base/hooks/lib/apply-edit.mjs +20 -0
- package/packs/base/hooks/lib/code-shapes.mjs +142 -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/crash-policy.mjs +47 -0
- package/packs/base/hooks/lib/dup-index.mjs +204 -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 +68 -0
- package/packs/base/hooks/lib/function-shapes.mjs +90 -0
- package/packs/base/hooks/lib/git-adds.mjs +21 -0
- package/packs/base/hooks/lib/git-auth.mjs +44 -0
- package/packs/base/hooks/lib/git-guard-selftest.mjs +192 -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 +69 -0
- package/packs/base/hooks/lib/layout.mjs +58 -0
- package/packs/base/hooks/lib/making-check-selftest.mjs +112 -0
- package/packs/base/hooks/lib/near-copies.mjs +76 -0
- package/packs/base/hooks/lib/program-read.mjs +199 -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/rust-scan.mjs +139 -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-lists.mjs +31 -0
- package/packs/base/hooks/lib/shape-prose.mjs +78 -0
- package/packs/base/hooks/lib/shape.mjs +273 -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 +86 -0
- package/packs/base/hooks/lib/unread-writes.mjs +24 -0
- package/packs/base/hooks/lib/writes.mjs +71 -0
- package/packs/base/hooks/making-check.mjs +248 -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 +140 -0
- package/packs/base/hooks/secrets-guard.mjs +99 -0
- package/packs/base/hooks/shape-law.mjs +140 -0
- package/packs/base/hooks/suite-once.mjs +96 -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 +19 -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 +23 -0
- package/packs/base/rules/unattended-runs.md +15 -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/new-project/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/skills/write-a-workorder/SKILL.md +41 -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 +26 -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 +29 -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/postgres/checks/pg-bind-values.json +33 -0
- package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
- package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
- package/packs/postgres/checks/pg-index-concurrently.json +18 -0
- package/packs/postgres/checks/pg-no-float-money.json +19 -0
- package/packs/postgres/checks/pg-no-money.json +18 -0
- package/packs/postgres/checks/pg-no-required-column.json +18 -0
- package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
- package/packs/postgres/checks/pg-not-exists.json +18 -0
- package/packs/postgres/checks/pg-timestamptz.json +18 -0
- package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
- package/packs/postgres/pack.json +48 -0
- package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
- package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
- package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
- package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
- package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
- package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -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 +22 -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 +22 -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/rust/checks/rust-allow-needs-reason.json +16 -0
- package/packs/rust/checks/rust-blocking-in-async.json +32 -0
- package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
- package/packs/rust/checks/rust-static-mut.json +32 -0
- package/packs/rust/checks/rust-string-as-error.json +34 -0
- package/packs/rust/checks/rust-stub-left-in.json +32 -0
- package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
- package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
- package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
- package/packs/rust/clippy.toml +5 -0
- package/packs/rust/pack.json +27 -0
- package/packs/rust/rules/rust-async-never-blocks.md +20 -0
- package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
- package/packs/rust/rules/rust-errors-are-returned.md +22 -0
- package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
- package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
- package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
- package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
- package/packs/rust/skills/rust-port/SKILL.md +22 -0
- package/packs/rust/skills/rust-review/SKILL.md +24 -0
- package/packs/rust/tools/rust-lints.toml +19 -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/bounce-a-plugin/SKILL.md +40 -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/sound-design/tools/vst3host/build.sh +73 -0
- package/packs/sound-design/tools/vst3host/host_events.h +101 -0
- package/packs/sound-design/tools/vst3host/options.h +172 -0
- package/packs/sound-design/tools/vst3host/render.cpp +245 -0
- package/packs/sound-design/tools/vst3host/session.h +241 -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 +175 -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 +21 -0
- package/packs/synth-creator/tools/buildq.mjs +74 -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/prompts/workorder/PROMPT.md +53 -0
- package/prompts/workorder/run.sh +36 -0
- package/prompts/workorder/workorder.md +59 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// A hook that should refuse and does not (0.18.0: whenever a hook is supposed to exit 2 and it exits 0, it is caught). Only exit 2
|
|
2
|
+
// blocks, so a guard fails open and silently. doctor sees three of the ways: a hook no settings entry runs (dispatch-once was that way for
|
|
3
|
+
// a moment in 0.11.0); a hook that crashed since it last changed, from its exit code in .claude/hooks.log, where "changed" counts every
|
|
4
|
+
// file in hooks/lib/, since most crashes are there (KF-40), and a crash no change answers is cleared by `logs clear` with its reason (KF-18);
|
|
5
|
+
// and param-id-lock holding no ids in a unit with parameters (KF-15). A hook killed at its timeout leaves no line and is not seen.
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { ANSWERS, clearedAt, clearsOf } from './logs.mjs';
|
|
11
|
+
|
|
12
|
+
const RUNNER = 'run-hooks.mjs';
|
|
13
|
+
const hookName = (file) => file.replace(/\.(mjs|py)$/, '');
|
|
14
|
+
|
|
15
|
+
// The time a hook last changed: its own file, or anything it shares in hooks/lib.
|
|
16
|
+
const changedAt = (dir, file, libTimes) => Math.max(statSync(join(dir, file)).mtimeMs, ...libTimes);
|
|
17
|
+
|
|
18
|
+
export function paramLockProblems(projectDir) {
|
|
19
|
+
const hook = join(projectDir, '.claude', 'hooks', 'param-id-lock.mjs');
|
|
20
|
+
if (!existsSync(hook)) return [];
|
|
21
|
+
const r = spawnSync('node', [hook, '--status'], { encoding: 'utf8', env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir } });
|
|
22
|
+
return r.status === 0 ? [] : [r.stdout.trim() || r.stderr.trim()];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function hookProofProblems(projectDir, settingsText) {
|
|
26
|
+
const dir = join(projectDir, '.claude', 'hooks');
|
|
27
|
+
if (!existsSync(dir)) return [];
|
|
28
|
+
const hooks = readdirSync(dir).filter((f) => /\.(mjs|py)$/.test(f) && f !== RUNNER);
|
|
29
|
+
const problems = hooks.filter((f) => !settingsText.includes(`.claude/hooks/${f}`)).map((f) => `${f} is in .claude/hooks but no settings entry runs it, so it refuses nothing`);
|
|
30
|
+
const log = join(dir, 'lib', 'hook-log.mjs');
|
|
31
|
+
if (!existsSync(log)) return problems;
|
|
32
|
+
const { readHookLog } = await import(pathToFileURL(log).href);
|
|
33
|
+
const libTimes = readdirSync(join(dir, 'lib')).map((f) => statSync(join(dir, 'lib', f)).mtimeMs);
|
|
34
|
+
const clears = clearsOf(projectDir);
|
|
35
|
+
const crashed = new Map();
|
|
36
|
+
for (const row of readHookLog(projectDir, { rotated: true })) {
|
|
37
|
+
const file = hooks.find((f) => hookName(f) === row.hook);
|
|
38
|
+
if (!file || ANSWERS.has(String(row.code))) continue;
|
|
39
|
+
if (Date.parse(row.time) > changedAt(dir, file, libTimes) && row.time > clearedAt(clears, row.hook)) crashed.set(file, (crashed.get(file) ?? 0) + 1);
|
|
40
|
+
}
|
|
41
|
+
for (const [f, n] of crashed) {
|
|
42
|
+
problems.push(`${f} crashed on ${n} call(s) since it last changed: those calls were not judged (\`logs crashes\`). Once fixed or superseded: \`logs clear ${hookName(f)} fixed|superseded "<why>"\``);
|
|
43
|
+
}
|
|
44
|
+
return problems;
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// doctor's hook timing (KWB-24): every hook writes one line per call to .claude/hooks.log (packs/base/hooks/lib/hook-log.mjs,
|
|
2
|
+
// lib/house.py's log_hook); per hook, the median and the worst of its last 100 calls. A hook whose MEDIAN is over 500 ms is a
|
|
3
|
+
// red check: the median is what every call pays, so a slow median is a slow hook. The worst is printed, never judged: one
|
|
4
|
+
// call on a loaded machine, or the once-only index built the first time, is a spike and not the hook's cost. A hook with fewer
|
|
5
|
+
// than MIN_CALLS calls logged is printed, not judged.
|
|
6
|
+
// The runner's own line (run-hooks: one process for a settings entry, node's start-up included) is judged the same way.
|
|
7
|
+
import { readHookLog } from '../packs/base/hooks/lib/hook-log.mjs';
|
|
8
|
+
|
|
9
|
+
export const SLOW_MS = 500;
|
|
10
|
+
export const LAST = 100;
|
|
11
|
+
export const MIN_CALLS = 3; // one or two calls are not a median: printed, not judged
|
|
12
|
+
|
|
13
|
+
export function median(values) {
|
|
14
|
+
const s = [...values].sort((a, b) => a - b);
|
|
15
|
+
const m = Math.floor(s.length / 2);
|
|
16
|
+
return s.length % 2 ? s[m] : Math.round((s[m - 1] + s[m]) / 2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// -> [{ hook, calls, median, worst }], slowest median first
|
|
20
|
+
export function hookTimes(projectDir, last = LAST) {
|
|
21
|
+
const by = new Map();
|
|
22
|
+
for (const r of readHookLog(projectDir)) {
|
|
23
|
+
if (!by.has(r.hook)) by.set(r.hook, []);
|
|
24
|
+
by.get(r.hook).push(r.ms);
|
|
25
|
+
}
|
|
26
|
+
return [...by].map(([hook, all]) => {
|
|
27
|
+
const ms = all.slice(-last);
|
|
28
|
+
return { hook, calls: ms.length, median: median(ms), worst: Math.max(...ms) };
|
|
29
|
+
}).sort((a, b) => b.median - a.median || a.hook.localeCompare(b.hook));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function timingProblems(projectDir, log = console.log) {
|
|
33
|
+
const times = hookTimes(projectDir);
|
|
34
|
+
if (!times.length) {
|
|
35
|
+
log(' hook timing: no calls logged yet (.claude/hooks.log fills as the hooks run)');
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
log(` hook timing (ms, each hook's last ${LAST} calls at most): ${times.map((t) => `${t.hook} ${t.median}/${t.worst}`).join(', ')} (median/worst)`);
|
|
39
|
+
return times.filter((t) => t.calls >= MIN_CALLS && t.median > SLOW_MS).map((t) =>
|
|
40
|
+
`${t.hook}: median ${t.median} ms over its last ${t.calls} calls (worst ${t.worst} ms), over ${SLOW_MS} ms: find what it does on every call ` +
|
|
41
|
+
'(a scan of the repo, a child process, a whole transcript read) and make it incremental');
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// 0.10.1 (Stellix B-261): a project with UI code names the gate that proves its hover.
|
|
2
|
+
// layout.json "gates": { "hover": "<command>" }. doctor is red when the hover rule is installed, a file stands under its paths (the
|
|
3
|
+
// ui concept), and no gate is declared; or the command names a file (a word with a "/") that is not there. "none: <reason>" declines.
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { projectFiles } from './coverage.mjs';
|
|
8
|
+
|
|
9
|
+
// One gate: the rule whose paths say where it applies, its key in layout.json "gates", and the skill that builds it.
|
|
10
|
+
async function gateProblems(projectDir, layout, { rule, key, skill }) {
|
|
11
|
+
const rulesDir = join(projectDir, '.claude', 'rules');
|
|
12
|
+
const rules = existsSync(rulesDir) ? readdirSync(rulesDir).filter((f) => rule.test(f)) : [];
|
|
13
|
+
if (!rules.length) return [];
|
|
14
|
+
const globs = rules.flatMap((f) => [...(readFileSync(join(rulesDir, f), 'utf8').match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '').matchAll(/^\s*-\s*"([^"]+)"/gm)].map((m) => m[1]));
|
|
15
|
+
if (!globs.length) return [];
|
|
16
|
+
const { matchesAny } = await import(pathToFileURL(join(projectDir, '.claude', 'hooks', 'lib', 'glob.mjs')).href);
|
|
17
|
+
const ui = projectFiles(projectDir).filter((f) => matchesAny(f, globs));
|
|
18
|
+
const gate = layout.gates?.[key];
|
|
19
|
+
if (typeof gate === 'string' && /^none:\s*\S/.test(gate)) return [];
|
|
20
|
+
if (ui.length === 0) return []; // no UI code yet: nothing to prove, and a gate named ahead of its file (a profile) is not red
|
|
21
|
+
const hint = `name it in layout.json as "gates": { "${key}": "<command>" } (the ${skill} skill says how to build it), or "none: <reason>"`;
|
|
22
|
+
if (typeof gate !== 'string' || !gate.trim() || /^none:/.test(gate)) return [`no ${key} gate declared for the UI code (${ui.slice(0, 3).join(', ')}${ui.length > 3 ? ', …' : ''}): ${hint}`];
|
|
23
|
+
const missing = gate.split(/\s+/).filter((w) => w.includes('/') && !w.startsWith('-') && !/^[a-z]+:\/\//.test(w)).filter((w) => !existsSync(join(projectDir, w)));
|
|
24
|
+
return missing.map((w) => `the ${key} gate "${gate}" names ${w}, which is not there`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const hoverGateProblems = (projectDir, layout) => gateProblems(projectDir, layout, { rule: /(?:hover-every-pressable|pointer-feedback)\.md$/, key: 'hover', skill: 'hover-state' });
|
|
28
|
+
// 0.17.0: a page is proved rendered (rule page-proved-rendered): the same law for accessibility and layout.
|
|
29
|
+
export const pageGateProblems = (projectDir, layout) => gateProblems(projectDir, layout, { rule: /page-proved-rendered\.md$/, key: 'a11y', skill: 'prove-page' });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// How this project runs the kit. `npx --no-install` needs the kit in its own node_modules, and a project in another language has none: there npx
|
|
2
|
+
// fetches it, and the --no-install form every installed file names fails with "npx canceled due to missing packages" (KWB-76).
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const PACKAGE = '@thedecipherist/claude-packs';
|
|
7
|
+
|
|
8
|
+
export const hasLocalKit = (dir) => existsSync(join(dir, 'node_modules', PACKAGE, 'package.json'));
|
|
9
|
+
export const kitCommand = (dir) => (hasLocalKit(dir) ? `npx --no-install ${PACKAGE}` : `npx ${PACKAGE}`);
|
|
10
|
+
|
|
11
|
+
// The line the CLAUDE.md block adds where the installed files' form cannot work: a project with no package.json at all.
|
|
12
|
+
export const noPackageLine = (dir) => (existsSync(join(dir, 'package.json')) || hasLocalKit(dir)
|
|
13
|
+
? []
|
|
14
|
+
: [`This project has no package.json: run the kit as \`npx ${PACKAGE} <command>\`, wherever its files say \`npx --no-install ${PACKAGE}\`.`, '']);
|
package/lib/kit-lock.mjs
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// The kit lock (.claude-packs-lock at a checkout's root of the claude-packs repo; the repo's own .claude/hooks/kit-lock.mjs
|
|
2
|
+
// takes it, renews it, and refuses another session's commits, merges and releases). Here: its one format, the lease, and
|
|
3
|
+
// `claude-packs lock status|take|release` (KWB-25: a lock left by a gone session could only be released by an rm by hand).
|
|
4
|
+
// The form: { version: 2, holder: { session, pid }, branch, started_at, last_seen }, with the old keys session and time beside
|
|
5
|
+
// them so an older kit-lock reads it. The old forms { session, branch, time } and { session, branch } are read too.
|
|
6
|
+
// The lock is a LEASE (KWB-29): its holder's hook renews last_seen at most once a minute (RENEW_MS) on every
|
|
7
|
+
// tool call; a holder whose session runs outside the kit (a Stellix lead and its agents, whose hooks are Stellix's) renews it
|
|
8
|
+
// by working: its transcript, or a subagent's, written later than last_seen counts as seen. No renewal for LEASE_MIN minutes
|
|
9
|
+
// and the lease is stale: the next session that starts work takes it over, logged in the main checkout's .claude/kit-lock.log
|
|
10
|
+
// (who held it, branch, since, last seen). A live lease is never taken or deleted; `lock release --force` is the only way.
|
|
11
|
+
// lock status each checkout's lock: holder, branch, held since, last seen, live or stale
|
|
12
|
+
// lock take [--branch <b>] take it for CLAUDE_CODE_SESSION_ID (a session whose own hooks are not the kit's)
|
|
13
|
+
// lock release [--force] the caller's own lock and stale ones go; another live lease stays (exit 1) unless --force
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { appendFileSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
export const LOCK = '.claude-packs-lock';
|
|
20
|
+
export const LOG = join('.claude', 'kit-lock.log');
|
|
21
|
+
export const LEASE_MIN = 30;
|
|
22
|
+
export const RENEW_MS = 60000;
|
|
23
|
+
const git = (cwd, ...a) => { try { return execFileSync('git', a, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); } catch { return ''; } };
|
|
24
|
+
const branchOf = (dir) => git(dir, 'symbolic-ref', '--short', 'HEAD') || git(dir, 'rev-parse', '--abbrev-ref', 'HEAD') || '?';
|
|
25
|
+
const ms = (t) => { const v = Date.parse(t ?? ''); return Number.isFinite(v) ? v : null; };
|
|
26
|
+
|
|
27
|
+
// Every checkout of the repo dir is in: the main one first, then each worktree.
|
|
28
|
+
export function checkouts(root) {
|
|
29
|
+
const out = git(root, 'worktree', 'list', '--porcelain').split('\n').filter((l) => l.startsWith('worktree ')).map((l) => l.slice(9));
|
|
30
|
+
return out.length ? out : [root];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// -> { session, pid, branch, startedAt, lastSeen } | null, from any form; a time the lock lacks is null, never undefined
|
|
34
|
+
export function readLock(dir) {
|
|
35
|
+
let raw;
|
|
36
|
+
try { raw = JSON.parse(readFileSync(join(dir, LOCK), 'utf8')); } catch { return null; }
|
|
37
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
38
|
+
const startedAt = ms(raw.started_at) !== null ? raw.started_at : ms(raw.time) !== null ? raw.time : null;
|
|
39
|
+
return { session: raw.holder?.session ?? raw.session ?? 'unknown', pid: raw.holder?.pid ?? null, branch: raw.branch ?? '?', startedAt, lastSeen: ms(raw.last_seen) !== null ? raw.last_seen : startedAt };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const write = (dir, l) => writeFileSync(join(dir, LOCK), `${JSON.stringify({ version: 2, holder: { session: l.session, pid: l.pid }, branch: l.branch, started_at: l.startedAt, last_seen: l.lastSeen, session: l.session, time: l.startedAt }, null, 2)}\n`);
|
|
43
|
+
|
|
44
|
+
export function takeLock(root, session, branch = branchOf(root), pid = Number(process.env.CLAUDE_PID) || null, now = Date.now()) {
|
|
45
|
+
const at = new Date(now).toISOString();
|
|
46
|
+
const lock = { session, pid, branch, startedAt: at, lastSeen: at };
|
|
47
|
+
write(root, lock);
|
|
48
|
+
return lock;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The holder's heartbeat: each checkout's lock held by `session` gets last_seen = now, when it is RENEW_MS old or more.
|
|
52
|
+
export function renew(dirs, session, now = Date.now()) {
|
|
53
|
+
let renewed = false;
|
|
54
|
+
for (const d of dirs) {
|
|
55
|
+
const l = readLock(d);
|
|
56
|
+
if (!l || l.session !== session || (ms(l.lastSeen) !== null && now - ms(l.lastSeen) < RENEW_MS)) continue;
|
|
57
|
+
write(d, { ...l, startedAt: l.startedAt ?? new Date(now).toISOString(), lastSeen: new Date(now).toISOString() });
|
|
58
|
+
renewed = true;
|
|
59
|
+
}
|
|
60
|
+
return renewed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The latest write of the session's transcript or one of its subagents' (~/.claude/projects/<p>/<session>[.jsonl|/subagents/]).
|
|
64
|
+
export function transcriptSeen(session, home = homedir()) {
|
|
65
|
+
const projects = join(home, '.claude', 'projects');
|
|
66
|
+
let latest = null;
|
|
67
|
+
const see = (f) => { try { latest = Math.max(latest ?? 0, statSync(f).mtimeMs); } catch { /* none */ } };
|
|
68
|
+
let dirs = [];
|
|
69
|
+
try { dirs = readdirSync(projects); } catch { /* no transcripts */ }
|
|
70
|
+
for (const d of dirs) {
|
|
71
|
+
see(join(projects, d, `${session}.jsonl`));
|
|
72
|
+
const sub = join(projects, d, session, 'subagents');
|
|
73
|
+
let files = [];
|
|
74
|
+
try { files = readdirSync(sub); } catch { /* none */ }
|
|
75
|
+
for (const f of files) if (f.endsWith('.jsonl')) see(join(sub, f));
|
|
76
|
+
}
|
|
77
|
+
return latest;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// -> { live, seenAt, why }
|
|
81
|
+
export function liveness(lock, home = homedir(), now = Date.now()) {
|
|
82
|
+
const lease = ms(lock.lastSeen);
|
|
83
|
+
const t = transcriptSeen(lock.session, home);
|
|
84
|
+
const seenAt = Math.max(lease ?? -Infinity, t ?? -Infinity);
|
|
85
|
+
if (!Number.isFinite(seenAt)) return { live: false, seenAt: null, why: 'never seen (no last_seen, no transcript)' };
|
|
86
|
+
const age = (now - seenAt) / 60000;
|
|
87
|
+
const by = t !== null && (lease === null || t > lease) ? 'its transcript' : 'its lease';
|
|
88
|
+
return { live: age < LEASE_MIN, seenAt, why: `last seen ${new Date(seenAt).toISOString()}, ${age.toFixed(0)} min ago, by ${by}` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const describe = (lock, l) => `held by session ${lock.session} (branch ${lock.branch}, held since ${lock.startedAt ?? 'start unknown'}; ${l.why}): ` +
|
|
92
|
+
(l.live ? 'live' : `stale (no renewal for ${LEASE_MIN} min)`);
|
|
93
|
+
|
|
94
|
+
// A stale lease out, the taker's in, the take-over logged in the main checkout's .claude/kit-lock.log.
|
|
95
|
+
export function takeOver(root, where, lock, l, session, now = Date.now()) {
|
|
96
|
+
const main = checkouts(root)[0];
|
|
97
|
+
mkdirSync(join(main, '.claude'), { recursive: true });
|
|
98
|
+
appendFileSync(join(main, LOG), `${new Date(now).toISOString()} took over ${join(where, LOCK)}: ${describe(lock, l)}; by session ${session}\n`);
|
|
99
|
+
rmSync(join(where, LOCK), { force: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The lock for `session` in checkout `dir`: its own already, none (taken), or a stale one (taken over). Another live lease:
|
|
103
|
+
// { held: { lock, where, l } } and nothing is changed.
|
|
104
|
+
export function acquire(dir, session, { home = homedir(), now = Date.now(), branch } = {}) {
|
|
105
|
+
const locks = checkouts(dir).map((where) => ({ where, lock: readLock(where) })).filter((x) => x.lock);
|
|
106
|
+
const mine = locks.find((x) => x.lock.session === session);
|
|
107
|
+
if (mine) return { mine: mine.where };
|
|
108
|
+
// The same claude process under a new session id (a reload) is its own holder, never another session (KWB-40).
|
|
109
|
+
const pid = Number(process.env.CLAUDE_PID) || null;
|
|
110
|
+
const own = pid && locks.find((x) => x.lock.pid === pid);
|
|
111
|
+
if (own) { takeLock(own.where, session, own.lock.branch, pid, now); return { mine: own.where }; }
|
|
112
|
+
const others = locks.map((x) => ({ ...x, l: liveness(x.lock, home, now) }));
|
|
113
|
+
const live = others.find((x) => x.l.live);
|
|
114
|
+
if (live) return { held: live };
|
|
115
|
+
for (const x of others) takeOver(dir, x.where, x.lock, x.l, session, now);
|
|
116
|
+
return { took: takeLock(dir, session, branch ?? branchOf(dir), undefined, now) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function lockCommand(sub, dir, { force = false, branch, session = process.env.CLAUDE_CODE_SESSION_ID ?? null, home = homedir(), log = console.log } = {}) {
|
|
120
|
+
if (!['status', 'take', 'release'].includes(sub)) throw new Error('use `lock status`, `lock take [--branch <b>]` or `lock release [--force]`');
|
|
121
|
+
if (sub === 'take') {
|
|
122
|
+
if (!session) { log('no CLAUDE_CODE_SESSION_ID: run it from the session that takes the lock'); return 1; }
|
|
123
|
+
const r = acquire(dir, session, { home, branch });
|
|
124
|
+
if (r.held) { log(`kept ${join(r.held.where, LOCK)}: ${describe(r.held.lock, r.held.l)}. Ask the user; --force on release is the only way to remove a live lease`); return 1; }
|
|
125
|
+
log(r.mine ? `already yours: ${join(r.mine, LOCK)}` : `took ${join(dir, LOCK)} for session ${session} (branch ${r.took.branch})`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
let code = 0;
|
|
129
|
+
let any = false;
|
|
130
|
+
for (const c of checkouts(dir)) {
|
|
131
|
+
const lock = readLock(c);
|
|
132
|
+
if (!lock) continue;
|
|
133
|
+
any = true;
|
|
134
|
+
const l = liveness(lock, home);
|
|
135
|
+
const where = join(c, LOCK);
|
|
136
|
+
if (sub === 'status') { log(`${where}: ${describe(lock, l)}`); continue; }
|
|
137
|
+
const mine = session && lock.session === session;
|
|
138
|
+
if (mine || !l.live || force) {
|
|
139
|
+
if (!mine && !l.live) takeOver(dir, c, lock, l, `${session ?? 'unknown'} (lock release)`);
|
|
140
|
+
rmSync(where, { force: true });
|
|
141
|
+
log(`released ${where}: ${describe(lock, l)}${mine ? ' (your own)' : !l.live ? '' : ' (--force)'}`);
|
|
142
|
+
} else {
|
|
143
|
+
log(`kept ${where}: session ${lock.session} is live (${l.why}). It is another session's lease: wait for its release or for it to go stale (${LEASE_MIN} min without renewal), or ask the user; --force is the only way to release a live lease`);
|
|
144
|
+
code = 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!any) log('no kit lock in any checkout');
|
|
148
|
+
return code;
|
|
149
|
+
}
|
package/lib/layout.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Builds .claude/layout.json from the installed packs, keeping every edit the user made.
|
|
2
|
+
// The layout may also hold (all project-owned, never overwritten by an update):
|
|
3
|
+
// "files": { alias: path } special files that checks refer to as "@alias"
|
|
4
|
+
// "protected": [globs] extra read-only paths (vendored copies)
|
|
5
|
+
// "buildOutput": [globs] the build output (default build/**); read-only, as vendored code
|
|
6
|
+
// "writeOnce": [globs] evidence: a new file may be added, an existing one never changed or removed (KWB-21)
|
|
7
|
+
// "keepWorktrees": [names] agent worktrees build-sweep never sweeps (the build the user plays)
|
|
8
|
+
// "disable": ["checks/x.json", "hooks/y.mjs", "rules/z.md", "skills/w"] pack items this project replaces with its own
|
|
9
|
+
// "commands": { name: "shell command" } how this project builds, tests and benches (skills use these)
|
|
10
|
+
// "paramIds": { "extensions", "patterns" } where param-id-lock reads parameter ids (default: JUCE ParameterID literals)
|
|
11
|
+
// "enable": ["hooks/x.py", "checks/y.json"] pack items this project switches on: opt-in hooks, and hooks or
|
|
12
|
+
// checks a later package version adds to a pack the project already had (see switches.mjs)
|
|
13
|
+
// "sourceFiles": "<regex>" what is source to folder-law and doctor (written once from the configuration's; KWB-37)
|
|
14
|
+
// "gates": { "hover": "<command>", "a11y": "<command>" } the gate that proves the UI's hover (doctor is red for UI code without one; "none: <reason>" declines; 0.10.1)
|
|
15
|
+
// "ruleTests": { "rule-name": "path/to/its/test" | "none: <reason>" } what answers a rule's named test when no test names the rule (KF-31)
|
|
16
|
+
// "followFolders": true base's language concepts cover every code folder of the layout. A first install writes it; an older project chooses it
|
|
17
|
+
// "guards": { "git", "copy", "time", "browser", "images" } the project's values for the house guards
|
|
18
|
+
// (repo owner and token, the sanctioned copy tools, ...); each hook has its defaults
|
|
19
|
+
// A seed layout (a profile, or one written from docs/project.md) is used when no layout exists yet.
|
|
20
|
+
// "configuration" names the project's configuration (lib/configs.mjs); its layout template is read like a pack's folders,
|
|
21
|
+
// before the packs', so a project gets the folder structure of its kind (0.10.0).
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { NEW_INSTALL_GIT } from '../packs/base/hooks/lib/git-auth.mjs';
|
|
25
|
+
import { codeFolders } from '../packs/base/hooks/lib/layout.mjs';
|
|
26
|
+
import { readPack } from './packs.mjs';
|
|
27
|
+
|
|
28
|
+
const OWNED = ['files', 'protected', 'buildOutput', 'writeOnce', 'keepWorktrees', 'disable', 'enable', 'commands', 'siblings', 'maxFileLines', 'paramIds', 'guards', 'sourceFiles', 'gates', 'ruleTests', 'followFolders'];
|
|
29
|
+
const OPTIONAL = ['maxFileLines', 'paramIds', 'guards', 'buildOutput', 'writeOnce', 'keepWorktrees', 'sourceFiles', 'gates', 'ruleTests', 'followFolders'];
|
|
30
|
+
// "complete": true marks a tailored layout; see below.
|
|
31
|
+
|
|
32
|
+
// Base's language concepts name folders, where every other language's are `**`: a file in a layout folder could be legal and have no rules (KWB-83).
|
|
33
|
+
// `**` would close that and hide a stray file from doctor --strict, so the concept follows the layout instead: a folder in it gets its rules, and one outside stays reported.
|
|
34
|
+
const FOLLOWED = { cpp: ['c', 'cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'mm'], js: ['{js,mjs,cjs,jsx,ts,tsx}'] };
|
|
35
|
+
|
|
36
|
+
function followFolders(layout) {
|
|
37
|
+
for (const [concept, extensions] of Object.entries(FOLLOWED)) {
|
|
38
|
+
if (!layout.concepts[concept]) continue;
|
|
39
|
+
const globs = codeFolders(layout).flatMap((folder) => extensions.map((ext) => `${folder}/**/*.${ext}`));
|
|
40
|
+
layout.concepts[concept] = [...new Set([...layout.concepts[concept], ...globs])];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildLayout(claudeDir, packNames, seed = null, config = null) {
|
|
45
|
+
const file = join(claudeDir, 'layout.json');
|
|
46
|
+
const firstInstall = !existsSync(file);
|
|
47
|
+
const current = firstInstall ? (seed ?? {}) : JSON.parse(readFileSync(file, 'utf8'));
|
|
48
|
+
// A project already installed changes only through "enable": "guards/git-house".
|
|
49
|
+
if (firstInstall) current.guards = { ...current.guards, git: { ...NEW_INSTALL_GIT, ...current.guards?.git } };
|
|
50
|
+
// The key is the choice: a new install is held to its rules' named tests, and one already installed is told, until it adds the key.
|
|
51
|
+
if (firstInstall) current.ruleTests ??= {};
|
|
52
|
+
if (firstInstall && !current.complete) current.followFolders ??= true;
|
|
53
|
+
const layout = {
|
|
54
|
+
_comment: 'Where everything lives. folders = the folders; kinds = what goes where (CLAUDE.md shows it, placement-law enforces it); concepts = which rules fire where. Edit here, then run `npx --no-install @thedecipherist/claude-packs update`.',
|
|
55
|
+
...(config ? { configuration: config.name } : current.configuration ? { configuration: current.configuration } : {}),
|
|
56
|
+
folders: { ...(current.folders ?? {}) },
|
|
57
|
+
concepts: { ...(current.concepts ?? {}) },
|
|
58
|
+
kinds: { ...(current.kinds ?? {}) },
|
|
59
|
+
};
|
|
60
|
+
for (const key of OWNED) {
|
|
61
|
+
if (current[key] !== undefined) layout[key] = current[key];
|
|
62
|
+
else if (!OPTIONAL.includes(key)) layout[key] = key === 'files' || key === 'commands' ? {} : [];
|
|
63
|
+
}
|
|
64
|
+
// A kind's content pattern that is exactly a pattern the pack used to ship ("contentWas") is
|
|
65
|
+
// replaced by the pack's current one; a pattern the project wrote itself is kept.
|
|
66
|
+
// "sourceFiles" (what folder-law and doctor count as source; KWB-37) comes from the configuration once, then is the project's
|
|
67
|
+
if (config?.sourceFiles && layout.sourceFiles === undefined) layout.sourceFiles = config.sourceFiles;
|
|
68
|
+
const sources = [...(config?.layout ? [config.layout] : []), ...packNames.map(readPack)];
|
|
69
|
+
for (const pack of sources) {
|
|
70
|
+
for (const [k, v] of Object.entries(pack.kinds ?? {})) {
|
|
71
|
+
const mine = layout.kinds[k];
|
|
72
|
+
if (mine && v.contentWas?.includes(mine.content)) layout.kinds[k] = { ...mine, content: v.content };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// A layout marked "complete" (a tailored project) gets no default folders from the packs. It
|
|
76
|
+
// still gains new pack concepts made only of raw globs (no folder names), such as "claude",
|
|
77
|
+
// so a rule added in a later package version always resolves.
|
|
78
|
+
if (current.complete) {
|
|
79
|
+
layout.complete = true;
|
|
80
|
+
for (const pack of sources) {
|
|
81
|
+
for (const [k, v] of Object.entries(pack.concepts ?? {})) {
|
|
82
|
+
if (!layout.concepts[k] && v.every((e) => e.startsWith('kind:') || e.includes('/') || e.includes('*') || e.includes('.'))) layout.concepts[k] = v;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
writeFileSync(file, JSON.stringify(layout, null, 2) + '\n');
|
|
86
|
+
return layout;
|
|
87
|
+
}
|
|
88
|
+
for (const pack of sources) {
|
|
89
|
+
for (const [k, v] of Object.entries(pack.folders ?? {})) layout.folders[k] ??= v;
|
|
90
|
+
for (const [k, v] of Object.entries(pack.files ?? {})) layout.files[k] ??= v;
|
|
91
|
+
for (const [k, { contentWas, ...v }] of Object.entries(pack.kinds ?? {})) layout.kinds[k] ??= v;
|
|
92
|
+
for (const [k, v] of Object.entries(pack.concepts ?? {})) {
|
|
93
|
+
if (!current.concepts?.[k]) layout.concepts[k] = [...new Set([...(layout.concepts[k] ?? []), ...v])];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (layout.followFolders) followFolders(layout);
|
|
97
|
+
writeFileSync(file, JSON.stringify(layout, null, 2) + '\n');
|
|
98
|
+
return layout;
|
|
99
|
+
}
|
package/lib/logs.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// claude-packs logs: which hooks earn their place. Reads every log, rotated ones included, and joins each refusal to its verdict.
|
|
2
|
+
// logs per hook: calls, refusals, right, wrong, not judged, crashes, time
|
|
3
|
+
// logs refusals [--hook <h>] [--open] every refusal with its reason (--open: not judged yet)
|
|
4
|
+
// logs mark <id> right|wrong "<why>" the verdict, added to .claude/logs/verdicts.jsonl; nothing is rewritten
|
|
5
|
+
// logs crashes every crash, and whether it is cleared (KF-40: a crash is no refusal, and has no verdict)
|
|
6
|
+
// logs clear <hook>|all fixed|superseded "<why>" clears the crashes so far, for doctor (KF-18); a later crash is red again
|
|
7
|
+
import { appendFileSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { readHookLog } from '../packs/base/hooks/lib/hook-log.mjs';
|
|
10
|
+
|
|
11
|
+
const DIR = join('.claude', 'logs');
|
|
12
|
+
const HOW = ['fixed', 'superseded'];
|
|
13
|
+
export const ANSWERS = new Set(['0', '2', '-', 'undefined', 'null']); // the codes of hooks.log that are no crash
|
|
14
|
+
|
|
15
|
+
function linesOf(dir, name) {
|
|
16
|
+
const logs = join(dir, DIR);
|
|
17
|
+
let files = [];
|
|
18
|
+
try { files = readdirSync(logs).filter((f) => f === `${name}.jsonl` || (f.startsWith(`${name}-`) && f.endsWith('.jsonl'))).sort(); } catch { return []; }
|
|
19
|
+
return files.flatMap((f) => readFileSync(join(logs, f), 'utf8').split('\n').filter(Boolean).flatMap((l) => { try { return [JSON.parse(l)]; } catch { return []; } }));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// A crash, in its own log or, in an old log, in refusals.jsonl with a code other than 2. Old lines are read as they are, never rewritten.
|
|
23
|
+
const crashesOf = (dir) => [...linesOf(dir, 'crashes'), ...linesOf(dir, 'refusals').filter((r) => r.code !== 2)];
|
|
24
|
+
const refusalsOf = (dir) => linesOf(dir, 'refusals').filter((r) => r.code === 2);
|
|
25
|
+
|
|
26
|
+
// hook -> the time of its last clear ("all" stands for every hook). doctor counts only a crash after it.
|
|
27
|
+
export function clearsOf(dir) {
|
|
28
|
+
const clears = new Map();
|
|
29
|
+
for (const v of linesOf(dir, 'verdicts')) if (v.clear) clears.set(v.clear, v.t);
|
|
30
|
+
return clears;
|
|
31
|
+
}
|
|
32
|
+
export const clearedAt = (clears, hook) => [clears.get(hook), clears.get('all')].filter(Boolean).sort().at(-1) ?? '';
|
|
33
|
+
|
|
34
|
+
function clearCrashes(dir, [, hook, how, ...why], log) {
|
|
35
|
+
if (!hook || !HOW.includes(how) || !why.length) throw new Error('use `logs clear <hook>|all fixed|superseded "<why>"`');
|
|
36
|
+
const logged = readHookLog(dir, { rotated: true }).filter((r) => !ANSWERS.has(String(r.code)));
|
|
37
|
+
if (![...crashesOf(dir), ...logged].some((c) => hook === 'all' || c.hook === hook)) throw new Error(`no crash of ${hook}: \`logs crashes\` lists them`);
|
|
38
|
+
mkdirSync(join(dir, DIR), { recursive: true });
|
|
39
|
+
appendFileSync(join(dir, DIR, 'verdicts.jsonl'), `${JSON.stringify({ clear: hook, how, why: why.join(' '), t: new Date().toISOString() })}\n`);
|
|
40
|
+
log(`${hook}: crashes so far cleared as ${how}`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listCrashes(dir, log) {
|
|
45
|
+
const clears = clearsOf(dir);
|
|
46
|
+
const list = crashesOf(dir);
|
|
47
|
+
for (const c of list) {
|
|
48
|
+
const state = c.t <= clearedAt(clears, c.hook) ? 'cleared' : 'open';
|
|
49
|
+
log(`${c.id} ${c.t} ${c.hook} ${state}\n ${c.file ?? c.command ?? c.tool ?? '-'}\n ${(c.reason ?? '').split('\n')[0]}`);
|
|
50
|
+
}
|
|
51
|
+
log(`${list.length} crash(es)`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const verdictsOf = (dir) => new Map(linesOf(dir, 'verdicts').map((v) => [v.id, v]));
|
|
56
|
+
const middle = (list) => [...list].sort((a, b) => a - b)[Math.floor(list.length / 2)] ?? 0;
|
|
57
|
+
|
|
58
|
+
// A refusal followed by a pass on the same file in the same session: the writer changed something and went on.
|
|
59
|
+
function passedAfter(refusal, events) {
|
|
60
|
+
return events.some((e) => e.t > refusal.t && e.session === refusal.session && e.file && e.file === refusal.file && e.hooks.some((h) => h.hook === refusal.hook && h.code === 0));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function logsSummary(dir) {
|
|
64
|
+
const events = linesOf(dir, 'events');
|
|
65
|
+
const verdicts = verdictsOf(dir);
|
|
66
|
+
const rows = new Map();
|
|
67
|
+
const row = (hook) => { if (!rows.has(hook)) rows.set(hook, { hook, calls: 0, refused: 0, crashed: 0, right: 0, wrong: 0, open: 0, passedAfter: 0, ms: [] }); return rows.get(hook); };
|
|
68
|
+
for (const e of events) for (const h of e.hooks ?? []) { const r = row(h.hook); r.calls += 1; r.ms.push(h.ms); }
|
|
69
|
+
for (const c of crashesOf(dir)) row(c.hook).crashed += 1;
|
|
70
|
+
for (const f of refusalsOf(dir)) {
|
|
71
|
+
const r = row(f.hook);
|
|
72
|
+
r.refused += 1;
|
|
73
|
+
const v = verdicts.get(f.id)?.verdict;
|
|
74
|
+
if (v === 'right') r.right += 1; else if (v === 'wrong') r.wrong += 1; else r.open += 1;
|
|
75
|
+
if (passedAfter(f, events)) r.passedAfter += 1;
|
|
76
|
+
}
|
|
77
|
+
return { first: events[0]?.t, last: events.at(-1)?.t, calls: events.length, rows: [...rows.values()].sort((a, b) => b.refused - a.refused || b.calls - a.calls) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function logsCommand(dir, rest, log = console.log) {
|
|
81
|
+
const [sub] = rest;
|
|
82
|
+
if (sub === 'mark') {
|
|
83
|
+
const [, id, verdict, ...why] = rest;
|
|
84
|
+
if (!id || !['right', 'wrong'].includes(verdict) || !why.length) throw new Error('use `logs mark <id> right|wrong "<why>"`');
|
|
85
|
+
if (crashesOf(dir).some((r) => r.id === id)) throw new Error(`${id} is a crash, which refused nothing: \`logs clear <hook> fixed|superseded "<why>"\` clears it`);
|
|
86
|
+
if (!refusalsOf(dir).some((r) => r.id === id)) throw new Error(`no refusal ${id}: \`logs refusals\` lists them`);
|
|
87
|
+
mkdirSync(join(dir, DIR), { recursive: true });
|
|
88
|
+
appendFileSync(join(dir, DIR, 'verdicts.jsonl'), `${JSON.stringify({ id, verdict, why: why.join(' '), t: new Date().toISOString() })}\n`);
|
|
89
|
+
log(`${id}: ${verdict}`);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
if (sub === 'crashes') return listCrashes(dir, log);
|
|
93
|
+
if (sub === 'clear') return clearCrashes(dir, rest, log);
|
|
94
|
+
if (sub === 'refusals') {
|
|
95
|
+
const hook = rest.includes('--hook') ? rest[rest.indexOf('--hook') + 1] : null;
|
|
96
|
+
const verdicts = verdictsOf(dir);
|
|
97
|
+
const list = refusalsOf(dir).filter((r) => (!hook || r.hook === hook) && (!rest.includes('--open') || !verdicts.has(r.id)));
|
|
98
|
+
for (const r of list) log(`${r.id} ${r.t} ${r.hook} ${verdicts.get(r.id)?.verdict ?? 'not judged'}\n ${r.file ?? r.command ?? r.tool ?? '-'}\n ${(r.reason ?? '').split('\n')[0]}`);
|
|
99
|
+
log(`${list.length} refusal(s)`);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
const s = logsSummary(dir);
|
|
103
|
+
if (!s.calls) { log('no calls logged yet (.claude/logs/ fills as the hooks run)'); return 0; }
|
|
104
|
+
log(`${s.calls} calls, ${s.first} to ${s.last}`);
|
|
105
|
+
log(`${'hook'.padEnd(24)}${'calls'.padStart(7)}${'refused'.padStart(8)}${'right'.padStart(6)}${'wrong'.padStart(6)}${'open'.padStart(6)}${'fixed'.padStart(6)}${'crash'.padStart(6)}${'ms'.padStart(6)}`);
|
|
106
|
+
for (const r of s.rows) log(`${r.hook.padEnd(24)}${String(r.calls).padStart(7)}${String(r.refused).padStart(8)}${String(r.right).padStart(6)}${String(r.wrong).padStart(6)}${String(r.open).padStart(6)}${String(r.passedAfter).padStart(6)}${String(r.crashed).padStart(6)}${String(middle(r.ms)).padStart(6)}`);
|
|
107
|
+
log('fixed: the same file passed the same hook later in that session. Judge the open ones: `logs refusals --open`, then `logs mark <id> right|wrong "<why>"`.');
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
package/lib/manifest.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Tracks every file the packs installed, with its hash, so updates never overwrite a file
|
|
2
|
+
// The user changed and files removed from a pack are cleaned up.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const LOCK = 'claude-packs.lock.json';
|
|
8
|
+
|
|
9
|
+
export const hashFile = (path) => createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
10
|
+
|
|
11
|
+
export function readLock(claudeDir) {
|
|
12
|
+
const p = join(claudeDir, LOCK);
|
|
13
|
+
return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : { packs: [], files: {} };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function writeLock(claudeDir, lock) {
|
|
17
|
+
writeFileSync(join(claudeDir, LOCK), JSON.stringify(lock, null, 2) + '\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A managed file is "modified" when it exists and no longer matches the hash we wrote.
|
|
21
|
+
export function isModified(projectDir, rel, lock) {
|
|
22
|
+
const abs = join(projectDir, rel);
|
|
23
|
+
return existsSync(abs) && lock.files[rel] !== undefined && hashFile(abs) !== lock.files[rel];
|
|
24
|
+
}
|
package/lib/new.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// `claude-packs new <name>`: a new project on the house structure, so the rules fire from the first file (it was the
|
|
2
|
+
// global new-project skill; it is a kit command now, and the global part stays at three pieces).
|
|
3
|
+
// - the folder, the house files (CLAUDE.md, build.md, bugs.md, workflow-bugs.md, docs/versions.md, .gitignore),
|
|
4
|
+
// - a git repo: the scaffold is the first commit on main, and work goes on from a `work/` branch (the house branch flow),
|
|
5
|
+
// - the kit installed into it (`init` with the packs named, or a profile), then doctor.
|
|
6
|
+
// A synth or effects unit is made by synthkit's new_synth.py first; `init` (or `new` on an empty folder) follows on it.
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { doctor } from './doctor.mjs';
|
|
11
|
+
import { sync } from './sync.mjs';
|
|
12
|
+
import { ensureShape } from './shape-cli.mjs';
|
|
13
|
+
|
|
14
|
+
const today = () => new Date().toISOString().slice(0, 10);
|
|
15
|
+
|
|
16
|
+
function files(name) {
|
|
17
|
+
return {
|
|
18
|
+
'CLAUDE.md': `# ${name}\n\nRead \`build.md\` at the start of every session; nothing is worked on unless it is IN PROGRESS there. Every bug is logged in \`bugs.md\` (and every workflow fault in \`workflow-bugs.md\`) before it is fixed. One branch per step (the \`build-step\` skill), never a commit on main.\n`,
|
|
19
|
+
'build.md': `# ${name}: the build\n\n## Rules for this file\n\n- A step is ticked only when its "done when" was measured or seen in the opened app.\n- Every step is built on its own branch and merged when done; its start and finish are in the log.\n\n## Where we are\n\nStarted ${today()}.\n\n## In progress\n\n## Steps\n\n## Log\n`,
|
|
20
|
+
'bugs.md': `# ${name}: bugs\n\nLogged first, fixed second. A fixed bug moves to Fixed with its cause, the fix, the date and the gate that catches it.\n\n## Open\n\n## Fixed\n`,
|
|
21
|
+
'workflow-bugs.md': `# ${name}: workflow bugs\n\nA fault in the workflow (a hook, a tool, a test run) is logged here before it is fixed.\n\n## Open\n\n## Fixed\n`,
|
|
22
|
+
'docs/versions.md': '| name | latest stable | checked | source | used | decision |\n|---|---|---|---|---|---|\n',
|
|
23
|
+
'.gitignore': 'build/\nout/\n.DS_Store\n',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function newProject(parent, name, { packs = ['audio-core'], seedLayout = null, config = null, log = console.log } = {}) {
|
|
28
|
+
if (!name || !/^[\w.-]+$/.test(name)) throw new Error('name the project: `npx --no-install @thedecipherist/claude-packs new <name>` (letters, digits, . _ -)');
|
|
29
|
+
const dir = join(parent, name);
|
|
30
|
+
if (existsSync(dir) && readdirSync(dir).length) throw new Error(`${dir} already exists and is not empty; use \`npx --no-install @thedecipherist/claude-packs init\` there`);
|
|
31
|
+
mkdirSync(join(dir, 'docs'), { recursive: true });
|
|
32
|
+
for (const [rel, text] of Object.entries(files(name))) writeFileSync(join(dir, rel), text);
|
|
33
|
+
const git = (...a) => execFileSync('git', a, { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
34
|
+
git('init', '-q', '-b', 'main');
|
|
35
|
+
await sync(dir, packs, { mkdirs: true, seedLayout, config, log });
|
|
36
|
+
ensureShape(dir); // the shape law: every concept is written down as ONE or MANY before it is built
|
|
37
|
+
try {
|
|
38
|
+
// by name: the house files, .claude/, and every file the kit installed elsewhere (nested skills, .clang-tidy)
|
|
39
|
+
const installed = Object.keys(JSON.parse(readFileSync(join(dir, '.claude', 'claude-packs.lock.json'), 'utf8')).files);
|
|
40
|
+
git('add', '--', ...Object.keys(files(name)), 'design/shape.json', '.claude', ...installed, ...(existsSync(join(dir, '.clang-tidy')) ? ['.clang-tidy'] : []));
|
|
41
|
+
git('commit', '-q', '-m', `${name}: the house structure and claude-packs`);
|
|
42
|
+
git('switch', '-q', '-c', 'work/start');
|
|
43
|
+
log(` git: the scaffold is the first commit on main; you are on work/start`);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
log(` git: the scaffold is not committed (${String(e.stderr ?? e.message).trim().split('\n')[0]}); commit it, then work on a work/ branch`);
|
|
46
|
+
}
|
|
47
|
+
log(`New project: ${dir}`);
|
|
48
|
+
return { dir, code: await doctor(dir, log, { installing: true }) };
|
|
49
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// What doctor prints for the making and once-only hooks (0.9.0 items 6 and 9), and `claude-packs dup scan`.
|
|
2
|
+
// onceOnlyDoctor(projectDir, log) -> [problem] the making text's cost per turn; the repo's duplicate count, which can
|
|
3
|
+
// only go down: the lowest count seen is kept in .claude/state/duplicates.json and a higher one is a problem.
|
|
4
|
+
// dupScan(dir, log) -> exit code every duplicate, listed by file (both sides of each), and the count.
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { findJscpd, repoClones } from '../packs/base/hooks/lib/dup.mjs';
|
|
9
|
+
import { readShape } from '../packs/base/hooks/lib/shape.mjs';
|
|
10
|
+
|
|
11
|
+
const layoutOf = (dir) => { try { return JSON.parse(readFileSync(join(dir, '.claude', 'layout.json'), 'utf8')); } catch { return {}; } };
|
|
12
|
+
|
|
13
|
+
// The project's detector, else the kit's own, for this process and every selftest it starts.
|
|
14
|
+
export function useJscpd(dir) {
|
|
15
|
+
const found = findJscpd([dir]);
|
|
16
|
+
if (found) process.env.CLAUDE_PACKS_JSCPD ??= found;
|
|
17
|
+
return found;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function onceOnlyDoctor(projectDir, log = console.log) {
|
|
21
|
+
const problems = [];
|
|
22
|
+
const hooks = join(projectDir, '.claude', 'hooks');
|
|
23
|
+
if (existsSync(join(hooks, 'making.mjs'))) {
|
|
24
|
+
const r = spawnSync(process.execPath, [join(hooks, 'making.mjs'), '--cost'], { encoding: 'utf8', env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir } });
|
|
25
|
+
log(` ${(r.stdout || `making: --cost failed (${r.stderr.split('\n')[0]})`).trim()}`);
|
|
26
|
+
}
|
|
27
|
+
if (!existsSync(join(hooks, 'once-only.mjs'))) return problems;
|
|
28
|
+
if (!useJscpd(projectDir)) return [`once-only has no detector: jscpd not found (an optional dependency of the kit; npm install it, or set CLAUDE_PACKS_JSCPD)`];
|
|
29
|
+
const r = repoClones(projectDir, { layout: layoutOf(projectDir), shape: readShape(projectDir) });
|
|
30
|
+
if (r.error) return [`once-only: ${r.error}`];
|
|
31
|
+
const n = r.clones.length;
|
|
32
|
+
const file = join(projectDir, '.claude', 'state', 'duplicates.json');
|
|
33
|
+
let st = null;
|
|
34
|
+
try { st = JSON.parse(readFileSync(file, 'utf8')); } catch { /* first count */ }
|
|
35
|
+
log(` duplicates: ${n} (jscpd, ${r.sources} sources, ${r.ms} ms; \`npx --no-install @thedecipherist/claude-packs dup scan\` lists them)${st ? `, lowest seen ${st.lowest}` : ''}`);
|
|
36
|
+
if (st && n > st.lowest) problems.push(`duplicates went up from ${st.lowest} to ${n}: the count only goes down (\`npx --no-install @thedecipherist/claude-packs dup scan\` lists them)`);
|
|
37
|
+
if (!st || n < st.lowest) {
|
|
38
|
+
mkdirSync(join(projectDir, '.claude', 'state'), { recursive: true });
|
|
39
|
+
writeFileSync(file, `${JSON.stringify({ lowest: n, at: new Date().toISOString() }, null, 2)}\n`);
|
|
40
|
+
}
|
|
41
|
+
return problems;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function dupScan(dir, log = console.log) {
|
|
45
|
+
if (!useJscpd(dir)) { log('jscpd not found (an optional dependency of the kit; npm install it, or set CLAUDE_PACKS_JSCPD)'); return 2; }
|
|
46
|
+
const r = repoClones(dir, { layout: layoutOf(dir), shape: readShape(dir) });
|
|
47
|
+
if (r.error) { log(r.error); return 2; }
|
|
48
|
+
const byFile = new Map();
|
|
49
|
+
const add = (s, o, c) => { if (!byFile.has(s.file)) byFile.set(s.file, []); byFile.get(s.file).push(` ${s.start}-${s.end} same as ${o.file}:${o.start}-${o.end} (${c.lines} lines, ${c.tokens} tokens)`); };
|
|
50
|
+
for (const c of r.clones) { add(c.a, c.b, c); if (c.b.file !== c.a.file) add(c.b, c.a, c); }
|
|
51
|
+
for (const f of [...byFile.keys()].sort()) { log(f); for (const l of byFile.get(f).sort((x, y) => parseInt(x, 10) - parseInt(y, 10))) log(l); }
|
|
52
|
+
log(`${r.clones.length} duplicate(s) in ${byFile.size} file(s) (${r.sources} sources scanned; both sides inside copy trees left out)`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|