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