@godv61/dsh-task-engine 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.p0-test.mjs CHANGED
@@ -224,20 +224,29 @@ await assertThrows(
224
224
  )
225
225
  }
226
226
 
227
- // ── 16. L: high-risk verification requires non-blank evidence ───────────────
227
+ // ── 16. L/4.2: high-risk verification requires a REAL command receipt ────────
228
228
  {
229
229
  const cfg = FLOW_PRESETS.standard.config
230
+ const receipt = overrides => ({
231
+ command: 'npm test', exit_code: 0, timed_out: false, aborted: false,
232
+ started_at: '2026-01-01T00:00:00Z', finished_at: '2026-01-01T00:00:01Z',
233
+ stdout: '', stderr: '', ...overrides,
234
+ })
230
235
  const mk = () => {
231
236
  const s = newTask({ id: 'L1', title: 'x', branch: 'main', work_size: 'standard', risk_level: 'high_risk', flow: snapshot })
232
237
  s.stage = '交付'
233
238
  return s
234
239
  }
235
- let s = mk(); s.verification = { passed: true, evidence: [''] }
236
- assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with empty-string evidence is blocked at the verified gate')
237
- s = mk(); s.verification = { passed: true, evidence: [' '] }
238
- assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with whitespace-only evidence is blocked')
239
- s = mk(); s.verification = { passed: true, evidence: ['单测通过'] }
240
- assert(assertAdvance(s, '代码审核', cfg).ok, 'high_risk with non-blank evidence passes the verified gate')
240
+ let s = mk(); s.verification = { passed: true, evidence: ['单测通过'] }
241
+ assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with text-only evidence is blocked (no command receipt)')
242
+ s = mk(); s.verification = { passed: true, evidence: [], receipt: receipt({}) }
243
+ assert(assertAdvance(s, '代码审核', cfg).ok, 'high_risk with an exit-0 receipt passes the verified gate')
244
+ s = mk(); s.verification = { passed: true, evidence: [], receipt: receipt({ exit_code: 1 }) }
245
+ assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with a non-zero exit receipt is blocked')
246
+ s = mk(); s.verification = { passed: true, evidence: [], receipt: receipt({ timed_out: true }) }
247
+ assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with a timed-out receipt is blocked')
248
+ s = mk(); s.verification = { passed: true, evidence: [], receipt: receipt({ aborted: true }) }
249
+ assert(!assertAdvance(s, '代码审核', cfg).ok, 'high_risk with an aborted receipt is blocked')
241
250
  }
242
251
 
243
252
  // ── 17. J: task id extracted from the summary (the hook uses it, not a guess) ─
@@ -264,14 +273,15 @@ await assertThrows(
264
273
  const hook = readFileSync('./hooks/commit-msg', 'utf8')
265
274
  assert(hook.includes('-c core.quotePath=false'), 'hook disables git octal path quoting')
266
275
  assert(hook.includes('-z'), 'hook reads NUL-separated raw-byte paths')
267
- assert(hook.includes('--diff-filter=ACMRD'), 'hook includes deletions in the scope check')
276
+ assert(hook.includes('--diff-filter=ACMRDT'), 'hook includes deletions and type changes in the scope check')
268
277
  }
269
278
 
270
279
  // ── 20. 4.1/4.3: hook uses the frozen snapshot; writeInit guards overwrite ──
271
280
  {
272
281
  const hook = readFileSync('./hooks/commit-msg', 'utf8')
273
- assert(hook.includes('state.flow && state.flow.config'), 'hook checks the task frozen flow snapshot')
282
+ assert(hook.includes('config = state.flow.config'), 'hook checks the task frozen flow snapshot')
274
283
  assert(hook.includes('function extractTaskId'), 'hook extracts the task id independent of the live config')
284
+ assert(!hook.includes('const FLOWS'), 'hook bundles the single source (workflows.ts), not a hand mirror')
275
285
  }
276
286
  {
277
287
  const controller = readFileSync('./lib/controller.js', 'utf8')
package/README.md CHANGED
@@ -174,4 +174,5 @@ dsh plugin --profile <name> add .
174
174
  - **会话工作目录修复(0.18.1)**:`dev_task` 的所有文件操作此前用 `fs.resolve(相对路径)` 不带 cwd,落到了 fs 后端默认目录(DSH 进程目录)而非会话工作区——在 web 会话里会把台账、配置、`AGENTS.md`、git 钩子写到/读到错误位置。改为从 `exec.agent.session.header.cwd` 取会话工作区并传给每个解析,补 4 项回归测试。
175
175
  - **工作台项目初始化(0.19.0)**:工作台新增置顶的「项目初始化」标签页——加载展示项目根 `AGENTS.md`、一键让 AI 扫描项目生成草稿(预览后再确认写回)、支持手动编辑与覆盖;Host 控制器新增 `readInit`/`writeInit`/`generateInit` 三个 Remote,`generateInit` 通过 `ctx.llm` + 默认模型在 Host 端直接生成,复用 `dev_task init` 的 200 行硬约束。
176
176
  - **使用手册跟进(0.19.1)**:随包发布的 `docs/manual.html` 补上工作台「项目初始化」(默认置顶标签页、项目根自动发现、AI 生成 150 秒超时 + 覆盖需人工确认),第 7 节标签页从「三个」改为「五个」;安装章节补全 dsh CLI(非源码)安装方式与 pnpm 前置、`dsh plugin add` 的挂载机制;删除顶层已废弃的 `USER_GUIDE.html`(v0.9.1、无引用、不随包发布),README 目录结构描述同步为五标签页。
177
- - **审计修复(0.19.2)**:补齐七项——① 提交钩子 `stagedFiles` 用 `core.quotePath=false` + `-z` 按 NUL 拆分(中文文件名不再被八进制转义误拒)并补 `D`(删除范围外文件也被拦);② 提交消息第一段改为 task id、钩子按 id 精确定位任务(不再按分支/mtime 猜,同分支多任务不再锁错);③ artifact id 全流程唯一 + `record` 校验产物属于当前阶段(堵越阶段复用);④ 高风险验证证据 `trim()` 后须非空(`evidence:[""]` 不再通过);⑤ README/手册加「诚实边界」,明说验证/评审/实施项是模型自报、需人工或 CI 兜底;⑥ 文档修正优先级(内置 &gt; 用户 &gt; 项目、内置不可覆盖)与 enable 措辞,`.dsh/task-*.json`/`eng.json` 豁免文件范围门;⑦ 提交钩子改用任务快照的 frozen 配置校验(对抗任务执行中改流程导致的配置漂移);⑧ 工作台 `writeInit` 对齐 `init` 保护(已有 `AGENTS.md` 时需显式 overwrite + 前端确认才覆盖);⑨ `.p0-test.mjs` 纳入发布包,装包后 `npm test` 可用。新增回归测试。
177
+ - **审计修复(0.19.2)**:补齐七项——① 提交钩子 `stagedFiles` 用 `core.quotePath=false` + `-z` 按 NUL 拆分(中文文件名不再被八进制转义误拒)并补 `D`(删除范围外文件也被拦);② 提交消息第一段改为 task id、钩子按 id 精确定位任务(不再按分支/mtime 猜,同分支多任务不再锁错);③ artifact id 全流程唯一 + `record` 校验产物属于当前阶段(堵越阶段复用);④ 高风险验证证据 `trim()` 后须非空(`evidence:[""]` 不再通过);⑤ README/手册加「诚实边界」,明说验证/评审/实施项是模型自报、需人工或 CI 兜底;⑥ 文档修正优先级(内置 &gt; 用户 &gt; 项目、内置不可覆盖)与 enable 措辞,`.dsh/task-*.json`/`eng.json` 豁免文件范围门;⑦ 提交钩子改用任务快照的 frozen 配置校验(对抗任务执行中改流程导致的配置漂移);⑧ 工作台 `writeInit` 对齐 `init` 保护(已有 `AGENTS.md` 时需显式 overwrite + 前端确认才覆盖);⑨ `.p0-test.mjs` 纳入发布包,装包后 `npm test` 可用。新增回归测试。
178
+ - **安全闭环(0.20.0)**:① 验证改真实命令回执——`dev_task verify` 新增 `command` 入参,引擎通过宿主 shell 服务真实运行该命令并落 `VerificationReceipt`(命令 / 退出码 / 超时 / 中止 / 起止时间 / stdout / stderr);`high_risk` 任务的 `verified` 门改为要求回执 `exit_code === 0` 且非超时 / 中止,纯文本 `passed` 声明不再放行(常规风险仍可用 `passed` + `evidence` 文本);② 文件范围检查补 `T` + 改用 `--name-status`——`--diff-filter=ACMRDT` 纳入类型变换,提交状态随范围拒绝一并报出,删除 / 类型变换 / 重命名的旧·新路径都受范围检查;③ 提交钩子改为从 `engine.ts` / `workflows.ts` 单一源打包生成(`build-hook.mjs`),彻底消除手写镜像漂移,未知流程在钩子侧同样 fail-closed(不再静默回退 `standard`)。
package/docs/manual.html CHANGED
@@ -81,7 +81,7 @@
81
81
  </div>
82
82
  <p>三样东西分工明确:<strong>流程预设</strong>决定「走哪条流水线、有哪些守卫」,<strong>Skill</strong> 决定「这一站做什么」,<strong>Rule</strong> 决定「这一站守什么」。任务状态单独落盘,负责「跨会话恢复到哪一步」。</p>
83
83
  <div class="callout warn">
84
- <p><strong>诚实边界:</strong>阶段流转、提交格式、文件范围、消息里的任务绑定是<b>代码硬校验</b>(不匹配直接拒绝);但「验证通过」「评审通过」「实施项完成」是模型通过工具上报的状态,代码只校验这些状态在流程里自洽,<b>不验证上报内容本身是否属实</b>——要硬保证请叠加外部 CI 或人工评审。只有「需求确认」「方案确认」两扇门由人工批准点亮。</p>
84
+ <p><strong>诚实边界:</strong>阶段流转、提交格式、文件范围、消息里的任务绑定是<b>代码硬校验</b>(不匹配直接拒绝);<b>高风险任务的「验证通过」也是真实命令回执</b>——引擎实际运行 <code>verify</code> 命令、取退出码(<code>exit_code === 0</code> 且非超时/中止)才放行,不是模型自报。仍属模型自报、需人工或 CI 兜底的是:常规风险的验证声明、「评审通过」「实施项完成」,以及回执命令本身的覆盖面。只有「需求确认」「方案确认」两扇门由人工批准点亮。</p>
85
85
  </div>
86
86
  </section>
87
87
 
@@ -167,7 +167,7 @@ pnpm install &amp;&amp; pnpm run build &amp;&amp; pnpm dsh web</code></pre>
167
167
  <tr><td><code>requirement-analysis</code></td><td>目标、验收、非目标,落需求说明,等人确认</td><td>编码</td></tr>
168
168
  <tr><td><code>solution-design</code></td><td>最小方案、技术基线、改动点,落设计文档</td><td>实现</td></tr>
169
169
  <tr><td><code>code-implement</code></td><td>实现、做「规格 + 质量」两阶段评审,都过才标完成</td><td>无关重构</td></tr>
170
- <tr><td><code>code-verify</code></td><td>按验收与风险验证、记录证据(高风险必须带证据)</td><td>掩盖失败或自动修复</td></tr>
170
+ <tr><td><code>code-verify</code></td><td>按验收与风险验证、记录结果(高风险必须跑真实命令回执)</td><td>掩盖失败或自动修复</td></tr>
171
171
  <tr><td><code>code-commit</code></td><td>校验阶段、范围、消息格式后提交</td><td>远程 push / 合并 / 发布</td></tr>
172
172
  <tr><td><code>code-review</code></td><td>评审变更,落结论与问题清单</td><td>代替实现或验证</td></tr>
173
173
  </tbody>
@@ -262,7 +262,7 @@ pnpm install &amp;&amp; pnpm run build &amp;&amp; pnpm dsh web</code></pre>
262
262
  <tr><td>需求评审</td><td>拆需求、落「需求说明」</td><td>字段填全,且<b>人点「允许」确认需求</b></td></tr>
263
263
  <tr><td>设计</td><td>出最小方案、落「设计文档」</td><td>字段填全,且<b>人点「允许」确认方案</b></td></tr>
264
264
  <tr><td>开发</td><td>逐项实现,每项做规格 + 质量两阶段评审</td><td>实施项非空且全部 done(每项带两阶段评审)</td></tr>
265
- <tr><td>交付</td><td>验证、记证据,按门禁提交</td><td>验证通过(高风险必须带证据)</td></tr>
265
+ <tr><td>交付</td><td>验证、记证据,按门禁提交</td><td>验证通过(高风险必须真实命令回执,退出码 0)</td></tr>
266
266
  <tr><td>代码审核</td><td>评审变更,落「评审记录」</td><td>结论通过且字段填全</td></tr>
267
267
  <tr><td>完成</td><td>收尾</td><td>—</td></tr>
268
268
  </tbody>
@@ -290,7 +290,7 @@ pnpm install &amp;&amp; pnpm run build &amp;&amp; pnpm dsh web</code></pre>
290
290
  <tr><td><code>risk_level</code></td><td>standard / high_risk,决定验证强度</td></tr>
291
291
  <tr><td><code>requirement_confirmed</code></td><td>需求是否已由人确认</td></tr>
292
292
  <tr><td><code>solution_confirmed</code></td><td>方案是否已由人确认</td></tr>
293
- <tr><td><code>verification</code></td><td>验证是否通过 + 证据</td></tr>
293
+ <tr><td><code>verification</code></td><td>验证是否通过 + 证据(高风险附真实命令回执)</td></tr>
294
294
  <tr><td><code>review</code></td><td>评审结论(通过 / 未过 + 问题)</td></tr>
295
295
  <tr><td><code>files</code></td><td>本任务允许修改的文件范围</td></tr>
296
296
  <tr><td><code>commits</code></td><td>已创建的提交记录</td></tr>
@@ -307,7 +307,7 @@ pnpm install &amp;&amp; pnpm run build &amp;&amp; pnpm dsh web</code></pre>
307
307
  <li>实施中优先受影响模块的编译、目标测试与静态检查。</li>
308
308
  <li>差异没变时复用有效证据,不重复跑相同命令。</li>
309
309
  <li>无法联调外部系统时必须如实写「未联调」,不能把编译成功说成功能通过。</li>
310
- <li><code>high_risk</code> 任务过验证门必须附证据。</li>
310
+ <li><code>high_risk</code> 任务过验证门必须跑真实命令回执(引擎运行命令、取退出码,非自报通过)。</li>
311
311
  </ul>
312
312
  <h3>评审</h3>
313
313
  <p>开发阶段每项做「规格 + 质量」两阶段评审,都 pass 才标 done;缺评审或任一阶段 fail 会挡住「开发 → 交付」。</p>
@@ -346,7 +346,7 @@ risk_level: high_risk // 涉及鉴权,验证要加证据
346
346
  - 规格 ✓ 质量 ✓ → done
347
347
 
348
348
  ④ 交付
349
- - 验证:跑鉴权用例,记录通过证据(high_risk 必须)。
349
+ - 验证:跑鉴权用例,`verify` 传真实命令(high_risk 由引擎取退出码判定)。
350
350
  - 提交:【模块】【TASK】查询接口增加权限校验(只含本任务文件)
351
351
 
352
352
  ⑤ 代码审核