diff --git a/oxlint.config.ts b/oxlint.config.ts index 32d259075..59f115f55 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,5 +1,37 @@ import { defineConfig } from 'oxlint'; +// Applied in the base config AND re-declared in the *.test.* override below +// (oxlint replaces, rather than merges, a rule's options per override) so the +// require() ban can be added for test files without silently dropping these. +const restrictedSyntax = [ + { + selector: + "CallExpression[callee.name='useEffect'] UnaryExpression[operator='typeof'] > Identifier[name='window']", + message: + "Do not use `typeof window !== 'undefined'` inside useEffect; useEffect already runs client-side.", + }, + { + selector: + "CallExpression[callee.name='useLayoutEffect'] UnaryExpression[operator='typeof'] > Identifier[name='window']", + message: + "Do not use `typeof window !== 'undefined'` inside useLayoutEffect; useLayoutEffect already runs client-side.", + }, +]; + +// Test files execute as ES modules, where a CommonJS `require()` is not +// defined. Calls like `require('./foo.ts')` or `require('node:fs')` break (or +// resolve inconsistently) at runtime. Use a static `import`, `await import()`, +// or `createRequire(import.meta.url)` for a native/CJS addon. The second +// selector also catches member calls like `require.resolve(...)`. Since both +// selectors key on the literal name `require`, binding a `createRequire` result +// to any other name (e.g. `require_`) keeps the escape hatch available without +// re-tripping either pattern. +const noRequireInTests = { + selector: "CallExpression[callee.name='require'], CallExpression[callee.object.name='require']", + message: + 'require() is not available in an ESM test module. Use a static import or await import(); for a native/CJS addon use createRequire(import.meta.url) bound to a name other than "require" (e.g. require_).', +}; + export default defineConfig({ // The .agents/skills, .codex/skills entries under // public/open-knowledge/ are real directories of per-skill symlinks back @@ -28,21 +60,7 @@ export default defineConfig({ enforceForIfStatements: true, }, ], - 'eslint-js/no-restricted-syntax': [ - 'error', - { - selector: - "CallExpression[callee.name='useEffect'] UnaryExpression[operator='typeof'] > Identifier[name='window']", - message: - "Do not use `typeof window !== 'undefined'` inside useEffect; useEffect already runs client-side.", - }, - { - selector: - "CallExpression[callee.name='useLayoutEffect'] UnaryExpression[operator='typeof'] > Identifier[name='window']", - message: - "Do not use `typeof window !== 'undefined'` inside useLayoutEffect; useLayoutEffect already runs client-side.", - }, - ], + 'eslint-js/no-restricted-syntax': ['error', ...restrictedSyntax], // TODO(oxlint): enable in priority order as each backlog is audited. // Correctness rules catch async/control-flow bugs; graduate these first. 'typescript/no-floating-promises': 'off', @@ -77,5 +95,11 @@ export default defineConfig({ 'typescript/no-deprecated': 'error', }, }, + { + files: ['**/*.test.{ts,tsx,cts,mts,mjs}'], + rules: { + 'eslint-js/no-restricted-syntax': ['error', ...restrictedSyntax, noRequireInTests], + }, + }, ], }); diff --git a/packages/app/tests/integration/conflict-aware-write-surfaces.test.ts b/packages/app/tests/integration/conflict-aware-write-surfaces.test.ts index 73f486737..7ab3933e8 100644 --- a/packages/app/tests/integration/conflict-aware-write-surfaces.test.ts +++ b/packages/app/tests/integration/conflict-aware-write-surfaces.test.ts @@ -24,9 +24,9 @@ * HTTP conflicts.json) and that the lifecycle transitions are * observable end to end through the integration harness. */ -import { describe, expect, test } from 'bun:test'; + import { execFile } from 'node:child_process'; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { promisify } from 'node:util'; import { @@ -36,6 +36,7 @@ import { getLogger, restoreLifecycleFromConflictsJson, } from '@inkeep/open-knowledge-server'; +import { describe, expect, test } from 'vitest'; import { createTestClient, createTestServer, pollUntil, type TestServer } from './test-harness'; const execFileAsync = promisify(execFile); @@ -376,7 +377,6 @@ describe('FR12: /api/sync/conflicts + /api/sync/status count parity', () => { const { tmpdir } = await import('node:os'); const tmpDir = realpathSync(mkdtempSync(join(tmpdir(), 'ok-fr12-'))); cleanups.push(() => { - const { rmSync } = require('node:fs') as typeof import('node:fs'); rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/packages/cli/src/native/mcp-toml-edit-binding.test.ts b/packages/cli/src/native/mcp-toml-edit-binding.test.ts index 04474b348..e8e343963 100644 --- a/packages/cli/src/native/mcp-toml-edit-binding.test.ts +++ b/packages/cli/src/native/mcp-toml-edit-binding.test.ts @@ -1,18 +1,17 @@ -import { describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; -import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import * as nativeConfig from '@inkeep/open-knowledge-native-config'; +import { describe, expect, test } from 'vitest'; // Exercise the insert-only upsert/remove + symlink-resolution across the real // JS<->Rust boundary by loading the built `.node` directly. The Rust unit tests // cover the engine's document + path semantics exhaustively; this guards the // napi marshalling the Rust tests cannot reach — the `McpEditResult` / // `SymlinkWritePaths` struct return shapes (including `Option` -> -// `undefined`), string in/out, and the throw on malformed input. It -// hard-requires the addon (rather than skipping when absent) so a gate that -// failed to build the binding fails loudly instead of vacuously passing, -// matching `toml-config-engine.test.ts`. +// `undefined`), string in/out, and the throw on malformed input. It statically +// imports the addon (rather than skipping when absent) so a gate that failed to +// build the binding fails loudly instead of vacuously passing. interface McpEditResult { text: string; @@ -31,8 +30,7 @@ interface NativeMcpEditBinding { resolveSymlinkWritePath(path: string): SymlinkWritePaths; } -const require = createRequire(import.meta.url); -const binding = require('@inkeep/open-knowledge-native-config') as NativeMcpEditBinding; +const binding: NativeMcpEditBinding = nativeConfig; const SERVER = 'open-knowledge'; const ENTRY = JSON.stringify({ command: '/bin/sh', args: ['-l', '-c', 'run-ok'] }); diff --git a/packages/cli/src/native/symlink-resolve.test.ts b/packages/cli/src/native/symlink-resolve.test.ts index a5c193bbd..1f6e5e7d1 100644 --- a/packages/cli/src/native/symlink-resolve.test.ts +++ b/packages/cli/src/native/symlink-resolve.test.ts @@ -1,23 +1,22 @@ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; -import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import * as nativeConfig from '@inkeep/open-knowledge-native-config'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { resolveHarnessWritePaths, type SymlinkWritePaths } from './symlink-resolve.ts'; // One contract suite run against BOTH backends — the pure-JS mirror and the // native (conformance-tested) resolver loaded from the built `.node` — so the // two implementations cannot drift: any divergence in chain-following or // cycle-breaking fails the same assertions for one backend but not the other. -// The native binding is hard-required (not skipped when absent) so a gate that -// failed to build the addon fails loudly, matching the sibling binding tests. +// The native binding is statically imported (not skipped when absent) so a gate +// that failed to build the addon fails loudly. interface NativeSymlinkBinding { resolveSymlinkWritePath(path: string): { readPath?: string | null; writePath: string }; } -const require = createRequire(import.meta.url); -const nativeBinding = require('@inkeep/open-knowledge-native-config') as NativeSymlinkBinding; +const nativeBinding: NativeSymlinkBinding = nativeConfig; const unix = process.platform !== 'win32'; diff --git a/packages/desktop/tests/unit/ok-wrapper.test.ts b/packages/desktop/tests/unit/ok-wrapper.test.ts index 71522c057..734ec30cc 100644 --- a/packages/desktop/tests/unit/ok-wrapper.test.ts +++ b/packages/desktop/tests/unit/ok-wrapper.test.ts @@ -2,10 +2,10 @@ // shape + exit code the wrapper emits when the bundled CLI or // Electron binary is missing (drag-to-Trash lifecycle). -import { describe, expect, test } from 'bun:test'; import { spawnSync } from 'node:child_process'; -import { accessSync, constants } from 'node:fs'; +import { accessSync, constants, readFileSync } from 'node:fs'; import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; const WRAPPER = join(import.meta.dir, '..', '..', 'resources', 'cli', 'bin', 'ok.sh'); @@ -91,7 +91,6 @@ describe('ok.sh wrapper', () => { // rescope verbatim. Without quoting, bash re-splits on whitespace // and only `--require` is captured; everything after is evaluated // as an extra command in the script's environment. - const { readFileSync } = require('node:fs') as typeof import('node:fs'); const script = readFileSync(WRAPPER, 'utf8'); expect(script).toContain('export OK_NODE_OPTIONS="$NODE_OPTIONS"'); expect(script).toContain('unset NODE_OPTIONS'); diff --git a/packages/server/src/api-rename-crash-injection.test.ts b/packages/server/src/api-rename-crash-injection.test.ts index c5c4a47ee..4f7bc520b 100644 --- a/packages/server/src/api-rename-crash-injection.test.ts +++ b/packages/server/src/api-rename-crash-injection.test.ts @@ -17,13 +17,23 @@ * Production builds elide these branches (NODE_ENV !== 'test' AND * OK_TEST_RENAME_FAULT unset). */ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { + existsSync, + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { Readable } from 'node:stream'; import simpleGit from 'simple-git'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { createApiExtension } from './api-extension.ts'; import { BacklinkIndex } from './backlink-index.ts'; import { swapContributors } from './contributor-tracker.ts'; @@ -62,9 +72,8 @@ function makeRes(): { res: ServerResponse; captured: CapturedResponse } { function buildFileIndex(contentDir: string): ReadonlyMap { const index = new Map(); - const fs = require('node:fs') as typeof import('node:fs'); function walk(dir: string) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { const fullPath = resolve(dir, entry.name); if (entry.isDirectory()) { if (entry.name === '.ok' || entry.name === '.git') continue; @@ -72,7 +81,7 @@ function buildFileIndex(contentDir: string): ReadonlyMap continue; } if (!entry.isFile() || !entry.name.endsWith('.md')) continue; - const stat = fs.statSync(fullPath); + const stat = statSync(fullPath); const docName = fullPath.slice(contentDir.length + 1).replace(/\.md$/, ''); index.set(docName, { size: stat.size, modified: stat.mtime.toISOString() }); } diff --git a/packages/server/src/api-rename-log-emission.test.ts b/packages/server/src/api-rename-log-emission.test.ts index 28e17fb23..bb92289fc 100644 --- a/packages/server/src/api-rename-log-emission.test.ts +++ b/packages/server/src/api-rename-log-emission.test.ts @@ -6,13 +6,23 @@ * writer's L2 commit. * - Folder rename of N docs produces N entries with shared `groupId`. */ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { + existsSync, + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { Readable } from 'node:stream'; import simpleGit from 'simple-git'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { createApiExtension } from './api-extension.ts'; import { BacklinkIndex } from './backlink-index.ts'; import { recordContributor, swapContributors } from './contributor-tracker.ts'; @@ -52,8 +62,7 @@ function makeRes(): { res: ServerResponse; captured: CapturedResponse } { function buildFileIndex(contentDir: string): ReadonlyMap { const index = new Map(); function walk(dir: string) { - const fs = require('node:fs') as typeof import('node:fs'); - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { const fullPath = resolve(dir, entry.name); if (entry.isDirectory()) { if (entry.name === '.ok' || entry.name === '.git') continue; @@ -61,7 +70,7 @@ function buildFileIndex(contentDir: string): ReadonlyMap continue; } if (!entry.isFile() || !entry.name.endsWith('.md')) continue; - const stat = fs.statSync(fullPath); + const stat = statSync(fullPath); const docName = fullPath.slice(contentDir.length + 1).replace(/\.md$/, ''); index.set(docName, { size: stat.size, modified: stat.mtime.toISOString() }); } diff --git a/packages/server/src/conflict-storage.test.ts b/packages/server/src/conflict-storage.test.ts index 2b99e0e06..b97ec2f4f 100644 --- a/packages/server/src/conflict-storage.test.ts +++ b/packages/server/src/conflict-storage.test.ts @@ -2,10 +2,11 @@ * Unit tests for ConflictStore — CRUD and resolve strategies. */ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { LOCAL_DIR } from '@inkeep/open-knowledge-core'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { type ConflictEntry, ConflictStore } from './conflict-storage.ts'; // ─── Test helpers ───────────────────────────────────────────────────────────── @@ -16,8 +17,6 @@ let storePath = ''; beforeEach(() => { // Create unique temp dirs per test - const { mkdtempSync } = require('node:fs'); - const { tmpdir } = require('node:os'); tmpDir = mkdtempSync(join(tmpdir(), 'conflict-store-test-')); projectDir = join(tmpDir, 'project'); storePath = join(projectDir, '.ok', LOCAL_DIR, 'conflicts.json'); diff --git a/packages/server/src/file-logger.test.ts b/packages/server/src/file-logger.test.ts index 0e08a6677..a495555e7 100644 --- a/packages/server/src/file-logger.test.ts +++ b/packages/server/src/file-logger.test.ts @@ -1,9 +1,9 @@ -import { afterEach, describe, expect, test } from 'bun:test'; import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import pino from 'pino'; -import { flushFileLogger, MAX_FILE_SIZE } from './file-logger.ts'; +import { afterEach, describe, expect, test } from 'vitest'; +import { createFileLogger, flushFileLogger, MAX_FILE_SIZE } from './file-logger.ts'; const TEST_DIR = join(tmpdir(), `ok-file-logger-test-${process.pid}`); @@ -69,7 +69,6 @@ describe('file logger', () => { expect(statSync(filePath).size).toBeGreaterThan(MAX_FILE_SIZE); // Import and test rotateIfNeeded via createFileLogger side effect - const { createFileLogger } = require('./file-logger.ts'); createFileLogger({ name: 'big', filePath }); const files = readdirSync(TEST_DIR).filter((f: string) => f.startsWith('big.log')); @@ -80,7 +79,6 @@ describe('file logger', () => { test('opens the destination synchronously so same-tick process.exit cannot race the open', () => { mkdirSync(TEST_DIR, { recursive: true }); const filePath = join(TEST_DIR, 'sync-open.log'); - const { createFileLogger } = require('./file-logger.ts'); const logger = createFileLogger({ name: 'sync-open', filePath }); // pino registers an exit hook that calls flushSync() on the destination. // With an async open (sync: false), the fs.open callback that assigns the @@ -98,7 +96,6 @@ describe('file logger', () => { test('createFileLogger unrefs the deferred prune timer so it never blocks process exit', () => { mkdirSync(TEST_DIR, { recursive: true }); - const { createFileLogger } = require('./file-logger.ts'); let unrefCalls = 0; let scheduledMs: number | undefined; // Fake scheduler: records the unref() call (the load-bearing contract) and @@ -131,7 +128,6 @@ describe('file logger', () => { const prev = process.env.OK_LOG_LEVEL; process.env.OK_LOG_LEVEL = 'info'; try { - const { createFileLogger } = require('./file-logger.ts'); const logger = createFileLogger({ name: 'flush', filePath }); logger.warn({ outcome: 'absent', host: 'github.com' }, '[auth] git-credential get'); await flushFileLogger(logger); diff --git a/packages/server/src/file-watcher.test.ts b/packages/server/src/file-watcher.test.ts index c56e67805..ab42fd151 100644 --- a/packages/server/src/file-watcher.test.ts +++ b/packages/server/src/file-watcher.test.ts @@ -1,8 +1,8 @@ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdirSync, realpathSync, symlinkSync, writeFileSync } from 'node:fs'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { resolve } from 'node:path'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { createContentFilter } from './content-filter.ts'; import type { DiskEvent } from './file-watcher'; import { @@ -16,6 +16,7 @@ import { reconcileFileIndexAfterFilterRebuild, registerWrite, startWatcher, + updateFileIndex, updateLastKnownHash, writeTracker, } from './file-watcher'; @@ -502,7 +503,6 @@ describe('startWatcher file index', () => { }); test('file index updates on create event', () => { - const { updateFileIndex } = require('./file-watcher.ts'); const index = new Map(); const event = { kind: 'create' as const, @@ -517,7 +517,6 @@ describe('startWatcher file index', () => { }); test('file index removes entry on delete event', () => { - const { updateFileIndex } = require('./file-watcher.ts'); const index = new Map([['existing', { size: 10, modified: new Date().toISOString() }]]); const event = { kind: 'delete' as const, @@ -529,7 +528,6 @@ describe('startWatcher file index', () => { }); test('file index updates size/modified on update event', () => { - const { updateFileIndex } = require('./file-watcher.ts'); const oldModified = '2020-01-01T00:00:00.000Z'; const index = new Map([['doc', { size: 5, modified: oldModified }]]); const event = { @@ -546,7 +544,6 @@ describe('startWatcher file index', () => { }); test('file index handles rename event', () => { - const { updateFileIndex } = require('./file-watcher.ts'); const index = new Map([['old-name', { size: 10, modified: new Date().toISOString() }]]); const event = { kind: 'rename' as const, @@ -563,7 +560,6 @@ describe('startWatcher file index', () => { }); test('file index caches title + icon on create/update/rename/conflict events', () => { - const { updateFileIndex } = require('./file-watcher.ts'); const index = new Map(); updateFileIndex( diff --git a/packages/server/src/git-preflight.test.ts b/packages/server/src/git-preflight.test.ts index b0350ea8b..7d1dcda38 100644 --- a/packages/server/src/git-preflight.test.ts +++ b/packages/server/src/git-preflight.test.ts @@ -1,7 +1,7 @@ -import { describe, expect, test } from 'bun:test'; -import { mkdtempSync, writeFileSync } from 'node:fs'; +import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { describe, expect, test } from 'vitest'; import { assertGitAvailable, buildGuidance, @@ -481,7 +481,7 @@ VERSION_ID="12"`, 'utf-8', ); // The fixture is fed in directly; we don't rely on /etc/os-release replacement. - const contents = require('node:fs').readFileSync(path, 'utf-8'); + const contents = readFileSync(path, 'utf-8'); expect(detectLinuxFamily(contents)).toBe('debian'); }); }); diff --git a/packages/server/src/process-lock.test.ts b/packages/server/src/process-lock.test.ts index 67f9305c1..c9071bd5b 100644 --- a/packages/server/src/process-lock.test.ts +++ b/packages/server/src/process-lock.test.ts @@ -1,9 +1,9 @@ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { mkdtemp, rm } from 'node:fs/promises'; import { hostname, tmpdir } from 'node:os'; import { resolve } from 'node:path'; import { LOCAL_DIR } from '@inkeep/open-knowledge-core'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { getMachineId } from './machine-id'; import { acquireProcessLock, @@ -647,7 +647,7 @@ describe('readProcessLockDetailed', () => { worktreeRoot: '/legacy', // No protocolVersion, no runtimeVersion — simulates a v0.x lock. }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(versionless), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); @@ -666,7 +666,7 @@ describe('readProcessLockDetailed', () => { protocolVersion: 1, // No runtimeVersion. }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(partial), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); @@ -674,7 +674,7 @@ describe('readProcessLockDetailed', () => { }); test('returns incompatible.corrupt for unparseable JSON', () => { - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, '{not json', 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); expect(result.status).toBe('incompatible'); @@ -683,7 +683,7 @@ describe('readProcessLockDetailed', () => { }); test('returns incompatible.corrupt for shape violation (missing pid)', () => { - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify({ port: 1234 }), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); expect(result.status).toBe('incompatible'); @@ -701,7 +701,7 @@ describe('readProcessLockDetailed', () => { protocolVersion: 1, runtimeVersion: '0.1.0', }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(stale), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); @@ -721,7 +721,7 @@ describe('readProcessLockDetailed', () => { protocolVersion: 1, runtimeVersion: '0.1.0', }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(remote), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir }); @@ -785,7 +785,7 @@ describe('lock-pid security validation', () => { startedAt: new Date().toISOString(), worktreeRoot: '/attacker', }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(hostile), 'utf-8'); expect(readProcessLock({ lockName: LOCK_NAME, lockDir })).toBeNull(); @@ -801,7 +801,7 @@ describe('lock-pid security validation', () => { protocolVersion: 1, runtimeVersion: '1.0.0', }; - require('node:fs').mkdirSync(lockDir, { recursive: true }); + mkdirSync(lockDir, { recursive: true }); writeFileSync(lockPath, JSON.stringify(hostile), 'utf-8'); const result = readProcessLockDetailed({ lockName: LOCK_NAME, lockDir });