Skip to content

Node Adapter ​

Installation ​

Configure roots in .ayni.toml and provide the documented runtime, package manager, and signal tools in the current environment. ayni env show discovers Node requirements, and ayni env lock resolves runtime/package-manager ranges using mise candidates while reading exact project-tool versions from either package-lock.json or pnpm-lock.yaml. Locking does not install dependencies or modify the checkout. env build stages only locked manifests and native package-manager inputs, runs an ignore-scripts frozen installation (npm ci or pnpm install --frozen-lockfile), and stores node_modules as an image seed. The image entrypoint verifies and restores protected dependency archives in the attached checkout's original layout, then runs the adapter's offline rebuild command. Workspace-source links remain checkout-relative, not links from protected image directories into mutable source. See prepared dependencies and project links. npm file: and link: dependencies are rejected because their referenced content is not part of the staged input contract. Yarn and Bun remain unsupported.

Signal Coverage ​

SignalRequired tool or methodVersion contract
testvitestmanaged: exact project version from the native lockfile; host: no version enforced
coveragevitest; @vitest/coverage-v8managed: exact project versions from the native lockfile; host: no version enforced
sizebuilt-in JavaScript/TypeScript source scanno external tool
complexityeslint; @typescript-eslint/parsermanaged: exact project versions from the native lockfile; host: no version enforced
depsbuilt-in Node workspace package-manifest graphno external tool
mutationunavailableAyni rejects Node mutation before invoking a tool; no override or managed tool is accepted

Reconciliation baselines ​

The adapter owns a catalog-complete baseline inventory in tooling.rs. Use ayni tools reconcile --check to inspect required tools and native lock resolutions. Version selection explains how project versions and adapter baselines are used. Custom command overrides exclude their default tools from inspection.

Focused verification ​

Shared artifact, completion, validation, and exact-command reuse semantics are defined in Completion and focused verification. The accepted Node selectors are:

Signal--file--package--name
testyesyesyes
coveragenonono
sizeyesnono
complexityyesnono
depsyesyesno
mutationunavailableunavailableunavailable

Package selection remains owned by the resolved npm, pnpm, Yarn, or Bun adapter path. --name is test-only, and --file cannot be combined with --package. Unsupported or ambiguous selectors are rejected before a tool runs. The Node mutation signal itself is unavailable, so ayni verify mutation --language node is rejected regardless of selectors or command overrides.

Verification commands carry their originating contract and target, for example: ayni verify test --config './.ayni.toml' --language node --root 'apps/web' --file 'src/example.test.ts' --name 'renders'.

Impact planning ​

impact show and impact run resolve the governing Node workspace, map changed JavaScript and TypeScript files to the deepest owning package, then include transitive reverse dependencies even when configured roots name individual workspace members. Only manifests matched by the workspace patterns enter the graph. npm, pnpm, Yarn, and Bun lock changes broaden the plan, and package- scoped dependency execution resolves the governing workspace. Tests and dependency checks use package scope; coverage broadens to the configured root; size and complexity use exact changed-file scope when safe. Package manifests, npm lockfiles, common JSON/YAML/TOML and *.config.* inputs, environment files, and ambiguous ownership broaden every enabled signal and record an uncertainty.

Contract ​

Enabled checks come from [checks]. Configure roots in [node].roots (default ["."]), size budgets in [node.size], complexity in [node.complexity], coverage in [node.coverage], and forbidden edges in [node.deps.forbidden]. Command overrides are optional in [node.tooling.test] and [node.tooling.coverage]; each override requires command and may set args. With both signals enabled, [node.tooling].coverage_satisfies_test = true opts repository check into one coverage-backed execution that must emit both a Vitest JSON test report and a new coverage summary.

Test evidence may come from stdout/stderr or Vitest 5's default .vitest/json/output.json file in the target workdir. Ayni removes the prior file before normal, focused, and coverage-backed test runs; missing or invalid new evidence fails closed. Default Vitest commands explicitly set the absolute report path so package selection and Vitest root configuration cannot redirect it.

Ayni appends an absolute --coverage.reportsDirectory=<target>/coverage option to coverage commands so repository Vitest root configuration cannot redirect the managed summary path. Missing either evidence type fails both rows closed. Node mutation is unavailable, including through [node.tooling.mutation]: Ayni rejects the signal instead of treating command success as mutation evidence.

Size requires a budget entry and complexity requires fn_cyclomatic; either missing value produces a clear collector error. Coverage thresholds and dependency rules are optional: without line_percent, coverage has no policy threshold, and without node.deps.forbidden, no edges are forbidden.

The dependency graph contains the governing workspace package and the declared workspace members matched from package.json. Edges come from workspace-package names declared in dependencies, devDependencies, peerDependencies, and optionalDependencies. Forbidden-edge globs match the complete, repository-relative package-directory endpoints in that graph. For example, use apps/web to match that package or packages/* to match package directories under packages; apps/web/** describes descendants and does not match the apps/web package endpoint itself.

The dependency signal does not inspect JavaScript, TypeScript, or Svelte import statements. Keep source-level import boundaries in the owning linter, for example with ESLint no-restricted-imports; use Ayni dependency rules for relationships declared between workspace packages.

Shared boundary rules are defined under Threshold semantics. Node line and branch coverage are independently enforced: a configured metric with missing or unparseable evidence fails the coverage row.

Configuration Example ​

toml
[languages]
enabled = ["node"]

[node]
roots = ["apps/web", "packages/ui"]

[node.tooling]
coverage_satisfies_test = true

[node.tooling.test]
command = "pnpm"
args = ["exec", "vitest", "run", "--reporter=json", "--passWithNoTests"]

[node.size]
"**/*.ts" = { warn = 300, fail = 600, exclude = ["node_modules/**", "dist/**", "coverage/**", ".git/**", ".ayni/**"] }

[node.complexity]
fn_cyclomatic = { warn = 10, fail = 20 }

[node.coverage]
line_percent = { warn = 70, fail = 50 }

[node.deps.forbidden]
"apps/web" = ["apps/legacy-*"]