ci(lint): enforce app→shared→shared module-boundary DAG (SF#1)#50
Merged
Conversation
Converts the CLAUDE.md "apps never depend on apps; shared depends only on shared" invariant from a human-review rule into a CI gate — the highest-leverage should-fix from the PR #49 code review (SF#1). A single accidental `dashboard -> cli` (or `shared -> app`) import now fails the build, not just review; the prior PR spent ~3,000 lines removing exactly that inverted edge. - eslint.config.mjs (root, flat): registers ONLY @nx/enforce-module-boundaries, keyed off the scope:* tags every project.json already carries (scope:shared -> shared only; scope:cli -> cli+shared+agents; scope:dashboard -> dashboard+shared; scope:agents -> agents). No typescript-eslint/react rule SUITES enabled — this stays a dependency-graph gate, not a style lint. - @typescript-eslint and react-hooks plugins are registered (rules OFF) only so the codebase's existing intentional inline disable directives resolve; reportUnusedDisableDirectives is off so those inert suppressions are not flagged. Turning those rule suites on is a separate, intentional change. - checkDynamicDependenciesExceptions exempts @open-code-review/* from the "static import of a lazy-loaded library" facet: the CLI intentionally lazy-loads persistence on hot paths (deferring node:sqlite). Lazy-load consistency is a separate concern from the DAG this gate owns. - nx.json registers @nx/eslint/plugin so the pre-existing `lint` scripts (`nx run-many -t lint`, `nx lint cli`) light up; ci.yml adds a Lint job. - Verified: lint is green across all 8 projects on the clean tree, and a planted `dashboard -> cli` import fails with "Imports of apps are forbidden". No source files changed — config + tooling only. Co-Authored-By: claude-flow <ruv@ruv.net>
Genuinely worked the approved review's should-fixes + cheap suggestions.
Should Fix:
- SF#1 (durable canary): add a fitness-function self-test
(packages/shared/platform/src/__tests__/module-boundary-gate.test.ts) that runs
REAL eslint over a planted dashboard->cli import and asserts it fails, plus a
legal dashboard->platform control that passes. Cross-platform (node + eslint's
JS entry, not .bin) so it runs in the Windows unit leg too. Closes the "gate can
silently disarm" gap the rule exists to prevent.
- SF#2: drop the unused jsonc-eslint-parser devDep (transitive of
@nx/eslint-plugin anyway); refresh lockfile.
- SF#4: normalize @nx/eslint + @nx/eslint-plugin pins ~22.4.0 -> ^22.0.0 to match
the other @nx/* deps (no skew on the next nx migrate).
- SF#3: comment that scope:cli covers cli-e2e and scope:dashboard covers
dashboard-{api,ui}-e2e, with a note to add scope:e2e if they ever diverge.
Suggestions folded in:
- Architect F3: removed the blanket `packages/agents/**` ignore so the one real TS
file there (release/version-actions.ts) IS boundary-checked; agents now lints
(9 projects). Content assets are excluded by the `.ts` files glob, not an ignore.
- Architect F1: comment that app->app is blocked by BOTH the allow-lists AND the
projectType:"application" default; the canary re-validates the combination.
- Architect F2: header note that the gate is single-axis (scope:* only) by design,
leaving type:* for future rules.
- Fowler F2: note that no-control-regex is a core rule (needs no plugin).
- Quality Q6/Q7: ASCII alias for the app->shared->shared arrow; comment the
default allow:[]/enforceBuildableLibDependency:false options.
Deferred (review agreed / explicitly future): the lint-cache `^default` input
tightening (Principal F5/F6 — defer until bake-in; tightening risks under-
invalidation since the rule reads dep tags), and React/TS rule SUITES.
Verified: lint + typecheck green across all 9 projects; canary passes (violation
fails, legal passes).
Note for the repo admin: add "Lint (module boundaries)" to main's required status
checks (Quality Q5) — a repo setting, not code.
Co-Authored-By: claude-flow <ruv@ruv.net>
Owner
Author
Addressed round-1 review (APPROVE) —
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enforce the
app → shared → sharedmodule-boundary DAG in CIImplements SF#1 — the highest-leverage should-fix from the PR #49 code review — as its own small, focused change.
The review noted: the predecessor PR spent ~3,000 lines removing an inverted
dashboard → clidependency, yet theapp → shared → sharedrule was policed only by CLAUDE.md + human review. A single accidentalimport "@open-code-review/cli"in the dashboard would silently reinstate the cycle and CI would not catch it. This converts that invariant into a CI gate.What this does
eslint.config.mjs(root, flat) — registers only@nx/enforce-module-boundaries, keyed off thescope:*tags everyproject.jsonalready carries:scope:shared→ shared onlyscope:cli→ cli + shared + agentsscope:dashboard→ dashboard + sharedscope:agents→ agentseslint-disabledirectives resolve (turning their suites on is a separate, intentional change).checkDynamicDependenciesExceptionsexempts@open-code-review/*from the "static import of a lazy-loaded library" facet — the CLI intentionally lazy-loadspersistenceon hot paths to defernode:sqlite; lazy-load consistency is a separate concern from the DAG.nx.jsonregisters@nx/eslint/pluginso the pre-existinglintscripts (nx run-many -t lint,nx lint cli) finally light up;ci.ymladds aLint (module boundaries)job.Verification
nx run-many -t lintgreen across all 8 projects on the clean tree.dashboard → cliimport fails withImports of apps are forbidden @nx/enforce-module-boundaries.nx run-many -t typecheckunaffected.Scope intentionally minimal: enabling the broader React/TS rule suites (and fixing the lazy-load consistency the rule also surfaced) are follow-ups, called out in the config comments.