2026-08-26 23:52:19 +08:00
/** Generate the paired shared-instance package graph from workspace peer dependencies. */
2026-06-16 21:00:24 +08:00
2026-08-26 23:52:19 +08:00
import { existsSync , readFileSync , writeFileSync } from 'node:fs'
2026-07-14 00:24:04 +08:00
import { resolve } from 'node:path'
import {
collectPackageGraph ,
escapeMermaidLabel as escLabel ,
graphNodeId as nodeId ,
type PackageGraphNode ,
} from './package-graph.ts'
2026-08-26 23:52:19 +08:00
import { gitBlobHash , storeGitBlob } from './translation-pairing-git.ts'
import { renderTranslationPairingRecord , translationPairPaths } from './translation-pairing-record.ts'
2026-06-16 21:00:24 +08:00
const root = resolve ( import . meta . dirname , '..' )
2026-08-26 23:52:19 +08:00
const SOURCE = 'docs/module-graph.md'
const PATHS = translationPairPaths ( SOURCE )
2026-07-14 00:24:04 +08:00
type Pkg = PackageGraphNode
2026-08-26 23:52:19 +08:00
type Locale = 'en' | 'zh'
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-08-26 23:52:19 +08:00
/ * *
* Render one locale of the complete deterministic package graph .
* @param pkgs - Dependency - first package nodes .
* @param locale - Output document language .
* @returns Complete generated Markdown .
* /
export function renderModuleGraph ( pkgs : readonly Pkg [ ] , locale : Locale ) : string {
2026-06-16 21:00:24 +08:00
const edges : string [ ] = [ ]
2026-08-26 23:52:19 +08:00
for ( const pkg of pkgs ) {
for ( const dependency of pkg . deps ) edges . push ( ` ${ nodeId ( 'pkg' , pkg . short ) } --> ${ nodeId ( 'pkg' , dependency ) } ` )
2026-07-05 01:25:58 +08:00
}
const byShort = new Map ( pkgs . map ( pkg = > [ pkg . short , pkg ] ) )
2026-08-26 23:52:19 +08:00
const groups = [ . . . new Set ( pkgs . map ( pkg = > pkg . group ) ) ] . sort ( ( left , right ) = > {
const leftIndex = GROUP_ORDER . indexOf ( left )
const rightIndex = GROUP_ORDER . indexOf ( right )
const normalizedLeft = leftIndex === - 1 ? Number . MAX_SAFE_INTEGER : leftIndex
const normalizedRight = rightIndex === - 1 ? Number . MAX_SAFE_INTEGER : rightIndex
return normalizedLeft - normalizedRight || left . localeCompare ( right )
2026-07-05 01:25:58 +08:00
} )
const groupBlocks : string [ ] = [ ]
for ( const group of groups ) {
groupBlocks . push ( ` subgraph ${ nodeId ( 'group' , group ) } ["packages/ ${ escLabel ( group ) } "] ` )
2026-08-26 23:52:19 +08:00
for ( const pkg of pkgs . filter ( candidate = > candidate . group === group )
. sort ( ( left , right ) = > left . short . localeCompare ( right . short ) ) ) {
2026-07-05 01:25:58 +08:00
groupBlocks . push ( ` ${ nodeId ( 'pkg' , pkg . short ) } [" ${ escLabel ( pkg . short ) } "] ` )
}
groupBlocks . push ( ' end' )
2026-06-16 21:00:24 +08:00
}
2026-08-26 23:52:19 +08:00
const rows = pkgs . map ( ( pkg ) = > {
const dependencies = pkg . deps . length > 0
? pkg . deps . map ( ( dependency ) = > {
const target = byShort . get ( dependency )
return target ? packageLink ( target ) : ` \` ${ dependency } \` `
} ) . join ( ', ' )
: '—'
return ` | ${ packageLink ( pkg ) } | \` ${ pkg . group } \` | ${ dependencies } | `
2026-07-05 01:25:58 +08:00
} )
2026-08-26 23:52:19 +08:00
const chinese = locale === 'zh'
2026-06-16 21:00:24 +08:00
return [
2026-08-26 23:52:19 +08:00
chinese
? '<!-- 由 scripts/gen-module-graph.ts 生成——请勿手工编辑。\n 运行 `pnpm run gen-module-graph` 重新生成。 -->'
: '<!-- Generated by scripts/gen-module-graph.ts — do not edit by hand.\n Run `pnpm run gen-module-graph` to regenerate. -->' ,
2026-06-16 21:00:24 +08:00
'' ,
2026-08-26 23:52:19 +08:00
chinese ? '# 共享实例依赖关系图' : '# Shared-instance dependency graph' ,
2026-06-16 21:00:24 +08:00
'' ,
2026-08-26 23:52:19 +08:00
. . . ( chinese ? [ '[English](module-graph.md) | 中文' , '' ] : [ ] ) ,
chinese
? '`@deepseek-ai/dsh-*` harness 包之间的 peer 依赖关系。peer 表示消费端需要提供共享实例,不包括普通运行时 dependency 或仅开发期关系。该图按 `packages/<group>/<pkg>` 层级分组;边 `a --> b` 表示包 `a` peer 依赖包 `b`。名称中的 `@deepseek-ai/dsh-` 前缀已移除。'
: 'Peer dependencies among the `@deepseek-ai/dsh-*` harness packages. A peer means the consumer requires a shared instance; ordinary runtime dependencies and development-only relationships are not shown. The graph is grouped by the `packages/<group>/<pkg>` hierarchy. An edge `a --> b` means package `a` has package `b` as a peer. Names omit the `@deepseek-ai/dsh-` prefix.' ,
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-08-26 23:52:19 +08:00
chinese ? '| 包 | 分组 | Peer 依赖 |' : '| Package | Group | Peer dependencies |' ,
2026-07-05 01:25:58 +08:00
'| --- | --- | --- |' ,
2026-06-16 21:00:24 +08:00
. . . rows ,
'' ,
] . join ( '\n' )
}
2026-08-26 23:52:19 +08:00
/ * *
* Compute both localized graph documents from the current workspace manifests .
* @param scanRoot - Repository root containing packages and documentation .
* @returns Repository - relative output paths and exact generated content .
* /
export function computeModuleGraphOutputs ( scanRoot : string = root ) : ReadonlyMap < string , string > {
const packages = collectPackageGraph ( scanRoot , GROUP_ORDER , 'gen-module-graph' )
return new Map ( [
[ PATHS . source , renderModuleGraph ( packages , 'en' ) ] ,
[ PATHS . zh , renderModuleGraph ( packages , 'zh' ) ] ,
] )
}
2026-06-16 21:00:24 +08:00
2026-08-26 23:52:19 +08:00
/ * *
* Write both graph documents and their recovery record .
* @param scanRoot - Repository root containing packages and documentation .
* @returns Repository - relative paths whose content changed .
* /
export function writeModuleGraph ( scanRoot : string = root ) : string [ ] {
const outputs = computeModuleGraphOutputs ( scanRoot )
const changed : string [ ] = [ ]
for ( const [ path , content ] of outputs ) {
const destination = resolve ( scanRoot , path )
if ( existsSync ( destination ) && readFileSync ( destination , 'utf8' ) === content ) continue
writeFileSync ( destination , content )
changed . push ( path )
}
const source = Buffer . from ( outputs . get ( PATHS . source ) ? ? '' )
const zh = Buffer . from ( outputs . get ( PATHS . zh ) ? ? '' )
const record = renderTranslationPairingRecord ( PATHS , {
sourceHash : storeGitBlob ( scanRoot , source ) ,
zhHash : storeGitBlob ( scanRoot , zh ) ,
} )
const recordPath = resolve ( scanRoot , PATHS . meta )
if ( ! existsSync ( recordPath ) || readFileSync ( recordPath , 'utf8' ) !== record ) {
writeFileSync ( recordPath , record )
changed . push ( PATHS . meta )
2026-06-16 21:00:24 +08:00
}
2026-08-26 23:52:19 +08:00
return changed . sort ( )
}
/** CLI entry: regenerate by default, or verify all paired outputs with `--check`. @returns Nothing. */
export function main ( ) : void {
const outputs = computeModuleGraphOutputs ( root )
const record = renderTranslationPairingRecord ( PATHS , {
sourceHash : gitBlobHash ( Buffer . from ( outputs . get ( PATHS . source ) ? ? '' ) ) ,
zhHash : gitBlobHash ( Buffer . from ( outputs . get ( PATHS . zh ) ? ? '' ) ) ,
} )
const expected = new Map ( [ . . . outputs , [ PATHS . meta , record ] ] )
if ( process . argv . includes ( '--check' ) ) {
const stale = [ . . . expected ] . filter ( ( [ path , content ] ) = > (
! existsSync ( resolve ( root , path ) ) || readFileSync ( resolve ( root , path ) , 'utf8' ) !== content
) ) . map ( ( [ path ] ) = > path )
if ( stale . length === 0 ) {
console . log ( ` gen-module-graph: ${ expected . size } artifact(s) are up to date. ` )
return
}
console . error ( ` gen-module-graph: stale — ${ stale . join ( ', ' ) } . Run \` pnpm run gen-module-graph \` and commit the result. ` )
process . exitCode = 1
return
2026-06-16 21:00:24 +08:00
}
2026-08-26 23:52:19 +08:00
const changed = writeModuleGraph ( root )
console . log ( ` gen-module-graph: ${ expected . size } artifact(s) computed, ${ String ( changed . length ) } written. ` )
2026-06-16 21:00:24 +08:00
}
2026-08-26 23:52:19 +08:00
if ( process . argv [ 1 ] !== undefined && import . meta . filename === resolve ( process . argv [ 1 ] ) ) main ( )