agentsclimarketplace

Architecture navigation

Skill andymai/brepjs/.claude/skills/architecture-navigation

This skill should be used when deciding which layer or module a new file, function, or directory belongs in, or when a layer-boundary check fails. It owns the placement decision (which layer/module) and the import-direction rules — not the end-to-end recipe for wiring an operation through the API ladder (that is adding-operations). Trigger phrases include "where should this file/function go", "which layer is X in", "can topology import from sketching", "layer boundary violation", "check:boundaries failed", "VIOLATION: src/... imports from", "Direct .oc access is banned", "method calls on .wrapped are banned", or adding a new src/ directory or module.From its SKILL.md

Install
npx -y skills add andymai/brepjs --skill architecture-navigation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

13.5 KB, as published. Nobody here has run it

Navigating the layered architecture

brepjs enforces a four-layer architecture where imports flow downward or sideways, never upward. The enforcement script is the source of truth for layer membership — CLAUDE.md and docs/architecture.md summaries lag behind it.

Layer table (source of truth: scripts/check-layer-boundaries.sh)

LayerDirectoriesMay import from
0kernel/, utils/nothing internal (same-layer only)
1core/layer 0
2topology/, 2d/, operations/, query/, measurement/, io/, worker/, csg/, voxel/, implicit/layers 0–1 + each other
3sketching/, text/, projection/, gear/, ns/, lattice/layers 0–2 + each other

Rules and caveats:

  • The rule is target_layer <= source_layer (the if (( target_layer > src_layer )) check in scripts/check-layer-boundaries.sh). Same-layer imports are always legal — e.g. src/topology/wrapperFns.ts imports @/operations/api.js and @/measurement/measureFns.js (both Layer 2).
  • Files at the src/ root (index.ts, quick.ts, sub-path entries like topology.ts, core.ts, result.ts) are exempt — the script assigns them layer -1 and skips them. They exist to re-export everything.
  • The script resolves both @/ alias and relative imports, but only greps from '...' statements — dynamic import() expressions without from are not checked. Do not rely on that gap.
  • When adding a new top-level src/ directory, add it to the case statement in the get_layer() function in scripts/check-layer-boundaries.sh and to the error-message layer listing at the bottom of the same script, or its imports go entirely unchecked.

Decision procedure: where does new code belong?

Work through this list in order; stop at the first match.

  1. A raw kernel/OCCT API call (anything touching oc.* or WASM objects) → a *Ops.ts file under src/kernel/occt/ (or src/kernel/brepkit/), exposed as a method on KernelAdapter in src/kernel/types.ts. Follow the /new-kernel-method command in .claude/commands/. See the kernel-abstraction skill.
  2. A pure helper with no geometry dependency (string/array/math utilities) → src/utils/.
  3. Shared types, Result, vectors, memory/disposal, branded shape types → src/core/ (e.g. src/core/result.ts, src/core/shapeTypes.ts, src/core/disposal.ts).
  4. A shape operation (transform, boolean, modifier, query, measurement, I/O) → the matching Layer 2 module's *Fns.ts file, then climb the API ladder below. See the adding-operations skill for the full recipe.
  5. High-level sugar composing Layer 2 operations (sketch DSL, text, projection, gear generators, namespace re-exports) → the matching Layer 3 module.
  6. In doubt between two layers → put it in the lower layer that has everything it needs. Code can always be re-exported upward; it can never import upward.

The API ladder (Layer 2 shape operations)

New functionality goes in *Fns.ts first, then surfaces upward. The full chain (mermaid diagram in src/topology/README.md):

shape() fluent facade (wrapperFns.ts)
  → api.ts (functional public API)
    → *Fns.ts implementations
      → cast.ts / getKernel()

Checklist when adding an operation:

  1. Implement in the module's *Fns.ts (e.g. src/topology/booleanFns.ts), taking/returning branded types from src/core/shapeTypes.ts, returning Result<T, E> for fallible operations (see the result-error-handling skill).

  2. Add a short-named wrapper in src/topology/api.ts that accepts Shapeable<T> and delegates via resolve() (both from src/topology/apiTypes.ts), using an options object for optional parameters. Pattern:

    export function translate<T extends AnyShape<Dimension>>(shape: Shapeable<T>, v: Vec3): T {
      return transforms.translate(resolve(shape), v);
    }
    
  3. Optionally add a chainable method on Wrapped<T> in src/topology/wrapperFns.ts — it must delegate to api.ts, never contain its own implementation.

  4. Export from src/index.ts (organized by layer with // ── Layer N ── banners) and the matching sub-path entry at src/ root (topology.ts, operations.ts, etc. — table in docs/codebase-map.md).

  5. Update the module's README.md if one exists (pre-commit prints a non-blocking reminder via scripts/check-readme-reminders.sh).

Import and naming rules

RuleEnforced by
Imports flow downward/same-layer onlycheck:boundaries (pre-commit, CI, npm run validate)
Cross-directory imports use @/ alias (@/kernel/index.js); same-directory imports stay relative (./foo.js)convention only
All .ts imports use .js extensions (ESM; Vite transforms at build time)convention only — typecheck passes without it, so review for it explicitly
camelCase filenames (no PascalCase, no kebab-case)convention only
import type for type-only importsESLint consistent-type-imports

The @/ alias maps to ./src/* via tsconfig.json paths and vite.config.ts resolve.alias.

The kernel-abstraction rule

Layer 2+ code treats shapes as opaque handles. Read shape.wrapped only to pass it into a kernel method — never call methods on it, and never touch .oc:

// Correct: .wrapped passed as an argument
const volume = getKernel().volume(shape.wrapped);

// Banned: method called ON .wrapped
const hash = shape.wrapped.HashCode(1000);

ESLint enforces both bans via the no-restricted-syntax rule in the "Kernel abstraction boundary" config block in eslint.config.js, but only for ten enumerated directories: topology/, operations/, measurement/, query/, io/, 2d/, sketching/, projection/, text/, worker/. The rule applies just as much in csg/, voxel/, implicit/, gear/, ns/, and lattice/ — lint simply does not cover them yet, so apply it by discipline there (and add new directories to the ESLint file list when creating them).

If an operation needs a kernel capability that KernelAdapter lacks, the fix is never to reach through .wrapped — add the method to the adapter first (kernel-abstraction skill, /new-kernel-method command).

Fixing violations

SymptomCauseFix
VIOLATION: src/operations/foo.ts (layer 2: operations) imports from '@/sketching/draw.js' (layer 3: sketching) from check:boundariesLower layer imports a higher oneOne of: (a) move the importing code up to the higher layer, (b) extract the shared logic down into a layer both can import (usually core/ or a Layer 2 sibling), (c) invert the dependency — have the higher layer pass data/callbacks down
Direct .oc access is banned in Layer 2+ code (ESLint)Raw OCCT call outside kernel/Move the call into a kernel *Ops.ts file behind a KernelAdapter method, then call getKernel().method(...)
Direct method calls on .wrapped are banned in Layer 2+ code (ESLint)Treating a shape handle as an object with behaviorFind (or add) the equivalent KernelAdapter method in src/kernel/types.ts and call it via getKernel()
Boundary check passes locally but fails in CIPre-commit runs only the staged variant (check:boundaries:staged, .husky/pre-commit Tier 1); CI's quality job runs the full treeRun npm run check:boundaries (full) before pushing
New directory's imports never flaggedDirectory missing from the script's case statement (layer -1 is skipped)Register it in scripts/check-layer-boundaries.sh

Reproduce locally:

npm run check:boundaries          # full tree
npm run check:boundaries:staged   # staged files only (what pre-commit runs)
npx eslint src/ --quiet           # .oc / .wrapped bans
npm run validate                  # typecheck + lint + boundaries + format + changed tests

Where each gate runs: pre-commit Tier 1 (staged boundaries, parallel with lint-staged and typecheck), CI quality job in .github/workflows/ci.yml (full boundaries + check:patterns + knip), and scripts/validate-change.sh via npm run validate. See the quality-gates skill for the full gate matrix.

Additional resources

  • docs/architecture.md — layer diagrams, data flow, key patterns with correct/banned .wrapped examples (layer lists predate csg/voxel/implicit/gear/ns/lattice; trust the script).
  • docs/codebase-map.md — sub-path entry-point table and module→key-file maps.
  • docs/which-api.md — choosing between the fluent wrapper, Sketcher, functional API, and Drawing as a consumer.
  • CONTRIBUTING.md — "Layer Boundaries" and "ESM Imports" sections, including the boundary-error walkthrough.
  • Module READMEs exist for 2d, core, io, kernel, kernel/manifold, measurement, operations, projection, query, sketching, text, topology, utils — read the target module's README before adding code there. (worker, csg, voxel, implicit, gear, ns, lattice have none yet.)
  • ADRs for the "why": docs/decisions/0001-layered-architecture.md, 0002-kernel-abstraction.md, 0006-domain-boundaries.md, 0007-kernel-interface-segregation.md.
  • Sibling skills: adding-operations (end-to-end operation recipe), kernel-abstraction (adapter methods, getKernel/withKernel), result-error-handling (Result<T,E> conventions), quality-gates (all local/CI checks).

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.