10 KiB
Capability Spec Delta: overview (overview)
Package: @suaveplan/overview
Status: PROPOSED
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.<NAME>"andnew OverviewError(code, "msg")produces an instance whosecodefield 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 onesrc/**/*.tsfile - THEN the assertion passes —
PARSE_FAILEDfromdiscover,INVALID_RECORDfromdiscover/schemas,CYCLE_DETECTEDfromgraph,UNKNOWN_PACKAGEfromgraph,DISCOVERY_ABORTEDfromdiscover,INVALID_TIERfromschemas
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<string> 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
<root>/packages/<category>/<plugin>/<pkg>/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.jsonfixture and callsdiscoverPackages({ root: tmp }) - THEN the returned manifest contains two records sorted by name
Scenario: invalid JSON throws PARSE_FAILED
- WHEN a fixture
package.jsonis unparseable andonError: "throw" - THEN an
OverviewErrorwithcode === "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<PackageRecord> 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 withOverviewErrorwhose code isOVERVIEW.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
topologicalOrderruns overa → b → c - THEN the result is
["c", "b", "a"](deps first)
Scenario: cycle detected
- WHEN
topologicalOrderruns overa → b → a - THEN an
OverviewErrorwithcode === "OVERVIEW.CYCLE_DETECTED"is thrown whosecontext.cyclecontains bothaandb
Scenario: transitive closure
- WHEN
transitiveDependencies(graph, "a")runs overa → b → c, b → d - THEN the result is
["b", "c", "d"]sorted
Scenario: unknown package query
- WHEN
transitiveDependencies(graph, "missing")runs - THEN an
OverviewErrorwithcode === "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
"<category>/<plugin>". byCategory SHALL return Record<string, number>
of package counts. byTier SHALL return Record<Tier, PackageRecord[]> for
all six tiers (empty arrays when absent).
Scenario: byPlugin aggregates counts and dep totals
- WHEN
byPluginruns 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
packagesandworkspaceDepstotals
Scenario: byTier covers all tiers
- WHEN
byTierruns 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
discoverPackagesruns against a fixture - THEN
overview.packages_discovered_totalincreases byrecords.lengthandoverview.discover_duration_msrecords 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.tsand every transitive import not undersrc/discover/ - THEN none of the resolved files contain
from "node:orrequire("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/overviewruns - THEN the exit code is 0
Scenario: coverage gate
- WHEN
bunx turbo test:coverage --filter=@suaveplan/overviewruns - 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