From 7c6ca6d778720202841557ff53695f4ddf955953 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:17:17 +0100 Subject: [PATCH] fix(rules): JS lockfile advice follows Bun-first runtime policy root_hygiene still said "use Deno" for every banned lockfile and marked bun.lockb (and pnpm-lock.yaml) high/delete, the inverse of standards docs/JS-RUNTIME-POLICY.adoc: order Bun > pnpm > npm, Deno banned 2026-09-22, and bun.lock / bun.lockb "permitted and expected". A dispatch acting on the old rule would delete a tier-1 runtime's lockfile. - bun.lockb is no longer banned; bun.lock/bun.lockb are never flagged. - pnpm-lock.yaml drops to a low-severity flag (tier 2, permitted where an upstream toolchain needs node_modules), never a delete. - package-lock.json, yarn.lock, .npmrc stay banned; their remedy and the nodejs_detected reason now say Bun and cite the policy doc. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QFphKkDVB9pUDSCD4bkz65 --- lib/rules/cicd_rules.ex | 2 +- lib/rules/root_hygiene.ex | 23 ++++++++++++++----- test/root_hygiene_test.exs | 13 ++++++++++- .../rules/cicd_rules_rescript_npm_js_test.exs | 7 +++--- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/rules/cicd_rules.ex b/lib/rules/cicd_rules.ex index 058f0ac9..ff299a51 100644 --- a/lib/rules/cicd_rules.ex +++ b/lib/rules/cicd_rules.ex @@ -282,7 +282,7 @@ defmodule Hypatia.Rules.CicdRules do id: :nodejs_detected, glob: "package-lock.json", reason: - "Node.js banned -- use Deno (org policy 2026-05-25; in-flight migration tracked under standards#253)", + "npm lockfile banned -- use Bun (`bun install`, bun.lock; JS runtime order Bun > pnpm > npm, Deno banned 2026-09-22, standards docs/JS-RUNTIME-POLICY.adoc)", path_allow_prefixes: [ # (1a) VSCode extension host-required (/vscode/ as path segment) "/vscode/", diff --git a/lib/rules/root_hygiene.ex b/lib/rules/root_hygiene.ex index d744aac2..df08439f 100644 --- a/lib/rules/root_hygiene.ex +++ b/lib/rules/root_hygiene.ex @@ -144,21 +144,32 @@ defmodule Hypatia.Rules.RootHygiene do action: :rename }, %{pattern: "Makefile", reason: "Use Justfile", severity: :medium, action: :replace}, + # JS runtime order is Bun > pnpm > npm; Deno is banned (2026-09-22). + # Source: standards docs/JS-RUNTIME-POLICY.adoc "Hard Rules". Bun + # lockfiles (bun.lock / bun.lockb) are permitted and expected, so they + # are deliberately absent here: this list used to mark bun.lockb (and + # pnpm-lock.yaml) high/delete and say "use Deno", the inverse of policy. %{ pattern: "package-lock.json", - reason: "npm banned -- use Deno", + reason: "npm lockfile must not be tracked -- use Bun (`bun install`, bun.lock)", severity: :high, action: :delete }, - %{pattern: "yarn.lock", reason: "Yarn banned -- use Deno", severity: :high, action: :delete}, - %{pattern: "bun.lockb", reason: "Bun banned -- use Deno", severity: :high, action: :delete}, + %{pattern: "yarn.lock", reason: "Yarn banned -- use Bun", severity: :high, action: :delete}, %{ pattern: "pnpm-lock.yaml", - reason: "pnpm banned -- use Deno", - severity: :high, + reason: + "pnpm is tier 2 -- permitted only where an upstream toolchain needs a " <> + "node_modules layout; prefer Bun", + severity: :low, + action: :flag + }, + %{ + pattern: ".npmrc", + reason: "npm config must not be tracked -- use Bun", + severity: :medium, action: :delete }, - %{pattern: ".npmrc", reason: "npm banned -- use Deno", severity: :medium, action: :delete}, %{ pattern: "tsconfig.json", reason: "TypeScript banned -- use AffineScript", diff --git a/test/root_hygiene_test.exs b/test/root_hygiene_test.exs index 8e2a9318..b3a96a92 100644 --- a/test/root_hygiene_test.exs +++ b/test/root_hygiene_test.exs @@ -26,8 +26,19 @@ defmodule Hypatia.Rules.RootHygieneTest do end test "flags banned package managers" do - findings = RootHygiene.scan_banned(["package-lock.json", "yarn.lock", "bun.lockb"]) + findings = RootHygiene.scan_banned(["package-lock.json", "yarn.lock", ".npmrc"]) assert length(findings) == 3 + refute Enum.any?(findings, &(&1.reason =~ "Deno")) + end + + # Bun is the tier-1 JS runtime (standards docs/JS-RUNTIME-POLICY.adoc): + # its lockfiles must never be flagged, let alone deleted. + test "Bun lockfiles are not flagged" do + assert RootHygiene.scan_banned(["bun.lock", "bun.lockb"]) == [] + end + + test "a pnpm lockfile is a low-severity flag, never a delete" do + assert [%{severity: :low, action: :flag}] = RootHygiene.scan_banned(["pnpm-lock.yaml"]) end test "ignores allowed files" do diff --git a/test/rules/cicd_rules_rescript_npm_js_test.exs b/test/rules/cicd_rules_rescript_npm_js_test.exs index 67deec2b..4cd07ba6 100644 --- a/test/rules/cicd_rules_rescript_npm_js_test.exs +++ b/test/rules/cicd_rules_rescript_npm_js_test.exs @@ -143,9 +143,10 @@ defmodule Hypatia.Rules.CicdRules.RescriptNpmJsTest do assert nj, "expected :nodejs_detected finding for non-exempt package-lock.json" assert length(nj.files) == 2 - assert nj.reason =~ "Node.js banned" - assert nj.reason =~ "Deno" - assert nj.reason =~ "standards#253" + # Deno is banned (2026-09-22); the remedy is Bun (JS-RUNTIME-POLICY). + assert nj.reason =~ "use Bun" + refute nj.reason =~ "use Deno" + assert nj.reason =~ "JS-RUNTIME-POLICY" end test "exempts VSCode extension host-required lockfiles" do