@thedecipherist/claude-packs 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +680 -0
- package/LICENSE +21 -0
- package/README.md +316 -2
- package/bin/claude-packs.mjs +204 -0
- package/configurations/base/config.json +85 -0
- package/configurations/cpp/config.json +92 -0
- package/configurations/full-stack/config.json +152 -0
- package/configurations/go/config.json +96 -0
- package/configurations/node/config.json +100 -0
- package/configurations/python/config.json +97 -0
- package/configurations/synth-creator/config.json +148 -0
- package/global/hooks/gnu-shell.mjs +131 -0
- package/global/hooks/lib/program-read.mjs +196 -0
- package/global/hooks/lib/program-writes.mjs +177 -0
- package/global/hooks/lib/shell-writes.mjs +293 -0
- package/global/hooks/shell-traps.mjs +79 -0
- package/global/hooks/version-pin-guard.mjs +280 -0
- package/global/skills/latest-versions/SKILL.md +49 -0
- package/lib/backup.mjs +34 -0
- package/lib/claude-md.mjs +58 -0
- package/lib/config-cli.mjs +36 -0
- package/lib/configs.mjs +145 -0
- package/lib/coverage.mjs +86 -0
- package/lib/custom-packs.mjs +93 -0
- package/lib/doctor.mjs +115 -0
- package/lib/fix-plan.mjs +71 -0
- package/lib/git-tokens.mjs +64 -0
- package/lib/global.mjs +125 -0
- package/lib/hook-proof.mjs +28 -0
- package/lib/hook-timing.mjs +42 -0
- package/lib/hover-gate.mjs +29 -0
- package/lib/kit-lock.mjs +149 -0
- package/lib/layout.mjs +75 -0
- package/lib/logs.mjs +69 -0
- package/lib/manifest.mjs +24 -0
- package/lib/new.mjs +49 -0
- package/lib/once-only-report.mjs +54 -0
- package/lib/pack-manifest.mjs +20 -0
- package/lib/packs.mjs +107 -0
- package/lib/planner.mjs +59 -0
- package/lib/repeats.mjs +65 -0
- package/lib/repo-weight.mjs +28 -0
- package/lib/rules.mjs +40 -0
- package/lib/scan-detect.mjs +94 -0
- package/lib/scan.mjs +168 -0
- package/lib/selftest-cache.mjs +39 -0
- package/lib/settings.mjs +138 -0
- package/lib/shape-cli.mjs +76 -0
- package/lib/shape.mjs +4 -0
- package/lib/suggest.mjs +18 -0
- package/lib/switches.mjs +44 -0
- package/lib/symmetry-doctor.mjs +26 -0
- package/lib/sync.mjs +130 -0
- package/package.json +34 -4
- package/packs/ai/checks/boundary-computes-its-own-hash.json +30 -0
- package/packs/ai/checks/model-call-needs-boundary.json +29 -0
- package/packs/ai/checks/model-id-from-config.json +28 -0
- package/packs/ai/checks/model-output-validated.json +26 -0
- package/packs/ai/pack.json +38 -0
- package/packs/ai/rules/ai-input-sha-boundary.md +31 -0
- package/packs/ai/rules/llm-pipeline-security.md +26 -0
- package/packs/audio-core/checks/denormals-not-in-prepare.json +12 -0
- package/packs/audio-core/checks/dsp-without-apvts.json +12 -0
- package/packs/audio-core/checks/float-phase-or-rate.json +12 -0
- package/packs/audio-core/checks/juce-snap-to-zero-off.json +12 -0
- package/packs/audio-core/checks/keep-werror.json +12 -0
- package/packs/audio-core/checks/no-fast-math.json +14 -0
- package/packs/audio-core/checks/no-locale-change.json +12 -0
- package/packs/audio-core/checks/no-march-native.json +14 -0
- package/packs/audio-core/clang-tidy +28 -0
- package/packs/audio-core/hooks/lib/realtime-checks.mjs +56 -0
- package/packs/audio-core/hooks/param-id-lock.mjs +129 -0
- package/packs/audio-core/hooks/realtime-guard.mjs +67 -0
- package/packs/audio-core/pack.json +187 -0
- package/packs/audio-core/rules/audio-thread-realtime-safe.md +27 -0
- package/packs/audio-core/rules/audio-ui-thread-handoff.md +21 -0
- package/packs/audio-core/rules/boundary-tests-cross-the-boundary.md +16 -0
- package/packs/audio-core/rules/build-flags-safe.md +20 -0
- package/packs/audio-core/rules/cached-parameter-pointers.md +28 -0
- package/packs/audio-core/rules/clang-tidy-core-guidelines.md +21 -0
- package/packs/audio-core/rules/control-reaches-every-surface.md +23 -0
- package/packs/audio-core/rules/cpu-worst-case-per-block.md +26 -0
- package/packs/audio-core/rules/dc-block-after-asymmetry.md +22 -0
- package/packs/audio-core/rules/discrete-params-snap.md +19 -0
- package/packs/audio-core/rules/double-where-precision-matters.md +20 -0
- package/packs/audio-core/rules/filters-stable-under-modulation.md +29 -0
- package/packs/audio-core/rules/host-parameters-at-scale.md +24 -0
- package/packs/audio-core/rules/house-control-behaviours.md +34 -0
- package/packs/audio-core/rules/math-domain-guards.md +19 -0
- package/packs/audio-core/rules/modulated-delay-interpolation.md +22 -0
- package/packs/audio-core/rules/multiband-phase-coherent.md +22 -0
- package/packs/audio-core/rules/nonlinear-stages-antialiased.md +28 -0
- package/packs/audio-core/rules/note-ledger-and-pedals.md +22 -0
- package/packs/audio-core/rules/one-formula-per-value.md +20 -0
- package/packs/audio-core/rules/optimisations-are-null-tested.md +23 -0
- package/packs/audio-core/rules/oscillators-bandlimited.md +23 -0
- package/packs/audio-core/rules/output-limiter.md +23 -0
- package/packs/audio-core/rules/parameter-ids-never-change.md +20 -0
- package/packs/audio-core/rules/params-and-state-thread-safe.md +19 -0
- package/packs/audio-core/rules/params-snapshot-per-block.md +21 -0
- package/packs/audio-core/rules/patch-excludes-performance-controls.md +18 -0
- package/packs/audio-core/rules/per-sample-code-inlinable.md +22 -0
- package/packs/audio-core/rules/prepare-is-repeatable.md +23 -0
- package/packs/audio-core/rules/recursive-state-nan-guard.md +20 -0
- package/packs/audio-core/rules/reverb-loop-gain-bounded.md +24 -0
- package/packs/audio-core/rules/sample-accurate-midi.md +22 -0
- package/packs/audio-core/rules/sample-rate-and-block-size-from-host.md +23 -0
- package/packs/audio-core/rules/scoped-no-denormals.md +26 -0
- package/packs/audio-core/rules/smooth-continuous-parameters.md +22 -0
- package/packs/audio-core/rules/state-load-versioned-and-safe.md +22 -0
- package/packs/audio-core/rules/straight-on-and-aligned.md +21 -0
- package/packs/audio-core/rules/ui-matches-reference-look.md +21 -0
- package/packs/audio-core/rules/undo-one-gesture-one-step.md +21 -0
- package/packs/audio-core/rules/voice-steal-fades.md +21 -0
- package/packs/audio-core/rules/voice-sum-headroom.md +20 -0
- package/packs/audio-core/skills/analyse-reference-look/SKILL.md +21 -0
- package/packs/audio-core/skills/copy-and-prove/SKILL.md +98 -0
- package/packs/audio-core/skills/juce-realtime-audit/SKILL.md +22 -0
- package/packs/audio-core/skills/measure-tone/SKILL.md +20 -0
- package/packs/audio-core/skills/new-panel-control/SKILL.md +15 -0
- package/packs/audio-core/skills/prove-control/SKILL.md +23 -0
- package/packs/audio-core/skills/trace-parameter/SKILL.md +29 -0
- package/packs/audio-core/tools/lib/spectrum.mjs +112 -0
- package/packs/audio-core/tools/lib/wav.mjs +41 -0
- package/packs/audio-core/tools/tone-report.mjs +47 -0
- package/packs/base/checks/field-map-over-if-runs.json +25 -0
- package/packs/base/checks/lookup-over-if-chains.json +25 -0
- package/packs/base/checks/lookup-over-ternary-chains.json +20 -0
- package/packs/base/checks/no-code-after-midline-comment.json +12 -0
- package/packs/base/checks/one-walk-per-template.json +23 -0
- package/packs/base/checks/stub-reports-success.json +31 -0
- package/packs/base/checks/test-skip-on-failed-setup.json +17 -0
- package/packs/base/hooks/brief-guard.mjs +96 -0
- package/packs/base/hooks/code-shape.mjs +95 -0
- package/packs/base/hooks/dispatch-once.mjs +87 -0
- package/packs/base/hooks/file-size-guard.mjs +61 -0
- package/packs/base/hooks/folder-law.mjs +74 -0
- package/packs/base/hooks/git-guard.mjs +289 -0
- package/packs/base/hooks/lesson-checks.mjs +174 -0
- package/packs/base/hooks/lib/apply-edit.mjs +20 -0
- package/packs/base/hooks/lib/code-shapes.mjs +137 -0
- package/packs/base/hooks/lib/content.mjs +14 -0
- package/packs/base/hooks/lib/cpp-scan.mjs +94 -0
- package/packs/base/hooks/lib/dup-index.mjs +199 -0
- package/packs/base/hooks/lib/dup.mjs +127 -0
- package/packs/base/hooks/lib/elements.mjs +87 -0
- package/packs/base/hooks/lib/event-log.mjs +67 -0
- package/packs/base/hooks/lib/function-shapes.mjs +90 -0
- package/packs/base/hooks/lib/git-adds.mjs +20 -0
- package/packs/base/hooks/lib/git-guard-selftest.mjs +99 -0
- package/packs/base/hooks/lib/glob.mjs +33 -0
- package/packs/base/hooks/lib/hook-log.mjs +100 -0
- package/packs/base/hooks/lib/house.py +77 -0
- package/packs/base/hooks/lib/languages.mjs +66 -0
- package/packs/base/hooks/lib/layout.mjs +54 -0
- package/packs/base/hooks/lib/making-check-selftest.mjs +109 -0
- package/packs/base/hooks/lib/near-copies.mjs +76 -0
- package/packs/base/hooks/lib/program-read.mjs +196 -0
- package/packs/base/hooks/lib/program-writes.mjs +177 -0
- package/packs/base/hooks/lib/project.mjs +81 -0
- package/packs/base/hooks/lib/prose.mjs +11 -0
- package/packs/base/hooks/lib/pushed.mjs +201 -0
- package/packs/base/hooks/lib/repo-index.mjs +52 -0
- package/packs/base/hooks/lib/secret-patterns.mjs +18 -0
- package/packs/base/hooks/lib/selftest.mjs +45 -0
- package/packs/base/hooks/lib/shape-code.mjs +126 -0
- package/packs/base/hooks/lib/shape.mjs +294 -0
- package/packs/base/hooks/lib/shell-writes-selftest.mjs +65 -0
- package/packs/base/hooks/lib/shell-writes.mjs +293 -0
- package/packs/base/hooks/lib/turn.mjs +81 -0
- package/packs/base/hooks/lib/writes.mjs +71 -0
- package/packs/base/hooks/making-check.mjs +243 -0
- package/packs/base/hooks/making.mjs +106 -0
- package/packs/base/hooks/once-only.mjs +150 -0
- package/packs/base/hooks/placement-law.mjs +84 -0
- package/packs/base/hooks/repo-litter.mjs +75 -0
- package/packs/base/hooks/reuse-hint.mjs +75 -0
- package/packs/base/hooks/run-hooks.mjs +132 -0
- package/packs/base/hooks/secrets-guard.mjs +99 -0
- package/packs/base/hooks/shape-law.mjs +133 -0
- package/packs/base/hooks/suite-once.mjs +80 -0
- package/packs/base/hooks/template-law.mjs +148 -0
- package/packs/base/hooks/untrusted-boundary.mjs +53 -0
- package/packs/base/pack.json +214 -0
- package/packs/base/rules/a-control-is-seen-to-fire.md +27 -0
- package/packs/base/rules/behaviour-lives-with-its-kind.md +51 -0
- package/packs/base/rules/bug-fix-starts-red.md +25 -0
- package/packs/base/rules/field-map-over-if-runs.md +47 -0
- package/packs/base/rules/finished-work-lands.md +17 -0
- package/packs/base/rules/git-right-first-time.md +19 -0
- package/packs/base/rules/instances-by-index-identities-by-id.md +54 -0
- package/packs/base/rules/lookup-over-if-chains.md +49 -0
- package/packs/base/rules/making-line-first.md +14 -0
- package/packs/base/rules/open-items-tagged.md +17 -0
- package/packs/base/rules/plan-and-logs.md +22 -0
- package/packs/base/rules/references-use-anchors.md +19 -0
- package/packs/base/rules/refusals-are-judged.md +18 -0
- package/packs/base/rules/reuse-before-writing.md +15 -0
- package/packs/base/rules/secrets-never-committed.md +16 -0
- package/packs/base/rules/skill-writing.md +23 -0
- package/packs/base/rules/split-before-the-limit.md +14 -0
- package/packs/base/rules/subagent-dispatch.md +26 -0
- package/packs/base/rules/test-only-what-changed.md +17 -0
- package/packs/base/rules/test-the-template-once.md +22 -0
- package/packs/base/rules/untrusted-content-boundary.md +15 -0
- package/packs/base/skills/add-lesson-check/SKILL.md +26 -0
- package/packs/base/skills/fix-from-scan/SKILL.md +25 -0
- package/packs/base/skills/land-branch/SKILL.md +24 -0
- package/packs/base/skills/make-it-a-template/SKILL.md +23 -0
- package/packs/base/skills/pdf-to-md/SKILL.md +25 -0
- package/packs/base/skills/pick-tests/SKILL.md +20 -0
- package/packs/base/skills/pressure-test-skill/SKILL.md +23 -0
- package/packs/base/skills/prove-instances-identical/SKILL.md +37 -0
- package/packs/base/tools/shape-symmetry-selftest.mjs +66 -0
- package/packs/base/tools/shape-symmetry.mjs +178 -0
- package/packs/base/tools/test-verdict.mjs +75 -0
- package/packs/ci/checks/ci-permissions-stated.json +18 -0
- package/packs/ci/checks/ci-tools-pinned.json +14 -0
- package/packs/ci/pack.json +21 -0
- package/packs/ci/rules/ci-covers-what-ships.md +20 -0
- package/packs/ci/rules/generated-files-have-no-diff.md +15 -0
- package/packs/content-site/checks/captcha-is-not-constant.json +27 -0
- package/packs/content-site/checks/img-has-size.json +20 -0
- package/packs/content-site/checks/page-head-complete.json +26 -0
- package/packs/content-site/pack.json +22 -0
- package/packs/content-site/rules/content-has-one-source.md +23 -0
- package/packs/content-site/rules/media-is-sized.md +24 -0
- package/packs/content-site/rules/page-head-is-complete.md +26 -0
- package/packs/content-site/rules/public-forms-are-defended.md +27 -0
- package/packs/content-site/skills/publish-a-page/SKILL.md +21 -0
- package/packs/cpp/checks/cpp-clamp-before-cast.json +16 -0
- package/packs/cpp/checks/cpp-find-with-the-library.json +12 -0
- package/packs/cpp/checks/cpp-hardened-test-build.json +18 -0
- package/packs/cpp/checks/cpp-m-pi-not-standard.json +23 -0
- package/packs/cpp/checks/cpp-no-atoi.json +16 -0
- package/packs/cpp/checks/cpp-no-const-cast.json +16 -0
- package/packs/cpp/checks/cpp-no-float-loop-counter.json +16 -0
- package/packs/cpp/checks/cpp-no-getenv-in-product.json +22 -0
- package/packs/cpp/checks/cpp-no-mutable-global.json +20 -0
- package/packs/cpp/checks/cpp-no-mutable-local-static.json +23 -0
- package/packs/cpp/checks/cpp-no-unchecked-fwrite.json +17 -0
- package/packs/cpp/checks/cpp-no-using-namespace-in-header.json +19 -0
- package/packs/cpp/checks/cpp-no-zero-size-array.json +23 -0
- package/packs/cpp/checks/cpp-numbers-pi.json +16 -0
- package/packs/cpp/pack.json +22 -0
- package/packs/cpp/rules/cpp-absent-and-failed-are-types.md +24 -0
- package/packs/cpp/rules/cpp-builds-everywhere.md +26 -0
- package/packs/cpp/rules/cpp-faults-only-in-the-test-build.md +21 -0
- package/packs/cpp/rules/cpp-find-with-the-library.md +19 -0
- package/packs/cpp/rules/cpp-gates-share-one-reader.md +22 -0
- package/packs/cpp/rules/cpp-headers-declare.md +22 -0
- package/packs/cpp/rules/cpp-numbers-and-casts.md +22 -0
- package/packs/cpp/rules/cpp-repeats-become-templates.md +33 -0
- package/packs/cpp/rules/cpp-static-analysis-runs.md +22 -0
- package/packs/cpp/rules/cpp-thread-contracts-are-code.md +23 -0
- package/packs/cpp/skills/cpp-extract-template/SKILL.md +38 -0
- package/packs/docker/checks/alpine-with-native-build.json +13 -0
- package/packs/docker/checks/compose-db-needs-auth.json +14 -0
- package/packs/docker/checks/compose-image-pinned.json +14 -0
- package/packs/docker/checks/compose-no-hardcoded-ip.json +12 -0
- package/packs/docker/checks/compose-no-host-bind-for-data.json +18 -0
- package/packs/docker/checks/compose-no-plaintext-secret.json +13 -0
- package/packs/docker/checks/compose-no-swarm-ignored-keys.json +13 -0
- package/packs/docker/checks/compose-overlay-not-bridge.json +13 -0
- package/packs/docker/checks/compose-service-needs-healthcheck.json +11 -0
- package/packs/docker/checks/compose-service-needs-init.json +11 -0
- package/packs/docker/checks/copy-needs-chown-with-user.json +17 -0
- package/packs/docker/checks/copy-source-before-deps.json +13 -0
- package/packs/docker/checks/docker-exec-form.json +16 -0
- package/packs/docker/checks/dockerfile-needs-healthcheck.json +17 -0
- package/packs/docker/checks/dockerfile-needs-user.json +21 -0
- package/packs/docker/checks/dockerignore-has-the-basics.json +21 -0
- package/packs/docker/checks/gitattributes-forces-lf.json +17 -0
- package/packs/docker/checks/health-checks-nothing.json +25 -0
- package/packs/docker/checks/no-crlf-in-scripts.json +15 -0
- package/packs/docker/checks/no-latest-base.json +13 -0
- package/packs/docker/checks/no-secret-in-image.json +13 -0
- package/packs/docker/checks/no-sensitive-file-copy.json +13 -0
- package/packs/docker/checks/node-env-before-install.json +13 -0
- package/packs/docker/checks/npm-ci-not-install.json +13 -0
- package/packs/docker/pack.json +46 -0
- package/packs/docker/rules/compose-pinned-and-closed.md +21 -0
- package/packs/docker/rules/compose-swarm-safe.md +26 -0
- package/packs/docker/rules/docker-exec-form.md +19 -0
- package/packs/docker/rules/dockerfile-multistage.md +13 -0
- package/packs/docker/rules/dockerfile-production.md +25 -0
- package/packs/docker/skills/docker-diagnose/SKILL.md +23 -0
- package/packs/dynamic-data/checks/draft-on-the-server.json +27 -0
- package/packs/dynamic-data/checks/guard-does-its-job.json +28 -0
- package/packs/dynamic-data/checks/key-is-not-a-display-name.json +28 -0
- package/packs/dynamic-data/checks/no-code-from-data.json +29 -0
- package/packs/dynamic-data/checks/no-collection-from-request.json +27 -0
- package/packs/dynamic-data/checks/no-pipeline-from-stored-text.json +27 -0
- package/packs/dynamic-data/checks/no-regexp-from-stored-config.json +27 -0
- package/packs/dynamic-data/checks/no-template-from-stored-text.json +27 -0
- package/packs/dynamic-data/checks/save-takes-named-paths.json +27 -0
- package/packs/dynamic-data/pack.json +23 -0
- package/packs/dynamic-data/rules/a-list-is-a-saved-query.md +23 -0
- package/packs/dynamic-data/rules/definitions-are-validated-data.md +24 -0
- package/packs/dynamic-data/rules/every-dynamic-surface-has-a-boundary.md +29 -0
- package/packs/dynamic-data/rules/fields-have-owners.md +22 -0
- package/packs/dynamic-data/rules/imports-drop-nothing.md +26 -0
- package/packs/dynamic-data/rules/one-definition-many-surfaces.md +24 -0
- package/packs/dynamic-data/rules/one-fact-one-place.md +28 -0
- package/packs/dynamic-data/rules/rules-live-in-the-engine.md +29 -0
- package/packs/dynamic-data/rules/saves-are-patches.md +28 -0
- package/packs/dynamic-data/rules/tenant-costs-one-document.md +22 -0
- package/packs/dynamic-data/skills/design-a-definition/SKILL.md +24 -0
- package/packs/fastify/checks/api-routes-versioned.json +28 -0
- package/packs/fastify/checks/cookie-host-prefix.json +24 -0
- package/packs/fastify/checks/hijack-needs-try.json +28 -0
- package/packs/fastify/checks/no-cast-at-route-boundary.json +24 -0
- package/packs/fastify/checks/no-db-in-route-file.json +28 -0
- package/packs/fastify/checks/no-err-message-to-client.json +24 -0
- package/packs/fastify/checks/no-regexp-from-input.json +24 -0
- package/packs/fastify/checks/no-trust-proxy-true.json +25 -0
- package/packs/fastify/checks/one-error-body.json +26 -0
- package/packs/fastify/checks/origin-from-config-not-host.json +31 -0
- package/packs/fastify/checks/password-needs-slow-hash.json +30 -0
- package/packs/fastify/checks/sec-headers-present.json +28 -0
- package/packs/fastify/checks/secrets-compared-constant-time.json +24 -0
- package/packs/fastify/checks/sse-needs-error-listener.json +28 -0
- package/packs/fastify/checks/user-bytes-need-disposition.json +29 -0
- package/packs/fastify/pack.json +41 -0
- package/packs/fastify/rules/credentials-and-input.md +23 -0
- package/packs/fastify/rules/security-one-switch.md +28 -0
- package/packs/fastify/rules/streams-and-hijacks.md +22 -0
- package/packs/fastify/rules/tests-use-inject.md +13 -0
- package/packs/fastify/rules/three-layers.md +20 -0
- package/packs/fastify/rules/trust-config-not-headers.md +24 -0
- package/packs/full-stack/checks/no-express-no-vite.json +23 -0
- package/packs/full-stack/hooks/stack-guard.mjs +90 -0
- package/packs/full-stack/pack.json +76 -0
- package/packs/full-stack/rules/stack-is-fastify-and-nextjs.md +20 -0
- package/packs/go/checks/go-exec-result-ignored.json +15 -0
- package/packs/go/checks/go-unmarshal-error-ignored.json +15 -0
- package/packs/go/pack.json +21 -0
- package/packs/go/rules/go-errors-are-handled.md +22 -0
- package/packs/gpu-painter/checks/dawn-backends-and-validation.json +9 -0
- package/packs/gpu-painter/checks/dawn-no-spontaneous.json +10 -0
- package/packs/gpu-painter/checks/dawn-present-on-render-thread.json +17 -0
- package/packs/gpu-painter/checks/gpu-no-frame-stalls.json +12 -0
- package/packs/gpu-painter/checks/no-opengl-link.json +11 -0
- package/packs/gpu-painter/checks/no-opengl.json +13 -0
- package/packs/gpu-painter/hooks/browser-guard.py +196 -0
- package/packs/gpu-painter/pack.json +91 -0
- package/packs/gpu-painter/rules/dawn-build-and-shaders.md +27 -0
- package/packs/gpu-painter/rules/dawn-device-and-render-thread.md +26 -0
- package/packs/gpu-painter/rules/gpu-antialiasing.md +20 -0
- package/packs/gpu-painter/rules/gpu-blend-order.md +22 -0
- package/packs/gpu-painter/rules/gpu-bloom.md +20 -0
- package/packs/gpu-painter/rules/gpu-frame-budget.md +25 -0
- package/packs/gpu-painter/rules/gpu-frame-pacing-and-power.md +27 -0
- package/packs/gpu-painter/rules/gpu-frame-work.md +30 -0
- package/packs/gpu-painter/rules/gpu-golden-images.md +21 -0
- package/packs/gpu-painter/rules/gpu-hdr-and-tonemap.md +23 -0
- package/packs/gpu-painter/rules/gpu-picking-on-cpu.md +19 -0
- package/packs/gpu-painter/rules/gpu-render-lifecycle.md +25 -0
- package/packs/gpu-painter/rules/gpu-text-atlas.md +24 -0
- package/packs/gpu-painter/rules/gpu-travellers-from-snapshot.md +24 -0
- package/packs/gpu-painter/rules/hover-every-pressable.md +25 -0
- package/packs/gpu-painter/skills/gpu-capture/SKILL.md +15 -0
- package/packs/gpu-painter/skills/gpu-frame-bench/SKILL.md +14 -0
- package/packs/gpu-painter/skills/gpu-golden-check/SKILL.md +13 -0
- package/packs/gpu-painter/skills/gpu-golden-update/SKILL.md +16 -0
- package/packs/gpu-painter/skills/gpu-lifecycle-stress/SKILL.md +15 -0
- package/packs/gpu-painter/skills/hover-state/SKILL.md +33 -0
- package/packs/hook-proof/checks/hook-catch-must-not-allow.json +16 -0
- package/packs/hook-proof/checks/hook-command-no-pipe.json +16 -0
- package/packs/hook-proof/checks/hook-exit-1-does-not-block.json +17 -0
- package/packs/hook-proof/checks/hook-no-or-true.json +18 -0
- package/packs/hook-proof/checks/hook-refusal-on-stderr.json +17 -0
- package/packs/hook-proof/pack.json +38 -0
- package/packs/hook-proof/rules/a-refusal-is-exit-2.md +28 -0
- package/packs/hook-proof/skills/prove-a-hook/SKILL.md +21 -0
- package/packs/host-agent/checks/file-path-from-param.json +27 -0
- package/packs/host-agent/checks/integrity-check-optional.json +27 -0
- package/packs/host-agent/checks/quoted-value-into-config.json +24 -0
- package/packs/host-agent/checks/secret-on-argv.json +28 -0
- package/packs/host-agent/checks/ssh-verifies-host-key.json +27 -0
- package/packs/host-agent/checks/system-file-written-in-place.json +26 -0
- package/packs/host-agent/checks/tmp-path-predictable.json +27 -0
- package/packs/host-agent/pack.json +22 -0
- package/packs/host-agent/rules/root-on-other-machines.md +27 -0
- package/packs/html-ui/checks/email-html-escapes.json +25 -0
- package/packs/html-ui/checks/external-link-rel.json +21 -0
- package/packs/html-ui/checks/no-duplicate-escape.json +26 -0
- package/packs/html-ui/checks/no-inline-client-script.json +21 -0
- package/packs/html-ui/checks/no-inline-style.json +23 -0
- package/packs/html-ui/pack.json +33 -0
- package/packs/html-ui/rules/email-html-escapes.md +19 -0
- package/packs/html-ui/rules/server-html-escapes-everything.md +22 -0
- package/packs/http-api/checks/api-path-versioned.json +31 -0
- package/packs/http-api/checks/authz-from-request-body.json +26 -0
- package/packs/http-api/checks/client-calls-a-version.json +28 -0
- package/packs/http-api/checks/client-ip-not-leftmost-xff.json +28 -0
- package/packs/http-api/checks/cors-needs-origin.json +26 -0
- package/packs/http-api/checks/csp-not-switched-off.json +28 -0
- package/packs/http-api/checks/debug-route-not-shipped.json +26 -0
- package/packs/http-api/checks/encrypted-name-plaintext-value.json +26 -0
- package/packs/http-api/checks/err-text-stays-server-side.json +27 -0
- package/packs/http-api/checks/failure-is-not-empty.json +28 -0
- package/packs/http-api/checks/gate-is-not-a-todo.json +27 -0
- package/packs/http-api/checks/guard-fails-open.json +28 -0
- package/packs/http-api/checks/hmac-over-reserialised-body.json +26 -0
- package/packs/http-api/checks/jwt-verify-pins-algorithm.json +26 -0
- package/packs/http-api/checks/middleware-skips-without-user.json +26 -0
- package/packs/http-api/checks/next-api-route-versioned.json +47 -0
- package/packs/http-api/checks/no-security-off-switch.json +28 -0
- package/packs/http-api/checks/oauth-state-signed.json +26 -0
- package/packs/http-api/checks/one-time-code-compared-raw.json +26 -0
- package/packs/http-api/checks/query-cast-is-not-validation.json +28 -0
- package/packs/http-api/checks/queue-action-from-client.json +26 -0
- package/packs/http-api/checks/rate-limit-one-module.json +28 -0
- package/packs/http-api/checks/role-checked-in-one-guard.json +27 -0
- package/packs/http-api/checks/route-body-needs-schema.json +31 -0
- package/packs/http-api/checks/route-scopes-to-the-caller.json +31 -0
- package/packs/http-api/checks/secret-compared-constant-time-any.json +28 -0
- package/packs/http-api/checks/secret-in-job-record.json +27 -0
- package/packs/http-api/checks/server-fetches-tenant-url.json +30 -0
- package/packs/http-api/checks/short-code-needs-limit.json +26 -0
- package/packs/http-api/checks/user-url-through-safe-fetch.json +28 -0
- package/packs/http-api/checks/webhook-dedupes-event-id.json +30 -0
- package/packs/http-api/pack.json +45 -0
- package/packs/http-api/rules/adapters-one-db-gateway.md +18 -0
- package/packs/http-api/rules/apis-are-versioned.md +27 -0
- package/packs/http-api/rules/credentials-handled-safely.md +22 -0
- package/packs/http-api/rules/handlers-own-the-work.md +17 -0
- package/packs/http-api/rules/health-and-correlation.md +17 -0
- package/packs/http-api/rules/one-error-contract.md +17 -0
- package/packs/http-api/rules/routes-are-thin.md +17 -0
- package/packs/http-api/rules/routes-scope-to-the-caller.md +26 -0
- package/packs/http-api/rules/routes-validate-and-answer.md +25 -0
- package/packs/http-api/skills/change-an-api/SKILL.md +20 -0
- package/packs/http-api/skills/secure-a-route/SKILL.md +21 -0
- package/packs/instrument-shell/checks/no-raw-struct-blob.json +23 -0
- package/packs/instrument-shell/pack.json +22 -0
- package/packs/instrument-shell/rules/fold-keeps-every-route.md +24 -0
- package/packs/instrument-shell/rules/quiet-announces-itself.md +22 -0
- package/packs/instrument-shell/rules/render-the-extremes.md +25 -0
- package/packs/instrument-shell/rules/seams-are-declared.md +27 -0
- package/packs/instrument-shell/rules/session-blob-append-only.md +25 -0
- package/packs/instrument-shell/skills/gates-from-commit-one/SKILL.md +20 -0
- package/packs/instrument-shell/skills/hit-the-painted-control/SKILL.md +18 -0
- package/packs/js-ts/checks/async-callback-unguarded.json +26 -0
- package/packs/js-ts/checks/background-loop-needs-stop.json +28 -0
- package/packs/js-ts/checks/child-process-error-handler.json +28 -0
- package/packs/js-ts/checks/entry-registers-shutdown.json +31 -0
- package/packs/js-ts/checks/fetch-needs-timeout.json +30 -0
- package/packs/js-ts/checks/job-only-in-memory.json +28 -0
- package/packs/js-ts/checks/json-only-through-helper.json +29 -0
- package/packs/js-ts/checks/line-under-200-chars.json +34 -0
- package/packs/js-ts/checks/localhost-default-in-server.json +29 -0
- package/packs/js-ts/checks/no-any.json +27 -0
- package/packs/js-ts/checks/no-cast-escape-hatches.json +24 -0
- package/packs/js-ts/checks/no-console-in-app.json +31 -0
- package/packs/js-ts/checks/no-dynamic-import-node-builtin.json +25 -0
- package/packs/js-ts/checks/no-inline-binary.json +20 -0
- package/packs/js-ts/checks/no-inline-zod-outside-schemas.json +27 -0
- package/packs/js-ts/checks/no-local-sleep.json +25 -0
- package/packs/js-ts/checks/no-nested-ternary.json +20 -0
- package/packs/js-ts/checks/no-secret-in-log.json +24 -0
- package/packs/js-ts/checks/no-shell-string-interpolation.json +24 -0
- package/packs/js-ts/checks/no-swallowed-errors.json +25 -0
- package/packs/js-ts/checks/no-ts-enum.json +20 -0
- package/packs/js-ts/checks/process-env-only-in-config.json +31 -0
- package/packs/js-ts/checks/race-leaves-work-running.json +23 -0
- package/packs/js-ts/checks/reject-as-catch-all-exists.json +24 -0
- package/packs/js-ts/checks/secret-has-no-fallback.json +24 -0
- package/packs/js-ts/checks/secret-needs-crypto-random.json +24 -0
- package/packs/js-ts/checks/server-close-awaited.json +27 -0
- package/packs/js-ts/checks/session-user-typed-once.json +24 -0
- package/packs/js-ts/checks/switch-is-a-table.json +24 -0
- package/packs/js-ts/checks/test-imports-the-source.json +19 -0
- package/packs/js-ts/checks/test-name-says-behaviour.json +16 -0
- package/packs/js-ts/checks/tsconfig-flags.json +21 -0
- package/packs/js-ts/checks/void-needs-catch.json +24 -0
- package/packs/js-ts/checks/zod-string-needs-max.json +25 -0
- package/packs/js-ts/checks/zod-v4-formats.json +23 -0
- package/packs/js-ts/pack.json +84 -0
- package/packs/js-ts/rules/await-handled-at-a-boundary.md +66 -0
- package/packs/js-ts/rules/env-through-config.md +36 -0
- package/packs/js-ts/rules/errors-never-swallowed.md +33 -0
- package/packs/js-ts/rules/files-under-300-lines.md +18 -0
- package/packs/js-ts/rules/guard-clauses-over-nesting.md +34 -0
- package/packs/js-ts/rules/js-repeats-become-templates.md +26 -0
- package/packs/js-ts/rules/json-only-through-one-helper.md +62 -0
- package/packs/js-ts/rules/named-constants-not-magic-numbers.md +34 -0
- package/packs/js-ts/rules/no-any.md +34 -0
- package/packs/js-ts/rules/no-console.md +30 -0
- package/packs/js-ts/rules/no-floating-promises.md +31 -0
- package/packs/js-ts/rules/no-param-mutation.md +28 -0
- package/packs/js-ts/rules/no-unsafe-assertions.md +30 -0
- package/packs/js-ts/rules/one-logger.md +24 -0
- package/packs/js-ts/rules/outbound-calls-have-a-limit.md +20 -0
- package/packs/js-ts/rules/parallel-independent-awaits.md +29 -0
- package/packs/js-ts/rules/process-lifecycle.md +47 -0
- package/packs/js-ts/rules/schema-single-source-of-truth.md +36 -0
- package/packs/js-ts/rules/secrets-stay-secret.md +22 -0
- package/packs/js-ts/rules/small-functions.md +20 -0
- package/packs/js-ts/rules/test-names-describe-behavior.md +28 -0
- package/packs/js-ts/rules/tests-can-fail.md +19 -0
- package/packs/js-ts/rules/tsconfig-strict.md +32 -0
- package/packs/js-ts/rules/typed-exports.md +27 -0
- package/packs/js-ts/rules/types-once-at-the-edge.md +18 -0
- package/packs/js-ts/rules/unions-over-enums.md +29 -0
- package/packs/js-ts/rules/zod-at-route-boundaries.md +40 -0
- package/packs/midi/checks/active-sensing-no-pulse.json +24 -0
- package/packs/midi/checks/clock-not-at-sample-zero.json +23 -0
- package/packs/midi/checks/midi-realtime-first.json +27 -0
- package/packs/midi/checks/no-tempo-writeback.json +23 -0
- package/packs/midi/checks/position-not-integrated.json +25 -0
- package/packs/midi/checks/stub-playhead-advances.json +27 -0
- package/packs/midi/checks/sysex-length-literal.json +23 -0
- package/packs/midi/checks/sysex-not-on-audio.json +24 -0
- package/packs/midi/checks/sysex-survives-realtime.json +27 -0
- package/packs/midi/pack.json +22 -0
- package/packs/midi/rules/clock-is-a-pulse-stream.md +30 -0
- package/packs/midi/rules/imports-count-every-loss.md +25 -0
- package/packs/midi/rules/measure-the-clock-in-audio.md +30 -0
- package/packs/midi/rules/sync-is-a-mode.md +29 -0
- package/packs/midi/rules/sysex-is-a-record.md +29 -0
- package/packs/midi/skills/clock-probe/SKILL.md +23 -0
- package/packs/midi/skills/clock-probe/clock-law.md +417 -0
- package/packs/midi/skills/does-it-sync/SKILL.md +19 -0
- package/packs/mongo/checks/bulk-over-many.json +28 -0
- package/packs/mongo/checks/created-at-immutable.json +25 -0
- package/packs/mongo/checks/driver-outside-gateway.json +24 -0
- package/packs/mongo/checks/key-lookup-is-hashed.json +24 -0
- package/packs/mongo/checks/mongoclient-cached-and-named.json +34 -0
- package/packs/mongo/checks/mongoclient-outside-gateway.json +24 -0
- package/packs/mongo/checks/no-count-plus-one-ids.json +25 -0
- package/packs/mongo/checks/no-db-write-in-loop.json +25 -0
- package/packs/mongo/checks/no-drop-in-code.json +27 -0
- package/packs/mongo/checks/no-empty-deletemany.json +26 -0
- package/packs/mongo/checks/no-find-reads.json +26 -0
- package/packs/mongo/checks/no-input-spread-into-write.json +24 -0
- package/packs/mongo/checks/no-mongoose.json +23 -0
- package/packs/mongo/checks/no-negation-delete.json +26 -0
- package/packs/mongo/checks/no-non-null-after-findone.json +25 -0
- package/packs/mongo/checks/objectid-equals.json +26 -0
- package/packs/mongo/checks/update-then-reread.json +24 -0
- package/packs/mongo/checks/watch-not-poll.json +25 -0
- package/packs/mongo/pack.json +52 -0
- package/packs/mongo/rules/deletes-name-what-dies.md +21 -0
- package/packs/mongo/rules/driver-only-in-gateway.md +26 -0
- package/packs/mongo/rules/keys-hashed-input-picked.md +21 -0
- package/packs/mongo/rules/one-client-one-pool.md +32 -0
- package/packs/mongo/rules/reads-aggregate-writes-bulk.md +25 -0
- package/packs/mongo/rules/writes-are-atomic.md +27 -0
- package/packs/multi-tenant/checks/role-sees-all-tenants.json +26 -0
- package/packs/multi-tenant/checks/tenant-filter-from-url.json +25 -0
- package/packs/multi-tenant/pack.json +22 -0
- package/packs/multi-tenant/rules/no-role-crosses-a-tenant.md +21 -0
- package/packs/multi-tenant/skills/prove-tenant-isolation/SKILL.md +19 -0
- package/packs/nginx/checks/modsec-no-blanket-off.json +16 -0
- package/packs/nginx/checks/nginx-csp-no-unsafe.json +15 -0
- package/packs/nginx/checks/nginx-headers-always.json +15 -0
- package/packs/nginx/checks/nginx-keepalive-http11.json +20 -0
- package/packs/nginx/checks/nginx-logs-to-stdout.json +15 -0
- package/packs/nginx/checks/nginx-no-crlf.json +17 -0
- package/packs/nginx/checks/nginx-resolver-ttl.json +19 -0
- package/packs/nginx/pack.json +43 -0
- package/packs/nginx/rules/modsecurity-tuning.md +22 -0
- package/packs/nginx/rules/nginx-production.md +26 -0
- package/packs/nginx/skills/waf-rollout/SKILL.md +20 -0
- package/packs/python/checks/py-no-bare-except.json +18 -0
- package/packs/python/checks/py-no-eval.json +18 -0
- package/packs/python/checks/py-no-mutable-default.json +18 -0
- package/packs/python/checks/py-no-shell-true.json +18 -0
- package/packs/python/checks/py-no-star-import.json +18 -0
- package/packs/python/checks/py-no-swallowed-exception.json +18 -0
- package/packs/python/checks/py-none-by-is.json +18 -0
- package/packs/python/checks/py-open-with-with.json +18 -0
- package/packs/python/checks/py-requests-timeout.json +18 -0
- package/packs/python/checks/py-subprocess-output-checked.json +18 -0
- package/packs/python/checks/py-yaml-safe-load.json +18 -0
- package/packs/python/pack.json +26 -0
- package/packs/python/rules/python-errors-and-resources.md +26 -0
- package/packs/python/rules/python-repeats-become-templates.md +27 -0
- package/packs/python/skills/python-extract-template/SKILL.md +35 -0
- package/packs/react/checks/dialog-needs-role.json +28 -0
- package/packs/react/checks/effect-not-for-derived-state.json +24 -0
- package/packs/react/checks/icon-button-needs-name.json +24 -0
- package/packs/react/checks/label-needs-a-field.json +24 -0
- package/packs/react/checks/ui-failed-fetch-says-so.json +23 -0
- package/packs/react/checks/ui-ok-branch-has-an-else.json +23 -0
- package/packs/react/pack.json +20 -0
- package/packs/react/rules/react-accessible-controls.md +20 -0
- package/packs/react/rules/react-state-and-effects.md +23 -0
- package/packs/reverse-engineering/checks/ear-constant-tagged.json +23 -0
- package/packs/reverse-engineering/checks/no-default-on-distrust.json +24 -0
- package/packs/reverse-engineering/checks/no-rom-table-literal.json +29 -0
- package/packs/reverse-engineering/checks/prose-never-decides.json +22 -0
- package/packs/reverse-engineering/hooks/firmware-out-of-git.mjs +49 -0
- package/packs/reverse-engineering/pack.json +35 -0
- package/packs/reverse-engineering/rules/a-negative-names-what-was-scanned.md +19 -0
- package/packs/reverse-engineering/rules/account-every-term.md +20 -0
- package/packs/reverse-engineering/rules/addresses-name-their-space.md +21 -0
- package/packs/reverse-engineering/rules/every-law-has-a-source.md +18 -0
- package/packs/reverse-engineering/rules/findings-are-withdrawn-in-place.md +17 -0
- package/packs/reverse-engineering/rules/firmware-stays-out.md +18 -0
- package/packs/reverse-engineering/rules/names-before-code.md +19 -0
- package/packs/reverse-engineering/rules/reproduction-or-original.md +18 -0
- package/packs/reverse-engineering/rules/two-sources-before-naming.md +21 -0
- package/packs/reverse-engineering/skills/bank-census/SKILL.md +18 -0
- package/packs/reverse-engineering/skills/gather-sources/SKILL.md +21 -0
- package/packs/reverse-engineering/skills/menu-map/SKILL.md +28 -0
- package/packs/reverse-engineering/skills/names-first/SKILL.md +41 -0
- package/packs/reverse-engineering/skills/re-deliverables/SKILL.md +21 -0
- package/packs/reverse-engineering/skills/tripwire-the-law/SKILL.md +19 -0
- package/packs/reverse-engineering/tools/lib/unwrap.mjs +115 -0
- package/packs/reverse-engineering/tools/names-sweep.mjs +264 -0
- package/packs/shell/checks/sh-mktemp.json +18 -0
- package/packs/shell/checks/sh-no-backticks.json +18 -0
- package/packs/shell/checks/sh-no-eval.json +18 -0
- package/packs/shell/checks/sh-no-parse-ls.json +18 -0
- package/packs/shell/checks/sh-no-pipe-to-shell.json +18 -0
- package/packs/shell/checks/sh-no-program-in-heredoc.json +18 -0
- package/packs/shell/checks/sh-quote-arguments.json +18 -0
- package/packs/shell/checks/sh-rm-guarded.json +18 -0
- package/packs/shell/checks/sh-strict-mode.json +20 -0
- package/packs/shell/pack.json +28 -0
- package/packs/shell/rules/shell-repeats-become-templates.md +23 -0
- package/packs/shell/rules/shell-strict-and-quoted.md +27 -0
- package/packs/shell/skills/shell-extract-template/SKILL.md +32 -0
- package/packs/shell-hooks/checks/gnu-only-in-shell.json +18 -0
- package/packs/shell-hooks/checks/hook-command-anchored.json +12 -0
- package/packs/shell-hooks/checks/hook-json-by-tool.json +18 -0
- package/packs/shell-hooks/checks/no-bash-c-with-variable.json +20 -0
- package/packs/shell-hooks/pack.json +36 -0
- package/packs/shell-hooks/rules/hooks-portable-anchored-quoted.md +26 -0
- package/packs/sound-design/checks/audio-seed-fixed.json +23 -0
- package/packs/sound-design/checks/mix-is-a-crossfade.json +30 -0
- package/packs/sound-design/checks/no-hash-in-the-signal.json +30 -0
- package/packs/sound-design/checks/no-literal-key-anchor.json +30 -0
- package/packs/sound-design/checks/test-compares-a-function-with-itself.json +22 -0
- package/packs/sound-design/pack.json +22 -0
- package/packs/sound-design/rules/algorithms-are-data.md +26 -0
- package/packs/sound-design/rules/meter-before-engine.md +27 -0
- package/packs/sound-design/rules/neutral-is-exact.md +26 -0
- package/packs/sound-design/rules/test-off-the-easy-point.md +28 -0
- package/packs/sound-design/rules/unreachable-is-said.md +25 -0
- package/packs/sound-design/rules/waveforms-and-octaves.md +24 -0
- package/packs/sound-design/skills/choose-observable/SKILL.md +18 -0
- package/packs/sound-design/skills/measure-a-law/SKILL.md +18 -0
- package/packs/sound-design/skills/measure-reverb/SKILL.md +19 -0
- package/packs/sound-design/skills/spread-probe/SKILL.md +18 -0
- package/packs/stellix/checks/no-keyboard-focus.json +12 -0
- package/packs/stellix/checks/one-seeded-random.json +16 -0
- package/packs/stellix/checks/world-never-includes-painter.json +12 -0
- package/packs/stellix/checks/world-no-libm.json +15 -0
- package/packs/stellix/checks/world-no-unordered.json +10 -0
- package/packs/stellix/hooks/no-shipped-images.mjs +88 -0
- package/packs/stellix/pack.json +61 -0
- package/packs/stellix/rules/stellix-deterministic-world.md +29 -0
- package/packs/stellix/rules/stellix-reset-generated.md +24 -0
- package/packs/stellix/rules/stellix-world-snapshot.md +27 -0
- package/packs/stellix/rules/world-continuous-collision.md +26 -0
- package/packs/stellix/rules/world-fp-determinism.md +28 -0
- package/packs/stored-text/checks/no-raw-html-from-data.json +31 -0
- package/packs/stored-text/checks/stored-text-libs-only-in-renderer.json +31 -0
- package/packs/stored-text/checks/stored-text-renderer-complete.json +24 -0
- package/packs/stored-text/checks/stored-text-through-renderer.json +25 -0
- package/packs/stored-text/pack.json +41 -0
- package/packs/stored-text/rules/stored-text-renders-once.md +26 -0
- package/packs/stored-text/skills/render-stored-text/SKILL.md +24 -0
- package/packs/synth-creator/checks/timing-from-synthcore.json +24 -0
- package/packs/synth-creator/checks/ui-drawn-in-code.json +23 -0
- package/packs/synth-creator/hooks/build-guard.mjs +232 -0
- package/packs/synth-creator/hooks/build-sweep.mjs +166 -0
- package/packs/synth-creator/hooks/frozen-files.mjs +63 -0
- package/packs/synth-creator/hooks/idle-wake.mjs +90 -0
- package/packs/synth-creator/hooks/protect-vendor.mjs +174 -0
- package/packs/synth-creator/hooks/spec-lock.mjs +117 -0
- package/packs/synth-creator/hooks/stage-guard.mjs +50 -0
- package/packs/synth-creator/hooks/time-guard.mjs +292 -0
- package/packs/synth-creator/hooks/user-asks.mjs +165 -0
- package/packs/synth-creator/pack.json +136 -0
- package/packs/synth-creator/rules/copy-trees-read-only.md +25 -0
- package/packs/synth-creator/rules/debug-by-measurement.md +20 -0
- package/packs/synth-creator/rules/done-means-fresh-evidence.md +18 -0
- package/packs/synth-creator/rules/hooks-and-gates.md +21 -0
- package/packs/synth-creator/rules/long-work-in-the-background.md +16 -0
- package/packs/synth-creator/rules/timing-from-synthcore.md +16 -0
- package/packs/synth-creator/skills/build-step/SKILL.md +125 -0
- package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +19 -0
- package/packs/synth-creator/tools/freeze.mjs +36 -0
- package/packs/tui/checks/tui-entry-restores-terminal.json +27 -0
- package/packs/tui/checks/tui-gates-on-tty.json +25 -0
- package/packs/tui/checks/tui-no-ris-reset.json +22 -0
- package/packs/tui/checks/tui-resize-listener-removed.json +26 -0
- package/packs/tui/checks/tui-width-is-state.json +33 -0
- package/packs/tui/pack.json +44 -0
- package/packs/tui/rules/tui-resize-and-screen.md +25 -0
- package/packs/tui/skills/build-tui/SKILL.md +21 -0
- package/packs/ui-craft/checks/ui-input-needs-autocomplete.json +23 -0
- package/packs/ui-craft/checks/ui-intl-for-dates.json +21 -0
- package/packs/ui-craft/checks/ui-no-ai-palette.json +22 -0
- package/packs/ui-craft/checks/ui-no-animate-layout.json +22 -0
- package/packs/ui-craft/checks/ui-no-assistant-palette.json +23 -0
- package/packs/ui-craft/checks/ui-no-bounce-easing.json +22 -0
- package/packs/ui-craft/checks/ui-no-custom-cursor.json +22 -0
- package/packs/ui-craft/checks/ui-no-emoji-as-ui.json +22 -0
- package/packs/ui-craft/checks/ui-no-empty-image.json +21 -0
- package/packs/ui-craft/checks/ui-no-eyebrow.json +21 -0
- package/packs/ui-craft/checks/ui-no-furniture-text.json +22 -0
- package/packs/ui-craft/checks/ui-no-gradient-text.json +22 -0
- package/packs/ui-craft/checks/ui-no-index-numbering.json +21 -0
- package/packs/ui-craft/checks/ui-no-lorem.json +22 -0
- package/packs/ui-craft/checks/ui-no-marketing-buzzword.json +22 -0
- package/packs/ui-craft/checks/ui-no-paste-block.json +21 -0
- package/packs/ui-craft/checks/ui-no-placeholder-names.json +21 -0
- package/packs/ui-craft/checks/ui-no-scroll-hijack.json +21 -0
- package/packs/ui-craft/checks/ui-no-side-tab.json +21 -0
- package/packs/ui-craft/checks/ui-no-stock-labels.json +22 -0
- package/packs/ui-craft/checks/ui-no-transition-all.json +22 -0
- package/packs/ui-craft/checks/ui-no-uppercase-table-header.json +21 -0
- package/packs/ui-craft/checks/ui-no-zebra-rows.json +22 -0
- package/packs/ui-craft/checks/ui-no-zoom-lock.json +21 -0
- package/packs/ui-craft/pack.json +43 -0
- package/packs/ui-craft/rules/apps-are-not-pages.md +26 -0
- package/packs/ui-craft/rules/data-tells-the-truth.md +26 -0
- package/packs/ui-craft/rules/dates-and-numbers.md +18 -0
- package/packs/ui-craft/rules/forms-never-fight.md +22 -0
- package/packs/ui-craft/rules/interface-copy.md +26 -0
- package/packs/ui-craft/rules/motion-is-rare.md +31 -0
- package/packs/ui-craft/rules/no-generated-look.md +37 -0
- package/packs/ui-craft/rules/page-holds-its-frame.md +28 -0
- package/packs/ui-craft/rules/page-proved-rendered.md +19 -0
- package/packs/ui-craft/rules/pictures-do-a-job.md +25 -0
- package/packs/ui-craft/rules/pointer-feedback.md +28 -0
- package/packs/ui-craft/rules/screen-answers-one-question.md +24 -0
- package/packs/ui-craft/rules/survives-real-data.md +23 -0
- package/packs/ui-craft/rules/touch-and-small-screens.md +19 -0
- package/packs/ui-craft/rules/type-space-colour.md +26 -0
- package/packs/ui-craft/rules/url-holds-the-state.md +19 -0
- package/packs/ui-craft/skills/app-screen/SKILL.md +33 -0
- package/packs/ui-craft/skills/build-from-reference/SKILL.md +28 -0
- package/packs/ui-craft/skills/chart-choice/SKILL.md +34 -0
- package/packs/ui-craft/skills/hover-state/SKILL.md +28 -0
- package/packs/ui-craft/skills/page-critique/SKILL.md +33 -0
- package/packs/ui-craft/skills/prove-page/SKILL.md +23 -0
- package/packs/ui-craft/skills/redesign-page/SKILL.md +19 -0
- package/packs/ui-craft/skills/ui-code-review/SKILL.md +22 -0
- package/packs/ui-craft/skills/write-the-page/SKILL.md +21 -0
- package/packs/ui-craft/tools/page-measure.mjs +41 -0
- package/packs/web/checks/a11y-img-needs-alt.json +26 -0
- package/packs/web/checks/a11y-no-bare-outline-none.json +21 -0
- package/packs/web/checks/a11y-no-clickable-div.json +26 -0
- package/packs/web/checks/a11y-no-positive-tabindex.json +26 -0
- package/packs/web/checks/client-no-api-key.json +29 -0
- package/packs/web/checks/client-no-browser-global-at-module-scope.json +28 -0
- package/packs/web/checks/client-no-server-import.json +31 -0
- package/packs/web/checks/client-no-window-location-nav.json +28 -0
- package/packs/web/checks/css-no-inline-style.json +30 -0
- package/packs/web/checks/ds-pages-compose-primitives.json +28 -0
- package/packs/web/checks/key-from-index.json +23 -0
- package/packs/web/checks/prodbuild-no-cdn-runtime-deps.json +21 -0
- package/packs/web/checks/prodbuild-no-dev-flags.json +21 -0
- package/packs/web/checks/prodbuild-no-remote-fonts.json +22 -0
- package/packs/web/checks/rcss-button-cursor.json +30 -0
- package/packs/web/checks/rcss-no-100vw.json +21 -0
- package/packs/web/checks/rcss-no-fixed-wide-width.json +21 -0
- package/packs/web/checks/rcss-viewport-meta.json +28 -0
- package/packs/web/checks/secret-not-in-web-storage.json +25 -0
- package/packs/web/checks/tw-no-raw-hex-in-markup.json +34 -0
- package/packs/web/checks/wp-hero-not-lazy.json +27 -0
- package/packs/web/checks/wp-img-needs-dimensions.json +26 -0
- package/packs/web/pack.json +73 -0
- package/packs/web/rules/accessibility.md +26 -0
- package/packs/web/rules/client-boundaries.md +23 -0
- package/packs/web/rules/production-build.md +22 -0
- package/packs/web/rules/secrets-not-in-the-browser.md +18 -0
- package/packs/web/rules/styles-live-in-stylesheets.md +23 -0
- package/packs/web/rules/tailwind-v4.md +23 -0
- package/packs/web/rules/ui-primitives-one-module.md +30 -0
- package/packs/web/rules/web-performance.md +21 -0
- package/packs/web/skills/design-direction/SKILL.md +30 -0
- package/packs/web/skills/design-review/SKILL.md +28 -0
- package/packs/web/skills/pick-rendering-mode/SKILL.md +21 -0
- package/packs/web/skills/preflight-build/SKILL.md +23 -0
- package/packs/web/skills/seo-audit/SKILL.md +22 -0
- package/packs/websocket/checks/no-token-in-query.json +26 -0
- package/packs/websocket/checks/stale-socket-deletes-new-entry.json +25 -0
- package/packs/websocket/checks/ws-identity-from-message.json +26 -0
- package/packs/websocket/pack.json +21 -0
- package/packs/websocket/rules/identity-comes-from-the-connection.md +24 -0
- package/packs/writing/checks/ascii-no-em-dash.json +17 -0
- package/packs/writing/checks/no-copula-dodging.json +18 -0
- package/packs/writing/checks/no-dramatic-colon.json +18 -0
- package/packs/writing/checks/no-not-x-but-y.json +18 -0
- package/packs/writing/checks/no-participial-opener.json +18 -0
- package/packs/writing/checks/no-pseudo-wisdom.json +18 -0
- package/packs/writing/checks/no-slop-words.json +18 -0
- package/packs/writing/checks/no-stock-connectors.json +18 -0
- package/packs/writing/checks/no-time-or-date.json +27 -0
- package/packs/writing/checks/no-trailing-ing.json +18 -0
- package/packs/writing/checks/no-vague-attribution.json +18 -0
- package/packs/writing/pack.json +43 -0
- package/packs/writing/rules/ascii-no-em-dash.md +21 -0
- package/packs/writing/rules/cut-the-padding.md +20 -0
- package/packs/writing/rules/direct-voice.md +22 -0
- package/packs/writing/rules/no-cargo-cult.md +16 -0
- package/packs/writing/rules/no-scaffolding.md +19 -0
- package/packs/writing/rules/no-stock-language.md +27 -0
- package/packs/writing/rules/no-time-or-date.md +18 -0
- package/packs/writing/rules/rhythm-like-talk.md +20 -0
- package/packs/writing/rules/write-from-your-own-corner.md +20 -0
- package/packs/writing/skills/argument-post/SKILL.md +28 -0
- package/packs/writing/skills/humanize/SKILL.md +28 -0
- package/packs/writing/skills/launch-post/SKILL.md +38 -0
- package/packs/writing/skills/pick-subreddit/SKILL.md +69 -0
- package/packs/writing/skills/post-voice/SKILL.md +22 -0
- package/packs/writing/skills/reddit-post/SKILL.md +63 -0
- package/packs/writing/skills/reddit-title/SKILL.md +23 -0
- package/packs/writing/skills/reply-moves/SKILL.md +40 -0
- package/packs/writing/skills/reply-stance/SKILL.md +23 -0
- package/packs/writing/skills/should-i-reply/SKILL.md +21 -0
- package/packs/zod/hooks/schemas-only.mjs +53 -0
- package/packs/zod/pack.json +44 -0
- package/packs/zod/rules/env-shapes-only.md +14 -0
- package/packs/zod/rules/zod-4-schemas.md +14 -0
- package/profiles/stellix.layout.json +315 -0
- package/profiles/sub37.layout.json +247 -0
- package/prompts/scan-review.md +40 -0
- package/prompts/survey.md +82 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gather-sources
|
|
3
|
+
description: Use when starting work on a machine, or before disassembling anything.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gather the sources in the right order (one job: read what exists before reading code)
|
|
7
|
+
|
|
8
|
+
<constraints>
|
|
9
|
+
Always do:
|
|
10
|
+
1. Read the folder: text files, filenames, the shape of the dump set.
|
|
11
|
+
2. Read the owner's manual (`pdf-to-md`) for what the machine calls things, and search the image for those names (`names-first`). This is the fastest step there is: do it before everything below.
|
|
12
|
+
3. Read the maker's sysex document.
|
|
13
|
+
4. Read the service manual, and check the scan is whole. Check for a real text layer first; where there is none, or a cell is in dispute, read the rendered page, not the OCR.
|
|
14
|
+
5. Read other people's conversions as `[PUB]` leads.
|
|
15
|
+
6. Read the disk OS's own strings.
|
|
16
|
+
7. Check the tool table before writing a disassembler.
|
|
17
|
+
8. Take ratios from a redrawn trace, never absolutes.
|
|
18
|
+
|
|
19
|
+
Never do:
|
|
20
|
+
- Start the disassembly before steps 1 to 4.
|
|
21
|
+
</constraints>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: menu-map
|
|
3
|
+
description: Use when mapping where each parameter is reached on the machine, when a control has no byte in the patch map, or before building effects or a panel from a decoded record.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Map the menus (one job: every parameter joined to the page that reaches it, and every mismatch listed)
|
|
7
|
+
|
|
8
|
+
A patch map says what byte N means. A menu map says how you reach byte N from the panel, and it names what you can edit that is no byte in a patch at all. On one machine the chorus and delay TYPE selectors sat outside the patch block, and that blocked the effects work for a day.
|
|
9
|
+
|
|
10
|
+
<constraints>
|
|
11
|
+
Always do:
|
|
12
|
+
1. Write the tree from the manual's own section order, page by page, with the printed page number each page is cited from, one tree per algorithm or mode where the pages differ. This is the only part edited by hand: a script looks up everything else and regenerates the table.
|
|
13
|
+
2. Tag each name by its source: `[FW]` in the firmware's display pools (`names-first`), `[MAN]` printed. Page membership and page order are usually `[MAN]` alone, and the map says so.
|
|
14
|
+
3. Join the two with the manual's own display drawings: the page name on the top line, the parameter on the bottom. Find each half in the firmware's pools. That is the two-source check for the whole tree.
|
|
15
|
+
4. Give every parameter its home: patch, multi or performance, global, or stored nowhere.
|
|
16
|
+
5. Print both mismatch lists, because they are the point of the file:
|
|
17
|
+
- on a page but not in the patch map: multi and global parameters, and anything with no published index;
|
|
18
|
+
- in the patch map but no page reaches it: dead, reserved, live and set by presets only, or a page still to find. A parameter list describes the control surface, not the engine: an algorithm with no knob for something usually has it, fixed.
|
|
19
|
+
6. Check whether every parameter that shapes the sound is in the dump. One that is not must be sent another way, and a rebuild that loads only the dump loses it.
|
|
20
|
+
7. Write down what changes the map. Menus are dynamic: a page hidden while its effect is off still exists. Pages re-label themselves: one byte can carry two names, by mode, so list both.
|
|
21
|
+
8. Keep corrections in the file, and end it with what is not established.
|
|
22
|
+
|
|
23
|
+
Never do:
|
|
24
|
+
- Conclude a parameter does not exist, or does nothing, because no page shows it.
|
|
25
|
+
- Give one byte one name when a mode re-labels it.
|
|
26
|
+
- Edit the generated table by hand.
|
|
27
|
+
- Build effects or a panel before the first mismatch list is read.
|
|
28
|
+
</constraints>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: names-first
|
|
3
|
+
description: Use when starting on a firmware image, an OS update or a sysex dump, before any disassembly, or when a table or a record has no name yet.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Map the machine by its own names (one job: names found, tables located, before reading any code)
|
|
7
|
+
|
|
8
|
+
A machine has to print what it calls things, so its names sit in the image as text. A name found is usually a table found, and a table usually leads to the record and the code. Not always: compiled-out debug strings have nothing pointing at them, far pointers and `push seg; push off` pairs need their own scan, and one string led to the wrong table. On one machine the descriptors found this way named every byte of the patch and carried the factory CC numbers, which matched the manual's chart 40 for 40.
|
|
9
|
+
|
|
10
|
+
<constraints>
|
|
11
|
+
Always do:
|
|
12
|
+
1. Write the names file from the manual first, one name per line with its group: patch and preset names, arp patterns, delay and reverb programs, effect types, menus and pages, parameters, value lists, messages. Add the factory bank's names from any dump. Keep the manual's order: order is evidence. Fold curly apostrophes and trailing spaces, and count what joins. Check for a text layer, and render any table that is a picture: OCR dropped noteheads and shuffled a parameter table, and cost one repo two wrong conclusions.
|
|
13
|
+
2. Run `node .claude/tools/names-sweep.mjs <image> --names names.txt`, with `--pair` for two EPROMs, `--base` for a known load address, and `--banked` when the ROM is larger than the CPU's address space. It searches each name as printed, squeezed and cut to a display column (`Decay Time` is `Decaytime`, `Filter Balance` is `FilterBalanc`), then tries the misses with vowels dropped (`BrickWll`, `ARP SWNG`). When nothing reads and entropy is high and flat, read what loads the image; if nothing does, record that the text is not in these files and stop.
|
|
14
|
+
3. Read the wrapping it reports. Text is rarely encrypted but often wrapped: an interleaved pair, scrambled address lines, 7-bit packing and its bit order, nibbles, swapped or reversed words, one header per sysex message, three bytes per word, a header in each bank, a ZIP, a custom character set. Many names reading finds the unwrapping; one or two prove nothing, because chance does that. Names are not the last word: an unpacker that lost two bytes per file still read names while 63% of the image was wrong. Where an EPROM exists, compare the decoded update with it byte for byte. The header length reported is an alignment, so check it against the message format.
|
|
15
|
+
4. Read each group as a map:
|
|
16
|
+
- stride between patch names: the record size and the name's place in it, when most gaps agree. Records can be variable, so find them by their marker over the whole image. ROM presets are often the internal record, smaller than the sysex one; compare value distributions to tie the two;
|
|
17
|
+
- order held: the parameter order, and the table index of every entry;
|
|
18
|
+
- stride between parameter names: a fixed-width table; look beside each name for its range, its control id, its CC. A field can change meaning partway down a table, so re-derive it per block;
|
|
19
|
+
- pointers: follow what reads them to the code that draws the page or handles the value.
|
|
20
|
+
5. Read a pool whole. A display is built from name pools: page names for the top line, long and short parameter names for the bottom. A value list is a blob of N fields of W characters with no separators, and W is the column the LCD reserves. Split a pool on its terminator, never by stride: a name that fills its field has no terminator.
|
|
21
|
+
6. Complete the manual from the firmware. A manual cuts a long list with `..`; the firmware holds all of it, in the machine's own order. Keep that order, however odd, and move the row's tag to `[FW]`.
|
|
22
|
+
7. Use the count. A list's item count says which parameter owns it: two menus of 14 and 18 items were swapped until the counts were read. A list of N names beside a table of N numbers pairs one for one, and that pairing gives the unit: 22 clock names against a 22-word table fixed a unit that had been wrong by a factor of two.
|
|
23
|
+
8. Search a sysex dump the same way. A factory patch's name gives the record's start and length, and the packing that makes it read.
|
|
24
|
+
9. Scan for what the manual does not list: the entries after the last hit, compiled-out debug strings, format strings. A compiler emits string literals in source order, so a block of them is a struct's order.
|
|
25
|
+
10. Check an abrupt name: look at the byte after it. A table may be dictionary-compressed, a high byte standing for another string.
|
|
26
|
+
11. Prove a table across images: the same blob byte-identical in other OS versions and sibling machines.
|
|
27
|
+
12. Write each located table into the research record with its address and which space it is in, its stride, count and the names that proved it, then map the menus (`menu-map`).
|
|
28
|
+
13. Before counting a named byte as done, find what reads it. A name in a patch map is not a consumer: one named byte nothing read was wrong on 48% of a bank.
|
|
29
|
+
|
|
30
|
+
Never do:
|
|
31
|
+
- Start disassembling while the names search has not run.
|
|
32
|
+
- Call an image empty of text because a plain `strings` found nothing. Say which wrappings were tried.
|
|
33
|
+
- Assign names by the order of the manual's prose. It put five right names in the wrong five slots and invented a sixth. Use the address table, the range and the item count.
|
|
34
|
+
- Trust a table built by walking the manual's names: it inherits the manual's errors and has no row for a slot the manual never lists. Read the machine's own slot table too.
|
|
35
|
+
- Name a reserved byte from its position. Unnamed stays unnamed.
|
|
36
|
+
- Look for storage offsets in a panel or menu table. It holds names, control ids and range pointers, and two sessions went into proving that. Offsets come from the code that reads the record.
|
|
37
|
+
- Take a name as a law. It says what a value is called, never its unit or curve.
|
|
38
|
+
- Trust a printed range over a firmware clamp. The corpus decides, and a range too small is data loss: a manual printed 0..19, the firmware clamps at 21, and real patches use 20 and 21.
|
|
39
|
+
</constraints>
|
|
40
|
+
|
|
41
|
+
Where the ROM and the manual disagree, the machine's own ROM says what it has (a sibling's ROM does not), and the disagreement is a finding: a manual's "Undefined" rows were named by the ROM's display strings, and a printed MIDI chart had two waveforms transposed.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: re-deliverables
|
|
3
|
+
description: Use when closing a machine, or when asked what a finished reverse-engineering leaves behind.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Leave the record (one job: the next reader can check every claim)
|
|
7
|
+
|
|
8
|
+
<constraints>
|
|
9
|
+
Always do:
|
|
10
|
+
1. The research record, with withdrawn findings in place.
|
|
11
|
+
2. CSV maps of every parameter, with provenance.
|
|
12
|
+
3. The names file, and every table it located: address, stride, count, and the names that proved it. The menu map, with both mismatch lists.
|
|
13
|
+
4. The laws reference: each law, its source, and where the code is.
|
|
14
|
+
5. The debt ledger of `[EAR]` values, with leads.
|
|
15
|
+
6. Tests, including a byte-exact round trip of every patch.
|
|
16
|
+
7. The non-negotiables list.
|
|
17
|
+
8. State which tags cannot exist here, and which check is lost with no corpus.
|
|
18
|
+
|
|
19
|
+
Never do:
|
|
20
|
+
- Leave a verdict with no number behind it.
|
|
21
|
+
</constraints>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tripwire-the-law
|
|
3
|
+
description: Use before changing a law, a coefficient or a formula that has a source.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tripwire the law (one job: the source and the engine cannot silently part)
|
|
7
|
+
|
|
8
|
+
<constraints>
|
|
9
|
+
Always do:
|
|
10
|
+
1. Write the source's sum longhand, as a separate function, with its citation.
|
|
11
|
+
2. Assert the engine against it, off the easy point.
|
|
12
|
+
3. Change the engine.
|
|
13
|
+
4. Re-anchor before re-scaling.
|
|
14
|
+
5. Take expected values from the printed source. A worked example is the first test.
|
|
15
|
+
|
|
16
|
+
Never do:
|
|
17
|
+
- Fit by ear when a term is missing. A 0.35 fudge once hid a whole term.
|
|
18
|
+
- Compare a function with itself.
|
|
19
|
+
</constraints>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Every way a firmware image or a dump has been seen to wrap its text. Text is rarely encrypted, but it is often wrapped, and a name a manual prints is
|
|
2
|
+
// the test of an unwrapping: if the name reads, the unwrapping is right. A new wrapping is one row.
|
|
3
|
+
// wrappings() -> [{ name, open(buffer) -> buffer }] interleave(a, b) -> one image from an even and an odd EPROM
|
|
4
|
+
// entropyOf(buffer) -> bits per byte
|
|
5
|
+
// shiftedHits(buffer, text) -> [{ at, shift }] a custom character set: the letters keep their distances, whatever the code of 'A'
|
|
6
|
+
|
|
7
|
+
const eachByte = (b, fn) => Buffer.from(b.map(fn));
|
|
8
|
+
|
|
9
|
+
function everyOther(b, from) {
|
|
10
|
+
const out = Buffer.alloc(Math.ceil((b.length - from) / 2));
|
|
11
|
+
for (let i = from, o = 0; i < b.length; i += 2, o += 1) out[o] = b[i];
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Each word of `width` bytes reversed, the first whole word starting at `phase`.
|
|
16
|
+
function reversedWords(b, width, phase) {
|
|
17
|
+
const out = Buffer.from(b);
|
|
18
|
+
for (let i = phase; i + width <= b.length; i += width) for (let k = 0; k < width; k += 1) out[i + k] = b[i + width - 1 - k];
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// MIDI's 8-to-7 packing: seven data bytes and one byte of their high bits, that byte first or last, and its bit k for data byte k or for data byte 6-k.
|
|
23
|
+
// Roland's updates are high bits first with bit k for byte k; the wrong order reads as noise, so both are tried.
|
|
24
|
+
function unpacked7(b, phase, highFirst, bitK) {
|
|
25
|
+
const out = [];
|
|
26
|
+
const take = (high, from, count) => { for (let k = 0; k < count; k += 1) out.push((b[from + k] & 0x7f) | (((high >> (bitK ? k : 6 - k)) & 1) << 7)); };
|
|
27
|
+
let i = phase;
|
|
28
|
+
for (; i + 8 <= b.length; i += 8) take(b[highFirst ? i : i + 7], highFirst ? i + 1 : i, 7);
|
|
29
|
+
if (highFirst && b.length - i >= 2) take(b[i], i + 1, b.length - i - 1); // a message's last group is short, and dropping it shifts everything after
|
|
30
|
+
return Buffer.from(out);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// One byte sent as two: the nibble in the low four bits of each.
|
|
34
|
+
function joinedNibbles(b, phase, highFirst) {
|
|
35
|
+
const out = Buffer.alloc(Math.floor((b.length - phase) / 2));
|
|
36
|
+
for (let i = phase, o = 0; i + 1 < b.length; i += 2, o += 1) {
|
|
37
|
+
const [first, second] = [b[i] & 15, b[i + 1] & 15];
|
|
38
|
+
out[o] = highFirst ? (first << 4) | second : (second << 4) | first;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A 16-bit word as three bytes, marker first: 0x40 | top four bits, then two groups of six. Not 7-in-8, and a CPU guess fails until it is undone.
|
|
44
|
+
function threeByteWords(b, bigEndian) {
|
|
45
|
+
const out = [];
|
|
46
|
+
for (let i = 0; i + 3 <= b.length; i += 3) {
|
|
47
|
+
const word = ((b[i] & 0x0f) << 12) | ((b[i + 1] & 0x3f) << 6) | (b[i + 2] & 0x3f);
|
|
48
|
+
out.push(...(bigEndian ? [word >> 8, word & 255] : [word & 255, word >> 8]));
|
|
49
|
+
}
|
|
50
|
+
return Buffer.from(out);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// An OS update is many sysex messages, in a MIDI file or not, each with its own header, so packed groups restart at every message. Data bytes are below 0x80, so F0 and F7 end them.
|
|
54
|
+
function sysexMessages(b) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (let start = b.indexOf(0xf0); start !== -1; start = b.indexOf(0xf0, start + 1)) {
|
|
57
|
+
const end = b.indexOf(0xf7, start);
|
|
58
|
+
if (end === -1) break;
|
|
59
|
+
out.push(b.subarray(start + 1, end));
|
|
60
|
+
start = end;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const perMessage = (b, header, open) => Buffer.concat(sysexMessages(b).map((m) => open(m.subarray(header))));
|
|
66
|
+
const LONGEST_HEADER = 20;
|
|
67
|
+
const SEVEN_BIT = [true, false].flatMap((first) => [true, false].map((bitK) => ({ first, bitK, name: `7-bit packed, high bits ${first ? 'first' : 'last'}, bit ${bitK ? 'k' : '6-k'}` })));
|
|
68
|
+
const PACKINGS = [
|
|
69
|
+
...SEVEN_BIT.map((p) => ({ name: p.name, open: (m) => unpacked7(m, 0, p.first, p.bitK) })),
|
|
70
|
+
...SEVEN_BIT.filter((p) => p.first).map((p) => ({ name: `${p.name}, checksum last`, open: (m) => unpacked7(m.subarray(0, -1), 0, p.first, p.bitK) })),
|
|
71
|
+
{ name: 'nibbles, high first', open: (m) => joinedNibbles(m, 0, true) },
|
|
72
|
+
{ name: 'nibbles, low first', open: (m) => joinedNibbles(m, 0, false) },
|
|
73
|
+
{ name: 'three bytes per word, high byte first', open: (m) => threeByteWords(m, true) },
|
|
74
|
+
{ name: 'three bytes per word, low byte first', open: (m) => threeByteWords(m, false) },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const phases = (n) => Array.from({ length: n }, (_, i) => i);
|
|
78
|
+
|
|
79
|
+
export function wrappings() {
|
|
80
|
+
return [
|
|
81
|
+
{ name: 'as stored', open: (b) => b },
|
|
82
|
+
{ name: 'high bit set', open: (b) => eachByte(b, (x) => x & 0x7f) },
|
|
83
|
+
{ name: 'even bytes only', open: (b) => everyOther(b, 0) },
|
|
84
|
+
{ name: 'odd bytes only', open: (b) => everyOther(b, 1) },
|
|
85
|
+
...phases(2).map((p) => ({ name: `16-bit words swapped, from ${p}`, open: (b) => reversedWords(b, 2, p) })),
|
|
86
|
+
...phases(3).map((p) => ({ name: `24-bit words reversed, from ${p}`, open: (b) => reversedWords(b, 3, p) })),
|
|
87
|
+
...phases(4).map((p) => ({ name: `32-bit words reversed, from ${p}`, open: (b) => reversedWords(b, 4, p) })),
|
|
88
|
+
...phases(8).flatMap((p) => SEVEN_BIT.map((s) => ({ name: `${s.name}, from ${p}`, open: (b) => unpacked7(b, p, s.first, s.bitK) }))),
|
|
89
|
+
...phases(2).flatMap((p) => [true, false].map((first) => ({ name: `nibbles, ${first ? 'high' : 'low'} first, from ${p}`, open: (b) => joinedNibbles(b, p, first) }))),
|
|
90
|
+
...phases(LONGEST_HEADER + 1).flatMap((h) => PACKINGS.map((p) => ({ name: `sysex messages, ${h}-byte header, ${p.name}`, open: (b) => perMessage(b, h, p.open) }))),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function interleave(even, odd) {
|
|
95
|
+
const out = Buffer.alloc(even.length + odd.length);
|
|
96
|
+
for (let i = 0; i < even.length; i += 1) out[i * 2] = even[i];
|
|
97
|
+
for (let i = 0; i < odd.length; i += 1) out[i * 2 + 1] = odd[i];
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const distances = (b) => { const out = Buffer.alloc(Math.max(0, b.length - 1)); for (let i = 1; i < b.length; i += 1) out[i - 1] = (b[i] - b[i - 1]) & 255; return out; };
|
|
102
|
+
|
|
103
|
+
export function shiftedHits(buffer, text) {
|
|
104
|
+
const hay = distances(buffer);
|
|
105
|
+
const needle = distances(Buffer.from(text, 'latin1'));
|
|
106
|
+
const hits = [];
|
|
107
|
+
for (let at = hay.indexOf(needle); at !== -1 && hits.length < 64; at = hay.indexOf(needle, at + 1)) hits.push({ at, shift: (buffer[at] - text.charCodeAt(0)) & 255 });
|
|
108
|
+
return hits;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function entropyOf(b) {
|
|
112
|
+
const counts = new Array(256).fill(0);
|
|
113
|
+
for (const x of b) counts[x] += 1;
|
|
114
|
+
return counts.filter(Boolean).reduce((bits, n) => bits - (n / b.length) * Math.log2(n / b.length), 0);
|
|
115
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The fastest way into a firmware image or a dump: search it for the names the manual prints. Patch names, arp patterns, delay and reverb programs, menus,
|
|
3
|
+
// parameters. One name gives a table, the table's stride gives a record size, its order gives the parameter order, and what points at it leads to the code.
|
|
4
|
+
// node names-sweep.mjs <image> [<image>...] --names <file> [--pair] [--base 0x8000,...] [--banked] [--min 4] [--json]
|
|
5
|
+
// The names file has one name per line, `group<TAB>name` to keep kinds apart (patch, arp, reverb, menu, parameter), `#` for a comment.
|
|
6
|
+
// --pair reads two images as one interleaved EPROM pair. Every wrapping in lib/unwrap.mjs is tried, and the one that reads the most names wins.
|
|
7
|
+
// Names reading prove the unwrapping. Exit 1 when too few read under any wrapping, and the report says what was searched.
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { basename } from 'node:path';
|
|
10
|
+
import { entropyOf, interleave, shiftedHits, wrappings } from './lib/unwrap.mjs';
|
|
11
|
+
|
|
12
|
+
const MAX_HITS = 2048; // a common word is everywhere, and the cap must not cut off the one hit in the table
|
|
13
|
+
const SPAN_PER_NAME = 640; // bytes a name may take: a preset record's stride can be 512
|
|
14
|
+
const MAX_SPAN = 32768;
|
|
15
|
+
const FLAT = 7.5; // bits per byte: past this no text is showing and no structure either
|
|
16
|
+
const PROOF = 3; // names: one or two can read by chance in a large image, so fewer proves nothing
|
|
17
|
+
const POINTERS = [
|
|
18
|
+
{ name: 'u16 big-endian', width: 2, write: 'writeUInt16BE', top: 0xffff },
|
|
19
|
+
{ name: 'u16 little-endian', width: 2, write: 'writeUInt16LE', top: 0xffff },
|
|
20
|
+
{ name: 'u32 big-endian', width: 4, write: 'writeUInt32BE', top: 0xffffffff },
|
|
21
|
+
{ name: 'u32 little-endian', width: 4, write: 'writeUInt32LE', top: 0xffffffff },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function readNames(text, min = 4) {
|
|
25
|
+
return text.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')).map((l) => {
|
|
26
|
+
const [group, name] = l.includes('\t') ? l.split('\t').map((s) => s.trim()) : ['name', l];
|
|
27
|
+
return { group, name };
|
|
28
|
+
}).filter((n) => n.name.length >= min);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hitsIn(view, text) {
|
|
32
|
+
const hits = [];
|
|
33
|
+
for (let at = view.indexOf(text); at !== -1 && hits.length < MAX_HITS; at = view.indexOf(text, at + 1)) hits.push(at);
|
|
34
|
+
return hits;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A display does not print what a manual prints: it changes case, drops spaces and cuts a name to its column ("Decay Time" is `Decaytime`, "Filter Balance" `FilterBalanc`).
|
|
38
|
+
// So a name is searched as printed, squeezed, and cut to each column width. A cut under eight characters would match by chance.
|
|
39
|
+
const COLUMNS = [16, 15, 12, 10, 8];
|
|
40
|
+
export function displayForms(name) {
|
|
41
|
+
const squeezed = name.replace(/\s+/g, '');
|
|
42
|
+
const cuts = COLUMNS.filter((w) => w < squeezed.length).map((w) => squeezed.slice(0, w));
|
|
43
|
+
return [...new Set([name, squeezed, ...cuts].map((f) => f.toLowerCase()))];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Firmware also drops vowels and stops early (`BrickWll`, `ARP SWNG`, `MinWool`), so a name that did not read is tried loosely: a word's first three letters stand, vowels may go, and it may end early.
|
|
47
|
+
const VOWEL = /[aeiou]/;
|
|
48
|
+
const letter = (c, kept) => { const safe = c.replace(/[^a-z0-9]/g, '\\$&'); return kept && VOWEL.test(c) ? `${safe}?` : safe; };
|
|
49
|
+
function wordPattern(word) {
|
|
50
|
+
const letters = [...word];
|
|
51
|
+
const tail = letters.slice(3).reverse().reduce((inner, c) => `(?:${letter(c, true)}${inner})?`, '');
|
|
52
|
+
return letters.slice(0, 3).map((c, i) => letter(c, i > 0)).join('') + tail;
|
|
53
|
+
}
|
|
54
|
+
export function abbreviationOf(name) {
|
|
55
|
+
const words = name.toLowerCase().split(/\s+/).filter(Boolean);
|
|
56
|
+
return new RegExp(`(?<![a-z])${words.map(wordPattern).join('[ .]?')}(?![a-z])`, 'g');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function abbreviated(view, names) {
|
|
60
|
+
const text = view.toString('latin1').toLowerCase();
|
|
61
|
+
return names.filter((n) => n.name.length >= 5).map((n) => {
|
|
62
|
+
const hits = [...text.matchAll(abbreviationOf(n.name))].filter((m) => m[0].length >= Math.max(5, n.name.length / 2)).slice(0, MAX_HITS);
|
|
63
|
+
return { ...n, hits: hits.map((m) => m.index), abbreviated: hits[0]?.[0] };
|
|
64
|
+
}).filter((n) => n.hits.length);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const folded = (view) => Buffer.from(view.toString('latin1').toLowerCase(), 'latin1');
|
|
68
|
+
function found(names, view) {
|
|
69
|
+
const low = folded(view);
|
|
70
|
+
const hitsOf = (n) => { for (const form of displayForms(n.name)) { const hits = hitsIn(low, Buffer.from(form, 'latin1')); if (hits.length) return { hits, form }; } return null; };
|
|
71
|
+
return names.map((n) => ({ ...n, ...hitsOf(n) })).filter((n) => n.hits?.length);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// A custom character set: only names long enough for the letters' distances to mean anything, in each case the display might use.
|
|
75
|
+
function foundShifted(names, view) {
|
|
76
|
+
const shifted = names.filter((n) => n.name.length >= 6).map((n) => {
|
|
77
|
+
const all = [n.name, n.name.toUpperCase(), n.name.toLowerCase()].flatMap((form) => shiftedHits(view, form));
|
|
78
|
+
return { ...n, hits: all.map((h) => h.at), shift: all[0]?.shift };
|
|
79
|
+
}).filter((n) => n.hits.length);
|
|
80
|
+
const counts = new Map();
|
|
81
|
+
for (const n of shifted) counts.set(n.shift, (counts.get(n.shift) ?? 0) + 1);
|
|
82
|
+
const best = [...counts].sort((a, b) => b[1] - a[1])[0];
|
|
83
|
+
return best ? { shift: best[0], names: shifted.filter((n) => n.shift === best[0]) } : { names: [] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mostCommonGap(offsets) {
|
|
87
|
+
const counts = new Map();
|
|
88
|
+
for (let i = 1; i < offsets.length; i += 1) { const gap = offsets[i] - offsets[i - 1]; counts.set(gap, (counts.get(gap) ?? 0) + 1); }
|
|
89
|
+
const [gap, count] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
|
|
90
|
+
return count >= 2 && count >= (offsets.length - 1) / 5 ? { gap, count, of: offsets.length - 1 } : null; // a gap a few pairs share is no stride, and reading one invents a table
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// How many neighbours in the names file's order also sit in that order in the image.
|
|
94
|
+
function orderHeld(hits) {
|
|
95
|
+
let held = 0;
|
|
96
|
+
for (let i = 1; i < hits.length; i += 1) if (hits[i].hits[0] > hits[i - 1].hits[0]) held += 1;
|
|
97
|
+
return { held, of: Math.max(0, hits.length - 1) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Where the most different names sit closest: the table itself. A common word also turns up in presets and messages, so the first hit is not the table.
|
|
101
|
+
function densest(hits, span) {
|
|
102
|
+
const all = hits.flatMap((n) => n.hits.map((at) => ({ at, name: n.name }))).sort((a, b) => a.at - b.at);
|
|
103
|
+
const held = new Map();
|
|
104
|
+
const drop = (name) => { if (held.get(name) > 1) held.set(name, held.get(name) - 1); else held.delete(name); };
|
|
105
|
+
let best = null;
|
|
106
|
+
let from = 0;
|
|
107
|
+
for (let to = 0; to < all.length; to += 1) {
|
|
108
|
+
held.set(all[to].name, (held.get(all[to].name) ?? 0) + 1);
|
|
109
|
+
while (all[to].at - all[from].at > span || held.get(all[from].name) > 1) { drop(all[from].name); from += 1; } // a stray earlier copy of a name is no part of the table
|
|
110
|
+
const width = all[to].at - all[from].at;
|
|
111
|
+
if (held.size > (best?.names ?? 1) || (held.size === best?.names && width < best.to - best.from)) best = { names: held.size, from: all[from].at, to: all[to].at, first: from, last: to };
|
|
112
|
+
}
|
|
113
|
+
return best && withNeighbours(all, best);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The tightest window drops a first entry whose name comes again later in the table, so it takes back any hit as close as the table's own entries. A stray copy is further off.
|
|
117
|
+
function withNeighbours(all, { names, first, last }) {
|
|
118
|
+
const gaps = all.slice(first + 1, last + 1).map((h, i) => h.at - all[first + i].at).sort((a, b) => a - b);
|
|
119
|
+
const reach = 2 * (gaps[Math.floor(gaps.length / 2)] ?? 0);
|
|
120
|
+
let [from, to] = [first, last];
|
|
121
|
+
while (from > 0 && all[from].at - all[from - 1].at <= reach) from -= 1;
|
|
122
|
+
while (to < all.length - 1 && all[to + 1].at - all[to].at <= reach) to += 1;
|
|
123
|
+
return { names, from: all[from].at, to: all[to].at };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Every place an address of one of the hits is stored, in this pointer format.
|
|
127
|
+
function seatsOf(view, offsets, base, kind) {
|
|
128
|
+
const encoded = (o) => { const b = Buffer.alloc(kind.width); b[kind.write](o + base); return b; };
|
|
129
|
+
return offsets.filter((o) => o + base >= 0 && o + base <= kind.top).flatMap((o) => hitsIn(view, encoded(o))).sort((a, b) => a - b);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Where addresses of the hits sit close together: that run is the table the code indexes.
|
|
133
|
+
function pointerTable(view, offsets, bases) {
|
|
134
|
+
let best = null;
|
|
135
|
+
for (const base of bases) {
|
|
136
|
+
for (const kind of POINTERS.filter((k) => base >= 0 || k.width === 2)) { // a bank is reached through a 16-bit address
|
|
137
|
+
let run = [];
|
|
138
|
+
for (const seat of seatsOf(view, offsets, base, kind)) {
|
|
139
|
+
run = run.length && seat - run[run.length - 1] <= kind.width * 4 ? [...run, seat] : [seat];
|
|
140
|
+
if (run.length >= 4 && run.length > (best?.entries ?? 0)) best = { at: run[0], entries: run.length, kind: kind.name, base };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return best;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Printable runs after the last hit: the entries of the table the manual does not list.
|
|
148
|
+
function nextRuns(view, from, count = 3) {
|
|
149
|
+
const text = view.subarray(from, from + 512).toString('latin1');
|
|
150
|
+
return (text.match(/[\x20-\x7e]{4,}/g) ?? []).slice(1, count + 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Hits in one unbroken printable run are a pool or a fixed-width blob with no separators: read the run whole, because a name that fills its field has no terminator.
|
|
154
|
+
function poolOf(view, offsets) {
|
|
155
|
+
const printable = (x) => x >= 0x20 && x < 0x7f;
|
|
156
|
+
const runs = new Map();
|
|
157
|
+
for (const at of offsets) {
|
|
158
|
+
let from = at;
|
|
159
|
+
let to = at;
|
|
160
|
+
while (from > 0 && printable(view[from - 1])) from -= 1;
|
|
161
|
+
while (to < view.length && printable(view[to])) to += 1;
|
|
162
|
+
runs.set(from, { from, to, hits: (runs.get(from)?.hits ?? 0) + 1 });
|
|
163
|
+
}
|
|
164
|
+
const best = [...runs.values()].sort((a, b) => b.hits - a.hits)[0];
|
|
165
|
+
return best?.hits >= 2 ? best : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function groupsOf(hits, view, bases, wanted) {
|
|
169
|
+
return [...new Set(wanted.map((n) => n.group))].map((group) => {
|
|
170
|
+
const mine = hits.filter((n) => n.group === group);
|
|
171
|
+
const offsets = [...new Set(mine.flatMap((n) => n.hits))].sort((a, b) => a - b);
|
|
172
|
+
const last = offsets[offsets.length - 1];
|
|
173
|
+
const dense = densest(mine, Math.min(MAX_SPAN, mine.length * SPAN_PER_NAME));
|
|
174
|
+
const inTable = dense ? mine.map((n) => ({ ...n, hits: n.hits.filter((at) => at >= dense.from && at <= dense.to) })).filter((n) => n.hits.length) : mine;
|
|
175
|
+
return {
|
|
176
|
+
group, found: mine.length, of: wanted.filter((n) => n.group === group).length, first: offsets[0], last, dense,
|
|
177
|
+
stride: mostCommonGap(offsets), order: orderHeld(inTable), pointers: offsets.length >= 4 ? pointerTable(view, offsets, bases) : null,
|
|
178
|
+
next: offsets.length ? nextRuns(view, last) : [], pool: poolOf(view, offsets),
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function sweep(image, names, { bases = [0] } = {}) {
|
|
184
|
+
const tried = wrappings().map((w) => { const view = w.open(image); return { name: w.name, view, hits: found(names, view) }; });
|
|
185
|
+
const relative = foundShifted(names, image);
|
|
186
|
+
tried.push({ name: `custom character set, shift ${relative.shift ?? '-'}`, view: image, hits: relative.names });
|
|
187
|
+
tried.sort((a, b) => b.hits.length - a.hits.length);
|
|
188
|
+
const best = tried[0];
|
|
189
|
+
const read = new Set(best.hits.map((n) => n.name));
|
|
190
|
+
const shortened = best.hits.length >= PROOF ? abbreviated(best.view, names.filter((n) => !read.has(n.name))) : [];
|
|
191
|
+
best.hits.push(...shortened);
|
|
192
|
+
const seen = new Set(best.hits.map((n) => n.name));
|
|
193
|
+
return {
|
|
194
|
+
names: names.length, wrapping: best.hits.length ? best.name : null, reads: best.hits.length, entropy: entropyOf(image), shortened: shortened.map((n) => [n.name, n.abbreviated]),
|
|
195
|
+
proven: best.hits.length >= Math.min(PROOF, names.length),
|
|
196
|
+
tried: tried.map((t) => ({ name: t.name, reads: t.hits.length })),
|
|
197
|
+
groups: best.hits.length ? groupsOf(best.hits, best.view, bases, names) : [],
|
|
198
|
+
missing: names.filter((n) => !seen.has(n.name)).map((n) => n.name),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const hex = (n) => `0x${n.toString(16).toUpperCase()}`;
|
|
203
|
+
function groupLine(g) {
|
|
204
|
+
if (!g.found) return ` ${g.group}: 0 of ${g.of}`;
|
|
205
|
+
const parts = [` ${g.group}: ${g.found} of ${g.of}, ${hex(g.first)}..${hex(g.last)}`];
|
|
206
|
+
if (g.dense) parts.push(`densest ${g.dense.names} names at ${hex(g.dense.from)}..${hex(g.dense.to)}`);
|
|
207
|
+
if (g.stride) parts.push(`stride ${g.stride.gap} (${g.stride.count} of ${g.stride.of} gaps)`);
|
|
208
|
+
if (g.order.of) parts.push(`the manual's order holds ${g.order.held} of ${g.order.of}`);
|
|
209
|
+
if (g.pointers) parts.push(`pointers at ${hex(g.pointers.at)} (${g.pointers.entries}, ${g.pointers.kind}, base ${g.pointers.base < 0 ? '-' : ''}${hex(Math.abs(g.pointers.base))})`);
|
|
210
|
+
if (g.pool) parts.push(`${g.pool.hits} in one run ${hex(g.pool.from)}..${hex(g.pool.to)} (${g.pool.to - g.pool.from} bytes, read it whole)`);
|
|
211
|
+
if (g.next.length) parts.push(`then ${g.next.map((s) => JSON.stringify(s)).join(' ')}`);
|
|
212
|
+
return parts.join('; ');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function nothingRead(label, r) {
|
|
216
|
+
const searched = r.tried.map((t) => t.name).join(', ');
|
|
217
|
+
const said = r.reads ? `only ${r.reads} of ${r.names} names read, which chance can do, under` : `none of ${r.names} names reads under`;
|
|
218
|
+
const flat = r.entropy >= FLAT ? ' It looks compressed or encrypted: read what loads it, and if nothing does, record that the text is not in these files and stop.' : '';
|
|
219
|
+
return `${label}: ${said} any of ${r.tried.length} wrappings. Searched: ${searched}.\n` +
|
|
220
|
+
`The image is still wrapped some other way, or the names file is wrong. This says nothing about what the machine holds. Entropy ${r.entropy.toFixed(2)} bits per byte.${flat}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function report(label, r) {
|
|
224
|
+
if (!r.proven) return nothingRead(label, r);
|
|
225
|
+
const others = r.tried.filter((t) => t.reads && t.name !== r.wrapping).slice(0, 4).map((t) => `${t.name} ${t.reads}`).join(', ');
|
|
226
|
+
const more = r.missing.length > 8 ? `, and ${r.missing.length - 8} more` : '';
|
|
227
|
+
return [
|
|
228
|
+
`${label}: ${r.reads} of ${r.names} names read ${r.wrapping} (${r.tried.length} wrappings tried${others ? `; also ${others}` : ''})`,
|
|
229
|
+
...r.groups.map(groupLine),
|
|
230
|
+
...(r.shortened.length ? [` abbreviated: ${r.shortened.slice(0, 8).map(([name, as]) => `${name} as "${as}"`).join(', ')}`] : []),
|
|
231
|
+
r.missing.length ? ` not found: ${r.missing.slice(0, 8).join(', ')}${more}` : ' every name found',
|
|
232
|
+
].join('\n');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// A CPU sees a bank of a larger ROM through a window, so an address is the file offset less the bank plus the window: every plausible pairing, as a base.
|
|
236
|
+
const BANK_STEP = 0x4000;
|
|
237
|
+
const WINDOWS = [0x0000, 0x4000, 0x8000, 0xc000];
|
|
238
|
+
function bankedBases(size) {
|
|
239
|
+
const bases = new Set([0]);
|
|
240
|
+
for (let bank = 0; bank < size; bank += BANK_STEP) for (const window of WINDOWS) bases.add(window - bank);
|
|
241
|
+
return [...bases];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function imagesOf(files, pair) {
|
|
245
|
+
if (!pair) return files.map((f) => [basename(f), readFileSync(f)]);
|
|
246
|
+
return [[`${basename(files[0])}+${basename(files[1])}`, interleave(readFileSync(files[0]), readFileSync(files[1]))]];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function main(argv) {
|
|
250
|
+
const value = (flag) => { const i = argv.indexOf(flag); return i === -1 ? undefined : argv[i + 1]; };
|
|
251
|
+
const files = argv.filter((a, i) => !a.startsWith('--') && !['--names', '--base', '--min'].includes(argv[i - 1]));
|
|
252
|
+
if (!files.length || !value('--names')) {
|
|
253
|
+
console.error('usage: names-sweep.mjs <image>... --names <file> [--pair] [--base 0x8000,...] [--banked] [--min 4] [--json]');
|
|
254
|
+
return 2;
|
|
255
|
+
}
|
|
256
|
+
const names = readNames(readFileSync(value('--names'), 'utf8'), Number(value('--min') ?? 4));
|
|
257
|
+
const given = (value('--base') ?? '0').split(',').map((b) => Number.parseInt(b));
|
|
258
|
+
const basesFor = (image) => (argv.includes('--banked') ? bankedBases(image.length) : given);
|
|
259
|
+
const results = imagesOf(files, argv.includes('--pair')).map(([label, image]) => [label, sweep(image, names, { bases: basesFor(image) })]);
|
|
260
|
+
console.log(argv.includes('--json') ? JSON.stringify(Object.fromEntries(results), null, 2) : results.map(([label, r]) => report(label, r)).join('\n\n'));
|
|
261
|
+
return results.some(([, r]) => r.proven) ? 0 : 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (process.argv[1] && import.meta.url.endsWith(basename(process.argv[1]))) process.exit(main(process.argv.slice(2)));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-mktemp",
|
|
3
|
+
"why": "A fixed path in /tmp collides with another run, and another user can plant it.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": ">>?\\s*\\\"?/tmp/\\w",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "echo x > /tmp/out.txt",
|
|
17
|
+
"good": "out=$(mktemp)\necho x > \"$out\""
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-no-backticks",
|
|
3
|
+
"why": "Backticks do not nest and hide in quoted text. $(...) does the same thing, and reads.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": "^[^#'\\n]*`[^`\\n]+`",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "now=`date`",
|
|
17
|
+
"good": "now=$(date)"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-no-eval",
|
|
3
|
+
"why": "eval runs whatever a variable holds, with the script's rights.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": "^[^#\\n]*(?:^|[;&|\\s])eval\\s",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "eval \"$cmd\"",
|
|
17
|
+
"good": "\"$@\""
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-no-parse-ls",
|
|
3
|
+
"why": "ls output breaks on spaces and newlines. A glob does not.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": "\\bfor\\s+\\w+\\s+in\\s+\\$\\(\\s*ls\\b|\\bls\\b[^|\\n]*\\|\\s*(?:while|xargs)\\b",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "for f in $(ls *.txt); do echo \"$f\"; done",
|
|
17
|
+
"good": "for f in *.txt; do echo \"$f\"; done"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-no-pipe-to-shell",
|
|
3
|
+
"why": "A download piped into a shell runs whatever the server sends, unread.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": "\\b(?:curl|wget)\\b[^\\n|]*\\|\\s*(?:sudo\\s+)?(?:ba|z|da)?sh\\b",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "curl -fsSL https://example.com/i.sh | sh",
|
|
17
|
+
"good": "curl -fsSL -o i.sh https://example.com/i.sh"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sh-no-program-in-heredoc",
|
|
3
|
+
"why": "A program in a heredoc cannot be linted, tested or reused. Give it its own file and call it.",
|
|
4
|
+
"rule": ".claude/rules/shell-strict-and-quoted.md",
|
|
5
|
+
"concepts": [
|
|
6
|
+
"shell"
|
|
7
|
+
],
|
|
8
|
+
"globs": [],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"**/node_modules/**",
|
|
11
|
+
"**/.venv/**",
|
|
12
|
+
"**/venv/**"
|
|
13
|
+
],
|
|
14
|
+
"pattern": "\\b(?:python3?|node|ruby|perl)\\s+(?:-\\w+\\s+)*-(?:\\s[^\\n]*)?<<",
|
|
15
|
+
"flags": "m",
|
|
16
|
+
"bad": "python3 - \"$out\" <<'EOF'\nprint(1)\nEOF",
|
|
17
|
+
"good": "python3 tools/read_out.py \"$out\""
|
|
18
|
+
}
|