189 lines
6.5 KiB
Markdown
189 lines
6.5 KiB
Markdown
# 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.
|