Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
# @suaveplan/overview - Machine-readable Suaveplan project manifest: typed PackageRecord / SuavePlanManifest / WorkspaceGraph, Zod schemas, tier inference, filesystem discovery (server sub-path), graph utilities (topo, cycle detection, transitive closure, leaves, roots), plugin/category/tier roll-ups, six OVERVIEW.* error codes, three OTel counters + one histogram.
> Machine-readable project manifest + graph utilities for the Suaveplan
> project.
`@suaveplan/overview` turns a populated
`packages/<category>/<plugin>/<pkg>/package.json` tree into a typed,
Zod-validated `SuavePlanManifest` and gives you graph utilities — forward /
reverse adjacency, topological order, cycle detection, transitive closure,
leaves, roots, plugin / category / tier roll-ups — for everything downstream.
It is the **library form** of the data model produced by
`scripts/folder-reorg/generate-manifest.ts` (whose own header comment names this
package as the eventual replacement).
The pure-data surface lives at the root entry. Filesystem-walking code (which
needs `node:fs` / `node:path`) lives behind the `./discover` sub-path so the
root entry stays universal.
<!-- begin:auto-docs -->
## Documentation tree
- [discover](./src/discover/discover.md) — Implements the filesystem walker that produces a validated `SuavePlanManifest` from a populated `packages/<category>/<pl…
- [discover](./src/discover/discover.md) — Implements the filesystem walker that produces a validated `SuavePlanManifest` from a populated `packages/<category>/<pl…
- [errors](./src/errors/errors.md) — Defines the stable error-code registry and the concrete `OverviewError` subclass of `SuaveplanError`. Every public entr…
- [errors](./src/errors/errors.md) — Defines the stable error-code registry and the concrete `OverviewError` subclass of `SuaveplanError`. Every public entr…
- [graph](./src/graph/graph.md) — Implements the graph utilities that operate on a `WorkspaceGraph` built from a list of `PackageRecord`s. Covers the ana…
- [graph](./src/graph/graph.md) — Implements the graph utilities that operate on a `WorkspaceGraph` built from a list of `PackageRecord`s. Covers the ana…
- [rollups](./src/rollups/rollups.md) — Provides the three aggregate views over a `PackageRecord[]` that the manifest needs: plugin roll-ups (one row per `(cat…
- [rollups](./src/rollups/rollups.md) — Provides the three aggregate views over a `PackageRecord[]` that the manifest needs: plugin roll-ups (one row per `(cat…
- [schemas](./src/schemas/schemas.md) — Provides Zod schemas (and one helper, `parseTier`) for every public shape that `@suaveplan/overview` produces or accept…
- [schemas](./src/schemas/schemas.md) — Provides Zod schemas (and one helper, `parseTier`) for every public shape that `@suaveplan/overview` produces or accept…
- [telemetry](./src/telemetry/telemetry.md) — Owns the OpenTelemetry meter for `@suaveplan/overview` and exposes the three counters and one histogram that the spec r…
- [telemetry](./src/telemetry/telemetry.md) — Owns the OpenTelemetry meter for `@suaveplan/overview` and exposes the three counters and one histogram that the spec r…
- [tier](./src/tier/tier.md) — Computes the deterministic `Tier` label for a package given its shortname and the parsed `package.json` shape. The infe…
- [tier](./src/tier/tier.md) — Computes the deterministic `Tier` label for a package given its shortname and the parsed `package.json` shape. The infe…
- [types](./src/types/types.md) — Declares the type-level vocabulary of `@suaveplan/overview`: the closed `Tier` union, the `PackageRecord` row, the `Plu…
- [types](./src/types/types.md) — Declares the type-level vocabulary of `@suaveplan/overview`: the closed `Tier` union, the `PackageRecord` row, the `Plu…
<!-- end:auto-docs -->
## Install
```sh
bun add @suaveplan/overview
```
## Surfaces
| Sub-path | Use |
|---|---|
| `@suaveplan/overview` | Types, schemas, errors, graph utilities, roll-ups, telemetry — all universal |
| `@suaveplan/overview/discover` | `discoverPackages` + `discoverPackagesIter` — server-tier (touches `node:fs`) |
| `@suaveplan/overview/errors` | `OVERVIEW_CODES`, `OverviewError` |
| `@suaveplan/overview/schemas` | Zod schemas + `parseTier` |
| `@suaveplan/overview/tier` | `inferTier`, `SERVER_DEP_HINTS` |
| `@suaveplan/overview/graph` | Graph utilities |
| `@suaveplan/overview/rollups` | `byPlugin`, `byCategory`, `byTier` |
| `@suaveplan/overview/telemetry` | OTel instruments |
## Example
```ts
import { discoverPackages } from "@suaveplan/overview/discover";
import {
buildGraph,
topologicalOrder,
byTier,
} from "@suaveplan/overview";
const manifest = discoverPackages({ root: process.cwd() });
const graph = buildGraph(manifest.packages);
const order = topologicalOrder(graph);
const buckets = byTier(manifest.packages);
console.log(`Packages: ${manifest.packages.length}`);
console.log(`Universal: ${buckets.universal.length}`);
console.log(`Build order (deps first): ${order.slice(0, 5).join(", ")} …`);
```
## Error codes
| Code | Thrown by |
|---|---|
| `OVERVIEW.PARSE_FAILED` | `discoverPackages` — `package.json` failed `JSON.parse` |
| `OVERVIEW.INVALID_RECORD` | `discoverPackages` — row failed `PackageRecordSchema` |
| `OVERVIEW.CYCLE_DETECTED` | `topologicalOrder` |
| `OVERVIEW.UNKNOWN_PACKAGE` | `transitiveDependencies` / `transitiveDependents` |
| `OVERVIEW.DISCOVERY_ABORTED` | `discoverPackagesIter` |
| `OVERVIEW.INVALID_TIER` | `parseTier` |
## Telemetry
Three counters and one histogram, scoped to meter
`@suaveplan/overview@0.1.0`:
- `overview.packages_discovered_total`
- `overview.records_rejected_total{reason}`
- `overview.graph_builds_total`
- `overview.discover_duration_ms`
Plug `@opentelemetry/sdk-metrics` in your runtime to harvest them.
## Tier
Universal at the root entry; server at `./discover`.

View File

@@ -0,0 +1,85 @@
# `discover/discover.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements the filesystem walker that produces a validated `SuavePlanManifest`
from a populated `packages/<category>/<plugin>/<pkg>/package.json` tree. Both
the synchronous `discoverPackages` and the async `discoverPackagesIter` live
here. The module is the only server-tier surface in `@suaveplan/overview`; it
ships under the `./discover` sub-path export so universal consumers can avoid
the `node:fs` / `node:path` imports entirely.
Discovery is straightforward: walk three directory levels deep, read each
`package.json`, infer its tier, collect workspace deps, validate against
`PackageRecordSchema`, then aggregate the resulting rows into a manifest.
## Features
- Synchronous `discoverPackages` returning a fully-validated manifest
- Async `discoverPackagesIter` honouring `AbortSignal`
- `onError: "throw" | "skip"` for graceful handling of malformed
`package.json` files
- Optional `tierHints` extending `SERVER_DEP_HINTS`
- Emits `overview.packages_discovered_total`,
`overview.records_rejected_total{reason}`, and
`overview.discover_duration_ms`
- Built-in roll-up so the manifest carries `plugins`, `categories`, and
`tierCounts` ready for consumption
## Usage: Basic
```ts
import { discoverPackages } from "@suaveplan/overview/discover";
const manifest = discoverPackages({ root: process.cwd() });
console.log(`Found ${manifest.packages.length} packages.`);
```
## Usage: Advanced
```ts
import { discoverPackagesIter } from "@suaveplan/overview/discover";
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
try {
for await (const r of discoverPackagesIter({
root: "/repo",
signal: controller.signal,
onError: "skip",
tierHints: new Set(["custom-native-binding"]),
})) {
process.stdout.write(`${r.name}\n`);
}
} catch (err) {
// OverviewError with code OVERVIEW.DISCOVERY_ABORTED when the signal fired.
}
```
`onError: "skip"` is the right mode for tooling that should never abort on a
single bad package.json (e.g. a long-running watcher); `"throw"` is right for
one-shot CLIs that prefer a hard fail on corruption.
## API Reference
- `discoverPackages(options: DiscoverOptions): SuavePlanManifest`
- `discoverPackagesIter(options: AsyncDiscoverOptions): AsyncGenerator<PackageRecord>`
- `interface DiscoverOptions { root, onError?, tierHints? }`
- `interface AsyncDiscoverOptions extends DiscoverOptions { signal? }`
- `type DiscoveryErrorMode = "throw" | "skip"`
## Implementation Notes
The walker enforces exactly three directory levels (category → plugin →
package). Anything shallower or deeper is silently ignored — partial-migration
trees are common and a permissive walker keeps the tooling robust.
`existsSync` + `statSync` are used to filter non-directories and packages
without a `package.json`; both are cheap on a populated tree because the OS
will have cached the directory listings. The histogram observation uses
`performance.now()` when available (it always is in Node 20+/Bun) and falls
back to `Date.now()` otherwise.

View File

@@ -0,0 +1,86 @@
# `errors/errors.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the stable error-code registry and the concrete `OverviewError`
subclass of `SuaveplanError`. Every public entry point in `@suaveplan/overview`
that throws does so through this class, so callers can rely on a single
`instanceof OverviewError` check and a literal-typed `code` field for
discrimination.
The registry has six entries — one per failure mode that the package can
detect — and the §2c "never delete spec elements to satisfy a gate" rule means
each entry is exercised by a real call site somewhere in `src/`, asserted by
the static test `every code has a real call site in src/`.
## Features
- Six stable codes registered through `defineCodes("OVERVIEW", [...])`
- Frozen, fully-inferred code record — `OVERVIEW_CODES.PARSE_FAILED` is the
literal `"OVERVIEW.PARSE_FAILED"`
- `OverviewCode` closed union for exhaustive switch statements
- `OverviewError` extends `SuaveplanError<OverviewCode>` so `instanceof`
narrows `error.code`
- Preserves cause chain, context, and trace-id propagation via
`SuaveplanErrorOptions`
## Usage: Basic
```ts
import { OverviewError, OVERVIEW_CODES } from "@suaveplan/overview";
throw new OverviewError(
OVERVIEW_CODES.UNKNOWN_PACKAGE,
"overview.unknown_package",
{ context: { pkg: "@suaveplan/missing" } },
);
```
## Usage: Advanced
```ts
import { OverviewError } from "@suaveplan/overview";
try {
// ...
} catch (err) {
if (err instanceof OverviewError) {
switch (err.code) {
case "OVERVIEW.CYCLE_DETECTED":
// type-narrowed; safe to read err.context?.cycle
break;
case "OVERVIEW.PARSE_FAILED":
break;
default:
break;
}
}
throw err;
}
```
The exhaustive `switch` benefits from `OverviewCode` being a closed union —
adding a new code without handling it in a downstream `switch` is a TypeScript
error.
## API Reference
- `OVERVIEW_CODES` — `Readonly<{PARSE_FAILED, INVALID_RECORD, CYCLE_DETECTED,
UNKNOWN_PACKAGE, DISCOVERY_ABORTED, INVALID_TIER}>` with literal `OVERVIEW.<X>`
values
- `type OverviewCode` — closed union of every value in `OVERVIEW_CODES`
- `class OverviewError extends SuaveplanError<OverviewCode>` — constructor
`(code, messageKey, options?)`
## Implementation Notes
The `defineCodes` helper from `@suaveplan/error` freezes its return value so the
registry cannot be mutated at runtime. `OverviewError` deliberately re-exposes
the `protected` constructor of `SuaveplanError` as `public` (the language
requires each subclass to do this explicitly) so callers can `new` an instance
directly without a factory. The class is final by convention — sub-classing it
would dilute `instanceof` narrowing.

View File

@@ -0,0 +1,90 @@
# `graph/graph.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements the graph utilities that operate on a `WorkspaceGraph` built from a
list of `PackageRecord`s. Covers the analyses every project tool eventually
needs: build a forward / reverse adjacency map, compute a topological
dependency-first order, detect cycles, gather transitive deps or dependents,
and identify graph roots (no deps) and leaves (no dependents).
Edges where the target is not also a node are silently dropped at build time.
This is the same posture as `scripts/folder-reorg/generate-manifest.ts` —
necessary because Suaveplan migrates in waves and partial-manifest
snapshots are routine.
## Features
- `buildGraph` — populates forward and reverse adjacency in one pass; emits
`overview.graph_builds_total`
- `topologicalOrder` — Kahn's algorithm with stable lexicographic tie-breaking;
throws `OVERVIEW.CYCLE_DETECTED` on a cycle (error carries `context.cycle`)
- `detectCycles` — Tarjan's SCC, returns every cycle as a sorted `string[]`
- `transitiveDependencies` / `transitiveDependents` — BFS over the appropriate
direction; throws `OVERVIEW.UNKNOWN_PACKAGE` for missing inputs
- `leafPackages` — no dependents (no incoming edges)
- `rootPackages` — no deps (no outgoing edges)
## Usage: Basic
```ts
import { buildGraph, topologicalOrder } from "@suaveplan/overview";
const graph = buildGraph(records);
const order = topologicalOrder(graph);
// `order[0]` has no workspace deps; the last entry depends on everything.
```
## Usage: Advanced
```ts
import {
buildGraph,
detectCycles,
transitiveDependencies,
transitiveDependents,
leafPackages,
rootPackages,
} from "@suaveplan/overview";
const graph = buildGraph(records);
const cycles = detectCycles(graph);
if (cycles.length > 0) {
console.error("Cycles detected:", cycles);
}
const blastRadius = transitiveDependents(graph, "@suaveplan/error");
const supplyChain = transitiveDependencies(graph, "@suaveplan/api");
const leaves = leafPackages(graph);
const roots = rootPackages(graph);
```
`topologicalOrder` is deterministic — two callers with the same graph see the
same order — so it can drive reproducible build plans and CI fan-outs.
## API Reference
- `buildGraph(records): WorkspaceGraph`
- `topologicalOrder(graph): string[]` — throws `OVERVIEW.CYCLE_DETECTED`
- `detectCycles(graph): string[][]`
- `transitiveDependencies(graph, pkg): string[]` — throws
`OVERVIEW.UNKNOWN_PACKAGE`
- `transitiveDependents(graph, pkg): string[]` — throws
`OVERVIEW.UNKNOWN_PACKAGE`
- `leafPackages(graph): string[]`
- `rootPackages(graph): string[]`
## Implementation Notes
`topologicalOrder` initialises a remaining-out-degree counter per node and
drains nodes with zero out-degree first; the queue is kept sorted on insertion
so the output is stable across runs. `detectCycles` uses a recursive
Tarjan implementation — the workspace size is bounded (hundreds of packages),
so the stack-depth budget is comfortable. BFS in `transitiveDependencies` /
`transitiveDependents` uses an array-based queue with `Array.prototype.shift`
— acceptable at workspace scale; if the package ever has to handle tens of
thousands of nodes the queue can be swapped for a head/tail index pair.

View File

@@ -0,0 +1,62 @@
# `rollups/rollups.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides the three aggregate views over a `PackageRecord[]` that the manifest
needs: plugin roll-ups (one row per `(category, plugin)`), category counts
(simple histogram), and tier bucketing (every tier key populated, even when
empty). These are pure functions with no side effects, so callers can run them
repeatedly without consulting the disk.
The roll-ups mirror the data that the human-readable `MANIFEST.md` table
produces, but typed: downstream tooling can consume them directly instead of
re-parsing the Markdown.
## Features
- `byPlugin` — sorted `PluginSummary[]`
- `byCategory` — `Record<string, number>` of per-category package counts
- `byTier` — `Record<Tier, PackageRecord[]>` with every tier key present
## Usage: Basic
```ts
import { byPlugin, byCategory, byTier } from "@suaveplan/overview";
const plugins = byPlugin(records);
const counts = byCategory(records);
const tiered = byTier(records);
```
## Usage: Advanced
```ts
import { byTier, TIERS } from "@suaveplan/overview";
const buckets = byTier(records);
for (const tier of TIERS) {
console.log(`${tier}: ${buckets[tier].length}`);
}
```
The `byTier` output always has every tier key, so downstream code never has to
guard against missing keys — a tier with zero packages simply maps to `[]`.
## API Reference
- `byPlugin(records): PluginSummary[]` — sorted by `"<category>/<plugin>"`
- `byCategory(records): Record<string, number>`
- `byTier(records): Record<Tier, PackageRecord[]>` — covers all six tier keys
## Implementation Notes
`byPlugin` accumulates into a `Map` keyed by `"<category>/<plugin>"`, then
sorts by key on emit. `byTier` initialises the output via
`Object.fromEntries(TIERS.map(...))` so the result has a stable insertion
order matching `TIERS`. None of the helpers emit telemetry on their own — they
are pure transformations; instrumentation is the caller's responsibility if
needed.

View File

@@ -0,0 +1,68 @@
# `schemas/schemas.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides Zod schemas (and one helper, `parseTier`) for every public shape that
`@suaveplan/overview` produces or accepts. The schemas are the authoritative
runtime gate: any `PackageRecord` that does not parse against
`PackageRecordSchema` is rejected by `discoverPackages` with
`OVERVIEW.INVALID_RECORD`, and `parseTier` converts an unknown string into a
typed `Tier` or throws `OVERVIEW.INVALID_TIER`.
The schemas are kept strict (`.strict()`) so extra fields are rejected — a
downstream consumer that wants to attach extension data must clone the record
into its own shape rather than smuggle properties through the manifest.
## Features
- `TierSchema` — the closed `Tier` enum
- `PackageRecordSchema` — every required field, validated with `.strict()`
- `PluginSummarySchema` — for plugin roll-ups
- `SuavePlanManifestSchema` — the full snapshot, including all six tier-count
keys as required numbers
- `parseTier` — convenience wrapper that throws an `OverviewError` instead of
returning a Zod `safeParse` failure
## Usage: Basic
```ts
import { PackageRecordSchema } from "@suaveplan/overview";
const r = PackageRecordSchema.parse(rawRow);
```
## Usage: Advanced
```ts
import { SuavePlanManifestSchema, parseTier } from "@suaveplan/overview";
const manifest = SuavePlanManifestSchema.parse(jsonFromDisk);
const userInput: unknown = process.env.OVERVIEW_TIER;
const tier = parseTier(userInput); // throws OVERVIEW.INVALID_TIER on bad input
```
The strict schemas allow consumers to safely round-trip a manifest through JSON
without losing fields or accidentally importing arbitrary extensions: a malformed
JSON payload simply fails the parse.
## API Reference
- `TierSchema: z.ZodType<Tier>`
- `PackageRecordSchema: z.ZodObject<...>` (strict, eleven fields)
- `PluginSummarySchema: z.ZodObject<...>` (strict, four fields)
- `SuavePlanManifestSchema: z.ZodObject<...>` (strict, four fields)
- `parseTier(input: unknown): Tier` — throws `OverviewError`
(`OVERVIEW.INVALID_TIER`) on failure
## Implementation Notes
The schemas use `z.enum(TIERS as readonly [Tier, ...Tier[]])` so the source of
truth is the `TIERS` tuple in `types/types.ts`; adding a new tier requires
updating that tuple (and the `tierCounts` shape) — both compile-time changes
that the test suite catches. `parseTier` deliberately surfaces a package-shaped
error rather than re-throwing the Zod `ZodError` so call sites only ever deal
with one error class.

View File

@@ -0,0 +1,73 @@
# `telemetry/telemetry.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns the OpenTelemetry meter for `@suaveplan/overview` and exposes the
three counters and one histogram that the spec requires. The meter is
constructed lazily at module import time via
`metrics.getMeter("@suaveplan/overview", "0.1.0")` so a no-op OTel SDK is the
default — there is no instrumentation cost on the cold path when no SDK is
installed.
Every metric is exported as a typed `Counter` or `Histogram` so other modules
can `.add` / `.record` against them directly without going through a wrapper.
## Features
- Single meter scoped to the package name + version
- `overview.packages_discovered_total` — counter
- `overview.records_rejected_total{reason}` — counter (reason is one of
`parse_failed` | `invalid_record`)
- `overview.graph_builds_total` — counter
- `overview.discover_duration_ms` — histogram with millisecond unit
- `RejectionReason` typed alias for the `reason` attribute
## Usage: Basic
```ts
import { packagesDiscoveredTotal } from "@suaveplan/overview/telemetry";
packagesDiscoveredTotal.add(7);
```
## Usage: Advanced
```ts
import {
discoverDurationMs,
recordsRejectedTotal,
type RejectionReason,
} from "@suaveplan/overview/telemetry";
const start = performance.now();
const result = await runDiscovery();
discoverDurationMs.record(performance.now() - start);
if (!result.ok) {
const reason: RejectionReason = result.reason;
recordsRejectedTotal.add(1, { reason });
}
```
Plug `@opentelemetry/sdk-metrics` in your runtime to harvest these metrics; no
adapter glue is needed inside `@suaveplan/overview`.
## API Reference
- `packagesDiscoveredTotal: Counter`
- `recordsRejectedTotal: Counter` (attribute: `reason: "parse_failed" |
"invalid_record"`)
- `graphBuildsTotal: Counter`
- `discoverDurationMs: Histogram` (unit: `ms`)
- `type RejectionReason = "parse_failed" | "invalid_record"`
## Implementation Notes
The meter is obtained once at module load and cached as a `const`. With the
no-op SDK installed (the default in test environments without a configured
OTel SDK), every `.add` and `.record` call is a fast no-op — there is no need
to feature-flag instrumentation. Tests use the SDK's `InMemoryMetricReader`
to assert that the counters and histogram move when discovery runs.

View File

@@ -0,0 +1,73 @@
# `tier/tier.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Computes the deterministic `Tier` label for a package given its shortname and
the parsed `package.json` shape. The inference order mirrors
`scripts/folder-reorg/generate-manifest.ts` exactly, so consumers can rely on
the library and the script producing identical tier assignments for the same
input.
The function is pure and synchronous. It takes no side-effects, so it is safe
to call from any tier — including the universal root barrel — even though
some of the dep hints it inspects are Node-only.
## Features
- Six-rung priority cascade — python → r3f → react → browser → server →
universal
- Exported `SERVER_DEP_HINTS` set for downstream introspection
- Optional `extraHints` parameter merges user-supplied server-tier dep names
with the built-in set
- Pure / synchronous — zero I/O, zero allocations on the universal fallback
path beyond the merged-hint set
## Usage: Basic
```ts
import { inferTier } from "@suaveplan/overview";
const tier = inferTier("foo", {
dependencies: { "node:fs": "*" },
});
// tier === "server"
```
## Usage: Advanced
```ts
import { inferTier, SERVER_DEP_HINTS } from "@suaveplan/overview";
const extra = new Set(["custom-native-binding"]);
const tier = inferTier(
"thing",
{ dependencies: { "custom-native-binding": "*" } },
extra,
);
// SERVER_DEP_HINTS exposes the built-in set for tooling that wants to
// surface the rule set in a UI without re-declaring it.
console.log([...SERVER_DEP_HINTS]);
```
The optional third argument is the path for tooling that wants to extend the
hint set (e.g. a project-local native-binding registry) without forking the
library. The base set is read-only and never mutated.
## API Reference
- `inferTier(short: string, pkg: RawPackageJson, extraHints?: ReadonlySet<string>): Tier`
- `const SERVER_DEP_HINTS: ReadonlySet<string>` — eighteen built-in entries
spanning `node:*` modules and known native bindings
## Implementation Notes
The function performs at most one shallow `Object.keys` pass over each of
`peerDependencies`, `engines`, `dependencies`, and `devDependencies` — O(1)
work per `package.json` in practice. The `mergeHints` helper allocates a new
`Set` only when `extraHints` carries entries; otherwise it returns the base
set unchanged so the common path is allocation-free.

View File

@@ -0,0 +1,84 @@
# `types/types.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the type-level vocabulary of `@suaveplan/overview`: the closed `Tier`
union, the `PackageRecord` row, the `PluginSummary` aggregate, the
`SuavePlanManifest` snapshot, the `WorkspaceGraph` adjacency structure, and the
`RawPackageJson` narrowing of the on-disk `package.json` fields that influence
tier inference.
Every other module in the package consumes these shapes. There is no runtime
code in this file — only `export type` / `export interface` declarations and
the `TIERS` constant array used for stable iteration order across roll-ups and
tests.
## Features
- Closed `Tier` union with documented priority order
- `TIERS` ordered tuple for deterministic iteration in roll-ups and tests
- `readonly`-deep `PackageRecord` shape — no accidental mutation downstream
- `PluginSummary` matches the columns produced by
`scripts/folder-reorg/generate-manifest.ts`
- `SuavePlanManifest` carries every aggregate that the Markdown manifest emits
- `WorkspaceGraph` exposes both forward + reverse adjacency for O(1) traversal
- `RawPackageJson` narrows `package.json` to only the fields the package reads
## Usage: Basic
```ts
import type { PackageRecord, Tier } from "@suaveplan/overview";
const record: PackageRecord = {
name: "@suaveplan/x",
short: "x",
category: "foundation",
plugin: "core",
dirName: "x",
version: "0.1.0",
description: "",
tier: "universal",
workspaceDeps: [],
hasBin: false,
hasBrowserField: false,
};
```
## Usage: Advanced
```ts
import { TIERS, type SuavePlanManifest, type Tier } from "@suaveplan/overview";
function totalCount(manifest: SuavePlanManifest): number {
return TIERS.reduce<number>(
(acc, tier: Tier) => acc + manifest.tierCounts[tier],
0,
);
}
```
`TIERS` is ordered for human-readable output (universal first, python last);
runtime code that needs the closed enum but is happy with any order can iterate
`Object.keys(manifest.tierCounts) as Tier[]`.
## API Reference
- `type Tier = "python" | "r3f" | "react" | "browser" | "server" | "universal"`
- `const TIERS: readonly Tier[]` — fixed ordering
- `interface PackageRecord` — `readonly` row, eleven fields
- `interface PluginSummary` — `{category, plugin, packages, workspaceDeps}`
- `interface SuavePlanManifest` — `{packages, plugins, categories, tierCounts}`
- `interface WorkspaceGraph` — `{nodes, forward, reverse}`
- `interface RawPackageJson` — only the fields read by `inferTier`
## Implementation Notes
Types live in a sibling `.types.ts` is not appropriate here because this file
defines the cross-module vocabulary rather than the types of a single feature
implementation. It is referenced directly by `src/index.ts` and every other
feature folder. The `readonly` modifiers cascade so downstream consumers cannot
accidentally mutate manifests they receive; mutation must be a conscious clone.