dsh-openspec-pipeline
OpenSpec 变更流水线。一张图 = openspec/pipeline.json,每个节点是一个 change;
按依赖顺序(或并行)在选定工作区里新建一个顶层普通对话来执行。
对话里收到的第一句话就是 执行 <change> Change —— 没写附加提示词时,这就是全部。
节点可以只设一套「默认组合」(对话模式 / 运营商 / 模型 / 强度 / 附加提示词),新加的节点连
提示词一起带上,不用逐格点。
安装
桌面端(DeepSeek Harness 桌面版):侧栏「插件」→「添加插件」,粘贴仓库地址:
https://github.com/heibaoxia/dsh-openspec-pipeline
Web / CLI:
dsh plugin --profile web add github:heibaoxia/dsh-openspec-pipeline
装完重启 DSH(桌面端重启应用;CLI 重跑 dsh web),再刷新页面。面板顶栏出现 v0.11.0 就算装上了。
前置条件(缺了不会崩,只是少功能):
- 目标仓库里要有
openspec/目录(openspec init生成的); openspec命令要在 PATH 上。插件靠它读进度(list/status)、做归档(validate/archive)。 没有它:任务进度退回自己数tasks.md,规划进度不显示,「归档」按钮会失败并写明原因。
装上之后从这里开始:面板顶部选工作区 → 列表里单击 change 加进图 → 拖线连到「开工」→ 点「开始」。
这一版不做什么、做什么
流水线仍然不验收、不归档。 0.8.0 起节点上的「验收方式」(机器验收 / 真人验收 /
Playwright 代验)和 ⋯ 里的「碰到真人验收」都删了,settle(验收并归档)也一并删了。
要验什么、怎么归档,写进节点的附加提示词,由那个对话自己做。不写就只发一句
执行 <change> Change。
0.8.2 起加了一道「归档门禁」(只查目录,不做质量判断):
- 这一轮正常收尾后,Host 看
openspec/changes/<x>/还在不在; - 进了
archive/→ 记「完成」,放行下一格; - 还在
changes/→ 记「等你」,写清“没归档”,后面的格子都不动。
0.9.2 起这道门禁会自己等:挂上「等你」之后 runner 不退出,每秒看一眼 archive/。
归档一出现(你手动 openspec archive,或在那个对话里补上),一秒内自动补记「完成」、
接着开下一格,不用再点「开始」。想提前收工就点「停止」。
也就是说:判断节点「完成」的标准是两条——这一轮正常收尾,而且 change 真的进了
archive/。只查目录,不要求 tasks 全勾;openspec validate 只在下面那个按钮上跑,
不参与「这一格算不算完成」的判定。
0.10.0 / 0.11.0 起多了三件事,都不改「流水线不做判断」这条底线:
「这一格做到哪」改读
openspec list --json,不再自己数tasks.md。它一项一项给completedTasks/totalTasks/status(complete/in-progress/no-tasks), 跟 OpenSpec 自己的口径一致。CLI 不在 PATH、或者退出码不是 0 时会退回自己数勾, 面板顶上写明原因——宁可少一个字段,也不能让面板白屏。卡住时有一个「归档」按钮(对话已经停了、change 还在
changes/才出现): 先openspec validate <change> --strict,把问题一条一条摊出来(可以复制给那个对话去改); 人点「仍然归档」才真的跑openspec archive <change> -y。归完现有那个每秒看目录的 watcher 会自己接着开下一格,不用再点「开始」。这不是 0.8 删掉的
settle。settle是「AI 判断验收 + 归档」,带判断; 这个按钮只跑两条openspec命令,零判断。验证没过时它停下来问人,不会自己放行。为什么必须在归档前验:归档之后 change 就不在
changes/里了,openspec validate用老名字、用带日期的新名字都只会回Unknown item(1.13 实测)。 归档前那一刻是唯一能验的时机。「propose 写完没」读
openspec status --all --json(0.11.0)。这一条治的是一个真问题: 只有proposal.md的 change,和「四件套只差tasks.md」的 change,在openspec list里 都是no-tasks、都是 0/0,面板分不出来。可这两者差得远——前者开跑等于让对话凭空开工, 后者差一步就能 apply。status一次给全部未归档 change:artifacts(proposal / specs / design / tasks 各自的done/ready/blocked)、isPlanningComplete,还有nextSteps(下一步该跑哪条命令, 原话)。面板上就是黄色的规划 2/4,鼠标停上去看「还差 specs、design」和「下一步:…」。这条也只显示、不拦:规划没写完的格子照样能进图、照样能跑——你可能就是想让它先把 propose 补完。要不要「规划没写完就不许开跑」,是一行判断的事,等你说了再加。
⚠️ 提示词里最好还是写归档那一步。不写的话,对话干完活不会自己归档,这一格会停在 「等你」——那时候点一下「归档」按钮,或者去那个对话里补一句,都行。
--strict 拦得住什么、拦不住什么(v1.13.2 实测,别指望它是万能的):
| 情况 | 默认档 | --strict |
|---|---|---|
| requirement 正文里没有 SHALL / MUST | 过 | 拦(WARNING 也算错) |
| scenario 只有标题、没有步骤 | 拦(ERROR) | 拦 |
| 一个 scenario 都没有 | 拦(ERROR) | 拦 |
| scenario 只有 WHEN,没有 THEN | 过 | 也过 ← 漏网 |
| scenario 只有 THEN,没有 WHEN | 过 | 也过 ← 漏网 |
也就是说它管的是「需求没写清必须/应当」「场景是空的」「根本没有场景」这三类, 管不了单边场景。所以按钮给的是报告 + 人决定,不是自动放行。
其余边界:
- 对话没正常收尾(abort / error)→ 标「失败」,下游不开。
- change 已经进了
archive/的格子,开跑时直接补记成「完成」,不会再拉对话。 - 归档提醒是另一回事:0.8.1 起 Host 会盯着
changes/和changes/archive/, 哪个 change 被移进 archive 就推一条消息给面板(见「怎么知道它完成 / 归档了」)。
启动纪律(重要)
这个插件同时有 Host 半身(lib/index.js)和 Web 客户端半身(lib/client.js)。
两边的 apply() 都不允许把异常抛出去:
- 客户端半身抛异常 → DSH 的 web boot 判整页失败:
web boot: N entry did not activate/dsh-openspec-pipeline: failed, 桌面端直接起不来,只能重置插件。 - Host 半身抛异常或停在 pending(声明的服务一直不出现)→
assertEntriesActivated同样致命。
所以代码里的约束是:
lib/index.js的apply()外面永远套一层 try/catch,里面失败只写日志、返回空清理函数; 工具注册、路由注册各自单独 try/catch。inject只声明必然存在的服务(tools、webServer)。sessionController、agents、workspaceRegistry都用到时才ctx.get(),缺了只让「开跑」失败,不让整个 Host 卡住。lib/client.js每条slots.inject/slots.register单独兜住; 构造插件失败就交空壳;可选服务(betterSidebar)用有限次轮询,拿不到就放弃, 不用ctx.inject去等(那会造一个等不到服务的子 fiber)。- 新增任何注册动作,都套进同样的
slot()/try结构,别破坏这条纪律。
日志
<DSH_PROFILE_DIR>/openspec-pipeline.log
Windows 桌面端默认是:
C:\Users\<你>\.dsh\profiles\desktop\openspec-pipeline.log
里面有 Host 的 apply 进度、工具/路由注册结果、以及客户端半身上报的异常堆栈
(客户端通过 POST /openspec-pipeline/api/client-log 上报,永远回 200)。
桌面端起不来时的恢复
- 先看上面那个日志文件,确认是哪个半身、什么错。
- 编辑
~/.dsh/profiles/desktop/package.json,把dsh.profile.bundles里的dsh-openspec-pipeline删掉(dependencies里那行留着不影响),重启桌面端。 - 插件源码在
F:\project\dsh\plugins\dsh-openspec-pipeline,删 bundle 不会丢代码; 修好后把 bundle 加回来即可。
面板
页面只有三块:顶栏按钮、正方形画布、未归档 change 列表。
- 顶栏:工作区下拉 / 刷新 / 保存 / 开始 / 停止 + 版本号。
⋯里是并行上限、跑到哪停、默认组合、工作区路径(仓库还没登记成工作区时用它直接切)。 - 画布:滚轮缩放(以指针为锚点),拖空白处平移,右下角显示缩放比例、点一下回到 100%。
- 列表:列出所有未归档的 change,带「还没开工 0/19」「做到 3/9」「只差归档」「已在他处进行」「已在图里」这类标记。
- 单击一行 → 加一个孤立节点进图(不自动连线),并立即落盘。
- 右键已在图里的行 → 从图里删掉。
- 节点:拖拽挪位置;从右边蓝点拉线到另一个节点 = 后者等前者;线中间的 × 断开。 双击节点打开设置:设置组合(含「用默认」「设为默认」)、对话模式(预置)、运营商、模型、思考强度、附加提示词。
- 右键节点:显示这个节点为什么标红,并提供「从图里删掉这一格」。
- 待处理提示:有节点「失败」或「等你」时,画布上方会逐条写出
N2 等你 · change 名 · 做到 3/9 · 原因,后面跟一个**「归档」按钮**(见下面「卡住的时候」)。 - 手动排序提示:左栏是「手动排序」时,面板顶部会多一行提醒——新对话会排到分组末尾,切成「最近更新」才看得见。
头部版本号(当前 v0.11.0)可以用来确认页面加载的是新代码;还是旧版本就硬刷新/重启。
提示词:唯一能定制「这一格干什么」的地方
节点设置最下面那个框就是附加提示词。发出去的话按这个规矩拼:
| 节点上的提示词 | 对话收到的第一句话 |
|---|---|
| 空 | 执行 add-login Change |
只做任务 1 到 3;做完跑 pnpm test;全过了再按 openspec-archive-change 归档。 |
执行 add-login Change + 空行 + 那段话 |
所以验收口径、要不要归档、要不要跑 Playwright,全都写在这里;插件只负责把话送到,
然后等这一轮结束。提示词跟着 openspec/pipeline.json 走,不进日志。
同一段话要在很多格重复时,把它连同四样设置存成一个组合(0.9.0 起组合带提示词), 以后选组合就一并换上;细节见「设置组合」。
工作区是插件自己的设置(不挑左侧对话)
顶栏第一个下拉就是工作区,选项来自宿主的 workspaceRegistry.list(),只列真的含 openspec/ 的。
选中后写进:
<DSH_PROFILE_DIR>/openspec-pipeline-settings.json
之后不管你从哪个对话打开面板,都读这一份,不再按当前对话的目录猜。这样同一个面板在任何
会话里打开,读写的都是同一张 openspec/pipeline.json、同一批 change。
解析顺序只有这三步,一步都不看当前对话:
- 插件设置里选过的仓库;
- 插件上次用过的仓库(
openspec-pipeline-root.json); - 登记过的 OpenSpec 工作区,按路径排序取第一个。
第 2/3 步一旦算出答案,立刻写进插件设置(settings.root),从此就是第 1 步。
所以第一次用之后,所有对话看到的是同一张图;想换仓库用顶栏下拉或 ⋯ 里的路径。
旧版第一步读的是「当前对话所在的目录」,所以在多个 OpenSpec 仓库(agent-forge / coatch / knowledge-skill / personal-agent …)之间切会话时,面板会跟着换成另一张图, 记忆也被来回覆盖。0.5.0 起这条路径已经删掉。
- 选中的仓库还没登记成工作区时,插件会自动登记一次(否则新建的会话在侧栏里看不见)。
- 只声明
openspec/的项目才能选;选到一半的目录会被拒绝并提示。 - 第一次用、还没选过时,才回退到「上次用过的 → 任一登记过的 OpenSpec 工作区」。
每个节点是一个顶层普通对话
点「开始」时,插件走和 GUI「新建对话」同一条路:
sessionController.create({ workspaceId, agentPreset }) // 建普通会话并挂到工作区
sessionController.selectModel({ sessionId, provider, model, reasoningEffort })
sessionController.prompt({ sessionId, content, requestId }) // 投第一句话
// 等这一轮的 turn/end 落进会话日志;到了再查 archive/:进了才算完成、放下游
所以:
- 新对话出现在插件工作区的分组里,点进去能看它实时干活,也能自己接着聊;
- 左栏能不能看到它,取决于左栏的排序方式:
- 「最近更新」:新对话会排到分组顶部,正在跑的会话会把可见条数抬到它(分组得先展开);
- 「手动排序」:新对话会被追加到分组末尾,而分组默认只画前 5 条(自动抬升上限 50 条), 位置靠后就会看不到。切成「最近更新」,或在分组里把它置顶,就能看见;
- 分组本身是折叠的也看不到:点一下分组标题展开。插件没有展开分组的公开接口, 面板只能提示,不能替你点;
- 面板顶部会多出一行「
N3 · 跑着(change 名)打开这个对话」。它按 sessionId 直接打开, 不受左栏排序影响;左侧看不到时就从这里进; - 面板在流水线运行期间每 15 秒让客户端重拉一次会话列表;点「开始」之后还会补读三次 (0.8s / 2.5s / 6s)。这只保证客户端拿到会话摘要,不改变左栏的排序和可见条数;
- 每个节点一个新会话,互不共享上下文;
- 节点选模型时,DSH 本来会把这个模型顺手写成全局默认模型;插件在同一个设置队列里 紧接着写回原值,所以跑完流水线不会改你新开对话的默认模型;
- 点「停止」只取消当前这一轮(
sessionController.cancel),不会把已经开起来的对话关掉。
之前的子会话方案(subagents.startActivation)已经去掉,不会再拉那种只能挂在父对话下面的
隐形子会话。
开始就是批准
没有「批准日期」这一项。点「开始」= 你当场点头,然后直接开跑。想改主意就点「停止」。
0.9.1 起开跑不再给全图盖时间戳:approved 只在哪一格真正开工时写哪一格,所以刚点「开始」
时 pipeline.json 里只有正在跑的那几格有时间,后面的格子还是空的。这张图也是一轮一轮算
的——每一轮从当前图算出「谁现在该跑」,开完就等它收尾,再算下一轮;没有在开跑时把整条链
排进队列。所以跑一半把下游断开再保存,它就不会被拉起来。
跑一半改图 / 跑完这一格就停
- 运行中也能保存:连线、位置、设置、增删只作用在还没开始的格子;正在跑和已经跑过的 格子保留服务器状态,不会被面板里的草稿覆盖。 所以 N2 跑着的时候把 N3 从线上摘掉、点「保存」,N2 收尾后就真的不会拉 N3。
⋯→「跑完当前这一格就停」:勾上并保存后,当前这一轮跑完、手上没有别的活,runner 就自己收工,后面的节点一个都不碰。运行中勾也一样生效(保存一次即可)。- 点「停止」仍然是取消当前这一轮;它和上面两个开关是两件不同的事。
节点失败 / 等你,怎么继续
| 情况 | 动作 |
|---|---|
| 对话跑偏 / 中途断了,需要接着做 | 双击节点确认设置 → 点顶部「开始」。会把 failed / needs_human 放回待跑,开一个新对话重跑。 |
| 这一格不做了 | 右键节点 →「从图里删掉这一格」。 |
| 显示「等你」:没归档 | 对话收尾了,但 changes/<x>/ 还在。往下有两个办法:点上面那行的**「归档」按钮**(见下节),或者去那个对话里补一句 openspec archive <x> -y。归完一秒内自己往下走,不用点「开始」。 |
| 显示「等你」:他处进行 | change 最近 15 分钟内被改过、而且做了一半,像是别的对话在同时做。流水线不开工,确认那边收工后再点「开始」。 |
卡住的时候:点一下归档
对话跑完了、也停住了,但 change 还在 changes/ 里——这一格就会挂在「等你」,后面的格子全不动。
面板会在画布上方列出这一格,后面跟一个**「归档」按钮**。点它会发生两件事,都是代码在跑:
openspec validate <change> --strict- 过了 → 继续第 2 步;
- 没过 → 不归档。把问题一条一条摆出来(
[WARNING] spec.md:...这种), 旁边有「复制问题」——粘给那个对话让它去改,改完再点一次。问题下面是「仍然归档」和「关闭」。
openspec archive <change> -y- 成功 → 这一格记「完成」,现有那个每秒看目录的 watcher 自己开下一格;
- 失败 → 把
openspec archive的原话贴出来(多半是解析不出来,或者任务还没写完)。
「仍然归档」只在验证没过的时候出现:它跳过第 1 步的判断直接归,节点说明里会写清 「validate 没过,人选择了仍然归档」——留个凭据,以后翻得到。
什么时候按钮不出现:这一格还在跑(先停)、change 已经归档了、或者 change 已经找不到。
「完成」是终态:这一格不会自己重跑。要重跑就先把节点状态放回待跑——目前靠「开始」只回收
failed / needs_human;已经「完成」的格子只能删掉重加(或者改 pipeline.json)。
模型从哪来
模型下拉不是手填的,直接从宿主 llm 服务读:
llm.listProviders()→ 运营商(id+ 显示名)llm.listModels(provider)→ 这个运营商下的模型(同一个id在不同运营商里各算一个,不会混)llm.resolveModelInfo(provider, model)→ 这个模型支持哪些思考强度;选中模型后强度下拉只列它支持的
目录在宿主里缓存 60 秒;点「刷新」会带着它一起重读。空结果不缓存:
宿主刚启动、llm 适配器还没注册完时列表会是空的,下次请求会自动重读;面板打开节点设置
发现运营商为空时也会补读(最多两次)。某个运营商读不出来时只留空列表,不会拖垮整张表
(原因写进 openspec-pipeline.log)。
设置组合:一套设置反复用
双击节点打开设置,最上面有「设置组合」,记五项:对话模式、运营商、模型、思考强度、附加提示词。
- 沿用上一个节点:从它的前置节点(
deps只有一个时)复制四样运行设置,不带提示词 —— 上一格的口径是给上一个 change 写的,搬过来通常是错的。 - 用默认:把默认组合的五项直接套到这一格。
- 设为默认 / 取消默认:把当前选中的组合设成默认组合(下拉里带 ★)。
- 套用到下游全部:沿依赖线往下的所有节点一次刷成同一套配方(含提示词)。
- 套用到所有节点:整张图(「开工」除外)一次刷成同一套配方(含提示词)。
- 存为组合:给当前这五项起个名字存起来,下次在「设置组合」下拉里直接选;组合存在
openspec/pipeline.json的profiles里,跟着仓库走。选中组合会套到当前节点。 第一次存组合时会顺手设为默认。
提示词在组合里的规矩:
- 选组合 / 用默认 / 套用到下游 / 套用到全部,提示词一起换;组合里没写提示词,这一格的 提示词就被清空(组合说了算,不搞半套)。
- 新加的节点(列表单击、
add-all)连提示词一起播种默认组合,所以「每格都要归档」这类 通用尾巴存进默认组合就够了。 - 开跑时的兜底不碰提示词:节点四样全留空才会用默认组合,而那只取四样运行设置; 节点上手写的提示词不会被默认组合悄悄盖掉。
默认组合
默认组合存在 pipeline.json 的 defaultProfileId(指向 profiles 里的一项),跟着仓库走:
- 新加的节点(列表单击、
add-all)会自动带上默认的五项(含提示词),面板里看得见; - 四样全留空的老节点,开跑时兜底用默认组合的四样;只要设了其中一样,就按节点自己的来, 不再和默认混。提示词始终取节点自己的(理由见上);
- 组合被删掉时默认一起清空,不会留一个指空的 id。
面板打开节点设置时最下面会写当前默认是哪个组合;没设时提示怎么设。
⋯ 里也有一个「默认组合」下拉,选完点「保存」同样生效。
套用改的是整套配方(五项);连线、位置不会被覆盖。组合的增删立即落盘,套用到节点 仍按手改处理(点「保存」生效)。
节点状态与重跑
- 画布上的状态以宿主落盘的状态为准,不是面板里的草稿副本。
- 状态只有五种:
待跑/跑着/完成/等你/失败。 (0.7 文件里遗留的「验收中 / 归档中」读回来按「失败」处理,点「开始」就能回收。) - 点「开始」会把上次
failed/needs_human的格子放回待跑,所以失败一次不会永久卡死。 - 面板不轮询仓库;但 Host 有推送(下一节),所以跑完/归档后面板会自己变。
怎么知道它完成 / 归档了(0.8.2:归档了才算完成,面板还会收到消息)
Host 到面板有一条 SSE 长连接:GET /openspec-pipeline/api/events。
事件只说明「变了」,面板收到就静默重读一次状态,自己不轮询磁盘。
开销按「有没有人听」算:
- 面板没打开:没有连接,也没有 watcher —— 零开销;
- 面板打开:一条空闲 socket(25 秒一次心跳,几十字节)+ 两个
fs.watch句柄; - 有变化时:才读一次
pipeline.json和那两个目录。
| 什么时候推 | 面板上看到什么 |
|---|---|
| runner 写状态(跑着 / 完成 / 失败 / 等你) | 画布上那一格自己变成新状态,不用点刷新 |
openspec/changes/<x>/ 被移进 changes/archive/ |
提示行出现「已归档:<x>(archive/…)」,节点标红「已归档」,列表里它消失 |
归档是怎么判断的:只盯 openspec/changes/ 和 openspec/changes/archive/ 两个目录
(fs.watch,persistent: false,不递归、不扫全仓库)。目录从前者消失、在后者的名字以
-<change> 结尾,就算「已归档」。这条 watcher 不跑 openspec validate —— 它只报告目录事实
(validate 是「归档」按钮那一步的事,见「卡住的时候:点一下归档」)。
所以:
- 对话在提示词里跑了
openspec archive,归档一完成,面板几秒内自己变; - 归档失败(目录还在
changes/):不会误报“已归档”,节点会停在「等你」并写明没归档;runner 继续等,归档一到就自动继续(0.9.2); - 面板没开着的时候连接不存在,事件不补发;下次打开面板会直接读到最新状态 (已归档的格子显示「已归档」)。
什么时候会自己读一次
面板自己不轮询仓库。会读盘的时刻:
- 面板第一次打开;
- 切换到另一个对话(座位变了)——右侧标签一直开着也一样会重读;
- 点「刷新」/「刷新进度」;
- 点「开始」之后补读三次(0.8s / 2.5s / 6s);
- 收到 Host 推送(上表那两种情况,0.8.1 起)。
画布是草稿,别被「刷新没反应」骗了
画布画的是你面板里的草稿,不是服务器的副本。只要这个面板里有过没保存的改动 (拖过节点、连过线、改过设置、加过格子都算),点「刷新」就只更新「服务器事实」 (状态、说明、进度、sessionId),不会拿服务器的图覆盖你的草稿——否则刚拖的位置、 刚删的线会自己弹回来。
这时如果服务器上的图真的变了(多了/少了格子、连线不同),面板会在刷新后直接说:
服务器上这张图变了(多了/少了格子或连线),你这边有没保存的改动,所以画布还是你的草稿。 [以服务器为准]
点「以服务器为准」= 丢弃草稿、按服务器重画;想留着改动就先点「保存」。 判断「图变没变」只看节点、change、连线,状态变化不算。
四种状态:这一格到底走到哪
change 的事实和节点的状态是两回事,面板把两样都摆出来:
| change 的事实 | 判据(从哪读) | 节点上写什么 |
|---|---|---|
| propose 还没写完 | openspec status 的 isPlanningComplete 是 false(四件套差一件以上) |
规划 1/4、规划 3/4(黄色) |
| 还没开始做 | status 是 no-tasks(还没写任务清单),或者 in-progress 且勾了 0 个 |
0/9 或 没任务清单 |
| 做到一半 | in-progress 且勾了 > 0 个 |
3/9 |
| 做完了,只差归档 | status 是 complete,目录还在 changes/ |
只差归档 |
| 做完并已归档 | 不在 list 里,且 archive/ 里有 |
已归档 |
前三行读 openspec list --json:completedTasks / totalTasks / status
(complete / in-progress / no-tasks)。第四行 list 看不到——它只列未归档的,
所以靠目录判断。
注意第一行和第二行在 list 里长得一模一样(都是 no-tasks 0/0)。分出它们的是
openspec status --all --json 的 isPlanningComplete 和 artifacts。这也是为什么两个命令都要读。
节点那一行同时还有流水线的状态:待跑 / 跑着 / 完成 / 等你 / 失败。
两样叠起来才是完整的一句话,比如:
N2 · 跑着 · 3/9—— 正在干,还有 6 个任务N2 · 等你 · 3/9—— 对话停了,但活没干完(这时候点归档是没意义的,它会 validate 不过)N2 · 等你 · 只差归档—— 活干完了、没归档。按钮就是给这一格用的N2 · 完成 · 已归档—— 收尾了
一个分不清的地方:in-progress + 勾了 0 个,光看文件分不清是「还没开始」还是
「刚开始干、第一个任务还没勾完」。agent-forge 里正在跑的 workspaces-from-canvas
就是 0/20 的 in-progress。这一格用节点自己的状态补:开过对话就是「跑着」,没开过就是「待跑」。
列表里的标记
| 标记 | 判据 | 能不能进图 |
|---|---|---|
还没开工 0/9 / 做到 3/9 |
勾数(来自 list --json) |
单击就能进图 |
任务全勾了 9/9,只差归档 |
status 是 complete |
也能进图;跑一遍就是开个对话,归档点按钮或写进提示词 |
他处进行 · N 分钟前改过 |
做了一半(勾了任务但没勾完),且文件在最近 15 分钟内改过 | 照样能加,但流水线开跑时会跳过它 |
注意:刚写好的 change(只有提案、或一项任务都没勾)文件时间也一定是新的, 但那只是「刚写完」,不会被判成「他处进行」——新建的提案可以正常进图。
「已在他处进行」是为了不跟你在别的对话里的手工推进重复。判定只看文件改动时间; 流水线自己拉起对话造成的改动会被认作自己人,不会误标。
图里的红灯
刷新时如果发现图里的节点对不上仓库,会标红并写清楚原因,删不删由你:
- 已归档:
openspec/changes/里没了,但archive/里找得到。 - 已找不到:哪都找不到(改名或删了)。
- 已在他处进行:change 做了一半,且最近 15 分钟被改过,像是别的对话在做。
右键节点 →「从图里删掉这一格」是立即删除并落盘;流水线正在跑时后端会拒绝保存, 这时改动留在面板里,停掉之后再点「保存」。
依赖:openspec 命令
0.10.0 起,任务做到哪和归档都靠 openspec 这条命令:
openspec list --json—— 读勾数和官方状态;openspec status --all --json—— 读规划写到第几件(0.11.0);openspec validate <change> --strict --json—— 归档前体检;openspec archive <change> -y—— 真正合并进主规格。
它不在 PATH 里也不会让插件坏掉:任务进度退回自己数 tasks.md,面板顶上会写明
「读不到 openspec 命令」。但归档按钮会失败——按钮就是靠这条命令干活的。
装法(任选):npm i -g @fission-ai/openspec,或者照 OpenSpec 官网的安装脚本。
装完在仓库根目录跑一次 openspec init(已有 openspec/ 的仓库不用)。
Host 调用它的方式是 cmd.exe /d /s /c openspec ...(Windows)/直接 openspec ...(其它平台),
参数一个一个传、不拼字符串;change 名只允许 [A-Za-z0-9._-],不合规就直接拒绝,不进命令行。
离线回归
不需要起 DSH,直接跑(用假 ctx):
node plugins/dsh-openspec-pipeline/test/default-preset-run.mjs
node plugins/dsh-openspec-pipeline/test/archive-gate.mjs
node plugins/dsh-openspec-pipeline/test/archived-node.mjs
node plugins/dsh-openspec-pipeline/test/event-stream.mjs
node plugins/dsh-openspec-pipeline/test/root-resolution.mjs
node plugins/dsh-openspec-pipeline/test/archive-action.mjs
node plugins/dsh-openspec-pipeline/test/planning-state.mjs
archive-action.mjs 会真的调 openspec(在临时目录里造两个 change:一个格式完整、一个残缺),
要证明四件事:CLI 状态读得到、完整的一次就归、残缺的只回报告不归档、人点强制才过。
planning-state.mjs 造三个完成度不同的 change(1/4、3/4、4/4),证明 list 分不出的
「只写了提案」和「只差 tasks」被 status 分开了。
机器上没有 openspec 时这两个都会自己跳过,不算失败。
No comments yet. Be the first to write one.