2026-06-16 21:00:24 +08:00
/**
2026-07-13 23:27:00 +08:00
* Generate `docs/module-graph.md` from in-repo `peerDependencies`, the canonical
* runtime edges. The deterministic output groups packages by directory and
* renders both Mermaid and a dependency table; `--check` verifies freshness.
2026-06-16 21:00:24 +08:00
*/
2026-07-14 00:24:04 +08:00
import { resolve } from 'node:path'
import { readFileSync , writeFileSync } from 'node:fs'
import {
collectPackageGraph ,
escapeMermaidLabel as escLabel ,
graphNodeId as nodeId ,
type PackageGraphNode ,
} from './package-graph.ts'
2026-06-16 21:00:24 +08:00
const root = resolve ( import . meta . dirname , '..' )
const OUT = 'docs/module-graph.md'
2026-07-14 00:24:04 +08:00
type Pkg = PackageGraphNode
2026-06-16 21:00:24 +08:00
2026-07-05 01:25:58 +08:00
const GROUP_ORDER = [
'util' ,
'llm' ,
'core' ,
2026-07-19 18:47:34 +08:00
'goal' ,
2026-07-05 01:25:58 +08:00
'bash' ,
'fs' ,
2026-07-10 14:19:06 +08:00
'skill' ,
2026-07-05 01:25:58 +08:00
'compact' ,
'subagent' ,
'web' ,
2026-07-08 19:20:50 +08:00
'spill' ,
2026-07-08 10:06:07 +08:00
'timeout' ,
2026-07-05 01:25:58 +08:00
'todo' ,
2026-07-22 16:57:23 +08:00
'plan' ,
2026-07-08 11:50:12 +08:00
'cordis' ,
2026-07-05 01:25:58 +08:00
'hooks' ,
'session-persistence' ,
2026-07-10 16:51:19 +08:00
'session-query' ,
2026-07-21 01:54:00 +08:00
'session-title' ,
2026-07-05 01:25:58 +08:00
'support' ,
2026-07-24 01:40:25 +08:00
'acp' ,
2026-07-05 01:25:58 +08:00
'ui' ,
]
function packageLink ( pkg : Pkg ) : string {
return `[\` ${ pkg . short } \`](../ ${ pkg . rel } )`
}
2026-06-16 21:00:24 +08:00
/** Render the full docs/module-graph.md content (pure, deterministic). */
function render ( pkgs : Pkg []) : string {
const edges : string [] = []
for ( const p of pkgs ) {
2026-07-05 01:25:58 +08:00
for ( const d of p . deps ) edges . push ( ` ${ nodeId ( 'pkg' , p . short ) } --> ${ nodeId ( 'pkg' , d ) } ` )
2026-06-16 21:00:24 +08:00
}
2026-07-05 01:25:58 +08:00
const byShort = new Map ( pkgs . map ( pkg => [ pkg . short , pkg ]))
const groups = [... new Set ( pkgs . map ( pkg => pkg . group ))]. sort (( a , b ) => {
const ia = GROUP_ORDER . indexOf ( a )
const ib = GROUP_ORDER . indexOf ( b )
const na = ia === - 1 ? Number . MAX_SAFE_INTEGER : ia
const nb = ib === - 1 ? Number . MAX_SAFE_INTEGER : ib
return na - nb || a . localeCompare ( b )
})
const groupBlocks : string [] = []
for ( const group of groups ) {
groupBlocks . push ( ` subgraph ${ nodeId ( 'group' , group ) } ["packages/ ${ escLabel ( group ) } "]` )
for ( const pkg of pkgs . filter ( p => p . group === group ). sort (( a , b ) => a . short . localeCompare ( b . short ))) {
groupBlocks . push ( ` ${ nodeId ( 'pkg' , pkg . short ) } [" ${ escLabel ( pkg . short ) } "]` )
}
groupBlocks . push ( ' end' )
}
const rows = pkgs . map (( p ) => {
const deps = p . deps . length ? p . deps . map (( d ) => {
const dep = byShort . get ( d )
return dep ? packageLink ( dep ) : `\` ${ d } \``
}). join ( ', ' ) : '—'
return `| ${ packageLink ( p ) } | \` ${ p . group } \` | ${ deps } |`
})
2026-06-16 21:00:24 +08:00
return [
'<!-- Generated by scripts/gen-module-graph.ts — do not edit by hand.' ,
' Run `pnpm run gen-module-graph` to regenerate. -->' ,
'' ,
'# Module dependency graph' ,
'' ,
2026-07-05 01:25:58 +08:00
'Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package\'s `peerDependencies` (the canonical runtime-dependency signal) and grouped by the `packages/<group>/<pkg>` hierarchy. An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped.' ,
2026-06-16 21:00:24 +08:00
'' ,
'```mermaid' ,
2026-07-05 01:25:58 +08:00
'flowchart TD' ,
... groupBlocks ,
2026-06-16 21:00:24 +08:00
... edges ,
'```' ,
'' ,
2026-07-05 01:25:58 +08:00
'| Package | Group | Depends on |' ,
'| --- | --- | --- |' ,
2026-06-16 21:00:24 +08:00
... rows ,
'' ,
]. join ( '\n' )
}
2026-07-14 00:24:04 +08:00
const content = render ( collectPackageGraph ( root , GROUP_ORDER , 'gen-module-graph' ))
2026-06-16 21:00:24 +08:00
if ( process . argv . includes ( '--check' )) {
let committed : string | null = null
try {
committed = readFileSync ( resolve ( root , OUT ), 'utf8' )
} catch {
2026-07-13 23:27:00 +08:00
// A missing artifact is the expected read failure. Any read failure has the
// same remedy here—regenerate—so it is reported as stale below.
2026-06-16 21:00:24 +08:00
committed = null
}
if ( committed === content ) {
console . log ( `gen-module-graph: ${ OUT } is up to date.` )
process . exit ( 0 )
}
console . error ( `gen-module-graph: ${ OUT } is stale. Run \`pnpm run gen-module-graph\` and commit ${ OUT } .` )
process . exit ( 1 )
}
writeFileSync ( resolve ( root , OUT ), content )
console . log ( `gen-module-graph: wrote ${ OUT } .` )