Initial Commit
This commit is contained in:
196
openspec/archive/2026-05-25-enterprise-overview/design.md
Normal file
196
openspec/archive/2026-05-25-enterprise-overview/design.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# 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>"
|
||||
short: string; // "<short>"
|
||||
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<Tier, number>;
|
||||
}
|
||||
|
||||
interface WorkspaceGraph {
|
||||
nodes: Set<string>;
|
||||
forward: Map<string, Set<string>>; // pkg → deps
|
||||
reverse: Map<string, Set<string>>; // 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
|
||||
`<root>/packages/<category>/<plugin>/<pkg>/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<string, number>` (package count).
|
||||
- `byTier(records)` returns `Record<Tier, PackageRecord[]>`.
|
||||
|
||||
## 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`.
|
||||
178
openspec/archive/2026-05-25-enterprise-overview/proposal.md
Normal file
178
openspec/archive/2026-05-25-enterprise-overview/proposal.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# OpenSpec Change: enterprise-overview
|
||||
|
||||
- **Change ID**: enterprise-overview
|
||||
- **Package**: `@suaveplan/overview`
|
||||
- **Category / plugin**: `devtools/tooling`
|
||||
- **Target path**: `packages/devtools/tooling/overview`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-05-25
|
||||
- **Design**: ./design.md
|
||||
- **Spec delta**: ./specs/overview/spec.md
|
||||
- **Reference docs**:
|
||||
- `/mnt/s/web/suaveplan/enterprise/overview.md` (enterprise contract)
|
||||
- `/mnt/s/web/suaveplan/packages/universal/overview/` (upstream — documentation-only bundle)
|
||||
- `scripts/folder-reorg/generate-manifest.ts` (genesis data model — the script
|
||||
explicitly notes "Replaces @suaveplan/overview"; this migration restores
|
||||
`overview` as the **library form** of that same data model)
|
||||
- `CLAUDE.md` foundation listing: "**overview** — machine-readable monorepo
|
||||
manifest with Zod schemas + graph utilities. Leaf."
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Migrate `@suaveplan/overview` onto genesis as a fresh, enterprise-grade
|
||||
universal-tier **machine-readable monorepo manifest + graph-utilities library**.
|
||||
|
||||
The upstream main-branch package is a documentation bundle (≈90 ad-hoc `.md`
|
||||
files plus a 27-line `postinstall.ts` that rewrites links). It carries no
|
||||
runtime API, no tests, no schemas. That artefact is not enterprise-grade and
|
||||
cannot be ported as-is.
|
||||
|
||||
The genesis form is a different package by name only: a typed library that
|
||||
discovers every `packages/<category>/<plugin>/<pkg>/package.json` on disk,
|
||||
parses each into a Zod-validated `PackageRecord`, exposes a typed
|
||||
`SuavePlanManifest`, and provides graph utilities (forward / reverse adjacency,
|
||||
topological order, cycle detection, transitive closure, leaf / root detection,
|
||||
tier overlay, plugin and category roll-ups).
|
||||
|
||||
The same data model is currently produced ad-hoc by
|
||||
`scripts/folder-reorg/generate-manifest.ts`, whose own header comment says
|
||||
"Replaces @suaveplan/overview". This change re-introduces `overview` as the
|
||||
**library** form of that script — single source of truth, exported as a typed
|
||||
API, fully testable, with full enterprise instrumentation. The script can later
|
||||
be refactored to consume the library; that refactor is out of scope for this
|
||||
change.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
- Package directory does not exist on genesis.
|
||||
- `STATUS.md` lists `@suaveplan/overview` as `–` (yet to migrate) under
|
||||
`devtools/tooling`.
|
||||
- `scripts/folder-reorg/assignment.yml` line 665 confirms `tooling/overview`.
|
||||
- `MANIFEST.md` does not yet contain an `@suaveplan/overview` row.
|
||||
- `CLAUDE.md` foundation listing already describes the intended genesis shape
|
||||
(machine-readable manifest + Zod schemas + graph utilities) — this change
|
||||
realises that description.
|
||||
|
||||
**Verified by:** `ls packages/devtools/tooling/` (no `overview/`),
|
||||
`grep -n "overview" STATUS.md` (one `–` row).
|
||||
|
||||
---
|
||||
|
||||
## Parity & enhancement checklist
|
||||
|
||||
The upstream package has no runtime surface, so this is functionally a
|
||||
**genesis-original** library aligned to the data model already encoded by
|
||||
`scripts/folder-reorg/generate-manifest.ts`. Every column below has a
|
||||
compensating enhancement; nothing is dropped.
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `.md` documentation bundle | ✅ docs-only | 🚫 dropped | Genesis ships per-package `README.md` + co-located `.md`; central doc bundle is obsolete. Replaced by **machine-readable manifest** as the cross-package index. |
|
||||
| `postinstall.ts` link-rewriter | ✅ | 🚫 dropped | No `dist/*.md` to rewrite on genesis. Replaced by typed API. |
|
||||
| Package discovery | 🚫 | ⬆️ NEW | `discoverPackages(root)` walks `packages/<category>/<plugin>/<pkg>/package.json`. |
|
||||
| Zod schemas for every type | 🚫 | ⬆️ NEW | `PackageRecordSchema`, `SuavePlanManifestSchema`, `TierSchema`, `PluginSummarySchema`. |
|
||||
| Tier inference | 🚫 | ⬆️ NEW | python → r3f → react → browser → server → universal (matches `openspec/specs/monorepo-structure/spec.md`). |
|
||||
| Graph utilities | 🚫 | ⬆️ NEW | forward / reverse adjacency, topo sort, cycle detection, transitive closure, leaves, roots. |
|
||||
| Plugin + category roll-ups | 🚫 | ⬆️ NEW | `byPlugin()`, `byCategory()`, `byTier()`. |
|
||||
| Stable error codes via `defineCodes` | 🚫 | ⬆️ NEW | 6 `OVERVIEW.*` codes — each thrown from a real call site. |
|
||||
| OpenTelemetry counters + histogram | 🚫 | ⬆️ NEW | 3 counters + 1 histogram. |
|
||||
| `AbortSignal` support on async I/O | 🚫 | ⬆️ NEW | `discover` accepts `signal`; aborts mid-walk. |
|
||||
| 100/100/100/100 Istanbul coverage | 🚫 | ⬆️ NEW | Test gate. |
|
||||
| Co-located `.md` per implementation `.ts` | 🚫 | ⬆️ NEW | §4 docs rule. |
|
||||
| Universal tier (zero Node-only run-time imports in library code) | n/a | ✅ | I/O helpers are server-tier and gated behind a sub-path entry; pure data + graph code is universal. |
|
||||
|
||||
No 🚫 entry without compensating ⬆️ enhancement.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | `SuaveplanError` base + `defineCodes` |
|
||||
| `@suaveplan/types` (`workspace:^`) | shared `Clock`-style types |
|
||||
| `tslib` (`catalog:build`) | runtime helpers |
|
||||
| `zod` (`catalog:forms`) | schemas |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | counters + histogram |
|
||||
|
||||
Dev deps: `@suaveplan/testing` (`workspace:^`), `vitest`,
|
||||
`@vitest/coverage-istanbul`, `typescript`, `vite`, `vite-plugin-dts`.
|
||||
|
||||
---
|
||||
|
||||
## Public API surface (summary)
|
||||
|
||||
```ts
|
||||
import {
|
||||
// schemas
|
||||
PackageRecordSchema,
|
||||
SuavePlanManifestSchema,
|
||||
TierSchema,
|
||||
// discovery (server sub-path: "@suaveplan/overview/discover")
|
||||
discoverPackages,
|
||||
// graph
|
||||
buildGraph,
|
||||
topologicalOrder,
|
||||
detectCycles,
|
||||
transitiveDependencies,
|
||||
transitiveDependents,
|
||||
leafPackages,
|
||||
rootPackages,
|
||||
// roll-ups
|
||||
byPlugin,
|
||||
byCategory,
|
||||
byTier,
|
||||
// errors
|
||||
OVERVIEW_CODES,
|
||||
OverviewError,
|
||||
} from "@suaveplan/overview";
|
||||
|
||||
// Types
|
||||
import type {
|
||||
PackageRecord,
|
||||
SuavePlanManifest,
|
||||
Tier,
|
||||
PluginSummary,
|
||||
WorkspaceGraph,
|
||||
} from "@suaveplan/overview";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
packages/devtools/tooling/overview/
|
||||
package.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts, README.md
|
||||
src/
|
||||
index.ts
|
||||
types/types.ts, types.md
|
||||
errors/errors.{ts,md,test.ts}
|
||||
schemas/schemas.{ts,md,test.ts}
|
||||
tier/tier.{ts,md,test.ts}
|
||||
discover/discover.{ts,md,test.ts}
|
||||
graph/graph.{ts,md,test.ts}
|
||||
rollups/rollups.{ts,md,test.ts}
|
||||
telemetry/telemetry.{ts,md,test.ts}
|
||||
tests/setup/polyfills.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
- No `@suaveplan/*` consumer currently imports `@suaveplan/overview` on
|
||||
genesis. The library is consumable by `scripts/folder-reorg/generate-manifest.ts`
|
||||
in a future cleanup change (out of scope here).
|
||||
- Downstream tools (CLI dashboards, dependency-graph visualisers, ratings
|
||||
dashboards) can adopt the typed API instead of re-implementing manifest
|
||||
discovery.
|
||||
|
||||
**Breaking changes**: none (new package on genesis; upstream was docs-only
|
||||
with no runtime API).
|
||||
@@ -0,0 +1,246 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# 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>"` 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<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.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<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 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
|
||||
`"<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** `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`
|
||||
83
openspec/archive/2026-05-25-enterprise-overview/tasks.md
Normal file
83
openspec/archive/2026-05-25-enterprise-overview/tasks.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Tasks — enterprise-overview
|
||||
|
||||
Numbered checklist of the discrete units of work to ship `@suaveplan/overview`.
|
||||
|
||||
## 1. Skeleton
|
||||
|
||||
- [x] Create `packages/devtools/tooling/overview/` with `package.json`,
|
||||
`tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`,
|
||||
`README.md`, and `tests/setup/polyfills.ts`.
|
||||
- [x] Wire `package.json` exports for `.` and `./discover`.
|
||||
|
||||
## 2. Types
|
||||
|
||||
- [x] `src/types/types.ts` — `Tier`, `PackageRecord`, `PluginSummary`,
|
||||
`SuavePlanManifest`, `WorkspaceGraph`. Sibling `types.md`.
|
||||
|
||||
## 3. Errors
|
||||
|
||||
- [x] `src/errors/errors.ts` — `defineCodes("OVERVIEW", [...])`, `OverviewError`
|
||||
subclass, code uniqueness test, sibling `.md`.
|
||||
|
||||
## 4. Schemas
|
||||
|
||||
- [x] `src/schemas/schemas.ts` — Zod schemas for `Tier`, `PackageRecord`,
|
||||
`PluginSummary`, `SuavePlanManifest`. Sibling `.md` and tests.
|
||||
|
||||
## 5. Tier inference
|
||||
|
||||
- [x] `src/tier/tier.ts` — `inferTier(short, pkgJson, hints?)`, exported
|
||||
`SERVER_DEP_HINTS`. Sibling `.md` and tests.
|
||||
|
||||
## 6. Discovery (server sub-path)
|
||||
|
||||
- [x] `src/discover/discover.ts` — `discoverPackages(options)`, async
|
||||
`discoverPackagesIter` with `AbortSignal`. Real-fs test fixture under
|
||||
`tests/setup/fixtures/`. Sibling `.md`.
|
||||
|
||||
## 7. Graph utilities
|
||||
|
||||
- [x] `src/graph/graph.ts` — `buildGraph`, `topologicalOrder`, `detectCycles`,
|
||||
`transitiveDependencies`, `transitiveDependents`, `leafPackages`,
|
||||
`rootPackages`. Sibling `.md` and tests including `fast-check`
|
||||
property-based cases.
|
||||
|
||||
## 8. Roll-ups
|
||||
|
||||
- [x] `src/rollups/rollups.ts` — `byPlugin`, `byCategory`, `byTier`. Sibling
|
||||
`.md` and tests.
|
||||
|
||||
## 9. Telemetry
|
||||
|
||||
- [x] `src/telemetry/telemetry.ts` — meter init, counter + histogram exports.
|
||||
Sibling `.md` and tests using in-memory metric reader.
|
||||
|
||||
## 10. Root barrel
|
||||
|
||||
- [x] `src/index.ts` — explicit named re-exports (no `export *`). README at
|
||||
package root documenting the API surface.
|
||||
|
||||
## 11. Gate
|
||||
|
||||
- [x] `bunx turbo lint typecheck test build --filter=@suaveplan/overview`
|
||||
green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/overview` reports
|
||||
100/100/100/100.
|
||||
|
||||
## 12. Manifest refresh
|
||||
|
||||
- [x] Run `bun scripts/folder-reorg/generate-manifest.ts` so `MANIFEST.md`
|
||||
lists the new package.
|
||||
|
||||
## 13. STATUS flip
|
||||
|
||||
- [x] In `STATUS.md`, change the `@suaveplan/overview` row from `–` to `✅`
|
||||
and update its info column to reflect the migrated surface.
|
||||
- [x] Update the Counts table (+1 migrated, -1 pending).
|
||||
|
||||
## 14. Fold + archive
|
||||
|
||||
- [x] Fold `openspec/changes/enterprise-overview/specs/overview/spec.md` into
|
||||
`openspec/specs/overview/spec.md` (canonical).
|
||||
- [x] Move `openspec/changes/enterprise-overview/` to
|
||||
`openspec/archive/2026-05-25-enterprise-overview/`.
|
||||
Reference in New Issue
Block a user