diff --git a/docs/trajectories/typescript-bun-migration/RESUME.md b/docs/trajectories/typescript-bun-migration/RESUME.md index e07b9bd921..6947ff7daa 100644 --- a/docs/trajectories/typescript-bun-migration/RESUME.md +++ b/docs/trajectories/typescript-bun-migration/RESUME.md @@ -6,7 +6,7 @@ **Next concrete action**: Maintain the bash-retirement inventory guard and treat any newly tracked non-Lean shell-family file outside the allowlist as drift. Do not revive the old Cluster G/H/I or budget-cluster port queues. -**Last updated**: 2026-05-27T17:48Z +**Last updated**: 2026-05-28T12:35Z ## Why this trajectory exists @@ -52,11 +52,12 @@ bun run hygiene:check-bash-retirement-inventory The expected retained surface is the explicit repo-wide allowlist: setup and bootstrap scripts, host-service wrappers, NixOS installer scripts, dev-cluster wrappers, launchd-bootstrap, and the Kiro loop wrapper. Any new -non-Lean shell-family file (`.sh`, `.bash`, `.zsh`, `.ksh`, or `.command`) -outside the allowlist is bash-retirement drift. Executable dotted-name files -with shell-family shebangs are also entrypoints and are classified as drift; -non-executable dotted documentation fixtures with shell-looking first lines stay -outside the retained-shell inventory. +non-Lean shell-family file (`.sh`, `.bash`, `.zsh`, `.ksh`, or `.command`, +including uppercase extension variants) outside the allowlist is +bash-retirement drift. Executable dotted-name files with shell-family shebangs +are also entrypoints and are classified as drift; non-executable dotted +documentation fixtures with shell-looking first lines stay outside the +retained-shell inventory. ### Bucket A — Should stay Shell (21 files) diff --git a/tools/hygiene/check-bash-retirement-inventory.test.ts b/tools/hygiene/check-bash-retirement-inventory.test.ts index bd07ba342b..837dc34d7c 100644 --- a/tools/hygiene/check-bash-retirement-inventory.test.ts +++ b/tools/hygiene/check-bash-retirement-inventory.test.ts @@ -196,10 +196,15 @@ describe("buildInventoryReport", () => { mkdirSync(join(repo, "scripts"), { recursive: true }); mkdirSync(join(repo, "tools", "lean4"), { recursive: true }); writeFileSync(join(repo, "scripts", "a.sh"), "#!/usr/bin/env bash\n"); + writeFileSync(join(repo, "scripts", "a-uppercase.SH"), "echo uppercase extension drift\n"); writeFileSync(join(repo, "scripts", "b.bash"), "#!/usr/bin/env bash\n"); + writeFileSync(join(repo, "scripts", "b-uppercase.BASH"), "echo uppercase extension drift\n"); writeFileSync(join(repo, "scripts", "c.zsh"), "#!/usr/bin/env zsh\n"); + writeFileSync(join(repo, "scripts", "c-uppercase.ZSH"), "echo uppercase extension drift\n"); writeFileSync(join(repo, "scripts", "d.ksh"), "#!/usr/bin/env ksh\n"); + writeFileSync(join(repo, "scripts", "d-uppercase.KSH"), "echo uppercase extension drift\n"); writeFileSync(join(repo, "scripts", "e.command"), "#!/usr/bin/env bash\n"); + writeFileSync(join(repo, "scripts", "e-uppercase.COMMAND"), "echo uppercase extension drift\n"); writeFileSync(join(repo, "scripts", "extensionless-bash"), "#!/usr/bin/env bash\n"); writeFileSync(join(repo, "scripts", "extensionless-bash-env-s"), "#!/usr/bin/env -S bash -eu\n"); writeFileSync(join(repo, "scripts", "extensionless-dash"), "#!/bin/dash\n"); @@ -214,11 +219,16 @@ describe("buildInventoryReport", () => { expect(trackedNonLeanShellFilesFromGit(repo)).toEqual([ "scripts/a.sh", + "scripts/a-uppercase.SH", "scripts/b.bash", + "scripts/b-uppercase.BASH", "scripts/c.zsh", + "scripts/c-uppercase.ZSH", "scripts/d.ksh", + "scripts/d-uppercase.KSH", "scripts/dotted-shell-entry.env", "scripts/e.command", + "scripts/e-uppercase.COMMAND", "scripts/extensionless-bash", "scripts/extensionless-bash-env-s", "scripts/extensionless-dash", diff --git a/tools/hygiene/check-bash-retirement-inventory.ts b/tools/hygiene/check-bash-retirement-inventory.ts index c39f50213e..af8d919812 100644 --- a/tools/hygiene/check-bash-retirement-inventory.ts +++ b/tools/hygiene/check-bash-retirement-inventory.ts @@ -210,7 +210,8 @@ function parseTrackedGitFile(entry: string): TrackedGitFile | undefined { } function isTrackedShellFamilyFile(repoRoot: string, file: string, executable: boolean): boolean { - if (SHELL_FILE_EXTENSIONS.some((extension) => file.endsWith(extension))) return true; + const lowerFile = file.toLowerCase(); + if (SHELL_FILE_EXTENSIONS.some((extension) => lowerFile.endsWith(extension))) return true; if (basename(file).includes(".") && !executable) return false; const firstLine = readFirstLine(join(repoRoot, file));