Initial Commit
This commit is contained in:
188
openspec/archive/2026-05-25-enterprise-docs/design.md
Normal file
188
openspec/archive/2026-05-25-enterprise-docs/design.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Design: enterprise-docs
|
||||
|
||||
> Non-normative architecture for `@suaveplan/docs`. Normative requirements live in `./specs/docs/spec.md`.
|
||||
|
||||
## Package position
|
||||
|
||||
`@suaveplan/docs` is a server-tier devtools library: it walks a monorepo file
|
||||
tree, parses every Markdown file via `remark` + `gray-matter`, and exposes a
|
||||
documentation graph, hierarchy, and search index. It is consumed by tooling
|
||||
(doc-site generators, link checkers, completeness audits) — never by a runtime
|
||||
application.
|
||||
|
||||
Path: `packages/devtools/docs/docs/` per `scripts/folder-reorg/assignment.yml`
|
||||
(`devtools` → `docs` plugin → `docs` package).
|
||||
|
||||
## Public surface
|
||||
|
||||
Upstream parity (preserved verbatim):
|
||||
|
||||
```ts
|
||||
discoverDocs(options: DiscoverOptions): Promise<DocFile[]>
|
||||
parseDoc(absolutePath: string, relativePath: string, rawContent: string): DocFile
|
||||
buildGraph(docs: DocFile[]): DocGraph
|
||||
findOrphans(graph: DocGraph): OrphanInfo[]
|
||||
buildHierarchy(graph: DocGraph, options?: BuildHierarchyOptions): DocHierarchy
|
||||
searchDocs(docs: DocFile[], query: string, limit?: number): SearchResult[]
|
||||
```
|
||||
|
||||
Genesis additions:
|
||||
|
||||
```ts
|
||||
const DOCS_CODES = defineCodes("DOCS", [...] as const)
|
||||
class DocsError extends SuaveplanError<DocsErrorCode>
|
||||
createDocsObservability(opts?): DocsObservability
|
||||
DiscoverOptionsSchema: z.ZodType<DiscoverOptions>
|
||||
```
|
||||
|
||||
## File layout (Pattern B per CLAUDE.md)
|
||||
|
||||
```text
|
||||
packages/devtools/docs/docs/
|
||||
package.json
|
||||
README.md
|
||||
tsconfig.lib.json
|
||||
vite.config.ts
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts # explicit named re-exports
|
||||
discovery/
|
||||
discover.ts
|
||||
discover.test.ts
|
||||
discover.md
|
||||
index.ts
|
||||
parser/
|
||||
parse.ts
|
||||
parse.test.ts
|
||||
parse.md
|
||||
index.ts
|
||||
graph/
|
||||
build-graph.ts
|
||||
build-graph.test.ts
|
||||
build-graph.md
|
||||
find-orphans.ts
|
||||
find-orphans.test.ts
|
||||
find-orphans.md
|
||||
index.ts
|
||||
hierarchy/
|
||||
build-hierarchy.ts
|
||||
build-hierarchy.test.ts
|
||||
build-hierarchy.md
|
||||
index.ts
|
||||
search/
|
||||
search.ts
|
||||
search.test.ts
|
||||
search.md
|
||||
index.ts
|
||||
errors/
|
||||
errors.ts
|
||||
errors.test.ts
|
||||
errors.md
|
||||
index.ts
|
||||
observability/
|
||||
observability.ts
|
||||
observability.test.ts
|
||||
observability.md
|
||||
observability.types.ts
|
||||
index.ts
|
||||
types/
|
||||
docs.types.ts
|
||||
index.ts
|
||||
```
|
||||
|
||||
## Error code registry
|
||||
|
||||
```ts
|
||||
export const DOCS_CODES = defineCodes("DOCS", [
|
||||
"INVALID_OPTIONS", // Zod validation of DiscoverOptions failed
|
||||
"READ_FAILED", // node:fs/promises.readFile threw
|
||||
"PARSE_FAILED", // gray-matter / remark threw on malformed input
|
||||
"GLOB_FAILED", // glob threw (permission / IO at directory level)
|
||||
"ABORTED", // signal.aborted observed during discovery
|
||||
"LINK_RESOLUTION_FAILED", // graph link resolver hit an unexpected state
|
||||
"HIERARCHY_CYCLE_LIMIT", // depth limit reached while walking hierarchy
|
||||
"SEARCH_INVALID_QUERY", // searchDocs called with non-string query
|
||||
] as const);
|
||||
```
|
||||
|
||||
Every code is reachable from a real call site and exercised by tests.
|
||||
|
||||
## OTel instruments
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| `docs_discovered_total` | counter | Total docs successfully parsed across all `discoverDocs` calls. |
|
||||
| `docs_parse_error_total` | counter | Total `parseDoc` failures partitioned by `code`. |
|
||||
| `docs_io_error_total` | counter | Total `readFile` / `glob` failures during discovery. |
|
||||
| `docs_search_total` | counter | Total `searchDocs` invocations. |
|
||||
| `docs_discover_duration_ms` | histogram | Wall-clock time of each `discoverDocs` call. |
|
||||
| `docs_parse_duration_ms` | histogram | Wall-clock time of each `parseDoc` call. |
|
||||
| `docs_graph_build_duration_ms` | histogram | Wall-clock time of each `buildGraph` call. |
|
||||
| `docs_search_duration_ms` | histogram | Wall-clock time of each `searchDocs` call. |
|
||||
|
||||
Observability is created via `createDocsObservability(opts?)` and accepted
|
||||
optionally on every public entrypoint. Default uses the no-op `metrics` /
|
||||
`trace` providers so tests pass without an SDK.
|
||||
|
||||
## Typed mdast traversal
|
||||
|
||||
Upstream uses `node as any` to read `Heading` and `Link` children. Genesis
|
||||
imports `Heading`, `Link`, `Text` from `@types/mdast` and narrows with type
|
||||
guards (`if (node.type === "heading")` etc.). No `any`, no casts.
|
||||
|
||||
## Discovery — error surfacing + AbortSignal
|
||||
|
||||
Upstream silently swallows `readFile` failures (`catch {}`). Genesis emits
|
||||
`docs_io_error_total{code:"DOCS.READ_FAILED"}` per failure, continues the sweep
|
||||
(non-fatal — a per-file IO error must not abort a 10k-file monorepo scan), and
|
||||
exposes the count via the observability counter. Callers wanting hard-fail can
|
||||
inspect the counter after the call.
|
||||
|
||||
`AbortSignal` is checked (a) before glob, (b) between glob and parse, and (c)
|
||||
after every batch of 64 parsed files. On abort, a `DocsError(DOCS.ABORTED)` is
|
||||
thrown and the discovery histogram records the partial duration.
|
||||
|
||||
## Category derivation
|
||||
|
||||
Eighteen genesis categories per the closed vocabulary in CLAUDE.md / Layout
|
||||
section:
|
||||
|
||||
```ts
|
||||
const GENESIS_CATEGORIES = [
|
||||
"foundation", "persistence", "networking", "communication", "api-stack",
|
||||
"identity", "security-stack", "observability", "media", "ui", "threejs",
|
||||
"localization", "geospatial", "money", "commerce", "testkit", "devtools",
|
||||
"gaming",
|
||||
] as const;
|
||||
type DocCategory = (typeof GENESIS_CATEGORIES)[number] | "root";
|
||||
```
|
||||
|
||||
`deriveCategory(relativePath)` reads the second path segment after `packages/`
|
||||
to recover the category. Upstream's six-tier union (`universal`/`react`/`server`
|
||||
/`browser`/`three`/`finance`) is replaced — the test suite no longer asserts
|
||||
those upstream values.
|
||||
|
||||
## Hierarchy depth limit
|
||||
|
||||
Upstream hard-codes depth 8. Genesis preserves the default but accepts an
|
||||
optional `maxDepth` field on `BuildHierarchyOptions` so doc-site builders with
|
||||
larger forests can opt in.
|
||||
|
||||
## Deps
|
||||
|
||||
Runtime: `@suaveplan/error`, `@suaveplan/opentelemetry`, `@suaveplan/types`,
|
||||
`tslib`, `zod`, `glob`, `gray-matter`, `remark-parse`, `remark-gfm`, `unified`,
|
||||
`unist-util-visit`, `@types/mdast` (runtime types only).
|
||||
|
||||
Catalog: a new `markdown` catalog key carries `glob`, `gray-matter`, `remark-*`,
|
||||
`unified`, `unist-util-visit`, `@types/mdast`. The root `package.json`
|
||||
`catalogs` block is extended in the same commit.
|
||||
|
||||
Dev: `vitest`, `@vitest/coverage-istanbul`, `@suaveplan/testing`, `@types/node`,
|
||||
`vite`, `vite-plugin-dts`, `typescript`.
|
||||
|
||||
## Testing strategy
|
||||
|
||||
Real `node:fs/promises` against `mkdtemp` temp dirs — never mocks. Coverage
|
||||
target: 100/100/100/100 with `provider: 'istanbul'`. Every error code is
|
||||
exercised by at least one negative-path test.
|
||||
Reference in New Issue
Block a user