[E00-S01-T04] Add dependency-boundary rule/test #371

Merged
kpcto merged 1 commits from feature/157 into main 2026-08-28 08:32:09 +00:00
Member

What changed

Adds the dependency-boundary rule and its enforcing architecture import test for [E00-S01-T04] Add dependency-boundary rule/test (#157):

  • dependency-boundaries.json (new, repo root) — the dependency-boundary rule, declared as machine-readable JSON:
    • groups — classifies workspace packages by directory: apps/ → apps, packages/ → packages, extensions/ → extensions
    • boundaries — encodes the boundary: packages -> extensions with "allow": false ("no core package can import a concrete extension"; core depends only on extension contracts, concrete extensions are wired in by the app layer)
  • tests/architecture-import.test.mjs (new) — the architecture import test that enforces the rule. Built on node:test with zero new dependencies (lockfile untouched):
    • loads and shape-validates the rule file (a malformed rule fails loudly)
    • enumerates every workspace package from the rule's groups (reads each package.json name)
    • scans each package's source (apps/*, packages/*, extensions/*; skips node_modules/dist/coverage) and resolves every module specifier — bare workspace names (@personal-blog/...), relative/absolute/file: paths, import, export … from, dynamic import(), require() — to a workspace group
    • fails with a per-file/line violation report on any forbidden packages/* -> extensions/* edge
    • comment/string-aware scanning (line/block comments, string and template literals stripped) so commented-out imports never false-positive and reported line numbers map to the original source
    • synthetic negative cases prove detection (bare name, relative path, export-from, dynamic import, require; line numbers; comments ignored; allowed directions and external/node builtins produce no violations) and the integration case proves the current graph is compliant

Explicitly out of scope per the brief: ESM package boundaries (E00-S01-T03, merged), root build/test scripts (E00-S01-T05 — no root scripts, no CI wiring here; run with node --test tests/architecture-import.test.mjs), non-container developer path docs (E00-S01-T06). No manifests, no lockfile, no CI changes in this PR.

Criterion → test table

Acceptance criterion Evidence / test
a dependency-boundary rule is added dependency-boundaries.json declares groups (apps/packages/extensions → apps/, packages/, extensions/) and a boundaries entry packages -> extensions with "allow": false; test "dependency-boundaries.json is present and declares the core -> extensions boundary" asserts the rule file exists and contains that forbidden edge
the architecture import test enforces the rule tests/architecture-import.test.mjs loads the rule, scans every workspace package's source, resolves each import to a group and fails on any core→extension edge. Unit tests prove detection (bare name / relative path / export-from / dynamic import / require → violation; comments, allowed directions, external + node: imports → no violation). Integration test over the real workspace graph passes with zero violations; a deliberately injected import … from '@personal-blog/example-extension' in packages/core/src/index.ts makes it fail with packages/core/src/index.ts:1 … violates packages -> extensions

Test plan executed

  • node --test tests/architecture-import.test.mjs (Node 22; node:test, CI runs Node 24) → 10/10 pass ✓
  • Negative probe: temporarily wrote export { register } from '@personal-blog/example-extension'; into packages/core/src/index.ts → integration test fails with packages/core/src/index.ts:1 imports @personal-blog/example-extension (@personal-blog/example-extension [extensions]) — violates packages -> extensions; reverted → green again ✓
  • corepack pnpm install --frozen-lockfile (pnpm 11.23.0, same as CI) → "Already up to date", exit 0, pnpm-lock.yaml unchanged (git status clean apart from the two new files) ✓
  • node --check tests/architecture-import.test.mjs + JSON parse of the rule file → valid ✓

Risks / notes

  • The rule is enforced only when the test is run — wiring it into root test scripts / CI is E00-S01-T05 (out of scope here); the run command is documented in the test header.
  • The scanner is regex/prefix based (no AST, no TypeScript compiler dependency): it resolves imports by workspace package name and by resolved path prefix, which covers the current and near-future source shapes without adding dependencies.
  • Only the packages -> extensions edge is currently forbidden (per the issue intent); the rule file's groups + boundaries structure is designed for more edges to be added later.

Closes #157

## What changed Adds the dependency-boundary rule and its enforcing architecture import test for [E00-S01-T04] Add dependency-boundary rule/test (#157): - **`dependency-boundaries.json`** (new, repo root) — the dependency-boundary rule, declared as machine-readable JSON: - `groups` — classifies workspace packages by directory: `apps/` → `apps`, `packages/` → `packages`, `extensions/` → `extensions` - `boundaries` — encodes the boundary: `packages -> extensions` with `"allow": false` ("no core package can import a concrete extension"; core depends only on extension contracts, concrete extensions are wired in by the app layer) - **`tests/architecture-import.test.mjs`** (new) — the architecture import test that enforces the rule. Built on `node:test` with **zero new dependencies** (lockfile untouched): - loads and shape-validates the rule file (a malformed rule fails loudly) - enumerates every workspace package from the rule's groups (reads each `package.json` `name`) - scans each package's source (`apps/*`, `packages/*`, `extensions/*`; skips `node_modules`/`dist`/`coverage`) and resolves every module specifier — bare workspace names (`@personal-blog/...`), relative/absolute/file: paths, `import`, `export … from`, dynamic `import()`, `require()` — to a workspace group - fails with a per-file/line violation report on any forbidden `packages/* -> extensions/*` edge - comment/string-aware scanning (line/block comments, string and template literals stripped) so commented-out imports never false-positive and reported line numbers map to the original source - synthetic negative cases prove detection (bare name, relative path, export-from, dynamic import, require; line numbers; comments ignored; allowed directions and external/node builtins produce no violations) and the integration case proves the current graph is compliant Explicitly out of scope per the brief: ESM package boundaries (E00-S01-T03, merged), root build/test scripts (E00-S01-T05 — no root `scripts`, no CI wiring here; run with `node --test tests/architecture-import.test.mjs`), non-container developer path docs (E00-S01-T06). No manifests, no lockfile, no CI changes in this PR. ## Criterion → test table | Acceptance criterion | Evidence / test | | --- | --- | | a dependency-boundary rule is added | `dependency-boundaries.json` declares `groups` (`apps`/`packages`/`extensions` → `apps/`, `packages/`, `extensions/`) and a `boundaries` entry `packages -> extensions` with `"allow": false`; test "dependency-boundaries.json is present and declares the core -> extensions boundary" asserts the rule file exists and contains that forbidden edge | | the architecture import test enforces the rule | `tests/architecture-import.test.mjs` loads the rule, scans every workspace package's source, resolves each import to a group and fails on any core→extension edge. Unit tests prove detection (bare name / relative path / export-from / dynamic import / require → violation; comments, allowed directions, external + `node:` imports → no violation). Integration test over the real workspace graph passes with zero violations; a deliberately injected `import … from '@personal-blog/example-extension'` in `packages/core/src/index.ts` makes it fail with `packages/core/src/index.ts:1 … violates packages -> extensions` | ## Test plan executed - `node --test tests/architecture-import.test.mjs` (Node 22; `node:test`, CI runs Node 24) → **10/10 pass** ✓ - Negative probe: temporarily wrote `export { register } from '@personal-blog/example-extension';` into `packages/core/src/index.ts` → integration test **fails** with `packages/core/src/index.ts:1 imports @personal-blog/example-extension (@personal-blog/example-extension [extensions]) — violates packages -> extensions`; reverted → green again ✓ - `corepack pnpm install --frozen-lockfile` (pnpm 11.23.0, same as CI) → "Already up to date", exit 0, `pnpm-lock.yaml` unchanged (`git status` clean apart from the two new files) ✓ - `node --check tests/architecture-import.test.mjs` + JSON parse of the rule file → valid ✓ ## Risks / notes - The rule is enforced only when the test is run — wiring it into root test scripts / CI is E00-S01-T05 (out of scope here); the run command is documented in the test header. - The scanner is regex/prefix based (no AST, no TypeScript compiler dependency): it resolves imports by workspace package name and by resolved path prefix, which covers the current and near-future source shapes without adding dependencies. - Only the `packages -> extensions` edge is currently forbidden (per the issue intent); the rule file's `groups` + `boundaries` structure is designed for more edges to be added later. Closes #157
bot-implementer added 1 commit 2026-08-28 08:30:49 +00:00
feat: add dependency-boundary rule and architecture import test (E00-S01-T04)
CI / Frozen lockfile install (pull_request) Successful in 38s
4d0df92290
Adds a declarative dependency-boundary rule (dependency-boundaries.json)
classifying workspace packages into apps/packages/extensions groups and
forbidding packages/* (core) from importing extensions/* (concrete
extensions); core depends only on extension contracts, never concrete
extensions.

Adds tests/architecture-import.test.mjs (node:test, zero new dependencies,
lockfile untouched) that loads the rule, walks every workspace package's
source and resolves each import/export/require specifier (bare workspace
names, relative paths, dynamic import(), require(), export-from) to a
group, failing on any forbidden core -> extension edge. Comment-aware
scanning avoids false positives; line numbers in violation reports map to
the original source. Synthetic negative cases prove detection (bare name,
relative path, export-from, dynamic import, require), while the current
compliant graph passes with zero violations.

Verified: 10/10 tests pass; injecting a real core -> extension import makes
the integration test fail with a per-file/line violation report; pnpm 11.23.0
install --frozen-lockfile passes unchanged (CI frozen-install job stays green).

Closes #157
kpcto merged commit 2c0d081983 into main 2026-08-28 08:32:09 +00:00
kpcto deleted branch feature/157 2026-08-28 08:32:10 +00:00
Sign in to join this conversation.