# 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 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 createDocsObservability(opts?): DocsObservability DiscoverOptionsSchema: z.ZodType ``` ## 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.