6.5 KiB
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):
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:
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)
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
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:
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.