From 907a7bcbbcc24b76ca3fe96baf8585e0511acf01 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Wed, 15 Jul 2026 12:26:19 +0000 Subject: [PATCH 1/7] refactor(checks): migrate repository check suite to .mts Checks importing first-party CJS source use dynamic import so the ESM entrypoints resolve named exports at runtime under tsx. Signed-off-by: Tinson Lai --- .../SKILL.md | 2 +- .../actions/ci-cli-coverage-shard/action.yaml | 2 +- .github/workflows/pr.yaml | 2 +- package.json | 8 +++---- ...dependency-pins.ts => dependency-pins.mts} | 0 ...ntial-env.ts => direct-credential-env.mts} | 4 ++-- ...e2e-mock-parity.ts => e2e-mock-parity.mts} | 0 ...dary.ts => hermes-light-skin-boundary.mts} | 4 +++- ...ndaries.ts => layer-import-boundaries.mts} | 0 ...pin.ts => local-credential-helper-pin.mts} | 8 +++---- ...erage-ignore.ts => no-coverage-ignore.mts} | 0 ...st-imports.ts => no-test-dist-imports.mts} | 0 ...-e2e.ts => no-unit-blocks-in-live-e2e.mts} | 0 ....ts => openshell-policy-mutation-read.mts} | 0 scripts/checks/{run.ts => run.mts} | 24 +++++++++---------- ...dget.ts => test-create-require-budget.mts} | 0 ...st-title-style.ts => test-title-style.mts} | 0 ...-overlap.ts => vitest-project-overlap.mts} | 0 src/lib/messaging/manifest/types.test.ts | 2 +- src/lib/security/credential-env.ts | 2 +- test/checks-runner.test.ts | 12 +++++----- test/dependency-pins-check.test.ts | 2 +- test/e2e-mock-parity.test.ts | 2 +- test/layer-import-boundaries.test.ts | 2 +- test/local-credential-helper-pin.test.ts | 2 +- test/no-coverage-ignore.test.ts | 2 +- test/no-direct-credential-env.test.ts | 4 ++-- test/no-unit-blocks-in-live-e2e.test.ts | 5 +++- test/policy-mutation-read-discovery.test.ts | 2 +- test/pr-workflow-contract.test.ts | 2 +- test/test-boundary-guards.test.ts | 4 ++-- test/test-create-require-budget.test.ts | 2 +- test/test-title-style.test.ts | 2 +- 33 files changed, 53 insertions(+), 48 deletions(-) rename scripts/checks/{dependency-pins.ts => dependency-pins.mts} (100%) rename scripts/checks/{direct-credential-env.ts => direct-credential-env.mts} (98%) rename scripts/checks/{e2e-mock-parity.ts => e2e-mock-parity.mts} (100%) rename scripts/checks/{hermes-light-skin-boundary.ts => hermes-light-skin-boundary.mts} (91%) rename scripts/checks/{layer-import-boundaries.ts => layer-import-boundaries.mts} (100%) rename scripts/checks/{local-credential-helper-pin.ts => local-credential-helper-pin.mts} (99%) rename scripts/checks/{no-coverage-ignore.ts => no-coverage-ignore.mts} (100%) rename scripts/checks/{no-test-dist-imports.ts => no-test-dist-imports.mts} (100%) rename scripts/checks/{no-unit-blocks-in-live-e2e.ts => no-unit-blocks-in-live-e2e.mts} (100%) rename scripts/checks/{openshell-policy-mutation-read.ts => openshell-policy-mutation-read.mts} (100%) rename scripts/checks/{run.ts => run.mts} (83%) rename scripts/checks/{test-create-require-budget.ts => test-create-require-budget.mts} (100%) rename scripts/checks/{test-title-style.ts => test-title-style.mts} (100%) rename scripts/checks/{vitest-project-overlap.ts => vitest-project-overlap.mts} (100%) diff --git a/.agents/skills/nemoclaw-contributor-update-dependencies/SKILL.md b/.agents/skills/nemoclaw-contributor-update-dependencies/SKILL.md index 64686c63d10..469089ea499 100644 --- a/.agents/skills/nemoclaw-contributor-update-dependencies/SKILL.md +++ b/.agents/skills/nemoclaw-contributor-update-dependencies/SKILL.md @@ -436,7 +436,7 @@ Use these NemoClaw precedents for durable evidence shape, not as inherited concl - `docs/security/openclaw-2026.6.10-dependency-review.md` and `test/openclaw-dependency-review.test.ts` for a tracked dependency review with contract tests; - `docs/security/openshell-0.0.72-compatibility-review.mdx` for a runtime compatibility boundary; -- `scripts/checks/dependency-pins.ts` and `test/dependency-pins-check.test.ts` for selector +- `scripts/checks/dependency-pins.mts` and `test/dependency-pins-check.test.ts` for selector coherence; and - `scripts/check-installer-hash.sh` and `test/installer-hash-check.test.ts` for independently trusted release manifests and consumed artifacts. diff --git a/.github/actions/ci-cli-coverage-shard/action.yaml b/.github/actions/ci-cli-coverage-shard/action.yaml index 0d433054c0e..33bdbb9743b 100644 --- a/.github/actions/ci-cli-coverage-shard/action.yaml +++ b/.github/actions/ci-cli-coverage-shard/action.yaml @@ -91,7 +91,7 @@ runs: exit 0 ;; esac - npx tsx scripts/checks/e2e-mock-parity.ts --base "$base" --head "$head" + npx tsx scripts/checks/e2e-mock-parity.mts --base "$base" --head "$head" - name: Build TypeScript plugin shell: bash diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0c18b66ef85..bdc53adb109 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -339,7 +339,7 @@ jobs: # covered without executing a mutable replacement action. - name: Validate changed live E2E mock parity (bootstrap) if: ${{ steps.trusted-shard-capabilities.outputs.e2e-support != 'true' && matrix.shard == 1 }} - run: npx tsx scripts/checks/e2e-mock-parity.ts --base HEAD^1 --head HEAD^2 + run: npx tsx scripts/checks/e2e-mock-parity.mts --base HEAD^1 --head HEAD^2 - name: Run E2E support shard (bootstrap) if: ${{ steps.trusted-shard-capabilities.outputs.e2e-support != 'true' }} diff --git a/package.json b/package.json index 30e4108765b..9326638f9e3 100644 --- a/package.json +++ b/package.json @@ -35,13 +35,13 @@ "test:coverage:cli": "npm run clean:cli && npm run build:cli && tsx scripts/check-dist-sourcemaps.ts dist && vitest run --project cli --project integration --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/cli --coverage.include=\"bin/**/*.js\" --coverage.include=\"src/**/*.ts\" --coverage.exclude=\"test/**/*.js\" --coverage.exclude=\"test/**/*.ts\" && tsx scripts/check-coverage-ratchet.ts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json \"CLI coverage\"", "test:coverage:plugin": "vitest run --project plugin --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/plugin --coverage.include=\"nemoclaw/src/**/*.ts\" --coverage.include=\"nemoclaw/src/**/*.cts\" --coverage.exclude=\"**/*.test.ts\" && tsx scripts/check-coverage-ratchet.ts coverage/plugin/coverage-summary.json ci/coverage-threshold-plugin.json \"Plugin coverage\"", "test:live-e2e": "npm run clean:cli && npm run build:cli && NEMOCLAW_RUN_LIVE_E2E=1 vitest run --project e2e-live", - "test:imports:check": "tsx scripts/checks/no-test-dist-imports.ts", - "test:projects:check": "tsx scripts/checks/vitest-project-overlap.ts", - "test:titles:check": "tsx scripts/checks/test-title-style.ts", + "test:imports:check": "tsx scripts/checks/no-test-dist-imports.mts", + "test:projects:check": "tsx scripts/checks/vitest-project-overlap.mts", + "test:titles:check": "tsx scripts/checks/test-title-style.mts", "bench": "tsx scripts/bench/run.ts", "check": "npx prek run --all-files --stage pre-commit && npx prek run --all-files --stage manual", "check:diff": "npx prek run --from-ref origin/main --to-ref HEAD --stage pre-commit && npx commitlint --from origin/main --to HEAD && npx prek run --from-ref origin/main --to-ref HEAD --stage pre-push", - "checks": "tsx scripts/checks/run.ts", + "checks": "tsx scripts/checks/run.mts", "lint": "npx @biomejs/biome lint . && npm run checks", "lint:fix": "npx @biomejs/biome lint --write . && npm run checks", "lint:ts": "cd nemoclaw && npm run check", diff --git a/scripts/checks/dependency-pins.ts b/scripts/checks/dependency-pins.mts similarity index 100% rename from scripts/checks/dependency-pins.ts rename to scripts/checks/dependency-pins.mts diff --git a/scripts/checks/direct-credential-env.ts b/scripts/checks/direct-credential-env.mts similarity index 98% rename from scripts/checks/direct-credential-env.ts rename to scripts/checks/direct-credential-env.mts index 0873c5ab913..e203a521e10 100644 --- a/scripts/checks/direct-credential-env.ts +++ b/scripts/checks/direct-credential-env.mts @@ -13,8 +13,8 @@ import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import * as ts from "typescript"; -import { SUPPORTED_CREDENTIAL_ENV_NAMES } from "../../src/lib/security/credential-env"; +const { SUPPORTED_CREDENTIAL_ENV_NAMES } = await import("../../src/lib/security/credential-env"); const CREDENTIAL_ENV_KEYS = SUPPORTED_CREDENTIAL_ENV_NAMES; const MESSAGE = @@ -251,7 +251,7 @@ function scriptKindForPath(filePath: string): ts.ScriptKind { function main(): void { const filePaths = process.argv.slice(2).filter((arg) => arg !== "--"); if (filePaths.length === 0) { - console.error("Usage: tsx scripts/checks/direct-credential-env.ts FILE..."); + console.error("Usage: tsx scripts/checks/direct-credential-env.mts FILE..."); process.exitCode = 2; return; } diff --git a/scripts/checks/e2e-mock-parity.ts b/scripts/checks/e2e-mock-parity.mts similarity index 100% rename from scripts/checks/e2e-mock-parity.ts rename to scripts/checks/e2e-mock-parity.mts diff --git a/scripts/checks/hermes-light-skin-boundary.ts b/scripts/checks/hermes-light-skin-boundary.mts similarity index 91% rename from scripts/checks/hermes-light-skin-boundary.ts rename to scripts/checks/hermes-light-skin-boundary.mts index f9d784dbbc4..75c70a4ac58 100644 --- a/scripts/checks/hermes-light-skin-boundary.ts +++ b/scripts/checks/hermes-light-skin-boundary.mts @@ -5,7 +5,9 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { NEMOCLAW_HERMES_LIGHT_SKIN_REVIEWED_HERMES_VERSIONS } from "../../src/lib/domain/sandbox/connect-env"; +const { NEMOCLAW_HERMES_LIGHT_SKIN_REVIEWED_HERMES_VERSIONS } = await import( + "../../src/lib/domain/sandbox/connect-env" +); const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const HERMES_DOCKERFILE_BASE = "agents/hermes/Dockerfile.base"; diff --git a/scripts/checks/layer-import-boundaries.ts b/scripts/checks/layer-import-boundaries.mts similarity index 100% rename from scripts/checks/layer-import-boundaries.ts rename to scripts/checks/layer-import-boundaries.mts diff --git a/scripts/checks/local-credential-helper-pin.ts b/scripts/checks/local-credential-helper-pin.mts similarity index 99% rename from scripts/checks/local-credential-helper-pin.ts rename to scripts/checks/local-credential-helper-pin.mts index 8a34024e0e6..ed28f6aed94 100644 --- a/scripts/checks/local-credential-helper-pin.ts +++ b/scripts/checks/local-credential-helper-pin.mts @@ -17,10 +17,10 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import ts from "typescript"; -import { - extractStarterPromptMarkdown, - STARTER_PROMPT_SOURCE_PATH, -} from "../generate-starter-prompt"; + +const { extractStarterPromptMarkdown, STARTER_PROMPT_SOURCE_PATH } = await import( + "../generate-starter-prompt" +); const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const HELPER_PATH = "scripts/local-credential-helper.mts"; diff --git a/scripts/checks/no-coverage-ignore.ts b/scripts/checks/no-coverage-ignore.mts similarity index 100% rename from scripts/checks/no-coverage-ignore.ts rename to scripts/checks/no-coverage-ignore.mts diff --git a/scripts/checks/no-test-dist-imports.ts b/scripts/checks/no-test-dist-imports.mts similarity index 100% rename from scripts/checks/no-test-dist-imports.ts rename to scripts/checks/no-test-dist-imports.mts diff --git a/scripts/checks/no-unit-blocks-in-live-e2e.ts b/scripts/checks/no-unit-blocks-in-live-e2e.mts similarity index 100% rename from scripts/checks/no-unit-blocks-in-live-e2e.ts rename to scripts/checks/no-unit-blocks-in-live-e2e.mts diff --git a/scripts/checks/openshell-policy-mutation-read.ts b/scripts/checks/openshell-policy-mutation-read.mts similarity index 100% rename from scripts/checks/openshell-policy-mutation-read.ts rename to scripts/checks/openshell-policy-mutation-read.mts diff --git a/scripts/checks/run.ts b/scripts/checks/run.mts similarity index 83% rename from scripts/checks/run.ts rename to scripts/checks/run.mts index ee5138e6a4d..278e466dc8f 100644 --- a/scripts/checks/run.ts +++ b/scripts/checks/run.mts @@ -32,7 +32,7 @@ export const CHECKS: readonly CheckCommand[] = [ name: "direct-credential-env", command: TSX, args: [ - "scripts/checks/direct-credential-env.ts", + "scripts/checks/direct-credential-env.mts", "src/lib/onboard.ts", "src/lib/onboard/provider-key-bridge.ts", "src/lib/onboard/providers.ts", @@ -41,57 +41,57 @@ export const CHECKS: readonly CheckCommand[] = [ { name: "local-credential-helper-pin", command: TSX, - args: ["scripts/checks/local-credential-helper-pin.ts"], + args: ["scripts/checks/local-credential-helper-pin.mts"], }, { name: "hermes-light-skin-boundary", command: TSX, - args: ["scripts/checks/hermes-light-skin-boundary.ts"], + args: ["scripts/checks/hermes-light-skin-boundary.mts"], }, { name: "dependency-pins", command: TSX, - args: ["scripts/checks/dependency-pins.ts"], + args: ["scripts/checks/dependency-pins.mts"], }, { name: "no-coverage-ignore", command: TSX, - args: ["scripts/checks/no-coverage-ignore.ts"], + args: ["scripts/checks/no-coverage-ignore.mts"], }, { name: "openshell-policy-mutation-read", command: TSX, - args: ["scripts/checks/openshell-policy-mutation-read.ts"], + args: ["scripts/checks/openshell-policy-mutation-read.mts"], }, { name: "layer-import-boundaries", command: TSX, - args: ["scripts/checks/layer-import-boundaries.ts"], + args: ["scripts/checks/layer-import-boundaries.mts"], }, { name: "no-test-dist-imports", command: TSX, - args: ["scripts/checks/no-test-dist-imports.ts"], + args: ["scripts/checks/no-test-dist-imports.mts"], }, { name: "test-create-require-budget", command: TSX, - args: ["scripts/checks/test-create-require-budget.ts"], + args: ["scripts/checks/test-create-require-budget.mts"], }, { name: "vitest-project-overlap", command: TSX, - args: ["scripts/checks/vitest-project-overlap.ts"], + args: ["scripts/checks/vitest-project-overlap.mts"], }, { name: "test-title-style", command: TSX, - args: ["scripts/checks/test-title-style.ts"], + args: ["scripts/checks/test-title-style.mts"], }, { name: "no-unit-blocks-in-live-e2e", command: TSX, - args: ["scripts/checks/no-unit-blocks-in-live-e2e.ts"], + args: ["scripts/checks/no-unit-blocks-in-live-e2e.mts"], }, ]; diff --git a/scripts/checks/test-create-require-budget.ts b/scripts/checks/test-create-require-budget.mts similarity index 100% rename from scripts/checks/test-create-require-budget.ts rename to scripts/checks/test-create-require-budget.mts diff --git a/scripts/checks/test-title-style.ts b/scripts/checks/test-title-style.mts similarity index 100% rename from scripts/checks/test-title-style.ts rename to scripts/checks/test-title-style.mts diff --git a/scripts/checks/vitest-project-overlap.ts b/scripts/checks/vitest-project-overlap.mts similarity index 100% rename from scripts/checks/vitest-project-overlap.ts rename to scripts/checks/vitest-project-overlap.mts diff --git a/src/lib/messaging/manifest/types.test.ts b/src/lib/messaging/manifest/types.test.ts index 77b790c3f7f..400bf5ed531 100644 --- a/src/lib/messaging/manifest/types.test.ts +++ b/src/lib/messaging/manifest/types.test.ts @@ -292,6 +292,6 @@ describe("messaging manifest type contracts", () => { }); // Import-layer isolation for the production manifest modules is enforced by - // scripts/checks/layer-import-boundaries.ts. Keep this unit test focused on + // scripts/checks/layer-import-boundaries.mts. Keep this unit test focused on // manifest serialization and type contracts rather than walking source files. }); diff --git a/src/lib/security/credential-env.ts b/src/lib/security/credential-env.ts index afaf5813f32..3d9f7be7547 100644 --- a/src/lib/security/credential-env.ts +++ b/src/lib/security/credential-env.ts @@ -8,7 +8,7 @@ // separator-free provider parameters such as `clientSecret`, browser/session // material such as cookies, and connection strings cannot slip past the // validator. The standalone local credential helper and browser form embed this -// literal pattern; scripts/checks/local-credential-helper-pin.ts enforces exact +// literal pattern; scripts/checks/local-credential-helper-pin.mts enforces exact // parity because those reviewed artifacts cannot import this module at runtime. export const CREDENTIAL_SHAPED_NAME_PATTERN = /(?:^|[_-])(?:api[_-]?key|access[_-]?key|secret[_-]?key|auth[_-]?token|refresh[_-]?token|access[_-]?token|client[_-]?secret|private[_-]?key|pass[_-]?code|personal[_-]?access[_-]?token|connection[_-]?string|webhook(?:[_-]?url)?|key|secret|token|password|passwd|passcode|auth|authorization|credential|credentials|bearer|bearer[_-]?token|cookie|cookies|pat|private|privatekey|pin|webhookurl|dsn|connectionstring)(?:$|[_-])/i; diff --git a/test/checks-runner.test.ts b/test/checks-runner.test.ts index 6d28b487fee..053774321da 100644 --- a/test/checks-runner.test.ts +++ b/test/checks-runner.test.ts @@ -4,12 +4,12 @@ import type { SpawnSyncOptions } from "node:child_process"; import { describe, expect, it, vi } from "vitest"; -import { buildCheckSpawnInvocation, runChecks } from "../scripts/checks/run"; +import { buildCheckSpawnInvocation, runChecks } from "../scripts/checks/run.mts"; const sampleCheck = { name: "sample", command: "tsx.cmd", - args: ["scripts/checks/sample.ts"], + args: ["scripts/checks/sample.mts"], }; function successfulSpawn(): { status: number | null } { @@ -24,7 +24,7 @@ describe("checks runner", () => { }), ).toEqual({ command: "C:\\Windows\\System32\\cmd.exe", - args: ["/d", "/s", "/c", "tsx.cmd", "scripts/checks/sample.ts"], + args: ["/d", "/s", "/c", "tsx.cmd", "scripts/checks/sample.mts"], }); }); @@ -37,7 +37,7 @@ describe("checks runner", () => { it("keeps POSIX runner execution direct", () => { expect(buildCheckSpawnInvocation(sampleCheck, "linux")).toEqual({ command: "tsx.cmd", - args: ["scripts/checks/sample.ts"], + args: ["scripts/checks/sample.mts"], }); }); @@ -57,7 +57,7 @@ describe("checks runner", () => { expect(spawn).toHaveBeenCalledWith( "C:\\Windows\\System32\\cmd.exe", - ["/d", "/s", "/c", "tsx.cmd", "scripts/checks/sample.ts"], + ["/d", "/s", "/c", "tsx.cmd", "scripts/checks/sample.mts"], expect.objectContaining({ stdio: "inherit" }), ); expect(calls[0]?.shell).toBeUndefined(); @@ -72,7 +72,7 @@ describe("checks runner", () => { expect(spawn).toHaveBeenCalledWith( "tsx.cmd", - ["scripts/checks/sample.ts"], + ["scripts/checks/sample.mts"], expect.objectContaining({ stdio: "inherit" }), ); }); diff --git a/test/dependency-pins-check.test.ts b/test/dependency-pins-check.test.ts index 9599a54d1f9..67bbeadc575 100644 --- a/test/dependency-pins-check.test.ts +++ b/test/dependency-pins-check.test.ts @@ -7,7 +7,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; -import { verifyDependencyPins } from "../scripts/checks/dependency-pins"; +import { verifyDependencyPins } from "../scripts/checks/dependency-pins.mts"; const OPENSHELL_MIN = "1.2.3"; const OPENSHELL_MAX = "1.2.4"; diff --git a/test/e2e-mock-parity.test.ts b/test/e2e-mock-parity.test.ts index 0de972be19b..b2d607abdff 100644 --- a/test/e2e-mock-parity.test.ts +++ b/test/e2e-mock-parity.test.ts @@ -6,7 +6,7 @@ import { isMockParityRelevantSourceChange, type MockParityManifest, validateMockParity, -} from "../scripts/checks/e2e-mock-parity"; +} from "../scripts/checks/e2e-mock-parity.mts"; const live = "test/e2e/live/example.test.ts"; const fast = "test/e2e/support/example.test.ts"; diff --git a/test/layer-import-boundaries.test.ts b/test/layer-import-boundaries.test.ts index daec1164a9e..6a8eac03d7f 100644 --- a/test/layer-import-boundaries.test.ts +++ b/test/layer-import-boundaries.test.ts @@ -6,7 +6,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; -import { findLayerImportBoundaryViolations } from "../scripts/checks/layer-import-boundaries"; +import { findLayerImportBoundaryViolations } from "../scripts/checks/layer-import-boundaries.mts"; const REPO_ROOT = path.join(import.meta.dirname, ".."); let fixtureCounter = 0; diff --git a/test/local-credential-helper-pin.test.ts b/test/local-credential-helper-pin.test.ts index a2d66212772..07175bdc792 100644 --- a/test/local-credential-helper-pin.test.ts +++ b/test/local-credential-helper-pin.test.ts @@ -9,7 +9,7 @@ import { extractProcessControlRules, extractStringSet, verifyFieldSafetySourceParity, -} from "../scripts/checks/local-credential-helper-pin"; +} from "../scripts/checks/local-credential-helper-pin.mts"; const FUNCTION_NAME = "isBlocked"; const SET_NAME = "BLOCKED_NAMES"; diff --git a/test/no-coverage-ignore.test.ts b/test/no-coverage-ignore.test.ts index fe5d310cc5b..6e0de027672 100644 --- a/test/no-coverage-ignore.test.ts +++ b/test/no-coverage-ignore.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, it } from "vitest"; -import { findCoverageIgnoreDirectives } from "../scripts/checks/no-coverage-ignore"; +import { findCoverageIgnoreDirectives } from "../scripts/checks/no-coverage-ignore.mts"; const forbiddenDirective = ["v8", "ignore"].join(" "); diff --git a/test/no-direct-credential-env.test.ts b/test/no-direct-credential-env.test.ts index 6312146724a..d559ec2c6e0 100644 --- a/test/no-direct-credential-env.test.ts +++ b/test/no-direct-credential-env.test.ts @@ -14,7 +14,7 @@ import { spawnSync } from "node:child_process"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { findDirectCredentialEnvReads } from "../scripts/checks/direct-credential-env"; +import { findDirectCredentialEnvReads } from "../scripts/checks/direct-credential-env.mts"; describe("direct credential env guard", () => { it.each([ @@ -96,7 +96,7 @@ describe("direct credential env guard", () => { "npx", [ "tsx", - "scripts/checks/direct-credential-env.ts", + "scripts/checks/direct-credential-env.mts", "src/lib/onboard.ts", "src/lib/onboard/provider-key-bridge.ts", "src/lib/onboard/providers.ts", diff --git a/test/no-unit-blocks-in-live-e2e.test.ts b/test/no-unit-blocks-in-live-e2e.test.ts index bb37f9d1197..8bdc4ca4519 100644 --- a/test/no-unit-blocks-in-live-e2e.test.ts +++ b/test/no-unit-blocks-in-live-e2e.test.ts @@ -3,7 +3,10 @@ import { describe, expect, it } from "vitest"; -import { findLiveUnitBlocks, formatViolations } from "../scripts/checks/no-unit-blocks-in-live-e2e"; +import { + findLiveUnitBlocks, + formatViolations, +} from "../scripts/checks/no-unit-blocks-in-live-e2e.mts"; const FILE = "test/e2e/live/example.test.ts"; diff --git a/test/policy-mutation-read-discovery.test.ts b/test/policy-mutation-read-discovery.test.ts index 6c02027145f..e1224195d79 100644 --- a/test/policy-mutation-read-discovery.test.ts +++ b/test/policy-mutation-read-discovery.test.ts @@ -10,7 +10,7 @@ import { describe, expect, it } from "vitest"; import { auditOpenShellPolicyMutationReads, discoverPolicyReadSites, -} from "../scripts/checks/openshell-policy-mutation-read"; +} from "../scripts/checks/openshell-policy-mutation-read.mts"; describe("OpenShell policy mutation read discovery", () => { it("discovers builder and direct policy reads in new production files", () => { diff --git a/test/pr-workflow-contract.test.ts b/test/pr-workflow-contract.test.ts index 87a1260f0e9..c340c53f45d 100644 --- a/test/pr-workflow-contract.test.ts +++ b/test/pr-workflow-contract.test.ts @@ -923,7 +923,7 @@ describe("pull request and main workflow contracts", () => { expect(parityStep.run).toContain("head=HEAD^2"); expect(parityStep.run).toContain('base="$PUSH_BASE_SHA"'); expect(parityStep.run).toContain( - 'npx tsx scripts/checks/e2e-mock-parity.ts --base "$base" --head "$head"', + 'npx tsx scripts/checks/e2e-mock-parity.mts --base "$base" --head "$head"', ); const trustedCapabilityProbe = requiredWorkflowStep( diff --git a/test/test-boundary-guards.test.ts b/test/test-boundary-guards.test.ts index 3237fbf5b9d..65de61e42cc 100644 --- a/test/test-boundary-guards.test.ts +++ b/test/test-boundary-guards.test.ts @@ -12,7 +12,7 @@ import { findFastProjectTransitiveViolations, isFastProjectTestPath, isScannedTestPath, -} from "../scripts/checks/no-test-dist-imports"; +} from "../scripts/checks/no-test-dist-imports.mts"; import { discoverVitestCandidates, EXPECTED_VITEST_PROJECTS, @@ -22,7 +22,7 @@ import { parseProjectListing, parseProjectRoster, resolveVitestInvocation, -} from "../scripts/checks/vitest-project-overlap"; +} from "../scripts/checks/vitest-project-overlap.mts"; const REPO_ROOT = path.join(import.meta.dirname, ".."); const SOURCE_RUNTIME = path.join(REPO_ROOT, "test", "helpers", "onboard-script-mocks.cjs"); diff --git a/test/test-create-require-budget.test.ts b/test/test-create-require-budget.test.ts index 5b4f0d67a5b..749bd292bf0 100644 --- a/test/test-create-require-budget.test.ts +++ b/test/test-create-require-budget.test.ts @@ -12,7 +12,7 @@ import { collectTestSupportCreateRequireSources, containsCreateRequireIdentifier, createRequireBudgetFailure, -} from "../scripts/checks/test-create-require-budget"; +} from "../scripts/checks/test-create-require-budget.mts"; const tempDirs = new Set(); diff --git a/test/test-title-style.test.ts b/test/test-title-style.test.ts index 81b6f35acda..9b6b6395906 100644 --- a/test/test-title-style.test.ts +++ b/test/test-title-style.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; -import { scanTestTitleStyle } from "../scripts/checks/test-title-style"; +import { scanTestTitleStyle } from "../scripts/checks/test-title-style.mts"; function rulesFor(source: string): string[] { return scanTestTitleStyle("test/virtual-title-style.test.ts", source).map( From b428260c5210997252532a5f96a05223b2af16e5 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Wed, 15 Jul 2026 15:20:26 +0000 Subject: [PATCH 2/7] fix(ci): restore trusted-action compatibility shim for e2e-mock-parity The base-pinned ci-cli-coverage-shard action still invokes the pre-migration `.ts` path until it lands on main. Signed-off-by: Tinson Lai --- scripts/checks/e2e-mock-parity.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 scripts/checks/e2e-mock-parity.ts diff --git a/scripts/checks/e2e-mock-parity.ts b/scripts/checks/e2e-mock-parity.ts new file mode 100644 index 00000000000..6dbc6066acc --- /dev/null +++ b/scripts/checks/e2e-mock-parity.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Compatibility shim: the trusted, base-pinned CI action +// (.github/actions/ci-cli-coverage-shard) still invokes this script under +// its pre-migration filename until that action lands on `main`. Remove once +// no trusted action references the `.ts` path anymore. + +import { spawnSync } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); + +const result = spawnSync( + process.execPath, + [ + "--import", + "tsx", + path.join(REPO_ROOT, "scripts/checks/e2e-mock-parity.mts"), + ...process.argv.slice(2), + ], + { cwd: REPO_ROOT, stdio: "inherit" }, +); + +process.exit(result.status ?? 1); From ac5a43cb493278f99daa6d0543cbfa8e977fd321 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Wed, 15 Jul 2026 16:00:17 +0000 Subject: [PATCH 3/7] chore(ci): remove e2e-mock-parity.ts compatibility shim Issue #6921 requires no live scripts/checks/*.ts entrypoint after migration. The trusted, base-pinned ci-cli-coverage-shard action will stay red against the old path until a prerequisite change updates it on main. Signed-off-by: Tinson Lai --- scripts/checks/e2e-mock-parity.ts | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 scripts/checks/e2e-mock-parity.ts diff --git a/scripts/checks/e2e-mock-parity.ts b/scripts/checks/e2e-mock-parity.ts deleted file mode 100644 index 6dbc6066acc..00000000000 --- a/scripts/checks/e2e-mock-parity.ts +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Compatibility shim: the trusted, base-pinned CI action -// (.github/actions/ci-cli-coverage-shard) still invokes this script under -// its pre-migration filename until that action lands on `main`. Remove once -// no trusted action references the `.ts` path anymore. - -import { spawnSync } from "node:child_process"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); - -const result = spawnSync( - process.execPath, - [ - "--import", - "tsx", - path.join(REPO_ROOT, "scripts/checks/e2e-mock-parity.mts"), - ...process.argv.slice(2), - ], - { cwd: REPO_ROOT, stdio: "inherit" }, -); - -process.exit(result.status ?? 1); From c05291ec72cbcbdde1edb42edbb63261df336a24 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Thu, 16 Jul 2026 06:12:32 +0000 Subject: [PATCH 4/7] fix(ci): correct e2e-mock-parity usage diagnostic for .mts entrypoint Signed-off-by: Tinson Lai --- scripts/checks/e2e-mock-parity.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checks/e2e-mock-parity.mts b/scripts/checks/e2e-mock-parity.mts index b9dbf9e64e6..93bcbc23fc2 100644 --- a/scripts/checks/e2e-mock-parity.mts +++ b/scripts/checks/e2e-mock-parity.mts @@ -182,7 +182,7 @@ function changedFiles(base: string, head: string): string[] { function main(): void { const base = argument("--base"); const head = argument("--head") ?? "HEAD"; - if (!base) throw new Error("usage: e2e-mock-parity.ts --base [--head ]"); + if (!base) throw new Error("usage: e2e-mock-parity.mts --base [--head ]"); const manifestPath = path.join(REPO_ROOT, DEFAULT_PARITY_MANIFEST); const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as MockParityManifest; From e2390dd9228ad32400bd02086bd29cf36c5f6727 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Thu, 16 Jul 2026 06:13:04 +0000 Subject: [PATCH 5/7] fix(ci): scan .mts sources for coverage exclusion directives Signed-off-by: Tinson Lai --- scripts/checks/no-coverage-ignore.mts | 4 ++-- test/no-coverage-ignore.test.ts | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/checks/no-coverage-ignore.mts b/scripts/checks/no-coverage-ignore.mts index 10011145773..3c518bbc920 100644 --- a/scripts/checks/no-coverage-ignore.mts +++ b/scripts/checks/no-coverage-ignore.mts @@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url"; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const SCAN_ROOTS = ["bin", "src", "scripts", "test", "nemoclaw/src"]; -const SOURCE_EXTENSIONS = new Set([".cjs", ".cts", ".js", ".mjs", ".ts", ".tsx"]); +const SOURCE_EXTENSIONS = new Set([".cjs", ".cts", ".js", ".mjs", ".mts", ".ts", ".tsx"]); const SKIP_DIRS = new Set([".git", "coverage", "dist", "node_modules"]); const FORBIDDEN_DIRECTIVE = ["v8", "ignore"].join(" "); const FORBIDDEN_DIRECTIVE_PATTERN = new RegExp( @@ -91,7 +91,7 @@ function* walkSourceFiles(dir: string): Generator { } } -function isScannedSourcePath(filePath: string): boolean { +export function isScannedSourcePath(filePath: string): boolean { return ( filePath.length > 0 && SCAN_ROOTS.some((root) => filePath === root || filePath.startsWith(`${root}/`)) && diff --git a/test/no-coverage-ignore.test.ts b/test/no-coverage-ignore.test.ts index 6e0de027672..d8865c3258a 100644 --- a/test/no-coverage-ignore.test.ts +++ b/test/no-coverage-ignore.test.ts @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, it } from "vitest"; -import { findCoverageIgnoreDirectives } from "../scripts/checks/no-coverage-ignore.mts"; +import { + findCoverageIgnoreDirectives, + isScannedSourcePath, +} from "../scripts/checks/no-coverage-ignore.mts"; const forbiddenDirective = ["v8", "ignore"].join(" "); @@ -29,3 +32,15 @@ describe("coverage ignore guard", () => { expect(findCoverageIgnoreDirectives(source, "src/example.ts")).toEqual([]); }); }); + +describe("scanned source path selection", () => { + it("scans .mts files under tracked roots", () => { + expect(isScannedSourcePath("scripts/checks/no-coverage-ignore.mts")).toBe(true); + expect(isScannedSourcePath("src/lib/example.mts")).toBe(true); + }); + + it("excludes non-source extensions and paths outside tracked roots", () => { + expect(isScannedSourcePath("scripts/checks/README.md")).toBe(false); + expect(isScannedSourcePath("docs/example.mts")).toBe(false); + }); +}); From 4386ce2cc324c3c651756f27ed886aa060328512 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Thu, 16 Jul 2026 06:15:24 +0000 Subject: [PATCH 6/7] test(ci): cover the Hermes light-skin version boundary gate Signed-off-by: Tinson Lai --- scripts/checks/hermes-light-skin-boundary.mts | 38 +++++++++++------- test/hermes-light-skin-boundary.test.ts | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 test/hermes-light-skin-boundary.test.ts diff --git a/scripts/checks/hermes-light-skin-boundary.mts b/scripts/checks/hermes-light-skin-boundary.mts index 75c70a4ac58..43539e3c7fd 100644 --- a/scripts/checks/hermes-light-skin-boundary.mts +++ b/scripts/checks/hermes-light-skin-boundary.mts @@ -12,22 +12,34 @@ const { NEMOCLAW_HERMES_LIGHT_SKIN_REVIEWED_HERMES_VERSIONS } = await import( const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const HERMES_DOCKERFILE_BASE = "agents/hermes/Dockerfile.base"; -function main(): void { - const dockerfile = fs.readFileSync(path.join(REPO_ROOT, HERMES_DOCKERFILE_BASE), "utf8"); - const pinnedVersion = dockerfile.match(/^ARG HERMES_VERSION=(\S+)$/m)?.[1]; +export function checkHermesLightSkinBoundary(options: { + dockerfileText: string; + reviewedVersions: readonly string[]; +}): string | null { + const { dockerfileText, reviewedVersions } = options; + const pinnedVersion = dockerfileText.match(/^ARG HERMES_VERSION=(\S+)$/m)?.[1]; if (!pinnedVersion) { - throw new Error(`${HERMES_DOCKERFILE_BASE}: could not find ARG HERMES_VERSION`); + return `${HERMES_DOCKERFILE_BASE}: could not find ARG HERMES_VERSION`; } - const reviewedVersions: readonly string[] = NEMOCLAW_HERMES_LIGHT_SKIN_REVIEWED_HERMES_VERSIONS; if (!reviewedVersions.includes(pinnedVersion)) { - throw new Error( - [ - "Hermes light terminal compatibility skin needs re-review.", - `${HERMES_DOCKERFILE_BASE} pins ${pinnedVersion}, but connect-env.ts was reviewed for ${reviewedVersions.join(", ")}.`, - "Remove the NemoClaw-managed light skin if upstream Hermes is readable in light terminals, or update the reviewed version constant after validating it still needs the shim.", - ].join(" "), - ); + return [ + "Hermes light terminal compatibility skin needs re-review.", + `${HERMES_DOCKERFILE_BASE} pins ${pinnedVersion}, but connect-env.ts was reviewed for ${reviewedVersions.join(", ")}.`, + "Remove the NemoClaw-managed light skin if upstream Hermes is readable in light terminals, or update the reviewed version constant after validating it still needs the shim.", + ].join(" "); } + return null; } -main(); +function main(): void { + const dockerfileText = fs.readFileSync(path.join(REPO_ROOT, HERMES_DOCKERFILE_BASE), "utf8"); + const error = checkHermesLightSkinBoundary({ + dockerfileText, + reviewedVersions: NEMOCLAW_HERMES_LIGHT_SKIN_REVIEWED_HERMES_VERSIONS, + }); + if (error) throw new Error(error); +} + +if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) { + main(); +} diff --git a/test/hermes-light-skin-boundary.test.ts b/test/hermes-light-skin-boundary.test.ts new file mode 100644 index 00000000000..801c3172189 --- /dev/null +++ b/test/hermes-light-skin-boundary.test.ts @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; +import { checkHermesLightSkinBoundary } from "../scripts/checks/hermes-light-skin-boundary.mts"; + +function dockerfileWithVersion(version: string): string { + return ["FROM debian:bookworm-slim", `ARG HERMES_VERSION=${version}`, ""].join("\n"); +} + +describe("hermes light-skin boundary check", () => { + it("passes when the pinned version is reviewed", () => { + expect( + checkHermesLightSkinBoundary({ + dockerfileText: dockerfileWithVersion("v2026.7.1"), + reviewedVersions: ["v2026.6.19", "v2026.7.1"], + }), + ).toBeNull(); + }); + + it("fails when the pinned version has not been reviewed", () => { + const error = checkHermesLightSkinBoundary({ + dockerfileText: dockerfileWithVersion("v2026.8.1"), + reviewedVersions: ["v2026.6.19", "v2026.7.1"], + }); + + expect(error).toContain("needs re-review"); + expect(error).toContain("v2026.8.1"); + }); + + it("fails when the Dockerfile has no HERMES_VERSION arg", () => { + const error = checkHermesLightSkinBoundary({ + dockerfileText: "FROM debian:bookworm-slim\n", + reviewedVersions: ["v2026.7.1"], + }); + + expect(error).toContain("could not find ARG HERMES_VERSION"); + }); +}); From 40c2e95025231f50d34fc5d8e3b9c0b54a0bce03 Mon Sep 17 00:00:00 2001 From: Tinson Lai Date: Thu, 16 Jul 2026 07:00:41 +0000 Subject: [PATCH 7/7] fix(ci): keep a compatibility entrypoint for the coverage-shard action Signed-off-by: Tinson Lai --- scripts/checks/e2e-mock-parity.mts | 2 +- scripts/checks/e2e-mock-parity.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 scripts/checks/e2e-mock-parity.ts diff --git a/scripts/checks/e2e-mock-parity.mts b/scripts/checks/e2e-mock-parity.mts index 93bcbc23fc2..b5c57db022e 100644 --- a/scripts/checks/e2e-mock-parity.mts +++ b/scripts/checks/e2e-mock-parity.mts @@ -179,7 +179,7 @@ function changedFiles(base: string, head: string): string[] { ); } -function main(): void { +export function main(): void { const base = argument("--base"); const head = argument("--head") ?? "HEAD"; if (!base) throw new Error("usage: e2e-mock-parity.mts --base [--head ]"); diff --git a/scripts/checks/e2e-mock-parity.ts b/scripts/checks/e2e-mock-parity.ts new file mode 100644 index 00000000000..8682ec10448 --- /dev/null +++ b/scripts/checks/e2e-mock-parity.ts @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Compatibility boundary: the coverage-shard composite action resolves this +// exact path until it is updated to prefer e2e-mock-parity.mts. Keep this file +// forwarding to that implementation rather than duplicating its logic. + +import { main } from "./e2e-mock-parity.mts"; + +main();