AGENTS.md · git:20261020.e2c517a · 2026-10-20 · sha256 d6947975373d19d2

AGENTS.md git:20261020.e2c517aB

Immutable. This exact content never changes and is served at /api/v1/blob/d6947975373d19d2.

# AGENTS.md

给在本仓库里干活的代理(和人)看的说明。

## 常用命令

| 命令 | 作用 |
|---|---|
| `pnpm install` | 安装依赖 |
| `pnpm run build` | 构建 `lib/`(改了前端或主机代码后,测试实例要重启才看得到) |
| `pnpm run typecheck` | 类型检查 |
| `pnpm run lint` | 代码检查(biome) |
| `pnpm test` | 单元测试(vitest) |
| `pnpm run hero` | 重做 README 封面图(中英两张,见下文) |

## 发布与推送

```sh
# 1. 先构建再验证:打包守卫要读 lib/,没构建过它整块跳过(那就等于没查)
pnpm run build && pnpm run typecheck && pnpm run lint && pnpm test

# 2. CHANGELOG.md:把 Unreleased 那节落成 ## [<ver>] - <日期>,随本版一起提交;不更新不得发版

# 3. 版本自增(会同时提交并打 tag;只想改版本号就加 --no-git-tag-version)
pnpm version minor        # 按下面的判据选 patch / minor / major

# 4. 提交 + 打标签(上一步已经做了就跳过)
git commit -am "release: workflow-lite v<ver>"
git tag workflow-lite-v<ver>

# 5. 推 git + 发 npm
git push && git push --tags
npm publish                # publishConfig 已声明 access: public

# 6. GitHub Release(手动,没有 workflow;notes 用 CHANGELOG 本节内容)
gh release create workflow-lite-v<ver> --title "v<ver>" --notes-file <notes 文件>
```

发版相关的几条:

- **版本号按严格 SemVer 判,不看改动大小**:新增功能(Added)→ `minor`;修 bug、只改措辞与内部实现(Fixed / 纯重构)→ `patch`;不兼容变更(删或改工具 action 与参数、数据格式、RPC 目标,或让旧版读不了新写的文件)→ `major`。判据落在**公共 API**上:工具接口、工作流 JSON / state.yaml 的数据格式、RPC 目标、peer 区间。**只改客户端呈现(排版、动效、配色、文案)不算公共 API 变更**,按它实际做了什么归类——新增了能力就是 minor,不是 patch。
- 不兼容变更要**在 CHANGELOG 里明说**:哪一侧不兼容(新版读旧版 / 旧版读新版)、会丢什么、别的部分是否不受影响。
- `.npmrc` 把 `@xiaoso` 作用域指到 npmjs 官方源;镜像站发布不了。
- `publishConfig.access: public`:scoped 包不加这条,`npm publish` 默认按 restricted 发。
- 插件版本与 dsh 版本**不锁步**:peer 是一条 `>=0.2.0-0 <0.2.1-0 || >=0.2.1-0 <0.3.0-0` 的区间(`engines.dsh` 与 peer 同串),插件自己按 SemVer 走;只有换支持版本线时才动它。
- peer 对不上运行版本时宿主**静默跳过**该 bundle(只记进 `skippedBundles`):症状是工具突然消失、补丁层不生效,而不是启动失败。排查看 `<profile>/cordis.yml` 里有没有自己的行。
- `THIRD_PARTY_NOTICES.md` 由 `scripts/notices.mjs` 生成,别手改。改过依赖跑一次 `pnpm run build`,它变了就一起提交;`pnpm run notices:check` 与 `tests/package.spec.ts` 盯着它有没有过期。

## CHANGELOG 写法

CHANGELOG 每节会原样贴进 GitHub Release,写给用户看,不是开发记录。

- **分类**:小节沿用 Added / Changed / Fixed,需要时加 Other;Release 里对应写成「新增功能 / 体验优化 / 问题修复 / 其他变更」。Other 放不兼容变更、需要迁移的事、依赖升级和面向开发者的接口变化。
- **一条一句**:一个改动写成一句完整的话,句末带句号;要补一层意思就用分号接一个分句,不再往下分子条目,也不加粗标题。一般一两行就写完,写不完的说明是在讲实现。
- **写用户能感知的结果**:现在能做什么、看到什么、哪个问题没了。不写组件名、CSS、RPC、源码路径、动效的实现手法;界面上的名字、命令、设置项、环境变量这类用户会碰到的照写。
- **开头的词定基调**:新增功能用「新增…」「支持…」「…可以…」;体验优化用「优化…」「改善…」「…更…」;问题修复写成「修复〈什么情况下〉〈出了什么问题〉的问题」,或者直接说现在的正确表现。不写「以前……现在……」的前后对比。
- **限制与实验性写进同一句**:实验性的标「实验性」,已知限制用一个分句交代(如「目前只支持……」),不另起一段免责说明。
- **不兼容变更**:同一句里写清谁受影响、要做什么(如「用旧字段的工作流包需要重新导出」);按上文要求明说哪一侧不兼容、会丢什么。
- **合并与取舍**:同一处界面的几项小改合成一条;纯重构、测试、只改了内部措辞的不写;这一版里新加、又在这一版里修掉的问题不写进「问题修复」,那只是这一版功能本来的样子。
- 一节开头可以留一两句概述这一版的重点,和一句「升级要不要做事」。

例子(同一条改动):

- 差:工作流中心「运行实例」「工作流」页的右栏、编辑页的「版本」对话框,内容一多是整栏连标题一起滚。现在标题、存版本那一栏钉在上面,只有实例表、版本表在自己的框里滚
- 好:实例、版本较多时只有列表在框内滚动,标题和保存栏保持不动。


## DSH 版本

- 兼容范围写成一条三段版本号的区间(预览版也要能装),放在每个 `@deepseek-ai/dsh-*` 的 `peerDependencies` 里;DSH 检查 peer 时带 `includePrerelease`,所以不能用 `^`。
- 编译用的 `devDependencies`(`@deepseek-ai/dsh-*`、`@deepseek-ai/cordis`)写死精确版本,配合 `pnpm-lock.yaml` 防依赖漂移。
- README 两版各带一个同区间的 DSH 徽标和一句适配说明。
- 换支持的版本线时四处一起改:peer 区间、dev 精确版本、两版 README 的徽标与说明;`tests/package.spec.ts` 检查它们一致。

## 编译口径(DSH / 通用)

同一张图编译成两种口径:插件里执行用 **DSH 口径**,导出成 skill 用**通用口径**(`PlanOptions.skill` 给了就是通用口径)。

- 跟执行环境有关的话——执行方式、复用执行者、设定目标、怎么加载 skill、运行状态——只写在 `src/shared/dialect.ts`,每句两套:DSH 口径可以点名工具与插件动作(`subagent`、`spawn_teammate`、`create_goal`、`workflow_lite` 的 `state` / `resume`……);通用口径不点名任何工具,只说要做成什么。`compile.ts` 只写和环境无关的部分(图、流程、资源、交接),不要在里面写工具名。
- 运行状态:DSH 口径由插件建实例、模型经 `state` 动作改;通用口径由模型自己建、自己改工作区里的 `.workflow-lite/runs/{instance}/state.yaml`,计划里按图给出初始内容。
- 加一条工作流设置或改一句措辞时两套一起写。`tests/shared/dialect.spec.ts` 把所有设置打开、用通用口径编译,扫计划和每份任务描述里有没有 `DSH_ONLY_TERMS`;新出现的工具名、插件说法要补进这张表。

## 配色主题

- 亮色、暗色各四套(晴白、瑞士、雾蓝、林间 / 石板、墨黑、深海、暖夜),色值全在 `src/client/model/themes.ts`;默认晴白 + 石板,瑞士排第二。
- 「瑞士」除了颜色还带一套「形」:亮色档选了它、此刻又是亮色时,视图根写 `data-look="swiss"`(`themeLook()`),`shell.module.css` 在这个开关下把 `--wl-round`(所有圆角的乘数,各 CSS 里写 `calc(Npx * var(--wl-round, 1))`;圆点的 50% 不乘)归零、去掉阴影、换 Helvetica 系字体;分组小标题的大写加粗写在各模块里的 `:global([data-look="swiss"]) .x`。新写圆角用 `calc(Npx * var(--wl-round, 1))`,别写死 px,否则瑞士下会漏圆角。瑞士下的其余做法同样写在各模块末尾的 `:global([data-look="swiss"]) .x` 里:成组列表只留上下线、输入框只留底线、分段反白、分组标题压黑线;淡底(`--wl-*-soft`)在 `shell.module.css` 的瑞士块里调淡。新加成组的盒子或卡片时顺手补一条。
- 视图根节点把选中的两套写成 `--wl-l-*`、`--wl-d-*` 两组内联变量,`shell.module.css` 用 `light-dark()` 跟着宿主的 `color-scheme` 拼成 `--wl-*`;CSS 里不再写死色值。
- 选择记在浏览器(`workflow-lite.theme`),在工作流中心「设置」页的「外观」里换,点了就生效,不走插件设置的保存。
- 一套主题是一整套颜色:线色(光带、图例、线上牌子跟着它)、强调色、步骤图标色、状态色一起换;非默认的几套照着成熟配色方案配(雾蓝 Nord、林间 Everforest、墨黑 Geist、深海 Tokyo Night、暖夜 Gruvbox),主干线用强调色。
- 改色或加主题要过 `tests/client/themes.spec.ts`:文字、语义色、线色、步骤色在面板上过 4.5:1,线色在画布上过 3:1,七种线两两 OKLab ΔE ≥ 8(默认两套 ≥ 13)。

## 浏览器验收(CDP)

`tests/cdp-*.mjs` 在真浏览器里走一遍功能,结论以磁盘 / RPC 为准。需要:

1. 一个装了本插件的 DSH 网页实例(默认 `http://127.0.0.1:3190`),从它打印的 URL 里取 `?token=`;
2. 一个带 DevTools 端口的 Chrome:`node tests/lib/cdp-chrome-launch.mjs 9222`。

```bash
DSH_WEB_TOKEN=<token> DSH_BASE=http://127.0.0.1:3190 node --experimental-strip-types tests/cdp-ui.mjs
```

`DSH_BASE` 是基址(`tests/lib/web-session.mjs` 每次现读环境变量,不固化成模块常量——截图管线是 import 之后才起实例、才知道端口);`DSH_AUTH_COOKIE` 可以跳过 token 换 cookie 那一步。

| 脚本 | 覆盖 |
|---|---|
| `cdp-ui.mjs` | 画布编辑、资源、工作流切换 / 改名 / 删除 |
| `cdp-runs.mjs` | 实例与运行状态 |
| `cdp-inputs.mjs` | 执行前提问 |
| `cdp-versions.mjs` | 版本、工作流中心「工作流」页 |
| `cdp-config.mjs` | 工作流中心「设置」页(含外观:配色主题) |
| `cdp-pack.mjs` | 工作流中心「工作流」页的导出、导入(另存为新名字、并入已有的)、导出成 Skill(预览整个包、下载 zip、装到本机;装的那份用一次性名字,验完删掉) |

## 插件图标与词标

- 两张 448 见方的图分工不同,**别互相替换**(`tests/package.spec.ts` 钉着这个分工):
  - `assets/icon.webp` = **插件图标**,`package.json` 顶层 `icon` 指它。**先裁到主体再缩**:脸占画面四成、头加头发占八成,标志物(黑猫发卡)完整,下缘留一点衣领和花束当锚点;裁框 950 见方、左上角 `(115, 0)`。整幅缩进去脸只剩三成,缩略图上认不出人——取景参照隔壁 tool-plus 的头像。
  - `assets/icon-full.webp` = **README 头图**,两版开头的 `<img width="140">` 指它。整幅不裁,保留完整构图。
- 两张都:LANCZOS 缩到 448、圆角半径 104、8 倍超采样(`rounded_rectangle` 画在 448×8 上再缩)、WebP 无损(`lossless=True, quality=100, method=6`),都在 `files` 里。
- 宿主只收**清单目录下的相对路径**的图标,格式 SVG / PNG / JPEG / WebP,上限 256 KiB;越界界面静默回退默认图。`tests/package.spec.ts` 的「插件图标」盯着:路径合法且在包内、不超过 256 KiB、本体与扩展名对得上、`files` 收得下。
- `assets/icon-source.png`(最初的源图)、`assets/icon-art.png`(重绘成赛璐璐平涂,1254 见方)只留在仓库里做参照,不随包发布。
- `assets/wordmark.png`:「dsh workflow lite」艺术字,Jua 400 Regular 逐词纯色,1147×130——蓝 `#3e7ce4`(项目色)、绿 `#60ba7e`(人物眼睛)、金 `#cd9e70`(金发压深)。README 里按 `width="360"` 摆,图标 140。
- 词标重做:Pillow 把三个词按 4 倍尺寸画到同一基线上,整幅 LANCZOS 缩到 1/4,裁到墨迹外留 6% 边距;字体不进仓库,用时从 Google Fonts 取 `ofl/jua/Jua-Regular.ttf`。
- README 两版开头都是「图标 + 词标」居中的一段 HTML,没有 H1;改文案两版同步。

## README 版本线

README 里、封面图下面的「版本线」是一张图(`assets/versions.{zh,en}.png`),挑重要变化,一条一站,不逐版本列。

- 内容在 `tests/readme/versions.json`:每站写落在哪个版本(要显示成别的就写 `label`,改过几个版本的写 `to`)、标题、两行短话(中英各一份),标题旁要挂「实验性」这类小签写 `tag`(英文要短,`Beta`,长了会折到下一行);还没发的写 `"next"`,还没定版本的规划写 `"planned"`(排在最后,显示成 `later`,不核对 CHANGELOG)。排版在 `tests/readme/versions.html`。
- 出图:`pnpm run timeline`(要一个带 DevTools 端口的 Chrome:`node tests/lib/cdp-chrome-launch.mjs 9222`,不用起 dsh)。脚本对照 `CHANGELOG.md`,json 里的版本不存在、或有新的小版本没写进 json 就报错。
- 发版时:把 `"next"` 改成这个版本号(有新的重要变化就加一站),重跑,新图一起提交。
- 6 站时一栏约 150px:标题 ≤ 7 个汉字 / 15 个英文字符(带小签的再短些),短话每行 ≤ 9 个汉字 / 22 个英文字符;站数 6 站左右,多了就把早期几个版本合成一站(写 `to`);出图后看一眼两张图。

## README 封面图

README 有中英两版:`README.md`(中文,默认)与 `README.en.md`(英文),开头互相链接。各用一张封面图:`assets/hero.zh.png`、`assets/hero.en.png`(2 倍像素),由同一页 HTML 按语言排版后截图得到,图里的界面都是真实截图(英文版用英文界面和英文示例)。

| 文件 | 作用 |
|---|---|
| `tests/support/shots-env.mjs` | 封面管线的**自备环境**:起一个假模型(`tests/support/mock-llm.mjs`)、生成截图专用 profile `workflow-lite-shots`、在**随机端口**起实例、换好 cookie,再备出那条固定标题的夹具会话;`startShotsEnv()` 一把起完,拆的时候等实例真的关掉 |
| `tests/readme-shots.mjs` | 自己把上面那套环境起起来,按语言切换浏览器语言,建示例工作流和一个实例,按封面里的大小拍素材到 `tests/runs/hero/<lang>/`(`canvas.png` 编辑页、`run-graph.png` 点亮的图、`run-position.png` / `run-timeline.png` 右栏两小块、`run-dashboard.png` 查看框里打开的 HTML 看板、`plan.txt` 编译出的计划),拍完自动清理并拆掉实例;`node … tests/readme-shots.mjs en` 只拍一种 |
| `tests/readme/hero.html` | 封面排版:标题、三段流程(设计流程 → 生成计划提示词 → 跟踪执行进度)、软工作流说明、功能标签;`?lang=zh` / `?lang=en` 切换,文案都在页尾的 `TEXT` 里 |
| `tests/readme-hero.mjs` | 在 Chrome 里另开一个标签页按 1040 宽渲染 `hero.html`,截成 `assets/hero.<lang>.png`;`node tests/readme-hero.mjs en` 只出一种 |

重做步骤:

```bash
# 界面有变化:重拍素材并出图。前置是一台装了 dsh 的机器 + 一个带 DevTools 端口的 Chrome
# (`node tests/lib/cdp-chrome-launch.mjs 9222`),不用先起 dsh web、不用给 token
pnpm run hero

# 只改了 hero.html 的文案或排版:直接出图(素材已在 tests/runs/hero/)
pnpm run hero:render
```

封面管线的前提:

- **自己一个家**:整跑用 `tests/runs/shots-home` 当 `DSH_HOME`,profile、会话库、工作区库、投影缓存全在它底下,跑前清空、跑完删掉。这几样在 DSH 里按 `DSH_HOME` 算、**不按 profile 隔离**——共用用户自己的家的话,那条"封面夹具"会永久留在他侧栏里。
- **不依赖真实环境数据**:封面拍的是脚本自己准备的那份数据(假模型 + 生成 profile + 随机端口 + 夹具会话);不发真实模型请求,也不受"当时 3190 上跑着什么"影响。
- **夹具是"认领"出来的**:会话按固定 id 复用,而会话跟着它的 cwd 走(换个目录再建会被 host 回 `session/conflict`,host 又没有删会话的接口)。所以 `prepareShotsFixture` 找到就用它自己的 cwd 当工作区,并把 `adoptedWorkspace` 交出来——**收尾时不许删认领来的目录**。
- **进会话靠搜索,不靠分组展开**:工作区分组的展开状态记在浏览器里(`dsh.workspace.view.v5` 的 `groupExpansion`),按 origin 存——换个端口就是新分组状态,于是所有分组都是折叠的,会话行压根不在 DOM 里。`enterSession` 因此走会话搜索框。
- **关实例要等它真的关掉**:随手 `spawn('taskkill')` 就走人,调用方一退出 taskkill 没机会跑,实例留在后台占着会话写句柄,下一跑撞 `session/writer-held`。所以 `stop()` 等 `close`,等不到**就抛**(不许报成功)。
- **"没发真实模型请求"有判据**:夹具发 prompt 的那一轮,必须等到**假模型真的收到请求**才算数。别拿 `blank` 变 false 当完成信号——它只说明消息记下了,模型调用还在后面,拿它当信号会抢跑报假红。

改封面时注意:

- 计划节选是从 `tests/runs/hero/zh/plan.txt` 里摘的原文(计划只有中文,英文版是对应的译文,图里标了 translated),编译器的措辞变了要同步改;
- 两种语言的文案改一处就要改另一处;
- 文案用平实的陈述句,不用「不是……而是……」这类对比句式,也不用过于口语的说法;
- 清晰:素材拍的时候就按封面里的大小换算像素比,`hero.html` 一律按「原图宽 ÷ 2」摆,一个像素对一个像素,不在封面里缩放(缩放就糊);右栏这类窄面板单独裁出来,别整块界面缩小塞进封面;
- 字要大:封面 1040 宽,GitHub 上大约显示成 880 宽;示例工作流只留四步,图越窄字越大。改页宽、页边要同步改 `readme-shots.mjs` 的 `GRAPH_WIDTH`;
- 层次:工作流的图是主角;右栏小块和看板按图相对原大的比例拍(不小于 `MIN_SCALE` 0.85),字号和图上的字接近,别比图还抢眼;
- 不留空:第三段下半截左列两小块叠起来,右边看板的高度按左列算好再拍,两边齐平;
- 出图后打开 `assets/hero.zh.png`、`assets/hero.en.png` 看一眼,确认没有错位、截断,也没有拍进本机路径或私人工作区名。