Architecture enforcement#
pnpm verify runs pnpm check:architecture against the checkout and
pnpm test:architecture against isolated filesystem/package fixtures, after the
strict format/lint/type/build/baseline/rule checks. All authored JS/TS variants
are scanned. Prototypes receive the same checks.
One explicit policy#
tooling/architecture/policy.mts describes actual module roots, names, source
entries, exact built export maps, private trees, permitted dependency directions,
runtime environments, and dependencies that must load on demand. The lint rule
and graph share this policy and the TypeScript resolver. Future foundation paths
remain illustrative; the manifest does not require empty packages.
The current direction is embedding -> reporting/capture and reporting -> capture. Capture cannot depend on reporting, including its UI. Embedding must defer capture and reporting/browser. This encodes ownership/loading constraints, not product interfaces or an obligation to add dependencies to the empty packages.
Support trees currently contain docs and tooling. New root files, support trees (for example examples or CI), extensions, and conventional names require explicit registration. Support trees do not waive naming, package registration, imports, or cycles. Packages anywhere require a module registration. Only listed generated and dependency directories, Git metadata, and TypeScript build info are excluded.
Paths and exports#
Use lowercase kebab-case directory/file basenames, with .test or .spec for
tests. Registered tool suffixes include .config, .build, .browser, .base,
and .d. Catalog tests use .snag.md and may start with a numeric filename prefix for deterministic ordering. README.md, AGENTS.md, GLOSSARY.md, SKILL.md and existing dotfiles are
explicit exceptions. Unknown dotfiles and suffixes fail. React symbol names may
remain PascalCase. Symlinked authored source is rejected to avoid hidden ownership.
JavaScript exports must match registered types and import targets exactly,
without extra subpaths or conditions. Optional CSS entries use exact registered
relative .css string targets, without extra conditions; their source entries
still count as UI for runtime boundary enforcement. Each source entry must exist
with exact case. External
callers must resolve to supported source entries. Package-name deep imports fail
even if they target public source. Relative imports to public source and
same-module private imports are valid.
Resolution uses actual files, inherited tsconfig aliases, and workspace entries. Aliases take precedence when they redirect workspace names. Slash styles are normalized; resolved spelling is checked against the filesystem, including on case-insensitive Windows. Invalid/unresolved local paths fail. Lint alone is not the unresolved-import check.
Dependencies and loading#
Static imports, re-exports, import types, import-equals, literal import() and
require() contribute edges. Computed arguments fail: use a literal. The one
registered exception is the deferred trusted project-module loader in
tooling/testing/plan.mts, using the exact projectModuleUrl identifier. It is
restricted to that tooling path; eager imports, require(), different identifiers,
and module-source exceptions still fail. Project modules execute trusted local
code and are outside the workspace dependency graph. File and
module cycles include type/deferred edges; module cycles also include workspace
dependencies declared in package manifests.
Runtime traversal starts at every supported entry and follows transitive helpers.
Declaration-level import type / export type edges load nothing. With the
required verbatimModuleSyntax setting, inline import { type View } and
export { type View } retain empty runtime declarations and eagerly evaluate
the target module, so they are runtime edges. Static imports and top-level dynamic imports,
including immediately invoked functions, are eager. Imports in other functions
are deferred. Deferred edges allow portable entries to reach optional browser/UI
code; top-level import() does not. Portable/browser/UI entries cannot reach
server code even lazily; server entries cannot reach browser/UI code. CSS and JSX
source count as UI.
Classify external runtime dependencies in externalEnvironments when adding them.
React/React DOM are UI; Node built-ins are server-only. Unknown external runtime
dependencies fail closed, including deferred ones. Longest matching module
zones refine the default environment, separating browser/UI source from portable
entries.
This source graph does not evaluate arbitrary JavaScript, infer calls to separately declared functions at initialization, inspect external packages' transitive contents, or interpret CSS imports/HTML script tags. Review initialization calls and external classifications. Retain #26's built-consumer/selective-loading checks for actual runtime and bundler behavior.
Evolve the policy#
Move source and update its root/entries/exports/private zones together. Register new packages with their dependencies/environments, and update workspace, TypeScript, build and lint configuration without relaxing strictness. Add path exceptions individually. Do not allowlist a prototype or disable checks. Changes to accepted foundation responsibilities need a recorded decision.
Add passing/failing isolated fixtures in tooling/architecture/check.test.mts for
each new category or meaningful regression. Tests create real package trees and
tsconfig files, assert diagnostics, and remove only their own fixtures. An actual
module move demonstrates policy evolution. Run pnpm test:architecture, then
pnpm verify. Diagnostics identify naming/layout, packages/exports, private and
unsupported imports, casing/resolution, dependency direction, computed imports,
runtime boundaries, eager optional dependencies, external classification, and
file/module cycles.