songgms/dsh-git-graph ↗★ 1

dsh-git-graph

在侧边栏绘制Git提交拓扑图及文件差异。 适合开发人员,在对话窗口旁直观查看代码仓库的提交历史与代码变更。

套件
dsh-git-graph
相容性
待驗證
版本
0.1.0
授權
MIT
最近更新
2026年9月30日

同名套件的其他儲存庫

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:songgms/dsh-git-graph

DeepSeek Git Graph

A Git Graph panel for DeepSeek Harness — commit topology, refs, commit details and per-file diffs, in the same window as your conversation.

License: MIT Node

English · 中文


The Git Graph panel

What it does

A sidebar panel, next to the conversation, that draws the commit graph of the repository you are working in.

  • The graph — branch lanes as SVG, drawn newest-first, coloured per line so a branch keeps one colour from its tip to the ref it merges into.
  • Refs — local branches, remotes and tags as chips on the rows that carry them, with the current HEAD marked.
  • Filtering — walk a single ref, or search messages, authors and hashes as you type.
  • Commit details — the full hash, parents, author and committer with dates, the commit body, and every changed file with its +/− counts.
  • Diffs — click a file for a unified diff with additions, deletions and hunk headers styled through the harness's own tokens.
  • Copy and export — copy the full hash, the abbreviation, the subject or the whole message; save the commit as a .patch file.
  • Create refs — make a branch or a tag at the selected commit.

The panel follows the harness theme, so it reads correctly in light and dark beside the shipped pages.

Layout

sidebar icon ──▶ main panel
                 ├── repo picker · pull · push · stash · refresh
                 ├── toolbar: ref filter · text search · row density
                 ├── commit table  → lane graph (SVG) · refs · subject · author · date
                 └── detail pane   → hash, parents, author/committer, body,
                                     changed files, unified diff, copy + patch + new ref

Install

The package is a DeepSeek Harness bundle: a Host half that registers HTTP routes and a browser half that renders the panel. Installing it takes two steps: get the package, then install the bundle into a profile.

1. Get the package

From npm — once the package is published to the registry:

npm install dsh-git-graph

It is not published yet; until it is, install it straight from GitHub:

npm install github:songgms/dsh-git-graph
# or
npm install git+https://github.com/songgms/dsh-git-graph.git

2. Install the bundle into DeepSeek Harness

From a Harness session, point the bundle installer at the package directory:

plugin_manager action=install_bundle target=

The target is the package directory: node_modules/dsh-git-graph when the package was installed with npm, or this checkout's own directory when you are developing it.

install_bundle performs the package installation and the bundle selection, and the change survives a restart. Do not hand-edit the profile's package.json or cordis.patch.yml — the installer owns those.

Then:

  1. Reload the page. The browser half is a client module, and a running page's boot graph does not contain it until the shell reboots.
  2. Restart the profile if this package was already installed before the change: the loader caches a plugin module by URL, so a link:-installed bundle keeps executing the JavaScript the process first imported.

Verify it is live with cordis_inspect_query: Slots.listSubTree on sidebar.panellist should show id: git-graph, and on main a key: git-graph. The Host row is include:git-graph.

Configure

Everything is optional; the defaults work without a patch layer.

- id: git-graph
  name: dsh-git-graph
  config:
    extraRoots: []        # extra repositories always offered in the picker
    probeRoots: []        # extra paths probed for a containing repository
    discoverMaxDepth: 6   # parent levels walked when looking for a repository
    scanChildren: true    # also probe each candidate's immediate subdirectories
    maxChildren: 24       # cap on subdirectories probed per candidate
    discoverCacheMs: 5000 # probe-result TTL
    timeoutMs: 30000      # ceiling for one git invocation
    maxCommits: 5000      # maximum commits one page may request

An invalid value is reported with its path and falls back to the default, so a typo in a patch layer never leaves the panel unable to open.

How the panel finds a repository

Two independent sources, merged:

  1. The open workspace — the page's session snapshot carries the directory and the Host probes it, then its parents (discoverMaxDepth), then its immediate subdirectories (scanChildren). A workspace that is a repository is found by the first; a session opened on a parent folder holding several checkouts is found by the last.
  2. The Harness process directory — plus any configured extraRoots or probeRoots.

A repository is identified by git rev-parse --show-toplevel, so the picker shows the repository root rather than whichever subdirectory happened to match. When nothing is found, the empty state accepts a pasted absolute path.

What it writes

Everything the panel does is a read except a small, deliberate set of repository operations, all of which live in the header's top-right corner and are user-only: the agent cannot reach them.

OperationWhat it runsWhy it is safe here
New branch / New taggit branch / git tag Add one ref, touch nothing else. An existing name is refused, and a name is checked locally and then by git check-ref-format --branch.
Pullgit pull (merge / rebase / ff-only)The one operation that can leave conflicts. The panel detects them (git's own markers and unmerged paths), shows a banner, and offers Abort — the machinery the read-only design deferred is now in place.
Pushgit push to the branch's upstreamNever touches the working tree; a refused push leaves the repository exactly as it was.
Stashgit stash push -u, list, pop, dropFully reversible: saving captures the changes (untracked files included by default, with an optional message) and the menu restores or deletes entries; a conflicting pop keeps the stash intact.

Save as patch is a read: git format-patch -1 --stdout renders the commit as an mbox patch and the browser turns it into a download, so the plugin still creates no file on disk.

Every state-changing route is POST-only and requires the x-dsh-plugin CSRF header, so a cross-site page cannot drive any of them.

Cherry-pick, revert, rebase by hand, fixup/squash and reset are still not exposed. They need machinery this plugin does not have — a confirmation flow that distinguishes a destructive action from a clipboard copy, a rebase todo editor, and a pre-flight check for uncommitted work. Shipping them without that would turn a plugin that is safe by construction into one that is safe by luck.

Architecture

PiecePathRole
Host halfindex.jsMount only: config schema, webServer route registration.
Git layerlib/git.jsEvery git invocation: argument arrays, no shell, non-interactive env, hard timeout. Parses log --numstat and show --numstat.
HTTP surfacelib/routes.jsThe /api/dsh-git/* handlers.
Live loaderlib/live.jsRe-imports the two modules above when their bytes change.
Layoutlib/graph.jsPure lane assignment for the commit DAG. No DOM, no React.
Client halfclient.jsSidebar icon, panel, graph renderer, detail and diff view. Generated artifact.
Stylesclient.cssTheme tokens only (--dsw-alias-*), light and dark.

Design notes

  • One request per page. git log -z --topo-order --numstat carries both the topology and every commit's change size. -z is not optional: without it git emits the whole numstat stream after the last formatted commit, so every file list lands on the wrong commit.
  • Lanes, not merge geometry. A lane is handed to exactly one parent, so a merge is a plain horizontal edge into the node rather than a special case.
  • Windowed rendering. Only the rows inside the viewport exist in the DOM, and the graph is one absolutely positioned SVG translated to the window, so a line running past the viewport still draws into it.
  • Truncated history is visible. An edge whose parent falls outside the loaded window is dashed to the bottom of the graph.
  • The panel installs its own stylesheet. The client runner's styles builtin is a closure symbol of dynamic packages; an installed client module's factory never receives it. The panel asks the composition for a styles service and otherwise appends its own `` element.

Why index.js is nearly empty

The loader caches a plugin module by URL, so a link:-installed bundle keeps executing the JavaScript the profile first imported — a set_plugin cycle remounts the fiber but reuses the cached module. So index.js does nothing but mount, and everything behavioural is imported through lib/live.js behind a content stamp:

Edited fileTakes effect
lib/git.js, lib/routes.jsOn the next HTTP request — no restart
index.js, cordis.patch.ymlAfter a profile restart
client.js, client.cssAfter a page reload (the bundle URL is content-revisioned)

Development

npm run build     # inline lib/graph.js and client.css into client.js
npm run check     # parse-check client.js and confirm it is up to date
npm run verify    # every offline suite (needs pwsh)

client.js is a committed build artifact: it is a hand-written window.__ModuleLoader__ package shell, and tools/build-client.mjs inlines lib/graph.js and client.css between markers inside it. Edit those two files, never their inlined copies, then run npm run build.

Verifying

pwsh tools/verify-offline.ps1 [repository-path]   # 232 checks, no server

node tools/serve-routes.mjs 19417                 # the real route table, no app
pwsh tools/verify-live.ps1   -Base http://127.0.0.1:19417/api/dsh-git   # 42
pwsh tools/verify-routes.ps1 -Base http://127.0.0.1:19417/api/dsh-git   # 30
SuiteCovers
tools/verify.mjsparseLog framing, lane assignment and its invariants, edgePath, the browser half's shell and slot registrations, the rendered tree, and the copy menu and ref-action controls driven through their real state machines.
tools/verify-host.mjsThe whole Host half against a real repository: discovery, status, refs, log, commit detail, diff, patch rendering, and every refusal — 13 malformed ref names, duplicate names, unknown kinds, missing commits. The writing path runs against a throwaway repository it creates and cleans up.
tools/verify-reload.mjsThat lib/live.js genuinely re-imports changed bytes.
tools/verify-live.ps1Every route over real HTTP.
tools/verify-routes.ps1/ref and /patch over real HTTP: method and CSRF refusal, creation, duplicate refusal, and that the working tree and HEAD are untouched.

tools/make-demo-repo.ps1 builds the fixture the first two run against — a deliberate merge topology with two feature branches, a nested merge, an annotated tag and a hotfix branch.

Why a harness. A route added to lib/routes.js does not exist in an already-running profile until it restarts, so tools/serve-routes.mjs mounts the same route table on a throwaway port. It adds only exact-path dispatch; everything it answers is the plugin's own handler, guard and CSRF check.

Why nothing captures a child's stdout. The development sandbox refuses the pipes execFile and spawnSync need, so each suite routes git's bytes through a file. The code under test is unchanged either way.

What the suites do not establish: pixels. Rendering, theme resolution and scrolling need the live page.

Contributing

Issues and pull requests are welcome. Two ground rules:

  • client.js is generated. Change lib/graph.js or client.css and run npm run build; a PR that edits the inlined regions directly will be asked to regenerate.
  • New writes need a safety story. If you want cherry-pick or reset in here, open an issue describing the confirmation, conflict and pre-flight handling first — see What it writes for why.

License

MIT © 2026 songgms

DeepSeek Harness is a separate project developed by DeepSeek; dsh-git-graph is an independent contribution to its plugin ecosystem.

dsh-git-graph is a plugin for DeepSeek Harness.