dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A Git Graph panel for DeepSeek Harness — commit topology, refs, commit details and per-file diffs, in the same window as your conversation.
English · 中文

A sidebar panel, next to the conversation, that draws the commit graph of the repository you are working in.
HEAD marked.+/− counts.feat:, fix:, docs:, …)
gets a small coloured chip in the message column, so a column of commits
scans by type. The message column is wide (the author and date columns are
compressed), hover shows the full title, and the date reads MM-DD HH:mm
with the full timestamp on hover..patch file.git reset --hard that moves the branch, the index and the working tree to
that commit. It is the one deliberately destructive operation, so the pane
asks for an explicit confirmation first and refuses while a merge, rebase or
cherry-pick is in progress.-d form only (unmerged branches are refused).A added, M modified, D deleted, R renamed, T
type change, U unmerged, ?? untracked — then the path and changed N,
its added+deleted line total against HEAD (untracked files have none). Two
letters (e.g. MM, AM) mean the index and the working tree disagree.The panel follows the harness theme, so it reads correctly in light and dark beside the shipped pages.
sidebar icon ──▶ main panel
├── repo picker · branch bar (switch/create/merge/delete) · pull · push · stash · refresh
├── toolbar: ref filter · text search · row density
├── commit table → lane graph (SVG) · refs · subject · author · date
│ right-click a row for branch actions
└── detail pane → hash, parents, author/committer, body,
changed files, unified or side-by-side diff, copy + patch + new tag
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.
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
From a Harness session, point the bundle installer at the package directory:
plugin_manager action=install_bundle target=<path to dsh-git-graph>
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:
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.
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.
Two independent sources, merged:
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.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.
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.
| Operation | What it runs | Why it is safe here |
|---|---|---|
| New branch / New tag | git branch <name> <commit> / git tag <name> <commit> | 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. |
| Pull | git 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. |
| Push | git push to the branch's upstream | Never touches the working tree; a refused push leaves the repository exactly as it was. |
| Stash | git stash push -u, list, pop, drop | Fully 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. |
| Switch branch | git checkout <branch> against a verified refs/heads/<name> | The branch bar's top row. git itself refuses a switch that would lose work, and the unknown-name path is refused before checkout runs. |
| Merge branch | git merge --no-edit <branch> against a verified refs/heads/<name> | A conflict leaves MERGE_HEAD and unmerged paths, which the same banner and Abort handle as a conflicting pull; a clean merge creates one commit and touches nothing else. |
| Delete branch | git branch -d <branch> against a verified refs/heads/<name> | The SAFE form only: -d refuses a branch whose commits are not merged, so no route can delete commits that exist nowhere else. The checked-out branch is refused up front; there is no force-delete anywhere. |
| Merge a commit | git merge --no-edit <hash> against a verified commit | The right-click path: merges one revision into the current branch without checking anything out. Conflicts leave MERGE_HEAD and unmerged paths, handled by the same banner and Abort. |
| Restore to a version | git reset --hard <hash> against a verified commit | The deliberately destructive one, walled in: the pane requires an explicit confirmation, the hash is resolved first, and a merge/rebase/cherry-pick in progress is refused (OPERATION_IN_PROGRESS) rather than clobbered. --hard means uncommitted changes and commits above the target are gone. |
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 and fixup/squash are still not exposed. They need machinery this plugin does not have — a rebase todo editor, and a pre-flight check for uncommitted work beyond the explicit confirmation the Restore action already requires. Shipping them without that would turn a plugin that is safe by construction into one that is safe by luck.
| Piece | Path | Role |
|---|---|---|
| Host half | index.js | Mount only: config schema, webServer route registration. |
| Git layer | lib/git.js | Every git invocation: argument arrays, no shell, non-interactive env, hard timeout. Parses log --numstat and show --numstat. |
| HTTP surface | lib/routes.js | The /api/dsh-git/* handlers. |
| Live loader | lib/live.js | Re-imports the two modules above when their bytes change. |
| Layout | lib/graph.js | Pure lane assignment for the commit DAG. No DOM, no React. |
| Client half | client.js | Sidebar icon, panel, graph renderer, detail and diff view. Generated artifact. |
| Styles | client.css | Theme tokens only (--dsw-alias-*), light and dark. |
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.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 <style> element.index.js is nearly emptyThe 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 file | Takes effect |
|---|---|
lib/git.js, lib/routes.js | On the next HTTP request — no restart |
index.js, cordis.patch.yml | After a profile restart |
client.js, client.css | After a page reload (the bundle URL is content-revisioned) |
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.
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
| Suite | Covers |
|---|---|
tools/verify.mjs | parseLog 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.mjs | The 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.mjs | That lib/live.js genuinely re-imports changed bytes. |
tools/verify-live.ps1 | Every 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.
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.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.