@godv61/dsh-task-engine 0.5.3 → 0.6.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/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  - **产物门**:每个阶段可声明必交产物 + 必填字段(需求说明 / 设计文档 / 评审记录…),字段没填全 → 不让流转。
10
10
  - **提交门禁**:到检查点才能提交,消息必须匹配 `message_pattern`,`manual` 策略永不自动提交。
11
11
  - **文件范围**:任务声明 `files`(本任务该改的文件),提交碰了范围外的文件 → 工具和 git 钩子都拒绝。
12
+ - **skill / rule 挂载(0.6.0)**:每个阶段可挂「这个阶段用什么 skill」+「守哪条 rule」;模型走到该阶段时,`dev_task` 只**渐进披露**这阶段挂载的内容,不一次性全给。内置一套通用库,也能在网页里新建自己的 skill/rule。
12
13
 
13
14
  ## 目录
14
15
 
@@ -19,8 +20,9 @@ hooks/commit-msg git 提交门禁钩子(install_hook 装进 .git/hooks/
19
20
  src/engine.ts 纯函数状态机 + 配置校验(无 harness 依赖,双端复用)
20
21
  src/index.ts 插件(host):注册 dev_task 工具 + 技能 + 挂 task-engine Remote 控制器
21
22
  src/controller.ts Host 端 Remote 控制器:网页读写 .dsh/eng.json
22
- src/client/ 浏览器半:设置页「工程流程配置」(表单 + 实时校验 + 保存)
23
- skills/eng-delivery/ 内置交付技能
23
+ src/client/ 浏览器半:设置页「工程流程配置」(表单 + 实时校验 + 保存 + 挂载/新建)
24
+ skills/ 内置技能库(需求分析/方案设计/实现/验证/审核/提交/交付编排)
25
+ rules/ 内置规则库(编码规范/提交规范/安全红线)
24
26
  preset/preset.yml 预设元数据
25
27
  preset/persona.md 预设 persona 文案(复制粘贴用)
26
28
  ```
@@ -86,6 +88,24 @@ dsh plugin --profile <name> add .
86
88
 
87
89
  > 纯 headless 装机不受影响(只是没有这个页);Web profile 需要包含 `@deepseek-ai/dsh-web-app`。
88
90
 
91
+ ## skill / rule 挂载(0.6.0)
92
+
93
+ 流程不止"走到哪个阶段",还有"这个阶段该用哪本手册"。每个阶段可以在 `stage_bindings` 里挂 skill 和 rule:
94
+
95
+ ```json
96
+ {
97
+ "stage_bindings": {
98
+ "需求评审": { "skills": ["requirement-analysis"], "rules": ["security-redlines"] },
99
+ "开发": { "skills": ["code-implement"], "rules": ["coding-conventions"] }
100
+ }
101
+ }
102
+ ```
103
+
104
+ - **渐进披露**:模型走到某阶段时,`dev_task`(`status` / `advance`)只返回**这个阶段**挂载的 skill 名 + rule 内容;skill 用 DSH 的 `skill` 工具按名加载,rule 直接把正文给出。阶段推进到哪,才给哪。
105
+ - **三层来源**:技能和规则都来自「内置 + 项目 + 用户」三层——内置(本包 `skills/`、`rules/`)、项目(`.dsh/skills/`、`.dsh/rules/`)、用户(`$DSH_HOME/skills/`、`$DSH_HOME/rules/`),同名时项目优先于用户优先于内置。
106
+ - **网页新建**:设置页「新建 skill / rule」填 name + description + 正文,即可写进项目 `.dsh/`(团队共享)或用户 `$DSH_HOME/`(个人全局),写好后自动出现在挂载清单里。
107
+ - **内置库**:6 个节点技能(`requirement-analysis` / `solution-design` / `code-implement` / `code-verify` / `code-review` / `code-commit`)+ 3 条规则(`coding-conventions` / `commit-conventions` / `security-redlines`),开箱即用;想加项目专属的,用 DSH 原生技能机制或网页新建即可。
108
+
89
109
  ## git 提交门禁(防止绕过)
90
110
 
91
111
  默认提交是「工具校验 + 模型执行 git」,模型理论上能绕过 `dev_task` 直接 `git commit`。装个机械钩子彻底封死:
@@ -99,4 +119,5 @@ dsh plugin --profile <name> add .
99
119
 
100
120
  - **脚本级已验证**:strict `tsc` typecheck/build 零错误;`dsh plugin add` 装进独立实例、`--dump-config` 确认 `task-engine` 进组合树;`validateWorkflow`(配置校验)、`artifacts_present`(产物门)、`file_scope`(文件范围门)、`hooks/commit-msg`(机械门禁)单测/冒烟全过——非法配置逐条报错、产物没填全挡流转、范围外文件挡提交、钩子该放放该拦拦。
101
121
  - **真实模型端到端(0.4.1)**:headless + NewAPI DeepSeek 下让真模型走 `dev_task` 全流程,抓到并修掉一个真实 bug——`readText`/`writeText` 调 `fs.resolve()` 漏了 `await`,把 `Promise<FsTarget>` 当 `target` 传给了读写方法,导致 create/record/advance/commit 全部写不了任务文件、读永远返回 undefined。0.4.1 修复(`await fs.resolve(relPath)`),并把 `task_id` 在 create 也必填的说明补进工具 schema 与技能。修复后走真实 `ctx.fs` 路径冒烟全绿。
102
- - **网页图形化配置界面(0.5.0)**:新增 `src/client/` 设置页「工程流程配置」+ Host 端 `task-engine` Remote 控制器;`@godv61/dsh-task-engine` 升级为双端包(`dsh.client` manifest + `./client` 出品,`exports` 暴露)。已实机验证:`dsh web` 起服务后 boot 数据里 `@godv61/dsh-task-engine` 以 `inject:["@deepseek-ai/dsh-api-gateway"]` 进入 application batch、`/plugins/…/client.js` 正常服务;headless 冒烟确认 boot + `dev_task` 未被新控制器破坏。浏览器点击级 e2e 留到发布后用真浏览器收尾。
122
+ - **网页图形化配置界面(0.5.0)**:新增 `src/client/` 设置页「工程流程配置」+ Host 端 `task-engine` Remote 控制器;`@godv61/dsh-task-engine` 升级为双端包(`dsh.client` manifest + `./client` 出品,`exports` 暴露)。已实机验证:`dsh web` 起服务后 boot 数据里 `@godv61/dsh-task-engine` 以 `inject:["@deepseek-ai/dsh-api-gateway"]` 进入 application batch、`/plugins/…/client.js` 正常服务;headless 冒烟确认 boot + `dev_task` 未被新控制器破坏。浏览器点击级 e2e 留到发布后用真浏览器收尾。
123
+ - **skill/rule 挂载与渐进披露(0.6.0)**:`WorkflowConfig` 新增 `stage_bindings`(每阶段挂 skills/rules,可选字段),`dev_task` 的 `status`/`advance` 按当前阶段披露挂载的 skill 名 + rule 正文;内置 6 技能 + 3 规则库;Host 控制器新增 `listSkills`/`listRules`/`writeSkill`/`writeRule`;设置页新增「节点挂载」和「新建 skill / rule」两块,同时补上 `dev_task` 缺失的 `items` 操作(更新实施项状态)。引擎层 `stage_bindings` 校验(未知阶段/空名)与合并已单测通过。
package/defaults/eng.json CHANGED
@@ -20,5 +20,12 @@
20
20
  "checkpoints": ["交付"],
21
21
  "file_scope": true
22
22
  },
23
- "high_risk_requires_verification": true
23
+ "high_risk_requires_verification": true,
24
+ "stage_bindings": {
25
+ "需求评审": { "skills": ["requirement-analysis"], "rules": ["security-redlines"] },
26
+ "设计": { "skills": ["solution-design"] },
27
+ "开发": { "skills": ["code-implement"], "rules": ["coding-conventions"] },
28
+ "交付": { "skills": ["code-verify", "code-commit"], "rules": ["coding-conventions", "commit-conventions"] },
29
+ "代码审核": { "skills": ["code-review"], "rules": ["security-redlines"] }
30
+ }
24
31
  }
package/lib/client.js CHANGED
@@ -4088,13 +4088,18 @@ var commitSchema = external_exports.object({
4088
4088
  checkpoints: external_exports.array(external_exports.string()),
4089
4089
  file_scope: external_exports.boolean()
4090
4090
  });
4091
+ var stageBindingSchema = external_exports.object({
4092
+ skills: external_exports.array(external_exports.string()).optional(),
4093
+ rules: external_exports.array(external_exports.string()).optional()
4094
+ });
4091
4095
  var configSchema = external_exports.object({
4092
4096
  stages: external_exports.array(external_exports.string()),
4093
4097
  start_stage: external_exports.string(),
4094
4098
  transitions: external_exports.array(transitionSchema),
4095
4099
  artifacts: external_exports.array(artifactSchema),
4096
4100
  commit: commitSchema,
4097
- high_risk_requires_verification: external_exports.boolean()
4101
+ high_risk_requires_verification: external_exports.boolean(),
4102
+ stage_bindings: external_exports.record(external_exports.string(), stageBindingSchema).optional()
4098
4103
  });
4099
4104
  var viewSchema = external_exports.object({
4100
4105
  ok: external_exports.boolean(),
@@ -4106,6 +4111,39 @@ var writeRequestSchema = external_exports.object({
4106
4111
  path: external_exports.string(),
4107
4112
  config: configSchema
4108
4113
  });
4114
+ var skillCatalogSchema = external_exports.object({
4115
+ skills: external_exports.array(external_exports.object({
4116
+ name: external_exports.string(),
4117
+ description: external_exports.string(),
4118
+ source: external_exports.string()
4119
+ }))
4120
+ });
4121
+ var ruleCatalogSchema = external_exports.object({
4122
+ rules: external_exports.array(external_exports.object({
4123
+ name: external_exports.string(),
4124
+ source: external_exports.string()
4125
+ }))
4126
+ });
4127
+ var writeSkillRequestSchema = external_exports.object({
4128
+ name: external_exports.string(),
4129
+ description: external_exports.string(),
4130
+ whenToUse: external_exports.string().optional(),
4131
+ content: external_exports.string(),
4132
+ level: external_exports.enum(["project", "user"]),
4133
+ path: external_exports.string().optional()
4134
+ });
4135
+ var writeRuleRequestSchema = external_exports.object({
4136
+ name: external_exports.string(),
4137
+ content: external_exports.string(),
4138
+ level: external_exports.enum(["project", "user"]),
4139
+ path: external_exports.string().optional()
4140
+ });
4141
+ var writeResourceResultSchema = external_exports.object({
4142
+ ok: external_exports.boolean(),
4143
+ name: external_exports.string(),
4144
+ path: external_exports.string(),
4145
+ error: external_exports.string().optional()
4146
+ });
4109
4147
  var PACKAGE = "@godv61/dsh-task-engine";
4110
4148
  var TYPERT_REMOTE = {
4111
4149
  package: PACKAGE,
@@ -4141,6 +4179,70 @@ var TYPERT_REMOTE = {
4141
4179
  }
4142
4180
  ],
4143
4181
  result: { mode: "strict", typeSymbol: `${PACKAGE}/types#EngConfigView`, schema: viewSchema }
4182
+ },
4183
+ {
4184
+ id: `${PACKAGE}#task-engine/listSkills`,
4185
+ service: "taskEngineController",
4186
+ namespace: "task-engine",
4187
+ method: "listSkills",
4188
+ invocation: { kind: "direct" },
4189
+ parameters: [
4190
+ {
4191
+ name: "path",
4192
+ wire: "path",
4193
+ source: "json",
4194
+ codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string() }
4195
+ }
4196
+ ],
4197
+ result: { mode: "strict", typeSymbol: `${PACKAGE}/types#SkillCatalog`, schema: skillCatalogSchema }
4198
+ },
4199
+ {
4200
+ id: `${PACKAGE}#task-engine/listRules`,
4201
+ service: "taskEngineController",
4202
+ namespace: "task-engine",
4203
+ method: "listRules",
4204
+ invocation: { kind: "direct" },
4205
+ parameters: [
4206
+ {
4207
+ name: "path",
4208
+ wire: "path",
4209
+ source: "json",
4210
+ codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string() }
4211
+ }
4212
+ ],
4213
+ result: { mode: "strict", typeSymbol: `${PACKAGE}/types#RuleCatalog`, schema: ruleCatalogSchema }
4214
+ },
4215
+ {
4216
+ id: `${PACKAGE}#task-engine/writeSkill`,
4217
+ service: "taskEngineController",
4218
+ namespace: "task-engine",
4219
+ method: "writeSkill",
4220
+ invocation: { kind: "direct" },
4221
+ parameters: [
4222
+ {
4223
+ name: "request",
4224
+ wire: "request",
4225
+ source: "json",
4226
+ codec: { mode: "strict", typeSymbol: `${PACKAGE}/types#WriteSkillRequest`, schema: writeSkillRequestSchema }
4227
+ }
4228
+ ],
4229
+ result: { mode: "strict", typeSymbol: `${PACKAGE}/types#WriteResourceResult`, schema: writeResourceResultSchema }
4230
+ },
4231
+ {
4232
+ id: `${PACKAGE}#task-engine/writeRule`,
4233
+ service: "taskEngineController",
4234
+ namespace: "task-engine",
4235
+ method: "writeRule",
4236
+ invocation: { kind: "direct" },
4237
+ parameters: [
4238
+ {
4239
+ name: "request",
4240
+ wire: "request",
4241
+ source: "json",
4242
+ codec: { mode: "strict", typeSymbol: `${PACKAGE}/types#WriteRuleRequest`, schema: writeRuleRequestSchema }
4243
+ }
4244
+ ],
4245
+ result: { mode: "strict", typeSymbol: `${PACKAGE}/types#WriteResourceResult`, schema: writeResourceResultSchema }
4144
4246
  }
4145
4247
  ]
4146
4248
  };
@@ -4206,6 +4308,22 @@ function validateWorkflow(config) {
4206
4308
  fieldSet.add(field);
4207
4309
  }
4208
4310
  });
4311
+ const bindings = config.stage_bindings ?? {};
4312
+ for (const [stage, binding] of Object.entries(bindings)) {
4313
+ if (!config.stages.includes(stage)) {
4314
+ problems.push(`stage_bindings: stage "${stage}" is not a stage`);
4315
+ }
4316
+ for (const skill of binding.skills ?? []) {
4317
+ if (typeof skill !== "string" || skill.trim() === "") {
4318
+ problems.push(`stage_bindings "${stage}": empty skill name`);
4319
+ }
4320
+ }
4321
+ for (const rule of binding.rules ?? []) {
4322
+ if (typeof rule !== "string" || rule.trim() === "") {
4323
+ problems.push(`stage_bindings "${stage}": empty rule name`);
4324
+ }
4325
+ }
4326
+ }
4209
4327
  if (!["task", "item", "manual"].includes(config.commit.policy)) {
4210
4328
  problems.push(`commit.policy "${config.commit.policy}" is not task|item|manual`);
4211
4329
  }
@@ -4454,7 +4572,14 @@ var STANDARD = {
4454
4572
  checkpoints: ["\u4EA4\u4ED8"],
4455
4573
  file_scope: true
4456
4574
  },
4457
- high_risk_requires_verification: true
4575
+ high_risk_requires_verification: true,
4576
+ stage_bindings: {
4577
+ "\u9700\u6C42\u8BC4\u5BA1": { skills: ["requirement-analysis"], rules: ["security-redlines"] },
4578
+ "\u8BBE\u8BA1": { skills: ["solution-design"] },
4579
+ "\u5F00\u53D1": { skills: ["code-implement"], rules: ["coding-conventions"] },
4580
+ "\u4EA4\u4ED8": { skills: ["code-verify", "code-commit"], rules: ["coding-conventions", "commit-conventions"] },
4581
+ "\u4EE3\u7801\u5BA1\u6838": { skills: ["code-review"], rules: ["security-redlines"] }
4582
+ }
4458
4583
  };
4459
4584
  var AGILE = {
4460
4585
  stages: ["\u9700\u6C42", "\u5F00\u53D1", "\u4EA4\u4ED8", "\u5BA1\u67E5"],
@@ -4474,7 +4599,13 @@ var AGILE = {
4474
4599
  checkpoints: ["\u4EA4\u4ED8"],
4475
4600
  file_scope: true
4476
4601
  },
4477
- high_risk_requires_verification: false
4602
+ high_risk_requires_verification: false,
4603
+ stage_bindings: {
4604
+ "\u9700\u6C42": { skills: ["requirement-analysis"] },
4605
+ "\u5F00\u53D1": { skills: ["code-implement"], rules: ["coding-conventions"] },
4606
+ "\u4EA4\u4ED8": { skills: ["code-verify", "code-commit"], rules: ["commit-conventions"] },
4607
+ "\u5BA1\u67E5": { skills: ["code-review"] }
4608
+ }
4478
4609
  };
4479
4610
  var MINIMAL = {
4480
4611
  stages: ["\u5F00\u53D1", "\u4EA4\u4ED8"],
@@ -4490,7 +4621,11 @@ var MINIMAL = {
4490
4621
  checkpoints: ["\u4EA4\u4ED8"],
4491
4622
  file_scope: false
4492
4623
  },
4493
- high_risk_requires_verification: false
4624
+ high_risk_requires_verification: false,
4625
+ stage_bindings: {
4626
+ "\u5F00\u53D1": { skills: ["code-implement"] },
4627
+ "\u4EA4\u4ED8": { skills: ["code-commit"], rules: ["commit-conventions"] }
4628
+ }
4494
4629
  };
4495
4630
  var WORKFLOW_TEMPLATES = [
4496
4631
  { id: "standard", label: "\u6807\u51C6\u7814\u53D1", description: "\u9700\u6C42\u8BC4\u5BA1 \u2192 \u8BBE\u8BA1 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u4EE3\u7801\u5BA1\u6838\uFF0C\u542B\u4EA7\u7269\u95E8 + \u786E\u8BA4\u95E8", config: STANDARD },
@@ -4534,6 +4669,22 @@ var styles = {
4534
4669
  padding: "8px 10px"
4535
4670
  },
4536
4671
  inline: { display: "inline-flex", alignItems: "center", gap: 6, marginRight: 8 },
4672
+ textarea: {
4673
+ background: "var(--dsw-alias-bg-layer-1)",
4674
+ border: `1px solid ${border2}`,
4675
+ borderRadius: 8,
4676
+ padding: "8px 10px",
4677
+ color: text,
4678
+ fontSize: 13,
4679
+ minHeight: 90,
4680
+ resize: "vertical",
4681
+ fontFamily: "inherit",
4682
+ width: "100%",
4683
+ boxSizing: "border-box"
4684
+ },
4685
+ sourceBadge: { color: textTertiary2, fontSize: 11 },
4686
+ bindingStage: { display: "flex", flexDirection: "column", gap: 6 },
4687
+ bindingLabel: { color: text, fontSize: 13, fontWeight: 600 },
4537
4688
  problems: {
4538
4689
  display: "flex",
4539
4690
  flexDirection: "column",
@@ -4601,6 +4752,8 @@ function TaskEngineSection(props) {
4601
4752
  const [savedAt, setSavedAt] = (0, import_react2.useState)("");
4602
4753
  const [loadError, setLoadError] = (0, import_react2.useState)("");
4603
4754
  const [loadedProblems, setLoadedProblems] = (0, import_react2.useState)([]);
4755
+ const [skills, setSkills] = (0, import_react2.useState)([]);
4756
+ const [rules, setRules] = (0, import_react2.useState)([]);
4604
4757
  (0, import_react2.useEffect)(() => {
4605
4758
  if (workspaces.length > 0 && workspace === "") {
4606
4759
  setWorkspace(workspaces[0].path);
@@ -4627,6 +4780,20 @@ function TaskEngineSection(props) {
4627
4780
  alive = false;
4628
4781
  };
4629
4782
  }, [remote, workspace]);
4783
+ const refreshCatalogs = (0, import_react2.useCallback)(() => {
4784
+ if (workspace === "") return;
4785
+ void remote.listSkills(workspace).then((result) => {
4786
+ if (result.ok) setSkills(result.value.skills);
4787
+ }, () => {
4788
+ });
4789
+ void remote.listRules(workspace).then((result) => {
4790
+ if (result.ok) setRules(result.value.rules);
4791
+ }, () => {
4792
+ });
4793
+ }, [remote, workspace]);
4794
+ (0, import_react2.useEffect)(() => {
4795
+ refreshCatalogs();
4796
+ }, [refreshCatalogs]);
4630
4797
  const problems = (0, import_react2.useMemo)(() => config === null ? [] : validateWorkflow(config), [config]);
4631
4798
  const notice = problems.length === 0 && loadedProblems.length > 0 ? loadedProblems : [];
4632
4799
  const addTransition = (from, to) => {
@@ -4693,6 +4860,8 @@ function TaskEngineSection(props) {
4693
4860
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 }), title: "\u6D41\u8F6C\uFF08\u9636\u6BB5 \u2192 \u9636\u6BB5\uFF0C\u53EF\u5E26\u5B88\u536B\uFF09", hint: "\u6BCF\u6761\u6D41\u8F6C = \u4ECE\u300C\u4E0A\u4E00\u6B65\u300D\u5230\u300C\u4E0B\u4E00\u6B65\u300D\u5141\u8BB8\u8D70\u7684\u8DEF\uFF1B\u53EF\u9644\u52A0\u5B88\u536B\uFF0C\u6EE1\u8DB3\u5B88\u536B\u624D\u653E\u884C\u3002\u4E5F\u53EF\u5728\u6D41\u7A0B\u56FE\u91CC\u76F4\u63A5\u8FDE\u7EBF/\u70B9\u7BAD\u5934\u5220\u9664\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransitionEditor, { config, setConfig }) }),
4694
4861
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconCheckOutline16, { size: 16 }), title: "\u4EA7\u7269\uFF08\u6BCF\u4E2A\u9636\u6BB5\u8981\u586B\u7684\u5B57\u6BB5\uFF09", hint: "\u7ED9\u67D0\u9636\u6BB5\u58F0\u660E\u8981\u4EA4\u7684\u4EA7\u7269\u548C\u5FC5\u586B\u5B57\u6BB5\uFF1B\u914D\u5408\u6D41\u8F6C\u91CC\u7684\u300C\u4EA7\u7269\u9F50\u5168\u300D\u5B88\u536B\uFF0C\u5B57\u6BB5\u6CA1\u586B\u5168\u5C31\u8FC7\u4E0D\u53BB\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactEditor, { config, setConfig }) }),
4695
4862
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconEditOutline16, { size: 16 }), title: "\u63D0\u4EA4\u89C4\u5219", hint: "\u63D0\u4EA4\u7B56\u7565\u3001\u6D88\u606F\u683C\u5F0F\u3001\u63D0\u4EA4\u68C0\u67E5\u70B9\u3001\u6587\u4EF6\u8303\u56F4\uFF0C\u4EE5\u53CA\u9AD8\u98CE\u9669\u662F\u5426\u5FC5\u987B\u9644\u9A8C\u8BC1\u8BC1\u636E\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitEditor, { config, setConfig }) }),
4863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 }), title: "\u8282\u70B9\u6302\u8F7D\uFF08skill / rule\uFF09", hint: "\u7ED9\u6BCF\u4E2A\u9636\u6BB5\u6302\u300C\u8FD9\u4E2A\u9636\u6BB5\u7528\u4EC0\u4E48 skill\u300D\u548C\u300C\u5B88\u54EA\u6761 rule\u300D\u3002\u5927\u6A21\u578B\u8D70\u5230\u8BE5\u9636\u6BB5\u65F6\uFF0Cdev_task \u53EA\u62AB\u9732\u8FD9\u9636\u6BB5\u6302\u8F7D\u7684\u5185\u5BB9\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BindingEditor, { config, setConfig, skills, rules }) }),
4864
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 }), title: "\u65B0\u5EFA skill / rule", hint: "\u6E05\u5355\u91CC\u6CA1\u6709\u5408\u9002\u7684\u5C31\u5F53\u573A\u65B0\u5EFA\uFF0C\u5199\u5230\u9879\u76EE .dsh/ \u6216\u7528\u6237 $DSH_HOME\uFF1B\u5199\u597D\u540E\u81EA\u52A8\u51FA\u73B0\u5728\u4E0A\u9762\u7684\u6302\u8F7D\u6E05\u5355\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResourceCreator, { remote, workspace, onCreated: refreshCatalogs }) }),
4696
4865
  problems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.problems, children: [
4697
4866
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.problemsTitle, children: [
4698
4867
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: "error" }),
@@ -5022,6 +5191,177 @@ function CommitEditor({ config, setConfig }) {
5022
5191
  ] })
5023
5192
  ] });
5024
5193
  }
5194
+ function BindingEditor({ config, setConfig, skills, rules }) {
5195
+ const toggleSkill = (stage, skill) => {
5196
+ update(config, setConfig, (c) => {
5197
+ const bindings = c.stage_bindings ?? {};
5198
+ const current = bindings[stage]?.skills ?? [];
5199
+ const next = current.includes(skill) ? current.filter((s) => s !== skill) : [...current, skill].sort();
5200
+ return { ...c, stage_bindings: { ...bindings, [stage]: { ...bindings[stage] ?? {}, skills: next } } };
5201
+ });
5202
+ };
5203
+ const toggleRule = (stage, rule) => {
5204
+ update(config, setConfig, (c) => {
5205
+ const bindings = c.stage_bindings ?? {};
5206
+ const current = bindings[stage]?.rules ?? [];
5207
+ const next = current.includes(rule) ? current.filter((s) => s !== rule) : [...current, rule].sort();
5208
+ return { ...c, stage_bindings: { ...bindings, [stage]: { ...bindings[stage] ?? {}, rules: next } } };
5209
+ });
5210
+ };
5211
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.section, children: config.stages.map((stage) => {
5212
+ const binding = config.stage_bindings?.[stage];
5213
+ const boundSkills = binding?.skills ?? [];
5214
+ const boundRules = binding?.rules ?? [];
5215
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: cardV, children: [
5216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.bindingLabel, children: stage }),
5217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.bindingStage, children: [
5218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "skill\uFF08\u505A\u4EC0\u4E48\uFF09" }),
5219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: skills.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sourceBadge, children: "\u6682\u65E0\u53EF\u7528 skill\uFF0C\u53EF\u5728\u4E0B\u65B9\u65B0\u5EFA" }) : skills.map((sk) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5220
+ import_dsh_client_ui_primitives.Pill,
5221
+ {
5222
+ active: boundSkills.includes(sk.name),
5223
+ onClick: () => {
5224
+ toggleSkill(stage, sk.name);
5225
+ },
5226
+ title: `${sk.description}\uFF08${sourceLabel(sk.source)}\uFF09`,
5227
+ children: sk.name
5228
+ },
5229
+ sk.name
5230
+ )) })
5231
+ ] }),
5232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.bindingStage, children: [
5233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "rule\uFF08\u5B88\u4EC0\u4E48\uFF09" }),
5234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: rules.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sourceBadge, children: "\u6682\u65E0\u53EF\u7528 rule\uFF0C\u53EF\u5728\u4E0B\u65B9\u65B0\u5EFA" }) : rules.map((rule) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5235
+ import_dsh_client_ui_primitives.Pill,
5236
+ {
5237
+ active: boundRules.includes(rule.name),
5238
+ onClick: () => {
5239
+ toggleRule(stage, rule.name);
5240
+ },
5241
+ title: `\u6765\u6E90\uFF1A${sourceLabel(rule.source)}`,
5242
+ children: rule.name
5243
+ },
5244
+ rule.name
5245
+ )) })
5246
+ ] })
5247
+ ] }, stage);
5248
+ }) });
5249
+ }
5250
+ function ResourceCreator({ remote, workspace, onCreated }) {
5251
+ const [skillName, setSkillName] = (0, import_react2.useState)("");
5252
+ const [skillDesc, setSkillDesc] = (0, import_react2.useState)("");
5253
+ const [skillWhen, setSkillWhen] = (0, import_react2.useState)("");
5254
+ const [skillBody, setSkillBody] = (0, import_react2.useState)("");
5255
+ const [ruleName, setRuleName] = (0, import_react2.useState)("");
5256
+ const [ruleBody, setRuleBody] = (0, import_react2.useState)("");
5257
+ const [level, setLevel] = (0, import_react2.useState)("project");
5258
+ const [msg, setMsg] = (0, import_react2.useState)("");
5259
+ const createSkill = async () => {
5260
+ setMsg("");
5261
+ const result = await remote.writeSkill({
5262
+ name: skillName,
5263
+ description: skillDesc,
5264
+ whenToUse: skillWhen === "" ? void 0 : skillWhen,
5265
+ content: skillBody,
5266
+ level,
5267
+ path: workspace
5268
+ });
5269
+ if (!result.ok) {
5270
+ setMsg("\u65B0\u5EFA skill \u5931\u8D25\uFF1A" + describeError(result.error));
5271
+ return;
5272
+ }
5273
+ if (!result.value.ok) {
5274
+ setMsg("\u65B0\u5EFA skill \u5931\u8D25\uFF1A" + (result.value.error ?? "\u672A\u77E5\u9519\u8BEF"));
5275
+ return;
5276
+ }
5277
+ setMsg(`\u5DF2\u65B0\u5EFA skill\u300C${result.value.name}\u300D`);
5278
+ setSkillName("");
5279
+ setSkillDesc("");
5280
+ setSkillWhen("");
5281
+ setSkillBody("");
5282
+ onCreated();
5283
+ };
5284
+ const createRule = async () => {
5285
+ setMsg("");
5286
+ const result = await remote.writeRule({ name: ruleName, content: ruleBody, level, path: workspace });
5287
+ if (!result.ok) {
5288
+ setMsg("\u65B0\u5EFA rule \u5931\u8D25\uFF1A" + describeError(result.error));
5289
+ return;
5290
+ }
5291
+ if (!result.value.ok) {
5292
+ setMsg("\u65B0\u5EFA rule \u5931\u8D25\uFF1A" + (result.value.error ?? "\u672A\u77E5\u9519\u8BEF"));
5293
+ return;
5294
+ }
5295
+ setMsg(`\u5DF2\u65B0\u5EFA rule\u300C${result.value.name}\u300D`);
5296
+ setRuleName("");
5297
+ setRuleBody("");
5298
+ onCreated();
5299
+ };
5300
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.section, children: [
5301
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
5302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "\u5199\u5165\u4F4D\u7F6E" }),
5303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.chips, children: [
5304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Pill, { active: level === "project", onClick: () => {
5305
+ setLevel("project");
5306
+ }, title: "\u5199\u5230\u5DE5\u4F5C\u533A .dsh/\uFF0C\u56E2\u961F\u5171\u4EAB", children: "\u9879\u76EE\u7EA7" }),
5307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Pill, { active: level === "user", onClick: () => {
5308
+ setLevel("user");
5309
+ }, title: "\u5199\u5230 $DSH_HOME\uFF0C\u4E2A\u4EBA\u6240\u6709\u9879\u76EE\u53EF\u7528", children: "\u7528\u6237\u7EA7" })
5310
+ ] })
5311
+ ] }),
5312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: cardV, children: [
5313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.bindingLabel, children: "\u65B0\u5EFA Skill" }),
5314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
5315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...control, flex: "1 1 140px" }, placeholder: "name\uFF08\u5982 my-skill\uFF09", value: skillName, onChange: (ev) => {
5316
+ setSkillName(ev.target.value);
5317
+ } }),
5318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...control, flex: "1 1 240px" }, placeholder: "description\uFF08\u4E00\u53E5\u8BDD\u8BF4\u660E\u5B83\u505A\u4EC0\u4E48\uFF09", value: skillDesc, onChange: (ev) => {
5319
+ setSkillDesc(ev.target.value);
5320
+ } })
5321
+ ] }),
5322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: control, placeholder: "whenToUse\uFF08\u53EF\u9009\uFF1A\u4EC0\u4E48\u60C5\u51B5\u8BE5\u7528\uFF09", value: skillWhen, onChange: (ev) => {
5323
+ setSkillWhen(ev.target.value);
5324
+ } }),
5325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { style: styles.textarea, placeholder: "skill \u6B63\u6587\uFF08\u544A\u8BC9\u5927\u6A21\u578B\u8FD9\u4E2A\u9636\u6BB5\u600E\u4E48\u5E72\uFF09", value: skillBody, onChange: (ev) => {
5326
+ setSkillBody(ev.target.value);
5327
+ } }),
5328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", size: "sm", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), disabled: skillName.trim() === "" || skillDesc.trim() === "" || skillBody.trim() === "", onClick: () => {
5329
+ void createSkill();
5330
+ }, children: "\u65B0\u5EFA skill" }) })
5331
+ ] }),
5332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: cardV, children: [
5333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.bindingLabel, children: "\u65B0\u5EFA Rule" }),
5334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: control, placeholder: "name\uFF08\u5982 my-redlines\uFF09", value: ruleName, onChange: (ev) => {
5335
+ setRuleName(ev.target.value);
5336
+ } }),
5337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { style: styles.textarea, placeholder: "rule \u6B63\u6587\uFF08\u8FD9\u6761\u89C4\u5219\u7EA6\u675F\u5927\u6A21\u578B\u9075\u5B88\u4EC0\u4E48\uFF09", value: ruleBody, onChange: (ev) => {
5338
+ setRuleBody(ev.target.value);
5339
+ } }),
5340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", size: "sm", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }), disabled: ruleName.trim() === "" || ruleBody.trim() === "", onClick: () => {
5341
+ void createRule();
5342
+ }, children: "\u65B0\u5EFA rule" }) })
5343
+ ] }),
5344
+ msg !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.status, children: msg }) : null
5345
+ ] });
5346
+ }
5347
+ function sourceLabel(source) {
5348
+ switch (source) {
5349
+ case "bundled":
5350
+ return "\u5185\u7F6E";
5351
+ case "project":
5352
+ case "project-dsh":
5353
+ case "project-agents":
5354
+ return "\u9879\u76EE";
5355
+ case "user":
5356
+ case "user-dsh":
5357
+ case "user-agents":
5358
+ return "\u7528\u6237";
5359
+ case "custom":
5360
+ return "\u81EA\u5B9A\u4E49";
5361
+ default:
5362
+ return source;
5363
+ }
5364
+ }
5025
5365
  function update(config, setConfig, fn) {
5026
5366
  setConfig(fn(config));
5027
5367
  }