replace cross-package _internals imports with owning-package public APIs
move shared length-prefixed wire encoding into @webnet/utils for SSH and SFTP
replace wildcard barrels with explicit named/type exports
remove cross-package re-exports across the workspace and import symbols from their owning packages
expose the loopback transport half as a public type instead of reconstructing it in consumers
enforce boundaries for static imports, dynamic imports, package re-exports, and relative source escapes with tested ESLint rules
document the _internals semver/test policy and add SSH public-surface typetests
keep malformed-auth wire coverage package-local to SSH while preserving the SFTP transport-cleanup regression test through public APIs
move shared path-helper coverage to VFS and leave protocol-specific coverage with FTP
Validation
node --test eslint.config.test.js
npm run lint
npm run format:check
affected-package tests and typechecks for utils, SSH, SFTP, transport, and HTTP
npm run typetest --workspace=packages/ssh
npm test --workspace=packages/ftp --workspace=packages/vfs
npm run build --workspace=packages/example-app
Review
addressed the Opus 5 review suggestions: shared cursor primitives, public loopback type, explicit example-app dependencies, VFS-owned path tests, removal of the empty CI retry commit, broader _internals enforcement, and normalized, invocation-independent relative package-escape enforcement, dedicated boundary-test CI reporting, and root config lint/format coverage
Closes #107
## Summary
- replace cross-package `_internals` imports with owning-package public APIs
- move shared length-prefixed wire encoding into `@webnet/utils` for SSH and SFTP
- replace wildcard barrels with explicit named/type exports
- remove cross-package re-exports across the workspace and import symbols from their owning packages
- expose the loopback transport half as a public type instead of reconstructing it in consumers
- enforce boundaries for static imports, dynamic imports, package re-exports, and relative source escapes with tested ESLint rules
- document the `_internals` semver/test policy and add SSH public-surface typetests
- keep malformed-auth wire coverage package-local to SSH while preserving the SFTP transport-cleanup regression test through public APIs
- move shared path-helper coverage to VFS and leave protocol-specific coverage with FTP
## Validation
- `node --test eslint.config.test.js`
- `npm run lint`
- `npm run format:check`
- affected-package tests and typechecks for utils, SSH, SFTP, transport, and HTTP
- `npm run typetest --workspace=packages/ssh`
- `npm test --workspace=packages/ftp --workspace=packages/vfs`
- `npm run build --workspace=packages/example-app`
## Review
- addressed the Opus 5 review suggestions: shared cursor primitives, public loopback type, explicit example-app dependencies, VFS-owned path tests, removal of the empty CI retry commit, broader `_internals` enforcement, and normalized, invocation-independent relative package-escape enforcement, dedicated boundary-test CI reporting, and root config lint/format coverage
Reviewed the full diff against origin/main (3 commits). The mechanical part of this refactor holds up well, and CI is green on all seven jobs. No correctness issues found; everything below is a nit or a follow-up suggestion.
What I verified
No cross-package _internals imports or cross-package re-exports remain anywhere under packages/*/src, and no export * survives — so the two new ESLint rules genuinely have nothing left to catch.
The explicit barrels are faithful to the wildcards they replaced. I diffed every top-level export in packages/ssh/src/*.ts, packages/smb2/src/protocol/*.ts, crypto/*.ts and auth/*.ts against the new explicit lists in packages/ssh/src/_internals.ts, packages/smb2/src/protocol/index.ts and packages/smb2/src/_internals.ts: nothing was silently dropped.
packages/sftp/src/sftp/cursor.ts is semantically identical to the SSH cursor it replaces for the subset SFTP uses: same BinaryWriter/BinaryReader base, same big-endian config, same u32-length-prefixed string()/utf8(). The bounds checks the SFTP parsers rely on (Reader overrunRangeError) still come from BinaryReader#check, so malformed-packet behaviour is unchanged.
The auth-failure leak coverage really is preserved. The rewritten packages/sftp/src/server/server.test.ts test drives Session.run() through the connection === undefined branch of its finally, which is the same this.#transport.close() path the old malformed-parse test exercised, and it now asserts serverTransport.closed directly rather than inferring it from a rejected read. The malformed-parse case is preserved package-locally in packages/ssh/src/auth.test.ts.
Removed public re-exports break no in-repo consumer and no documented example: nothing imports RawTransport/RawListener/RawDialer/ReadBufferOptions from @webnet/http, nothing imports VFSError/AsyncVFS/Stat/VFSErrorCode from @webnet/sftp/@webnet/ftp/@webnet/drive/@webnet/smb2, and deleting packages/http/src/common/buffer.ts touches no entry in the exports map.
Suggestions
packages/sftp/src/sftp/cursor.ts — duplicated wire cursor, silent drift risk.Writer.string, Reader.string and Reader.utf8 are now byte-for-byte copies of packages/ssh/src/cursor.ts. The two sides encode the same wire convention (u32 length prefix, over the same channel), so a future fix to one copy won't reach the other. Both packages already depend on @webnet/utils, which already owns BinaryReader/BinaryWriter — hosting the length-prefixed string/utf8 pair there would remove the duplication without reintroducing a cross-package _internals edge.
packages/http/src/client/pool.test.ts:12 — ReturnType alias as a workaround.type LoopbackTransportHalf = ReturnType<typeof loopbackTransportPair>[number] works, but it exists only because the class is exported from @webnet/transport/loopback/_internals and not from the public ./loopback entry. Since ./loopback is deliberately cross-package test infrastructure, exporting LoopbackTransportHalf as a type from it and importing it normally would be more honest than reconstructing it structurally.
packages/example-app imports undeclared workspace deps.src/components/Http.tsx now imports @webnet/transport, which isn't in packages/example-app/package.json (@webnet/http was already undeclared there before this PR). It resolves via workspace hoisting, so nothing breaks — but a PR whose whole point is making package boundaries explicit is the natural place to add both.
Path-helper test table now lives in the wrong package. The table deleted from packages/sftp/src/sftp/paths.test.ts is byte-identical to the one still in packages/ftp/src/common/paths.test.ts, which after this change tests @webnet/vfs's resolvePath/parentPath/baseName from a consumer package. Net coverage is unchanged, but several traversal-clamping cases (resolvePath("/x", "//b//c"), resolvePath("/", ".."), empty-arg, trailing slash) exist only in packages/ftp and are missing from packages/vfs/src/path.test.ts. Folding those cases into the vfs suite and leaving only quotePath in the FTP file would put the coverage where the code lives.
8ae0d4b "ci: retry flaky node tests" is an empty commit. It changes no files, so either the intended CI change was lost or it was a rerun trigger with a misleading message. Either way, it'd be good to name which node test was flaky — retrying a flake tends to outlive the reason for it, and the test churn in this PR is in exactly the area (loopback SSH sessions, connection teardown) where a real race would hide.
ESLint rule scope, optional. The two no-restricted-imports patterns enumerate one and two path segments; @webnet/*/**/_internals would cover arbitrary depth in one pattern. Separately, neither rule stops a deep relative escape (../../ssh/src/cursor.js) — there are none today, and adding a pattern for it would close the last hole in the invariant the README now documents.
Nothing here blocks merging.
## Autonomous review — package boundary enforcement
Reviewed the full diff against `origin/main` (3 commits). The mechanical part of this refactor holds up well, and CI is green on all seven jobs. No correctness issues found; everything below is a nit or a follow-up suggestion.
### What I verified
- **No cross-package `_internals` imports or cross-package re-exports remain** anywhere under `packages/*/src`, and no `export *` survives — so the two new ESLint rules genuinely have nothing left to catch.
- **The explicit barrels are faithful to the wildcards they replaced.** I diffed every top-level `export` in `packages/ssh/src/*.ts`, `packages/smb2/src/protocol/*.ts`, `crypto/*.ts` and `auth/*.ts` against the new explicit lists in `packages/ssh/src/_internals.ts`, `packages/smb2/src/protocol/index.ts` and `packages/smb2/src/_internals.ts`: nothing was silently dropped.
- **`packages/sftp/src/sftp/cursor.ts` is semantically identical to the SSH cursor it replaces** for the subset SFTP uses: same `BinaryWriter`/`BinaryReader` base, same big-endian config, same `u32`-length-prefixed `string()`/`utf8()`. The bounds checks the SFTP parsers rely on (`Reader overrun` `RangeError`) still come from `BinaryReader#check`, so malformed-packet behaviour is unchanged.
- **The auth-failure leak coverage really is preserved.** The rewritten `packages/sftp/src/server/server.test.ts` test drives `Session.run()` through the `connection === undefined` branch of its `finally`, which is the same `this.#transport.close()` path the old malformed-parse test exercised, and it now asserts `serverTransport.closed` directly rather than inferring it from a rejected read. The malformed-parse case is preserved package-locally in `packages/ssh/src/auth.test.ts`.
- **Removed public re-exports break no in-repo consumer and no documented example**: nothing imports `RawTransport`/`RawListener`/`RawDialer`/`ReadBufferOptions` from `@webnet/http`, nothing imports `VFSError`/`AsyncVFS`/`Stat`/`VFSErrorCode` from `@webnet/sftp`/`@webnet/ftp`/`@webnet/drive`/`@webnet/smb2`, and deleting `packages/http/src/common/buffer.ts` touches no entry in the `exports` map.
### Suggestions
1. **`packages/sftp/src/sftp/cursor.ts` — duplicated wire cursor, silent drift risk.** `Writer.string`, `Reader.string` and `Reader.utf8` are now byte-for-byte copies of `packages/ssh/src/cursor.ts`. The two sides encode the *same* wire convention (u32 length prefix, over the same channel), so a future fix to one copy won't reach the other. Both packages already depend on `@webnet/utils`, which already owns `BinaryReader`/`BinaryWriter` — hosting the length-prefixed `string`/`utf8` pair there would remove the duplication without reintroducing a cross-package `_internals` edge.
2. **`packages/http/src/client/pool.test.ts:12` — `ReturnType` alias as a workaround.** `type LoopbackTransportHalf = ReturnType<typeof loopbackTransportPair>[number]` works, but it exists only because the class is exported from `@webnet/transport/loopback/_internals` and not from the public `./loopback` entry. Since `./loopback` is deliberately cross-package test infrastructure, exporting `LoopbackTransportHalf` as a type from it and importing it normally would be more honest than reconstructing it structurally.
3. **`packages/example-app` imports undeclared workspace deps.** `src/components/Http.tsx` now imports `@webnet/transport`, which isn't in `packages/example-app/package.json` (`@webnet/http` was already undeclared there before this PR). It resolves via workspace hoisting, so nothing breaks — but a PR whose whole point is making package boundaries explicit is the natural place to add both.
4. **Path-helper test table now lives in the wrong package.** The table deleted from `packages/sftp/src/sftp/paths.test.ts` is byte-identical to the one still in `packages/ftp/src/common/paths.test.ts`, which after this change tests `@webnet/vfs`'s `resolvePath`/`parentPath`/`baseName` from a consumer package. Net coverage is unchanged, but several traversal-clamping cases (`resolvePath("/x", "//b//c")`, `resolvePath("/", "..")`, empty-arg, trailing slash) exist *only* in `packages/ftp` and are missing from `packages/vfs/src/path.test.ts`. Folding those cases into the vfs suite and leaving only `quotePath` in the FTP file would put the coverage where the code lives.
5. **`8ae0d4b "ci: retry flaky node tests"` is an empty commit.** It changes no files, so either the intended CI change was lost or it was a rerun trigger with a misleading message. Either way, it'd be good to name which node test was flaky — retrying a flake tends to outlive the reason for it, and the test churn in this PR is in exactly the area (loopback SSH sessions, connection teardown) where a real race would hide.
6. **ESLint rule scope, optional.** The two `no-restricted-imports` patterns enumerate one and two path segments; `@webnet/*/**/_internals` would cover arbitrary depth in one pattern. Separately, neither rule stops a deep *relative* escape (`../../ssh/src/cursor.js`) — there are none today, and adding a pattern for it would close the last hole in the invariant the README now documents.
Nothing here blocks merging.
codinget
marked the pull request as ready for review 2026-07-26 15:58:59 +02:00
codinget
marked the pull request as work in progress 2026-07-26 16:13:21 +02:00
Re-checked the six commits now on the branch against my earlier review. Every item is addressed correctly, CI is green on run 393, and I found no new defects. Details of what I verified independently of CI:
Cursor consolidation (item 1) — LengthPrefixedBinaryWriter.string / LengthPrefixedBinaryReader.string/utf8 in packages/utils/src/binary.ts are byte-identical in behaviour to both copies they replace (u32 length prefix then bytes; bytes() still routes through BinaryReader#check, so the overrun RangeError the SSH and SFTP parsers depend on is intact). Writer.mpint/nameList in packages/ssh/src/cursor.ts now inherit string() and the chained return this.u32(...).bytes(...) is equivalent to the old two-statement form. Nice side effect: packages/utils/src/binary.test.ts had no test script and was never executed before this PR — it now runs under turbo, with new coverage for the length-prefixed pair including the overrun case.
LoopbackTransportHalf (item 2) — exported as a type from the public ./loopback entry and imported normally in pool.test.ts; the structural ReturnType alias is gone.
Undeclared deps (item 3) — @webnet/http and @webnet/transport added to packages/example-app, in alphabetical position, and the lockfile is updated to match (CI npm ci passes).
Path coverage relocation (item 4) — checked case-by-case: packages/vfs/src/path.test.ts now carries the full 14-case resolvePath table plus the three assertions vfs already had, and the merged parentPath/baseName cases are a strict superset of both old files. Nothing was dropped in the move, and packages/ftp/src/common/paths.test.ts is correctly reduced to quotePath.
Empty commit (item 5) — gone after the rewrite; all seven commits now carry changes.
Lint rule scope (item 6) — I checked the globstar semantics against ESLint's actual matcher (no-restricted-imports feeds group to the ignore package): @webnet/**/_internals matches @webnet/ssh/_internals, @webnet/transport/loopback/_internals and deeper, and correctly does not match @webnet/ssh or @webnet/ssh/internals. The custom package-boundary/no-relative-source-escapes rule resolves the specifier before comparing owners, so ../../ssh/./src/… and ../../ssh/other/../src/… are caught too, and the switch from process.cwd() to a config-relative repositoryRoot in 668ae52 is the right fix — the added test that lints with cwd set to packages/sftp pins it. Testing the ESLint config with the real ESLint API is a good call, and npm test does run it in the node-tests job.
Remaining nits (non-blocking)
eslint.config.test.js:7 — the top-level const filePath = "packages/sftp/src/package-boundary-check.ts" is dead; it's shadowed by the identically-valued default parameter on messages. Worth deleting. Note that root-level files are covered by neither npm run lint (eslint packages/*/src) nor npm run format:check (packages/*/src/**), so nothing will flag this or keep the file formatted.
"test": "node --test eslint.config.test.js && turbo run test" — a failing config test now suppresses every package test result in the node-tests job. Running the config test after turbo run test, or as its own CI step, would keep the two signals independent.
The config test's internals loop only exercises the one-segment depth (@webnet/ssh/_internals). Adding @webnet/transport/loopback/_internals would pin the globstar behaviour I verified by hand, which is the part of the pattern change most likely to regress silently.
no-relative-source-escapes keys on parts[2] === "src", so a relative escape into another package's non-src directory (../../ssh/dist/index.js) isn't reported. There are none today, and dist isn't checked in, so this is theoretical.
Looks ready to me.
## Follow-up review — all six items addressed
Re-checked the six commits now on the branch against my earlier review. Every item is addressed correctly, CI is green on run 393, and I found no new defects. Details of what I verified independently of CI:
- **Cursor consolidation (item 1)** — `LengthPrefixedBinaryWriter.string` / `LengthPrefixedBinaryReader.string`/`utf8` in `packages/utils/src/binary.ts` are byte-identical in behaviour to both copies they replace (`u32` length prefix then bytes; `bytes()` still routes through `BinaryReader#check`, so the overrun `RangeError` the SSH and SFTP parsers depend on is intact). `Writer.mpint`/`nameList` in `packages/ssh/src/cursor.ts` now inherit `string()` and the chained `return this.u32(...).bytes(...)` is equivalent to the old two-statement form. Nice side effect: `packages/utils/src/binary.test.ts` had no `test` script and was never executed before this PR — it now runs under turbo, with new coverage for the length-prefixed pair including the overrun case.
- **`LoopbackTransportHalf` (item 2)** — exported as a type from the public `./loopback` entry and imported normally in `pool.test.ts`; the structural `ReturnType` alias is gone.
- **Undeclared deps (item 3)** — `@webnet/http` and `@webnet/transport` added to `packages/example-app`, in alphabetical position, and the lockfile is updated to match (CI `npm ci` passes).
- **Path coverage relocation (item 4)** — checked case-by-case: `packages/vfs/src/path.test.ts` now carries the full 14-case `resolvePath` table plus the three assertions vfs already had, and the merged `parentPath`/`baseName` cases are a strict superset of both old files. Nothing was dropped in the move, and `packages/ftp/src/common/paths.test.ts` is correctly reduced to `quotePath`.
- **Empty commit (item 5)** — gone after the rewrite; all seven commits now carry changes.
- **Lint rule scope (item 6)** — I checked the globstar semantics against ESLint's actual matcher (`no-restricted-imports` feeds `group` to the `ignore` package): `@webnet/**/_internals` matches `@webnet/ssh/_internals`, `@webnet/transport/loopback/_internals` and deeper, and correctly does *not* match `@webnet/ssh` or `@webnet/ssh/internals`. The custom `package-boundary/no-relative-source-escapes` rule resolves the specifier before comparing owners, so `../../ssh/./src/…` and `../../ssh/other/../src/…` are caught too, and the switch from `process.cwd()` to a config-relative `repositoryRoot` in `668ae52` is the right fix — the added test that lints with `cwd` set to `packages/sftp` pins it. Testing the ESLint config with the real ESLint API is a good call, and `npm test` does run it in the `node-tests` job.
### Remaining nits (non-blocking)
1. `eslint.config.test.js:7` — the top-level `const filePath = "packages/sftp/src/package-boundary-check.ts"` is dead; it's shadowed by the identically-valued default parameter on `messages`. Worth deleting. Note that root-level files are covered by neither `npm run lint` (`eslint packages/*/src`) nor `npm run format:check` (`packages/*/src/**`), so nothing will flag this or keep the file formatted.
2. `"test": "node --test eslint.config.test.js && turbo run test"` — a failing config test now suppresses every package test result in the `node-tests` job. Running the config test after `turbo run test`, or as its own CI step, would keep the two signals independent.
3. The config test's internals loop only exercises the one-segment depth (`@webnet/ssh/_internals`). Adding `@webnet/transport/loopback/_internals` would pin the globstar behaviour I verified by hand, which is the part of the pattern change most likely to regress silently.
4. `no-relative-source-escapes` keys on `parts[2] === "src"`, so a relative escape into another package's non-`src` directory (`../../ssh/dist/index.js`) isn't reported. There are none today, and `dist` isn't checked in, so this is theoretical.
Looks ready to me.
codinget
marked the pull request as work in progress 2026-07-26 17:49:29 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #107
Summary
_internalsimports with owning-package public APIs@webnet/utilsfor SSH and SFTP_internalssemver/test policy and add SSH public-surface typetestsValidation
node --test eslint.config.test.jsnpm run lintnpm run format:checknpm run typetest --workspace=packages/sshnpm test --workspace=packages/ftp --workspace=packages/vfsnpm run build --workspace=packages/example-appReview
_internalsenforcement, and normalized, invocation-independent relative package-escape enforcement, dedicated boundary-test CI reporting, and root config lint/format coverageAutonomous review — package boundary enforcement
Reviewed the full diff against
origin/main(3 commits). The mechanical part of this refactor holds up well, and CI is green on all seven jobs. No correctness issues found; everything below is a nit or a follow-up suggestion.What I verified
_internalsimports or cross-package re-exports remain anywhere underpackages/*/src, and noexport *survives — so the two new ESLint rules genuinely have nothing left to catch.exportinpackages/ssh/src/*.ts,packages/smb2/src/protocol/*.ts,crypto/*.tsandauth/*.tsagainst the new explicit lists inpackages/ssh/src/_internals.ts,packages/smb2/src/protocol/index.tsandpackages/smb2/src/_internals.ts: nothing was silently dropped.packages/sftp/src/sftp/cursor.tsis semantically identical to the SSH cursor it replaces for the subset SFTP uses: sameBinaryWriter/BinaryReaderbase, same big-endian config, sameu32-length-prefixedstring()/utf8(). The bounds checks the SFTP parsers rely on (Reader overrunRangeError) still come fromBinaryReader#check, so malformed-packet behaviour is unchanged.packages/sftp/src/server/server.test.tstest drivesSession.run()through theconnection === undefinedbranch of itsfinally, which is the samethis.#transport.close()path the old malformed-parse test exercised, and it now assertsserverTransport.closeddirectly rather than inferring it from a rejected read. The malformed-parse case is preserved package-locally inpackages/ssh/src/auth.test.ts.RawTransport/RawListener/RawDialer/ReadBufferOptionsfrom@webnet/http, nothing importsVFSError/AsyncVFS/Stat/VFSErrorCodefrom@webnet/sftp/@webnet/ftp/@webnet/drive/@webnet/smb2, and deletingpackages/http/src/common/buffer.tstouches no entry in theexportsmap.Suggestions
packages/sftp/src/sftp/cursor.ts— duplicated wire cursor, silent drift risk.Writer.string,Reader.stringandReader.utf8are now byte-for-byte copies ofpackages/ssh/src/cursor.ts. The two sides encode the same wire convention (u32 length prefix, over the same channel), so a future fix to one copy won't reach the other. Both packages already depend on@webnet/utils, which already ownsBinaryReader/BinaryWriter— hosting the length-prefixedstring/utf8pair there would remove the duplication without reintroducing a cross-package_internalsedge.packages/http/src/client/pool.test.ts:12—ReturnTypealias as a workaround.type LoopbackTransportHalf = ReturnType<typeof loopbackTransportPair>[number]works, but it exists only because the class is exported from@webnet/transport/loopback/_internalsand not from the public./loopbackentry. Since./loopbackis deliberately cross-package test infrastructure, exportingLoopbackTransportHalfas a type from it and importing it normally would be more honest than reconstructing it structurally.packages/example-appimports undeclared workspace deps.src/components/Http.tsxnow imports@webnet/transport, which isn't inpackages/example-app/package.json(@webnet/httpwas already undeclared there before this PR). It resolves via workspace hoisting, so nothing breaks — but a PR whose whole point is making package boundaries explicit is the natural place to add both.Path-helper test table now lives in the wrong package. The table deleted from
packages/sftp/src/sftp/paths.test.tsis byte-identical to the one still inpackages/ftp/src/common/paths.test.ts, which after this change tests@webnet/vfs'sresolvePath/parentPath/baseNamefrom a consumer package. Net coverage is unchanged, but several traversal-clamping cases (resolvePath("/x", "//b//c"),resolvePath("/", ".."), empty-arg, trailing slash) exist only inpackages/ftpand are missing frompackages/vfs/src/path.test.ts. Folding those cases into the vfs suite and leaving onlyquotePathin the FTP file would put the coverage where the code lives.8ae0d4b "ci: retry flaky node tests"is an empty commit. It changes no files, so either the intended CI change was lost or it was a rerun trigger with a misleading message. Either way, it'd be good to name which node test was flaky — retrying a flake tends to outlive the reason for it, and the test churn in this PR is in exactly the area (loopback SSH sessions, connection teardown) where a real race would hide.ESLint rule scope, optional. The two
no-restricted-importspatterns enumerate one and two path segments;@webnet/*/**/_internalswould cover arbitrary depth in one pattern. Separately, neither rule stops a deep relative escape (../../ssh/src/cursor.js) — there are none today, and adding a pattern for it would close the last hole in the invariant the README now documents.Nothing here blocks merging.
6c3b499f38to8451205d62Follow-up review — all six items addressed
Re-checked the six commits now on the branch against my earlier review. Every item is addressed correctly, CI is green on run 393, and I found no new defects. Details of what I verified independently of CI:
LengthPrefixedBinaryWriter.string/LengthPrefixedBinaryReader.string/utf8inpackages/utils/src/binary.tsare byte-identical in behaviour to both copies they replace (u32length prefix then bytes;bytes()still routes throughBinaryReader#check, so the overrunRangeErrorthe SSH and SFTP parsers depend on is intact).Writer.mpint/nameListinpackages/ssh/src/cursor.tsnow inheritstring()and the chainedreturn this.u32(...).bytes(...)is equivalent to the old two-statement form. Nice side effect:packages/utils/src/binary.test.tshad notestscript and was never executed before this PR — it now runs under turbo, with new coverage for the length-prefixed pair including the overrun case.LoopbackTransportHalf(item 2) — exported as a type from the public./loopbackentry and imported normally inpool.test.ts; the structuralReturnTypealias is gone.@webnet/httpand@webnet/transportadded topackages/example-app, in alphabetical position, and the lockfile is updated to match (CInpm cipasses).packages/vfs/src/path.test.tsnow carries the full 14-caseresolvePathtable plus the three assertions vfs already had, and the mergedparentPath/baseNamecases are a strict superset of both old files. Nothing was dropped in the move, andpackages/ftp/src/common/paths.test.tsis correctly reduced toquotePath.no-restricted-importsfeedsgroupto theignorepackage):@webnet/**/_internalsmatches@webnet/ssh/_internals,@webnet/transport/loopback/_internalsand deeper, and correctly does not match@webnet/sshor@webnet/ssh/internals. The custompackage-boundary/no-relative-source-escapesrule resolves the specifier before comparing owners, so../../ssh/./src/…and../../ssh/other/../src/…are caught too, and the switch fromprocess.cwd()to a config-relativerepositoryRootin668ae52is the right fix — the added test that lints withcwdset topackages/sftppins it. Testing the ESLint config with the real ESLint API is a good call, andnpm testdoes run it in thenode-testsjob.Remaining nits (non-blocking)
eslint.config.test.js:7— the top-levelconst filePath = "packages/sftp/src/package-boundary-check.ts"is dead; it's shadowed by the identically-valued default parameter onmessages. Worth deleting. Note that root-level files are covered by neithernpm run lint(eslint packages/*/src) nornpm run format:check(packages/*/src/**), so nothing will flag this or keep the file formatted."test": "node --test eslint.config.test.js && turbo run test"— a failing config test now suppresses every package test result in thenode-testsjob. Running the config test afterturbo run test, or as its own CI step, would keep the two signals independent.@webnet/ssh/_internals). Adding@webnet/transport/loopback/_internalswould pin the globstar behaviour I verified by hand, which is the part of the pattern change most likely to regress silently.no-relative-source-escapeskeys onparts[2] === "src", so a relative escape into another package's non-srcdirectory (../../ssh/dist/index.js) isn't reported. There are none today, anddistisn't checked in, so this is theoretical.Looks ready to me.