DSH-EZ Commit Plugin

August 22, 2026 · View on GitHub

One-Click Commit, a static dual-face DSH (DeepSeek Harness) plugin: after install and a profile restart, every web session header automatically shows a One-Click Commit button. The current session model splits your workspace's git changes into multiple commits by business intent (Conventional Commits); once you review the plan, the batches are executed one by one. If the model rules the changes as "environment noise", it only notifies you — no git write operations are performed.

License: MIT Node.js Version Release

English | 简体中文

Table of Contents

Introduction

DSH-EZ Commit is a static dual-face DSH plugin (the same mechanism used by third-party skin packages such as dsh-skin-market):

  • One package, two planes: the Client half (exports "./client" + the dsh.client declaration, a browser module factory) owns the UI (button + dialogs); the Host half (the main entry) mounts a same-origin HTTP API on webServer, collects git state, asks the model for a verdict, and executes batches.
  • Install and it works: dsh plugin --profile <name> add dsh-ezcommit-plugin installs and upgrades it (the dsh.bundle contract, see INSTALL.en.md); after a profile restart the session header automatically shows the button. No cordis preset and no cordis_define / cordis_run are needed.
  • Zero runtime dependencies: only Node built-ins plus browser-native capabilities (fetch / React.createElement); React comes from the web shell's seeded baseline.
  • Model first: splitting and the noise verdict are made entirely by the current session model — the plugin itself never performs speculative git write operations.

Features

#RequirementImplementation
1Non-git workspace → button disabledHost shell runs git rev-parse --is-inside-work-tree
2Branch name shown left of the buttongit symbolic-ref --short HEAD (falls back to short hash when detached)
3Click opens a confirmation dialogCustom modal drawn in the Client shell.overlay slot
4Model splits commits by business intentHost llm.stream() analyzes change facts and outputs a JSON plan
5No changes → disabledDisabled when the git status --porcelain count is 0
6Model rules "environment noise" → notify, don't commitVerdict verdict: "noise"; zero git write ops on the Host

Quick Start

Installation

Install with the official DSH CLI for distribution and version management (full spec in INSTALL.en.md, 中文):

# from npm (once published) — registers a profile bundle layer, versioned by pnpm
dsh plugin --profile web add dsh-ezcommit-plugin

# or from the git repository / a local checkout
dsh plugin --profile web add git+https://github.com/PenguinAndy/dsh-ezcommit-plugin.git
dsh plugin --profile web add link:/path/to/dsh-ezcommit-plugin

Then restart the profile (e.g. dsh web): the startup log prints [dsh-ezcommit-plugin] v<x.y.z> installed plus the route-mounting result; after a page refresh, every web session header shows [branch] [One-Click Commit], grayed out outside git repositories or with no changes. No in-session steps are required.

Usage

  1. Click One-Click Commit → a confirmation dialog shows the branch and change counts (second confirmation).
  2. Start Analysis → the Host collects status / diff / untracked samples (sensitive paths are filtered — never sampled, never included in the prompt) and asks the model currently routed for this session to rule:
    • noise → a yellow dialog shows the reason; no git write operations are performed;
    • commit → a plan-review dialog lists the batches (order, message, files), the filtered sensitive files, and warnings about uncovered files.
  3. Execute N Commits → batch by batch: git add --pathspec-from-file=- -- (the file list is piped through stdin, naturally avoiding whitespace/special-character path escaping) + git commit -m title [-m body], returning the short hash of each batch.
  4. On completion the button refreshes to disabled immediately; if any batch fails, execution stops (no rollback) and successful batches are still reported.

How It Works

One package, two planes: the Client half is composed into the web boot graph by dsh-client-modules (/plugins/dsh-ezcommit-plugin/client.js) and mounted in the browser via the window.__ModuleLoader__ factory format; the Host half loads in the Node process and registers the /ezcommit/api prefix routes on webServer. The Client calls the Host over same-origin HTTP. The sequence below shows one complete operation:

sequenceDiagram
    participant C as Client (browser)
    participant H as Host (Node process)
    participant M as Current session model

    Note over C: setInterval: polls every 5s
    C->>H: POST /ezcommit/api/git.state {sessionId}
    H-->>C: { inRepo, branch, hasChanges, changedCount }
    Note over C: not a git repo / no changes → button disabled

    C->>C: click "One-Click Commit" → confirmation dialog
    C->>H: POST /ezcommit/api/commit.analyze {sessionId}
    H->>H: collect status / diff / untracked samples<br/>truncation caps + sensitive-path filtering
    H->>M: llm.stream(change facts + system prompt)
    M-->>H: JSON plan { verdict, commits }
    alt verdict = "noise"
        H-->>C: environment noise → notification dialog (zero git writes)
    else verdict = "commit"
        H-->>C: batch plan + file list (user review)
        C->>H: POST /ezcommit/api/commit.execute {sessionId, commits}
        H->>H: batch git add + git commit<br/>stop on first failure (no rollback)
        H-->>C: { results: [{ title, hash }], leftoverCount }
    end

API Contract

HTTP API Overview

The Host half mounts three same-origin JSON APIs on webServer (POST only, same-origin checked; arguments and return values are lossless JSON only):

MethodPathArgumentsKey results
git.statePOST /ezcommit/api/git.state{sessionId}{ok, inRepo, branch, hasChanges, changedCount, untrackedCount}
commit.analyzePOST /ezcommit/api/commit.analyze{sessionId}{ok, verdict:'noise'|'commit', reason, commits:[{title,body,files}], unplanned, problems, sensitive:[path], diffTruncated, target}
commit.executePOST /ezcommit/api/commit.execute{sessionId, commits}{ok, results:[{title,hash}], leftoverCount}

All failures return {ok:false, error:{message, code}}; non-POST → 405, unknown method → 404, cross-origin → 403.

Model Output Contract

The system prompt asks the model to output only the following JSON (the parser strips markdown code fences from the response; on parse failure it retries once):

{
  "verdict": "noise",
  "reason": "one-line explanation (required for noise)",
  "commits": [
    { "title": "feat(scope): ...", "body": "optional multi-line body", "files": ["relative paths..."] }
  ]
}
  • files must be taken verbatim from the change list; missing or duplicated files go into unplanned and are surfaced as UI warnings.
  • Model resolution order: session-routed config (session.requestHeader().config) → agent.options → default model (agentDefaultModel.currentSelection()).

Security & Privacy

  • The noise verdict happens before any git add/commit; the noise path performs zero git write operations.
  • Sensitive path filtering: .env*, .npmrc, .pypirc, .netrc, .git-credentials, *.pem, *.key, id_rsa*, .ssh/, .aws/, .kube/, credentials*.json and similar — their paths and contents are never sent to the model, never enter a plan, and can never be committed; filtered files are listed in the plan/noise dialog (displayed locally only).
  • Privacy boundary: after clicking "Start Analysis", the workspace status, diff and untracked text samples (first 8 KB per file, up to 32 files) are sent to the current session model provider — sensitive paths excluded. Use it only on workspaces you are allowed to share with the model provider.
  • The API only accepts same-origin POSTs (Origin must match Host); cross-site calls are rejected.
  • Before commit.execute, the known file set is rebuilt from the current porcelain output — stale or invented paths are always rejected.
  • No git push, no --force, no history rewriting; if a batch fails, execution stops, nothing is rolled back, and everything is reported truthfully.
  • Analyze/execute per sessionId have an in-process in-flight lock; duplicate clicks are rejected.
  • All side effects (routes, slots, polling, styles) hang off the plugin lifecycle and are cleaned up when the bundle layer unloads.

Development

Project Structure

.
├── .github/workflows/
│   └── release.yml               release workflow
├── docs/
│   ├── README.en.md              English README (this file)
│   ├── INSTALL.md                Installation spec (Chinese)
│   ├── INSTALL.en.md             Installation spec (English)
│   ├── dsh-ez-commit-design.md  Design doc (requirements → contract mapping, decisions, risks)
│   └── VERIFICATION.md          On-device verification walkthrough (six requirements)
├── src/
│   ├── index.js                  Host half: /ezcommit/api routes + git/model logic + notice/self-check
│   └── client.js                 Client half: button + dialogs + styles (__ModuleLoader__ factory)
├── scripts/
│   ├── verify.mjs               Offline smoke verification (no DSH runtime; real-git integration)
│   └── stage-fixture.mjs        Scenario fixture: demo | noise | clean
├── cordis.patch.yml             Profile bundle layer patch (the ezcommit anchor row)
├── package.json                 Version + dsh.bundle & dsh.client dual-face contract; zero runtime deps
├── pnpm-lock.yaml
└── LICENSE                      MIT

Local Verification

pnpm verify   # or: npm run verify — package contract + HTTP-route integration (real git) + client factory smoke

Runtime verification: after install and a profile restart, follow the walkthrough matrix in VERIFICATION.md to verify the six requirements one by one (using node scripts/stage-fixture.mjs demo|noise|clean to prepare scenarios). The model-verdict path can be exercised with a workspace containing noise such as .DS_Store or lock files.

Contributing

Issues and pull requests are welcome. For PRs, please:

  • Write commit messages following Conventional Commits;
  • Run pnpm verify after touching src/;
  • Update this README and the docs/ when behavior changes.

License

MIT © PenguinAndy