@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.
- package/README.md +18 -103
- package/dist/bin/main.js +95 -27
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/CLAUDE.md +6 -0
- package/dist/host/agents/orchestration.md +23 -15
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +6 -6
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +4 -4
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +5 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +15 -15
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +43 -17
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +41 -16
- package/dist/host/claude/agents/orkestrel.md +56 -56
- package/dist/host/claude/agents/reviewer.md +13 -0
- package/dist/host/claude/rules/architecture.md +51 -45
- package/dist/host/claude/rules/documentation.md +18 -1
- package/dist/host/claude/rules/portability.md +2 -0
- package/dist/host/claude/rules/quality.md +1 -1
- package/dist/host/claude/rules/tests.md +12 -11
- package/dist/host/claude/rules/typescript.md +5 -0
- package/dist/host/claude/rules/workspace.md +23 -18
- package/dist/host/claude/rules/writing.md +4 -0
- package/dist/host/codex/agents/orkestrel.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -2
- package/dist/host/configs/helpers.ts +311 -2
- package/dist/host/configs/policy.ts +1100 -51
- package/dist/host/dotfiles/oxlintrc.json +72 -1
- package/dist/host/guides/guide.md +749 -222
- package/dist/host/guides/scaffold.md +472 -378
- package/dist/host/manifest.json +34 -33
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +1200 -16
- package/dist/host/tests/policy.test.ts +157 -173
- package/dist/host/tests/setupPolicy.ts +522 -1007
- package/dist/src/core/index.cjs +371 -277
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +130 -120
- package/dist/src/core/index.d.ts +130 -120
- package/dist/src/core/index.js +371 -276
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +28 -21
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +38 -33
- package/dist/src/server/index.d.ts +38 -33
- package/dist/src/server/index.js +28 -21
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -16
|
@@ -1,62 +1,109 @@
|
|
|
1
1
|
# Guide
|
|
2
2
|
|
|
3
|
-
> A
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
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
|
|
51
|
-
on, from [`constants.ts`](../src/core/constants.ts).
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
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
|
|
69
|
-
|
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
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 |
|
|
109
|
-
| --------------- | -------- | ------------------------------------------------------------------- |
|
|
110
|
-
| `parseManifest` | function | `(markdown: string, directory: string) => readonly ManifestEntry[]` |
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
| Name | Kind |
|
|
119
|
-
| -------------------- | ----- |
|
|
120
|
-
| `surfaceSymbolShape` | const |
|
|
121
|
-
| `methodGroupShape` | const |
|
|
122
|
-
| `
|
|
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 |
|
|
130
|
-
| ----------------- | ----- |
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
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 |
|
|
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`
|
|
145
|
-
| `createSurfaceSymbolContract` | function | `() => ContractInterface<SurfaceSymbol>` | Compiles `surfaceSymbolShape` into a guard
|
|
146
|
-
| `createMethodGroupContract` | function | `() => ContractInterface<MethodGroup>` | Compiles `methodGroupShape` into a guard
|
|
147
|
-
| `
|
|
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
|
-
|
|
153
|
-
`@orkestrel/markdown` and never touches the filesystem — `Guide`
|
|
154
|
-
|
|
155
|
-
readonly array on every call. See [`## Methods`](#methods) for its
|
|
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
|
-
|
|
164
|
-
comment/template-excluded projected code lines; `enum` is outside this reflection population without
|
|
165
|
-
general package policy;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 (
|
|
352
|
+
backticked name (`.claude/rules/documentation.md` § Parity).
|
|
191
353
|
|
|
192
354
|
#### `GuideInterface`
|
|
193
355
|
|
|
194
|
-
| Method | Returns |
|
|
195
|
-
| ---------- | -------------------------- |
|
|
196
|
-
| `sections` | `readonly string[]` |
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
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 |
|
|
206
|
-
| ---------- | -------------------------- |
|
|
207
|
-
| `exports` | `readonly SurfaceSymbol[]` |
|
|
208
|
-
| `surface` | `readonly SurfaceSymbol[]` |
|
|
209
|
-
| `methods` | `readonly
|
|
210
|
-
| `exists` | `boolean` |
|
|
211
|
-
| `hidden` | `readonly SurfaceSymbol[]` |
|
|
212
|
-
| `examples` | `readonly
|
|
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
|
|
217
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
241
|
-
|
|
|
242
|
-
| `source`
|
|
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 (
|
|
247
|
-
caches
|
|
248
|
-
`fences` — so every accessor is a cheap array return, not a re-parse.
|
|
249
|
-
`## Surface` section (`
|
|
250
|
-
column-0 code span (
|
|
251
|
-
positionally so it survives reordering) and every
|
|
252
|
-
|
|
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 `
|
|
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, ``, 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
|
|
298
|
-
|
|
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. `
|
|
301
|
-
adjacency parser and apply their distinct
|
|
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`, `
|
|
306
|
-
`^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per projected line
|
|
307
|
-
|
|
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
|
-
|
|
310
|
-
`
|
|
311
|
-
column-zero
|
|
312
|
-
returns
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
`
|
|
336
|
-
rows, and diamonds. `
|
|
337
|
-
same-name/different-
|
|
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 (
|
|
354
|
-
|
|
355
|
-
surface → guide surface, and guide surface → barrel surface. Every comparison uses `
|
|
356
|
-
so a declaration may drift in neither name nor
|
|
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,
|
|
375
|
-
authoritative span carries an
|
|
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`-
|
|
378
|
-
Presence-only — fence and JSDoc
|
|
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
|
|
382
|
-
alias), imports only names that exist in `source.surface()`. `
|
|
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
|
-
`
|
|
387
|
-
a phantom Guide row must be missing from the barrel;
|
|
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
|
|
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
|
-
|
|
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
|
|
399
|
-
|
|
400
|
-
|
|
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',
|
|
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',
|
|
440
|
-
source.surface() // [{ name: 'Guide',
|
|
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',
|
|
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,
|
|
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
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
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) — `
|
|
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
|
|
509
|
-
- [`tests/src/core/Guide.test.ts`](../tests/src/core/Guide.test.ts) — `Guide`'s
|
|
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) — `
|
|
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
|
|
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; §
|
|
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.
|