Files
deepseek-harness/scripts/gen-module-graph.ts
kingwl 1f38b211be Merge origin/master (generic task runtime #219, single-exe closure, package renames)
Semantic resolutions beyond line merges:
- The bash seam keeps resolveMode + the bash/resolve-mode waterfall on
  master's task-free BashExecutor (run/start/resolve only; BashProcess
  handles); tool-bash consults it at its stamping site and escalation
  baseline on master's render/background split, with a waterfall test on
  the recording executor.
- dsh-mode's BASH_FAMILY narrows to ['bash']: bash_output/bash_kill are
  replaced by the kind-generic task_output/task_kill, which span every
  task kind and only observe or stop work, so the access cap withholds
  only the starter it can reason about.
- The plan-mode snapshot suite adopts master's pin grammar (tool-schema
  sidecars; the expectedHeaderSnapshots extension is gone — the exit
  transition deltas, and entering-before-turn-1 needs no second
  snapshot); modes-advertise joins the plan header class (no-model, so
  membership is vacuous). Fixtures re-recorded on the acp-demo bin;
  the replay overlay gains the passthrough sandbox runner.
- examples/plan-acp-agent rewires to @deepseek-ai/dsh-acp-demo and drops
  its tool-bash entry (the spine bundle now composes it); dsh-stdio (the
  renamed stdio-chat home) keeps its /mode command and gains the dsh-mode
  peer edge; the acp bridge keeps the modes surface beside master's
  permission presets.
- mode README gains the Model Experience / Known Limitations sections the
  new README gates require; AGENTS.md ceiling 1370 → 1440 for the kept
  mode/ layout line and Agent efficiency section.
2026-07-15 23:17:18 +08:00

116 lines
3.6 KiB
TypeScript

/**
* 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.
*/
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'
const root = resolve(import.meta.dirname, '..')
const OUT = 'docs/module-graph.md'
type Pkg = PackageGraphNode
const GROUP_ORDER = [
'util',
'llm',
'core',
'bash',
'fs',
'skill',
'compact',
'subagent',
'web',
'timeout',
'todo',
'mode',
'cordis',
'hooks',
'session-persistence',
'session-query',
'support',
'ui',
]
function packageLink(pkg: Pkg): string {
return `[\`${pkg.short}\`](../${pkg.rel})`
}
/** Render the full docs/module-graph.md content (pure, deterministic). */
function render(pkgs: Pkg[]): string {
const edges: string[] = []
for (const p of pkgs) {
for (const d of p.deps) edges.push(` ${nodeId('pkg', p.short)} --> ${nodeId('pkg', d)}`)
}
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} |`
})
return [
'<!-- Generated by scripts/gen-module-graph.ts — do not edit by hand.',
' Run `pnpm run gen-module-graph` to regenerate. -->',
'',
'# Module dependency graph',
'',
'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.',
'',
'```mermaid',
'flowchart TD',
...groupBlocks,
...edges,
'```',
'',
'| Package | Group | Depends on |',
'| --- | --- | --- |',
...rows,
'',
].join('\n')
}
const content = render(collectPackageGraph(root, GROUP_ORDER, 'gen-module-graph'))
if (process.argv.includes('--check')) {
let committed: string | null = null
try {
committed = readFileSync(resolve(root, OUT), 'utf8')
} catch {
// A missing artifact is the expected read failure. Any read failure has the
// same remedy here—regenerate—so it is reported as stale below.
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}.`)