@orkestrel/scaffold 0.0.24 → 0.0.26

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.
@@ -1,21 +1,22 @@
1
1
  # Guide
2
2
 
3
- > A zero-dependency guides-parity toolkit: `Guide` extracts a markdown guide's documented
4
- > surface, method groups, links, and test links; `Source` reflects the same shapes from a
5
- > consumer-supplied file inventory via pure text scanners (no filesystem, no TypeScript
6
- > compiler API); a small set of pure comparison helpers (`missingSymbols`, `findMissing`,
3
+ > A pure, I/O-free guides-parity toolkit: `Guide` extracts a markdown guide's documented
4
+ > surface, method groups, links, and test links; `Source` reflects direct declarations and
5
+ > 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`,
7
8
  > `resolveLink`, …) reduce every guides-parity check to `expect([]).toEqual([])`
8
- > (AGENTS §22). Source: [`src/core`](../../src/core). Surfaced through the `@src/core` barrel.
9
+ > (AGENTS §22). Source: [`src/core`](../../src/core). Published through `@orkestrel/guide`.
9
10
 
10
11
  The doctrine: a guide is a contract, not prose. `createGuide(markdown)` parses a guide's
11
12
  source once (via `@orkestrel/markdown`) into a `GuideInterface` — its `## Surface` identifiers
12
13
  (kind-tagged), its `## Methods` interface/method groups, every link, and its `## Tests`
13
14
  links, each cached at construction. `createSource({ files, module })` builds a
14
- `SourceInterface` that reflects the SAME two shapes — exported symbols and interface/class
15
- methods by scanning a consumer-gathered file inventory with plain-text line scanners
16
- (`exportsFrom`, `declarationBody` + `memberMethods`), never touching disk itself. A
17
- guides-parity test then asserts these two views agree in both directions: nothing documented
18
- is phantom, nothing exported is undocumented. `parseManifest` reads a `guides/README.md`'s
15
+ `SourceInterface` that reflects intentional direct declarations, conventional barrel-reachable
16
+ declarations, and interface/class methods by scanning a consumer-gathered file inventory with
17
+ plain-text line scanners, never touching disk itself. A guides-parity test asserts direct
18
+ declarations equal the barrel surface and the barrel surface equals the documented surface,
19
+ in both directions. `parseManifest` reads a `guides/README.md`'s
19
20
  `## By concept` table into the list of `{ concept, spec, source, tests }` entries a suite
20
21
  iterates to run this check once per documented concept.
21
22
 
@@ -25,17 +26,18 @@ iterates to run this check once per documented concept.
25
26
 
26
27
  The manifest/extraction shapes every check is built from, from [`types.ts`](../../src/core/types.ts).
27
28
 
28
- | Name | Kind | Shape |
29
- | ----------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
30
- | `ExportKind` | type | `'type' \| 'interface' \| 'const' \| 'function' \| 'class'` — the declaration kind half of a symbol's bijection key. |
31
- | `SurfaceSymbol` | interface | `{ name, kind }` — one documented / exported symbol. |
32
- | `GuideModule` | type | `string \| readonly string[]` — one source directory, or several for a layer guide spanning multiple directories. |
33
- | `ManifestEntry` | interface | `{ concept, spec, source, tests }` — one `## By concept` manifest row, paths normalized to workspace root. |
34
- | `MethodGroup` | interface | `{ interface, methods }` — one `#### \`Interface\`` block's documented method names, in table order. |
35
- | `GuideInterface` | interface | `{ sections, surface, methods, links, tests, patterns }` — the structured, pure view over one parsed guide. See [`## Methods`](#methods). |
36
- | `SourceInterface` | interface | `{ exports, methods, exists, hidden, examples }` — the reflected source truth a guide's surface is checked against. See [`## Methods`](#methods). |
37
- | `SourceOptions` | interface | `{ files, module }` — the construction input for a `Source`: a consumer-supplied file inventory plus the module scope to reflect. |
38
- | `DeclarationHead` | interface | `{ text, end }` — a declaration head joined into one line (across an oxfmt-wrapped signature) plus the index of the line ending in `{`. |
29
+ | Name | Kind | Shape |
30
+ | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
+ | `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. |
32
+ | `SurfaceSymbol` | interface | `{ name, kind }` — one documented / exported symbol. |
33
+ | `GuideModule` | type | `string \| readonly string[]` — one source directory, or several; `'.'` is the canonical workspace root. |
34
+ | `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`. |
35
+ | `ManifestEntry` | interface | `{ concept, spec, source, tests }` — one `## By concept` manifest row, paths normalized to workspace root. |
36
+ | `MethodGroup` | interface | `{ interface, methods }` — one `#### \`Interface\`` block's documented method names, in table order. |
37
+ | `GuideInterface` | interface | `{ sections, surface, methods, links, tests, patterns }` — the structured, pure view over one parsed guide. See [`## Methods`](#methods). |
38
+ | `SourceInterface` | interface | `{ exports, surface, methods, exists, hidden, examples }` — direct declarations, conventional barrel reachability, members, paths, discipline, and examples. See [`## Methods`](#methods). |
39
+ | `SourceOptions` | interface | `{ files, module }` — exact canonical-segment opaque workspace-relative inventory keys plus the canonicalized module scope to reflect. |
40
+ | `DeclarationHead` | interface | `{ text, end }` — a declaration head joined into one line (across an oxfmt-wrapped signature) plus the index of the line ending in `{`. |
39
41
 
40
42
  ### Constants
41
43
 
@@ -55,43 +57,47 @@ on, from [`constants.ts`](../../src/core/constants.ts).
55
57
  Pure, total leaves from [`helpers.ts`](../../src/core/helpers.ts) — the building blocks
56
58
  `parsers.ts`'s extractors and a consumer's parity test both reach for directly.
57
59
 
58
- | Name | Kind | Signature | Behavior |
59
- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
- | `moduleDirs` | function | `(module: GuideModule) => readonly string[]` | Normalizes a `GuideModule` scope to its directory list a single string becomes a one-element list. |
61
- | `moduleKeys` | function | `(files: Readonly<Record<string, string>>, module: GuideModule) => readonly string[]` | The file inventory's keys belonging to a `GuideModule` scope: `.ts` files under a scope directory, excluding that directory's `index.ts` and `*.test.ts`; sorted. |
62
- | `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. |
63
- | `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. |
64
- | `missingSymbols` | function | `(symbols: readonly SurfaceSymbol[], source: readonly SurfaceSymbol[]) => readonly string[]` | The `symbolKey` set-difference between two symbol lists. |
65
- | `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. |
66
- | `resolveLink` | function | `(from: string, target: string) => string` | Resolves a relative link `target` against `from` and normalizes the result, purely (no `node:path`). |
67
- | `firstCode` | function | `(nodes: readonly InlineNode[]) => string \| undefined` | The first code-span value found by descending an inline node list, following into `emphasis` / `link` children. |
68
- | `identifierOf` | function | `(code: string) => string` | The identifier prefix of a code-span text everything before its first `<`, trimmed (strips generic-parameter annotation). |
69
- | `kindIndex` | function | `(table: TableNode) => number \| undefined` | The index of a table's `Kind` column, found by its header text so it survives column reordering. |
70
- | `cellLinks` | function | `(cell: readonly InlineNode[]) => readonly string[]` | The link hrefs found within one table cell's inline content, in walk order. |
71
- | `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. |
72
- | `fenceImports` | function | `(fence: string) => readonly { specifier: string, names: readonly string[] }[]` | Parses a fence's `import` statements into per-specifier imported identifier names the FI check's core comparison. |
60
+ | Name | Kind | Signature | Behavior |
61
+ | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
62
+ | `normalizeDirectories` | function | `(module: GuideModule) => readonly string[]` | Canonicalizes through `resolvePath`, uses `'.'` for root, and removes duplicates in first-seen order. |
63
+ | `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. |
64
+ | `hasCanonicalSegments` | function | `(key: string) => boolean` | Rejects empty, `.` and `..` slash-separated segments without normalization while retaining ordinary dotfiles. |
65
+ | `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. |
66
+ | `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. |
67
+ | `missingSymbols` | function | `(symbols: readonly SurfaceSymbol[], source: readonly SurfaceSymbol[]) => readonly string[]` | The `symbolKey` set-difference between two symbol lists. |
68
+ | `extractSourceLines` | function | `(source: string) => readonly SourceLine[]` | Equal-length physical source, code, and JSDoc projection with bounded literal Unicode identifier slash state. |
69
+ | `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. |
70
+ | `resolveLink` | function | `(file: string, target: string) => string` | Derives a declaring file's directory, including workspace-root files, then delegates to `resolvePath`. |
71
+ | `resolvePath` | function | `(directory: string, target: string) => string` | Sole dot-segment reducer; returns `'.'` when no segment remains and preserves every excess leading parent. |
72
+ | `firstCode` | function | `(nodes: readonly InlineNode[]) => string \| undefined` | The first code-span value found by descending an inline node list, following into `emphasis` / `link` children. |
73
+ | `identifierOf` | function | `(code: string) => string` | The identifier prefix of a code-span text everything before its first `<`, trimmed (strips generic-parameter annotation). |
74
+ | `kindIndex` | function | `(table: TableNode) => number \| undefined` | The index of a table's `Kind` column, found by its header text so it survives column reordering. |
75
+ | `cellLinks` | function | `(cell: readonly InlineNode[]) => readonly string[]` | The link hrefs found within one table cell's inline content, in walk order. |
76
+ | `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. |
77
+ | `fenceImports` | function | `(fence: string) => readonly { specifier: string, names: readonly string[] }[]` | Parses a fence's `import` statements into per-specifier imported identifier names — the FI check's core comparison. |
73
78
 
74
79
  ### Parsers
75
80
 
76
81
  The guide/manifest extraction pipeline, from [`parsers.ts`](../../src/core/parsers.ts) —
77
82
  the orchestration `Guide` composes out of `helpers.ts`'s leaves.
78
83
 
79
- | Name | Kind | Signature | Behavior |
80
- | ----------------- | -------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
81
- | `exportsFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The module-scope exports declared in one file's source text, deduped by (kind, name); a generator export scans as `function`. |
82
- | `hiddenFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The module-scope declarations LACKING the `export` keyword the export-discipline (AGENTS §5) mirror of `exportsFrom`, same five-kind grammar. |
83
- | `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 `{`. |
84
- | `declarationBody` | function | `(source: string, keyword: 'class' \| 'interface', name: string) => readonly string[]` | The body lines of the named `export class` / `export interface` declaration in one file's source text. |
85
- | `memberMethods` | function | `(lines: readonly string[]) => readonly string[]` | The declared callable-member names in a declaration body plain / `async` / generator / optional; getters, setters, `static`, `#` privates never count. |
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
- | `examplesFrom` | function | `(source: string) => readonly string[]` | The exported functions in one file's source text whose immediately preceding JSDoc block carries `@example`. |
92
- | `exampleMethods` | function | `(lines: readonly string[]) => readonly string[]` | The `exampleMethods`-flavored `memberMethods` callable-member names in a declaration body whose preceding JSDoc carries `@example`. |
93
- | `extractPatterns` | function | `(document: MarkdownDocument) => readonly string[]` | Every fenced `ts` code block's body text anywhere in the guide document a full AST walk. |
94
- | `parseManifest` | function | `(markdown: string, base: string) => readonly ManifestEntry[]` | Parses a `## By concept` manifest table into `ManifestEntry` rows, resolving Spec / Source / Tests links against `base`. |
84
+ | Name | Kind | Signature | Behavior |
85
+ | --------------------- | -------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `exportsFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | Direct five-kind exports over projected lines with an uninterrupted column-zero head; projection never widens membership. |
87
+ | `hiddenFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The non-exported mirror with the same projected, uninterrupted column-zero head and five-kind population. |
88
+ | `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 `{`. |
89
+ | `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. |
90
+ | `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. |
91
+ | `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). |
92
+ | `extractSurface` | function | `(document: MarkdownDocument) => readonly SurfaceSymbol[]` | Every `## Surface` identifier: each table's rows union every backticked H3 entity heading, deduped by `symbolKey`. |
93
+ | `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. |
94
+ | `extractLinks` | function | `(document: MarkdownDocument) => readonly string[]` | Every link href in the guide document, including table cells — a full, depth-first AST walk. |
95
+ | `extractTests` | function | `(document: MarkdownDocument) => readonly string[]` | The relative test links declared under `## Tests`. |
96
+ | `extractExampleLines` | function | `(lines: readonly SourceLine[]) => readonly SourceLine[]` | The next physical candidate after the authoritative exact `@example` span in a leading chain. |
97
+ | `examplesFrom` | function | `(source: string) => readonly string[]` | Matches exported functions against shared eligible genuine JSDoc adjacency and aligned code. |
98
+ | `exampleMethods` | function | `(lines: readonly string[]) => readonly string[]` | Matches callable members against the same shared eligible genuine JSDoc adjacency and aligned code. |
99
+ | `extractPatterns` | function | `(document: MarkdownDocument) => readonly string[]` | Every fenced `ts` code block's body text anywhere in the guide document a full AST walk. |
100
+ | `parseManifest` | function | `(markdown: string, directory: string) => readonly ManifestEntry[]` | Resolves manifest links and canonicalizes Source values through `normalizeDirectories`, preserving one-versus-many shape. |
95
101
 
96
102
  ### Shapers
97
103
 
@@ -141,13 +147,17 @@ surface.
141
147
  ### `Source`
142
148
 
143
149
  The implementing class of `SourceInterface`, from [`Source.ts`](../../src/core/Source.ts). A
144
- pure reflection over a consumer-supplied file inventory (root-relative path → file text) plus
145
- a module scope reflects exported symbols (`exportsFrom`) and interface/class methods
146
- (`declarationBody` + `memberMethods`) with text-only line scanners, never the TypeScript
147
- compiler API or the filesystem. `exports()` is computed once on first access and cached; the
148
- consumer gathers `files` however their environment allows (`node:fs` in a Node script,
149
- `import.meta.glob` in a browser/vitest run). See [`## Methods`](#methods) for its public
150
- call-signature surface.
150
+ pure reflection over a consumer-supplied file inventory (root-relative path → file text) plus a
151
+ module scope. `exports()` inventories direct `type`, `interface`, `const`, `function`, and
152
+ `class` declarations in the selected canonical directories' exact opaque module keys over
153
+ comment/template-excluded projected code lines; `enum` is outside this reflection population without being forbidden by
154
+ general package policy;
155
+ `surface()` inventories declarations reachable through each selected directory's conventional
156
+ root barrel. Both projections are computed on first access, cached, deduplicated by name and
157
+ kind, and sorted by name. Member structure comes from projected lines while raw bodies preserve
158
+ JSDoc evidence. `Source` never uses the
159
+ TypeScript compiler API or filesystem; the consumer gathers `files` however its environment
160
+ allows. See [`## Methods`](#methods) for the public call-signature surface.
151
161
 
152
162
  ## Methods
153
163
 
@@ -167,19 +177,20 @@ backticked name (AGENTS §22).
167
177
 
168
178
  #### `SourceInterface`
169
179
 
170
- | Method | Returns | Behavior |
171
- | ---------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- |
172
- | `exports` | `readonly SurfaceSymbol[]` | Every module-scope export, including type-only, by (name, kind). |
173
- | `methods` | `readonly string[]` | The call-signature members of the `class` / `interface` named `name`. |
174
- | `exists` | `boolean` | Whether a workspace-root-relative path exists in the inventory. |
175
- | `hidden` | `readonly SurfaceSymbol[]` | Every module-scope declaration LACKING `export` (AGENTS §5). |
176
- | `examples` | `readonly string[]` | The exported functions (or, given `name`, the members of `name`) whose preceding JSDoc carries `@example`. |
180
+ | Method | Returns | Behavior |
181
+ | ---------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | `exports` | `readonly SurfaceSymbol[]` | Direct `type`, `interface`, `const`, `function`, and `class` declarations in the selected module keys. |
183
+ | `surface` | `readonly SurfaceSymbol[]` | Every declaration reachable through the selected directories' conventional root `index.ts` barrels. |
184
+ | `methods` | `readonly string[]` | The call-signature members of the `class` / `interface` named `name`. |
185
+ | `exists` | `boolean` | Whether a workspace-root-relative path exists in the inventory. |
186
+ | `hidden` | `readonly SurfaceSymbol[]` | Every module-scope declaration LACKING `export` (AGENTS §5). |
187
+ | `examples` | `readonly string[]` | The exported functions (or, given `name`, members) whose eligible leading JSDoc chain ends in an exact block-position `@example` span. |
177
188
 
178
189
  ## The extraction model
179
190
 
180
191
  `Guide` parses a guide's markdown once (via `@orkestrel/markdown`'s `createMarkdown`) and
181
- caches five projections at construction — `sections`, `surface`, `methods`, `links`, `tests`
182
- — so every accessor is a cheap array return, not a re-parse. `extractSurface` scopes to the
192
+ caches six projections at construction — `sections`, `surface`, `methods`, `links`, `tests`,
193
+ `patterns` — so every accessor is a cheap array return, not a re-parse. `extractSurface` scopes to the
183
194
  `## Surface` section (`sectionBlocks`) and unions two sources of identifiers: every table's
184
195
  column-0 code span (kind read from the column whose header text is `Kind`, located
185
196
  positionally so it survives reordering) and every backticked H3 entity heading (a class
@@ -189,32 +200,98 @@ table becomes that interface's `MethodGroup`. Both extractors normalize every id
189
200
  through `identifierOf`, stripping a generic-parameter annotation (`` `WidgetInterface<T>` ``
190
201
  → `WidgetInterface`) so the bijection key is always the bare name. `extractLinks` walks the
191
202
  whole AST for every `link` node (table cells included); `extractTests` does the same walk
192
- scoped to the `## Tests` section only.
203
+ scoped to the `## Tests` section only; and `extractPatterns` walks the whole AST for every
204
+ fenced `ts` code block.
205
+
206
+ `parseManifest(markdown, directory)` resolves every Spec, Source, and Tests link through
207
+ `resolvePath(directory, target)`, so root, nested, and dotted directory names are ordinary path
208
+ components. Source links then canonicalize through `normalizeDirectories`: `'.'` is workspace root and
209
+ duplicates collapse in first-seen order. `resolvePath` owns the only forward-slash dot-segment reducer, returns `'.'` when all components cancel, and retains every
210
+ excess leading parent. `resolveLink(file, target)` adds the declaring-file boundary: it derives
211
+ the directory before the final slash, treats a slashless file as workspace-root, and delegates.
212
+ Neither helper consults the filesystem, infers extensions, or guesses whether a dotted component
213
+ is a file.
193
214
 
194
215
  `Source` never parses markdown or touches disk — it scans a consumer-supplied file
195
- inventory's TEXT with regex-based line scanners. `exportsFrom` matches
196
- `^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per line, deduped by
197
- (kind, name). `declarationBody` locates a named `export class` / `export interface` head
198
- (joining an oxfmt-wrapped signature via `joinHead` until a line ends in `{`), then collects
199
- body lines to the column-0 `}`. `memberMethods` matches
216
+ inventory's text with deliberately narrow physical-line grammars. `extractSourceLines` is the
217
+ sole character engine and emits one `SourceLine` per LF/CRLF physical line plus the final line:
218
+ `source` is exact, `code` is equal-length with real comments and complete template tokens masked,
219
+ and `jsdoc` is equal-length with every genuine span retained at its physical column or is
220
+ `undefined`. A genuine JSDoc capture starts only from ordinary reflection code, never inside an
221
+ open comment, raw template, or template substitution. The engine traverses escapes, nested
222
+ template substitutions, strings, regex character classes, comments, and division/regex contexts
223
+ only to identify those spans; it does not parse TypeScript. Literal ECMAScript Unicode identifiers
224
+ participate in bounded slash-state recognition, but escaped identifier spellings are not decoded.
225
+ Regex recognition is a bounded lexical goal: because the projection cannot infer whether `}`
226
+ closes an expression or a statement/declaration block, slash immediately after bare `}` is
227
+ division and a post-brace regex statement needs an explicit `;`. General semicolonless
228
+ declaration/ASI classification is also outside this finite projector; callers use an explicit
229
+ `;` before a slash-leading statement after such a declaration. Projection preserves columns;
230
+ each consumer still owns membership. Direct and hidden heads remain uninterrupted and
231
+ column-zero, while barrel rows retain their separate whitespace-tolerant whole-line grammar.
232
+
233
+ `extractExampleLines` walks only `SourceLine` records. A genuine JSDoc opener is eligible only
234
+ when it is the first non-whitespace source material. Within a leading whitespace-separated chain,
235
+ each later span replaces the earlier one and is authoritative. Only an exact block-position
236
+ `@example` tag qualifies; same-line title text is allowed. Source material between or after spans
237
+ severs association, a leading JSDoc on the next line replaces pending state, and any other next
238
+ physical record is returned once as the candidate. `examplesFrom` and `exampleMethods` share this
239
+ adjacency parser and apply their distinct exported-function and callable-member grammars only to
240
+ `code`.
241
+
242
+ Across the `.ts` module keys under each selected directory, excluding its root `index.ts` and
243
+ every `*.test.ts`, `exportsFrom` matches
244
+ `^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per projected line, deduped by
245
+ (kind, name). `hiddenFrom` applies the same five-kind head grammar without `export`. Comment and
246
+ template payload, enums, `let`, `var`, and other TypeScript declaration forms are outside these
247
+ two populations; enum exclusion describes reflection scope, not a general package-policy ban.
248
+ `declarationBody` locates a named real `export class` / `export interface` head and exact
249
+ column-zero close in projected lines (joining an oxfmt-wrapped signature via `joinHead`), then
250
+ returns the aligned raw body. `memberMethods` projects that body once and matches
200
251
  `^\t(?:async )?\*?(\w+)(<[^>]*>)?\??\(` against those body lines — plain / `async` /
201
252
  generator / optional methods count; getters, setters, `static` members, and `#` privates
202
253
  never match (their keyword or sigil breaks the `name(` shape), and `constructor` is filtered
203
- out of `Source.methods`. `moduleKeys` scopes the inventory to one `GuideModule`'s `.ts`
204
- files, excluding each scope directory's own `index.ts` and any `*.test.ts` file. `hiddenFrom`
205
- runs the mirror-image scan the same five-kind grammar, but only lines LACKING the `export`
206
- keyword so `Source.hidden()` mechanically asserts AGENTS §5's export-discipline rule and
207
- catches a hidden declaration the surface bijection alone would never see (it never appears as
208
- an export, so it never shows up as a missing Surface row either).
254
+ out of `Source.methods`. `selectModuleKeys` scopes the inventory to one `GuideModule`'s `.ts` files,
255
+ excluding each scope directory's own `index.ts` and any `*.test.ts` file. `Source.hidden()`
256
+ mechanically asserts AGENTS §5's export-discipline rule and catches a hidden five-kind
257
+ declaration the surface bijection alone would never see.
258
+
259
+ `Source.surface()` starts only at exact `index.ts` for canonical `'.'`, or exact
260
+ `<directory>/index.ts` for each nested directory returned by `normalizeDirectories(module)`.
261
+ Inventory keys remain opaque and are never normalized; `hasCanonicalSegments` rejects empty,
262
+ `.` and `..` segments while retaining dotfiles. Both the initial index and every resolved target
263
+ must be canonical after relative-row reduction; a parent row that reduces to a canonical key
264
+ remains valid. A complete row must be equivalent to
265
+ `export * from './target.js'`: the target starts with `./` or `../` and ends in `.js`;
266
+ surrounding whitespace, either quote, an optional semicolon, and an optional trailing `//`
267
+ comment is accepted, while the inactive quote delimiter remains target data. The same projection
268
+ masks actual comment/template spans, preserving valid row code around them and markers inside a
269
+ quoted target. Arbitrary trailing source is rejected. Only the terminal `.js` becomes `.ts`;
270
+ `resolveLink(currentIndex, target)` derives the current index file's directory and delegates to
271
+ `resolvePath`, the only dot-segment reducer. Exact workspace-root `index.ts` and nested targets
272
+ ending `/index.ts` recurse as barrels, while another exact `.ts` target contributes its direct
273
+ `exportsFrom` declarations. One visited set terminates self-cycles, multi-index cycles, repeated
274
+ rows, and diamonds. `symbolKey` deduplicates same-name/same-kind rows while retaining
275
+ same-name/different-kind rows, and the final list uses the same name sort as `exports()`.
276
+
277
+ Missing roots and targets, empty barrels, and unsupported rows contribute nothing without
278
+ throwing, while valid siblings continue. Named, default, namespace, type-only, non-relative, and
279
+ extensionless re-exports are outside the population. So are enums, declarations written directly
280
+ in an `index.ts`, and re-export syntax inside a terminal non-index target. Ignoring these forms does not
281
+ validate them: repository barrel policy, typechecking, and builds own validity. There is no
282
+ filesystem, package-map, alias, config, directory-index fallback, or general TypeScript module
283
+ resolution. The supplied inventory is never mutated; the first computed result is cached and the
284
+ same readonly array instance is returned thereafter.
209
285
 
210
286
  ## The check catalog
211
287
 
212
288
  Every guides-parity check reduces to `expect([]).toEqual([])`, paired with a non-vacuousness
213
289
  guard so a renamed heading fails loudly instead of passing on an empty extraction:
214
290
 
215
- - **SB — Surface bijection (kind folded in).** `guide.surface()` vs `source.exports()`,
216
- `missingSymbols` both directions over `symbolKey` a symbol may drift in neither name nor
217
- kind. Guard: `guide.surface().length > 0`.
291
+ - **SB — Direct/barrel/guide surface parity (kind folded in).** `missingSymbols` proves all four
292
+ directions: direct declarations barrel surface, barrel surface direct declarations, barrel
293
+ surface guide surface, and guide surface → barrel surface. Every comparison uses `symbolKey`,
294
+ so a declaration may drift in neither name nor kind. Guard: `guide.surface().length > 0`.
218
295
  - **MB — Methods bijection + class-no-extra.** Per `MethodGroup`, its `methods` vs
219
296
  `source.methods(group.interface)`, `findMissing` both directions; then, by the
220
297
  `XInterface → X` naming convention, `findMissing(source.methods('X'), group.methods)` must
@@ -227,15 +304,24 @@ guard so a renamed heading fails loudly instead of passing on an empty extractio
227
304
  - **NV — Non-vacuousness.** `parseManifest` yields at least one entry; each guide's
228
305
  `surface()` and every `MethodGroup` is non-empty — the guard behind every other check.
229
306
  - **EX — Examples presence.** A documented symbol "has an example" when its bare name
230
- appears (word boundary) in any of `guide.patterns()`'s fence bodies, OR its source
231
- JSDoc carries an `@example` block (`source.examples()` / `source.examples(name)`).
307
+ appears (word boundary) in any of `guide.patterns()`'s fence bodies, OR its source has
308
+ an immediately preceding eligible leading JSDoc chain whose final authoritative span carries
309
+ an exact block-position `@example` tag, with optional title text,
310
+ (`source.examples()` / `source.examples(name)`).
232
311
  Applies to every `function`-kind `Surface` symbol and every `MethodGroup` member.
233
312
  Presence-only — fence and JSDoc CONTENT are never checked. `findUnexampled` is the
234
313
  comparison. Guard: the SB/MB extractions this check reuses already prove non-vacuous.
235
314
  - **FI — Fence-import reality.** Every `import { ... } from 'specifier'` in a
236
315
  `guide.patterns()` fence, for a SELF specifier (this repo's own package name / path
237
- alias), imports only names that exist in `source.exports()`. `fenceImports` parses the
238
- statement; `findMissing` diffs the imported names against `source.exports()`'s names.
316
+ alias), imports only names that exist in `source.surface()`. `fenceImports` parses the
317
+ statement; `findMissing` diffs the imported names against the public/barrel surface's names.
318
+
319
+ Permanent controls bind the SB population boundaries through production `Source`, `Guide`,
320
+ `missingSymbols`, and `symbolKey`: a stranded direct declaration must be missing from the barrel;
321
+ a phantom Guide row must be missing from the barrel; kind drift must fail in both barrel/Guide
322
+ directions; a barrel-only declaration outside `selectModuleKeys()` must be missing from direct exports;
323
+ a correlated commented declaration must remain absent from direct and barrel populations while
324
+ failing Guide-to-barrel; and a workspace-root `index.ts` hop must reach its real terminal symbol.
239
325
 
240
326
  ## The pure file-inventory model
241
327
 
@@ -245,7 +331,8 @@ CONSUMER gathers however their runtime allows: a recursive `node:fs` walk in a N
245
331
  run, `import.meta.glob('/**/*.ts', { eager: true, query: '?raw', import: 'default' })` in a
246
332
  browser/vitest run, or a static bundle in any other environment. This keeps the package
247
333
  itself environment-agnostic while every check still runs against real, on-disk truth in the
248
- consumer's own test.
334
+ consumer's own test. The inventory must include each selected module's root `index.ts` and every
335
+ reachable exact `.ts` target for `surface()` to observe them; absent keys remain empty reflection.
249
336
 
250
337
  ## Patterns
251
338
 
@@ -266,12 +353,14 @@ import { createSource } from '@orkestrel/guide'
266
353
 
267
354
  const source = createSource({
268
355
  files: {
356
+ 'src/core/index.ts': "export * from './Guide.js'\nexport * from './types.js'\n",
269
357
  'src/core/Guide.ts': 'export class Guide {}\n',
270
358
  'src/core/types.ts': 'export interface GuideInterface {\n\tsections(): void\n}\n',
271
359
  },
272
360
  module: 'src/core',
273
361
  })
274
362
  source.exports() // [{ name: 'Guide', kind: 'class' }, { name: 'GuideInterface', kind: 'interface' }]
363
+ source.surface() // [{ name: 'Guide', kind: 'class' }, { name: 'GuideInterface', kind: 'interface' }]
275
364
  source.methods('GuideInterface') // ['sections']
276
365
  source.exists('src/core/Guide.ts') // true
277
366
  ```
@@ -283,30 +372,52 @@ import { createGuide, createSource, missingSymbols } from '@orkestrel/guide'
283
372
 
284
373
  const guide = createGuide('## Surface\n\n| Name | Kind |\n| --- | --- |\n| `Guide` | class |')
285
374
  const source = createSource({
286
- files: { 'src/core/Guide.ts': 'export class Guide {}\n' },
375
+ files: {
376
+ 'src/core/index.ts': "export * from './Guide.js'\n",
377
+ 'src/core/Guide.ts': 'export class Guide {}\n',
378
+ },
287
379
  module: 'src/core',
288
380
  })
289
381
 
290
- // Every check collapses to an empty-array assertion, both directions.
291
- missingSymbols(source.exports(), guide.surface()) // [] — every export is documented
292
- missingSymbols(guide.surface(), source.exports()) // [] — every documented symbol is real
382
+ // Direct declarations, public barrel, and guide surface agree in all four directions.
383
+ missingSymbols(source.exports(), source.surface()) // []
384
+ missingSymbols(source.surface(), source.exports()) // []
385
+ missingSymbols(source.surface(), guide.surface()) // []
386
+ missingSymbols(guide.surface(), source.surface()) // []
387
+ ```
388
+
389
+ ### Project source into physical code lines
390
+
391
+ ```ts
392
+ import { extractSourceLines } from '@orkestrel/guide'
393
+
394
+ extractSourceLines('export const visible = true // note\n')
395
+ // [{ source: 'export const visible = true // note', code: 'export const visible = true ', jsdoc: undefined }, ...]
396
+ ```
397
+
398
+ ### Resolve directory and file targets
399
+
400
+ ```ts
401
+ import { resolveLink, resolvePath } from '@orkestrel/guide'
402
+
403
+ resolvePath('guides/nested', './spec.md') // 'guides/nested/spec.md'
404
+ resolveLink('index.ts', './root.ts') // 'root.ts'
293
405
  ```
294
406
 
295
407
  ## Tests
296
408
 
297
- - [`tests/src/core/helpers.test.ts`](../../tests/src/core/helpers.test.ts) — `moduleDirs` / `moduleKeys` / `symbolKey` / `findMissing` / `missingSymbols` / `isExternalLink` / `resolveLink` / `firstCode` / `identifierOf` / `kindIndex`.
298
- - [`tests/src/core/parsers.test.ts`](../../tests/src/core/parsers.test.ts) — `exportsFrom` / `joinHead` / `declarationBody` / `memberMethods` / `sectionBlocks` / `extractSurface` / `extractMethods` / `extractLinks` / `extractTests` / `parseManifest`, incl. entity-heading surface and multi-dir Source cells.
409
+ - [`tests/src/core/helpers.test.ts`](../../tests/src/core/helpers.test.ts) — direct `SourceLine`, lexical, JSDoc-alignment, canonical-key, runtime-name, `resolvePath`, and `resolveLink` invariants; all remaining helper leaves.
410
+ - [`tests/src/core/parsers.test.ts`](../../tests/src/core/parsers.test.ts) — projected five-kind direct/hidden reflection, genuine JSDoc example adjacency, faux JSDoc exclusion, nested-directory `parseManifest`, and every guide/manifest extractor.
299
411
  - [`tests/src/core/validators.test.ts`](../../tests/src/core/validators.test.ts) — `isExportKind` / `isSurfaceSymbol` / `isMethodGroup` / `isManifestEntry`.
300
412
  - [`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.
301
413
  - [`tests/src/core/factories.test.ts`](../../tests/src/core/factories.test.ts) — `createGuide` / `createSource` + the compiled symbol/group/manifest contracts.
302
- - [`tests/src/core/Guide.test.ts`](../../tests/src/core/Guide.test.ts) — `Guide`'s five cached projections, constructed once, called repeatedly.
303
- - [`tests/src/core/Source.test.ts`](../../tests/src/core/Source.test.ts) — `Source`'s pure reflection against an in-memory fixture inventory, incl. `memberMethods` on every excluded shape (getters, setters, `static`, `#` privates, `constructor`).
304
- - [`tests/guides/src/parity.test.ts`](../../tests/guides/src/parity.test.ts) — the drop-in guides-parity suite, run against THIS repo's own `guides/README.md` manifest the self-dogfooding acceptance criterion.
414
+ - [`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.
415
+ - [`tests/src/core/Source.test.ts`](../../tests/src/core/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.
416
+ - [`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`.
417
+ - [`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.
305
418
 
306
419
  ## See also
307
420
 
308
421
  - `AGENTS.md` (workspace root) — the rules; §22 documentation-as-contracts.
309
- - `PROPOSAL.md` (workspace root) — the design proposal this package implements; §5 the check catalog, §6 the drop-in, §7 source-scanning fidelity.
310
422
  - [`README.md`](../README.md) — the guides index.
311
- - [`markdown.md`](markdown.md) — the dependency mirror for `@orkestrel/markdown`, the AST/parse layer `Guide` and `Source` are built on.
312
- </content>
423
+ - [`markdown.md`](markdown.md) — the dependency mirror for `@orkestrel/markdown`, the AST/parse layer `Guide` is built on.