@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,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gnu-shell (SessionStart; global by design): the Bash tool runs bash 5 with the GNU tools, not zsh with the BSD ones.
|
|
3
|
+
//
|
|
4
|
+
// "can you do a proper skill/hook for zsh because this seems to happen alot?". Claude Code's Bash tool runs
|
|
5
|
+
// the login shell, zsh on this Mac: `$VAR` holding several words is not split, an unmatched glob is an error, and
|
|
6
|
+
// `sed -i`, `date -d`, `stat -c` are the BSD tools'. The fix has two halves:
|
|
7
|
+
// 1. THE SHELL: settings.json `env` sets CLAUDE_CODE_SHELL and SHELL to /opt/homebrew/bin/bash (Homebrew's bash 5).
|
|
8
|
+
// 2. THE TOOLS: this hook writes one `export PATH=...` to the file CLAUDE_ENV_FILE names, which Claude Code sources
|
|
9
|
+
// before every Bash call: ~/.claude/bin first (buildq, the machine-wide build queue), then the `libexec/gnubin`
|
|
10
|
+
// folder of every GNU formula installed, so sed, date, stat, awk, tar, find, xargs, which are the GNU builds; and a
|
|
11
|
+
// line that warns on EVERY command while the session runs zsh (WB-41). The user's own terminal is not touched.
|
|
12
|
+
// It says so at the session's start when it cannot deliver: no Homebrew, a formula missing, the shell not bash 5.
|
|
13
|
+
// On Linux (KWB-27) the system's sed, date, stat and awk are the GNU builds already: no Homebrew is looked for, the PATH line
|
|
14
|
+
// puts only ~/.claude/bin first, and the shell is still checked for bash 5 (any bash 5, not Homebrew's).
|
|
15
|
+
// node gnu-shell.mjs --selftest
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { appendFileSync, chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { basename, join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
export const FORMULAS = ['coreutils', 'findutils', 'gawk', 'gnu-sed', 'gnu-tar', 'gnu-which', 'grep'];
|
|
22
|
+
const BASH = '/opt/homebrew/bin/bash';
|
|
23
|
+
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
|
|
24
|
+
const gnubin = (pre, f) => join(pre, 'opt', f, 'libexec', 'gnubin');
|
|
25
|
+
|
|
26
|
+
// Homebrew's prefix, found on the disk, not by asking brew (a hook started by an app may not have brew on PATH).
|
|
27
|
+
export function prefix() {
|
|
28
|
+
return ['/opt/homebrew', '/usr/local'].find((p) => isDir(join(p, 'opt')) && FORMULAS.some((f) => isDir(gnubin(p, f)))) ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function bashMajor(path) {
|
|
32
|
+
try { return Number(execFileSync(path, ['--version'], { encoding: 'utf8', timeout: 5000 }).match(/version (\d+)\./)?.[1] ?? 0); } catch { return 0; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const shellSaid = (sh, mac) => `gnu-shell: the Bash tool's shell is ${sh || 'unknown'}, not ${mac ? `Homebrew's bash 5 (${BASH})` : 'bash 5'}: ` +
|
|
36
|
+
'`$VAR` lists do not split (use ${=VAR} or an array) and an unmatched glob is an error. The env block in .claude/settings.json takes effect from the next session.';
|
|
37
|
+
const shellOk = (sh) => basename(sh ?? '') === 'bash' && bashMajor(sh) >= 5;
|
|
38
|
+
const ZSH_WARNING = '[ -n "$ZSH_VERSION" ] && echo "gnu-shell: THIS SESSION RUNS ZSH, not bash 5 (variables are not split into words, an unmatched glob is an error). Restart Claude Code to get bash 5." >&2\n';
|
|
39
|
+
|
|
40
|
+
// Linux: the tools are GNU already; ~/.claude/bin first, the zsh warning, and the shell checked.
|
|
41
|
+
function runLinux(envFile, shell) {
|
|
42
|
+
const said = [];
|
|
43
|
+
if (envFile) {
|
|
44
|
+
appendFileSync(envFile, 'export PATH="$HOME/.claude/bin:${PATH}"\n');
|
|
45
|
+
appendFileSync(envFile, ZSH_WARNING);
|
|
46
|
+
} else said.push('gnu-shell: CLAUDE_ENV_FILE is not set, so ~/.claude/bin could not be put first on the Bash tool\'s PATH.');
|
|
47
|
+
if (!shellOk(shell)) said.push(shellSaid(shell, false));
|
|
48
|
+
return said;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Writes the PATH line; returns what must be said (empty when all is as it should be).
|
|
52
|
+
export function run(envFile, shell, { platform = process.platform, pre } = {}) {
|
|
53
|
+
if (platform !== 'darwin') return runLinux(envFile, shell);
|
|
54
|
+
if (pre === undefined) pre = prefix();
|
|
55
|
+
const said = [];
|
|
56
|
+
if (!pre) {
|
|
57
|
+
return ["gnu-shell: no Homebrew found (/opt/homebrew or /usr/local): this session's sed, date, stat and awk are the macOS (BSD) builds, " +
|
|
58
|
+
"not GNU. Write BSD-safe commands (sed -i '' ..., no date -d)."];
|
|
59
|
+
}
|
|
60
|
+
const have = FORMULAS.filter((f) => isDir(gnubin(pre, f))).map((f) => gnubin(pre, f));
|
|
61
|
+
const missing = FORMULAS.filter((f) => !isDir(gnubin(pre, f)));
|
|
62
|
+
if (envFile && have.length) {
|
|
63
|
+
appendFileSync(envFile, `export PATH="$HOME/.claude/bin:${have.join(':')}:\${PATH}"\n`);
|
|
64
|
+
appendFileSync(envFile, ZSH_WARNING);
|
|
65
|
+
}
|
|
66
|
+
if (missing.length) {
|
|
67
|
+
said.push(`gnu-shell: GNU formula(s) not installed: ${missing.join(', ')}; those tools are the macOS (BSD) builds this session. ` +
|
|
68
|
+
`Install them with: brew install ${missing.join(' ')}`);
|
|
69
|
+
}
|
|
70
|
+
if (!envFile) said.push('gnu-shell: CLAUDE_ENV_FILE is not set, so the GNU tools could not be put first on the Bash tool\'s PATH.');
|
|
71
|
+
if (!shellOk(shell)) said.push(shellSaid(shell, true));
|
|
72
|
+
return said;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A fake bash that reports the given version: the selftest never depends on this machine's /bin/bash (KWB-27).
|
|
76
|
+
function fakeBash(dir, version) {
|
|
77
|
+
const file = join(dir, `bash-${version}`, 'bash');
|
|
78
|
+
mkdirSync(join(dir, `bash-${version}`), { recursive: true });
|
|
79
|
+
writeFileSync(file, `#!/bin/sh\necho 'GNU bash, version ${version}(1)-release'\n`);
|
|
80
|
+
chmodSync(file, 0o755);
|
|
81
|
+
return file;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function selftest() {
|
|
85
|
+
const bad = [];
|
|
86
|
+
const expect = (name, ok) => { if (!ok) bad.push(name); };
|
|
87
|
+
const d = mkdtempSync(join(tmpdir(), 'gnu-shell-'));
|
|
88
|
+
const bash5 = fakeBash(d, '5.2.37');
|
|
89
|
+
const bash32 = fakeBash(d, '3.2.57');
|
|
90
|
+
const notBash5 = (said) => said.some((s) => s.includes('not ') && s.includes('bash 5'));
|
|
91
|
+
// macOS
|
|
92
|
+
const pre = join(d, 'brew');
|
|
93
|
+
for (const f of FORMULAS) mkdirSync(gnubin(pre, f), { recursive: true });
|
|
94
|
+
const mac = (file, shell) => run(join(d, file), shell, { platform: 'darwin', pre });
|
|
95
|
+
const said = mac('env.sh', bash5);
|
|
96
|
+
const text = readFileSync(join(d, 'env.sh'), 'utf8');
|
|
97
|
+
const line = text.split('\n')[0];
|
|
98
|
+
expect("mac: every formula's gnubin written first on PATH", FORMULAS.every((f) => line.includes(`/opt/${f}/libexec/gnubin`)) && line.endsWith(':${PATH}"'));
|
|
99
|
+
expect('mac: ~/.claude/bin first', line.startsWith('export PATH="$HOME/.claude/bin:'));
|
|
100
|
+
expect('mac: a zsh session is warned on every command (WB-41)', text.includes('[ -n "$ZSH_VERSION" ]') && text.toUpperCase().includes('RESTART'));
|
|
101
|
+
expect('mac: bash 5 and every formula: nothing said', said.length === 0);
|
|
102
|
+
expect('mac: no Homebrew is said', run(join(d, 'e1.sh'), bash5, { platform: 'darwin', pre: null }).some((s) => s.includes('no Homebrew')));
|
|
103
|
+
expect("mac: zsh as the shell is said", mac('e3.sh', '/bin/zsh').some((s) => s.includes("not Homebrew's bash 5")));
|
|
104
|
+
expect("mac: Apple's bash 3.2 is said", mac('e4.sh', bash32).some((s) => s.includes("not Homebrew's bash 5")));
|
|
105
|
+
rmSync(gnubin(pre, 'gnu-sed'), { recursive: true });
|
|
106
|
+
expect('mac: a missing formula is said, by name', mac('e2.sh', bash5).some((s) => s.includes('gnu-sed')));
|
|
107
|
+
// Linux (KWB-27): the system tools are GNU; no Homebrew is looked for, even when one is there
|
|
108
|
+
const linux = (file, shell) => run(join(d, file), shell, { platform: 'linux', pre });
|
|
109
|
+
const lsaid = linux('l1.sh', bash5);
|
|
110
|
+
const ltext = readFileSync(join(d, 'l1.sh'), 'utf8');
|
|
111
|
+
expect('linux: bash 5: nothing said (no Homebrew, no BSD tools)', lsaid.length === 0);
|
|
112
|
+
expect('linux: ~/.claude/bin first, no gnubin', ltext.split('\n')[0] === 'export PATH="$HOME/.claude/bin:${PATH}"');
|
|
113
|
+
expect('linux: a zsh session is warned on every command', ltext.includes('[ -n "$ZSH_VERSION" ]'));
|
|
114
|
+
expect('linux: zsh as the shell is said', notBash5(linux('l2.sh', '/usr/bin/zsh')));
|
|
115
|
+
expect('linux: bash 3.2 is said', notBash5(linux('l3.sh', bash32)));
|
|
116
|
+
expect('linux: no env file is said', run(undefined, bash5, { platform: 'linux' }).some((s) => s.includes('CLAUDE_ENV_FILE')));
|
|
117
|
+
rmSync(d, { recursive: true, force: true });
|
|
118
|
+
for (const b of bad) console.error(`FAIL ${b}`);
|
|
119
|
+
console.log(`gnu-shell selftest: ${bad.length ? `${bad.length} wrong` : 'OK'}`);
|
|
120
|
+
return bad.length ? 1 : 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function main() {
|
|
124
|
+
if (process.argv.includes('--selftest')) return selftest();
|
|
125
|
+
try { readFileSync(0, 'utf8'); } catch { /* no stdin */ }
|
|
126
|
+
const said = run(process.env.CLAUDE_ENV_FILE, process.env.CLAUDE_CODE_SHELL || process.env.SHELL);
|
|
127
|
+
if (said.length) console.log(said.join('\n')); // SessionStart: what is printed reaches the session's context
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.exit(main());
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// program-read: a small reader of an inline program (python, node, perl, ruby) for program-writes.mjs (KWB-8). It never
|
|
2
|
+
// runs the program; it reads string literals, splits statements, and evaluates the simple expressions a file-writing
|
|
3
|
+
// program uses (a literal, a variable, Path(...) and joins, a file's text read, .replace chains, + of strings).
|
|
4
|
+
// scan(code, lang) -> { code, lits } code: the program with every literal replaced by \x01n\x02 and comments blanked;
|
|
5
|
+
// lits[n]: its text, or null when it interpolates (f-string, ${}, "$x")
|
|
6
|
+
// statements(code) -> [string] split at newlines and ; outside brackets
|
|
7
|
+
// splitArgs(text) -> [string] top-level comma split of a call's arguments
|
|
8
|
+
// closeOf(text, open) -> index the bracket that closes text[open]
|
|
9
|
+
// evaluate(expr, ctx) -> value | null value: { t: 'str'|'path', v }; ctx: { env, lang, at, readText }
|
|
10
|
+
const PAIRS = { '(': ')', '[': ']', '{': '}' };
|
|
11
|
+
|
|
12
|
+
function unescape(s, lang) {
|
|
13
|
+
return s.replace(/\\(\n|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|.)/gs, (m, e) => {
|
|
14
|
+
if (e === '\n') return '';
|
|
15
|
+
if (e[0] === 'x' && e.length === 3) return String.fromCharCode(parseInt(e.slice(1), 16));
|
|
16
|
+
if (e[0] === 'u' && e.length === 5) return String.fromCharCode(parseInt(e.slice(1), 16));
|
|
17
|
+
const map = { n: '\n', t: '\t', r: '\r', 0: '\0', '\\': '\\', "'": "'", '"': '"', '`': '`', $: '$' };
|
|
18
|
+
if (e in map) return map[e];
|
|
19
|
+
return lang === 'python' ? m : e;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function scan(src, lang) {
|
|
24
|
+
const lits = [];
|
|
25
|
+
let out = '';
|
|
26
|
+
let i = 0;
|
|
27
|
+
const hash = lang !== 'node';
|
|
28
|
+
while (i < src.length) {
|
|
29
|
+
const c = src[i];
|
|
30
|
+
if ((hash && c === '#') || (!hash && c === '/' && src[i + 1] === '/')) {
|
|
31
|
+
while (i < src.length && src[i] !== '\n') i += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!hash && c === '/' && src[i + 1] === '*') { const j = src.indexOf('*/', i + 2); i = j < 0 ? src.length : j + 2; continue; }
|
|
35
|
+
let pre = '';
|
|
36
|
+
if (lang === 'python') pre = src.slice(i).match(/^[rRbBuUfF]{0,2}(?=['"])/)?.[0] ?? '';
|
|
37
|
+
if (pre && /\w/.test(src[i - 1] ?? '')) pre = '';
|
|
38
|
+
const q0 = src[i + pre.length];
|
|
39
|
+
const quote = q0 === "'" || q0 === '"' || (q0 === '`' && lang === 'node') ? q0 : null;
|
|
40
|
+
if (!quote || (pre === '' && /\w/.test(src[i - 1] ?? '') && lang === 'python')) { out += c; i += 1; continue; }
|
|
41
|
+
const start = i + pre.length;
|
|
42
|
+
const triple = lang === 'python' && src.startsWith(quote.repeat(3), start);
|
|
43
|
+
const open = triple ? 3 : 1;
|
|
44
|
+
let j = start + open;
|
|
45
|
+
while (j < src.length && !(triple ? src.startsWith(quote.repeat(3), j) : src[j] === quote) && (triple || quote === '`' || src[j] !== '\n')) j += src[j] === '\\' ? 2 : 1;
|
|
46
|
+
const raw = src.slice(start + open, j);
|
|
47
|
+
i = Math.min(src.length, j + open);
|
|
48
|
+
const rawFlag = /[rR]/.test(pre);
|
|
49
|
+
let v;
|
|
50
|
+
if (/[fF]/.test(pre) && /[{}]/.test(raw)) v = null;
|
|
51
|
+
else if (quote === '`' && raw.includes('${')) v = null;
|
|
52
|
+
else if ((lang === 'perl' || lang === 'ruby') && quote === '"' && /[$@]|#\{/.test(raw)) v = null;
|
|
53
|
+
else if ((lang === 'perl' || lang === 'ruby') && quote === "'") v = raw.replace(/\\([\\'])/g, '$1');
|
|
54
|
+
else v = rawFlag ? raw : unescape(raw, lang);
|
|
55
|
+
out += `\x01${lits.length}\x02`;
|
|
56
|
+
lits.push(v);
|
|
57
|
+
}
|
|
58
|
+
return { code: out, lits };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function closeOf(text, open) {
|
|
62
|
+
const want = PAIRS[text[open]];
|
|
63
|
+
let depth = 0;
|
|
64
|
+
for (let k = open; k < text.length; k += 1) {
|
|
65
|
+
if (PAIRS[text[k]]) depth += 1;
|
|
66
|
+
else if (')]}'.includes(text[k])) { depth -= 1; if (depth === 0) return text[k] === want ? k : -1; }
|
|
67
|
+
}
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function splitTop(text, seps) {
|
|
72
|
+
const out = [];
|
|
73
|
+
let depth = 0;
|
|
74
|
+
let cur = '';
|
|
75
|
+
for (const c of text) {
|
|
76
|
+
if (PAIRS[c]) depth += 1;
|
|
77
|
+
else if (')]}'.includes(c)) depth -= 1;
|
|
78
|
+
if (depth <= 0 && seps.includes(c)) { out.push(cur); cur = ''; } else cur += c;
|
|
79
|
+
}
|
|
80
|
+
out.push(cur);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const statements = (code) => splitTop(code, '\n;').map((s) => s.trim()).filter(Boolean);
|
|
85
|
+
export const splitArgs = (text) => (text.trim() ? splitTop(text, ',').map((s) => s.trim()) : []);
|
|
86
|
+
|
|
87
|
+
// A postfix chain: a primary (a literal, a name, a parenthesised expression) and its .name, (args) and [index] steps.
|
|
88
|
+
function chain(expr) {
|
|
89
|
+
let k = 0;
|
|
90
|
+
let primary;
|
|
91
|
+
const lit = expr.match(/^\x01(\d+)\x02/);
|
|
92
|
+
const name = expr.match(/^[A-Za-z_$][\w$]*/);
|
|
93
|
+
if (lit) { primary = { lit: Number(lit[1]) }; k = lit[0].length; }
|
|
94
|
+
else if (expr[0] === '(') { const e = closeOf(expr, 0); if (e < 0) return null; primary = { group: expr.slice(1, e) }; k = e + 1; }
|
|
95
|
+
else if (name) { primary = { name: name[0] }; k = name[0].length; }
|
|
96
|
+
else return null;
|
|
97
|
+
const steps = [];
|
|
98
|
+
while (k < expr.length) {
|
|
99
|
+
const rest = expr.slice(k);
|
|
100
|
+
const dot = rest.match(/^\s*(?:\.|->|::)\s*([A-Za-z_$][\w$]*)/);
|
|
101
|
+
if (dot) { steps.push({ name: dot[1] }); k += dot[0].length; continue; }
|
|
102
|
+
const ws = rest.match(/^\s*/)[0].length;
|
|
103
|
+
if (rest[ws] === '(' || rest[ws] === '[') {
|
|
104
|
+
const e = closeOf(expr, k + ws);
|
|
105
|
+
if (e < 0) return null;
|
|
106
|
+
steps.push(rest[ws] === '(' ? { args: splitArgs(expr.slice(k + ws + 1, e)) } : { index: expr.slice(k + ws + 1, e) });
|
|
107
|
+
k = e + 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return { primary, steps };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const str = (v) => (v && (v.t === 'str' || v.t === 'path') ? v.v : null);
|
|
116
|
+
const joinPaths = (parts) => (parts.some((p) => p === null) ? null : parts.reduce((a, b) => (b.startsWith('/') ? b : a ? `${a.replace(/\/$/, '')}/${b}` : b), ''));
|
|
117
|
+
const PATH_FNS = /^(?:(?:pathlib\.)?(?:Pure)?(?:Posix)?Path|os\.path\.join|path\.join|path\.resolve|join|resolve|File\.join|os\.path\.expanduser|os\.path\.abspath|path\.normalize)$/;
|
|
118
|
+
const READS = /^(?:(?:fs|fsp|fs\.promises)\.)?readFileSync$|^File\.read$|^IO\.read$/;
|
|
119
|
+
|
|
120
|
+
export function evaluate(expr, ctx) {
|
|
121
|
+
const e = expr.trim().replace(/^await\s+/, '');
|
|
122
|
+
if (!e) return null;
|
|
123
|
+
const plus = splitTop(e, '+');
|
|
124
|
+
if (plus.length > 1 && ctx.lang !== 'perl') {
|
|
125
|
+
const vs = plus.map((p) => str(evaluate(p, ctx)));
|
|
126
|
+
return vs.some((v) => v === null) ? null : { t: 'str', v: vs.join('') };
|
|
127
|
+
}
|
|
128
|
+
if (ctx.lang === 'python') {
|
|
129
|
+
const slash = splitTop(e, '/');
|
|
130
|
+
if (slash.length > 1) {
|
|
131
|
+
const vs = slash.map((p) => evaluate(p, ctx));
|
|
132
|
+
return vs[0]?.t === 'path' ? { t: 'path', v: joinPaths(vs.map(str)) } : null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const c = chain(e);
|
|
136
|
+
if (!c) return null;
|
|
137
|
+
// callee: the dotted name while the chain is still a plain name (os.path.join, p.read_text); recv: the value before its last dot
|
|
138
|
+
let cur = null;
|
|
139
|
+
let callee = null;
|
|
140
|
+
let recv = null;
|
|
141
|
+
let method = null;
|
|
142
|
+
if (c.primary.lit !== undefined) cur = ctx.lits[c.primary.lit] === null ? null : { t: 'str', v: ctx.lits[c.primary.lit] };
|
|
143
|
+
else if (c.primary.group !== undefined) cur = evaluate(c.primary.group, ctx);
|
|
144
|
+
else { callee = c.primary.name; cur = ctx.env.get(callee) ?? null; }
|
|
145
|
+
for (const s of c.steps) {
|
|
146
|
+
if (s.name !== undefined) {
|
|
147
|
+
if (callee !== null) { recv = cur; callee = `${callee}.${s.name}`; cur = ctx.env.get(callee) ?? null; } else { method = s.name; }
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (s.args) {
|
|
151
|
+
if (callee !== null) {
|
|
152
|
+
const last = callee.split('.').pop();
|
|
153
|
+
cur = callFn(callee, s.args, ctx) ?? (recv && callee.includes('.') ? callMethod(recv, last, s.args, ctx) : null);
|
|
154
|
+
callee = null;
|
|
155
|
+
} else cur = method && cur ? callMethod(cur, method, s.args, ctx) : null;
|
|
156
|
+
method = null;
|
|
157
|
+
recv = null;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
cur = null; callee = null; method = null;
|
|
161
|
+
}
|
|
162
|
+
return method ? null : cur;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function callFn(name, args, ctx) {
|
|
166
|
+
const vs = args.filter((a) => !/^\w+\s*=[^=]/.test(a)).map((a) => evaluate(a, ctx));
|
|
167
|
+
if (/(?:^|\.)Path\.home$/.test(name)) return process.env.HOME ? { t: 'path', v: process.env.HOME } : null;
|
|
168
|
+
if (/(?:^|\.)Path\.cwd$|^process\.cwd$|^os\.getcwd$/.test(name)) return { t: 'path', v: ctx.at('.') };
|
|
169
|
+
if (PATH_FNS.test(name)) return vs.length ? { t: 'path', v: joinPaths(vs.map(str)) } : null;
|
|
170
|
+
if (READS.test(name) || /^(?:io\.|codecs\.)?open$/.test(name)) {
|
|
171
|
+
const p = str(vs[0]);
|
|
172
|
+
if (p === null) return null;
|
|
173
|
+
ctx.reads?.add(ctx.at(p));
|
|
174
|
+
return READS.test(name) ? { t: 'str', v: ctx.readText(ctx.at(p)) ?? '' } : { t: 'file', v: p };
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function callMethod(base, m, args, ctx) {
|
|
180
|
+
const vs = args.map((a) => evaluate(a, ctx));
|
|
181
|
+
if (['expanduser', 'resolve', 'absolute'].includes(m)) return base.t === 'path' ? base : null;
|
|
182
|
+
if (m === 'joinpath') return { t: 'path', v: joinPaths([base.v, ...vs.map(str)]) };
|
|
183
|
+
if ((m === 'read_text' && base.t === 'path') || (m === 'read' && base.t === 'file')) {
|
|
184
|
+
ctx.reads?.add(ctx.at(base.v));
|
|
185
|
+
return { t: 'str', v: ctx.readText(ctx.at(base.v)) ?? '' };
|
|
186
|
+
}
|
|
187
|
+
if ((m === 'replace' || m === 'replaceAll') && base.t === 'str') {
|
|
188
|
+
const [a, b] = vs.map(str);
|
|
189
|
+
if (a === null || b === null || /^\//.test(args[0] ?? '')) return null;
|
|
190
|
+
if (m === 'replace' && ctx.lang === 'node') return { t: 'str', v: base.v.replace(a, () => b) };
|
|
191
|
+
const n = vs[2] ? Number(str(vs[2]) ?? args[2]) : -1;
|
|
192
|
+
if (!(n < 0)) { let out = base.v; for (let k = 0; k < n && out.includes(a); k += 1) out = out.replace(a, () => b); return { t: 'str', v: out }; }
|
|
193
|
+
return { t: 'str', v: base.v.split(a).join(b) };
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// program-writes: the files an inline program writes (KWB-8: `python3 - <<EOF ... open(p,'w').write(...)` got past every
|
|
2
|
+
// write-time hook; the shell reader saw redirects, tee, sed -i, cp and heredocs, not a program's own writes).
|
|
3
|
+
// programOf(exe, args, stdin) -> { lang, code } | null
|
|
4
|
+
// an interpreter given its program inline: python -c / python - or python with a heredoc on stdin; node -e / -p /
|
|
5
|
+
// node - or a heredoc; perl -e / -E (or a heredoc); ruby -e (or a heredoc). A script file (python3 tools/x.py,
|
|
6
|
+
// node tools/x.mjs, python3 -m mod) is null: it is the project's own tool, reviewed like code, not judged as a write.
|
|
7
|
+
// programWrites(program, at, readText) -> [{ path, op, content, from? }] (op and content as in shell-writes.mjs)
|
|
8
|
+
// a write is a call that opens or makes a file for writing: python open(p, 'w'|'a'|'x'|'r+'), Path.write_text /
|
|
9
|
+
// write_bytes / touch / unlink, shutil.copy* / move / copytree / rmtree, os.remove / rename / replace; node
|
|
10
|
+
// fs.writeFileSync / appendFileSync / copyFileSync / cpSync / renameSync / rmSync / unlinkSync / createWriteStream;
|
|
11
|
+
// perl open(FH, '>' ...), rename, unlink, File::Copy; ruby File.write / File.open(p, 'w') / FileUtils.*.
|
|
12
|
+
// A read (open(p), open(p, 'r'), read_text, readFileSync) is never a write. content is the new text where the
|
|
13
|
+
// program says it (a literal, or a file's text read and .replace'd, in a program with no loop or branch), else null.
|
|
14
|
+
// A write whose path the reader cannot work out makes every path the program names (its path-like literals, less
|
|
15
|
+
// the ones it only reads) a write of unknown content, so the path-based hooks still judge it.
|
|
16
|
+
import { basename, join } from 'node:path';
|
|
17
|
+
import { statSync } from 'node:fs';
|
|
18
|
+
import { closeOf, evaluate, scan, splitArgs, statements } from './program-read.mjs';
|
|
19
|
+
|
|
20
|
+
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
|
|
21
|
+
|
|
22
|
+
export function programOf(exe, args, stdin) {
|
|
23
|
+
const lang = /^python(\d+(\.\d+)*)?$|^pypy3?$/.test(exe) ? 'python' : /^(node|nodejs|bun)$/.test(exe) ? 'node' : exe === 'perl' ? 'perl' : exe === 'ruby' ? 'ruby' : null;
|
|
24
|
+
if (!lang) return null;
|
|
25
|
+
const inline = { python: /^-[A-Za-z]*c$/, node: /^(-e|--eval|-p|--print|-pe)$/, perl: /^-[A-Za-z]*[eE]$/, ruby: /^-[A-Za-z]*e$/ }[lang];
|
|
26
|
+
const takesValue = { python: /^-[WXQ]$/, node: /^(-r|--require|--import|--loader|--experimental-loader|-C|--conditions)$/, perl: /^-[IMx]$/, ruby: /^-[Ir]$/ }[lang];
|
|
27
|
+
const code = [];
|
|
28
|
+
for (let k = 0; k < args.length; k += 1) {
|
|
29
|
+
const a = args[k];
|
|
30
|
+
if (inline.test(a)) { if (args[k + 1] !== undefined) code.push(args[k + 1]); k += 1; if (lang === 'python') break; continue; }
|
|
31
|
+
if (lang === 'node' && /^--(eval|print)=/.test(a)) { code.push(a.slice(a.indexOf('=') + 1)); continue; }
|
|
32
|
+
if (lang === 'python' && /^-[A-Za-z]*m$/.test(a)) return null;
|
|
33
|
+
if (takesValue.test(a)) { k += 1; continue; }
|
|
34
|
+
if (a.startsWith('-') && a !== '-') continue;
|
|
35
|
+
if (code.length) break;
|
|
36
|
+
return a === '-' && stdin !== null ? { lang, code: stdin } : null;
|
|
37
|
+
}
|
|
38
|
+
if (code.length) return { lang, code: code.join('\n') };
|
|
39
|
+
return stdin !== null && stdin !== undefined ? { lang, code: stdin } : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The receiver of a method call: the postfix expression that ends just before text[dot].
|
|
43
|
+
function receiverBefore(text, dot) {
|
|
44
|
+
let k = dot - 1;
|
|
45
|
+
while (k >= 0) {
|
|
46
|
+
const c = text[k];
|
|
47
|
+
if (c === ')' || c === ']') {
|
|
48
|
+
const open = c === ')' ? '(' : '[';
|
|
49
|
+
let depth = 0;
|
|
50
|
+
for (; k >= 0; k -= 1) { if (text[k] === c) depth += 1; else if (text[k] === open && --depth === 0) break; }
|
|
51
|
+
k -= 1;
|
|
52
|
+
} else if (c === '\x02') k = text.lastIndexOf('\x01', k) - 1;
|
|
53
|
+
else if (/[\w$.]/.test(c)) k -= 1;
|
|
54
|
+
else break;
|
|
55
|
+
}
|
|
56
|
+
return text.slice(k + 1, dot);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const modeOp = (m) => (m === null ? 'write' : /^\+?>>|a/.test(m) ? 'append' : /^\+</.test(m) || /r\+/.test(m) ? 'edit' : /[wx>]|\+/.test(m) ? 'write' : null);
|
|
60
|
+
|
|
61
|
+
const SITES = {
|
|
62
|
+
python: /(?<![\w.])(?:io\.|codecs\.)?open\s*\(|\.(open|write_text|write_bytes|touch|unlink|rmdir)\s*\(|\bshutil\.(copy|copy2|copyfile|move|copytree|rmtree)\s*\(|\bos\.(remove|unlink|rename|replace)\s*\(/g,
|
|
63
|
+
node: /\b(writeFileSync|writeFile|appendFileSync|appendFile|copyFileSync|copyFile|cpSync|renameSync|rmSync|unlinkSync|createWriteStream|truncateSync|openSync|outputFileSync)\s*\(|\bBun\.(write)\s*\(/g,
|
|
64
|
+
perl: /\bopen\s*(?=[\s(])|\b(rename|unlink|copy|move)\s*\(/g,
|
|
65
|
+
ruby: /\b(?:File|IO)\.(write|binwrite|open|rename|delete|unlink)\s*\(|\bFileUtils\.(cp|copy|mv|move|rm|rm_f|rm_rf|touch|install)\s*\(/g,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const COPY = /^(copy|copy2|copyfile|copyFileSync|copyFile|cp|install)$/;
|
|
69
|
+
const MOVE = /^(move|rename|replace|renameSync|mv)$/;
|
|
70
|
+
const DELETE = /^(rmtree|remove|unlink|rmdir|rmSync|unlinkSync|delete|rm|rm_f|rm_rf)$/;
|
|
71
|
+
|
|
72
|
+
export function programWrites({ lang, code: src }, at, readText) {
|
|
73
|
+
const { code, lits } = scan(src, lang);
|
|
74
|
+
const stmts = statements(code);
|
|
75
|
+
const branchy = /\b(for|while|foreach|until|unless|lambda|function|each|map|if|elif|else|try|switch|case|def)\b|=>|\bdo\b/.test(code);
|
|
76
|
+
const ASSIGN = /^(?:(?:const|let|var|my|our)\s+)?([$@%]?[A-Za-z_][\w.]*)\s*(\+?)=(?!=)\s*([\s\S]+)$/;
|
|
77
|
+
const count = new Map();
|
|
78
|
+
for (const s of stmts) { const m = s.match(ASSIGN); if (m) count.set(m[1], (count.get(m[1]) ?? 0) + 1); }
|
|
79
|
+
const env = new Map();
|
|
80
|
+
const reads = new Set();
|
|
81
|
+
const ctx = { env, lits, lang, at, readText, reads };
|
|
82
|
+
const val = (e) => (e === undefined ? null : evaluate(e, ctx));
|
|
83
|
+
const pathOf = (e) => { const v = val(e); return v && (v.t === 'str' || v.t === 'path') && v.v ? at(v.v) : null; };
|
|
84
|
+
const textOf = (e) => { const v = val(e); return !branchy && v && v.t === 'str' ? v.v : null; };
|
|
85
|
+
const out = [];
|
|
86
|
+
const handles = new Map();
|
|
87
|
+
let unresolved = false;
|
|
88
|
+
const add = (path, op, content, extra = {}) => { if (path === null) unresolved = true; else out.push({ path, op, content, ...extra }); return out.at(-1); };
|
|
89
|
+
const kw = (args, name) => args.find((a) => new RegExp(`^${name}\\s*=`).test(a))?.replace(/^\w+\s*=\s*/, '');
|
|
90
|
+
const modeArg = (e) => (e === undefined ? undefined : val(e)?.t === 'str' ? val(e).v : null);
|
|
91
|
+
|
|
92
|
+
for (const s of stmts) {
|
|
93
|
+
for (const m of s.matchAll(SITES[lang])) {
|
|
94
|
+
const name = m.slice(1).find(Boolean) ?? 'open';
|
|
95
|
+
const paren = s.indexOf('(', m.index + m[0].length - 1);
|
|
96
|
+
let args;
|
|
97
|
+
let end;
|
|
98
|
+
if (lang === 'perl' && name === 'open' && s[paren] !== '(' ) {
|
|
99
|
+
const rest = s.slice(m.index + m[0].length).split(/\s+(?:or|\|\|)\s+|\s+or$/)[0];
|
|
100
|
+
args = splitArgs(rest.replace(/^\s*\(|\)\s*$/g, ''));
|
|
101
|
+
end = s.length;
|
|
102
|
+
} else {
|
|
103
|
+
end = closeOf(s, paren);
|
|
104
|
+
if (end < 0) continue;
|
|
105
|
+
args = splitArgs(s.slice(paren + 1, end));
|
|
106
|
+
}
|
|
107
|
+
const pos = args.filter((a) => !/^\w+\s*=[^=]/.test(a));
|
|
108
|
+
const method = m[0].startsWith('.');
|
|
109
|
+
const recv = method ? receiverBefore(s, m.index) : null;
|
|
110
|
+
let site = null;
|
|
111
|
+
if (lang === 'perl' && name === 'open') {
|
|
112
|
+
const mode = pos.length >= 3 ? modeArg(pos[1]) : modeArg(pos[1]);
|
|
113
|
+
if (pos.length >= 3) { const op = modeOp(mode); if (op) site = add(pathOf(pos[2]), op, null); } else if (mode !== null && mode !== undefined) {
|
|
114
|
+
const two = mode.match(/^\s*(\+?[<>]{1,2}|\|)?\s*(.*)$/);
|
|
115
|
+
const op = two[1] && two[1] !== '|' ? modeOp(two[1]) : null;
|
|
116
|
+
if (op) site = add(two[2] ? at(two[2]) : null, op, null);
|
|
117
|
+
} else if (pos[1] !== undefined) site = add(null, 'write', null);
|
|
118
|
+
} else if (name === 'open' || name === 'openSync') {
|
|
119
|
+
if (method && val(recv)?.t !== 'path') continue;
|
|
120
|
+
const target = method ? recv : pos[0];
|
|
121
|
+
const mode = modeArg(method ? pos[0] ?? kw(args, 'mode') : pos[1] ?? kw(args, 'mode'));
|
|
122
|
+
const op = mode === undefined ? null : modeOp(mode);
|
|
123
|
+
if (!op) { const p = pathOf(target); if (p) reads.add(p); continue; }
|
|
124
|
+
site = add(pathOf(target), op, op === 'edit' ? null : '');
|
|
125
|
+
const after = s.slice(end + 1);
|
|
126
|
+
const chained = after.match(/^\s*\.\s*write\s*\(/);
|
|
127
|
+
const as = after.match(/^\s*as\s+([A-Za-z_]\w*)/) ?? s.slice(0, m.index).match(/^([A-Za-z_]\w*)\s*=\s*$/);
|
|
128
|
+
if (site && chained) { const e = closeOf(after, after.indexOf('(')); site.content = e < 0 ? null : textOf(after.slice(after.indexOf('(') + 1, e)); }
|
|
129
|
+
else if (site && as) handles.set(as[1], { site, parts: [], ok: !branchy && site.content !== null, uses: 0 });
|
|
130
|
+
else if (site) site.content = null;
|
|
131
|
+
} else if (name === 'write_text' || name === 'write' || name === 'binwrite' || /^(writeFile|outputFile)/.test(name) || /^appendFile/.test(name)) {
|
|
132
|
+
const target = method ? recv : pos[0];
|
|
133
|
+
site = add(pathOf(target), /^append/.test(name) ? 'append' : 'write', name === 'binwrite' ? null : textOf(method ? pos[0] : pos[1]));
|
|
134
|
+
} else if (name === 'write_bytes' || name === 'createWriteStream' || name === 'truncateSync') site = add(pathOf(method ? recv : pos[0]), name === 'truncateSync' ? 'truncate' : 'write', null);
|
|
135
|
+
else if (name === 'touch') site = add(pathOf(method ? recv : pos[0]), 'touch', null);
|
|
136
|
+
else if (DELETE.test(name)) { if (method && val(recv)?.t !== 'path') continue; for (const a of method ? [recv] : pos.slice(0, lang === 'ruby' ? pos.length : 1)) add(pathOf(a), 'delete', null); }
|
|
137
|
+
else if (COPY.test(name) || MOVE.test(name) || /^(copytree|cpSync)$/.test(name)) {
|
|
138
|
+
if (method) continue;
|
|
139
|
+
const from = pathOf(pos[0]);
|
|
140
|
+
let to = pathOf(pos[1]);
|
|
141
|
+
if (to && from && isDir(to) && !/^(copytree|cpSync)$/.test(name)) to = join(to, basename(from));
|
|
142
|
+
const tree = /^(copytree|cpSync)$/.test(name);
|
|
143
|
+
add(to, MOVE.test(name) ? 'move' : 'copy', tree || !from ? null : readText(from), from ? { from } : {});
|
|
144
|
+
if (MOVE.test(name)) add(from, 'delete', null);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const [h, rec] of handles) {
|
|
148
|
+
for (const m of s.matchAll(new RegExp(`(?<![\\w.])${h.replace(/\$/g, '\\$')}\\b`, 'g'))) {
|
|
149
|
+
rec.uses += 1;
|
|
150
|
+
const after = s.slice(m.index + h.length);
|
|
151
|
+
const w = after.match(/^\s*\.\s*(write|close)\s*\(/);
|
|
152
|
+
if (!w) continue;
|
|
153
|
+
rec.uses -= 1;
|
|
154
|
+
if (w[1] === 'close') continue;
|
|
155
|
+
const open = m.index + h.length + after.indexOf('(');
|
|
156
|
+
const e = closeOf(s, open);
|
|
157
|
+
const t = e < 0 ? null : textOf(s.slice(open + 1, e));
|
|
158
|
+
if (t === null) rec.ok = false; else rec.parts.push(t);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const a = s.match(ASSIGN);
|
|
162
|
+
if (a) {
|
|
163
|
+
const v = branchy && count.get(a[1]) > 1 ? null : a[2] ? ((x, y) => (x?.t === 'str' && y?.t === 'str' ? { t: 'str', v: x.v + y.v } : null))(env.get(a[1]), val(a[3])) : val(a[3]);
|
|
164
|
+
if (v) env.set(a[1], v); else env.delete(a[1]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const rec of handles.values()) rec.site.content = rec.ok && rec.uses <= 1 ? rec.parts.join('') : null;
|
|
168
|
+
if (unresolved) {
|
|
169
|
+
const named = new Set(out.map((w) => w.path));
|
|
170
|
+
for (const l of lits) {
|
|
171
|
+
if (l === null || l.length > 300 || !/^[~\w.@+\-/]+$/.test(l) || /^[-.]+$|^-|^\d+(\.\d+)*$/.test(l) || !(l.includes('/') || /\.[A-Za-z][A-Za-z0-9]{0,7}$/.test(l))) continue;
|
|
172
|
+
const p = at(l);
|
|
173
|
+
if (!named.has(p) && !reads.has(p)) { named.add(p); out.push({ path: p, op: 'write', content: null, named: true }); }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|