# dsh-git-graph-sidebar [English](README.md) | 中文 一个给 DeepSeek Harness(DSH)用的 Git 插件:输入框旁的**分支徽章**、[dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar) 里的**仓库图谱**页,以及(可选)同一张图谱作为对话主区域里的一页。它把工作区往下三层内发现的 Git 仓库列出来,把选中仓库的提交图渲染成 SVG 泳道画布,并把该仓库的提交规范变成你和模型都真的会照做的东西。 ![仓库图谱页,深色主题](assets/shot-dark.png) ## 为什么选它 会画提交图的插件不少。这个插件把力气花在「提交信息到底能不能写对」上,下面三条别处没有: 1. **规范进的是模型的提示,不只是面板上的标签。** 规范文档注册成 `systemPrompt` 的一个段落,每个模型步骤在动笔之前就知道这个仓库要什么,而不是先被拒绝一次再学;`strict: true` 更进一步,注入的提示要求把不合规的信息先改写再提交。 2. **规范会被读回来,散文也算。** 可机检规则(type 前缀、标题长度、大小写、句号、祈使句、主题语言)既从规范文档里解析,也从旁边的 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`、`CONTRIBUTING.md` 里解析——没写过 `GIT_CONVENTIONS.md` 的仓库照样能被读懂。再往后,向后兼容的 `ai: required | optional | forbidden` 字段检查的是提交**正文**里的 trailer,署名本来就写在那儿。 3. **两份规范打架时,绝不会被悄悄投票投掉。** 往运行中的项目里中途装插件,是第二份规范最常见的来源。面板会逐字段、逐规则比较工作区文档、本仓库文档与上级仓库文档,注入的提示要求模型先问你、不要自己选边;点一下就能把它们合并进 `<工作区>/.dsh/GIT_CONVENTIONS.md`,每份被替换的文件留时间戳备份,被并入的仓库文档改成插件会跟随的指针。 4. **三个入口,一套本体。** 同一份页面既能挂在 better-sidebar 的页签里,也能(开关打开后、随时可关)作为对话区 *对话* / *轨迹* 旁边的一页,图谱想看宽一点不用离开侧边栏这个主入口。 所有 Git 命令都在本机跑,走的是宿主已经提供的回环地址:不联网、不埋点,除了你亲手按的开关之外每个 Git 命令都是只读的。 ## 功能 ### 仓库图谱页(better-sidebar) 它把会话的工作目录当作一个容器:目录下面三层以内的 Git 仓库都能直接访问,不需要把每个仓库单独注册成 DSH 工作区。 - **多级仓库发现**——工作区根目录本身,加上最多 3 层以内的嵌套仓库。每个条目带当前分支、短 head、脏文件数与冲突数。 - **扫描有分寸**——每个仓库先用一次 `git for-each-ref` 同时拿到分支时间、上游与领先/落后距离;refs 没动过就整轮复用上次结果,状态(脏文件、冲突)照旧每次都问。工作区里仓库多的时候,刷新不再按仓库数成倍变慢。 - **仓库树**——根目录、中间目录、每个仓库按层级排开,层级关系直接看得见,而不是靠路径去猜。 - **关联 checkout,只读**——仓库行可以展开它关联的 worktree(`git worktree list`),每行带分支与 HEAD。选中一棵,状态行就切到这棵的分支、HEAD 与脏文件数,图谱里它的 HEAD 行也会被标出——是**一张图**而不是一棵一张图:linked worktree 共享 refs 与对象库,提交集合本来就相同。从这里可以在新会话中打开这棵、作为工作区打开,或复制它的路径;还没注册成工作区的 checkout 会说明原因而不是静默失败,目录已被删掉的仍会列出来。tab 从不新建、删除或切换 worktree——那些仍在 chip 的对话框里。 - **SVG 提交图**——一次提交一行,一条分支一条彩色泳道,父提交在别的泳道时画曲线连接,合并提交画成菱形,带 ref 徽章与相对时间;没有父提交的起点、以及没人从它往下走的末端,在节点正中画一条短横——互不相关的历史因此看起来是刻意断开,而不是线断了。 - **一个分支一枚引用标签**——`main` 与 `origin/main` 指的是同一个分支,所以共用一枚标签,静音一次两边一起关掉;`origin/HEAD` 这类符号引用不再变成标签;**标签不再是筛选项**(一个标签只指向一个提交,静音它只能藏一行),它们仍然显示在提交行上;顺序是当前分支、其他本地分支、只有远程的分支,超过 12 枚先折成 `+N`,有静音时旁边出现「清除筛选」;可筛的分支少于 4 条时整排不显示,不为筛不了什么东西腾地方。行内徽章与导出的 SVG 用的是同一套标签。 - **缩放**——默认双行;`−` / `+` 把行与文字一起放大缩小,从紧凑单行到宽松列表。 - **提交详情卡**——图下方的卡片始终显示你点选的提交:完整 hash、主题、作者、绝对提交时间、引用与父提交。点一行固定,再点一次(或 `×`)取消。 - **复制给 AI**——「复制提交信息」把一段可直接粘贴的文本放进剪贴板(仓库路径、hash、主题、作者、时间、引用、父提交),要模型「回滚到这个提交」时不用再手打;「复制 SHA」只复制完整 hash。 - **导出图片**——工具栏的「导出图片」把当前视图(筛选后的行、泳道、ref 徽章、固定行的高亮)序列化为一张独立 SVG:配色跟着主题走、尺寸自带宽高,浏览器里直接下载 `git-graph-<仓库名>.svg`;没有 Blob/object URL 的环境自动退化为复制 SVG 文本;首尾短横也一并导出。 - **自动更新且不闪烁**——页面订阅宿主的 Git 变更事件,同时自己安静地后台轮询,新提交、新分支会自己出现。每次后台刷新都保留当前内容:结果没变化就不重渲染,轮询失败也不会把已有数据换成报错。 - **刷新**——按需重新扫描;手动刷新期间同样保留面板内容。宿主对一次扫描结果缓存 60 秒(点刷新会绕过缓存)。 - **重开不再空白**——关闭再打开 tab 以前会从空面板重来。现在 tab 把上次画出的仓库列表、选中项和图谱保存在内存里,重开时第一帧就能画出这些内容,再在背后静默刷新。 - **可选的主区域视图**——图谱也能作为对话区 *对话* / *轨迹* 旁边的一页打开:规范条上的「在主区域打开」开关打开,页面右上角的 `×` 关掉,选择会记住,侧边栏页签仍然主入口。没打开之前什么都不注册,别人的视图页签条不会有任何变化。主区域又宽又矮,所以那里的提交卡片显示在图谱右侧而不是下方;整列高度按宿主发布的两个变量算(`--dsh-conversation-viewport-height` 减 `--dsh-composer-height`),卡片不会滑到输入框后面。 - **选中仓库一目了然**——仓库树里当前选中的那行用淡青蓝底色加描边高亮,屏幕上画的是哪个仓库不再有歧义。 - **Git 规范**——图上方一条状态带标出当前仓库的规范文档状态(`草稿` / `已定稿` / 未建立),可一键在编辑器打开,并为全新仓库提供七套验证过的框架来起步;工具栏齿轮里还能保存、编辑和删除自己的模板,并隐藏不想看到的内置模板。 - **生效的规则一目了然**——规范条把文档自己写明的可机检规则列成小标签:`type: feat / fix / docs`、`标题 ≤ 72 字`、`小写开头`、`不加句号`、`祈使句`,写了主题语言时再加 `中文` / `英文` / `中英混排`,强约束文档另有琥珀色「强约束」标记——看到的正是注入给助手的那几条。它**不统计已经写下的提交**:那些信息在面板上改不了,提醒没有意义。文档没写规则时只说「文档没有写明可机检的规则」,绝不自作主张。 - **规范升级预览**——已经写过规范文档的仓库,点「更换框架」不会再被直接覆盖:先给出「写入前的差异」,几节新增、几节改写、几节移除,逐条列出会被改写的小节标题,按「确认写入」才真的落盘,按「取消」什么都不发生。 - **工作区总纲**——工作区里有两个以上仓库时,工具栏下方会出现「工作区规范」一条,一键把 `<工作区>/.dsh/GIT_CONVENTIONS.md` 写成总纲;子仓库没有自己的规范时,注入的提示块会标明「继承自」哪个目录。 - **强约束模式**——新建、更换框架或写工作区总纲时用的是同一个选择列表,列表里多了一个「强约束」开关:打开后文档会写入 `strict: true`,注入的提示要求不合规的提交先改写再提交,规范条上也会挂一个「强约束」标记。列表选完会自动收起,预览和文档不再被挡在下面。开关点下去即刻写入文档(文档已存在时),下次打开列表显示的就是文档里的值。 - **工作区总纲会真的进提示**——容器不是仓库时,`<工作区>/.dsh/GIT_CONVENTIONS.md` 的内容(强约束、主题语言、可机检规则与正文)也会进注入块,并附上子仓库索引。 - **提交语言**——选择列表里多一行「提交语言:中文 / 英文 / 中英混排」。选择写进文档的 `language:` 字段,注入的提示块里也有一行 `Subject language: …`;选中文时英文主题算违规,选英文时中文主题算违规,选混排则只看其他规则。文档没写 `language:` 时不检查主题语言。 - **中英混排的合规检查**——合规检查接受中文、英文和中英混排:type 可以用「新增」「修复」「文档」这类中文别名,全角冒号也认;大小写和祈使句只在首个词是拉丁字母时才检查,所以中文标题不会被误报。 ### 分支徽章(输入框) 沿用上游插件的徽章:空白会话里可以切换或新建分支,并打开交互式提交图对话框与 worktree 管理器。分支操作在宿主进程中执行;活跃会话上徽章自动隐藏。 ## 运行要求 | 项目 | 值 | | --- | --- | | DSH | `>=0.2.1-rc.2 <0.3.0-0` | | Node(宿主运行时) | `^22.19.0 \|\| >=24.0.0` | | `dsh-better-sidebar` | **只影响图谱页**——没装时插件照常加载、徽章照常工作,只是不注册这个页 | ## 安装 从 [最新 Release](https://github.com/Athosoo/dsh-git-graph-sidebar/releases/latest) 下载预构建包 `dsh-git-graph-sidebar.tgz`(无需构建),当成插件 bundle 安装: - **桌面版**(`desktop` profile 由 Electron 应用独占,CLI 会拒绝):在应用的插件页里安装这个 `.tgz`。 - **CLI 管理的 profile**: ```sh dsh plugin --profile add github:Athosoo/dsh-git-graph-sidebar # 或用已下载的 tarball dsh plugin --profile add ./dsh-git-graph-sidebar.tgz ``` **装完需要重启 DSH。** 宿主那一半在启动时注册 `/git/*` HTTP 路由,首次安装仅刷新页面不够;升级到新构建同样要重启进程才会被加载。 图谱页需要 [`dsh-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar);没装它时,分支徽章、图谱对话框、worktree 全部照常工作,只是没有那个侧栏页。 ## 仓库发现规则 | 规则 | 值 | | --- | --- | | 扫描深度 | 工作目录往下 3 层(某层命中仓库后不再向下钻) | | 数量上限 | 单次最多 200 个仓库,超出后结果标记为截断 | | 跳过的目录名 | `node_modules`、`dist`、`build`、`out`、`coverage`、`target`、`vendor`,以及所有点开头目录 | | 仓库判定 | 存在 `.git` 条目(目录、文件或符号链接都算,因此 worktree 与 submodule 都能识别) | | 单条状态预算 | 5 秒;`git status` 超时的条目仍会列出,只是没有分支元信息 | | 缓存 | 按基路径缓存 60 秒,并发请求共用一次扫描;面板上的刷新按钮会绕过缓存 | ## Git 规范 提交图记录「已经发生了什么」,规范文档记录「下次打算怎么做」。图谱页可以为它列出的每个仓库架起这份文档。 - **文档由你定**——插件不替你编团队规矩。它写出的是一份空壳:六节都标着 `待定`、`status: draft`,决定权留给团队,而不是继承别人的默认值。 - **由新仓库触发**——还没有文档、且一次提交都没有(`unborn HEAD`)的仓库,会自动建出空壳并直接弹出框架选择;已有提交但缺文档的仓库只显示「未建立」,点一下即写空壳。 - **七套验证过的框架**——选中一套即把完整模板写进文档,并置为 `status: settled`: | 框架 | 适合 | | --- | --- | | 约定式提交(Conventional Commits) | 需要从提交信息自动生成变更日志与语义化版本的项目 | | GitHub Flow | 从 `main` 发布、用短生命周期功能分支的小团队 | | Git Flow | 需要在多套环境间推进版本发布的项目 | | 主干开发(Trunk-based) | 持续交付、分支生命极短的团队 | | 约定式分支(Conventional Branch) | 分支名要编码类型与 issue 编号的团队 | | GitLab Flow | 在 `main` 与生产之间显式设置环境分支的团队 | | Worktree 工作流(Git Worktree Flow) | 同时维护多个版本、并行开发或热修的项目 | - **绝不覆盖**——已存在的文档只有在请求带 `force` 时才会被替换,界面不会对已定稿的文档提供这个选项。文档是仓库根目录里的普通 Markdown,可以像源码一样评审和修改。 - **我的模板**——工具栏右上角的齿轮先打开「设置」入口列表,点「模板管理」进入模板管理页(选择列表里的「管理模板」直达):把团队自己的规矩写成模板(id、名称、一句话说明、适用场景、参考链接、正文),保存后出现在「更换框架」列表末尾的「我的模板」分组里,带「自定义」徽章;内置七套只读——不能编辑也不能删除,但设置页把它们列在用户模板上方,每套带一个显示/隐藏开关(`POST /git/convention/templates` 的 `toggle` action,落盘为 `hiddenBuiltins`);隐藏只影响「更换框架」列表,绝不改正文,已经用该框架写出的文档照常解析。正文保存时强制写入 `framework: <模板 id>` 与 `status:` 行,超过 64KB 或缺 `status:` 会被拒绝,id 不能与内置框架或 `none` 重名,最多 50 个。模板存在 `$DSH_HOME/git-graph/conventions-templates.json`,不进 profile 配置;删除模板不会改动已经写出的文档,只是文档里的 `framework:` 变成悬空值(冲突对账仍会把它报出来)。 - **AI 署名是明确的政策,不是默认假设**——前置字段新增 `ai: required | optional | forbidden`,默认 `optional`,老文档不受影响。`required` 要求每条信息带一个 AI 署名 trailer(`Assisted-by:` / `Generated-by:` / `Co-authored-by:` 都认),`forbidden` 只拦 AI 专用的那几个(人类结对提交的 `Co-authored-by:` 不算违规)。检查读的是**提交正文**——以前只看标题,所以根本看不到 trailer。和其它规则一样:面板始终标出,只有 `strict: true` 时才真的拒绝提交。 - **冲突会被报出来,不会被悄悄投票**——插件把「管同一批提交」的文档三方对比(工作区 `<工作区>/.dsh/GIT_CONVENTIONS.md`、当前仓库自己的、以及祖先仓库的):字段(`status` / `framework` / `strict` / `language` / `ai`)与规则(标题长度、type 词表、小写、句号、祈使句)都比,但**只有双方都明确写了同一个字段且取值不同才算冲突**,沉默不算。有冲突时,每一步注入给助手的提示里都会带上这份分歧清单,并明确要求它「不要自己选边,先问用户」——所以中途才加入工作区的插件、模型也不会闷头照旧规范写。工作区或仓库旁边的 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`、`CONTRIBUTING.md` 也走同一套规则解析器:写在别的文件里的规则同样会进冲突清单,而这些文件只读、永不改写。 - **合并由智能体来做,不是正则拼的**——面板上的「AI 合并」与冲突条上的按钮是同一条路:把这件事交给模型。它把管这批提交的每一份文档完整读出来,逐段比较,自己判断哪些是重复、哪些是真冲突,最后把合并成的一份写回 `<工作区>/.dsh/GIT_CONVENTIONS.md`。落盘前不会偷偷覆盖:每份被替换的文件都留 `<文件名>.bak-<年月日-时分秒>`,仓库级文档改成指针文件并在下面保留合并前原文(`inherited from …`),所以别人 clone 下来照样能读,仓库也不会被两套规范同时管。插件自己不带合并器——没有正则并集,也没有写入路由——所以落盘的那份永远是模型读过原文之后写出来的。 - **只有一个兜底工具,没有技能**——侧栏不提供任何 worktree 写操作,所以模型可见面只剩一个 `git_worktree`:`init` 让 AI 按插件的方式建仓(用面板同一套框架/开关写规范文档;strict 文档还会装 `commit-msg` hook,裸 `git commit` 也会被拦,`--no-verify` 是逃生门),`create` 在 `$DSH_HOME/worktrees` 下建一个托管 worktree 并把它注册成工作区(下一个新会话开在那里就在 worktree 里干活),`list` 列出现有的,`remove` 清掉过期的。没有开关、没有第二个工具:规范合并完全靠注入提示 + 助手自己的文件工具,插件也不带任何技能。 - **智能体真的会读到**——宿主注册了一个 `systemPrompt` 段,在**每一次**模型调用前装配:它告诉智能体会话所在仓库的规范文档在哪,并把文档内容(最多 3000 字符)内联进去。整个工作区都没有任何仓库定义规范时不注入任何内容;文本按文件 mtime 缓存,因此不会每步重复读同一个未变文件。 - **就近生效,可以继承**——查找顺序是三档:会话目录所在仓库自己的文档 → 最近的祖先仓库(monorepo 容器)的文档 → 其下已定义规范的仓库清单。继承来的文档会标注 `inherited Git conventions` 并写明来源仓库,所以 `packages/api` 在没写自己的文档之前会遵循容器根那份规则。清单最多向下扫三层,跳过点目录与 `node_modules`。 ## HTTP 路由 由宿主半边注册在 `/git` 下,除 `/git/events` 是 SSE 事件流外,其余都返回 JSON(`{ok:true,value}` / `{ok:false,error}`)。 | 路由 | 用途 | | --- | --- | | `/git/repos` | 多级仓库发现(`{path, refresh?}`) | | `/git/status` | 分支、head、脏文件/未跟踪/冲突数量 | | `/git/branches` | 分支列表(标出当前分支) | | `/git/graph` | 提交列表与父提交、refs(`{path, limit?}`) | | `/git/switch` | 切换分支 | | `/git/create-branch` | 新建并切换 | | `/git/worktrees` | worktree 列表 | | `/git/worktree-add` | 创建 worktree | | `/git/worktree-remove` | 删除 worktree | | `/git/worktree/list` | 某仓库关联的 worktree 列表,只读(`{path}`;锚点须在已注册工作区内,返回 `git worktree list --porcelain` 的结果并保留 `bare`/`locked`/`prunable`/`detached`) | | `/git/worktree/status` | 在某一棵关联 checkout 里读分支、head 与脏状态(`{path, worktree}`;`worktree` 只与列表比对,绝不直接当 cwd) | | `/git/worktree/branches` | 某一棵关联 checkout 的分支列表(`{path, worktree}`) | | `/git/worktree/graph` | 在某一棵关联 checkout 里读提交列表(`{path, worktree, limit?}`) | | `/git/conventions` | 某仓库的规范文档状态 + 框架目录(`{path}`) | | `/git/convention` | 写入规范文档(`{path, framework?, force?}`) | | `/git/convention/templates` | 用户自己的规范模板:`list` / `starter` / `get` / `save` / `delete`(`{action, id?, template?}`;存储位置 `$DSH_HOME/git-graph/conventions-templates.json`,不写 profile 配置) | | `/git/init` | 为还没有仓库的目录初始化仓库(`{path, branch?}`) | | `/git/cleanup` | 分支清理提示:本地分支相对基线的已合并/未合并计数(`{path}`) | | `/git/health` | 仓库体检:分离头指针、无上游分支、stash、大未跟踪文件、`.git` 体积(`{path}`) | - 说明:`/git/cleanup` 与 `/git/health` 从 1.6.1 起只保留为只读 HTTP 接口,tab 里不再有入口;工具栏也去掉了「总览」与「全屏查看」。 | `/git/compliance` | 提交合规:最近 20 个提交对照文档规则的不合规清单与原因(只读接口;面板只列生效规则,不展示这些历史违规)(`{path}`) | | `/git/convention/preview` | 规范升级预览:写入前的新增/改写/移除小节计数与标题,只读不落盘(`{path}`, `framework`) | | `/git/workspace-convention` | 工作区总纲:`state` 读、`preview` 预览、`write` 写 `.dsh/GIT_CONVENTIONS.md`(`{path}`, `action`, `framework`, `force`) | | `/git/conventions/conflicts` | 文档冲突:工作区 / 本仓库 / 祖先仓库逐字段逐规则比对(`{path}`) | | `/git/events` | 变更通知(界面轮询) | 每个路由都会先 `realpath` 解析请求路径,只有在某个已注册工作区**内部**时才放行;嵌套仓库目录会被接受,工作区之外的路径返回 `workspace-unknown`。四条 `/git/worktree/*` 路由校验的是**请求里的锚点仓库**,checkout 本身可以在工作区之外:锚点过门禁后,`git worktree list --porcelain` 的输出就是唯一白名单,所有探针都在 git 打印的路径里跑。 ## 已知限制 - **图谱页不改动已有仓库的 Git 状态。** 它写规范文档,并且可以对「还不是仓库」的目录执行 `git init`——除此之外什么都不做。不做提交、不切分支、不新建或删除 worktree,那些仍在徽章的对话框里。关联 checkout 只是只读列出,跳转只负责打开会话或工作区,绝不新建 worktree。 - **工作区之外、且自身未注册的关联 checkout:能读状态,但页面不会替你注册。** 四条 `/git/worktree/*` 校验的是它所属的仓库,所以未注册的 checkout 也能给出分支、head 与脏状态;真要进去,点「在新会话中打开」或「作为工作区打开」时才注册。 - **不要和 `@linxin666/dsh-client-ui-git-graph` 同时安装。** 两者都占用 `/git` 这个路由前缀,重复注册会冲突,二选一。 - **超过 3 层的仓库不会被列出**;位于被跳过目录名(`node_modules`、`dist` 等)里的仓库同样不会列出,这是设计如此。 - **宿主半边是构建产物。** `lib/index.js` 是直接改的(新增部分在带标记的 `//#region` 块里);`lib/client.js` 由 `src/client/graph/SidebarTab.js` 拼接生成。细节见 [DEVELOPMENT.md](DEVELOPMENT.md)。 ## 开发 ```sh npm run build # 把 src/client/graph/SidebarTab.js 拼进 lib/client.js npm run check # 对两个 bundle 跑 node --check npm run validate # 校验每个 t("…") 键在两套语言字典里都存在 npm run preview # 不启动 DSH,直接渲染出 preview/*.html ``` `npm run preview` 会写出独立的 HTML(深色与浅色各一份),用示例仓库与提交展示这个页,改完布局可以直接在浏览器里看,不用装进 DSH。 ## 归属与许可 Apache-2.0——见 [LICENSE](LICENSE) 与 [NOTICE](NOTICE)。 本项目是 linxin666 的 [`@linxin666/dsh-client-ui-git-graph`](https://www.npmjs.com/package/@linxin666/dsh-client-ui-git-graph) 0.4.8(同为 Apache-2.0)的分叉:分支徽章、提交图对话框、worktree 管理器与宿主 git 服务来自上游。本分叉的改动:重命名包、把工作区校验从「路径完全相等」放宽到「位于工作区内」、新增 `/git/repos` 发现路由、新增 better-sidebar 图谱页(仓库树、SVG 画布、缩放、提交详情卡)及其语言键;自 1.15.0 起图谱页还能只读列出关联 checkout(4 条 `/git/worktree/*` 路由)并跳转过去。