From 11bf26b6cf92b407602cb71e02763017d720c1b9 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 6 May 2026 17:58:22 -0700 Subject: [PATCH 1/2] chore(tooling): enforce honest coverage reporting Signed-off-by: Carlos Villela --- .pre-commit-config.yaml | 13 +- Makefile | 25 ++-- ci/coverage-threshold-cli.json | 8 +- package.json | 20 +-- scripts/check-coverage-ratchet.ts | 2 +- .../direct-credential-env.ts} | 2 +- .../layer-import-boundaries.ts} | 2 +- scripts/checks/no-coverage-ignore.ts | 118 ++++++++++++++++++ scripts/checks/run.ts | 50 ++++++++ src/lib/actions/maintenance.ts | 1 - src/lib/actions/sandbox/connect.ts | 1 - src/lib/actions/sandbox/destroy.ts | 1 - src/lib/actions/sandbox/doctor.ts | 1 - src/lib/actions/sandbox/gateway-state.ts | 1 - src/lib/actions/sandbox/logs.ts | 6 - src/lib/actions/sandbox/policy-channel.ts | 1 - src/lib/actions/sandbox/process-recovery.ts | 1 - src/lib/actions/sandbox/rebuild.ts | 1 - src/lib/actions/sandbox/runtime.ts | 1 - src/lib/actions/sandbox/skill-install.ts | 1 - src/lib/actions/sandbox/snapshot.ts | 1 - src/lib/actions/sandbox/status.ts | 1 - src/lib/actions/uninstall-plan.ts | 2 - src/lib/actions/uninstall-run-plan.ts | 2 - src/lib/actions/upgrade-sandboxes.ts | 1 - src/lib/adapters/openshell/runtime.ts | 1 - src/lib/commands/gateway-token.ts | 3 +- src/lib/commands/uninstall.ts | 2 +- src/lib/domain/policy-channel.ts | 3 - src/lib/domain/sandbox/destroy.ts | 3 - src/lib/domain/sandbox/logs.ts | 3 - src/lib/domain/uninstall/paths.ts | 2 - src/lib/domain/uninstall/plan.ts | 2 - src/lib/domain/uninstall/shims.ts | 2 - src/lib/list-command-deps.ts | 1 - src/lib/recover-cli-command.ts | 1 - src/lib/sandbox-config-set-cli-command.ts | 1 - src/lib/status-command-deps.ts | 1 - test/layer-import-boundaries.test.ts | 2 +- test/no-direct-credential-env.test.ts | 4 +- test/preinstall-node-version.test.ts | 4 +- 41 files changed, 214 insertions(+), 84 deletions(-) rename scripts/{check-direct-credential-env.ts => checks/direct-credential-env.ts} (98%) rename scripts/{check-layer-import-boundaries.ts => checks/layer-import-boundaries.ts} (99%) create mode 100644 scripts/checks/no-coverage-ignore.ts create mode 100644 scripts/checks/run.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f5d6a0cfc..95053a5399 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -149,12 +149,12 @@ repos: files: ^(nemoclaw-blueprint/.*\.yaml$|nemoclaw/openclaw\.plugin\.json$|schemas/.*\.json$) priority: 10 - - id: direct-credential-env - name: Direct credential env guard - entry: npx tsx scripts/check-direct-credential-env.ts + - id: repository-checks + name: Repository checks + entry: npm run checks language: system - files: ^src/lib/onboard\.ts$ - pass_filenames: true + files: ^(bin/.*\.(cjs|js|mjs)$|src/.*\.tsx?$|scripts/.*\.(cjs|js|mjs|ts|tsx)$|test/.*\.(cjs|js|mjs|ts|tsx)$|nemoclaw/src/.*\.tsx?$) + pass_filenames: false priority: 10 - repo: https://github.com/shellcheck-py/shellcheck-py @@ -266,7 +266,8 @@ repos: hooks: - id: test-cli name: Test (CLI) - entry: bash -c 'npm run clean:cli && npm run build:cli && npm run dist:sourcemaps:check && npx vitest run --project cli --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/cli --coverage.include="bin/**/*.js" --coverage.include="dist/lib/**/*.js" --coverage.exclude="test/**/*.js" --coverage.exclude="test/**/*.ts" && npx tsx scripts/check-coverage-ratchet.ts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json "CLI coverage"' + entry: >- + bash -c 'node -e "require(\"node:fs\").rmSync(\"dist\", { recursive: true, force: true })" && npm run build:cli && npx tsx scripts/check-dist-sourcemaps.ts dist && npx vitest run --project cli --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/cli --coverage.include="bin/**/*.js" --coverage.include="dist/lib/**/*.js" --coverage.exclude="test/**/*.js" --coverage.exclude="test/**/*.ts" && npx tsx scripts/check-coverage-ratchet.ts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json "CLI coverage"' language: system pass_filenames: false files: ^(bin/|src/|test/) diff --git a/Makefile b/Makefile index 0a1bf183c0..4211f08836 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,39 @@ .PHONY: check lint format format-biome lint-ts format-ts check-installer-hash docs docs-strict docs-live docs-clean check: - npx prek run --all-files - @echo "All checks passed." + npm run check -lint: check +lint: + npm run check # Targeted subproject checks (not part of `make check` — use for focused runs). lint-ts: - cd nemoclaw && npm run check + npm run lint:ts -format: format-biome +format: + npm run format format-biome: - npx biome format --write . + npm run format format-ts: - cd nemoclaw && npm run lint:fix && npm run format + npm run format:ts # --- Integrity checks --- check-installer-hash: - bash scripts/check-installer-hash.sh + npm run check:installer-hash # --- Documentation --- docs: - uv run --group docs sphinx-build -b html docs docs/_build/html + npm run docs docs-strict: - uv run --group docs sphinx-build -W -b html docs docs/_build/html + npm run docs:strict docs-live: - uv run --group docs sphinx-autobuild docs docs/_build/html --open-browser + npm run docs:live docs-clean: - rm -rf docs/_build + npm run docs:clean diff --git a/ci/coverage-threshold-cli.json b/ci/coverage-threshold-cli.json index 8c78856fe9..724eeccdb8 100644 --- a/ci/coverage-threshold-cli.json +++ b/ci/coverage-threshold-cli.json @@ -1,6 +1,6 @@ { - "lines": 22, - "functions": 31.8, - "branches": 22, - "statements": 22 + "lines": 33.2, + "functions": 29.8, + "branches": 23.4, + "statements": 32.7 } diff --git a/package.json b/package.json index ade97d8dcc..cb46b634d7 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,15 @@ "scripts": { "preinstall": "node scripts/check-node-version.js", "test": "vitest run", - "clean:cli": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"", - "dist:sourcemaps:check": "tsx scripts/check-dist-sourcemaps.ts dist", - "lint": "biome lint . && npm run check:credential-env", - "lint:fix": "biome lint --write . && npm run check:credential-env", - "format": "biome format --write .", - "format:check": "biome format .", - "check:credential-env": "tsx scripts/check-direct-credential-env.ts src/lib/onboard.ts", + "check": "npx prek run --all-files", + "checks": "tsx scripts/checks/run.ts", + "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", + "format": "npx @biomejs/biome format --write .", + "format:check": "npx @biomejs/biome format .", + "format:ts": "cd nemoclaw && npm run lint:fix && npm run format", + "check:installer-hash": "bash scripts/check-installer-hash.sh", "typecheck": "tsc -p jsconfig.json", "build:cli": "tsc -p tsconfig.src.json && tsc -p nemoclaw-blueprint/tsconfig.json", "typecheck:cli": "tsc -p tsconfig.cli.json", @@ -38,6 +40,10 @@ "source-shape:scan": "tsx scripts/find-source-shape-tests.ts --metrics", "source-shape:check": "tsx scripts/find-source-shape-tests.ts --check", "bump:version": "tsx scripts/bump-version.ts", + "docs": "uv run --group docs sphinx-build -b html docs docs/_build/html", + "docs:strict": "uv run --group docs sphinx-build -W -b html docs docs/_build/html", + "docs:live": "uv run --group docs sphinx-autobuild docs docs/_build/html --open-browser", + "docs:clean": "rm -rf docs/_build", "prepare": "if command -v tsc >/dev/null 2>&1 || [ -x node_modules/.bin/tsc ]; then npm run build:cli; fi && (npm install --omit=dev --ignore-scripts 2>/dev/null || true) && if [ -d .git ]; then bash scripts/npm-link-or-shim.sh; if command -v prek >/dev/null 2>&1; then prek install; else echo \"Skipping git hook setup (prek not installed)\"; fi; fi", "prepublishOnly": "git describe --tags --match 'v*' | sed 's/^v//' > .version && test -s .version && cd nemoclaw && env -u npm_config_global -u npm_config_prefix -u npm_config_omit npm install --ignore-scripts && ./node_modules/.bin/tsc" }, diff --git a/scripts/check-coverage-ratchet.ts b/scripts/check-coverage-ratchet.ts index bcaf17f698..b9c484a095 100755 --- a/scripts/check-coverage-ratchet.ts +++ b/scripts/check-coverage-ratchet.ts @@ -54,7 +54,7 @@ function main(): void { const [summaryPath, thresholdPath, label = "coverage"] = process.argv.slice(2); if (!summaryPath || !thresholdPath) { throw new Error( - "Usage: check-coverage-ratchet.ts [label]", + "Usage: coverage-ratchet.ts [label]", ); } diff --git a/scripts/check-direct-credential-env.ts b/scripts/checks/direct-credential-env.ts similarity index 98% rename from scripts/check-direct-credential-env.ts rename to scripts/checks/direct-credential-env.ts index 7ed669039f..0d8b317f0a 100644 --- a/scripts/check-direct-credential-env.ts +++ b/scripts/checks/direct-credential-env.ts @@ -227,7 +227,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/check-direct-credential-env.ts FILE..."); + console.error("Usage: tsx scripts/checks/direct-credential-env.ts FILE..."); process.exitCode = 2; return; } diff --git a/scripts/check-layer-import-boundaries.ts b/scripts/checks/layer-import-boundaries.ts similarity index 99% rename from scripts/check-layer-import-boundaries.ts rename to scripts/checks/layer-import-boundaries.ts index b3d54b2152..9f47a5114a 100644 --- a/scripts/check-layer-import-boundaries.ts +++ b/scripts/checks/layer-import-boundaries.ts @@ -20,7 +20,7 @@ type ImportRef = { column: number; }; -const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const SRC_LIB_ROOT = path.join(REPO_ROOT, "src", "lib"); const SKIP_DIRS = new Set([".git", "coverage", "dist", "node_modules"]); diff --git a/scripts/checks/no-coverage-ignore.ts b/scripts/checks/no-coverage-ignore.ts new file mode 100644 index 0000000000..58fa1a8cbf --- /dev/null +++ b/scripts/checks/no-coverage-ignore.ts @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Guard source files against coverage tool exclusion directives. + * + * Coverage reports should reflect executable code honestly. If a path is only + * covered by subprocess integration tests, keep it visible in coverage and let + * the ratchet baseline account for that instead of hiding it from the report. + */ + +import fs from "node:fs"; +import path from "node:path"; +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", ".js", ".mjs", ".ts", ".tsx"]); +const SKIP_DIRS = new Set([".git", "coverage", "dist", "node_modules"]); +const FORBIDDEN_DIRECTIVE = ["v8", "ignore"].join(" "); + +export interface CoverageIgnoreViolation { + filePath: string; + line: number; + column: number; + text: string; +} + +export function findCoverageIgnoreDirectives( + sourceText: string, + filePath: string, +): CoverageIgnoreViolation[] { + const violations: CoverageIgnoreViolation[] = []; + for (const [index, lineText] of sourceText.split(/\r?\n/).entries()) { + const column = lineText.indexOf(FORBIDDEN_DIRECTIVE); + if (column !== -1) { + violations.push({ + filePath, + line: index + 1, + column: column + 1, + text: lineText.trim(), + }); + } + } + return violations; +} + +export function checkFiles(filePaths: readonly string[]): CoverageIgnoreViolation[] { + return filePaths.flatMap((filePath) => { + const absolutePath = path.resolve(REPO_ROOT, filePath); + return findCoverageIgnoreDirectives( + fs.readFileSync(absolutePath, "utf-8"), + path.relative(REPO_ROOT, absolutePath), + ); + }); +} + +export function formatViolations(violations: readonly CoverageIgnoreViolation[]): string { + const directive = FORBIDDEN_DIRECTIVE; + return [ + `Coverage exclusion directives are not allowed (${directive}).`, + "Keep code visible to coverage reports and ratchet the honest baseline instead.", + "", + ...violations.map( + (violation) => + `${violation.filePath}:${violation.line}:${violation.column} ${violation.text}`, + ), + ].join("\n"); +} + +function sourceFiles(): string[] { + return SCAN_ROOTS.flatMap((root) => [...walkSourceFiles(path.join(REPO_ROOT, root))]).map( + (filePath) => path.relative(REPO_ROOT, filePath), + ); +} + +function* walkSourceFiles(dir: string): Generator { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) yield* walkSourceFiles(fullPath); + continue; + } + if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) { + yield fullPath; + } + } +} + +function isScannedSourcePath(filePath: string): boolean { + return ( + filePath.length > 0 && + SCAN_ROOTS.some((root) => filePath === root || filePath.startsWith(`${root}/`)) && + SOURCE_EXTENSIONS.has(path.extname(filePath)) + ); +} + +function normalizeCliPaths(args: readonly string[]): string[] { + return args + .filter((arg) => arg !== "--") + .map((arg) => path.relative(REPO_ROOT, path.resolve(arg))) + .filter(isScannedSourcePath); +} + +function main(): void { + const cliPaths = normalizeCliPaths(process.argv.slice(2)); + const filePaths = cliPaths.length > 0 ? cliPaths : sourceFiles(); + const violations = checkFiles(filePaths); + if (violations.length > 0) { + console.error(formatViolations(violations)); + process.exitCode = 1; + } +} + +if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) { + main(); +} diff --git a/scripts/checks/run.ts b/scripts/checks/run.ts new file mode 100644 index 0000000000..c54eef74ee --- /dev/null +++ b/scripts/checks/run.ts @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** Runs local repository checks that are not first-class Biome rules. */ + +import { spawnSync } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +type CheckCommand = { + name: string; + command: string; + args: string[]; +}; + +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const TSX = process.platform === "win32" ? "tsx.cmd" : "tsx"; +const CHECKS: readonly CheckCommand[] = [ + { + name: "direct-credential-env", + command: TSX, + args: ["scripts/checks/direct-credential-env.ts", "src/lib/onboard.ts"], + }, + { + name: "no-coverage-ignore", + command: TSX, + args: ["scripts/checks/no-coverage-ignore.ts"], + }, + { + name: "layer-import-boundaries", + command: TSX, + args: ["scripts/checks/layer-import-boundaries.ts"], + }, +]; + +function main(): void { + for (const check of CHECKS) { + const result = spawnSync(check.command, check.args, { + cwd: REPO_ROOT, + encoding: "utf-8", + stdio: "inherit", + }); + if (result.status !== 0) { + console.error(`Check failed: ${check.name}`); + process.exit(result.status ?? 1); + } + } +} + +main(); diff --git a/src/lib/actions/maintenance.ts b/src/lib/actions/maintenance.ts index e10a589c01..a3b8084497 100644 --- a/src/lib/actions/maintenance.ts +++ b/src/lib/actions/maintenance.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess maintenance tests. */ import { prompt as askPrompt } from "../credentials"; import { diff --git a/src/lib/actions/sandbox/connect.ts b/src/lib/actions/sandbox/connect.ts index 8876d2071d..0bdafd46ac 100644 --- a/src/lib/actions/sandbox/connect.ts +++ b/src/lib/actions/sandbox/connect.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess connect tests. */ import { spawnSync } from "node:child_process"; import os from "node:os"; diff --git a/src/lib/actions/sandbox/destroy.ts b/src/lib/actions/sandbox/destroy.ts index 79e27da053..57dd610392 100644 --- a/src/lib/actions/sandbox/destroy.ts +++ b/src/lib/actions/sandbox/destroy.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess destroy/rebuild tests. */ import fs from "node:fs"; diff --git a/src/lib/actions/sandbox/doctor.ts b/src/lib/actions/sandbox/doctor.ts index 83d17aaa08..58aefc4cb8 100644 --- a/src/lib/actions/sandbox/doctor.ts +++ b/src/lib/actions/sandbox/doctor.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess doctor tests. */ import { execFileSync, spawnSync } from "node:child_process"; import fs from "node:fs"; diff --git a/src/lib/actions/sandbox/gateway-state.ts b/src/lib/actions/sandbox/gateway-state.ts index 73226640d1..e885191d2d 100644 --- a/src/lib/actions/sandbox/gateway-state.ts +++ b/src/lib/actions/sandbox/gateway-state.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- extracted legacy sandbox liveness paths are covered through CLI subprocess tests. */ import fs from "node:fs"; import os from "node:os"; diff --git a/src/lib/actions/sandbox/logs.ts b/src/lib/actions/sandbox/logs.ts index e69562e67d..4a183c813f 100644 --- a/src/lib/actions/sandbox/logs.ts +++ b/src/lib/actions/sandbox/logs.ts @@ -17,7 +17,6 @@ import { } from "../../domain/sandbox/logs"; import type { SandboxLogsOptions } from "../../domain/sandbox/log-options"; -/* v8 ignore next -- process exit mapping is covered through CLI subprocess log tests. */ function exitWithSpawnResult(result: LogProbeResult) { if (result.status !== null) { process.exit(result.status); @@ -26,7 +25,6 @@ function exitWithSpawnResult(result: LogProbeResult) { process.exit(exitCodeFromSignal(result.signal ?? null)); } -/* v8 ignore next -- OpenShell subprocess call is covered through CLI subprocess log tests. */ function runOpenclawGatewayLogs( sandboxName: string, options: SandboxLogsOptions, @@ -46,7 +44,6 @@ function runOpenclawGatewayLogs( return result; } -/* v8 ignore next -- multi-process follow handling is covered through CLI subprocess log tests. */ function streamSandboxFollowLogs(sandboxName: string, options: SandboxLogsOptions): void { const openclawArgs = options.since ? null @@ -146,7 +143,6 @@ function streamSandboxFollowLogs(sandboxName: string, options: SandboxLogsOption maybeExit(); } -/* v8 ignore next -- OpenShell audit setting is covered through CLI subprocess log tests. */ function enableSandboxAuditLogs(sandboxName: string) { const args = buildEnableSandboxAuditLogsArgs(sandboxName); const result = runOpenshell(args, { @@ -159,7 +155,6 @@ function enableSandboxAuditLogs(sandboxName: string) { } } -/* v8 ignore next -- warning output is exercised through CLI subprocess log tests. */ function warnSandboxAuditLogsUnavailable( sandboxName: string, args: string[], @@ -176,7 +171,6 @@ function warnSandboxAuditLogsUnavailable( console.error(" Policy denial events may be missing from OpenShell logs."); } -/* v8 ignore next -- external log streaming is covered through CLI subprocess log tests. */ export function showSandboxLogs(sandboxName: string, options: SandboxLogsOptions | boolean) { const logsOptions = normalizeSandboxLogsOptions(options); if (logsOptions.follow) { diff --git a/src/lib/actions/sandbox/policy-channel.ts b/src/lib/actions/sandbox/policy-channel.ts index a7766bad35..5f04d4356a 100644 --- a/src/lib/actions/sandbox/policy-channel.ts +++ b/src/lib/actions/sandbox/policy-channel.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess policy/channel tests. */ import fs from "node:fs"; import path from "node:path"; diff --git a/src/lib/actions/sandbox/process-recovery.ts b/src/lib/actions/sandbox/process-recovery.ts index 61f33c15fc..8d92ac479b 100644 --- a/src/lib/actions/sandbox/process-recovery.ts +++ b/src/lib/actions/sandbox/process-recovery.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess connect/status/rebuild tests. */ import { spawnSync } from "node:child_process"; import fs from "node:fs"; diff --git a/src/lib/actions/sandbox/rebuild.ts b/src/lib/actions/sandbox/rebuild.ts index 5313fc2012..f0a81c2afd 100644 --- a/src/lib/actions/sandbox/rebuild.ts +++ b/src/lib/actions/sandbox/rebuild.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess rebuild tests. */ import { CLI_NAME } from "../../branding"; import { prompt as askPrompt } from "../../credentials"; diff --git a/src/lib/actions/sandbox/runtime.ts b/src/lib/actions/sandbox/runtime.ts index bdadd1d9b1..18502d718f 100644 --- a/src/lib/actions/sandbox/runtime.ts +++ b/src/lib/actions/sandbox/runtime.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- transitional action facade until implementations leave src/nemoclaw.ts. */ import type { DestroySandboxOptions, RebuildSandboxOptions } from "../../domain/lifecycle/options"; import type { SandboxConnectOptions } from "./connect"; diff --git a/src/lib/actions/sandbox/skill-install.ts b/src/lib/actions/sandbox/skill-install.ts index 19d84768b9..428eda58dc 100644 --- a/src/lib/actions/sandbox/skill-install.ts +++ b/src/lib/actions/sandbox/skill-install.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess skill install tests. */ import fs from "node:fs"; import os from "node:os"; diff --git a/src/lib/actions/sandbox/snapshot.ts b/src/lib/actions/sandbox/snapshot.ts index 024f36c54a..5fa23a50a3 100644 --- a/src/lib/actions/sandbox/snapshot.ts +++ b/src/lib/actions/sandbox/snapshot.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess snapshot tests. */ import fs from "node:fs"; import path from "node:path"; diff --git a/src/lib/actions/sandbox/status.ts b/src/lib/actions/sandbox/status.ts index 9e998b01dd..81730df8eb 100644 --- a/src/lib/actions/sandbox/status.ts +++ b/src/lib/actions/sandbox/status.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess status tests. */ import { CLI_DISPLAY_NAME, CLI_NAME } from "../../branding"; import { parseSandboxPhase } from "../../state/gateway"; diff --git a/src/lib/actions/uninstall-plan.ts b/src/lib/actions/uninstall-plan.ts index f9885ad76a..124dd67e3b 100644 --- a/src/lib/actions/uninstall-plan.ts +++ b/src/lib/actions/uninstall-plan.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ import fs from "node:fs"; import { defaultUninstallPaths } from "../domain/uninstall/paths"; @@ -53,4 +52,3 @@ export function buildHostUninstallPlan(options: HostUninstallPlanOptions): Unins shim: classifyShimPath(paths.nemoclawShimPath, options.fs), }); } -/* v8 ignore stop */ diff --git a/src/lib/actions/uninstall-run-plan.ts b/src/lib/actions/uninstall-run-plan.ts index caa7ca6df7..859f7d9354 100644 --- a/src/lib/actions/uninstall-run-plan.ts +++ b/src/lib/actions/uninstall-run-plan.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ import { spawnSync, type SpawnSyncOptions, type SpawnSyncReturns } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; @@ -467,4 +466,3 @@ export function runUninstallPlan(options: UninstallRunOptions, deps: UninstallRu printBye(runtime.log); return { exitCode: 0, plan }; } -/* v8 ignore stop */ diff --git a/src/lib/actions/upgrade-sandboxes.ts b/src/lib/actions/upgrade-sandboxes.ts index 71993a7cc0..60379a93ed 100644 --- a/src/lib/actions/upgrade-sandboxes.ts +++ b/src/lib/actions/upgrade-sandboxes.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess upgrade tests. */ import { CLI_NAME } from "../branding"; import { prompt as askPrompt } from "../credentials"; diff --git a/src/lib/adapters/openshell/runtime.ts b/src/lib/adapters/openshell/runtime.ts index 17abdf2deb..7d5ce2a609 100644 --- a/src/lib/adapters/openshell/runtime.ts +++ b/src/lib/adapters/openshell/runtime.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- exercised through CLI subprocess tests. */ import type { StdioOptions } from "node:child_process"; diff --git a/src/lib/commands/gateway-token.ts b/src/lib/commands/gateway-token.ts index 28e3f6076a..83c9adf792 100644 --- a/src/lib/commands/gateway-token.ts +++ b/src/lib/commands/gateway-token.ts @@ -9,7 +9,6 @@ type GatewayTokenRuntimeBridge = { fetchGatewayAuthTokenFromSandbox: (sandboxName: string) => string | null; }; -/* v8 ignore next -- source tests inject this bridge; CLI subprocess tests cover the real onboard module. */ let runtimeBridgeFactory = (): GatewayTokenRuntimeBridge => { const onboard = require("../onboard") as GatewayTokenRuntimeBridge; return { fetchGatewayAuthTokenFromSandbox: onboard.fetchGatewayAuthTokenFromSandbox }; @@ -51,7 +50,7 @@ export default class GatewayTokenCliCommand extends Command { const { args, flags } = await this.parse(GatewayTokenCliCommand); // Suppress EPIPE traces when the consumer closes the pipe early // (e.g. `... | head -c 0`). The token has already been written. - process.stdout.on("error", /* v8 ignore next -- pipe-close behavior is covered by CLI usage. */ (err: NodeJS.ErrnoException) => { + process.stdout.on("error", (err: NodeJS.ErrnoException) => { if (err.code === "EPIPE") process.exit(0); }); diff --git a/src/lib/commands/uninstall.ts b/src/lib/commands/uninstall.ts index 78e34fedc2..b03e27d634 100644 --- a/src/lib/commands/uninstall.ts +++ b/src/lib/commands/uninstall.ts @@ -30,7 +30,7 @@ export default class UninstallCliCommand extends Command { spawnSyncImpl: spawnSync, log: console.log, error: console.error, - exit: /* v8 ignore next -- uninstall exit behavior is covered by uninstall command tests. */ (code: number) => process.exit(code), + exit: (code: number) => process.exit(code), }); } } diff --git a/src/lib/domain/policy-channel.ts b/src/lib/domain/policy-channel.ts index 8517d1731a..8a55f529ea 100644 --- a/src/lib/domain/policy-channel.ts +++ b/src/lib/domain/policy-channel.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- pure helper tests exercise this module; orchestration coverage still runs through dist. */ export type CustomPolicySource = | { kind: "none" } @@ -66,5 +65,3 @@ export function parsePolicyAddArgs( presetArg: args.find((arg) => !arg.startsWith("-")) ?? null, }; } - -/* v8 ignore stop */ diff --git a/src/lib/domain/sandbox/destroy.ts b/src/lib/domain/sandbox/destroy.ts index acdc5011a3..37b19d3aab 100644 --- a/src/lib/domain/sandbox/destroy.ts +++ b/src/lib/domain/sandbox/destroy.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- pure helper tests exercise this module; orchestration coverage still runs through dist. */ const ANSI_RE = /\x1b\[[0-9;]*m/g; @@ -57,5 +56,3 @@ export function shouldCleanupGatewayAfterDestroy(input: { input.noLiveSandboxes ); } - -/* v8 ignore stop */ diff --git a/src/lib/domain/sandbox/logs.ts b/src/lib/domain/sandbox/logs.ts index c7dad18e44..ea9610b32e 100644 --- a/src/lib/domain/sandbox/logs.ts +++ b/src/lib/domain/sandbox/logs.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- pure helper tests exercise this module; orchestration coverage still runs through dist. */ import os from "node:os"; @@ -84,5 +83,3 @@ export function buildSandboxLogsArgs(sandboxName: string, options: SandboxLogsOp } return args; } - -/* v8 ignore stop */ diff --git a/src/lib/domain/uninstall/paths.ts b/src/lib/domain/uninstall/paths.ts index 6ea64b3c54..1ee44a99ef 100644 --- a/src/lib/domain/uninstall/paths.ts +++ b/src/lib/domain/uninstall/paths.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ import path from "node:path"; export const DEFAULT_GATEWAY_NAME = "nemoclaw"; @@ -67,4 +66,3 @@ export function defaultUninstallPaths(options: UninstallPathOptions): UninstallP export function uninstallStatePaths(paths: Pick): string[] { return [paths.nemoclawStateDir, paths.openshellConfigDir, paths.nemoclawConfigDir]; } -/* v8 ignore stop */ diff --git a/src/lib/domain/uninstall/plan.ts b/src/lib/domain/uninstall/plan.ts index 4fd504c903..ae384b0ac0 100644 --- a/src/lib/domain/uninstall/plan.ts +++ b/src/lib/domain/uninstall/plan.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ import { DEFAULT_GATEWAY_NAME, gatewayVolumeCandidates, @@ -113,4 +112,3 @@ export function buildUninstallPlan(paths: UninstallPaths, options: UninstallPlan export function flattenUninstallPlan(plan: UninstallPlan): UninstallPlanAction[] { return plan.steps.flatMap((step) => step.actions); } -/* v8 ignore stop */ diff --git a/src/lib/domain/uninstall/shims.ts b/src/lib/domain/uninstall/shims.ts index e190b09262..9d58d4e734 100644 --- a/src/lib/domain/uninstall/shims.ts +++ b/src/lib/domain/uninstall/shims.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ export const DEV_SHIM_MARKER = "# NemoClaw dev-shim - managed by scripts/npm-link-or-shim.sh"; export type ShimKind = @@ -87,4 +86,3 @@ export function classifyNemoclawShim(input: ShimInput): ShimClassification { reason: "regular file is not an installer-managed shim", }; } -/* v8 ignore stop */ diff --git a/src/lib/list-command-deps.ts b/src/lib/list-command-deps.ts index 41d74c6d91..5f1abfce18 100644 --- a/src/lib/list-command-deps.ts +++ b/src/lib/list-command-deps.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- runtime dependency adapter covered through CLI integration tests. */ import * as onboardSession from "./onboard-session"; import type { ListSandboxesCommandDeps } from "./inventory-commands"; diff --git a/src/lib/recover-cli-command.ts b/src/lib/recover-cli-command.ts index 43a78bb5b5..fd5eb77473 100644 --- a/src/lib/recover-cli-command.ts +++ b/src/lib/recover-cli-command.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- thin oclif adapter covered through CLI integration tests. */ import { Args, Command, Flags } from "@oclif/core"; diff --git a/src/lib/sandbox-config-set-cli-command.ts b/src/lib/sandbox-config-set-cli-command.ts index b53f1e58fe..40ae493bdc 100644 --- a/src/lib/sandbox-config-set-cli-command.ts +++ b/src/lib/sandbox-config-set-cli-command.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- thin oclif adapter covered through CLI integration tests. */ import { Args, Command, Flags } from "@oclif/core"; diff --git a/src/lib/status-command-deps.ts b/src/lib/status-command-deps.ts index fb239833f9..a9ee7dea42 100644 --- a/src/lib/status-command-deps.ts +++ b/src/lib/status-command-deps.ts @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -/* v8 ignore start -- runtime dependency adapter covered through CLI integration tests. */ import { spawnSync } from "node:child_process"; diff --git a/test/layer-import-boundaries.test.ts b/test/layer-import-boundaries.test.ts index 5a5047207c..cffe53279d 100644 --- a/test/layer-import-boundaries.test.ts +++ b/test/layer-import-boundaries.test.ts @@ -8,7 +8,7 @@ import { describe, expect, it } from "vitest"; const REPO_ROOT = path.join(import.meta.dirname, ".."); const TSX = path.join(REPO_ROOT, "node_modules", ".bin", "tsx"); -const BOUNDARY_SCRIPT = path.join(REPO_ROOT, "scripts", "check-layer-import-boundaries.ts"); +const BOUNDARY_SCRIPT = path.join(REPO_ROOT, "scripts", "checks", "layer-import-boundaries.ts"); describe("CLI layer import boundaries", () => { it("keeps domain, adapter, action, and command layers separated", () => { diff --git a/test/no-direct-credential-env.test.ts b/test/no-direct-credential-env.test.ts index 8689bfa2cb..5aa89ab2f9 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/check-direct-credential-env"; +import { findDirectCredentialEnvReads } from "../scripts/checks/direct-credential-env"; describe("direct credential env guard", () => { it.each([ @@ -89,7 +89,7 @@ describe("direct credential env guard", () => { const repoRoot = path.join(import.meta.dirname, ".."); const result = spawnSync( "npx", - ["tsx", "scripts/check-direct-credential-env.ts", "src/lib/onboard.ts"], + ["tsx", "scripts/checks/direct-credential-env.ts", "src/lib/onboard.ts"], { cwd: repoRoot, encoding: "utf-8", diff --git a/test/preinstall-node-version.test.ts b/test/preinstall-node-version.test.ts index caea0432e3..39c67b7924 100644 --- a/test/preinstall-node-version.test.ts +++ b/test/preinstall-node-version.test.ts @@ -84,9 +84,7 @@ describe("preinstall node-version guard (#2399)", () => { // pointed at it via a sibling tmp dir. The script reads // path.join(__dirname, '..', 'package.json'), so we copy the // script next to a custom package.json. - const tmpDir = fs.mkdtempSync( - path.join(os.tmpdir(), "preinstall-guard-no-engines-"), - ); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "preinstall-guard-no-engines-")); try { const scriptsDir = path.join(tmpDir, "scripts"); fs.mkdirSync(scriptsDir); From 63e79d03b691ab028f916c28f49a20dc21839127 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Wed, 6 May 2026 22:02:40 -0700 Subject: [PATCH 2/2] test(nim): isolate unified-memory GPU fixtures Signed-off-by: Carlos Villela --- src/lib/nim.test.ts | 119 +++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 58 deletions(-) diff --git a/src/lib/nim.test.ts b/src/lib/nim.test.ts index ee44cb00b3..c732c641c4 100644 --- a/src/lib/nim.test.ts +++ b/src/lib/nim.test.ts @@ -11,6 +11,21 @@ import * as nim from "../../dist/lib/nim"; const require = createRequire(import.meta.url); const NIM_DIST_PATH = require.resolve("../../dist/lib/nim"); const RUNNER_PATH = require.resolve("../../dist/lib/runner"); +const fs = require("fs"); + +function withFirmwareModel(model: string, fn: () => void): void { + const origReadFileSync = fs.readFileSync; + fs.readFileSync = (p: string, ...args: unknown[]) => { + if (p === "/sys/class/dmi/id/product_name") return model; + if (p === "/sys/firmware/devicetree/base/model") return ""; + return origReadFileSync(p, ...args); + }; + try { + fn(); + } finally { + fs.readFileSync = origReadFileSync; + } +} function loadNimWithMockedRunner(runCapture: Mock) { const runner = require(RUNNER_PATH); @@ -96,22 +111,6 @@ describe("nim", () => { }); describe("detectNvidiaPlatform", () => { - const fs = require("fs"); - - function withFirmwareModel(model: string, fn: () => void): void { - const origReadFileSync = fs.readFileSync; - fs.readFileSync = (p: string, ...args: unknown[]) => { - if (p === "/sys/class/dmi/id/product_name") return model; - if (p === "/sys/firmware/devicetree/base/model") return ""; - return origReadFileSync(p, ...args); - }; - try { - fn(); - } finally { - fs.readFileSync = origReadFileSync; - } - } - function withDmiUnavailableAndDevicetreeModel(model: string, fn: () => void): void { const origReadFileSync = fs.readFileSync; fs.readFileSync = (p: string, ...args: unknown[]) => { @@ -320,53 +319,57 @@ describe("nim", () => { }); it("detects Orin unified-memory GPUs without marking them as Spark", () => { - const runCapture = vi.fn((cmd: string | string[]) => { - if (!Array.isArray(cmd)) throw new Error("expected argv array"); - if (cmd.some((a: string) => a.includes("memory.total"))) return ""; - if (cmd.some((a: string) => a.includes("query-gpu=name"))) return "NVIDIA Jetson AGX Orin"; - if (cmd[0] === "free" && cmd[1] === "-m") return " total used free shared buff/cache available\nMem: 32768 5120 20000 512 7148 27136\nSwap: 0 0 0"; - return ""; - }); - const { nimModule, restore } = loadNimWithMockedRunner(runCapture); - - try { - expect(nimModule.detectGpu()).toMatchObject({ - type: "nvidia", - name: "NVIDIA Jetson AGX Orin", - count: 1, - totalMemoryMB: 32768, - perGpuMB: 32768, - nimCapable: true, - unifiedMemory: true, - spark: false, + withFirmwareModel("Generic Linux", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if (cmd.some((a: string) => a.includes("memory.total"))) return ""; + if (cmd.some((a: string) => a.includes("query-gpu=name"))) return "NVIDIA Jetson AGX Orin"; + if (cmd[0] === "free" && cmd[1] === "-m") return " total used free shared buff/cache available\nMem: 32768 5120 20000 512 7148 27136\nSwap: 0 0 0"; + return ""; }); - } finally { - restore(); - } - }); + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); - it("marks low-memory unified-memory NVIDIA devices as not NIM-capable", () => { - const runCapture = vi.fn((cmd: string | string[]) => { - if (!Array.isArray(cmd)) throw new Error("expected argv array"); - if (cmd.some((a: string) => a.includes("memory.total"))) return ""; - if (cmd.some((a: string) => a.includes("query-gpu=name"))) return "NVIDIA Xavier"; - if (cmd[0] === "free" && cmd[1] === "-m") return " total used free shared buff/cache available\nMem: 4096 1024 2048 256 1024 2816\nSwap: 0 0 0"; - return ""; + try { + expect(nimModule.detectGpu()).toMatchObject({ + type: "nvidia", + name: "NVIDIA Jetson AGX Orin", + count: 1, + totalMemoryMB: 32768, + perGpuMB: 32768, + nimCapable: true, + unifiedMemory: true, + spark: false, + }); + } finally { + restore(); + } }); - const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + }); - try { - expect(nimModule.detectGpu()).toMatchObject({ - type: "nvidia", - name: "NVIDIA Xavier", - totalMemoryMB: 4096, - nimCapable: false, - unifiedMemory: true, - spark: false, + it("marks low-memory unified-memory NVIDIA devices as not NIM-capable", () => { + withFirmwareModel("Generic Linux", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if (cmd.some((a: string) => a.includes("memory.total"))) return ""; + if (cmd.some((a: string) => a.includes("query-gpu=name"))) return "NVIDIA Xavier"; + if (cmd[0] === "free" && cmd[1] === "-m") return " total used free shared buff/cache available\nMem: 4096 1024 2048 256 1024 2816\nSwap: 0 0 0"; + return ""; }); - } finally { - restore(); - } + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + + try { + expect(nimModule.detectGpu()).toMatchObject({ + type: "nvidia", + name: "NVIDIA Xavier", + totalMemoryMB: 4096, + nimCapable: false, + unifiedMemory: true, + spark: false, + }); + } finally { + restore(); + } + }); }); });