一键 Commit(DSH-EZ Commit)DSH 插件设计
August 22, 2026 · View on GitHub
状态:已实现为静态双面 bundle(v0.0.3 起)。所有 API 契约均通过 Inspect Provider 与源码检出实际验证(2026-08-21);双面挂载机制对照
dsh-skin-market/ maid-atelier 实测。
0. 结论:需求足以独立开发为一个 DSH 静态双面 Plugin
六条需求全部能被当前 DSH 环境已挂载的能力覆盖,无需改 DSH 本体:
| # | 需求 | 承载能力(已验证) |
|---|---|---|
| 1 | 非 git 仓库 → 按钮置灰 | Host shell 服务执行 git rev-parse --is-inside-work-tree;Client 按钮禁用态 |
| 2 | 按钮左侧展示分支名 | git symbolic-ref --short HEAD(detached 时退化为短 hash),渲染在按钮左侧 |
| 3 | 点击弹二次确认框 | Client shell.overlay 插槽(已验证:root 域、空占用、点击穿透层)自绘 modal |
| 4 | 模型按业务颗粒度拆分 commit | Host llm 服务 stream()(已验证 GenerateOptions 契约 + dsh-compaction-basic 的一次性调用配方) |
| 5 | 无改动 → 置灰 | git status --porcelain 计数,为 0 时禁用按钮 |
| 6 | 模型判定"环境噪音"→ 弹窗提示不 commit | 同一模型调用输出 verdict: "noise",Client 弹反馈框,不执行任何 git 写操作 |
架构归属:一个包,双平面(Host + Client),DSH 静态双面 bundle(dsh.bundle.patch + dsh.client + exports "./client",与第三方皮肤包同机制)。
- Host 半(
main):git 状态采集 / diff 分析 / 模型调用 / 分批执行 commit(全部有服务可依:shell、llm、workspaceRegistry、agents、agentDefaultModel);向webServer注册/ezcommit/api前缀路由。 - Client 半(
exports "./client"):按钮 + 弹窗 UI(插槽conversation.session.header.actions+shell.overlay,均已查到完整注册协议),浏览器模块工厂(window.__ModuleLoader__)加载。 - 通信:Client 经同源 HTTP(
fetchPOST JSON)调用 Host 路由;同源校验拒绝跨站调用。安装并重启 profile 后即生效,无需 cordis preset /cordis_define。
1. 总体架构
Client(浏览器) Host(Node 进程)
┌─────────────────────────────┐ ┌──────────────────────────────────────┐
│ conversation.session.header │ │ RPC: git.state(sessionId) │
│ .actions 插槽 │ │ ├─ workspaceRegistry.list() →路径 │
│ └─ CommitBar 组件 │ │ └─ shell: git rev-parse/status │
│ ├─ [分支名] [一键Commit]│ │ → {inRepo, branch, hasChanges}│
│ └─ 状态机: enabled/ │ │ RPC: commit.analyze(sessionId) │
│ disabled │ │ ├─ git diff/status(截断上限) │
│ shell.overlay 插槽 │ │ ├─ llm.stream(分析提示词, 当前会话 │
│ └─ CommitDialog 组件 │ │ │ 模型) → JSON 计划 │
│ 确认 → 分析中 → 计划 │ host. │ └─ 校验 JSON → {verdict, commits} │
│ 审查 → 执行 → 结果 │ fetch │ RPC: commit.execute(sessionId, plan) │
│ (噪音 → 提示弹窗) │ │ ├─ 逐批: git add --pathspec-from- │
│ timer: 每 5s 轮询 git.state │ │ │ file=- ; git commit -m │
└─────────────────────────────┘ │ └─ 返回每批 commit hash │
└──────────────────────────────────────┘
数据流(一次完整操作)
- 挂载时 + 每 5s:Client
POST /ezcommit/api/git.state {sessionId}→ 更新按钮(分支名、灰/亮)。 - 点击按钮(仅在有改动时可点)→ 弹确认框(分支 + 改动计数)【需求 3】。
- 用户确认 →
commit.analyze:Host 取 diff → 调当前会话模型分析 → 返回裁决【需求 4/6】。 - 裁决为
noise→ Client 弹"环境噪音,无需 commit"反馈框,不执行任何 git 写操作【需求 6】。 - 裁决为计划 → 弹计划审查框:展示拆分后的批次(顺序、每批 message、文件清单),用户确认后执行。
commit.execute:逐批git add+git commit,返回每批短 hash;Client 弹成功框并立即刷新按钮状态(置灰)【需求 5 闭环】。
2. 需求 → 设计映射(细节)
2.1 仓库判定与按钮置灰(需求 1、5)
- Host
git.state返回:inRepo:git -C <path> rev-parse --is-inside-work-tree(支持工作区在仓库子目录)。branch:git symbolic-ref --short HEAD,失败(detached HEAD)→git rev-parse --short HEAD。hasChanges:git status --porcelain非空(含 staged / unstaged / untracked)。changedCount/untrackedCount:--porcelain行分类计数,供确认框展示。
- 按钮禁用条件:
!inRepo || !hasChanges || inFlight || analyzing。 - 轮询间隔 5s(Client 原生
setInterval,随组件卸载清理)+ 点击/执行完成时立即刷新。不监听文件系统(避免复杂 watcher;轮询成本 ≈ 一次 porcelain,可接受)。
2.2 分支名展示(需求 2)
按钮条结构:[分支名 chip] [一键Commit button],同一 flex 行。分支名为空/非仓库时显示"非Git仓库";无改动时仍显示分支名但整体置灰。
2.3 二次确认(需求 3)
shell.overlay 注册 id one-click-commit-dialog 的 modal(层本身点击穿透,modal 根节点 pointerEvents: 'auto' + 遮罩)。四态状态机:confirm → analyzing → plan → done/error,噪音分支跳 noise 态。
2.4 模型拆分颗粒度(需求 4)
- 模型来源(当前会话模型):优先
agents.get(sessionId)→agent.session.requestHeader()?.config(会话已路由的 provider/model,源码中dsh-compaction-basic同款取法);退化为agent.options.provider/model;再退化agentDefaultModel.currentSelection()。 - 调用方式:
ctx.llm.stream({ provider, model, messages: [{role:'user', content:[{type:'text',text:PROMPT}], source:{kind:'plugin',plugin:'one-click-commit'}}], system: SYSTEM, maxTokens: 4096, signal });手写迷你 assembler 累积text-delta,读取finish块处理 error/aborted。 - 喂给模型的改动事实:
git status --porcelain(全部条目,含未跟踪;敏感路径行替换为[敏感文件已隐藏]占位)git diff --stat+git diff(统一上限,如 200KB,截断时注入"DIFF TRUNCATED"标记并告知模型;敏感文件的 stat 路径与 diff hunk 整体省略)- 未跟踪文本文件内容采样(每个 ≤8KB,超限标记"binary/large";敏感路径不采样,提示词中的路径使用相对路径)
- 敏感路径过滤:
.env*、.npmrc、.pypirc、.netrc、.git-credentials、*.pem、*.key、id_rsa*、.ssh/、.aws/、.kube/、credentials*.json等路径不进入fileSet与模型提示词;commit.execute二次校验时同样拒绝这些路径,commit.analyze通过sensitive字段把清单返回给 Client 仅做本地展示。 - 输出 JSON 契约(system 提示词内定义,响应解析时剥离 ```json 围栏):
{ "verdict": "noise" | "commit", "reason": "一句话说明(noise 时必填)", "commits": [ { "title": "feat(scope): ...", "body": "可选多行说明", "files": ["相对路径..."] } ] } - 拆分规则(提示词要求):按业务意图分组(一个功能/一个修复/一个重构 = 一批);每个文件恰好属于一批;批次顺序按依赖(先基础后上层);title 遵循 Conventional Commits(feat/fix/refactor/chore/docs/test);
files路径必须来自输入的改动清单,模型不得发明路径(解析后校验:不存在或重复的文件 → 归入未计划集合并在 UI 提示)。 - 噪音判定标准(提示词要求):模型产物、锁文件自动生成、格式化器副产品、
.DS_Store、空变更、与需求无关的临时文件等 →noise。是否 commit 完全由模型裁决,插件不做写操作直到裁决为 commit 且用户确认。
2.5 执行(需求 4 后半)
commit.execute 逐批执行:
git -C <path> add --pathspec-from-file=- --(文件清单经 stdin 传入——已验证ShellExecRequest.stdin字段,天然规避空格/特殊字符路径转义问题)git -C <path> commit -m <title> [-m <body>]- 每批后
git rev-parse --short HEAD记录 hash;任一批失败 → 停止并返回已成功批次 + 错误详情(不回滚,由用户决定)。 - 完成后
git status --porcelain复查残留,返回leftoverCount。
2.6 噪音防污染(需求 6)
- 噪音裁决在
git add/commit之前完成;noise 路径上 Host 零 git 写操作。 - 噪音信息 UI:黄色反馈框"环境噪音,无需 commit"+ 模型给出的 reason + "仍然查看详情"折叠。
3. 已验证的关键契约清单(实现时直接引用)
| 契约 | 事实 |
|---|---|
Host shell | resolve(ShellExecRequest{command, workdir?, timeoutMs?, stdoutMaxBytes?, stdin?, signal?}) → run(spec) → ShellRunResult{exitCode, stdout, stderr, timedOut, aborted} |
Host llm | stream(GenerateOptions{provider, model, reasoningEffort?, messages, system?, maxTokens?, signal?}) → AsyncIterable<StreamChunk>(block-start/text-delta/block-end/finish{reason:{kind:'stop'|'error'|...}}) |
| 一次性调用配方 | dsh-compaction-basic:messages 手拼 user 消息(source:{kind:'plugin', plugin})、llm.stream() 消费 chunks |
| 会话模型取法 | agents.get(sessionId).session.requestHeader()?.config;兜底 agentDefaultModel.currentSelection() → ModelSelection{provider, model, reasoningEffort?} |
| 工作区路径 | workspaceRegistry.list() → Workspace(含 path、sessionIds);按 sessionId 反查 |
Client 插槽 conversation.session.header.actions | kind:list、scope:session、注册项 {id, order?, label?}、无 owner props;标准 props:sessionId、useSession、useWorkspaces、useInput、inputActions;现有占用:agent-preset(-10)、subagent-catalog(10)、job-list(20) → 新 id one-click-commit、order 建议 30 |
Client 插槽 shell.overlay | kind:list、scope:root、空占用;层点击穿透,入口需自设 pointer-events |
| Client 侧能力 | React.createElement/useState/useEffect(require("react") 基线)、fetch(同源 JSON)、<style> 元素注入 |
| 双面通信 | Host webServer.register({kind:'prefix', path:'/ezcommit/api', handler});Client fetch POST JSON;仅同源、参数与返回值仅无损 JSON |
| Client 服务 | slots(register/inject,ctx.get('slots')) |
| 会话→工作区 | Client 侧亦可 useWorkspaces().items.find(w => w.sessionIds.includes(sessionId))?.path(WorkspaceView{workspaceId, path, title, sessionIds} 已验证);本设计以 Host 反查为准,Client 只传 sessionId |
4. 需求未覆盖、由本设计补足的决策点
| 决策点 | 默认选择 | 备选 |
|---|---|---|
| 按钮位置 | 会话标题行操作区(conversation.session.header.actions) | 输入框工具行左端 conversation.input.left;dock 条 |
| 分析后是否先给用户看拆分计划 | 是(计划审查框,用户确认后才执行)——这是"分批 commit"价值的可见部分,也符合"二次确认"精神 | 直接执行(少一次点击,但用户看不到拆分结果) |
| 模型裁决噪音 | 纯模型裁决(含噪音事实清单) | 加确定性预过滤(.DS_Store 等先剔除再问模型) |
| commit message 风格 | Conventional Commits | 自由文本 |
| diff 上限 | 200KB 截断 + 标记 | 可配置 |
| 轮询间隔 | 5s | 事件驱动(fs watcher,复杂度高) |
| 多批次中某批失败 | 停止、不回滚、报告 | 继续执行后续批次 |
| 空仓库(无 HEAD) | 支持 root commit(rev-parse HEAD 失败时走未出生分支路径) | 提示用户先手动初始化提交 |
5. 风险与边界
- 模型输出解析失败(非 JSON / 非法 files)→ 重试一次;再失败返回错误弹窗,绝不猜测执行。
- 超大 diff / 二进制:截断与采样上限兜底,模型被告知截断事实,宁判噪音勿误 commit。
- 并发:同一 sessionId 的 analyze/execute 设置 in-flight 锁(Host 内存态),重复点击直接拒绝。
- 提交安全:绝不触碰用户未确认的批次;不
git push;不--force;不修改历史。插件生命周期内所有副作用(RPC handler、轮询、插槽、样式)均通过ctx.effect/slots.inject挂在 Fiber 上,stop/update/undefine 时自动清理。 - 数据外发边界:
commit.analyze会把 status、diff 与非敏感未跟踪文本采样发送给当前会话模型服务商;敏感路径(.env*、密钥、凭据类)在 Host 侧过滤,不进入提示词、不参与计划、也不允许执行提交。使用者仍应只对允许外发的代码/数据使用本插件。 - 生效方式:静态双面包随 profile 启动加载,客户端 bundle 由
dsh-client-modules组装进 web 启动图;更新/卸载需重启 profile 生效。
6. 实施计划(静态双面 bundle)
- 包契约:
dsh.bundle.patch(锚点行ezcommit)+dsh.client(platform: 'web'、inject: [])+exports "./client"。 - Host 半(
src/index.js,main入口):webServer.register({kind:'prefix', path:'/ezcommit/api', handler})挂载三个方法路由(git.state/commit.analyze/commit.execute),同源校验 + JSON 体解析;git.state:路径反查 + 三个只读 git 命令;commit.analyze:diff 采集(含截断)→ 模型调用 → JSON 校验;commit.execute:分批 add/commit + hash 收集;ctx.get('webServer'|'shell'|'llm'|'workspaceRegistry'|'agents'|'agentDefaultModel')缺失时优雅降级(返回{ok:false, error}),路由挂载失败仅告警。
- Client 半(
src/client.js,window.__ModuleLoader__.load({id:'dsh-ezcommit-plugin', factory})):slots.inject('conversation.session.header.actions', ...)注册 CommitBar(分支 chip + 按钮 + 禁用态);slots.inject('shell.overlay', ...)注册 CommitDialog(四态状态机 + 噪音框);<style id="ezcommit-styles">注入局部样式(主题 CSS 变量,ctx.effect持有卸载);setInterval每 5s 轮询 + 动作后即时刷新;fetch('/ezcommit/api/<method>', …)调 Host。
dsh plugin --profile web add安装 → 重启 profile → 用一个测试仓库逐项验证六条需求(见 docs/VERIFICATION.md)。