@orkestrel/scaffold 0.0.63 → 0.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +18 -103
  2. package/dist/bin/main.js +95 -27
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/AGENTS.md +2 -2
  5. package/dist/host/CLAUDE.md +6 -0
  6. package/dist/host/agents/orchestration.md +23 -15
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +6 -6
  9. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +4 -4
  10. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +5 -5
  11. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +15 -15
  13. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +43 -17
  14. package/dist/host/agents/skills/orkestrel-publish/references/window.md +41 -16
  15. package/dist/host/claude/agents/orkestrel.md +56 -56
  16. package/dist/host/claude/agents/reviewer.md +13 -0
  17. package/dist/host/claude/rules/architecture.md +51 -45
  18. package/dist/host/claude/rules/documentation.md +18 -1
  19. package/dist/host/claude/rules/portability.md +2 -0
  20. package/dist/host/claude/rules/quality.md +1 -1
  21. package/dist/host/claude/rules/tests.md +12 -11
  22. package/dist/host/claude/rules/typescript.md +5 -0
  23. package/dist/host/claude/rules/workspace.md +23 -18
  24. package/dist/host/claude/rules/writing.md +4 -0
  25. package/dist/host/codex/agents/orkestrel.toml +3 -3
  26. package/dist/host/codex/agents/reviewer.toml +4 -2
  27. package/dist/host/configs/helpers.ts +311 -2
  28. package/dist/host/configs/policy.ts +1100 -51
  29. package/dist/host/dotfiles/oxlintrc.json +72 -1
  30. package/dist/host/guides/guide.md +749 -222
  31. package/dist/host/guides/scaffold.md +472 -378
  32. package/dist/host/manifest.json +34 -33
  33. package/dist/host/scripts/codex.sh +0 -0
  34. package/dist/host/scripts/cursor.sh +0 -0
  35. package/dist/host/scripts/deps.sh +0 -0
  36. package/dist/host/scripts/ollama.sh +0 -0
  37. package/dist/host/tests/config.test.ts +1200 -16
  38. package/dist/host/tests/policy.test.ts +157 -173
  39. package/dist/host/tests/setupPolicy.ts +522 -1007
  40. package/dist/src/core/index.cjs +371 -277
  41. package/dist/src/core/index.cjs.map +1 -1
  42. package/dist/src/core/index.d.cts +130 -120
  43. package/dist/src/core/index.d.ts +130 -120
  44. package/dist/src/core/index.js +371 -276
  45. package/dist/src/core/index.js.map +1 -1
  46. package/dist/src/server/index.cjs +28 -21
  47. package/dist/src/server/index.cjs.map +1 -1
  48. package/dist/src/server/index.d.cts +38 -33
  49. package/dist/src/server/index.d.ts +38 -33
  50. package/dist/src/server/index.js +28 -21
  51. package/dist/src/server/index.js.map +1 -1
  52. package/package.json +15 -16
@@ -1,62 +1,109 @@
1
1
  # Guide
2
2
 
3
- > A pure, I/O-free guides-parity toolkit: `Guide` extracts a markdown guide's documented
4
- > surface, method groups, links, test links, and fenced code blocks; `Source` reflects direct
5
- > declarations and conventional barrel reachability from a consumer-supplied file inventory via pure text
6
- > scanners (no filesystem or TypeScript compiler API); runtime dependencies provide markdown
7
- > and contract primitives, while comparison helpers (`missingSymbols`, `findMissing`,
8
- > `resolveLink`, …) reduce every guides-parity check to `expect([]).toEqual([])`
9
- > (AGENTS §22). Source: [`src/core`](../src/core). Published through `@orkestrel/guide`.
10
-
11
- The doctrine: a guide is a contract, not prose. `createGuide(markdown)` parses a guide's
12
- source once (via `@orkestrel/markdown`) into a `GuideInterface` — its `## Surface` identifiers
13
- (kind-tagged), its `## Methods` interface/method groups, every link, its `## Tests`
14
- links, and every fenced code block, each cached at construction.
15
- `createSource({ files, module })` builds a
16
- `SourceInterface` that reflects intentional direct declarations, conventional barrel-reachable
17
- declarations, and interface/class methods by scanning a consumer-gathered file inventory with
18
- plain-text line scanners, never touching disk itself. `createSourceManager({ files, modules })`
19
- resolves the consumer's own import specifiers onto those views, one shared `Source` per module, so
20
- a check that meets an import decides from the specifier alone which face of the package it names.
21
- A guides-parity test asserts direct declarations equal the barrel surface and the barrel surface
22
- equals the documented surface, in both directions. `parseManifest` reads a `guides/README.md`'s
23
- `## By concept` table into the list of `{ concept, spec, source, tests }` entries a suite
24
- iterates to run this check once per documented concept.
3
+ > A guides-parity toolkit: pure inventory readers and comparisons in core, plus a reusable server
4
+ > command that checks or explicitly rewrites a package's guides.
5
+
6
+ A guide is a contract, not prose. `createGuide(markdown)` parses a guide's source once (through
7
+ `@orkestrel/markdown`) into a `GuideInterface` — its `## Surface` identifiers (keyword-tagged),
8
+ its `## Methods` interface/method groups, every link, its `## Tests` links, and every fenced code
9
+ block, each cached at construction. `createSource({ files, module })` builds a `SourceInterface`
10
+ that reflects intentional direct declarations, conventional barrel-reachable declarations, and
11
+ interface/class methods by scanning a consumer-gathered file inventory with plain-text line
12
+ scanners, never touching disk itself. `createSourceManager({ files, modules })` resolves the
13
+ consumer's own import specifiers onto those views, one shared `Source` per module, so a check
14
+ that meets an import decides from the specifier alone which face of the package it names. A
15
+ guides-parity test asserts direct declarations equal the barrel surface and the barrel surface
16
+ equals the documented surface, in both directions, and `findDrift` reports where a `Summary`
17
+ cell, a documented method, or a titled fence disagrees with the source it documents.
18
+ `parseManifest` reads a `guides/README.md`'s `## By concept` table into the list of
19
+ `{ concept, spec, source, tests }` entries a suite iterates to run this check once per documented
20
+ concept. [`GuideCommand`](../src/server/GuideCommand.ts) supplies the Node shell shared by package
21
+ guide scripts. It keeps package assertions in a worker callback while it owns native argument
22
+ selection, fresh inventory reads, explicit writes, the guides-project run, reporting, and exit
23
+ precedence. This package publishes core through `@orkestrel/guide` and the command through
24
+ `@orkestrel/guide/server`; its source is [`src/core`](../src/core) and
25
+ [`src/server`](../src/server).
25
26
 
26
27
  ## Surface
27
28
 
28
29
  ### Types
29
30
 
30
31
  The manifest/extraction shapes every check is built from, from [`types.ts`](../src/core/types.ts).
31
-
32
- | Name | Kind | Shape |
33
- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | `ExportKind` | type | `'type' \| 'interface' \| 'const' \| 'function' \| 'class'` — the five-kind reflection population. Comment/template payload and enums are outside it; general package policy does not forbid enums. |
35
- | `SurfaceSymbol` | interface | `{ name, kind }` — one documented / exported symbol. |
36
- | `GuideModule` | type | `string \| readonly string[]` — one source directory, or several; `'.'` is the canonical workspace root. |
37
- | `SourceLine` | interface | `{ source, code, jsdoc }` — one terminator-free physical source line with exact raw text, equal-length projections, and every genuine JSDoc span at its physical column or `undefined`. |
38
- | `ManifestEntry` | interface | `{ concept, spec, source, tests }` — one `## By concept` manifest row, paths normalized to workspace root. |
39
- | `MethodGroup` | interface | `{ interface, methods }` — one `#### \`Interface\`` block's documented method names, in table order. |
40
- | `GuideFence` | interface | `{ language, code }` — one fenced code block; `language` is its info-string tag, or `undefined` when the fence is untagged. |
41
- | `GuideInterface` | interface | `{ sections, surface, methods, links, tests, fences }` — the structured, pure view over one parsed guide. See [`## Methods`](#methods). |
42
- | `SourceInterface` | interface | `{ exports, surface, methods, exists, hidden, examples }` — direct declarations, conventional barrel reachability, members, paths, discipline, and examples. See [`## Methods`](#methods). |
43
- | `SourceManagerInterface` | interface | `{ source }` — resolves one import specifier to the shared source view of the module it names. See [`## Methods`](#methods). |
44
- | `SourceOptions` | interface | `{ files, module }` — exact canonical-segment opaque workspace-relative inventory keys plus the canonicalized module scope to reflect. |
45
- | `SourceManagerOptions` | interface | `{ files, modules }` — one shared inventory plus the consumer's own specifier-to-module policy. |
46
- | `DeclarationHead` | interface | `{ text, end }` — a declaration head joined into one line (across an oxfmt-wrapped signature) plus the index of the line ending in `{`. |
32
+ A `Shape` cell lists an interface's property names alone, and a type alias's value.
33
+
34
+ | Name | Kind | Shape | Summary |
35
+ | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
36
+ | `ExportKeyword` | type | `'type' \| 'interface' \| 'const' \| 'function' \| 'class'` | Represents the declaration keyword a documented / exported symbol carries — the reflected `type`, `interface`, `const`, `function`, and `class` heads, derived from `EXPORT_KEYWORDS` so the type, the guard, and the shape name one population. Comment/template payload is excluded before reflection. `enum` is outside this population, not forbidden by general package policy. |
37
+ | `SurfaceSymbol` | interface | `{ name, keyword, summary? }` | Represents one documented / exported symbol — its identifier, its declaration keyword, and the description paragraph the guide and the doc block are compared on. |
38
+ | `GuideModule` | type | `string \| readonly string[]` | Represents the source scope a guide's manifest entry covers — one module directory, or several when a layer guide spans multiple source directories (a core module plus its backend implementations). `'.'` is the canonical workspace-root directory; empty, trailing-slash, and dot-segment spellings canonicalize to the same value before reflection. |
39
+ | `SourceLine` | interface | `{ source, code, jsdoc }` | Represents one terminator-free physical source line and its aligned reflection projections. Every projection has the same length as `source`, and every genuine JSDoc span retains its physical opener column; the final physical line is present even when it is empty. |
40
+ | `SourceComment` | interface | `{ text, line }` | Represents one eligible genuine JSDoc block paired with the physical record it documents — the block's unwrapped body and the `SourceLine` that follows its chain. |
41
+ | `MethodEntry` | interface | `{ name, summary? }` | Represents one documented method — its identifier plus the description paragraph the guide's `Summary` cell and the member's doc block are compared on. |
42
+ | `SourceExample` | interface | `{ name, title?, code, language? }` | Represents one `@example` block read from a doc comment — the declaration it documents, its pairing title, its code, and its fence language. |
43
+ | `DriftCategory` | type | `'summary' \| 'example'` | Represents the compared site one disagreement came from. |
44
+ | `Drift` | interface | `{ key, category, guide?, source? }` | Represents one disagreement between a guide and the source it documents — the compared key with the text each side carries there, the side carrying no text omitted. |
45
+ | `ManifestEntry` | interface | `{ concept, spec, source, tests }` | Represents one `## By concept` manifest row — a single guides-parity check target, paths normalized to workspace root. |
46
+ | `ParityPitch` | interface | `{ readme, spec }` | Names the inventory keys paired for README pitch parity. |
47
+ | `ParityOptions` | interface | `{ files, entries, modules, languages, language, pitch? }` | Represents the pure inputs that configure a guides-parity composition. |
48
+ | `ParityRow` | interface | `{ entry, guide, source }` | Represents a manifest row joined to its parsed guide and reflected source. |
49
+ | `ParityFinding` | interface | `{ spec?, text }` | Represents a preformatted guides-parity finding. |
50
+ | `ParityExampleResult` | interface | `{ fences, functions, methods, titles }` | Groups executable-example findings by their independent evidence populations. |
51
+ | `ParityResult` | interface | `{ input, sections, surface, methods, declarations, links, tests, fences, examples, imports, drift, pitch }` | Groups independently assertable guides-parity findings by subject. |
52
+ | `ParityDirection` | type | `'guide' \| 'source'` | Represents the destination an explicit parity rewrite updates. |
53
+ | `ParityChange` | interface | `{ path, content }` | Represents a changed inventory text returned by a parity rewrite. |
54
+ | `ParityRewriteResult` | interface | `{ changes, findings }` | Represents the changed texts and unresolved findings from an explicit rewrite. |
55
+ | `ParityInterface` | interface | `{ rows, inspect, document, annotate }` | Represents a pure guides-parity composition over caller-supplied inventory. |
56
+ | `MethodGroup` | interface | `{ interface, methods }` | Represents one behavioral interface a guide's `## Methods` section documents — the H4 heading naming that interface as a code span, and the member entries its table lists. |
57
+ | `FenceImport` | interface | `{ specifier, names }` | Represents one brace `import` statement projected from a guide fence — its specifier paired with the exported names it binds. |
58
+ | `GuideFence` | interface | `{ language, code, title? }` | Represents one fenced code block projected from a guide document — its `language` is the fence's info-string tag and is absent when the fence is untagged, and its `title` is the flattened text of its nearest preceding heading. |
59
+ | `GuideInterface` | interface | `{ sections, tagline, surface, methods, unnamed, links, tests, fences }` | Represents the structured, pure view of one parsed guide — every projection extracted and cached once at construction (see `createGuide`). |
60
+ | `SourceInterface` | interface | `{ exports, surface, methods, exists, hidden, examples }` | Represents the reflected source truth a guide's documented surface is checked against — a pure view over a consumer-supplied file inventory (see `Source`). |
61
+ | `SourceManagerInterface` | interface | `{ source, sources }` | Represents a specifier resolver that shares one `SourceInterface` per module and enumerates those views. |
62
+ | `SourceOptions` | interface | `{ files, module }` | Represents the construction input for a `Source` — a consumer-supplied file inventory (root-relative path → file text) plus the module scope to reflect. The consumer gathers `files` however their environment allows (`node:fs` in a Node script, `import.meta.glob` in a browser/vitest run) — `Source` itself never touches disk. |
63
+ | `SourceManagerOptions` | interface | `{ files, modules }` | Represents the construction input for a `SourceManager`: one shared file inventory plus the consumer's specifier-to-module policy. |
64
+ | `DeclarationHead` | interface | `{ text, end }` | Pairs a declaration head joined into a single line with the index of the line carrying its opening `{` — how a head that oxfmt wrapped across lines (printWidth 100) is matched as if it were written on one. |
65
+ | `Declaration` | interface | `{ body, bases }` | Represents one located `export class` / `export interface` declaration — the body lines and the base identifiers read from the same head, so a consumer never pairs one declaration's body with another declaration's heritage (see `collectDeclarations`). |
66
+ | `DeclarationKeyword` | type | `'class' \| 'interface'` | Represents which declaration head `collectDeclarations` and `Source` locate — a `class` or an `interface`. That pair is the subset of `ExportKeyword` carrying a body whose members a guide's `## Methods` table documents. |
67
+
68
+ The server command contracts come from [`types.ts`](../src/server/types.ts).
69
+
70
+ | Name | Kind | Shape | Summary |
71
+ | ----------------------- | --------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
72
+ | `GuideReadFunction` | type | `(root: URL \| string, targets: readonly string[]) => Readonly<Record<string, string>>` | Reads a workspace inventory through the consumer's host reader. |
73
+ | `GuideRunnerOptions` | interface | `{ root, config, project, reporters, cache, watch }` | Defines the fixed guides-project runner invocation. |
74
+ | `GuideRunnerFunction` | type | `(mode: 'test', options: GuideRunnerOptions) => Promise<unknown>` | Creates a foreign guides-project runner whose consumed members Guide validates before use. |
75
+ | `GuideCommandContext` | interface | `{ root, files, rows, report }` | Supplies fresh owned inventory, joined rows, and its generic parity result to package assertion setup. |
76
+ | `GuideCommandHandler` | type | `(context: GuideCommandContext) => Promise<void>` | Registers package assertions against the fresh worker context. |
77
+ | `GuideCommandOptions` | interface | `{ root, patterns, modules, languages, language, reader, runner }` | Configures workspace policy and direct host ports for the reusable command. |
78
+ | `GuideCommandInterface` | interface | `{ execute }` | Represents the server command's native and worker behavior. |
47
79
 
48
80
  ### Constants
49
81
 
50
- The section-heading keys and external-link schemes every extractor and link check is keyed
51
- on, from [`constants.ts`](../src/core/constants.ts).
52
-
53
- | Name | Kind | Behavior |
54
- | ------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------- |
55
- | `SURFACE` | const | `'Surface'` — the `## Surface` heading text. |
56
- | `METHODS` | const | `'Methods'` — the `## Methods` heading text. |
57
- | `TESTS` | const | `'Tests'` — the `## Tests` heading text. |
58
- | `MANIFEST` | const | `'By concept'` — the `## By concept` manifest heading text. |
59
- | `EXTERNAL_SCHEMES` | const | `readonly string[]` — `['http:', 'https:', 'mailto:', 'tel:']`; a link with one of these prefixes is never filesystem-resolved. |
82
+ The declaration-keyword population, the section-heading keys, and the external-link schemes every
83
+ extractor and link check is keyed on, from [`constants.ts`](../src/core/constants.ts). The server
84
+ workflow paths and usage text come from [`constants.ts`](../src/server/constants.ts).
85
+
86
+ | Name | Kind | Value | Summary |
87
+ | ------------------ | ----- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | `EXPORT_KEYWORDS` | const | `['type', 'interface', 'const', 'function', 'class']` | Lists the declaration keywords a documented or exported symbol carries, in the order the reflection grammar names them — the frozen population `ExportKeyword`, `isExportKeyword`, and `surfaceSymbolShape` all derive from. |
89
+ | `DRIFT_CATEGORIES` | const | `['summary', 'example']` | Lists the compared sites a `Drift` can describe. |
90
+ | `SURFACE` | const | `'Surface'` | Names the `## Surface` heading text a guide's documented exports section is keyed on. |
91
+ | `METHODS` | const | `'Methods'` | Names the `## Methods` heading text a guide's documented interface-methods section is keyed on. |
92
+ | `TESTS` | const | `'Tests'` | Names the `## Tests` heading text a guide's documented test-link section is keyed on. |
93
+ | `MANIFEST` | const | `'By concept'` | Names the `## By concept` heading text the manifest's run-map table is keyed on. |
94
+ | `KIND` | const | `'Kind'` | Names the header text of the column a `## Surface` table's declaration keyword is read from. |
95
+ | `SUMMARY` | const | `'Summary'` | Names the header text of the column a `## Surface` or `## Methods` table's compared description paragraph is read from. |
96
+ | `EXTERNAL_SCHEMES` | const | `['http:', 'https:', 'mailto:', 'tel:']` | Lists the link `href` schemes a guides-parity link check skips as external — a link with one of these prefixes (or a bare `#` anchor, handled separately in `isExternalLink`) is never resolved against the filesystem. |
97
+ | `WRAP_WIDTH` | const | `100` | Holds the default character budget a rewritten doc block's description paragraph wraps inside — the greatest number of characters a re-wrapped line may carry, counted from the line's first character with a tab counting as one, so a wrapped line reads `${indent} * ${text}` and never passes that count. |
98
+
99
+ ### Server constants
100
+
101
+ | Name | Kind | Value | Summary |
102
+ | ---------------- | ----- | ----------------------------------------------------------- | ---------------------------------------------------------------------- |
103
+ | `GUIDE_INDEX` | const | `'guides/README.md'` | Names the workspace-relative guide concept index. |
104
+ | `GUIDE_MANIFEST` | const | `'package.json'` | Names the package manifest read for native pitch selection. |
105
+ | `GUIDE_README` | const | `'README.md'` | Names the package README paired with its indexed guide pitch. |
106
+ | `GUIDE_USAGE` | const | `'usage: npm run test:guides [-- --to guide\|--to source]'` | Holds the native command usage text written for unsupported arguments. |
60
107
 
61
108
  ### Helpers
62
109
 
@@ -65,109 +112,224 @@ the declaration, member, JSDoc, and guide-document grammars built on it, and the
65
112
  path primitives `Guide`, `Source`, `parsers.ts`, and a consumer's parity test all reach for
66
113
  directly.
67
114
 
68
- | Name | Kind | Signature | Behavior |
69
- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
- | `normalizeDirectories` | function | `(module: GuideModule) => readonly string[]` | Canonicalizes through `resolvePath`, uses `'.'` for root, and removes duplicates in first-seen order. |
71
- | `moduleKey` | function | `(module: GuideModule) => string` | The stable cache key for a module scope — its normalized directories joined by NUL, so two spellings of one module share a key and no directory boundary can collide with directory text. |
72
- | `selectModuleKeys` | function | `(files: Readonly<Record<string, string>>, module: GuideModule) => readonly string[]` | Selects exact canonical-segment opaque `.ts` keys under any scope and excludes every selected exact `index.ts` plus `*.test.ts`; sorted. |
73
- | `hasCanonicalSegments` | function | `(key: string) => boolean` | Rejects empty, `.` and `..` slash-separated segments without normalization while retaining ordinary dotfiles. |
74
- | `symbolKey` | function | `(symbol: SurfaceSymbol) => string` | The bijection key for a surface symbol — `${kind} ${name}` — so a symbol comparison diffs (name, kind) pairs, not names alone. |
75
- | `findMissing` | function | `(names: readonly string[], source: readonly string[]) => readonly string[]` | The names present in `names` but absent from `source` — the set-difference behind a both-directions bijection assertion. |
76
- | `missingSymbols` | function | `(symbols: readonly SurfaceSymbol[], source: readonly SurfaceSymbol[]) => readonly string[]` | The `symbolKey` set-difference between two symbol lists. |
77
- | `extractSourceLines` | function | `(source: string) => readonly SourceLine[]` | Equal-length physical source, code, and JSDoc projection with bounded literal Unicode identifier slash state. |
78
- | `exportsFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | Direct five-kind exports over projected lines with an uninterrupted column-zero head; projection never widens membership. |
79
- | `hiddenFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The non-exported mirror with the same projected, uninterrupted column-zero head and five-kind population. |
80
- | `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins a declaration head starting at `start` into one space-separated line, consuming lines until the first ending in `{`. |
81
- | `declarationBody` | function | `(source: string, keyword: 'class' \| 'interface', name: string) => readonly string[]` | Selects a real head/close from projected lines and returns the aligned raw body for JSDoc evidence. |
82
- | `memberMethods` | function | `(lines: readonly string[]) => readonly string[]` | Matches callable members on one projection of the body; commented candidates, getters, setters, `static`, and `#` privates never count. |
83
- | `extractExampleLines` | function | `(lines: readonly SourceLine[]) => readonly SourceLine[]` | The next physical candidate after the authoritative exact `@example` span in a leading chain. |
84
- | `examplesFrom` | function | `(source: string) => readonly string[]` | Matches exported functions against shared eligible genuine JSDoc adjacency and aligned code. |
85
- | `exampleMethods` | function | `(lines: readonly string[]) => readonly string[]` | Matches callable members against the same shared eligible genuine JSDoc adjacency and aligned code. |
86
- | `sectionBlocks` | function | `(document: MarkdownDocument, heading: string) => readonly BlockNode[]` | The block nodes under a named `##` heading, up to the next `##`-or-higher heading (or the document's end). |
87
- | `extractSurface` | function | `(document: MarkdownDocument) => readonly SurfaceSymbol[]` | Every `## Surface` identifier: each table's rows union every backticked H3 entity heading, deduped by `symbolKey`. |
88
- | `extractMethods` | function | `(document: MarkdownDocument) => readonly MethodGroup[]` | One `MethodGroup` per documented behavioral interface in `## Methods` — an H4 code span sets the interface, the following table lists its methods. |
89
- | `extractLinks` | function | `(document: MarkdownDocument) => readonly string[]` | Every link href in the guide document, including table cells — a full, depth-first AST walk. |
90
- | `extractTests` | function | `(document: MarkdownDocument) => readonly string[]` | The relative test links declared under `## Tests`. |
91
- | `extractFences` | function | `(document: MarkdownDocument) => readonly GuideFence[]` | Every fenced code block anywhere in the guide document, tagged or not — a full AST walk with no language filter. |
92
- | `isExternalLink` | function | `(href: string) => boolean` | Whether a link `href` should be skipped by guides-parity link checks — an external scheme (`EXTERNAL_SCHEMES`) or a bare `#` anchor. |
93
- | `resolveLink` | function | `(file: string, target: string) => string` | Derives a declaring file's directory, including workspace-root files, then delegates to `resolvePath`. |
94
- | `resolvePath` | function | `(directory: string, target: string) => string` | Sole dot-segment reducer; returns `'.'` when no segment remains and preserves every excess leading parent. |
95
- | `firstCode` | function | `(nodes: readonly InlineNode[]) => string \| undefined` | The first code-span value found by descending an inline node list, following into `emphasis`, `link`, and `image` children. |
96
- | `identifierOf` | function | `(code: string) => string` | The identifier prefix of a code-span text — everything before its first `<`, trimmed (strips generic-parameter annotation). |
97
- | `kindIndex` | function | `(table: TableNode) => number \| undefined` | The index of a table's `Kind` column, found by its header text so it survives column reordering. |
98
- | `cellLinks` | function | `(cell: readonly InlineNode[]) => readonly string[]` | The link hrefs found within one table cell's inline content, in walk order. |
99
- | `findUnexampled` | function | `(names: readonly string[], fences: readonly string[], examples: readonly string[]) => readonly string[]` | The names with no fence mention (word boundary) and no `@example` membership — the EX check's core comparison. |
100
- | `findUnlisted` | function | `(fences: readonly GuideFence[], languages: readonly string[]) => readonly GuideFence[]` | The fences whose language the caller did not list, plus every untagged fence — an untagged fence has no language to list. |
101
- | `fenceImports` | function | `(fence: string) => readonly { specifier: string, names: readonly string[] }[]` | Parses a fence's brace `import` statements into per-specifier imported identifier names — the FI check's core comparison. Brace bindings only: a default, namespace, side-effect, or mixed `import Default, { named }` statement is not surfaced. |
115
+ | Name | Kind | Signature | Summary |
116
+ | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
117
+ | `compareMembership` | function | `(spec: string, name: string, documented: readonly string[], declared: readonly string[]) => ParityFinding \| undefined` | Compares documented and declared membership exactly after sorting copies, preserving duplicates. |
118
+ | `identifyDrift` | function | `(spec: string, drift: Drift) => string` | Computes a category-separated identity for a guide finding. |
119
+ | `formatSide` | function | `(value: string \| undefined) => string` | Formats present parity text as a JSON string and absent text as `absent`. |
120
+ | `formatDrift` | function | `(drift: Drift) => string` | Formats a categorized drift with its guide and source sides. |
121
+ | `normalizeDirectories` | function | `(module: GuideModule) => readonly string[]` | Normalizes a module scope to its canonical directory list. `'.'` represents workspace root; empty, trailing, and dot-segment spellings reduce through `resolvePath`, and duplicates are removed in first-seen order. |
122
+ | `computeModuleKey` | function | `(module: GuideModule) => string` | Computes the stable cache key for a `GuideModule`. Directories normalize before joining, so equivalent module spellings share one key. The NUL separator cannot occur in filesystem-backed canonical-segment inventory keys, so no directory boundary can collide with directory text. |
123
+ | `selectModuleKeys` | function | `(files: Readonly<Record<string, string>>, module: GuideModule) => readonly string[]` | Selects the exact opaque file-inventory keys belonging under any canonical `GuideModule` directory, sorted. `'.'` selects canonical root-relative keys without accepting `/`, `./`, or `../` aliases. Every selected exact `index.ts` and every `.test.ts` key is excluded independent of scope order. |
124
+ | `hasCanonicalSegments` | function | `(key: string) => boolean` | Checks whether an opaque inventory key contains only canonical slash-separated segments. Empty, `.` and `..` segments are rejected without rewriting the key; ordinary dotfile segments remain valid. |
125
+ | `computeSymbolKey` | function | `(symbol: SurfaceSymbol) => string` | Computes the bijection key for a surface symbol — `${keyword} ${name}` — so a symbol-set comparison diffs (name, keyword) pairs rather than names alone. |
126
+ | `findMissing` | function | `(names: readonly string[], source: readonly string[]) => readonly string[]` | Finds the names present in `names` but absent from `source` — the set-difference behind a both-directions bijection assertion. |
127
+ | `findMissingSymbols` | function | `(symbols: readonly SurfaceSymbol[], source: readonly SurfaceSymbol[]) => readonly string[]` | Finds the symbol-key set-difference between two symbol lists — `symbols` present but absent from `source`, compared by `computeSymbolKey` so a symbol can drift in neither name nor keyword. |
128
+ | `extractSourceLines` | function | `(source: string) => readonly SourceLine[]` | Extracts aligned physical source-line records in one character traversal. Real line and block comments and complete template tokens become spaces in `SourceLine.code`, while ordinary code, quoted strings, and recognized regex literals retain their columns. Genuine JSDoc opened from reflection code is retained span by span at its exact physical column in `SourceLine.jsdoc`; faux openers in comments and templates are excluded. Membership remains each consumer's separate anchored grammar. |
129
+ | `extractExports` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope exports declared in one file's source text — the declaration keys `collectKeys` reports, each split back into the keyword and name that built it, deduped by (keyword, name). |
130
+ | `extractHidden` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope declarations lacking the `export` keyword in one file's source text — the mirror image of `extractExports`'s grammar, anchored the same way (column 0, so an indented inner declaration never matches). |
131
+ | `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins the declaration head starting at `start` into one space-separated line, consuming lines until the first that ends with `{`. |
132
+ | `escapeRegExp` | function | `(value: string) => string` | Escapes every regex metacharacter in a literal string so it reads as text inside a larger `RegExp` source rather than as syntax. |
133
+ | `collectDeclarations` | function | `(source: string) => ReadonlyMap<string, Declaration>` | Collects every `export class` / `export interface` declaration one file's source text declares, each keyed `${keyword} ${name}` and carrying the body lines and the base identifiers read from its own head, so a body and a heritage clause always come from the same declaration. |
134
+ | `extractDeclaration` | function | `(source: string, keyword: DeclarationKeyword, name: string) => Declaration \| undefined` | Locates the named `export class` / `export interface` declaration in one file's source text and returns its body lines and its base identifiers read from that one head, so a body and a heritage clause always come from the same declaration, or `undefined` when the file declares no such head. |
135
+ | `extractMemberMethods` | function | `(lines: readonly string[]) => readonly MethodEntry[]` | Selects the member lines declaring a callable member: plain, `async`, generator (`*`), and optional (`records?(`) methods all count; getters, setters, `static` members, and `#` privates never do (their keyword or `#` breaks the `name(` shape). The grammar is `collectKeys`'s, read once over `extractBodyLines`'s projection of the body, so commented method-like payload never becomes eligible and each member keys to the owner head that projection supplies. Each member carries its own doc block's description paragraph, read through `collectSummaries`; the first declaration of a name answers for it. |
136
+ | `extractSourceComments` | function | `(lines: readonly SourceLine[]) => readonly SourceComment[]` | Extracts every eligible genuine JSDoc block paired with the physical record it documents. |
137
+ | `normalizeComment` | function | `(comment: string) => string` | Returns the canonical body of one genuine JSDoc span — the `/**` opener, the closing marker, each line's continuation marker, and the block's leading indentation removed, with per-line trailing whitespace trimmed and the surrounding blank lines dropped. A line's own indentation beyond the marker is kept, so an `@example` fence body keeps the shape it was written in. |
138
+ | `unwrapComment` | function | `(comment: string) => readonly string[]` | Unwraps one genuine JSDoc span into one content line per physical line — the `/**` opener, the closing marker, each line's continuation marker, and per-line trailing whitespace removed, with a line's own indentation past the marker kept. The result is aligned with `comment.split('\n')`, so an index found in it addresses the same physical line of the span it was built from, which is what lets a rewrite keep every line it does not replace. `normalizeComment` is this projection joined and trimmed at its ends. |
139
+ | `buildComment` | function | `(lines: readonly string[], indent: string) => string` | Builds one genuine JSDoc span from its content lines — the inverse of `unwrapComment`. Each line is emitted at `indent` behind a continuation marker, an empty line as the bare marker so no line carries trailing whitespace, and the leading and trailing empty lines are dropped because the opener and the closer take those physical lines. |
140
+ | `wrapText` | function | `(text: string, width: number) => readonly string[]` | Wraps one paragraph into greedy lines no longer than `width` characters. Every run of whitespace separates words, and a word longer than `width` takes its own line rather than being split, so a long code token or URL survives the wrap intact. |
141
+ | `normalizeSummary` | function | `(text: string) => string` | Returns the canonical compared form of a description paragraph. `{@link Target}` and `{@link Target \| label}` become the code token of the target text, or of the label where one is written; a target's module part — the inline import form `import('./module.js').` and TSDoc's package-qualified form `@scope/pkg#` — is dropped first. Every run of whitespace, including a collapsed continuation marker and a line break, becomes one space, the ends trim, and a code span keeps its delimiters while its own boundary whitespace goes. The guide's side and the source's side read through this one form. |
142
+ | `maskFences` | function | `(text: string) => string` | Returns one doc block's unwrapped text with every fenced body replaced by aligned spaces, so a tag search reads the block's structure and never its example code. A line opening with three or more backticks or tildes opens a body, the first line opening with a run of the same character at least as long closes it, an unclosed body runs to the end, and the marker lines themselves stay. The projection preserves every line and every column, so an index found in it addresses the same character of the text it was built from. |
143
+ | `collectSummaries` | function | `(lines: readonly SourceLine[]) => ReadonlyMap<SourceLine, string>` | Collects the description paragraph of every documented physical record — a doc block's text before its first block tag, in `normalizeSummary`'s compared form — keyed by the record it documents. A record whose block carries no description contributes no entry, so an absent summary stays absent rather than becoming an empty string. |
144
+ | `extractBodyLines` | function | `(lines: readonly string[]) => readonly SourceLine[]` | Extracts the aligned physical records of a declaration's body, read inside an owner head this function supplies, so a callable member in the body carries the `Owner.member` key `collectKeys` reports for it, and that head's own record opens the projection. A body read on its own carries no head, and the member grammar attaches a member to the head enclosing it. |
145
+ | `collectKeys` | function | `(lines: readonly SourceLine[]) => ReadonlyMap<SourceLine, string>` | Collects the compared key of every physical record a key names — a `computeSymbolKey` symbol key for a column-zero `export` declaration head, an `Owner.member` key for a one-tab callable member inside one — keyed by the record itself. The owner closes at the first column-zero `}` or at a column-zero `export` declaration carrying another keyword, and a record no key names contributes no entry. |
146
+ | `collectExamples` | function | `(comment: string, name: string) => readonly SourceExample[]` | Collects the `@example` blocks one doc block's unwrapped text carries, each named for the declaration or member the block documents. The text after the tag becomes the block's `title`; a body opening with a fence contributes that fence's language and its verbatim body, and a body with no fence contributes its own trimmed text as the code. |
147
+ | `extractExampleLines` | function | `(lines: readonly SourceLine[]) => readonly SourceLine[]` | Selects the next physical record after an eligible genuine JSDoc whose final authoritative span carries an `@example` tag opening a line at its first non-blank column — the `extractSourceComments` walk filtered to the blocks that carry one. Title text is allowed, and `maskFences` keeps a fenced body's own lines out of the search. |
148
+ | `extractExamples` | function | `(source: string) => readonly SourceExample[]` | Extracts the `@example` blocks carried by the exported declaration heads in one file's source text, each named for the declaration its block documents. Shared adjacency comes from `extractSourceComments` and each block is read by `collectExamples`; head membership is the `collectKeys` key of the documented record under every keyword that grammar admits at column zero, so comment and template payload cannot qualify and the head grammar stays the one every reader here shares. A member key carries a dot and a head key does not, so a member's block belongs to `extractExampleMethods` instead. A head carrying several blocks contributes each. |
149
+ | `extractExampleMethods` | function | `(lines: readonly string[]) => readonly SourceExample[]` | Extracts the `@example` blocks carried by the callable members of a declaration body (per `collectKeys`' grammar, the same one `extractMemberMethods` reads), each named for the member its block documents. Shared adjacency comes from `extractSourceComments` and each block is read by `collectExamples`; member membership is the key `extractBodyLines`'s projection gives the documented record. |
150
+ | `selectSectionBlocks` | function | `(document: MarkdownDocument, heading: string) => readonly BlockNode[]` | Selects the block nodes under the named `##` heading, up to the next `##`-or-higher heading (or the document's end) — the section-scoping window `extractSurface` / `extractMethods` walk over. |
151
+ | `extractTagline` | function | `(document: MarkdownDocument) => string \| undefined` | Extracts the guide's tagline — the text of the blockquote following the document's H1, with every code span kept as a code span and whitespace collapsed. A heading before the blockquote ends the window, so a blockquote elsewhere in the document is not the tagline. |
152
+ | `extractSurface` | function | `(document: MarkdownDocument) => readonly SurfaceSymbol[]` | Extracts every `## Surface` identifier the guide documents — each table row's column 0 code span (the name) paired with its `Kind` column (located by header text), unioned with every H3 entity heading whose trimmed compared inline content is exactly its backticked code-span name, deduped by `computeSymbolKey`. A row with no code-span name has no name to key a symbol on, so this reader skips it and `extractUnnamed` reports it; a row with an unrecognized `Kind` text is skipped. A row also carries its `SUMMARY` column's compared text when the table has that column; a table without it leaves every row's summary absent, which `findDrift` reports. |
153
+ | `extractMethods` | function | `(document: MarkdownDocument) => readonly MethodGroup[]` | Extracts one `MethodGroup` per documented behavioral interface in `## Methods` — an H4 with a code span sets the current interface, and the table immediately following becomes its documented methods. A row with no code-span name has no name to key a member on, so this reader skips it and `extractUnnamed` reports it. Each row carries its `SUMMARY` column's compared text when the table has that column. |
154
+ | `collectGroups` | function | `(document: MarkdownDocument) => ReadonlyMap<TableNode, string>` | Collects each `## Methods` table keyed to the interface its `####` heading names — an H4 carrying a code span sets the current interface and the table immediately following claims it, so a heading with no table and a table with no heading before it contribute nothing. The map iterates in document order and a node keys itself, so a repeated identical table keeps its own entry. |
155
+ | `extractUnnamed` | function | `(document: MarkdownDocument) => readonly string[]` | Extracts every `## Surface` or `## Methods` table row whose first cell carries no code span. `extractSurface` and `extractMethods` skip such a row, because a row with no name gives them nothing to key a symbol or a member on, and this projection is what reports the skip. Each entry is the row's cells read through `extractCellText` and joined by `\|`, so a reader can locate the row in the guide; the `## Surface` rows come first, then the `## Methods` rows, each in document order. `GuideInterface.unnamed` caches it. |
156
+ | `extractLinks` | function | `(document: MarkdownDocument) => readonly string[]` | Extracts every link href in the guide document, including table cells — a full, depth-first walk of the whole AST. |
157
+ | `extractTests` | function | `(document: MarkdownDocument) => readonly string[]` | Extracts the relative test links declared under `## Tests` — every link href found within that section only. |
158
+ | `extractFences` | function | `(document: MarkdownDocument) => readonly GuideFence[]` | Extracts every fenced code block anywhere in the guide document. A full AST walk includes fences nested inside blockquotes and lists, and the same walk carries each fence's nearest preceding heading as its `title` — the key an `@example` block pairs on. A heading's text is flattened, so a title written with a code span pairs with a plain `@example` title. |
159
+ | `collectFences` | function | `(document: MarkdownDocument) => ReadonlyMap<CodeBlockNode, GuideFence>` | Collects each fenced code block keyed by its own node, paired with the `GuideFence` `extractFences` reports for it — the node-addressed form a rewrite needs, so a caller that located a fence by title can read that node's source region back from the parser. The map iterates in document order and a node keys itself, so a repeated identical fence keeps its own entry. |
160
+ | `isExternalLink` | function | `(href: string) => boolean` | Checks whether a guides-parity link check skips a link `href` — an external scheme (`EXTERNAL_SCHEMES`) or a bare in-document `#` anchor. |
161
+ | `resolveLink` | function | `(file: string, target: string) => string` | Resolves a relative `target` from the directory containing a root-relative declaring `file`. A slashless file belongs to the workspace root; path reduction is delegated to `resolvePath`. |
162
+ | `resolvePath` | function | `(directory: string, target: string) => string` | Resolves a relative `target` from a root-relative `directory`, normalizing forward-slash dot segments without filesystem or extension inference. A parent pops only a retained real component; every excess leading parent is preserved. |
163
+ | `findFirstCode` | function | `(nodes: readonly InlineNode[]) => string \| undefined` | Finds the first code-span value by descending an inline node list, following into `emphasis` / `link` / `image` children — the extraction rule behind a Surface or Methods table row's first-column identifier. |
164
+ | `normalizeIdentifier` | function | `(code: string) => string` | Returns the identifier prefix of a code-span text — everything before its first `<`, trimmed. Guide cells and headings may annotate a generic-parameterized name (`MarkdownHandler<TNode, T>`) for readability, but the bijection key is the bare identifier the source scanner captures, so both sides must normalize the same way. |
165
+ | `findColumnIndex` | function | `(table: TableNode, header: string) => number \| undefined` | Finds the index of the column whose header text is `header` so a table's columns survive reordering. The match is exact and case-sensitive — a table without that exact header contributes nothing to the projection reading it, so a `## Surface` table with no `SUMMARY` column leaves every row's summary absent and `findDrift` reports each of those rows, never agreement. |
166
+ | `extractRowSymbol` | function | `(table: TableNode, row: number) => SurfaceSymbol \| undefined` | Extracts one `## Surface` table row's symbol — its column 0 code span as the name, its `KIND` column as the keyword, and its `SUMMARY` column as the compared summary when the table has that column. A row with no code-span name and a row whose `Kind` text is no `ExportKeyword` have no symbol to key, so each returns `undefined`. |
167
+ | `extractRowEntry` | function | `(table: TableNode, row: number) => MethodEntry \| undefined` | Extracts one `## Methods` table row's entry — its column 0 code span as the name and its `SUMMARY` column as the compared summary when the table has that column. A row with no code-span name has no member to key, so it returns `undefined`. |
168
+ | `extractRowSummary` | function | `(table: TableNode, row: number) => string \| undefined` | Extracts one row's compared summary — its `SUMMARY` column read through `extractCellText` and `normalizeSummary`. A table with no such column and a row whose cell is empty each carry no summary and return `undefined` rather than an empty string, so `findDrift` reports the absence. |
169
+ | `extractCellText` | function | `(cell: readonly InlineNode[]) => string` | Extracts compared inline content — the text of a table cell or candidate entity heading flattened with every code span kept as a code span, so \`\` \`Widget\` \`\` reads the same wherever the parity reader compares it. Emphasis drops to its text, a link drops to its text, an image drops to its alternative text, and the markdown parser has already unescaped \`\\\|\`. |
170
+ | `buildCell` | function | `(text: string) => readonly InlineNode[]` | Builds one table cell's inline content from its compared text — the inverse of `extractCellText`. A single-backtick run whose text carries no inner backtick and neither a leading nor a trailing space becomes a code span; every other character, a backtick included, becomes literal text, which `renderMarkdown` escapes so the rendered cell parses back to the text this function was given. |
171
+ | `extractCellLinks` | function | `(cell: readonly InlineNode[]) => readonly string[]` | Extracts the link hrefs within one table cell's inline content, in walk order. |
172
+ | `findUnexampled` | function | `(names: readonly string[], fences: readonly string[], examples: readonly string[]) => readonly string[]` | Finds the names in `names` that have no example — a fence containing the name at a word boundary in `fences`, or a membership in `examples`, both count as "has an example"; presence-only, fence and JSDoc content are never checked. |
173
+ | `findUnlisted` | function | `(fences: readonly GuideFence[], languages: readonly string[]) => readonly GuideFence[]` | Finds the fences whose language is absent from the caller's listed languages. Untagged fences are always returned because they have no language to list. |
174
+ | `extractFenceImports` | function | `(fence: string) => readonly FenceImport[]` | Parses a fence's brace `import` statements into per-specifier imported identifier names — `import type`, mixed multiline braces, and `x as y` aliases all count, each alias resolved to the exported name `x` because that is the name the checked barrel surface must hold. Brace bindings only: a default, namespace, side-effect, or mixed `import Default, { named }` statement is not surfaced. |
175
+ | `collectTitles` | function | `(guide: GuideInterface, source: SourceInterface) => ReadonlyMap<string, SourceExample>` | Collects the titled `@example` blocks a guide's documented surface reaches — the module's exported declaration heads, plus the own members of every documented `class` and `interface` — keyed by title, the first block of a title answering for it. |
176
+ | `computeDrift` | function | `(key: string, category: DriftCategory, guide: string \| undefined, source: string \| undefined) => Drift \| undefined` | Computes the drift between one compared key's guide text and source text. A pair agrees only when both sides carry the same text, and every other state is a drift: guide text alone reports the guide's side, source text alone reports the source's, and neither side carrying text reports the key by itself. A side carrying no text is absent from the result, so a documented symbol with no doc block reports as a drift naming the guide's text alone, and a row a table has no `Summary` column for against a declaration with no doc block reports as `{ key }`. |
177
+ | `findDrift` | function | `(guide: GuideInterface, source: SourceInterface) => readonly Drift[]` | Finds every disagreement between a guide and the source it documents, naming both sites: each `## Surface` row against its declaration's description paragraph, each `## Methods` row against its member's, and the first titled guide fence of a heading against the `@example` block of the same title. An example's compared text is its language on the first line and its body beneath, so a fence that names another language drifts on its own. |
178
+ | `renderSurface` | function | `(symbols: readonly SurfaceSymbol[]) => string` | Renders a `## Surface` table from the symbols a source declares — a `Name`, `KIND`, and `SUMMARY` table with one row per symbol, the name as a code span, the keyword as its text, and the summary through `buildCell`. A symbol carrying no summary renders an empty cell, which `extractSurface` reads back as an absent summary. |
179
+ | `renderMethods` | function | `(group: MethodGroup) => string` | Renders one `## Methods` group — the `####` heading naming the interface as a code span, then a `Name` and `SUMMARY` table with one row per documented member. A member carrying no summary renders an empty cell, which `extractMethods` reads back as an absent summary. |
180
+ | `renderExample` | function | `(example: SourceExample) => string` | Renders one `@example` block as the guide fence it pairs with — an H3 heading carrying the block's title, then a fence carrying its language and its code. An untitled block renders the fence alone, because a fence pairs on its nearest preceding heading and an untitled block claims none. |
181
+ | `buildTable` | function | `(table: TableNode, row: number, column: number, text: string) => TableNode` | Builds a copy of `table` with one cell's inline content rebuilt from `text` through `buildCell`. Every other cell keeps its own nodes, so a rewrite touches the one cell it names and the header and the alignment row travel unchanged. |
182
+ | `buildFence` | function | `(example: SourceExample) => CodeBlockNode` | Builds the fenced code block one `@example` block renders as — its code inside a fence carrying its language, and an untagged fence when the block names none. |
183
+ | `spliceSpan` | function | `(source: string, span: MarkdownSpan, replacement: string) => string` | Splices `replacement` into `source` over the region `span` addresses, and returns the result. The text before the region and the text after it travel byte for byte, so a rewrite that addresses one node's region changes nothing else in the document. |
184
+ | `replaceCell` | function | `(guide: string, key: string, summary: string) => string \| undefined` | Replaces one compared cell in a guide's text and returns the whole guide back. `key` names the row the way `findDrift` names it — a `computeSymbolKey` key for a `## Surface` row, an `Owner.member` key for a `## Methods` row — and the row's `SUMMARY` cell becomes `summary`. Only the table's own source region is rewritten, so every byte outside it travels unchanged; a key reaching no cell returns `undefined`, and a row already carrying the summary returns the guide byte for byte. |
185
+ | `replaceFence` | function | `(guide: string, title: string, example: SourceExample) => string \| undefined` | Replaces one titled fence in a guide's text and returns the whole guide back. The first fence carrying `title` — the pairing `findDrift` compares on — takes `example`'s language and code. Only the fence's own source region is rewritten, so every byte outside it travels unchanged; a title no fence carries returns `undefined`, and a fence already carrying that body returns the guide byte for byte. |
186
+ | `replaceSummary` | function | `(comment: string, summary: string, width?: number) => string \| undefined` | Replaces one doc block's description paragraph with `summary` and returns the whole block back. The paragraph is the block's text before its first block tag, and it re-wraps inside `width`; the blank line before the first tag, every tag line, the block's indentation, and its continuation markers all survive. A block already carrying the summary returns byte for byte, and a text that is no doc block and a summary carrying no word each return `undefined`. |
187
+ | `replaceExample` | function | `(comment: string, example: SourceExample) => string \| undefined` | Replaces the body of one titled `@example` tag in a doc block's raw text and returns the whole block back. The tag carrying `example`'s title takes a fence of its language and its code; every other tag, the description paragraph, the block's indentation, and its continuation markers all survive. A text that is no doc block, a title no tag carries, and a language or code the emitted three-backtick fence cannot enclose or the doc block cannot hold each return `undefined`. |
188
+ | `locateComment` | function | `(text: string, key: string) => MarkdownSpan \| undefined` | Locates the doc block a compared key attaches to inside one file's text and returns the block's own character region, so a caller can `slice` the block, rewrite it through `replaceSummary` or `replaceExample`, and write the result back through `spliceSpan`. `key` names the pair the way `findDrift` names it — a `computeSymbolKey` key for a declaration, an `Owner.member` key for an interface or class member; the region covers the block's own indentation, and no block carrying the key returns `undefined`. |
189
+
190
+ The server command leaves come from [`helpers.ts`](../src/server/helpers.ts).
191
+
192
+ | Name | Kind | Signature | Summary |
193
+ | -------------------- | -------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
194
+ | `formatGuideFinding` | function | `(finding: ParityFinding) => string` | Formats one finding with its guide prefix when needed. |
195
+ | `matchesGuideResult` | function | `(result: unknown) => boolean` | Reads an owned foreign result view and accepts passed modules without unhandled errors. |
196
+ | `resolveGuideRoot` | function | `(root: URL \| string) => string` | Resolves a command root to a native absolute path. |
197
+ | `selectGuidePitch` | function | `(entries: readonly ManifestEntry[], name: string \| undefined) => string \| undefined` | Selects the indexed guide matching a package's bare name. |
102
198
 
103
199
  ### Parsers
104
200
 
105
201
  The manifest coercer, from [`parsers.ts`](../src/core/parsers.ts) — the one scanner that turns
106
202
  markdown text into typed values, composed out of `helpers.ts`'s leaves.
107
203
 
108
- | Name | Kind | Signature | Behavior |
109
- | --------------- | -------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
110
- | `parseManifest` | function | `(markdown: string, directory: string) => readonly ManifestEntry[]` | Resolves manifest links and canonicalizes Source values through `normalizeDirectories`, preserving one-versus-many shape. |
204
+ | Name | Kind | Signature | Summary |
205
+ | --------------- | -------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
206
+ | `parseManifest` | function | `(markdown: string, directory: string) => readonly ManifestEntry[]` | Parses a `## By concept` manifest table into its `ManifestEntry` rows — each row's Concept cell (flattened text), Spec / Tests cells (a single link href, resolved against `directory`), and Source cell (every link href, resolved against `directory`; Source links canonicalize through `normalizeDirectories`, one directory collapses to a `string`, and several become a `readonly string[]`). A row missing a concept, spec link, tests link, or source link is skipped as malformed. |
207
+
208
+ The server argument and package-manifest parsers come from
209
+ [`parsers.ts`](../src/server/parsers.ts).
210
+
211
+ | Name | Kind | Signature | Summary |
212
+ | --------------------- | -------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
213
+ | `parseGuideDirection` | function | `(args: readonly string[]) => ParityDirection \| undefined` | Maps the supported `--to guide` and `--to source` arguments to an explicit rewrite destination. |
214
+ | `parsePackageName` | function | `(manifest: string) => string \| undefined` | Reads the bare package name from package manifest JSON. |
111
215
 
112
216
  ### Shapers
113
217
 
114
218
  Declarative `ContractShape` values (from `@orkestrel/contract`) from
115
- [`shapers.ts`](../src/core/shapers.ts) — every documented data type here is
116
- non-recursive, so each shapes directly.
117
-
118
- | Name | Kind | Builds |
119
- | -------------------- | ----- | ----------------------------------------------------------------------------------- |
120
- | `surfaceSymbolShape` | const | The shape of a `SurfaceSymbol` — `{ name: string, kind: ExportKind }`. |
121
- | `methodGroupShape` | const | The shape of a `MethodGroup` — `{ interface: string, methods: readonly string[] }`. |
122
- | `manifestEntryShape` | const | The shape of a `ManifestEntry` — `source` accepting a single directory or several. |
219
+ [`shapers.ts`](../src/core/shapers.ts) — every documented data type here is non-recursive, so
220
+ each shapes directly. A `Shape` cell lists the shaped object's properties with their types.
221
+
222
+ | Name | Kind | Shape | Summary |
223
+ | -------------------- | ----- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | `surfaceSymbolShape` | const | `{ name: string, keyword: ExportKeyword, summary?: string }` | Shapes a `SurfaceSymbol` — a documented / exported symbol's `name` paired with its `ExportKeyword` and its optional compared `summary`. |
225
+ | `methodGroupShape` | const | `{ interface: string, methods: readonly MethodEntry[] }` | Shapes a `MethodGroup` — a backticked `interface` name paired with its documented `methods`. |
226
+ | `methodEntryShape` | const | `{ name: string, summary?: string }` | Shapes a `MethodEntry` — one documented method's `name` paired with its optional compared `summary`. |
227
+ | `sourceExampleShape` | const | `{ name: string, title?: string, code: string, language?: string }` | Shapes a `SourceExample` — one `@example` block's `name`, its optional pairing `title`, its `code`, and its optional fence `language`. |
228
+ | `driftShape` | const | `{ key: string, category: DriftCategory, guide?: string, source?: string }` | Shapes a `Drift` — one disagreement's compared `key` with the optional text each side carries there. |
229
+ | `manifestEntryShape` | const | `{ concept: string, spec: string, source: GuideModule, tests: string }` | Shapes a `ManifestEntry` — one `## By concept` manifest row, `source` accepting either a single directory or several. |
123
230
 
124
231
  ### Validators
125
232
 
126
233
  Total from-unknown guards composed from `@orkestrel/contract` combinators, from
127
234
  [`validators.ts`](../src/core/validators.ts).
128
235
 
129
- | Name | Kind | Narrows to / Tests | Behavior |
130
- | ----------------- | ----- | ------------------ | ------------------------------------------------------------------------ |
131
- | `isExportKind` | const | `value: unknown` | `true` when `value` is one of the five documented `ExportKind` literals. |
132
- | `isSurfaceSymbol` | const | `value: unknown` | `true` when `value` is a well-formed `SurfaceSymbol`. |
133
- | `isMethodGroup` | const | `value: unknown` | `true` when `value` is a well-formed `MethodGroup`. |
134
- | `isManifestEntry` | const | `value: unknown` | `true` when `value` is a well-formed `ManifestEntry`. |
236
+ | Name | Kind | Summary |
237
+ | ----------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
238
+ | `isExportKeyword` | const | Checks whether `value` is one of the documented `ExportKeyword` literals — the guard behind extracting a Surface table's `Kind` cell into a typed symbol. |
239
+ | `isDriftCategory` | const | Checks whether `value` names a supported drift category. |
240
+ | `isSurfaceSymbol` | const | Checks whether `value` is a well-formed `SurfaceSymbol` — a `name` string paired with a valid `ExportKeyword`, and an optional `summary`. |
241
+ | `isMethodGroup` | const | Checks whether `value` is a well-formed `MethodGroup` — a backticked `interface` name paired with its documented `MethodEntry` rows. |
242
+ | `isMethodEntry` | const | Checks whether `value` is a well-formed `MethodEntry` — a `name` string with an optional `summary`. |
243
+ | `isSourceExample` | const | Checks whether `value` is a well-formed `SourceExample` — a `name` and `code` string with an optional `title` and `language`. |
244
+ | `isDrift` | const | Checks whether `value` is a well-formed `Drift` — a compared `key` with an optional `guide` and `source` text. |
245
+ | `isManifestEntry` | const | Checks whether `value` is a well-formed `ManifestEntry` — a `## By concept` manifest row, its `source` accepting either a single directory or several. |
135
246
 
136
247
  ### Factories
137
248
 
138
249
  From [`factories.ts`](../src/core/factories.ts).
139
250
 
140
- | Name | Kind | Signature | Behavior |
141
- | ----------------------------- | -------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
142
- | `createGuide` | function | `(source: string) => GuideInterface` | Creates a structured `GuideInterface` view over one guide's markdown source. |
143
- | `createSource` | function | `(options: SourceOptions) => SourceInterface` | Creates a pure `SourceInterface` over a consumer-supplied file inventory. |
144
- | `createSourceManager` | function | `(options: SourceManagerOptions) => SourceManagerInterface` | Creates a `SourceManagerInterface` over a specifier-to-module policy, sharing one `Source` per module. |
145
- | `createSurfaceSymbolContract` | function | `() => ContractInterface<SurfaceSymbol>` | Compiles `surfaceSymbolShape` into a guard / parser / schema / generator bundle. |
146
- | `createMethodGroupContract` | function | `() => ContractInterface<MethodGroup>` | Compiles `methodGroupShape` into a guard / parser / schema / generator bundle. |
147
- | `createManifestEntryContract` | function | `() => ContractInterface<ManifestEntry>` | Compiles `manifestEntryShape` into a guard / parser / schema / generator bundle. |
251
+ | Name | Kind | Signature | Summary |
252
+ | ----------------------------- | -------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
253
+ | `createGuide` | function | `(source: string) => GuideInterface` | Creates a structured `GuideInterface` view over one guide's markdown source — parses once and caches its `sections` / `surface` / `methods` / `links` / `tests` / `fences` projections. |
254
+ | `createSource` | function | `(options: SourceOptions) => SourceInterface` | Creates a pure `SourceInterface` over a consumer-supplied file inventory — see `Source`. |
255
+ | `createSourceManager` | function | `(options: SourceManagerOptions) => SourceManagerInterface` | Creates a `SourceManagerInterface` that resolves the consumer's local import specifiers and shares one source view per module. |
256
+ | `createSurfaceSymbolContract` | function | `() => ContractInterface<SurfaceSymbol>` | Compiles the `surfaceSymbolShape` into a `ContractInterface` for `SurfaceSymbol` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
257
+ | `createMethodGroupContract` | function | `() => ContractInterface<MethodGroup>` | Compiles the `methodGroupShape` into a `ContractInterface` for `MethodGroup` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
258
+ | `createMethodEntryContract` | function | `() => ContractInterface<MethodEntry>` | Compiles the `methodEntryShape` into a `ContractInterface` for `MethodEntry` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
259
+ | `createSourceExampleContract` | function | `() => ContractInterface<SourceExample>` | Compiles the `sourceExampleShape` into a `ContractInterface` for `SourceExample` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
260
+ | `createDriftContract` | function | `() => ContractInterface<Drift>` | Compiles the `driftShape` into a `ContractInterface` for `Drift` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
261
+ | `createManifestEntryContract` | function | `() => ContractInterface<ManifestEntry>` | Compiles the `manifestEntryShape` into a `ContractInterface` for `ManifestEntry` — a guard, coercing parser, JSON Schema, and seeded generator from one shape declaration (AGENTS.md § Design laws). |
262
+
263
+ ### Classes
264
+
265
+ The implementing classes, from [`Guide.ts`](../src/core/Guide.ts),
266
+ [`Parity.ts`](../src/core/Parity.ts), [`GuideCommand.ts`](../src/server/GuideCommand.ts),
267
+ [`Source.ts`](../src/core/sources/Source.ts), and
268
+ [`SourceManager.ts`](../src/core/sources/SourceManager.ts) — each documented in full under its own
269
+ heading following this table.
270
+
271
+ | Name | Kind | Summary |
272
+ | --------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
273
+ | `Guide` | class | Presents a pure, structured view over one parsed guide — the documented projections (`sections` / `tagline` / `surface` / `methods` / `unnamed` / `links` / `tests` / `fences`) are extracted once at construction and cached. |
274
+ | `Parity` | class | Composes generic guide-parity inspection and explicit in-memory rewrites over a caller inventory. |
275
+ | `GuideCommand` | class | Drives native checking and explicit rewrites or registers package assertions in the guides worker. |
276
+ | `Source` | class | Reflects, as a pure `SourceInterface`, a module scope's intentional direct declarations, conventional barrel-reachable surface, and member methods over a consumer-supplied file inventory, using text-only line scanners rather than the TypeScript compiler API or the filesystem. `Source` never touches disk: the consumer gathers the inventory however their environment allows (`node:fs` in a Node script, `import.meta.glob` in a browser/vitest run) and passes it in as `files`. |
277
+ | `SourceManager` | class | Resolves a consumer-owned import-specifier policy into pure source views and caches those views by module, so aliases for one module share one entity. Unmapped specifiers remain foreign to the consumer and return `undefined`. `sources()` enumerates the same shared views, one per distinct module the policy maps. |
148
278
 
149
279
  ### `Guide`
150
280
 
151
281
  The implementing class of `GuideInterface`, from [`Guide.ts`](../src/core/Guide.ts). A
152
- stateful, structured view over one parsed guide: parses `source` once via
153
- `@orkestrel/markdown` and never touches the filesystem — `Guide` has no notion of "where"
154
- the guide came from, only its markdown text. Every accessor returns the same cached,
155
- readonly array on every call. See [`## Methods`](#methods) for its public call-signature
156
- surface.
282
+ pure, structured view over one parsed guide: parses `source` once through
283
+ `@orkestrel/markdown` and never touches the filesystem — `Guide` reads only the markdown
284
+ text it is given and records nothing about where the guide came from. Every accessor
285
+ returns the same cached, readonly array on every call. See [`## Methods`](#methods) for its
286
+ public call-signature surface.
287
+
288
+ ### `Parity`
289
+
290
+ The implementing class of `ParityInterface`, from [`Parity.ts`](../src/core/Parity.ts). It joins
291
+ manifest rows to cached `Guide` and `Source` views, reports generic parity findings by subject, and
292
+ returns explicit guide- or source-directed changes without touching disk or mutating the caller's
293
+ inventory. It uses the existing parsed fence and source-example records, Markdown-backed targeted
294
+ guide replacers, and source-comment spans for accumulated writes.
295
+
296
+ ### `GuideCommand`
297
+
298
+ The implementing class of `GuideCommandInterface`, from
299
+ [`GuideCommand.ts`](../src/server/GuideCommand.ts). In a guides worker, `execute` reads fresh
300
+ workspace bytes and supplies the resolved root, owned file inventory, joined parity rows, and
301
+ generic parity result to the package callback. Outside a guides worker, it accepts no arguments
302
+ for a no-write check, `--to guide` to update guide summaries and examples from source, or
303
+ `--to source` to update source doc blocks from the guide. A rewrite rereads the workspace before
304
+ checking unresolved findings and then runs the real guides Vitest project. Unsupported arguments
305
+ print `GUIDE_USAGE`; no-write and rewrite failures preserve a higher existing process exit code.
306
+
307
+ The `reader` port may throw while gathering the initial or fresh inventory. The `runner` port may
308
+ throw while creating the foreign runner or starting its guides project. Guide validates the
309
+ foreign runner's callable `start` and `close` members at arrival, keeps their receiver, owns the
310
+ result collections it reads, and calls a validated callable `close` in `finally`; it cannot clean
311
+ up a value that supplies no callable `close`. Native reader, runner creation, start, validation,
312
+ and cleanup exceptions are written to stderr, raise the process exit status, and leave `execute`
313
+ fulfilled after native handling. A failed or malformed runner result raises the exit status.
314
+ Worker inventory and registration failures escape and reject `execute`.
157
315
 
158
316
  ### `Source`
159
317
 
160
318
  The implementing class of `SourceInterface`, from [`Source.ts`](../src/core/sources/Source.ts). A
161
319
  pure reflection over a consumer-supplied file inventory (root-relative path → file text) plus a
162
- module scope. `exports()` inventories direct `type`, `interface`, `const`, `function`, and
163
- `class` declarations in the selected canonical directories' exact opaque module keys over
164
- comment/template-excluded projected code lines; `enum` is outside this reflection population without being forbidden by
165
- general package policy;
166
- `surface()` inventories declarations reachable through each selected directory's conventional
167
- root barrel. Both projections are computed on first access, cached, deduplicated by name and
168
- kind, and sorted by name. Member structure comes from projected lines while raw bodies preserve
169
- JSDoc evidence. `Source` never uses the
170
- TypeScript compiler API or filesystem; the consumer gathers `files` however its environment
320
+ module scope. `exports()` inventories direct `type`, `interface`, `const`, `function`, and `class`
321
+ declarations in the selected canonical directories' exact opaque module keys over
322
+ comment/template-excluded projected code lines; `enum` is outside this reflection population without
323
+ being forbidden by general package policy; `surface()` inventories declarations reachable through
324
+ each selected directory's conventional root barrel. Both projections are deduplicated by name and
325
+ keyword, and sorted by name. Member structure comes from projected lines while raw bodies preserve
326
+ JSDoc evidence, and `methods(name)` resolves a declaration's members through its `extends` chain
327
+ within the same module scope, reading the first file that declares the name. Every reading derives
328
+ once per instance from the immutable inventory and is reused — the `exports` and `surface`
329
+ projections on first access, the scope's declaration map on the first `methods` or `examples`
330
+ lookup, then each name's members and each example collection under the name it was asked for — so a
331
+ whole-guide comparison reads each file once rather than once per compared row. `Source` never uses
332
+ the TypeScript compiler API or filesystem; the consumer gathers `files` however its environment
171
333
  allows. See [`## Methods`](#methods) for the public call-signature surface.
172
334
 
173
335
  ### `SourceManager`
@@ -178,43 +340,60 @@ cannot: a guide fence may import from a face of the package this `Source` does n
178
340
  check needs the right `Source` for whichever specifier the fence names. `modules` is the consumer's
179
341
  own policy — it maps each import specifier the package publishes to the source module behind it —
180
342
  and `SourceManager` never infers or normalizes that map. `source(specifier)` returns `undefined` for
181
- an unmapped specifier, which is how a fence-import check skips a foreign import; a separate list of
182
- self-specifiers is therefore no longer needed, because a mapped specifier is local and absence is
183
- the skip signal. One `Source` is cached per module, so two specifiers naming one module share one
184
- entity and the inventory is scanned once. See [`## Methods`](#methods) for its public
185
- call-signature surface.
343
+ an unmapped specifier, and a fence-import check skips the import on that signal; a mapped specifier
344
+ is local. `sources()` enumerates the same views, one per distinct module the policy maps, so a
345
+ check that must sweep every face of the package reads them without repeating the policy. One
346
+ `Source` is cached per module, so two specifiers naming one module share one entity and the
347
+ inventory is scanned once. See [`## Methods`](#methods) for its public call-signature surface.
186
348
 
187
349
  ## Methods
188
350
 
189
351
  The public methods of each behavioral interface — one table per type, keyed by its
190
- backticked name (AGENTS §22).
352
+ backticked name (`.claude/rules/documentation.md` § Parity).
191
353
 
192
354
  #### `GuideInterface`
193
355
 
194
- | Method | Returns | Behavior |
195
- | ---------- | -------------------------- | ------------------------------------------------------------------------------------------- |
196
- | `sections` | `readonly string[]` | The `##` heading names, in document order — the non-vacuousness guard for section presence. |
197
- | `surface` | `readonly SurfaceSymbol[]` | Every `## Surface` identifier + kind — table rows union backticked entity headings. |
198
- | `methods` | `readonly MethodGroup[]` | One `MethodGroup` per documented behavioral interface in `## Methods`. |
199
- | `links` | `readonly string[]` | Every link href in the guide, including table cells. |
200
- | `tests` | `readonly string[]` | The relative test links declared under `## Tests`. |
201
- | `fences` | `readonly GuideFence[]` | Every fenced code block in the whole document, tagged or not — no language filter. |
356
+ | Method | Returns | Summary |
357
+ | ---------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
358
+ | `sections` | `readonly string[]` | Lists the `##` heading names, in document order — the non-vacuousness guard for section presence. |
359
+ | `tagline` | `string \| undefined` | Returns the text of the blockquote following the document's H1 — the guide's tagline, or `undefined` when a heading intervenes first. |
360
+ | `surface` | `readonly SurfaceSymbol[]` | Lists every `## Surface` identifier + keyword — table rows union H3 entity headings whose trimmed compared inline content is exactly their backticked code-span name. Each row carries its `Summary` cell, located by header text, when the table has that column. |
361
+ | `methods` | `readonly MethodGroup[]` | Returns one `MethodGroup` per documented behavioral interface in `## Methods`, each row carrying its `Summary` cell. |
362
+ | `unnamed` | `readonly string[]` | Lists every `## Surface` or `## Methods` row whose first cell carries no code span — the rows `surface` and `methods` skip for want of a name, each entry being the row's cells joined by `\|`. Such a row reaches neither projection, so no bijection check can report it and this one names it instead. The `## Surface` rows come first, then the `## Methods` rows, each in document order. |
363
+ | `links` | `readonly string[]` | Lists every link href in the guide, including table cells. |
364
+ | `tests` | `readonly string[]` | Lists the relative test links declared under `## Tests`. |
365
+ | `fences` | `readonly GuideFence[]` | Lists every fenced code block in the whole document, tagged or not, each carrying its nearest preceding heading as `title` — no language filter, so a consumer decides which languages its checks read. |
366
+
367
+ #### `ParityInterface`
368
+
369
+ | Method | Returns | Summary |
370
+ | ---------- | ---------------------- | --------------------------------------------------------------------------------------------- |
371
+ | `rows` | `readonly ParityRow[]` | Lists the manifest rows whose guide text is present, joined to parsed guide and source views. |
372
+ | `inspect` | `ParityResult` | Inspects the configured inventory and groups generic parity findings. |
373
+ | `document` | `ParityRewriteResult` | Updates guide summaries and titled examples from source authority without touching disk. |
374
+ | `annotate` | `ParityRewriteResult` | Updates source doc-block summaries and examples from guide authority without touching disk. |
375
+
376
+ #### `GuideCommandInterface`
377
+
378
+ | Method | Returns | Summary |
379
+ | --------- | --------------- | ----------------------------------------------------------------------------------------------- |
380
+ | `execute` | `Promise<void>` | Runs the native command or registers package assertions with fresh worker inventory and parity. |
202
381
 
203
382
  #### `SourceInterface`
204
383
 
205
- | Method | Returns | Behavior |
206
- | ---------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
207
- | `exports` | `readonly SurfaceSymbol[]` | What the package **declares** — direct `type`, `interface`, `const`, `function`, and `class` declarations in the selected module keys. |
208
- | `surface` | `readonly SurfaceSymbol[]` | What a consumer can **import** — every declaration reachable through the selected directories' conventional root `index.ts` barrels. |
209
- | `methods` | `readonly string[]` | The call-signature members of the `class` / `interface` named `name`. |
210
- | `exists` | `boolean` | Whether a workspace-root-relative path exists in the inventory. |
211
- | `hidden` | `readonly SurfaceSymbol[]` | Every module-scope declaration LACKING `export` (AGENTS §5). |
212
- | `examples` | `readonly string[]` | The exported functions (or, given `name`, members) whose eligible leading JSDoc chain ends in an exact block-position `@example` span. |
384
+ | Method | Returns | Summary |
385
+ | ---------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
386
+ | `exports` | `readonly SurfaceSymbol[]` | Lists every direct declaration in the selected module keys matching `export (async )?(function*?\|class\|const\|interface\|type) Name`, by (name, keyword). |
387
+ | `surface` | `readonly SurfaceSymbol[]` | Lists every declaration reachable from each selected module's conventional root `index.ts` through complete relative `.js` `export *` rows. Unlike `exports`, this inventories barrel reachability rather than all intentional direct declarations under the selected directories. |
388
+ | `methods` | `readonly MethodEntry[]` | Returns the call-signature members of the `class` / `interface` named `name`, unioned with those of every declaration it extends within the module scope, each with its own doc block's description paragraph. The first file declaring the name answers for it. |
389
+ | `exists` | `boolean` | Checks whether a workspace-root-relative path names a file or a directory present in the inventory. |
390
+ | `hidden` | `readonly SurfaceSymbol[]` | Lists every module-scope declaration lacking the `export` keyword — the export-discipline reflection `.claude/rules/architecture.md` § Barrel exports states — across the same projected physical code lines and declaration keywords as `exports`. Comment/template payload and `enum` are outside this population; projection preserves physical columns but does not widen the uninterrupted column-zero declaration-head grammar. This does not forbid enums by general package policy. Empty on a conforming module. |
391
+ | `examples` | `readonly SourceExample[]` | Lists every `@example` block carried by an exported declaration head — a `type`, `interface`, `const`, `function`, or `class` head at column zero — whose next-physical-record eligible genuine JSDoc chain ends in a span holding an `@example` tag opening a line at its first non-blank column. Each block carries its title, its fence language, and its code; intervening material severs association. A member's block belongs to the `name` overload instead. |
213
392
 
214
393
  #### Which projector a check uses
215
394
 
216
- `exports()` and `surface()` answer different questions, and picking the wrong one is the single
217
- mistake this package sees most often.
395
+ `exports()` and `surface()` answer different questions, and picking the wrong one is the most
396
+ common error in a consumer's parity test.
218
397
 
219
398
  **`surface()` is what a guide is checked against.** A guide documents what a consumer can import,
220
399
  and `surface()` is the barrel-reachable set. Use it for the documented-surface bijection (SB) and
@@ -227,32 +406,36 @@ so they are not part of the package's public surface. Use `exports()` where the
227
406
  what the package declares — the direct-versus-barrel legs of SB, which catch a declaration the
228
407
  barrel never re-exports.
229
408
 
230
- A package reaching for a denylist over `exports()`, or for a second projector built on the
231
- TypeScript compiler, should first check whether `surface()` already answers its question. It
232
- usually does: `surface()` excludes exactly the internal implementation classes such a denylist
233
- enumerates by hand. Measured across the 41 published packages — 552 `ts` fences, 718 import rows —
234
- zero imports would newly fail if every package switched its fence-import check from `exports()` to
235
- `surface()`. The switch costs nothing, so a package carrying machinery to work around `exports()`
236
- is carrying it for a question it never had.
409
+ Check whether `surface()` already answers the question before reaching for a denylist over
410
+ `exports()` or a second projector built on the TypeScript compiler. `surface()` excludes the
411
+ internal implementation classes a denylist would enumerate by hand, so a fence-import check reads
412
+ `surface()`.
237
413
 
238
414
  #### `SourceManagerInterface`
239
415
 
240
- | Method | Returns | Behavior |
241
- | -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
242
- | `source` | `SourceInterface \| undefined` | The shared source view of the module `specifier` names, or `undefined` when the policy does not map it — a foreign import. |
416
+ | Method | Returns | Summary |
417
+ | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
418
+ | `source` | `SourceInterface \| undefined` | Resolves a mapped specifier to the shared source view of the module it names, and returns `undefined` when the policy does not map it — a foreign import. |
419
+ | `sources` | `readonly SourceInterface[]` | Lists one shared source view per distinct module the policy maps, in first-seen specifier order, sharing the same per-module entities `source` returns. |
243
420
 
244
421
  ## The extraction model
245
422
 
246
- `Guide` parses a guide's markdown once (via `@orkestrel/markdown`'s `createMarkdown`) and
247
- caches six projections at construction — `sections`, `surface`, `methods`, `links`, `tests`,
248
- `fences` — so every accessor is a cheap array return, not a re-parse. `extractSurface` scopes to the
249
- `## Surface` section (`sectionBlocks`) and unions two sources of identifiers: every table's
250
- column-0 code span (kind read from the column whose header text is `Kind`, located
251
- positionally so it survives reordering) and every backticked H3 entity heading (a class
252
- documented outside a table, kind fixed to `'class'`). `extractMethods` scopes to
423
+ `Guide` parses a guide's markdown once (through `@orkestrel/markdown`'s `createMarkdown`) and
424
+ caches its projections at construction — `sections`, `tagline`, `surface`, `methods`, `unnamed`,
425
+ `links`, `tests`, `fences` — so every accessor is a cheap array return, not a re-parse.
426
+ `extractSurface` scopes to the `## Surface` section (`selectSectionBlocks`) and unions its sources
427
+ of identifiers: every table's column-0 code span (keyword read from the column whose header text
428
+ is `Kind`, located positionally so it survives reordering) and every H3 entity heading whose
429
+ trimmed compared inline content is exactly its backticked code-span name (a class documented
430
+ outside a table, keyword fixed to `'class'`). Surrounding spaces, emphasis, and links preserve
431
+ that identity. `extractSurface` refuses a heading carrying any other visible text or an additional
432
+ code span. Surface entries retain encounter order and deduplicate by name + keyword: the
433
+ first-seen entry wins, so a genuine entity heading before its matching class row keeps the
434
+ summary-less heading entry, while a table row before that heading keeps its `Summary`; the same
435
+ name under another keyword remains distinct. `extractMethods` scopes to
253
436
  `## Methods`: an H4 whose first code span sets the current interface name, and the very next
254
437
  table becomes that interface's `MethodGroup`. Both extractors normalize every identifier
255
- through `identifierOf`, stripping a generic-parameter annotation (`` `WidgetInterface<T>` ``
438
+ through `normalizeIdentifier`, stripping a generic-parameter annotation (`` `WidgetInterface<T>` ``
256
439
  → `WidgetInterface`) so the bijection key is always the bare name. `extractLinks` walks the
257
440
  whole AST for every `link` node (table cells included); `extractTests` does the same walk
258
441
  scoped to the `## Tests` section only; and `extractFences` walks the whole AST for every
@@ -274,6 +457,76 @@ the directory before the final slash, treats a slashless file as workspace-root,
274
457
  Neither helper consults the filesystem, infers extensions, or guesses whether a dotted component
275
458
  is a file.
276
459
 
460
+ The guide's side and the source's side read into one form. `extractSurface` and `extractMethods` locate
461
+ the compared column by its header text, `Summary`, exactly as they locate `Kind`, and read that cell
462
+ through `extractCellText` and `normalizeSummary`. `extractSourceComments` walks the aligned
463
+ `SourceLine` records once, pairs each eligible genuine JSDoc block with the physical record it
464
+ documents, and every doc-block reader is a projection of that one walk: `collectSummaries` for the
465
+ description paragraph, `collectExamples` for the `@example` blocks, and `extractExampleLines` for the
466
+ records an `@example` documents. `extractTagline` reads the blockquote following the H1, and
467
+ `extractUnnamed` returns the `## Surface` and `## Methods` rows `extractSurface` and
468
+ `extractMethods` skip for want of a code-span name.
469
+
470
+ One transform reads both sides, so its clauses are stated once and each fires wherever its input
471
+ occurs rather than on a side reserved for it:
472
+
473
+ - Every single-backtick code span — one backtick per side, no inner backtick, no adjacent backtick — is located, before any other clause runs.
474
+ - `{@link X}` and `{@link A.b}` become the code token of the target text, outside a located span.
475
+ - A target's module part — the inline import form `import('./module.js').` and TSDoc's package-qualified form `@scope/pkg#`, each a package or path token, one carrying `@` or `/` — drops, so `{@link @scope/pkg#A.b}` becomes the code token of `A.b`, outside a located span.
476
+ - `{@link Owner#member}` and `{@link #member}` travel whole — a `#` that no `@` or `/` precedes is JSDoc's member reference rather than a module part — so a guide cell documents each as written, outside a located span.
477
+ - `{@link X | text}` becomes the code token of `text`, outside a located span.
478
+ - Emphasis — `**text**` and `_text_` — drops to its text.
479
+ - A link, `[text](target)`, drops to `text`.
480
+ - An image, `![text](target)`, drops to its alternative text.
481
+ - `\|` unescapes.
482
+ - Every run of whitespace, a continuation marker and a line break included, collapses to one space.
483
+ - The leading and trailing whitespace trims.
484
+ - A code span stays a code span, and the whitespace at each end of a located span's own content trims; a span whose content is all whitespace keeps one space.
485
+
486
+ Nothing else is transformed. Emphasis, a link, an image, and `\|` are markdown syntax the parser
487
+ resolves, so those clauses reach only a guide cell; `{@link Widget}` is ordinary text, so a guide cell carrying that token
488
+ rewrites to `` `Widget` `` exactly as a doc block's paragraph does, while the same token written
489
+ inside a code span stays literal on both sides. The `extractCellText` function
490
+ reads the markdown nodes and `normalizeSummary` reads the text tokens, and both sides end in
491
+ `normalizeSummary`.
492
+
493
+ The order is what makes the clauses agree. Locating spans first keeps a delimiter the `{@link}`
494
+ expansion inserts from reading as an authored one; trimming a span's boundary last, after the
495
+ whitespace collapse, judges a span wrapped across two physical lines on the characters the parser
496
+ judged. The trim is symmetric because the markdown parser is not: it strips one space from each end
497
+ of a code span only when both ends carry one, so trimming every boundary space on the source side
498
+ is the rule that meets a guide cell however it was written.
499
+
500
+ A code span delimited by more than one backtick sits outside the compared form and travels
501
+ untouched, as does a code span whose own text carries a backtick. The form spells every span with
502
+ one backtick per side, so neither can be written back: a summary carrying either cannot converge,
503
+ and the construct belongs in prose instead.
504
+
505
+ The compared unit is the description paragraph, not its first sentence: on the source side the doc
506
+ block's text from its opening to its first block tag, and on the guide side the `Summary` cell. A
507
+ block tag opens a line whose first non-blank character is `@`, so a tag written past one space after
508
+ the continuation marker still ends the paragraph and still reads as a tag. A line inside a fenced
509
+ body is example code rather than block structure, so it opens no tag: a body runs from a line
510
+ opening with three or more backticks or tildes to the first line opening with a run of the same
511
+ character at least as long, and `maskFences` replaces its characters with aligned spaces before
512
+ every tag search reads the block.
513
+ `@param`, `@returns` and the `Returns` column, `@remarks` and narrative, and the H1 tagline are
514
+ outside the comparison — `tagline()` reads the tagline for a package that compares it against its own
515
+ README, and it gains a partner to compare against when a source declares `@packageDocumentation`.
516
+ `Shape`, `Signature`, `Value`, and `Returns` are guide-only data columns and stay unread.
517
+
518
+ Examples pair by title. A `GuideFence` carries the flattened text of its nearest preceding heading as
519
+ `title`, a `SourceExample` carries the text after its `@example` tag, and a block claims the fence of
520
+ the same title. A heading's text is flattened, so a title written with a code span pairs with a plain
521
+ `@example` title. The pairing is per title across the whole document, not per heading: the first
522
+ fence a title reaches is the compared one, and every later fence of that title is outside the
523
+ comparison, whether it sits under the same heading or under a second heading of the same text. A
524
+ heading can therefore carry a setup fence and a result fence, and only the first answers for the
525
+ title. The bodies compare after `normalizeComment` removes the continuation marker and the
526
+ block's leading indentation and trims per-line trailing whitespace, and the fence language compares
527
+ with them: an example's compared text is its language on the first line and its body beneath. An
528
+ untitled `@example` keeps its presence role for `findUnexampled`.
529
+
277
530
  `Source` never parses markdown or touches disk — it scans a consumer-supplied file
278
531
  inventory's text with deliberately narrow physical-line grammars. `extractSourceLines` is the
279
532
  sole character engine and emits one `SourceLine` per LF/CRLF physical line plus the final line:
@@ -294,28 +547,64 @@ column-zero, while barrel rows retain their separate whitespace-tolerant whole-l
294
547
 
295
548
  `extractExampleLines` walks only `SourceLine` records. A genuine JSDoc opener is eligible only
296
549
  when it is the first non-whitespace source material. Within a leading whitespace-separated chain,
297
- each later span replaces the earlier one and is authoritative. Only an exact block-position
298
- `@example` tag qualifies; same-line title text is allowed. Source material between or after spans
550
+ each later span replaces the earlier one and is authoritative. Only an `@example` tag opening a line
551
+ at its first non-blank column qualifies; same-line title text is allowed. Source material between or after spans
299
552
  severs association, a leading JSDoc on the next line replaces pending state, and any other next
300
- physical record is returned once as the candidate. `examplesFrom` and `exampleMethods` share this
301
- adjacency parser and apply their distinct exported-function and callable-member grammars only to
302
- `code`.
553
+ physical record is returned once as the candidate. `extractExamples` and `extractExampleMethods` share this
554
+ adjacency parser and apply their distinct declaration-head and callable-member grammars only to
555
+ `code`. `extractExamples` dedupes by name and title, so a `type` and a `const` sharing one name
556
+ contribute the first block of a title rather than one block each.
557
+ `collectTitles` reads a module's head blocks before its documented members' blocks, so where a head
558
+ and a member carry one title the head's block answers.
303
559
 
304
560
  Across the `.ts` module keys under each selected directory, excluding its root `index.ts` and
305
- every `*.test.ts`, `exportsFrom` matches
306
- `^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per projected line, deduped by
307
- (kind, name). `hiddenFrom` applies the same five-kind head grammar without `export`. Comment and
561
+ every `*.test.ts`, `collectKeys` matches
562
+ `^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per projected line and keys that
563
+ line by `computeSymbolKey`; `extractExports` reads those keys, splits each at its one space, and
564
+ dedupes by (keyword, name). `collectKeys` is the package's one key grammar, and `extractExports`,
565
+ `extractExamples`, `extractMemberMethods`, `extractExampleMethods`, and `locateComment` each read
566
+ their own part out of the same map, so a change to the head shape or to the member shape reaches
567
+ every one of them. A column-zero `export` declaration carrying another keyword closes the owner
568
+ it follows, the same way a column-zero `}` does. `extractHidden` applies the same
569
+ declaration-keyword head grammar without `export`. Comment and
308
570
  template payload, enums, `let`, `var`, and other TypeScript declaration forms are outside these
309
- two populations; enum exclusion describes reflection scope, not a general package-policy ban.
310
- `declarationBody` locates a named real `export class` / `export interface` head and exact
311
- column-zero close in projected lines (joining an oxfmt-wrapped signature via `joinHead`), then
312
- returns the aligned raw body. `memberMethods` projects that body once and matches
313
- `^\t(?:async )?\*?(\w+)(<[^>]*>)?\??\(` against those body lines — plain / `async` /
314
- generator / optional methods count; getters, setters, `static` members, and `#` privates
315
- never match (their keyword or sigil breaks the `name(` shape), and `constructor` is filtered
316
- out of `Source.methods`. `selectModuleKeys` scopes the inventory to one `GuideModule`'s `.ts` files,
317
- excluding each scope directory's own `index.ts` and any `*.test.ts` file. `Source.hidden()`
318
- mechanically asserts AGENTS §5's export-discipline rule and catches a hidden five-kind
571
+ populations; enum exclusion describes reflection scope, not a general package-policy ban.
572
+ `collectDeclarations` reads a file's real `export class` / `export interface` heads and their exact
573
+ column-zero closes from one projection of it (joining an oxfmt-wrapped signature through
574
+ `joinHead`), and returns each head's aligned raw body and its `extends` bases as one `Declaration`,
575
+ keyed `${keyword} ${name}`. One collector is what keeps a body and a heritage clause on the same
576
+ declaration. The identifier is the head's own run up to its generic parameter list or its heritage
577
+ clause, so it enters the key as literal text: a name carrying a regex metacharacter reaches no
578
+ `RegExp`, and a lookup of that name matches the character rather than a wildcard. A head that opens
579
+ no column-zero close records nothing, and a later head of a key already collected adds nothing.
580
+ `extractDeclaration` is the named lookup over that map: it spells the `${keyword} ${name}` key so a
581
+ consumer reading one name never writes that convention, and it collects the file afresh on every
582
+ call. A consumer reading many names from one file calls `collectDeclarations` once and reads the
583
+ map, and `Source` holds one such map per module scope. `extractMemberMethods` projects that body
584
+ once through `extractBodyLines`, which reads it inside an owner head so each member keys to its
585
+ owner, and `collectKeys` matches `^\t(?:async )?\*?(\w+)\??(?:<.*>)?\(` against those body lines —
586
+ plain / `async` / generator / optional methods count; getters, setters, `static` members, and `#`
587
+ privates never match (their keyword or sigil breaks the `name(` shape), and `constructor` is
588
+ filtered out of `Source.methods`. Every balanced `<...>` span is removed from the head before its
589
+ `extends` clause is read, so a `T extends Base` type parameter never reads as a base and `Base<T>`
590
+ reads as `Base`, and a class's `implements` clause is excluded. `Source.methods(name)` unions the
591
+ located declaration's own members with those of every declaration it extends, following each base
592
+ through the same module scope and keeping the keyword it started from — an interface chain resolves
593
+ through interfaces, a class chain through classes, so an interface extending a name only a class
594
+ declares gets nothing from it. One declaration answers for a name: the module scope's files are read
595
+ in sorted key order, and the first one whose located head has a body or has bases supplies both the
596
+ members and the bases; a head with neither a body nor bases does not count as declared, so an empty
597
+ `export interface X {}` is skipped and the scan continues to a later file or falls through to a
598
+ same-named class, and a second file declaring the same name after one is found adds nothing. The
599
+ inventory is the further bound: a base the selected directories do not declare, whether it is
600
+ imported from another package or written as a qualified name such as `external.Store`, contributes
601
+ no members and is not an error, and one visited set per call collapses a cycle and a diamond to a
602
+ single visit. `Source.examples(name)` is deliberately asymmetric with it — it reads only the named
603
+ declaration's own body, under each keyword, and follows no `extends` clause, so an inherited
604
+ member's `@example` belongs to the base that declares it. `selectModuleKeys` scopes the inventory to
605
+ one `GuideModule`'s `.ts` files, excluding each scope directory's own `index.ts` and any `*.test.ts`
606
+ file. `Source.hidden()` mechanically asserts the export-discipline rule
607
+ `.claude/rules/architecture.md` § Barrel exports states, and catches a hidden declaration-keyword
319
608
  declaration the surface bijection alone would never see.
320
609
 
321
610
  `Source.surface()` starts only at exact `index.ts` for canonical `'.'`, or exact
@@ -332,9 +621,9 @@ quoted target. Arbitrary trailing source is rejected. Only the terminal `.js` be
332
621
  `resolveLink(currentIndex, target)` derives the current index file's directory and delegates to
333
622
  `resolvePath`, the only dot-segment reducer. Exact workspace-root `index.ts` and nested targets
334
623
  ending `/index.ts` recurse as barrels, while another exact `.ts` target contributes its direct
335
- `exportsFrom` declarations. One visited set terminates self-cycles, multi-index cycles, repeated
336
- rows, and diamonds. `symbolKey` deduplicates same-name/same-kind rows while retaining
337
- same-name/different-kind rows, and the final list uses the same name sort as `exports()`.
624
+ `extractExports` declarations. One visited set terminates self-cycles, multi-index cycles, repeated
625
+ rows, and diamonds. `computeSymbolKey` deduplicates same-name/same-keyword rows while retaining
626
+ same-name/different-keyword rows, and the final list uses the same name sort as `exports()`.
338
627
 
339
628
  Missing roots and targets, empty barrels, and unsupported rows contribute nothing without
340
629
  throwing, while valid siblings continue. Named, default, namespace, type-only, non-relative, and
@@ -350,19 +639,29 @@ same readonly array instance is returned thereafter.
350
639
  Every guides-parity check reduces to `expect([]).toEqual([])`, paired with a non-vacuousness
351
640
  guard so a renamed heading fails loudly instead of passing on an empty extraction:
352
641
 
353
- - **SB — Direct/barrel/guide surface parity (kind folded in).** `missingSymbols` proves all four
354
- directions: direct declarations → barrel surface, barrel surface → direct declarations, barrel
355
- surface → guide surface, and guide surface → barrel surface. Every comparison uses `symbolKey`,
356
- so a declaration may drift in neither name nor kind. Guard: `guide.surface().length > 0`.
642
+ - **SB — Direct/barrel/guide surface parity (keyword folded in).** `findMissingSymbols` proves every
643
+ direction: direct declarations → barrel surface, barrel surface → direct declarations, barrel
644
+ surface → guide surface, and guide surface → barrel surface. Every comparison uses `computeSymbolKey`,
645
+ so a declaration may drift in neither name nor keyword. Guard: `guide.surface().length > 0`.
357
646
  - **MB — Methods bijection + class-no-extra.** Per `MethodGroup`, its `methods` vs
358
647
  `source.methods(group.interface)`, `findMissing` both directions; then, by the
359
648
  `XInterface → X` naming convention, `findMissing(source.methods('X'), group.methods)` must
360
649
  also be empty — the implementing class exposes no undocumented public method. Guard:
361
650
  `group.methods.length > 0`.
651
+ - **RN — Row naming.** `guide.unnamed()` keeps every `## Surface` or `## Methods` row whose first
652
+ cell carries no code span. `extractSurface` and `extractMethods` key a row on that code span, so a
653
+ row without one enters neither `guide.surface()` nor a `MethodGroup`, no bijection leg can report
654
+ it, and this check names the row instead of letting it go in silence. A finding is the row's cells
655
+ read through `extractCellText` and joined by `` ` | ` ``. Guard: RN reads table rows, so a guide
656
+ documenting its surface with backticked H3 entity headings and no `## Surface` table gives RN
657
+ nothing to read, and SB's `guide.surface().length > 0` covers that surface instead.
362
658
  - **LI — Link integrity.** `guide.links()`, dropping `isExternalLink` hrefs, `resolveLink`
363
- the rest against the guide's own path, keep those failing `source.exists`.
659
+ the rest against the guide's own path, keep those failing `source.exists` — which holds for a
660
+ directory link too, because `exists` answers for an inventory key and for any directory a key
661
+ sits beneath. Guard: `guide.links().length > 0`.
364
662
  - **TE — Tests-link existence.** `guide.tests()`, `resolveLink` + `source.exists`, keep the
365
- missing.
663
+ missing; a link naming a fixture directory resolves on the same directory rule. Guard:
664
+ `guide.tests().length > 0`.
366
665
  - **NV — Non-vacuousness.** `parseManifest` yields at least one entry; each guide's
367
666
  `surface()` and every `MethodGroup` is non-empty — the guard behind every other check.
368
667
  - **FL — Fence-language listing.** `findUnlisted(guide.fences(), LANGUAGES)` keeps every fence
@@ -371,33 +670,116 @@ guard so a renamed heading fails loudly instead of passing on an empty extractio
371
670
  languages and keeps its remaining checks scoped to the one they parse.
372
671
  - **EX — Examples presence.** A documented symbol "has an example" when its bare name
373
672
  appears (word boundary) in any fence body from `guide.fences()` filtered to the example
374
- language, OR its source has an immediately preceding eligible leading JSDoc chain whose final
375
- authoritative span carries an exact block-position `@example` tag, with optional title text,
673
+ language, **or** its source has an immediately preceding eligible leading JSDoc chain whose final
674
+ authoritative span carries an `@example` tag opening a line at its first non-blank column, with
675
+ optional title text,
376
676
  (`source.examples()` / `source.examples(name)`).
377
- Applies to every `function`-kind `Surface` symbol and every `MethodGroup` member.
378
- Presence-only — fence and JSDoc CONTENT are never checked. `findUnexampled` is the
677
+ Applies to every `function`-keyword `Surface` symbol and every `MethodGroup` member.
678
+ Presence-only — fence and JSDoc **content** are never checked. `findUnexampled` is the
379
679
  comparison. Guard: the SB/MB extractions this check reuses already prove non-vacuous.
680
+ - **SQ — Surface summary equality.** For every symbol both `guide.surface()` and `source.surface()`
681
+ carry, the guide row's `Summary` cell equals the declaration's description paragraph. A pair agrees
682
+ only when both sides carry the same text: guide text alone reports the guide's side, source text
683
+ alone reports the source's, and neither side carrying text reports the key by itself — never as
684
+ agreement. A table with no `Summary` column therefore reports every row it documents, and a package
685
+ adopting SQ cannot pass it vacuously. `findDrift` is the comparison. Guard: the SB extractions
686
+ this check reuses already prove non-vacuous.
687
+ - **MQ — Methods summary equality.** The same comparison per `MethodGroup`, over the members
688
+ `group.methods` and `source.methods(group.interface)` both carry, keyed `Owner.member`.
689
+ - **EQ — Example equality.** Every titled guide fence against the `@example` block of the same title,
690
+ body and fence language together. The block is an exported declaration head's own — a `type`, `interface`,
691
+ `const`, `function`, or `class` head at column zero — or a documented `class` or `interface`
692
+ member's, so a head's titled block is compared the way a member's is. The pairing is per title
693
+ across the document, not per heading: the
694
+ first fence a title reaches is the compared one, and every later fence of that title is outside the
695
+ comparison, whether it sits under the same heading or under a second heading of the same text. A title one side alone carries is outside
696
+ the comparison too, and an untitled `@example` stays EX's presence evidence.
697
+ - **RQ — README pitch equality.** The blockquote under the README's H1 equals the guide's
698
+ tagline, both read through `createGuide(text).tagline()`. The pair is outside `findDrift`, which
699
+ compares a guide against its source: the drop-in's README case is the gate, and the direct
700
+ `npm run test:guides` entry reports the pair beside the drift rows and never writes it. Guard: both sides read a defined
701
+ tagline before the comparison, so a README without a blockquote reddens rather than passing on
702
+ `undefined`.
380
703
  - **FI — Fence-import reality.** Every `import { ... } from 'specifier'` in a `guide.fences()`
381
- fence of the checked language, for a SELF specifier (this repo's own package name / path
382
- alias), imports only names that exist in `source.surface()`. `fenceImports` parses the
704
+ fence of the checked language, for a **self** specifier (this repo's own package name / path
705
+ alias), imports only names that exist in `source.surface()`. `extractFenceImports` parses the
383
706
  statement; `findMissing` diffs the imported names against the public/barrel surface's names.
707
+ Guard: the comparison runs against at least one resolved import.
708
+
709
+ SQ, MQ, and EQ share one function: `findDrift(guide, source)` returns every disagreement with both
710
+ sites, so a package's whole equality gate is `expect(findDrift(guide, source)).toEqual([])`. It
711
+ compares only the pairs both sides carry, so a symbol, a member, or a title one side lacks is left to
712
+ the bijection check that owns it and is never reported twice.
384
713
 
385
714
  Permanent controls bind the SB population boundaries through production `Source`, `Guide`,
386
- `missingSymbols`, and `symbolKey`: a stranded direct declaration must be missing from the barrel;
387
- a phantom Guide row must be missing from the barrel; kind drift must fail in both barrel/Guide
715
+ `findMissingSymbols`, and `computeSymbolKey`: a stranded direct declaration must be missing from the barrel;
716
+ a phantom Guide row must be missing from the barrel; keyword drift must fail in both barrel/Guide
388
717
  directions; a barrel-only declaration outside `selectModuleKeys()` must be missing from direct exports;
389
718
  a correlated commented declaration must remain absent from direct and barrel populations while
390
719
  failing Guide-to-barrel; and a workspace-root `index.ts` hop must reach its real terminal symbol.
391
720
 
392
- ## The pure file-inventory model
721
+ ## The renderers and the replacers
722
+
723
+ The core readers say where a guide and its source disagree; the core renderers and replacers carry
724
+ a change across. These core leaves return text and write nothing, so the worker gate that reads
725
+ `findDrift` calls no writer. The server `GuideCommand` gathers host inventory and writes only for an
726
+ explicit `npm run test:guides -- --to guide` or `--to source` request.
727
+
728
+ `renderSurface`, `renderMethods`, and `renderExample` produce fresh guide text from source
729
+ entries — a `Name` / `Kind` / `Summary` table, a `####` group and its `Name` / `Summary` table,
730
+ and a titled fence. Each builds a markdown node and renders it through the parser's own
731
+ `renderMarkdown`, so the text it returns is the text the package parses back. Each renders the
732
+ block a guide section contains rather than the section itself, because a guide documents its
733
+ surface in several tables under their own sub-headings: reading a render back therefore parses it
734
+ under the section heading its caller supplies, so `extractSurface` reads
735
+ `'## Surface\n\n' + renderSurface(symbols)` back to the symbols it was rendered from, and
736
+ `extractMethods` reads `'## Methods\n\n' + renderMethods(group)` back to the group.
737
+ `renderExample` needs no such heading, because `extractFences` scopes a fence to no section. The
738
+ render is one-space padded whatever the committed guide's column alignment was, so the checkout's
739
+ own formatter re-aligns it and the comparison stays on parsed entries rather than on bytes.
740
+
741
+ `replaceCell` and `replaceFence` rewrite one node inside a guide that already exists. Each locates
742
+ its node through the readers — `extractRowSymbol` and `collectGroups` for a row, `collectFences`
743
+ for a fence — reads that node's source region from the parser's provenance, rebuilds the node, and
744
+ splices the render over the region through `spliceSpan`. Only the located node's own region is
745
+ rewritten, so every byte of the guide outside it travels unchanged.
746
+
747
+ `replaceSummary` and `replaceExample` rewrite one doc block's raw text — its description paragraph,
748
+ or the body of the `@example` tag carrying a given title. `locateComment` is how a caller finds
749
+ that block: it takes a file's text and a compared key and returns the block's own character region,
750
+ which the caller slices, hands to a replacer, and splices back through `spliceSpan`. `unwrapComment`
751
+ gives one content line per physical line, so the rewrite addresses the lines it replaces and keeps
752
+ every other line: the tag lines, the blank separator before the first tag, the block's indentation,
753
+ and its continuation markers. A replaced description re-wraps inside the caller's `width`, defaulting
754
+ to `WRAP_WIDTH`, because a doc block's own wrapping is not recoverable from its text.
755
+
756
+ Every replacer reports a miss the same way. `undefined` means "not replaced", and it covers a key
757
+ that reaches no row, a table carrying no `Summary` column, a title no fence or tag carries, a text
758
+ that is no doc block, a summary carrying no word, and a language or code the emitted three-backtick
759
+ fence cannot enclose or the doc block cannot hold — a body carrying `*/`. A caller reports the key
760
+ it could not place instead of writing a file it could not read back.
761
+
762
+ Every replacement whose target already carries the value returns its input byte for byte. That is
763
+ what lets a package run the propagation over a tree that has no drift and see no file move: a
764
+ re-render would re-pad a table and a re-wrap would move most doc blocks, and neither is a change
765
+ anyone asked for. The identity reads both sides through the compared form, so handing a replacer
766
+ text the form still moves writes once and is a fixed point on the next run.
767
+
768
+ The caller's named destination governs summaries and examples alike. The `guide` destination copies
769
+ source text into the guide. The `source` destination copies guide text into the source.
770
+ The gate reports and never writes in core; neither the core readers nor renderers decide. The
771
+ server command alone applies the caller's explicit destination to host files.
772
+
773
+ ## The core file-inventory model
393
774
 
394
775
  Neither `Guide` nor `Source` ever imports `node:fs` or any other I/O primitive — `Source`'s
395
776
  construction input (`SourceOptions.files`) is a plain `Readonly<Record<string, string>>` the
396
- CONSUMER gathers however their runtime allows: a recursive `node:fs` walk in a Node vitest
777
+ **consumer** gathers however their runtime allows: a recursive `node:fs` walk in a Node vitest
397
778
  run, `import.meta.glob('/**/*.ts', { eager: true, query: '?raw', import: 'default' })` in a
398
- browser/vitest run, or a static bundle in any other environment. This keeps the package
399
- itself environment-agnostic while every check still runs against real, on-disk truth in the
400
- consumer's own test. The inventory must include each selected module's root `index.ts` and every
779
+ browser/vitest run, or a static bundle in any other environment. This keeps the core
780
+ environment-agnostic while every check still runs against real, on-disk truth in the consumer's
781
+ own test. The server command supplies a Node host shell over that core contract. The inventory must
782
+ include each selected module's root `index.ts` and every
401
783
  reachable exact `.ts` target for `surface()` to observe them; absent keys remain empty reflection.
402
784
 
403
785
  ## Patterns
@@ -408,7 +790,7 @@ reachable exact `.ts` target for `surface()` to observe them; absent keys remain
408
790
  import { createGuide } from '@orkestrel/guide'
409
791
 
410
792
  const guide = createGuide('## Surface\n\n| Name | Kind |\n| --- | --- |\n| `X` | class |')
411
- guide.surface() // [{ name: 'X', kind: 'class' }]
793
+ guide.surface() // [{ name: 'X', keyword: 'class' }]
412
794
  guide.sections() // ['Surface']
413
795
  ```
414
796
 
@@ -436,10 +818,11 @@ const source = createSource({
436
818
  },
437
819
  module: 'src/core',
438
820
  })
439
- source.exports() // [{ name: 'Guide', kind: 'class' }, { name: 'GuideInterface', kind: 'interface' }]
440
- source.surface() // [{ name: 'Guide', kind: 'class' }, { name: 'GuideInterface', kind: 'interface' }]
441
- source.methods('GuideInterface') // ['sections']
821
+ source.exports() // [{ name: 'Guide', keyword: 'class' }, { name: 'GuideInterface', keyword: 'interface' }]
822
+ source.surface() // [{ name: 'Guide', keyword: 'class' }, { name: 'GuideInterface', keyword: 'interface' }]
823
+ source.methods('GuideInterface') // [{ name: 'sections' }]
442
824
  source.exists('src/core/Guide.ts') // true
825
+ source.exists('src/core') // true — a directory any inventory key sits beneath
443
826
  ```
444
827
 
445
828
  ### Resolve a fence's import specifier to the right `Source`
@@ -455,15 +838,16 @@ const sources = createSourceManager({
455
838
  modules: { '@scope/package': 'src/core', '@scope/package/core': 'src/core' },
456
839
  })
457
840
 
458
- sources.source('@scope/package')?.surface() // [{ name: 'Guide', kind: 'class' }]
841
+ sources.source('@scope/package')?.surface() // [{ name: 'Guide', keyword: 'class' }]
459
842
  sources.source('node:fs') // undefined — a foreign import, which a fence check skips
460
843
  sources.source('@scope/package') === sources.source('@scope/package/core') // true
844
+ sources.sources() // [the one shared view both specifiers name]
461
845
  ```
462
846
 
463
847
  ### The bijection assertion shape
464
848
 
465
849
  ```ts
466
- import { createGuide, createSource, missingSymbols } from '@orkestrel/guide'
850
+ import { createGuide, createSource, findMissingSymbols } from '@orkestrel/guide'
467
851
 
468
852
  const guide = createGuide('## Surface\n\n| Name | Kind |\n| --- | --- |\n| `Guide` | class |')
469
853
  const source = createSource({
@@ -474,11 +858,138 @@ const source = createSource({
474
858
  module: 'src/core',
475
859
  })
476
860
 
477
- // Direct declarations, public barrel, and guide surface agree in all four directions.
478
- missingSymbols(source.exports(), source.surface()) // []
479
- missingSymbols(source.surface(), source.exports()) // []
480
- missingSymbols(source.surface(), guide.surface()) // []
481
- missingSymbols(guide.surface(), source.surface()) // []
861
+ // Direct declarations, public barrel, and guide surface agree in every direction.
862
+ findMissingSymbols(source.exports(), source.surface()) // []
863
+ findMissingSymbols(source.surface(), source.exports()) // []
864
+ findMissingSymbols(source.surface(), guide.surface()) // []
865
+ findMissingSymbols(guide.surface(), source.surface()) // []
866
+ ```
867
+
868
+ ### Compare a guide against the source it documents
869
+
870
+ ```ts
871
+ import { createGuide, createSource, findDrift } from '@orkestrel/guide'
872
+
873
+ const guide = createGuide(
874
+ '## Surface\n\n| Name | Kind | Summary |\n| --- | --- | --- |\n| `walk` | function | Walks the tree. |',
875
+ )
876
+ const source = createSource({
877
+ files: {
878
+ 'src/core/index.ts': "export * from './helpers.js'\n",
879
+ 'src/core/helpers.ts': '/**\n * Walks a tree.\n */\nexport function walk(): void {}\n',
880
+ },
881
+ module: 'src/core',
882
+ })
883
+
884
+ // One entry per disagreement, naming both sites; a symbol one side lacks belongs to SB.
885
+ findDrift(guide, source) // [{ key: 'function walk', category: 'summary', guide: 'Walks the tree.', source: 'Walks a tree.' }]
886
+ ```
887
+
888
+ ### Compare declaration membership
889
+
890
+ ```ts
891
+ import { compareMembership } from '@orkestrel/guide'
892
+
893
+ compareMembership('guides/widget.md', 'WidgetInterface', ['open'], ['open']) // undefined
894
+ ```
895
+
896
+ ### Identify categorized drift
897
+
898
+ ```ts
899
+ import { identifyDrift } from '@orkestrel/guide'
900
+
901
+ identifyDrift('guides/widget.md', { category: 'summary', key: 'function open' })
902
+ // 'guides/widget.md\nsummary\nfunction open'
903
+ ```
904
+
905
+ ### Format a parity side
906
+
907
+ ```ts
908
+ import { formatSide } from '@orkestrel/guide'
909
+
910
+ formatSide('Walks.') // '"Walks."'
911
+ formatSide(undefined) // 'absent'
912
+ ```
913
+
914
+ ### Format categorized drift
915
+
916
+ ```ts
917
+ import { formatDrift } from '@orkestrel/guide'
918
+
919
+ formatDrift({ category: 'summary', key: 'function walk', guide: 'Walks.', source: 'Walks a tree.' })
920
+ // 'summary function walk: guide "Walks." source "Walks a tree."'
921
+ ```
922
+
923
+ ### Inspect and rewrite a caller-owned inventory
924
+
925
+ ```ts
926
+ import { Parity } from '@orkestrel/guide'
927
+
928
+ const parity = new Parity({
929
+ files,
930
+ entries,
931
+ modules: { '@scope/package': 'src/core' },
932
+ languages: ['ts'],
933
+ language: 'ts',
934
+ })
935
+
936
+ parity.rows()
937
+ parity.inspect()
938
+ parity.document()
939
+ parity.annotate()
940
+ ```
941
+
942
+ ### Run the shared server command
943
+
944
+ ```ts
945
+ import { GuideCommand } from '@orkestrel/guide/server'
946
+ import { readInventory } from '@orkestrel/test/server'
947
+ import { createVitest } from 'vitest/node'
948
+
949
+ await new GuideCommand({
950
+ root: new URL('../', import.meta.url),
951
+ patterns: ['src/**/*.ts', 'tests/**/*.ts', 'guides/*.md', '*.md'],
952
+ modules: { '@scope/package': ['src/core', 'src/server'] },
953
+ languages: ['ts'],
954
+ language: 'ts',
955
+ reader: readInventory,
956
+ runner: createVitest,
957
+ }).execute(async ({ files, report, root, rows }) => {
958
+ const { expect, it } = await import('vitest')
959
+ it('checks the documented inventory', () => {
960
+ expect(root.length).toBeGreaterThan(0)
961
+ expect(Object.keys(files).length).toBeGreaterThan(0)
962
+ expect(rows.length).toBeGreaterThan(0)
963
+ expect(report.input).toEqual([])
964
+ })
965
+ })
966
+ ```
967
+
968
+ ### Carry a summary across into the guide
969
+
970
+ ```ts
971
+ import { replaceCell, replaceSummary } from '@orkestrel/guide'
972
+
973
+ const guide =
974
+ '## Surface\n\n| Name | Kind | Summary |\n| --- | --- | --- |\n| `walk` | function | Walks the tree. |'
975
+
976
+ // The guide's text back, with that one cell replaced and every byte outside the table unchanged.
977
+ replaceCell(guide, 'function walk', 'Walks a tree.')
978
+ // '## Surface\n\n| Name | Kind | Summary |\n| --- | --- | --- |\n| `walk` | function | Walks a tree. |'
979
+ replaceCell(guide, 'function phantom', 'Absent.') // undefined — no row carries that key
980
+ replaceCell(guide, 'function walk', 'Walks the tree.') === guide // true — the row already carries it
981
+
982
+ // The other direction: one doc block's raw text, its description paragraph replaced.
983
+ replaceSummary('/** Walks the tree. */', 'Walks a tree.') // '/** Walks a tree. */'
984
+ ```
985
+
986
+ ### Read a guide's tagline
987
+
988
+ ```ts
989
+ import { createGuide } from '@orkestrel/guide'
990
+
991
+ const guide = createGuide('# Widget\n\n> A widget toolkit.\n\n## Surface\n')
992
+ guide.tagline() // 'A widget toolkit.'
482
993
  ```
483
994
 
484
995
  ### Project source into physical code lines
@@ -487,7 +998,8 @@ missingSymbols(guide.surface(), source.surface()) // []
487
998
  import { extractSourceLines } from '@orkestrel/guide'
488
999
 
489
1000
  extractSourceLines('export const visible = true // note\n')
490
- // [{ source: 'export const visible = true // note', code: 'export const visible = true ', jsdoc: undefined }, ...]
1001
+ // [{ source: 'export const visible = true // note', code: 'export const visible = true ', jsdoc: undefined }]
1002
+ // … one record per remaining line
491
1003
  ```
492
1004
 
493
1005
  ### Resolve directory and file targets
@@ -501,18 +1013,33 @@ resolveLink('index.ts', './root.ts') // 'root.ts'
501
1013
 
502
1014
  ## Tests
503
1015
 
504
- - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — direct `SourceLine`, lexical, and JSDoc-alignment invariants; projected five-kind direct/hidden reflection; genuine JSDoc example adjacency and faux JSDoc exclusion; every guide-document extractor; canonical-key, runtime-name, `resolvePath`, and `resolveLink` invariants; all remaining helper leaves.
1016
+ This repository runs the catalog against itself. Its `tests/guides.test.ts` wires RN, SB, MB, LI,
1017
+ TE, NV, FL, EX, FI, SQ, MQ, EQ, and RQ. Every `## Surface` and `## Methods` table here heads its
1018
+ compared column `Summary`, so `findDrift` reads every row this guide documents and the equality
1019
+ case asserts the whole worklist empty; `Kind`, `Shape`, `Signature`, `Value`, and `Returns` are the
1020
+ data columns beside it and stay unread. EQ compares a `## Patterns` fence only where an `@example`
1021
+ block carries the same title, so a fence composing several symbols, a fence whose body a
1022
+ three-backtick doc-block fence cannot enclose, a fence whose body carries the doc-comment
1023
+ terminator `*/`, and a class's constructor-door block stay outside it; a case beside the equality
1024
+ one pins that at least one title pairs, so retiring the example half reddens the suite. Converge
1025
+ source authority into guides with `npm run test:guides -- --to guide`, or guide authority into
1026
+ source with `npm run test:guides -- --to source`; never weaken the case.
1027
+
1028
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — direct `SourceLine`, lexical, and JSDoc-alignment invariants; projected declaration-keyword direct/hidden reflection; genuine JSDoc example adjacency and faux JSDoc exclusion; every guide-document extractor; the compared form clause by clause on both sides; the `Summary` locator over a reordered header and a table without the column; the nameless-row finding against a name the reader reads through emphasis; a block tag written past one space after the continuation marker, and a tag-shaped line inside a fenced body left to the example code; the fenced-body projection `maskFences` returns; fence titles and the tagline; `findDrift` with a negative control drawn from the symbols the bijection legs already report, a planted disagreement of each kind, and a later fence under one heading left outside the comparison; the doc-block reader against `parseSync`'s own reading, which names the shape the reader misses; the compared cell built back from its text, over every doc-block summary this package ships; the compared form's code-span clause converging a padded, a one-sided, and an all-whitespace span from either side, leaving a link token inside a span literal, and leaving a multi-backtick span untouched; the corpus this package ships carrying a floor of blocks, read at its raw spans through the aligned JSDoc projection and checked against the blocks `extractSourceComments` attaches; the renderers round-tripping through the readers that own them and the render read the same as the column-aligned committed form; each replacer's located node rewritten with every byte outside it unchanged, its miss returning `undefined`, and its identity case byte-stable over every described doc block this package ships and a fixed point on the second run, with a control from outside each replacer's membership; the one key grammar `collectKeys` reports over the control fixtures and a member fixture, its owner closing at a column-zero brace, and the keyword and member name each reader splits back out of it; `locateComment` over an overload set, a member key, an owner closed at its brace, a CRLF file, and the shapes the attaching reader misses, then a rewrite spliced back and read through `collectSummaries`, and every `Owner.member` key this package's own source declares located back to the block carrying that member's summary; canonical-key, runtime-name, `resolvePath`, and `resolveLink` invariants; all remaining helper leaves.
505
1029
  - [`tests/src/core/parsers.test.ts`](../tests/src/core/parsers.test.ts) — `parseManifest` row parsing, malformed-row skipping, one-versus-many Source canonicalization, and nested manifest directories.
506
- - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — `isExportKind` / `isSurfaceSymbol` / `isMethodGroup` / `isManifestEntry`.
1030
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — `isExportKeyword` / `isSurfaceSymbol` / `isMethodEntry` / `isSourceExample` / `isDrift` / `isMethodGroup` / `isManifestEntry`.
507
1031
  - [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts) — per-shape guard exactness, JSON Schema essentials, seeded generate round-trips, parse rebuilds.
508
- - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createGuide` / `createSource` + the compiled symbol/group/manifest contracts.
509
- - [`tests/src/core/Guide.test.ts`](../tests/src/core/Guide.test.ts) — `Guide`'s six cached projections and production barrel/Guide phantom and kind-drift controls.
1032
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createGuide` / `createSource` + the compiled symbol, entry, example, drift, group, and manifest contracts.
1033
+ - [`tests/src/core/Guide.test.ts`](../tests/src/core/Guide.test.ts) — `Guide`'s cached projections, its tagline, its nameless rows, and its fence titles, and production barrel/Guide phantom and keyword-drift controls.
510
1034
  - [`tests/src/core/sources/Source.test.ts`](../tests/src/core/sources/Source.test.ts) — direct/barrel projections, lexical and JSDoc regressions, canonical-key populations, root and nested indexes, exact row grammar, graph invariants, and correlated population controls.
511
- - [`tests/src/core/sources/SourceManager.test.ts`](../tests/src/core/sources/SourceManager.test.ts) — `moduleKey` boundary collision, specifier resolution, the `undefined` skip for an unmapped specifier, array-valued module scopes, and per-module entity sharing with a differently-scoped identity control.
1035
+ - [`tests/src/core/sources/SourceManager.test.ts`](../tests/src/core/sources/SourceManager.test.ts) — `computeModuleKey` boundary collision, specifier resolution, the `undefined` skip for an unmapped specifier, array-valued module scopes, `sources()` enumeration, and per-module entity sharing with a differently-scoped identity control.
1036
+ - [`tests/src/server/GuideCommand.test.ts`](../tests/src/server/GuideCommand.test.ts) — direct installed reader and runner ports, fresh worker context, registration rejection, real runner cleanup after start failure, cleanup failure reporting, and higher native exit preservation.
1037
+ - [`tests/src/server/helpers.test.ts`](../tests/src/server/helpers.test.ts) — finding formatting, native and file-URL root resolution, pitch selection, and owned foreign-result reading with live receivers.
1038
+ - [`tests/src/server/parsers.test.ts`](../tests/src/server/parsers.test.ts) — native direction arguments and package-name parsing.
512
1039
  - [`tests/fixtures/broken/stranded-export`](../tests/fixtures/broken/stranded-export) — permanent negative control: its guide and direct declarations agree while its conventional barrel omits `strandedExport`.
513
- - [`tests/guides.test.ts`](../tests/guides.test.ts) — the drop-in guides-parity suite, run against THIS repo's own `guides/README.md` manifest — the self-dogfooding acceptance criterion.
1040
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the drop-in guides-parity suite, run against **this** repository's own `guides/README.md` manifest — the self-dogfooding acceptance criterion.
514
1041
 
515
1042
  ## See also
516
1043
 
517
- - `AGENTS.md` (workspace root) — the rules; §22 documentation-as-contracts.
1044
+ - `AGENTS.md` (workspace root) — the rules; `.claude/rules/documentation.md` § Parity states the documentation-as-contract law.
518
1045
  - [`README.md`](README.md) — the guides index.