# Design — `@suaveplan/overview` > Non-normative design notes for the enterprise-overview change. Normative > requirements live in `./specs/overview/spec.md`. --- ## 1. What the library is `@suaveplan/overview` is the **typed, schema-validated, graph-aware library form of the monorepo manifest**. The data model mirrors what `scripts/folder-reorg/generate-manifest.ts` produces ad-hoc today (and that script's own header comment names this package as the eventual replacement). It is a **leaf** package. Direct workspace deps: - `@suaveplan/error` — `SuaveplanError`, `defineCodes` - `@suaveplan/types` — for option-bundle ergonomics (we re-use `Clock` etc. only if useful; not required by the core) Plus catalog deps `zod`, `tslib`, `@opentelemetry/api`. ## 2. Tier model Universal-tier **for pure data + graph operations**. The discovery surface (`discoverPackages`) touches `node:fs` and `node:path` and is therefore server-tier; it ships under the `./discover` sub-path export so universal consumers (browser, edge, R3F) can `import` only the pure pieces. The package itself stays universal at the root entry — only the `./discover` sub-path is server-flavoured. This matches the genesis pattern used by `db-sqlite` / `packer-browser` etc. ## 3. Data model (Zod-validated) ```ts type Tier = "python" | "r3f" | "react" | "browser" | "server" | "universal"; interface PackageRecord { name: string; // "@suaveplan/" short: string; // "" category: string; // top-level super-category folder plugin: string; // mid-level plugin folder dirName: string; // last path segment version: string; description: string; tier: Tier; workspaceDeps: string[]; // sorted, deduped @suaveplan/* names hasBin: boolean; hasBrowserField: boolean; } interface PluginSummary { category: string; plugin: string; packages: number; workspaceDeps: number; // total edges out } interface SuavePlanManifest { packages: PackageRecord[]; plugins: PluginSummary[]; categories: string[]; tierCounts: Record; } interface WorkspaceGraph { nodes: Set; forward: Map>; // pkg → deps reverse: Map>; // pkg → dependents } ``` All four shapes have Zod schemas exported alongside the types. ## 4. Tier inference Identical priority to `generate-manifest.ts`: 1. `python !== undefined` or `short.startsWith("python-")` → `python` 2. `peerDependencies` includes `@react-three/fiber` or `three` or `short.startsWith("r3f-")` → `r3f` 3. `peerDependencies` includes `react` or `short.startsWith("react-")` → `react` 4. `browser !== undefined` or `engines` set with neither `node` nor `bun` keys → `browser` 5. `bin !== undefined` or `dependencies`/`devDependencies` hit a known server-only dep (`bun:sqlite`, `pg`, `ioredis`, `better-sqlite3`, `node:*` namespace) → `server` 6. fallback → `universal` The hint set is exported as `SERVER_DEP_HINTS` so downstream code can extend it via a `tierHints` option to `discoverPackages`. ## 5. Discovery semantics `discoverPackages(options)` walks `/packages////package.json` exactly three levels deep. Anything that fails `JSON.parse` is reported via the `OVERVIEW.PARSE_FAILED` error path (caller decides: throw or skip). Anything without a `package.json` is silently ignored — that matches the script behaviour and reflects partial-migration reality on `genesis`. The walk is **synchronous by default** (cheap on a populated monorepo) with an optional async generator variant (`discoverPackagesIter`) that yields one `PackageRecord` at a time and honours `AbortSignal`. ## 6. Graph utilities - `buildGraph(records)` returns `WorkspaceGraph`. Only edges where the target is also in `nodes` are recorded (so a missing-yet-to-migrate dep is dropped rather than producing a phantom node — matches the script's behaviour). - `topologicalOrder(graph)` returns packages in dep-first order using Kahn's algorithm. On a cycle it throws `OVERVIEW.CYCLE_DETECTED` carrying the cycle nodes. - `detectCycles(graph)` returns `string[][]` of strongly-connected components larger than one node (Tarjan). - `transitiveDependencies(graph, pkg)` / `transitiveDependents(graph, pkg)` BFS in the respective direction. Returns a sorted `string[]`. - `leafPackages(graph)` — packages with no incoming workspace edge (no dependents). - `rootPackages(graph)` — packages with no outgoing workspace edge (no deps). ## 7. Roll-ups - `byPlugin(records)` returns a sorted `PluginSummary[]`. - `byCategory(records)` returns `Record` (package count). - `byTier(records)` returns `Record`. ## 8. Error codes ```ts defineCodes("OVERVIEW", [ "PARSE_FAILED", // package.json failed JSON.parse "INVALID_RECORD", // a record failed Zod validation "CYCLE_DETECTED", // topological sort hit a cycle "UNKNOWN_PACKAGE", // queried a package not in the graph "DISCOVERY_ABORTED", // AbortSignal fired mid-walk "INVALID_TIER", // schema rejected an out-of-range tier string ] as const); ``` Every code is thrown from a real call site (no dead entries — §2c). ## 9. OpenTelemetry Three counters and one histogram, namespaced `overview.*`: - `overview.packages_discovered_total` — counter incremented per `discoverPackages` call by `records.length`. - `overview.records_rejected_total{reason}` — counter (reason ∈ {`parse_failed`, `invalid_record`}). - `overview.graph_builds_total` — counter incremented per `buildGraph`. - `overview.discover_duration_ms` — histogram (boundaries 1, 5, 25, 100, 500, 2500) per successful `discoverPackages`. The OTel meter is obtained via `metrics.getMeter("@suaveplan/overview", "0.1.0")` so a no-op OTel SDK is the default — no instrumentation cost on the cold path. ## 10. Why a sub-path for discovery Tier purity: the package root MUST be safe to import from a universal context (browser, R3F, edge worker). Node-only `node:fs` / `node:path` imports live under `./discover` which `package.json` `exports` lists distinctly. This matches the pattern used by `db-sqlite`. ## 11. Test plan - **`errors/errors.test.ts`** — every code constructable; cause chain preserved; `instanceof OverviewError`; code uniqueness. - **`schemas/schemas.test.ts`** — every shape parses a valid example + rejects each malformed shape (one negative case per field). - **`tier/tier.test.ts`** — one case per inference rung incl. server-hint walk and `python` early-out; `tierHints` extension. - **`discover/discover.test.ts`** — discovers a temp-dir fixture (real files, real `package.json`s, no mocks — rule 2); async variant honours `AbortSignal`; invalid JSON triggers `PARSE_FAILED`; missing field triggers `INVALID_RECORD`. - **`graph/graph.test.ts`** — adjacency build; topo on linear + DAG; cycle case throws + reports; transitive closure correctness on a chain; leaves + roots on a star; missing-target edge is dropped. - **`rollups/rollups.test.ts`** — plugin / category / tier aggregates on a hand-crafted record array. - **`telemetry/telemetry.test.ts`** — installs an in-memory metric reader, observes counter + histogram values after `discoverPackages` + `buildGraph`. Property-based tests via `fast-check` for `topologicalOrder` (random DAG → all edges respected) and `transitiveDependencies` (closure is reflexive on self-loops and matches multi-step BFS). ## 12. File-size discipline The largest implementation file is expected to be `graph/graph.ts` (Tarjan + topo + closure) at ~180 lines. Everything else stays under 200. Test files are budget-aware; if `graph.test.ts` pushes 500, split into `graph.spec.basics.test.ts` + `graph.spec.cycles.test.ts`.