# Capability Spec Delta: overview (overview) **Package**: `@suaveplan/overview` **Status**: SHIPPED (2026-05-25) > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: Stable error registry The package SHALL expose `OVERVIEW_CODES` registered via `defineCodes("OVERVIEW", [...])` and a `OverviewError` class extending `@suaveplan/error`'s `SuaveplanError`. The registry SHALL contain exactly six entries: `PARSE_FAILED`, `INVALID_RECORD`, `CYCLE_DETECTED`, `UNKNOWN_PACKAGE`, `DISCOVERY_ABORTED`, `INVALID_TIER`. Each code MUST be reachable from a real call site exercised by tests; no dead entries. #### Scenario: error code uniqueness - **WHEN** the test enumerates `Object.values(OVERVIEW_CODES)` - **THEN** every value is a unique string in the form `"OVERVIEW."` and `new OverviewError(code, "msg")` produces an instance whose `code` field equals the input #### Scenario: every code is thrown somewhere - **WHEN** the test asserts each code appears as a `throw new OverviewError(...)` argument in at least one `src/**/*.ts` file - **THEN** the assertion passes — `PARSE_FAILED` from `discover`, `INVALID_RECORD` from `discover`/`schemas`, `CYCLE_DETECTED` from `graph`, `UNKNOWN_PACKAGE` from `graph`, `DISCOVERY_ABORTED` from `discover`, `INVALID_TIER` from `schemas` **Verified by:** `src/errors/errors.test.ts::"every OVERVIEW code is unique"` AND `src/errors/errors.test.ts::"every code has a real call site in src/"` --- ### Requirement: Zod-validated data model The package SHALL export Zod schemas `TierSchema`, `PackageRecordSchema`, `PluginSummarySchema`, and `SuavePlanManifestSchema`, each paired with an inferred TypeScript type. Schemas SHALL reject malformed inputs at the package boundary and `INVALID_RECORD` SHALL be thrown when a discovered `package.json` fails to parse against `PackageRecordSchema`. #### Scenario: valid PackageRecord parses - **WHEN** `PackageRecordSchema.safeParse({ name: "@suaveplan/x", short: "x", category: "foundation", plugin: "core", dirName: "x", version: "0.1.0", description: "d", tier: "universal", workspaceDeps: [], hasBin: false, hasBrowserField: false })` runs - **THEN** `success === true` #### Scenario: malformed tier rejected - **WHEN** `TierSchema.safeParse("rust")` runs - **THEN** `success === false` **Verified by:** `src/schemas/schemas.test.ts::"TierSchema accepts every tier"` AND `src/schemas/schemas.test.ts::"PackageRecordSchema rejects bad input per field"` --- ### Requirement: Tier inference The package SHALL provide `inferTier(short, pkgJson, hints?)` that returns a `Tier` using the priority order: python → r3f → react → browser → server → universal. The hint set for the server-tier walk SHALL be exported as `SERVER_DEP_HINTS` and SHALL be extensible per call via an optional `hints: Set` parameter. #### Scenario: python wins - **WHEN** `inferTier("python-foo", { python: {} })` runs - **THEN** the return value is `"python"` #### Scenario: server hint matches a Node-only dep - **WHEN** `inferTier("x", { dependencies: { "node:fs": "*" } })` runs - **THEN** the return value is `"server"` #### Scenario: universal fallback - **WHEN** `inferTier("x", {})` runs - **THEN** the return value is `"universal"` **Verified by:** `src/tier/tier.test.ts::"inferTier prioritises python > r3f > react > browser > server > universal"` AND `src/tier/tier.test.ts::"inferTier honours extension hints"` --- ### Requirement: Filesystem discovery (server sub-path) The package SHALL expose `discoverPackages(options)` from the `./discover` sub-path export. The function SHALL walk `/packages////package.json` exactly three folder levels deep, parse each file as JSON, validate against `PackageRecordSchema`, and return `SuavePlanManifest`. Invalid JSON SHALL produce `OVERVIEW.PARSE_FAILED`; validation failure SHALL produce `OVERVIEW.INVALID_RECORD` (default behaviour: throw; opt-in `onError: "skip"` collects errors into a returned array). #### Scenario: temp-dir fixture discovers two packages - **WHEN** the test creates a temp `packages/foundation/core/{a,b}/package.json` fixture and calls `discoverPackages({ root: tmp })` - **THEN** the returned manifest contains two records sorted by name #### Scenario: invalid JSON throws PARSE_FAILED - **WHEN** a fixture `package.json` is unparseable and `onError: "throw"` - **THEN** an `OverviewError` with `code === "OVERVIEW.PARSE_FAILED"` is thrown **Verified by:** `src/discover/discover.test.ts::"discovers a 3-level fixture"` AND `src/discover/discover.test.ts::"throws PARSE_FAILED on invalid JSON"` AND `src/discover/discover.test.ts::"throws INVALID_RECORD when fields missing"` --- ### Requirement: AbortSignal-aware async discovery The package SHALL export `discoverPackagesIter(options)` returning an `AsyncGenerator` that honours `options.signal`. On abort the generator SHALL throw `OVERVIEW.DISCOVERY_ABORTED`. #### Scenario: abort mid-walk - **WHEN** a controller is aborted between the first and second yielded record - **THEN** the second `.next()` rejects with `OverviewError` whose code is `OVERVIEW.DISCOVERY_ABORTED` **Verified by:** `src/discover/discover.test.ts::"discoverPackagesIter honours AbortSignal"` --- ### Requirement: Workspace graph utilities The package SHALL export `buildGraph(records)`, `topologicalOrder(graph)`, `detectCycles(graph)`, `transitiveDependencies(graph, pkg)`, `transitiveDependents(graph, pkg)`, `leafPackages(graph)`, and `rootPackages(graph)`. Graph edges SHALL only be recorded when both endpoints are nodes (orphan dep names are dropped). Topological order SHALL throw `OVERVIEW.CYCLE_DETECTED` carrying the offending cycle. Queries against unknown packages SHALL throw `OVERVIEW.UNKNOWN_PACKAGE`. #### Scenario: topo on a linear chain - **WHEN** `topologicalOrder` runs over `a → b → c` - **THEN** the result is `["c", "b", "a"]` (deps first) #### Scenario: cycle detected - **WHEN** `topologicalOrder` runs over `a → b → a` - **THEN** an `OverviewError` with `code === "OVERVIEW.CYCLE_DETECTED"` is thrown whose `context.cycle` contains both `a` and `b` #### Scenario: transitive closure - **WHEN** `transitiveDependencies(graph, "a")` runs over `a → b → c, b → d` - **THEN** the result is `["b", "c", "d"]` sorted #### Scenario: unknown package query - **WHEN** `transitiveDependencies(graph, "missing")` runs - **THEN** an `OverviewError` with `code === "OVERVIEW.UNKNOWN_PACKAGE"` is thrown **Verified by:** `src/graph/graph.test.ts::"topologicalOrder respects dependency direction"` AND `src/graph/graph.test.ts::"topologicalOrder throws CYCLE_DETECTED on a cycle"` AND `src/graph/graph.test.ts::"transitiveDependencies BFS closure"` AND `src/graph/graph.test.ts::"transitiveDependencies throws UNKNOWN_PACKAGE"` AND `src/graph/graph.test.ts::"leaves and roots on a star"` --- ### Requirement: Roll-up aggregates The package SHALL export `byPlugin(records)`, `byCategory(records)`, and `byTier(records)`. `byPlugin` SHALL return `PluginSummary[]` sorted by `"/"`. `byCategory` SHALL return `Record` of package counts. `byTier` SHALL return `Record` for all six tiers (empty arrays when absent). #### Scenario: byPlugin aggregates counts and dep totals - **WHEN** `byPlugin` runs over three records across two `(category, plugin)` pairs with workspace-dep counts `[2, 3, 1]` and the two pairs have `[2, 1]` records respectively - **THEN** the summary rows carry the expected `packages` and `workspaceDeps` totals #### Scenario: byTier covers all tiers - **WHEN** `byTier` runs over an empty list - **THEN** the returned record has every tier key with an empty array value **Verified by:** `src/rollups/rollups.test.ts::"byPlugin aggregates packages + workspace deps"` AND `src/rollups/rollups.test.ts::"byCategory counts packages"` AND `src/rollups/rollups.test.ts::"byTier covers every Tier key"` --- ### Requirement: OpenTelemetry instrumentation The package SHALL emit three counters and one histogram via `metrics.getMeter("@suaveplan/overview", "0.1.0")`: `overview.packages_discovered_total`, `overview.records_rejected_total{reason}`, `overview.graph_builds_total`, and `overview.discover_duration_ms`. Counters and histograms SHALL increment / record during the normal API paths. #### Scenario: counters move on discovery - **WHEN** an in-memory metric reader is installed and `discoverPackages` runs against a fixture - **THEN** `overview.packages_discovered_total` increases by `records.length` and `overview.discover_duration_ms` records one observation **Verified by:** `src/telemetry/telemetry.test.ts::"discoverPackages emits counter + histogram"` AND `src/telemetry/telemetry.test.ts::"buildGraph emits graph_builds_total"` --- ### Requirement: Tier purity at the root entry The package root entry SHALL NOT import `node:*` modules. Node-only behaviour SHALL live exclusively under the `./discover` sub-path export so universal-tier consumers (browser, R3F, edge) can import the root safely. #### Scenario: root barrel has no node: imports - **WHEN** the test parses `src/index.ts` and every transitive import not under `src/discover/` - **THEN** none of the resolved files contain `from "node:` or `require("node:` literal strings **Verified by:** `src/discover/discover.test.ts::"root barrel imports no node:* modules"` --- ### Requirement: Co-located docs and 100% coverage Every non-barrel, non-test, non-types `.ts` file under `src/` SHALL have a sibling `.md` documentation file matching the required-section template. The package SHALL meet 100% statement, branch, function, and line coverage under Istanbul. #### Scenario: docs:check passes - **WHEN** `bun run docs:check --filter=@suaveplan/overview` runs - **THEN** the exit code is 0 #### Scenario: coverage gate - **WHEN** `bunx turbo test:coverage --filter=@suaveplan/overview` runs - **THEN** the Istanbul report shows 100/100/100/100 **Verified by:** `bunx turbo test:coverage --filter=@suaveplan/overview` AND `bun run docs:check --filter=@suaveplan/overview`