Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/trajectories/typescript-bun-migration/RESUME.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Trajectory — TypeScript / Bun migration

**Status**: Closed-maintained bash-retirement phase (Lane B slice 21 merged — [#908](https://github.com/Lucent-Financial-Group/Zeta/pull/908); bash-retirement inventory guard landed — [#2764](https://github.com/Lucent-Financial-Group/Zeta/pull/2764); **Bucket B is empty**; retained non-Lean shell surface is the repo-wide setup/bootstrap/service-wrapper/installer/dev-cluster allowlist)
**Milestone**: 43 ported. All clusters complete: budget (14/18/19), peer-call (15/16/17), git (13/20), pr-preservation (21), cluster-inventory capture. Bucket B is empty as of 2026-04-30T08:07:32Z. The remaining non-Lean `.sh` inventory is guarded repo-wide by `tools/hygiene/check-bash-retirement-inventory.ts` and wired through package script `hygiene:check-bash-retirement-inventory` plus the `gate.yml` bash-retirement inventory lint job.
**Milestone**: 43 ported. All clusters complete: budget (14/18/19), peer-call (15/16/17), git (13/20), pr-preservation (21), cluster-inventory capture. Bucket B is empty as of 2026-04-30T08:07:32Z. The remaining non-Lean shell-family inventory is guarded repo-wide by `tools/hygiene/check-bash-retirement-inventory.ts` and wired through package script `hygiene:check-bash-retirement-inventory` plus the `gate.yml` bash-retirement inventory lint job.
**Current blocker**: None.
**Next concrete action**: Maintain the bash-retirement inventory guard and treat
any newly tracked non-Lean `.sh` outside the allowlist as drift. Do not revive
the old Cluster G/H/I or budget-cluster port queues.
**Last updated**: 2026-05-26
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

## Why this trajectory exists

Expand Down Expand Up @@ -52,7 +52,8 @@ 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 `.sh` outside the allowlist is bash-retirement drift.
non-Lean shell-family file (`.sh`, `.bash`, `.zsh`, `.ksh`, or `.command`)
outside the allowlist is bash-retirement drift.

### Bucket A — Should stay Shell (21 files)

Expand Down
44 changes: 44 additions & 0 deletions tools/hygiene/check-bash-retirement-inventory.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { describe, expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import {
buildInventoryReport,
Expand All @@ -9,6 +13,18 @@ import {
trackedNonLeanShellFilesFromGit,
} from "./check-bash-retirement-inventory";

function runGit(args: readonly string[], cwd: string): void {
// Test helper uses repo-pinned git with explicit argv; no shell expansion.
// eslint-disable-next-line sonarjs/no-os-command-from-path
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
if (result.error) {
throw new Error(`failed to start git ${args.join(" ")}: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed`);
}
}
Comment thread
AceHack marked this conversation as resolved.

function splitExpectedRetained(): readonly [string, readonly string[]] {
const [missing, ...rest] = EXPECTED_RETAINED_SHELL;
if (missing === undefined) throw new Error("expected retained shell allowlist must be non-empty");
Expand Down Expand Up @@ -145,6 +161,34 @@ describe("buildInventoryReport", () => {
expect(report.drift.unexpected).toEqual([]);
expect(report.drift.missingRetained).toEqual([]);
});

test("enumerates tracked shell-family files while excluding Lean vendor scripts", () => {
const repo = mkdtempSync(join(tmpdir(), "zeta-bash-retirement-"));
try {
runGit(["init"], repo);

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", "b.bash"), "#!/usr/bin/env bash\n");
writeFileSync(join(repo, "scripts", "c.zsh"), "#!/usr/bin/env zsh\n");
writeFileSync(join(repo, "scripts", "d.ksh"), "#!/usr/bin/env ksh\n");
writeFileSync(join(repo, "scripts", "e.command"), "#!/usr/bin/env bash\n");
writeFileSync(join(repo, "tools", "lean4", "vendor.sh"), "#!/usr/bin/env bash\n");
writeFileSync(join(repo, "README.md"), "not shell\n");
runGit(["add", "."], repo);

expect(trackedNonLeanShellFilesFromGit(repo)).toEqual([
"scripts/a.sh",
"scripts/b.bash",
"scripts/c.zsh",
"scripts/d.ksh",
"scripts/e.command",
]);
} finally {
rmSync(repo, { recursive: true, force: true });
}
});
});

describe("renderReport", () => {
Expand Down
11 changes: 6 additions & 5 deletions tools/hygiene/check-bash-retirement-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// check-bash-retirement-inventory.ts — verify the retained shell surface.
//
// The TypeScript/Bun migration is in bash-retirement mode: repo-owned scripts
// should not grow new `.sh` entrypoints outside the explicit repo-wide
// should not grow new shell-family entrypoints outside the explicit repo-wide
// retained-shell allowlist. Retained shell exists only where the script runs
// before Bun is available, bootstraps a host service environment, or belongs
// to a low-level installer/dev-cluster surface that is still shell-native.
Expand Down Expand Up @@ -66,6 +66,7 @@ export interface InventoryReport {

const SPAWN_MAX_BUFFER = 64 * 1024 * 1024;
export const RETAINED_SHELL_SCOPE = "repo-wide setup/bootstrap/service-wrapper/installer/dev-cluster allowlist";
export const TRACKED_SHELL_FILE_GLOBS: readonly string[] = ["*.sh", "*.bash", "*.zsh", "*.ksh", "*.command"];

export const EXPECTED_RETAINED_SHELL: readonly string[] = [
".gemini/service/install-lior-service.sh",
Expand Down Expand Up @@ -166,9 +167,9 @@ function runGit(args: readonly string[], cwd?: string): string {
return result.stdout;
}

export function trackedNonLeanShellFilesFromGit(): readonly string[] {
const repoRoot = runGit(["rev-parse", "--show-toplevel"]).trim();
const raw = runGit(["ls-files", "-z", "*.sh"], repoRoot);
export function trackedNonLeanShellFilesFromGit(cwd?: string): readonly string[] {
const repoRoot = runGit(["rev-parse", "--show-toplevel"], cwd).trim();
const raw = runGit(["ls-files", "-z", ...TRACKED_SHELL_FILE_GLOBS], repoRoot);
return raw
.split("\0")
.filter((file): file is string => file.length > 0)
Expand Down Expand Up @@ -361,7 +362,7 @@ function usage(): string {
" bun tools/hygiene/check-bash-retirement-inventory.ts --enforce",
" bun tools/hygiene/check-bash-retirement-inventory.ts --json",
"",
`Checks that non-Lean tracked .sh files match ${RETAINED_SHELL_SCOPE}.`,
`Checks that non-Lean tracked shell-family files match ${RETAINED_SHELL_SCOPE}.`,
].join("\n");
}

Expand Down
Loading