@rarusoft/dendrite-wiki 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +79 -0
  2. package/dist/api-extractor/extract.js +269 -0
  3. package/dist/api-extractor/language-extractor.js +15 -0
  4. package/dist/api-extractor/python-extractor.js +358 -0
  5. package/dist/api-extractor/render.js +195 -0
  6. package/dist/api-extractor/tree-sitter-extractor.js +1079 -0
  7. package/dist/api-extractor/types.js +11 -0
  8. package/dist/api-extractor/typescript-extractor.js +50 -0
  9. package/dist/api-extractor/walk.js +178 -0
  10. package/dist/api-reference.js +438 -0
  11. package/dist/benchmark-events.js +129 -0
  12. package/dist/benchmark.js +270 -0
  13. package/dist/binder-export.js +381 -0
  14. package/dist/canonical-target.js +168 -0
  15. package/dist/chart-insert.js +377 -0
  16. package/dist/chart-prompts.js +414 -0
  17. package/dist/context-cache.js +98 -0
  18. package/dist/contradicts-shipped-memory.js +232 -0
  19. package/dist/diff-context.js +142 -0
  20. package/dist/doctor.js +220 -0
  21. package/dist/generated-docs.js +219 -0
  22. package/dist/i18n.js +71 -0
  23. package/dist/index.js +49 -0
  24. package/dist/librarian.js +255 -0
  25. package/dist/maintenance-actions.js +244 -0
  26. package/dist/maintenance-inbox.js +842 -0
  27. package/dist/maintenance-runner.js +62 -0
  28. package/dist/page-drift.js +225 -0
  29. package/dist/page-inbox.js +168 -0
  30. package/dist/report-export.js +339 -0
  31. package/dist/review-bridge.js +1386 -0
  32. package/dist/search-index.js +199 -0
  33. package/dist/store.js +1617 -0
  34. package/dist/telemetry-defaults.js +44 -0
  35. package/dist/telemetry-report.js +263 -0
  36. package/dist/telemetry.js +544 -0
  37. package/dist/wiki-synthesis.js +901 -0
  38. package/package.json +35 -0
  39. package/src/api-extractor/extract.ts +333 -0
  40. package/src/api-extractor/language-extractor.ts +37 -0
  41. package/src/api-extractor/python-extractor.ts +380 -0
  42. package/src/api-extractor/render.ts +267 -0
  43. package/src/api-extractor/tree-sitter-extractor.ts +1210 -0
  44. package/src/api-extractor/types.ts +41 -0
  45. package/src/api-extractor/typescript-extractor.ts +56 -0
  46. package/src/api-extractor/walk.ts +209 -0
  47. package/src/api-reference.ts +552 -0
  48. package/src/benchmark-events.ts +216 -0
  49. package/src/benchmark.ts +376 -0
  50. package/src/binder-export.ts +437 -0
  51. package/src/canonical-target.ts +192 -0
  52. package/src/chart-insert.ts +478 -0
  53. package/src/chart-prompts.ts +417 -0
  54. package/src/context-cache.ts +129 -0
  55. package/src/contradicts-shipped-memory.ts +311 -0
  56. package/src/diff-context.ts +187 -0
  57. package/src/doctor.ts +260 -0
  58. package/src/generated-docs.ts +316 -0
  59. package/src/i18n.ts +106 -0
  60. package/src/index.ts +59 -0
  61. package/src/librarian.ts +331 -0
  62. package/src/maintenance-actions.ts +314 -0
  63. package/src/maintenance-inbox.ts +1132 -0
  64. package/src/maintenance-runner.ts +85 -0
  65. package/src/page-drift.ts +292 -0
  66. package/src/page-inbox.ts +254 -0
  67. package/src/report-export.ts +392 -0
  68. package/src/review-bridge.ts +1729 -0
  69. package/src/search-index.ts +266 -0
  70. package/src/store.ts +2171 -0
  71. package/src/telemetry-defaults.ts +50 -0
  72. package/src/telemetry-report.ts +365 -0
  73. package/src/telemetry.ts +757 -0
  74. package/src/wiki-synthesis.ts +1307 -0
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @rarusoft/dendrite-wiki
2
+
3
+ > The markdown-wiki adapter for [`@rarusoft/dendrite-memory`](../memory/).
4
+
5
+ A VitePress-rendered living wiki that pairs with the AI memory brain. Implements `CanonicalTarget` against `docs/wiki/`, owns the wiki page store / lint / search / synthesis / maintenance review surface / browser-side review bridge / API reference generator / chart insertion / telemetry pipeline / doctor health check.
6
+
7
+ ## Status
8
+
9
+ `0.1.0-alpha.0` — publish-prep alpha. The package is now public-publishable from the monorepo workflow and pairs with `@rarusoft/dendrite-memory`. Publish both extracted packages under the same dist-tag during the first alpha pass. See the [Library Extraction Roadmap](../../docs/wiki/library-extraction-roadmap.md) for the migration story.
10
+
11
+ ## What's inside
12
+
13
+ - **`store`** — wiki page CRUD + lint + claims + context briefing. The wiki-tier counterpart of the brain's `memory-store`.
14
+ - **`canonical-target`** — `WikiCanonicalTarget` implementation of the brain's interface. Self-registers as the default at module load.
15
+ - **`search-index`** — wiki search ranking (re-uses the brain's tokenizer).
16
+ - **`context-cache`** — `wiki_context` LRU+TTL cache; subscribes to brain mutation events so writes don't serve stale briefings.
17
+ - **`maintenance-actions` / `maintenance-inbox` / `maintenance-runner`** — the Review Board's verb side.
18
+ - **`page-drift` / `contradicts-shipped-memory`** — wiki lint rules that compare prose to shipped memories.
19
+ - **`page-inbox` / `librarian`** — per-page and multi-category audit aggregators.
20
+ - **`review-bridge`** — HTTP bridge for the browser-viewable Review Board.
21
+ - **`api-reference` + `api-extractor/`** — multi-language API doc generator (TypeScript / tree-sitter / Python).
22
+ - **`chart-insert` / `chart-prompts`** — Mermaid chart authoring.
23
+ - **`wiki-synthesis`** — LLM-assisted wiki narration (claims, guidance, proposals, drift resolution, chart synthesis, memory auto-clean decisions).
24
+ - **`telemetry` / `telemetry-defaults` / `telemetry-report`** — opt-in aggregate-counters upload pipeline.
25
+ - **`benchmark` / `benchmark-events`** — wiki benchmark snapshots and per-session event capture.
26
+ - **`report-export` / `binder-export`** — HTML report and printable binder exports.
27
+ - **`doctor`** — health-check audit.
28
+ - **`diff-context`** — git diff → relevant memories + skills aggregator.
29
+ - **`generated-docs`** — derived artifact refresher (search index, maintenance-inbox.json, etc.).
30
+ - **`i18n`** — translation table.
31
+
32
+ ## Quickstart
33
+
34
+ ```ts
35
+ import '@rarusoft/dendrite-wiki'; // auto-registers WikiCanonicalTarget on @rarusoft/dendrite-memory
36
+
37
+ import { rememberProjectMemory, applyProjectMemoryPromotion } from '@rarusoft/dendrite-memory';
38
+ import { listWikiPages, readWikiPage, buildWikiContext } from '@rarusoft/dendrite-wiki';
39
+
40
+ // Brain promotion now resolves against the wiki adapter — promoted memories
41
+ // land as markdown bullets in the appropriate docs/wiki/<slug>.md page with a
42
+ // provenance line per record.
43
+ const memory = await rememberProjectMemory({ /* … */ });
44
+ await applyProjectMemoryPromotion({ memoryIds: [memory.id] });
45
+
46
+ // The wiki tier itself: list pages, read raw content, build the briefing the
47
+ // MCP server returns from `wiki_context`.
48
+ const pages = await listWikiPages();
49
+ const briefing = await buildWikiContext({ query: 'what auth pattern do we use?' });
50
+ ```
51
+
52
+ The wiki adapter expects a `docs/wiki/` markdown surface in the process cwd. To run against a different root, change directory before importing — `store.ts` captures the wiki root at module-init time via `process.cwd()` (a known tradeoff documented in the architecture page).
53
+
54
+ ## Design contract
55
+
56
+ The wiki tier reaches the brain ONLY through the `@rarusoft/dendrite-memory` barrel. One contract test in the parent monorepo pins this at `npm test` time:
57
+
58
+ - `test/wiki-no-brain-internals.test.ts` — every `.ts` file under `packages/wiki/src/` must reach brain symbols via `@rarusoft/dendrite-memory`. Deep imports into `packages/memory/src/` internals fail the test.
59
+
60
+ `npm run build -w @rarusoft/dendrite-wiki` succeeds standalone (proves the same at the type-resolution level, with `@rarusoft/dendrite-memory` declared as a workspace dependency).
61
+
62
+ ## How wiki ↔ brain talk
63
+
64
+ Two inversions established during Phase 4 of the extraction:
65
+
66
+ - **Tokenizer.** The brain owns `tokenizeSearchQuery`. The wiki re-exports it from `@rarusoft/dendrite-memory` so the two share tokenization rules without the wiki being a brain dependency.
67
+ - **Cache invalidation.** When the brain mutates, it emits an `onMemoryMutation` event. The wiki's `context-cache.ts` registers its invalidator on import. No brain → wiki reverse calls.
68
+
69
+ `CanonicalTarget` uses module-level default-target DI: `WikiCanonicalTarget` registers itself as the brain's default canonical target via a top-level `setDefaultCanonicalTarget(createWikiCanonicalTarget())` side effect, run on `canonical-target.js` load. Because the barrel re-exports `canonical-target.js` first, `import { anything } from '@rarusoft/dendrite-wiki'` is enough to wire the brain DI.
70
+
71
+ ## Related
72
+
73
+ - [`@rarusoft/dendrite-memory`](../memory/) — the AI memory brain core.
74
+ - [Dendrite Wiki MCP](https://github.com/mfillalan/dendrite-wiki-mcp) — the umbrella product.
75
+ - [Library Extraction Roadmap](../../docs/wiki/library-extraction-roadmap.md) — the migration story.
76
+
77
+ ## License
78
+
79
+ Apache-2.0.
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Extracts an `ApiFileReference` from a single TypeScript source file.
3
+ *
4
+ * Uses the TypeScript Compiler API directly (no typedoc, no extra dependencies — the
5
+ * `typescript` package is already a devDep). Parses the file with `ts.createSourceFile`
6
+ * and `setParentNodes: true` so `Node.getText()`, the printer, and parent-walking helpers
7
+ * all work without a full Program. JSDoc is read off `node.jsDoc[last]` directly because
8
+ * Program-loaded source files don't set parent pointers in a way `getJSDocCommentsAndTags`
9
+ * can use; the last entry of the array is the immediately-preceding doc block.
10
+ *
11
+ * Each top-level exported declaration becomes one `ApiSymbol`. Function default values
12
+ * are stripped from rendered signatures (implementation detail, not type contract).
13
+ * Interfaces render with their full member body via the TS printer. `@internal`-tagged
14
+ * exports are filtered. The renderer in `./render.ts` formats the result as markdown.
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import path from 'node:path';
18
+ import ts from 'typescript';
19
+ export function extractApiFileReference(sourcePath, options = {}) {
20
+ const rootDir = options.rootDir ?? process.cwd();
21
+ const absolute = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(rootDir, sourcePath);
22
+ const relative = toForwardSlash(path.relative(rootDir, absolute));
23
+ // Parse the file directly with parent pointers enabled. We don't need a Program for A1 —
24
+ // there's no type-checker work, just AST traversal of declarations and JSDoc. Setting parent
25
+ // pointers makes ts.Node.getText() and printNode work without a separate setup step.
26
+ const text = readFileSync(absolute, 'utf8');
27
+ const sourceFile = ts.createSourceFile(absolute, text, ts.ScriptTarget.ES2022, /*setParentNodes*/ true, ts.ScriptKind.TS);
28
+ const symbols = [];
29
+ for (const statement of sourceFile.statements) {
30
+ const extracted = extractSymbolsFromStatement(statement, sourceFile);
31
+ for (const symbol of extracted) {
32
+ if (isInternal(symbol)) {
33
+ continue;
34
+ }
35
+ symbols.push(symbol);
36
+ }
37
+ }
38
+ symbols.sort((left, right) => left.sourceLine - right.sourceLine);
39
+ return {
40
+ sourcePath: relative,
41
+ moduleSlug: deriveModuleSlug(relative),
42
+ symbols,
43
+ fileDocComment: extractFileDocComment(sourceFile)
44
+ };
45
+ }
46
+ function extractSymbolsFromStatement(statement, sourceFile) {
47
+ if (!hasExportModifier(statement)) {
48
+ return [];
49
+ }
50
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
51
+ return [buildSymbol(statement, statement.name.text, 'function', renderFunctionSignature(statement), sourceFile)];
52
+ }
53
+ if (ts.isClassDeclaration(statement) && statement.name) {
54
+ return [buildSymbol(statement, statement.name.text, 'class', renderClassSignature(statement), sourceFile)];
55
+ }
56
+ if (ts.isInterfaceDeclaration(statement)) {
57
+ return [buildSymbol(statement, statement.name.text, 'interface', renderInterfaceSignature(statement), sourceFile)];
58
+ }
59
+ if (ts.isTypeAliasDeclaration(statement)) {
60
+ return [buildSymbol(statement, statement.name.text, 'type-alias', renderTypeAliasSignature(statement), sourceFile)];
61
+ }
62
+ if (ts.isEnumDeclaration(statement)) {
63
+ return [buildSymbol(statement, statement.name.text, 'enum', renderEnumSignature(statement), sourceFile)];
64
+ }
65
+ if (ts.isVariableStatement(statement)) {
66
+ const out = [];
67
+ for (const decl of statement.declarationList.declarations) {
68
+ if (!ts.isIdentifier(decl.name)) {
69
+ continue;
70
+ }
71
+ out.push(buildSymbol(statement, decl.name.text, 'variable', renderVariableSignature(statement, decl), sourceFile));
72
+ }
73
+ return out;
74
+ }
75
+ return [];
76
+ }
77
+ function buildSymbol(jsDocCarrier, name, kind, signature, sourceFile) {
78
+ const { docComment, tags } = parseJSDoc(jsDocCarrier);
79
+ const sourceLine = sourceFile.getLineAndCharacterOfPosition(jsDocCarrier.getStart(sourceFile)).line + 1;
80
+ const isDeprecated = tags.some((tag) => tag.name === 'deprecated');
81
+ return {
82
+ name,
83
+ kind,
84
+ signature,
85
+ docComment,
86
+ tags,
87
+ sourceLine,
88
+ isDeprecated
89
+ };
90
+ }
91
+ function hasExportModifier(node) {
92
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
93
+ if (!modifiers) {
94
+ return false;
95
+ }
96
+ return modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
97
+ }
98
+ function parseJSDoc(node) {
99
+ // ts.getJSDocCommentsAndTags walks the parent chain, but Program-created source files do
100
+ // not have parent pointers set, so it returns nothing. Read .jsDoc directly instead. The
101
+ // parser attaches one JSDoc node per JSDoc block preceding the declaration; the LAST entry
102
+ // is the immediately-preceding one, which is the "owning" doc for the declaration.
103
+ const jsDocList = node.jsDoc;
104
+ if (!jsDocList || jsDocList.length === 0) {
105
+ return { docComment: null, tags: [] };
106
+ }
107
+ const owning = jsDocList[jsDocList.length - 1];
108
+ const body = renderJSDocComment(owning.comment).trim();
109
+ const tags = [];
110
+ if (owning.tags) {
111
+ for (const tag of owning.tags) {
112
+ tags.push(parseTag(tag));
113
+ }
114
+ }
115
+ return { docComment: body.length > 0 ? body : null, tags };
116
+ }
117
+ function parseTag(tag) {
118
+ const name = tag.tagName.text;
119
+ const text = renderJSDocComment(tag.comment).trim();
120
+ if (ts.isJSDocParameterTag(tag)) {
121
+ return {
122
+ name,
123
+ text,
124
+ paramName: ts.isIdentifier(tag.name) ? tag.name.text : tag.name.getText()
125
+ };
126
+ }
127
+ return { name, text };
128
+ }
129
+ function renderJSDocComment(comment) {
130
+ if (!comment) {
131
+ return '';
132
+ }
133
+ if (typeof comment === 'string') {
134
+ return normalizeLineEndings(comment);
135
+ }
136
+ const joined = comment
137
+ .map((part) => {
138
+ if (part.kind === ts.SyntaxKind.JSDocText) {
139
+ return part.text;
140
+ }
141
+ if (part.kind === ts.SyntaxKind.JSDocLink || part.kind === ts.SyntaxKind.JSDocLinkCode || part.kind === ts.SyntaxKind.JSDocLinkPlain) {
142
+ const link = part;
143
+ const targetName = link.name ? link.name.getText() : '';
144
+ const linkText = link.text ?? '';
145
+ const label = linkText.trim().length > 0 ? `${targetName} ${linkText.trim()}`.trim() : targetName;
146
+ return `{@link ${label}}`;
147
+ }
148
+ return '';
149
+ })
150
+ .join('');
151
+ return normalizeLineEndings(joined);
152
+ }
153
+ /**
154
+ * Normalize CRLF / lone CR to LF in extracted text. Source files checked out on Windows
155
+ * with `core.autocrlf=true` come back from disk with CRLF, which would otherwise leak
156
+ * into the extracted JSDoc/TSDoc text fields and produce platform-dependent fixtures
157
+ * and pages. Normalizing at the extraction boundary keeps every downstream artifact
158
+ * (`ApiFileReference`, rendered markdown, manifest content hash) byte-identical across
159
+ * Windows / macOS / Linux checkouts.
160
+ */
161
+ function normalizeLineEndings(text) {
162
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
163
+ }
164
+ function isInternal(symbol) {
165
+ return symbol.tags.some((tag) => tag.name === 'internal');
166
+ }
167
+ function extractFileDocComment(sourceFile) {
168
+ const fullText = sourceFile.getFullText();
169
+ const ranges = ts.getLeadingCommentRanges(fullText, 0);
170
+ if (!ranges) {
171
+ return null;
172
+ }
173
+ for (const range of ranges) {
174
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
175
+ continue;
176
+ }
177
+ const raw = fullText.slice(range.pos, range.end);
178
+ if (!raw.startsWith('/**')) {
179
+ continue;
180
+ }
181
+ return cleanBlockComment(raw);
182
+ }
183
+ return null;
184
+ }
185
+ function cleanBlockComment(raw) {
186
+ const inner = raw.replace(/^\/\*\*/, '').replace(/\*\/$/, '');
187
+ const lines = inner.split(/\r?\n/).map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
188
+ while (lines.length > 0 && lines[0].trim() === '') {
189
+ lines.shift();
190
+ }
191
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
192
+ lines.pop();
193
+ }
194
+ return lines.join('\n').trim();
195
+ }
196
+ // --- signature renderers -----------------------------------------------------
197
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments: true, omitTrailingSemicolon: true });
198
+ function renderFunctionSignature(node) {
199
+ const typeParams = renderTypeParameters(node.typeParameters);
200
+ const params = (node.parameters ?? []).map((p) => renderParameter(p)).join(', ');
201
+ const returnType = node.type ? `: ${printNode(node.type)}` : '';
202
+ return `function ${node.name?.text ?? ''}${typeParams}(${params})${returnType}`;
203
+ }
204
+ function renderParameter(node) {
205
+ // Strip default values from the rendered signature — they're implementation detail, not
206
+ // part of the public type contract, and they add visual noise to the API page. Preserve
207
+ // rest tokens, optionals, and the type annotation.
208
+ const dotDotDot = node.dotDotDotToken ? '...' : '';
209
+ const name = node.name.getText();
210
+ const question = node.questionToken ? '?' : '';
211
+ const typeAnnotation = node.type ? `: ${printNode(node.type)}` : '';
212
+ return `${dotDotDot}${name}${question}${typeAnnotation}`;
213
+ }
214
+ function renderClassSignature(node) {
215
+ const typeParams = renderTypeParameters(node.typeParameters);
216
+ const heritage = (node.heritageClauses ?? [])
217
+ .map((clause) => printNode(clause).trim())
218
+ .join(' ');
219
+ const heritageSuffix = heritage.length > 0 ? ` ${heritage}` : '';
220
+ return `class ${node.name?.text ?? ''}${typeParams}${heritageSuffix}`;
221
+ }
222
+ function renderInterfaceSignature(node) {
223
+ // Print the full interface including its members. An empty `interface Foo` line on its
224
+ // own is useless on the API page; readers care about the shape of the type, which is the
225
+ // member list. The TS printer handles formatting + indentation cleanly. Strip the leading
226
+ // `export` modifier — every symbol on an API reference page is exported by definition.
227
+ return printNode(node).replace(/^export\s+/, '');
228
+ }
229
+ function renderTypeAliasSignature(node) {
230
+ const typeParams = renderTypeParameters(node.typeParameters);
231
+ return `type ${node.name.text}${typeParams} = ${printNode(node.type)}`;
232
+ }
233
+ function renderEnumSignature(node) {
234
+ const members = node.members.map((member) => ` ${printNode(member)}`).join(',\n');
235
+ return `enum ${node.name.text} {\n${members}\n}`;
236
+ }
237
+ function renderVariableSignature(statement, decl) {
238
+ const flags = statement.declarationList.flags;
239
+ const keyword = flags & ts.NodeFlags.Const ? 'const' : flags & ts.NodeFlags.Let ? 'let' : 'var';
240
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : decl.name.getText();
241
+ const typeAnnotation = decl.type ? `: ${printNode(decl.type)}` : '';
242
+ return `${keyword} ${name}${typeAnnotation}`;
243
+ }
244
+ function renderTypeParameters(params) {
245
+ if (!params || params.length === 0) {
246
+ return '';
247
+ }
248
+ return `<${params.map((p) => printNode(p)).join(', ')}>`;
249
+ }
250
+ function printNode(node) {
251
+ const sourceFile = node.getSourceFile();
252
+ if (sourceFile) {
253
+ return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
254
+ }
255
+ // Fallback for synthesized nodes: build a transient SourceFile.
256
+ const transientFile = ts.createSourceFile('__transient__.ts', '', ts.ScriptTarget.ES2022, false, ts.ScriptKind.TS);
257
+ return printer.printNode(ts.EmitHint.Unspecified, node, transientFile);
258
+ }
259
+ function deriveModuleSlug(relativeSourcePath) {
260
+ const trimmed = relativeSourcePath.replace(/\\/g, '/').replace(/^\.\//, '');
261
+ const withoutExt = trimmed.replace(/\.[cm]?tsx?$/i, '');
262
+ const stripped = withoutExt
263
+ .replace(/^src\//, '')
264
+ .replace(/^packages\/([^/]+)\/src\//, '$1/');
265
+ return `api/${stripped}`;
266
+ }
267
+ function toForwardSlash(value) {
268
+ return value.replace(/\\/g, '/');
269
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Language pluggability surface for the API reference generator.
3
+ *
4
+ * The orchestrator (`refreshApiReference`) walks a registered list of `LanguageExtractor`
5
+ * implementations and dispatches to the first one whose `detect(rootDir)` returns true.
6
+ * TypeScript is the only built-in today (`./typescript-extractor.ts`); future Python/Rust/Go
7
+ * support is a drop-in module implementing this same interface — no orchestrator changes.
8
+ *
9
+ * The interface is deliberately small and async-friendly so a Python extractor that shells
10
+ * out to `pdoc --output json` or a Rust extractor wrapping `rustdoc --output-format json`
11
+ * can implement it without contortion. It is also free of TypeScript-specific shapes;
12
+ * everything the orchestrator needs is the language-agnostic `ApiFileReference` from
13
+ * `./types.ts`. Phase A7 of the API reference roadmap establishes this layering.
14
+ */
15
+ export {};