docs: address graph review placement

This commit is contained in:
Tianyi Cui
2026-07-05 02:54:01 +08:00
parent 8e70efcfcc
commit 54a3c8b2a4
11 changed files with 74 additions and 53 deletions

View File

@@ -5,7 +5,7 @@
* - module-graph.md answers "which packages depend on which packages?"
* - cordis-catalog/ answers "which events and services exist?"
* - 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
* 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 { dirname, resolve } from 'node:path'
import { dirname, relative, resolve } from 'node:path'
import ts from 'typescript'
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' },
]
function generatedHeader(title: string, source: string): string[] {
function generatedHeader(title: string): string[] {
return [
'<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.',
' Run `pnpm run gen-doc-graphs` to regenerate. -->',
'',
`# ${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[] {
const pkgs: Pkg[] = []
for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) {
@@ -305,6 +315,7 @@ function assertServiceRolesComplete(): void {
function renderCapabilitySeams(pkgs: Pkg[]): string {
assertServiceRolesComplete()
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 edges = 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)}"]`)
}
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(
'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) {
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')
}
@@ -375,7 +386,7 @@ function stripYamlScalar(value: string): string {
const APP_EXAMPLES = [
{
id: 'echo',
rel: 'docs/echo-agent-composition.md',
rel: 'examples/echo-agent/composition.md',
title: 'Echo Agent App Composition',
label: 'examples/echo-agent',
config: 'examples/echo-agent/cordis.yml',
@@ -383,7 +394,7 @@ const APP_EXAMPLES = [
},
{
id: 'coding',
rel: 'docs/coding-agent-composition.md',
rel: 'examples/coding-agent/composition.md',
title: 'Coding Agent App Composition',
label: 'examples/coding-agent',
config: 'examples/coding-agent/cordis.yml',
@@ -391,7 +402,7 @@ const APP_EXAMPLES = [
},
{
id: 'acp',
rel: 'docs/acp-agent-composition.md',
rel: 'examples/acp-agent/composition.md',
title: 'ACP Agent App Composition',
label: 'examples/acp-agent',
config: 'examples/acp-agent/cordis.yml',
@@ -421,7 +432,8 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
function renderAppComposition(example: AppExample): string {
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(
example.summary,
'',
@@ -444,9 +456,9 @@ function renderAppComposition(example: AppExample): string {
'| --- | --- |',
...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')
}
@@ -528,7 +540,8 @@ function renderEventRelations(pkgs: Pkg[]): string {
const events = collectEvents()
const relations = collectEventRelations()
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(
'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('')
lines.push('', ...maintenanceFooter(maintenance))
return lines.join('\n')
}
function renderLifecycle(): string {
const maintenance = 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'
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/*`.',
'',
'```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.',
'',
...maintenanceFooter(maintenance),
].join('\n')
}
function renderToolPipeline(): string {
const maintenance = 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'
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.',
'',
'```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.',
'',
...maintenanceFooter(maintenance),
].join('\n')
}
function renderSnapshotReplay(): string {
const maintenance = 'curated Mermaid sequence based on the snapshot test harness'
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.',
'',
'```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.',
'',
...maintenanceFooter(maintenance),
].join('\n')
}
@@ -685,9 +704,9 @@ function renderDocs(): GraphDoc[] {
function renderIndex(docs: GraphDoc[]): string {
const labels: Record<string, string> = {
'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/agent-lifecycle.md': 'agent turn and step lifecycle',
'docs/tool-execution-pipeline.md': 'tool execution pipeline',
@@ -695,9 +714,9 @@ function renderIndex(docs: GraphDoc[]): string {
}
const modes: Record<string, string> = {
'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/agent-lifecycle.md': 'curated',
'docs/tool-execution-pipeline.md': 'curated',
@@ -707,12 +726,13 @@ function renderIndex(docs: GraphDoc[]): string {
'| [module dependency graph](module-graph.md) | `generated` |',
'| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` |',
...docs.map((doc) => {
const link = doc.rel.replace(/^docs\//, '')
const link = graphIndexLink(doc.rel)
return `| [${labels[doc.rel] ?? link}](${link}) | \`${modes[doc.rel] ?? 'generated'}\` |`
}),
]
const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode'
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).',
'',
'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`.',
'',
...maintenanceFooter(maintenance),
].join('\n')
}