|
@@ -5,7 +5,7 @@
|
|
|
* - module-graph.md answers "which packages depend on which packages?"
|
|
* - module-graph.md answers "which packages depend on which packages?"
|
|
|
* - cordis-catalog/ answers "which events and services exist?"
|
|
* - cordis-catalog/ answers "which events and services exist?"
|
|
|
* - tool-catalog/ answers "which tools does the model see?"
|
|
* - tool-catalog/ answers "which tools does the model see?"
|
|
|
- * - docs/*.md relationship diagrams answer "how do those pieces fit together?"
|
|
|
|
|
|
|
+ * - generated relationship diagrams answer "how do those pieces fit together?"
|
|
|
*
|
|
*
|
|
|
* Generated pages discover the enumerable facts from source. Hybrid pages use
|
|
* Generated pages discover the enumerable facts from source. Hybrid pages use
|
|
|
* discovered inventory plus small manifests for policy that source cannot infer
|
|
* discovered inventory plus small manifests for policy that source cannot infer
|
|
@@ -19,7 +19,7 @@
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
-import { dirname, resolve } from 'node:path'
|
|
|
|
|
|
|
+import { dirname, relative, resolve } from 'node:path'
|
|
|
import ts from 'typescript'
|
|
import ts from 'typescript'
|
|
|
import { collectEvents, collectServices } from './gen-cordis-catalog.ts'
|
|
import { collectEvents, collectServices } from './gen-cordis-catalog.ts'
|
|
|
|
|
|
|
@@ -196,18 +196,28 @@ const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: str
|
|
|
{ event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' },
|
|
{ event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' },
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
-function generatedHeader(title: string, source: string): string[] {
|
|
|
|
|
|
|
+function generatedHeader(title: string): string[] {
|
|
|
return [
|
|
return [
|
|
|
'<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.',
|
|
'<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.',
|
|
|
' Run `pnpm run gen-doc-graphs` to regenerate. -->',
|
|
' Run `pnpm run gen-doc-graphs` to regenerate. -->',
|
|
|
'',
|
|
'',
|
|
|
`# ${title}`,
|
|
`# ${title}`,
|
|
|
'',
|
|
'',
|
|
|
- `Maintenance mode: ${source}.`,
|
|
|
|
|
- '',
|
|
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function maintenanceFooter(source: string): string[] {
|
|
|
|
|
+ return [`Maintenance mode: ${source}.`, '']
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function graphIndexLink(rel: string): string {
|
|
|
|
|
+ return relative('docs', rel).replaceAll('\\', '/')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function linkFromDoc(docRel: string, targetRel: string): string {
|
|
|
|
|
+ return relative(dirname(docRel), targetRel).replaceAll('\\', '/')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function collectPackages(): Pkg[] {
|
|
function collectPackages(): Pkg[] {
|
|
|
const pkgs: Pkg[] = []
|
|
const pkgs: Pkg[] = []
|
|
|
for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) {
|
|
for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) {
|
|
@@ -305,6 +315,7 @@ function assertServiceRolesComplete(): void {
|
|
|
function renderCapabilitySeams(pkgs: Pkg[]): string {
|
|
function renderCapabilitySeams(pkgs: Pkg[]): string {
|
|
|
assertServiceRolesComplete()
|
|
assertServiceRolesComplete()
|
|
|
const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg]))
|
|
const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg]))
|
|
|
|
|
+ const maintenance = 'hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard'
|
|
|
const nodes = new Map<string, string>()
|
|
const nodes = new Map<string, string>()
|
|
|
const edges = new Set<string>()
|
|
const edges = new Set<string>()
|
|
|
const companionEdges = new Set<string>()
|
|
const companionEdges = new Set<string>()
|
|
@@ -312,7 +323,7 @@ function renderCapabilitySeams(pkgs: Pkg[]): string {
|
|
|
if (!nodes.has(id)) nodes.set(id, ` ${id}["${escLabel(label)}"]`)
|
|
if (!nodes.has(id)) nodes.set(id, ` ${id}["${escLabel(label)}"]`)
|
|
|
}
|
|
}
|
|
|
const addEdge = (from: string, to: string): void => { edges.add(` ${from} --> ${to}`) }
|
|
const addEdge = (from: string, to: string): void => { edges.add(` ${from} --> ${to}`) }
|
|
|
- const lines = generatedHeader('Capability Seams And Core Services', 'hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard')
|
|
|
|
|
|
|
+ const lines = generatedHeader('Capability Seams And Core Services')
|
|
|
lines.push(
|
|
lines.push(
|
|
|
'A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly.',
|
|
'A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly.',
|
|
|
'',
|
|
'',
|
|
@@ -343,7 +354,7 @@ function renderCapabilitySeams(pkgs: Pkg[]): string {
|
|
|
for (const role of SERVICE_ROLES) {
|
|
for (const role of SERVICE_ROLES) {
|
|
|
lines.push(`| \`ctx.${role.key}\` | \`${role.mode}\` | ${pkgLink(pkgsByShort.get(role.pkg), role.pkg)} | ${pkgList(role.implementations, pkgsByShort)} | ${pkgList(role.consumers, pkgsByShort)} | ${pkgList(role.companions, pkgsByShort)} | ${tableCell(role.note)} |`)
|
|
lines.push(`| \`ctx.${role.key}\` | \`${role.mode}\` | ${pkgLink(pkgsByShort.get(role.pkg), role.pkg)} | ${pkgList(role.implementations, pkgsByShort)} | ${pkgList(role.consumers, pkgsByShort)} | ${pkgList(role.companions, pkgsByShort)} | ${tableCell(role.note)} |`)
|
|
|
}
|
|
}
|
|
|
- lines.push('')
|
|
|
|
|
|
|
+ lines.push('', ...maintenanceFooter(maintenance))
|
|
|
return lines.join('\n')
|
|
return lines.join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -375,7 +386,7 @@ function stripYamlScalar(value: string): string {
|
|
|
const APP_EXAMPLES = [
|
|
const APP_EXAMPLES = [
|
|
|
{
|
|
{
|
|
|
id: 'echo',
|
|
id: 'echo',
|
|
|
- rel: 'docs/echo-agent-composition.md',
|
|
|
|
|
|
|
+ rel: 'examples/echo-agent/composition.md',
|
|
|
title: 'Echo Agent App Composition',
|
|
title: 'Echo Agent App Composition',
|
|
|
label: 'examples/echo-agent',
|
|
label: 'examples/echo-agent',
|
|
|
config: 'examples/echo-agent/cordis.yml',
|
|
config: 'examples/echo-agent/cordis.yml',
|
|
@@ -383,7 +394,7 @@ const APP_EXAMPLES = [
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 'coding',
|
|
id: 'coding',
|
|
|
- rel: 'docs/coding-agent-composition.md',
|
|
|
|
|
|
|
+ rel: 'examples/coding-agent/composition.md',
|
|
|
title: 'Coding Agent App Composition',
|
|
title: 'Coding Agent App Composition',
|
|
|
label: 'examples/coding-agent',
|
|
label: 'examples/coding-agent',
|
|
|
config: 'examples/coding-agent/cordis.yml',
|
|
config: 'examples/coding-agent/cordis.yml',
|
|
@@ -391,7 +402,7 @@ const APP_EXAMPLES = [
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 'acp',
|
|
id: 'acp',
|
|
|
- rel: 'docs/acp-agent-composition.md',
|
|
|
|
|
|
|
+ rel: 'examples/acp-agent/composition.md',
|
|
|
title: 'ACP Agent App Composition',
|
|
title: 'ACP Agent App Composition',
|
|
|
label: 'examples/acp-agent',
|
|
label: 'examples/acp-agent',
|
|
|
config: 'examples/acp-agent/cordis.yml',
|
|
config: 'examples/acp-agent/cordis.yml',
|
|
@@ -421,7 +432,8 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
|
|
|
|
|
|
|
|
function renderAppComposition(example: AppExample): string {
|
|
function renderAppComposition(example: AppExample): string {
|
|
|
const plugins = parseExampleCordis(example.config)
|
|
const plugins = parseExampleCordis(example.config)
|
|
|
- const lines = generatedHeader(example.title, 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source')
|
|
|
|
|
|
|
+ const maintenance = 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source'
|
|
|
|
|
+ const lines = generatedHeader(example.title)
|
|
|
lines.push(
|
|
lines.push(
|
|
|
example.summary,
|
|
example.summary,
|
|
|
'',
|
|
'',
|
|
@@ -444,9 +456,9 @@ function renderAppComposition(example: AppExample): string {
|
|
|
'| --- | --- |',
|
|
'| --- | --- |',
|
|
|
...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`),
|
|
...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`),
|
|
|
'',
|
|
'',
|
|
|
- `Source config: ${repoLink(example.config, `\`${example.config}\``, '..')}.`,
|
|
|
|
|
- '',
|
|
|
|
|
|
|
+ `Source config: [\`${example.config}\`](${linkFromDoc(example.rel, example.config)}).`,
|
|
|
)
|
|
)
|
|
|
|
|
+ lines.push('', ...maintenanceFooter(maintenance))
|
|
|
return lines.join('\n')
|
|
return lines.join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -528,7 +540,8 @@ function renderEventRelations(pkgs: Pkg[]): string {
|
|
|
const events = collectEvents()
|
|
const events = collectEvents()
|
|
|
const relations = collectEventRelations()
|
|
const relations = collectEventRelations()
|
|
|
const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg]))
|
|
const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg]))
|
|
|
- const lines = generatedHeader('Event Producer And Consumer Matrix', 'hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`')
|
|
|
|
|
|
|
+ const maintenance = 'hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`'
|
|
|
|
|
+ const lines = generatedHeader('Event Producer And Consumer Matrix')
|
|
|
lines.push(
|
|
lines.push(
|
|
|
'This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment.',
|
|
'This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment.',
|
|
|
'',
|
|
'',
|
|
@@ -549,13 +562,14 @@ function renderEventRelations(pkgs: Pkg[]): string {
|
|
|
lines.push(`| \`${event}\` | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`)
|
|
lines.push(`| \`${event}\` | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- lines.push('')
|
|
|
|
|
|
|
+ lines.push('', ...maintenanceFooter(maintenance))
|
|
|
return lines.join('\n')
|
|
return lines.join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function renderLifecycle(): string {
|
|
function renderLifecycle(): string {
|
|
|
|
|
+ const maintenance = 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'
|
|
|
return [
|
|
return [
|
|
|
- ...generatedHeader('Agent Turn And Step Lifecycle', 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'),
|
|
|
|
|
|
|
+ ...generatedHeader('Agent Turn And Step Lifecycle'),
|
|
|
'This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.',
|
|
'This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.',
|
|
|
'',
|
|
'',
|
|
|
'```mermaid',
|
|
'```mermaid',
|
|
@@ -599,12 +613,14 @@ function renderLifecycle(): string {
|
|
|
'',
|
|
'',
|
|
|
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.',
|
|
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.',
|
|
|
'',
|
|
'',
|
|
|
|
|
+ ...maintenanceFooter(maintenance),
|
|
|
].join('\n')
|
|
].join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function renderToolPipeline(): string {
|
|
function renderToolPipeline(): string {
|
|
|
|
|
+ const maintenance = 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'
|
|
|
return [
|
|
return [
|
|
|
- ...generatedHeader('Tool Execution Pipeline', 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'),
|
|
|
|
|
|
|
+ ...generatedHeader('Tool Execution Pipeline'),
|
|
|
'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, and UI rendering fit without changing the loop. The key extension points are the `tools/pre-execute` and `tools/post-execute` waterfalls.',
|
|
'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, and UI rendering fit without changing the loop. The key extension points are the `tools/pre-execute` and `tools/post-execute` waterfalls.',
|
|
|
'',
|
|
'',
|
|
|
'```mermaid',
|
|
'```mermaid',
|
|
@@ -638,12 +654,14 @@ function renderToolPipeline(): string {
|
|
|
'',
|
|
'',
|
|
|
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate, while hook bridges and future permission prompts live on the generic tool waterfalls. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.',
|
|
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate, while hook bridges and future permission prompts live on the generic tool waterfalls. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.',
|
|
|
'',
|
|
'',
|
|
|
|
|
+ ...maintenanceFooter(maintenance),
|
|
|
].join('\n')
|
|
].join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function renderSnapshotReplay(): string {
|
|
function renderSnapshotReplay(): string {
|
|
|
|
|
+ const maintenance = 'curated Mermaid sequence based on the snapshot test harness'
|
|
|
return [
|
|
return [
|
|
|
- ...generatedHeader('ACP Snapshot Replay', 'curated Mermaid sequence based on the snapshot test harness'),
|
|
|
|
|
|
|
+ ...generatedHeader('ACP Snapshot Replay'),
|
|
|
'This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, ACP stdout is normalized and diffed, and scenario workspaces preserve tool side effects that the UI stream alone cannot prove.',
|
|
'This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, ACP stdout is normalized and diffed, and scenario workspaces preserve tool side effects that the UI stream alone cannot prove.',
|
|
|
'',
|
|
'',
|
|
|
'```mermaid',
|
|
'```mermaid',
|
|
@@ -665,6 +683,7 @@ function renderSnapshotReplay(): string {
|
|
|
'',
|
|
'',
|
|
|
'The fs and hook snapshot matrix is valuable because it proves world state, hook decisions, and failed tool-card rendering, not just that replay returns text.',
|
|
'The fs and hook snapshot matrix is valuable because it proves world state, hook decisions, and failed tool-card rendering, not just that replay returns text.',
|
|
|
'',
|
|
'',
|
|
|
|
|
+ ...maintenanceFooter(maintenance),
|
|
|
].join('\n')
|
|
].join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -685,9 +704,9 @@ function renderDocs(): GraphDoc[] {
|
|
|
function renderIndex(docs: GraphDoc[]): string {
|
|
function renderIndex(docs: GraphDoc[]): string {
|
|
|
const labels: Record<string, string> = {
|
|
const labels: Record<string, string> = {
|
|
|
'docs/capability-seams.md': 'capability seams and core services',
|
|
'docs/capability-seams.md': 'capability seams and core services',
|
|
|
- 'docs/echo-agent-composition.md': 'echo-agent app composition',
|
|
|
|
|
- 'docs/coding-agent-composition.md': 'coding-agent app composition',
|
|
|
|
|
- 'docs/acp-agent-composition.md': 'acp-agent app composition',
|
|
|
|
|
|
|
+ 'examples/echo-agent/composition.md': 'echo-agent app composition',
|
|
|
|
|
+ 'examples/coding-agent/composition.md': 'coding-agent app composition',
|
|
|
|
|
+ 'examples/acp-agent/composition.md': 'acp-agent app composition',
|
|
|
'docs/event-producer-consumer.md': 'event producer/consumer matrix',
|
|
'docs/event-producer-consumer.md': 'event producer/consumer matrix',
|
|
|
'docs/agent-lifecycle.md': 'agent turn and step lifecycle',
|
|
'docs/agent-lifecycle.md': 'agent turn and step lifecycle',
|
|
|
'docs/tool-execution-pipeline.md': 'tool execution pipeline',
|
|
'docs/tool-execution-pipeline.md': 'tool execution pipeline',
|
|
@@ -695,9 +714,9 @@ function renderIndex(docs: GraphDoc[]): string {
|
|
|
}
|
|
}
|
|
|
const modes: Record<string, string> = {
|
|
const modes: Record<string, string> = {
|
|
|
'docs/capability-seams.md': 'hybrid generated',
|
|
'docs/capability-seams.md': 'hybrid generated',
|
|
|
- 'docs/echo-agent-composition.md': 'hybrid generated',
|
|
|
|
|
- 'docs/coding-agent-composition.md': 'hybrid generated',
|
|
|
|
|
- 'docs/acp-agent-composition.md': 'hybrid generated',
|
|
|
|
|
|
|
+ 'examples/echo-agent/composition.md': 'hybrid generated',
|
|
|
|
|
+ 'examples/coding-agent/composition.md': 'hybrid generated',
|
|
|
|
|
+ 'examples/acp-agent/composition.md': 'hybrid generated',
|
|
|
'docs/event-producer-consumer.md': 'hybrid generated',
|
|
'docs/event-producer-consumer.md': 'hybrid generated',
|
|
|
'docs/agent-lifecycle.md': 'curated',
|
|
'docs/agent-lifecycle.md': 'curated',
|
|
|
'docs/tool-execution-pipeline.md': 'curated',
|
|
'docs/tool-execution-pipeline.md': 'curated',
|
|
@@ -707,12 +726,13 @@ function renderIndex(docs: GraphDoc[]): string {
|
|
|
'| [module dependency graph](module-graph.md) | `generated` |',
|
|
'| [module dependency graph](module-graph.md) | `generated` |',
|
|
|
'| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` |',
|
|
'| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` |',
|
|
|
...docs.map((doc) => {
|
|
...docs.map((doc) => {
|
|
|
- const link = doc.rel.replace(/^docs\//, '')
|
|
|
|
|
|
|
+ const link = graphIndexLink(doc.rel)
|
|
|
return `| [${labels[doc.rel] ?? link}](${link}) | \`${modes[doc.rel] ?? 'generated'}\` |`
|
|
return `| [${labels[doc.rel] ?? link}](${link}) | \`${modes[doc.rel] ?? 'generated'}\` |`
|
|
|
}),
|
|
}),
|
|
|
]
|
|
]
|
|
|
|
|
+ const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode'
|
|
|
return [
|
|
return [
|
|
|
- ...generatedHeader('Documentation Graph Index', 'mixed: each linked page declares generated, hybrid, or curated mode'),
|
|
|
|
|
|
|
+ ...generatedHeader('Documentation Graph Index'),
|
|
|
'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md).',
|
|
'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md).',
|
|
|
'',
|
|
'',
|
|
|
'The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).',
|
|
'The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).',
|
|
@@ -723,6 +743,7 @@ function renderIndex(docs: GraphDoc[]): string {
|
|
|
'',
|
|
'',
|
|
|
'Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`.',
|
|
'Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`.',
|
|
|
'',
|
|
'',
|
|
|
|
|
+ ...maintenanceFooter(maintenance),
|
|
|
].join('\n')
|
|
].join('\n')
|
|
|
}
|
|
}
|
|
|
|
|
|