@godv61/dsh-task-engine 0.26.0 → 0.26.1

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/README.md CHANGED
@@ -56,7 +56,7 @@ dsh plugin --profile web add @godv61/dsh-task-engine
56
56
  | **技能** | 安装、编辑技能,并集中维护每个技能唯一的规则列表。 |
57
57
  | **规则** | 选择 Markdown 文件安装规则,维护项目或个人开发约定。 |
58
58
 
59
- 内置技能和规则只读;项目与个人资源可编辑。安装、删除都会明确展示目标位置。
59
+ 内置技能和规则是只读样本,可用“以此为模板新建”复制正文到项目或个人目录后编辑;新资源的规则和阶段绑定由你单独配置。安装、删除都会明确展示目标位置。
60
60
 
61
61
  **流程与工作方法是两回事。** 流程只决定工作怎么流转(阶段顺序与门禁),预设不自带任何技能或提交格式。要用什么技能、遵守哪些规则、提交信息写什么,都由你配置——「采用推荐配置」只是把一份现成的起点**写入**你的配置,之后完全归你:删掉的不再补回,升级也不覆盖。规则挂在技能下,所以打开一个技能看到的就是它完整的规则列表,挂到任何节点都一样。
62
62
 
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
6
6
 
7
+ ## 0.26.1
8
+
9
+ - 将会话预设强制加载的 `eng-delivery` 收敛为纯流程编排:只按任务状态和冻结配置加载技能、遵守规则及检查证据,不再暗示固定阶段技能映射或所有技能都需要命令回执;同步修正预设人设和工具参数说明。
10
+ - 内置业务技能与规则明确作为只读样本;工作台可从样本预填新资源,改名后保存到项目或个人目录,再单独配置完成凭证、规则和阶段绑定。复制与保存不会自动启用样本。
11
+ - 修正内置实现技能对未绑定 `coding-conventions` 的强制引用,以及旧规则迁移提示中的配置位置。
12
+
7
13
  ## 0.26.0
8
14
 
9
15
  - 修复旧内联阶段绑定与新 `skill_profiles` 同时存在时,技能档案规则未展开、同一技能跨节点误报冲突的问题;明确的技能档案现在覆盖旧内联副本,并在任务创建时冻结实际生效的规则。
@@ -52,6 +52,8 @@
52
52
 
53
53
  **预设不带任何绑定。** 一个只写了 `flow` 的配置就是字面意思:该流程的节点没有绑定,阶段仍按流程骨架流转。要一份现成的工程起点,在工作台点「采用推荐配置」,它会把技能、提交信息格式与产物字段**写入**你的配置一次;此后这些值就是你的普通配置,删掉不会补回,升级也不覆盖。
54
54
 
55
+ 内置业务技能和规则作为只读样本保留。在“技能”或“规则”页选择“以此为模板新建”,可预填一份可编辑的项目或个人资源;复制只带正文,不自动复制该技能的规则档案,也不挂到任何阶段。保存后请单独配置完成凭证、规则与阶段引用。
56
+
55
57
  进入阶段后,`dev_task` 披露该阶段技能和规则的冻结正文。新任务离开阶段前会检查 Harness 的 skill 工具成功加载记录;技能可声明证据类型(`command` / `artifact` / `review` / `manual` / `none`)。`manual` 通过宿主人工审批记录,不要求执行命令;`artifact` 需要该阶段有产物定义且必填字段完整。未声明时沿用命令回执。`status.skill_obligations` 中的 `command_receipts_required` 列出需要命令回执的技能。
56
58
 
57
59
  挂在终态(例如"完成")的技能在进入终态前执行。测试技能通常建议挂在"交付";现有"完成"绑定也会在审核阶段执行后才放行。标准流程 v2 在审核通过后提交,并核对真实 Git HEAD。修改文件或声明范围后,旧验证回执失效。
package/docs/faq.md CHANGED
@@ -24,7 +24,7 @@
24
24
 
25
25
  ## 能移除推荐技能、修改内置规则吗?
26
26
 
27
- 流程预设没有强制技能绑定。「采用推荐配置」只是一次性写入项目配置,之后可任意增删技能和规则。内置资源的正文只读;可新建自己的规则并挂到技能下,也可选择不同来源的同名资源,引用会保留来源,不按名称混用。
27
+ 流程预设没有强制技能绑定。「采用推荐配置」只是一次性写入项目配置,之后可任意增删技能和规则。内置业务技能和规则是只读样本:可用“以此为模板新建”复制正文,改名后保存为自己的资源,再配置完成凭证、规则和阶段绑定。复制不会自动启用它们;不同来源的同名资源也不会混用。会话预设中的 `eng-delivery` 只负责按 `dev_task` 的状态与门禁编排,不决定业务方法。
28
28
 
29
29
  ## 工具显示验证或审核通过,能完全相信吗?
30
30
 
package/docs/manual.html CHANGED
@@ -200,7 +200,7 @@ pnpm install &amp;&amp; pnpm run build &amp;&amp; pnpm dsh web</code></pre>
200
200
  <li><strong>选流程预设</strong>:standard / agile / minimal;如需现成起点,可单独点击「采用推荐配置」。</li>
201
201
  <li><strong>给节点选技能</strong>:先选节点,再用双栏选择器挑选技能;点技能的规则配置,在右侧栏绑定规则与证据类型,最后保存。</li>
202
202
  </ol>
203
- <p>「技能」和「规则」页提供搜索、来源筛选、新建、安装、查看、编辑与删除。内置资源只读。点「安装技能」直接打开系统文件选择器,选择含 <code>SKILL.md</code> 的文件夹;点「安装规则」选择一个 <code>.md</code> 文件。选择后先显示预览:名称、正文、文件数、体积、目标路径和同名冲突。确认项目或个人范围后点击「确认安装」;预览本身不写入文件,改变范围后需要重新预览。</p>
203
+ <p>「技能」和「规则」页提供搜索、来源筛选、新建、安装、查看、编辑与删除。内置资源是只读样本,可点「以此为模板新建」预填可编辑副本;副本的完成凭证、规则与阶段绑定需另行配置。点「安装技能」直接打开系统文件选择器,选择含 <code>SKILL.md</code> 的文件夹;点「安装规则」选择一个 <code>.md</code> 文件。选择后先显示预览:名称、正文、文件数、体积、目标路径和同名冲突。确认项目或个人范围后点击「确认安装」;预览本身不写入文件,改变范围后需要重新预览。</p>
204
204
  <p>项目资源写入工作区 <code>.dsh/skills</code> 或 <code>.dsh/rules</code>;个人资源写入 <code>$DSH_HOME</code> 对应目录。浏览器选择的是浏览器所在电脑的文件;高级目录模式读取 Harness 主机的目录。技能保留脚本、references、模板和二进制资源,排除常见缓存;上限 1000 文件、100 MB、单文件 20 MB、20 层目录,<code>SKILL.md</code> 和规则正文上限 1 MB。拒绝覆盖同名资源;主机扫描拒绝符号链接和 junction。浏览器上传不提供源链接元数据。</p>
205
205
  <p>资源删除前会显示名称与实际目标路径,确认后永久删除;可点击「保留」取消。任务台账支持搜索、风险与阶段筛选,并显示验证、审核和更新时间。流程配置读取与保存失败时显示错误,损坏任务记录不会被静默视为没有任务。</p>
206
206
  <pre><code>// .dsh/eng.json 等价内容
@@ -390,7 +390,7 @@ risk_level: high_risk // 涉及鉴权,验证要加证据
390
390
  <p>状态查询中的 <code>evidence_blockers</code> 列出过期验证和附加技能回执;<code>commit.allowed</code> 同时检查这些阻塞及技能执行义务。<code>legal_next</code> 是流程定义的候选去向,不表示所有门禁已通过。</p>
391
391
  <details><summary>记录需求时提示字段不存在怎么办?</summary><div>先读取 <code>status.artifact_requirements</code>,按当前阶段列出的字段填写。标准需求为 <code>scope</code> 和 <code>acceptance_criteria</code>,敏捷流程只有 <code>scope</code>。疑问、假设或待确认取舍写入字段正文,不新增字段名。错误输入整次不保存,修正后再提交;不需要修改流程配置或历史任务记录。</div></details>
392
392
  <details><summary>验证命令被沙箱阻止怎么办?</summary><div><code>verify</code> 与 <code>skill_result</code> 默认使用会话权限。原命令被沙箱阻止后,用相同命令和 <code>sandbox_permissions: "danger-full-access"</code>、<code>justification</code> 申请单次重试。审批显示命令,批准后才执行;拒绝、取消或审批不可用时不执行、不写回执。授权只对本次调用生效,不改变会话权限。以本次回执的实际模式和退出码判断结果。</div></details>
393
- <p class="stamp">适用于插件 0.26.0 · 最后更新:2026-09-24</p>
393
+ <p class="stamp">适用于插件 0.26.1 · 最后更新:2026-09-24</p>
394
394
  </section>
395
395
  </main>
396
396
  </div>
package/lib/client.js CHANGED
@@ -6064,6 +6064,7 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6064
6064
  const [description, setDescription] = (0, import_react5.useState)("");
6065
6065
  const [whenToUse, setWhenToUse] = (0, import_react5.useState)("");
6066
6066
  const [body2, setBody] = (0, import_react5.useState)("");
6067
+ const [templateSource, setTemplateSource] = (0, import_react5.useState)(null);
6067
6068
  const [deleting, setDeleting] = (0, import_react5.useState)(null);
6068
6069
  const input = (0, import_react5.useRef)(null);
6069
6070
  const fileEpoch = (0, import_react5.useRef)(0);
@@ -6121,6 +6122,7 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6121
6122
  fileEpoch.current++;
6122
6123
  setImporting(false);
6123
6124
  setEditing(null);
6125
+ setTemplateSource(null);
6124
6126
  setDeleting(null);
6125
6127
  setRequest(null);
6126
6128
  setError("");
@@ -6193,6 +6195,7 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6193
6195
  }
6194
6196
  };
6195
6197
  const edit = async (row, view = false) => {
6198
+ setTemplateSource(null);
6196
6199
  setEditing(row);
6197
6200
  setViewing(view);
6198
6201
  setName(row === "new" ? "" : row.name);
@@ -6223,6 +6226,39 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6223
6226
  setBusy(false);
6224
6227
  }
6225
6228
  };
6229
+ const copyTemplate = async (row) => {
6230
+ if (busy || levelOf(row) !== "bundled") return;
6231
+ setBusy(true);
6232
+ setError("");
6233
+ try {
6234
+ const r = kind === "skill" ? await remote.readSkill({ name: row.name, level: "bundled", path: workspace }) : await remote.readRule({ name: row.name, level: "bundled", path: workspace });
6235
+ if (!r.ok) throw new Error(fail(r));
6236
+ if (!r.value.ok) throw new Error(r.value.error ?? "\u8BFB\u53D6\u5185\u7F6E\u6837\u672C\u5931\u8D25");
6237
+ const used = new Set(rows.map((item) => item.name));
6238
+ const base = `${row.name}-copy`;
6239
+ let candidate = base;
6240
+ for (let index = 2; used.has(candidate); index++) candidate = `${base}-${index}`;
6241
+ setName(candidate);
6242
+ setBody(r.value.content);
6243
+ setLevel("project");
6244
+ setViewing(false);
6245
+ if (kind === "skill") {
6246
+ const skill = r.value;
6247
+ setDescription(skill.description);
6248
+ setWhenToUse(skill.whenToUse);
6249
+ } else {
6250
+ setDescription("");
6251
+ setWhenToUse("");
6252
+ }
6253
+ setTemplateSource(row);
6254
+ setEditing("new");
6255
+ setNotice("");
6256
+ } catch (e) {
6257
+ setError("\u590D\u5236\u6837\u672C\u5931\u8D25\uFF1A" + describeError(e));
6258
+ } finally {
6259
+ setBusy(false);
6260
+ }
6261
+ };
6226
6262
  const save2 = async () => {
6227
6263
  if (busy) return;
6228
6264
  setBusy(true);
@@ -6232,9 +6268,12 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6232
6268
  const r = kind === "skill" ? await remote.writeSkill({ name, description, whenToUse, content: body2, level, path: workspace }) : await remote.writeRule({ name, content: body2, level, path: workspace });
6233
6269
  if (!r.ok) throw new Error(fail(r));
6234
6270
  if (!r.value.ok) throw new Error(r.value.error ?? "\u4FDD\u5B58\u5931\u8D25");
6235
- setNotice(`\u5DF2\u4FDD\u5B58${noun} \xB7 ${r.value.path}`);
6271
+ const copied = templateSource !== null;
6272
+ setNotice(`\u5DF2\u4FDD\u5B58${noun} \xB7 ${r.value.path}${copied ? "\uFF1B\u8BF7\u6309\u9700\u914D\u7F6E\u5B8C\u6210\u51ED\u8BC1\u3001\u89C4\u5219\u4E0E\u9636\u6BB5\u7ED1\u5B9A" : ""}`);
6236
6273
  setEditing(null);
6274
+ setTemplateSource(null);
6237
6275
  refresh((n) => n + 1);
6276
+ if (copied && kind === "skill") onConfigureSkill?.({ source: level, name: r.value.name });
6238
6277
  } catch (e) {
6239
6278
  setError(describeError(e));
6240
6279
  } finally {
@@ -6296,9 +6335,11 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6296
6335
  (0, import_react5.createElement)("p", null, row.description || "\u9879\u76EE\u7EA6\u5B9A\u4E0E\u6267\u884C\u89C4\u5219"),
6297
6336
  (0, import_react5.createElement)("div", { className: "te-actions" }, kind === "skill" && onConfigureSkill ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "primary", size: "sm", onClick: () => onConfigureSkill({ source: levelOf(row), name: row.name }) }, "\u914D\u7F6E\u89C4\u5219") : null, (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "outline", size: "sm", onClick: () => {
6298
6337
  void edit(row, true);
6299
- } }, "\u67E5\u770B"), levelOf(row) !== "bundled" ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", size: "sm", onClick: () => {
6338
+ } }, "\u67E5\u770B"), levelOf(row) === "bundled" ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => {
6339
+ void copyTemplate(row);
6340
+ } }, "\u4EE5\u6B64\u4E3A\u6A21\u677F\u65B0\u5EFA") : (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", size: "sm", onClick: () => {
6300
6341
  void edit(row);
6301
- } }, "\u7F16\u8F91") : null, levelOf(row) !== "bundled" ? (0, import_react5.createElement)("button", { className: "te-delete", onClick: () => {
6342
+ } }, "\u7F16\u8F91"), levelOf(row) !== "bundled" ? (0, import_react5.createElement)("button", { className: "te-delete", onClick: () => {
6302
6343
  setDeleting(row);
6303
6344
  setError("");
6304
6345
  } }, "\u5220\u9664") : null)
@@ -6332,10 +6373,13 @@ function ResourceManager({ workspace, remote, kind, onConfigureSkill }) {
6332
6373
  ) : null,
6333
6374
  editing ? (0, import_react5.createElement)(
6334
6375
  ResourceModal,
6335
- { title: editing === "new" ? `\u65B0\u5EFA${noun}` : `${viewing ? "\u67E5\u770B" : "\u7F16\u8F91"}${noun} \xB7 ${name}`, onClose: close, footer: (0, import_react5.createElement)("div", { className: "te-actions" }, (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", disabled: busy, onClick: close }, "\u5173\u95ED"), !readonly ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "outline", disabled: busy, onClick: () => setViewing(!viewing) }, viewing ? "\u7F16\u8F91\u5185\u5BB9" : "\u9884\u89C8") : null, !readonly ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "primary", disabled: busy || !name.trim() || !body2.trim() || kind === "skill" && !description.trim(), onClick: () => {
6376
+ { title: editing === "new" ? `\u65B0\u5EFA${noun}` : `${viewing ? "\u67E5\u770B" : "\u7F16\u8F91"}${noun} \xB7 ${name}`, onClose: close, footer: (0, import_react5.createElement)("div", { className: "te-actions" }, (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", disabled: busy, onClick: close }, "\u5173\u95ED"), readonly ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "outline", disabled: busy, onClick: () => {
6377
+ void copyTemplate(editing);
6378
+ } }, "\u4EE5\u6B64\u4E3A\u6A21\u677F\u65B0\u5EFA") : null, !readonly ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "outline", disabled: busy, onClick: () => setViewing(!viewing) }, viewing ? "\u7F16\u8F91\u5185\u5BB9" : "\u9884\u89C8") : null, !readonly ? (0, import_react5.createElement)(import_dsh_client_ui_primitives5.Button, { variant: "primary", disabled: busy || !name.trim() || !body2.trim() || kind === "skill" && !description.trim(), onClick: () => {
6336
6379
  void save2();
6337
6380
  } }, busy ? "\u5904\u7406\u4E2D\u2026" : "\u4FDD\u5B58") : null) },
6338
- !readonly ? targets() : (0, import_react5.createElement)("p", { className: "te-badge" }, "\u5185\u7F6E\u8D44\u6E90 \xB7 \u53EA\u8BFB"),
6381
+ !readonly ? targets() : (0, import_react5.createElement)("p", { className: "te-badge" }, "\u5185\u7F6E\u6837\u672C \xB7 \u53EA\u8BFB"),
6382
+ templateSource ? (0, import_react5.createElement)("p", null, `\u4EE5\u300C${templateSource.name}\u300D\u4E3A\u6A21\u677F\u590D\u5236\u6B63\u6587\u3002\u65B0\u8D44\u6E90\u7684\u5B8C\u6210\u51ED\u8BC1\u3001\u89C4\u5219\u548C\u9636\u6BB5\u7ED1\u5B9A\u9700\u5355\u72EC\u914D\u7F6E\uFF0C\u4FDD\u5B58\u672C\u8EAB\u4E0D\u4F1A\u542F\u7528\u5B83\u3002`) : null,
6339
6383
  status(),
6340
6384
  viewing ? (0, import_react5.createElement)("div", { className: "te-markdown" }, (0, import_react5.createElement)(import_dsh_client_ui_primitives5.MarkdownText, { text: body2, labels })) : (0, import_react5.createElement)("div", { className: "te-form" }, (0, import_react5.createElement)("label", null, "\u540D\u79F0", (0, import_react5.createElement)("input", { className: "te-input", value: name, disabled: editing !== "new" || busy, onChange: (e) => setName(e.target.value), placeholder: "\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26" })), kind === "skill" ? (0, import_react5.createElement)("label", null, "\u63CF\u8FF0", (0, import_react5.createElement)("input", { className: "te-input", value: description, disabled: busy, onChange: (e) => setDescription(e.target.value) })) : null, kind === "skill" ? (0, import_react5.createElement)("label", null, "\u4F7F\u7528\u65F6\u673A\uFF08\u53EF\u9009\uFF09", (0, import_react5.createElement)("input", { className: "te-input", value: whenToUse, disabled: busy, onChange: (e) => setWhenToUse(e.target.value) })) : null, (0, import_react5.createElement)("label", null, "Markdown \u6B63\u6587", (0, import_react5.createElement)("textarea", { className: "te-input te-editor", value: body2, disabled: busy, onChange: (e) => setBody(e.target.value) })))
6341
6385
  ) : null,