# DSH-EZ Commit Plugin > 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](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE) [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green)](../package.json) [![Version](https://img.shields.io/badge/version-0.0.3-lightgrey)](../package.json) [![Release](https://img.shields.io/github/v/release/PenguinAndy/dsh-ezcommit-plugin?label=release)](https://github.com/PenguinAndy/dsh-ezcommit-plugin/releases) English | [简体中文](../README.md) ## Table of Contents - [Introduction](#introduction) - [Features](#features) - [Quick Start](#quick-start) - [Installation](#installation) - [Usage](#usage) - [How It Works](#how-it-works) - [API Contract](#api-contract) - [HTTP API Overview](#http-api-overview) - [Model Output Contract](#model-output-contract) - [Security & Privacy](#security--privacy) - [Development](#development) - [Project Structure](#project-structure) - [Local Verification](#local-verification) - [Related Documents](#related-documents) - [Contributing](#contributing) - [License](#license) ## 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 add dsh-ezcommit-plugin` installs and upgrades it (the `dsh.bundle` contract, see [INSTALL.en.md](./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 | # | Requirement | Implementation | |---|-------------|----------------| | 1 | Non-git workspace → button disabled | Host `shell` runs `git rev-parse --is-inside-work-tree` | | 2 | Branch name shown left of the button | `git symbolic-ref --short HEAD` (falls back to short hash when detached) | | 3 | Click opens a confirmation dialog | Custom modal drawn in the Client `shell.overlay` slot | | 4 | Model splits commits by business intent | Host `llm.stream()` analyzes change facts and outputs a JSON plan | | 5 | No changes → disabled | Disabled when the `git status --porcelain` count is 0 | | 6 | Model rules "environment noise" → notify, don't commit | Verdict `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](./INSTALL.en.md), [中文](../docs/INSTALL.md)): ```bash # 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 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: ```mermaid 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
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
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): | Method | Path | Arguments | Key results | |--------|------|-----------|-------------| | `git.state` | `POST /ezcommit/api/git.state` | `{sessionId}` | `{ok, inRepo, branch, hasChanges, changedCount, untrackedCount}` | | `commit.analyze` | `POST /ezcommit/api/commit.analyze` | `{sessionId}` | `{ok, verdict:'noise'\|'commit', reason, commits:[{title,body,files}], unplanned, problems, sensitive:[path], diffTruncated, target}` | | `commit.execute` | `POST /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): ```json { "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 ```bash 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](./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. ### Related Documents - Installation spec: [INSTALL.en.md](./INSTALL.en.md) / [INSTALL.md](./INSTALL.md) (Chinese) - Design: [dsh-ez-commit-design.md](./dsh-ez-commit-design.md) (Chinese) - Verification: [VERIFICATION.md](./VERIFICATION.md) (Chinese) ## Contributing Issues and pull requests are welcome. For PRs, please: - Write commit messages following [Conventional Commits](https://www.conventionalcommits.org/); - Run `pnpm verify` after touching `src/`; - Update this README and the `docs/` when behavior changes. ## License [MIT](../LICENSE) © [PenguinAndy](https://github.com/PenguinAndy)