@godv61/dsh-task-engine 0.26.0 → 0.27.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.
@@ -4,10 +4,20 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { join, resolve, sep } from 'node:path'
6
6
  import { registerDevTask } from './lib/dev-task.js'
7
- import { newTask } from './lib/engine.js'
8
- import { adoptRecommendation, resolveFlow } from './lib/workflows.js'
9
- import Controller from './lib/controller.js'
10
- import { registerShippedSkills } from './lib/shipped-skills.js'
7
+ import { newTask, validateWorkflow } from './lib/engine.js'
8
+ import { adoptRecommendation, resolveFlow, retainStageBindings } from './lib/workflows.js'
9
+ import Controller from './lib/controller.js'
10
+ import { registerShippedSkills } from './lib/shipped-skills.js'
11
+
12
+ test('switching to minimal drops only stages outside its graph', () => {
13
+ const requirement = { skills: [{ skill: { source: 'project', name: 'requirements' }, rules: [] }] }
14
+ const development = { skills: [{ skill: { source: 'project', name: 'implement' }, rules: [] }] }
15
+ const result = retainStageBindings('minimal', { '需求': requirement, '开发': development })
16
+ assert.deepEqual(result.dropped, ['需求'])
17
+ assert.deepEqual(result.bindings, { '开发': development })
18
+ const resolved = resolveFlow('minimal', { flow: 'minimal', stage_bindings: result.bindings })
19
+ assert.deepEqual(validateWorkflow(resolved.config), [])
20
+ })
11
21
 
12
22
  /**
13
23
  * The preset as a project would actually run it: the skeleton plus the shipped
@@ -24,7 +34,7 @@ function adoptedFlow(id, extra) {
24
34
  }
25
35
 
26
36
 
27
- function fixture(stage = '开发', extra = {}, services = {}) {
37
+ function fixture(stage = '开发', extra = {}, services = {}) {
28
38
  const cwd = resolve('test-project')
29
39
  // A project running the standard flow: the skeleton plus the adopted
30
40
  // recommendation, with one extra skill mounted on 完成 for the tests that need
@@ -36,12 +46,15 @@ function fixture(stage = '开发', extra = {}, services = {}) {
36
46
  ...adopted,
37
47
  stage_bindings: {
38
48
  ...adopted.stage_bindings,
39
- 完成: { skills: [{ skill: { source: 'bundled', name: 'software-testing' }, rules: [] }] },
49
+ 完成: { skills: [{ skill: { source: 'project', name: 'software-testing' }, rules: [] }] },
40
50
  },
41
51
  }).config
42
52
  const state = newTask({ id: 'LIVE-1', title: 'EAM regression', branch: 'test', work_size: 'standard', risk_level: 'standard', flow: { flow: 'standard', version: 3, config }, root: cwd })
43
53
  Object.assign(state, { stage, execution_version: 1, files: ['app.js'], requirement_confirmed: true, solution_confirmed: true, ...extra })
44
- const records = new Map([[join(cwd, '.dsh/task-LIVE-1.json'), JSON.stringify(state)], [join(cwd, 'app.js'), 'source']])
54
+ const records = new Map([[join(cwd, '.dsh/task-LIVE-1.json'), JSON.stringify(state)],
55
+ [join(cwd, 'app.js'), 'source'],
56
+ [join(cwd, '.dsh/skills/software-testing/SKILL.md'), 'test instructions'],
57
+ [join(cwd, '.dsh/skills/human-check/SKILL.md'), 'manual check instructions']])
45
58
  const events = [], runs = []
46
59
  const session = { id: 'test-session', header: { cwd }, snapshotEvents: () => events }
47
60
  const policy = { mode: 'workspace-write', workspaceRoot: cwd, sessionId: session.id }
@@ -73,93 +86,99 @@ function fixture(stage = '开发', extra = {}, services = {}) {
73
86
  }
74
87
  return { call: args => execute(JSON.parse(JSON.stringify({ task_id: 'LIVE-1', ...args })), exec), load, runs, policy, exec, records, cwd,
75
88
  state: () => JSON.parse(records.get(join(cwd, '.dsh/task-LIVE-1.json'))) }
76
- }
77
-
78
- test('manual skill evidence is approved by the host without a shell command', async () => {
79
- const config = resolveFlow('minimal', { flow: 'minimal', stage_bindings: {
80
- '开发': { skills: [{ skill: { source: 'project', name: 'human-check' }, rules: [], evidence: 'manual' }] },
81
- } }).config
82
- const approvals = []
83
- const f = fixture('开发', { flow: { flow: 'minimal', version: 3, config } }, {
84
- approval: { request: async request => { approvals.push(request); return 'allowed-once' } },
85
- })
86
- f.load('human-check')
87
- await f.call({ operation: 'skill_result', skill_name: 'human-check', evidence: ['checked by owner'] })
88
- assert.equal(f.runs.length, 0)
89
- assert.equal(approvals.length, 1)
90
- assert.equal(f.state().skill_results['开发']['human-check'].approved, true)
91
- assert.deepEqual(JSON.parse(await f.call({ operation: 'status' })).skill_blockers, [])
92
- const denied = fixture('开发', { flow: { flow: 'minimal', version: 3, config } }, {
93
- approval: { request: async () => 'rejected' },
94
- })
95
- denied.load('human-check')
96
- await assert.rejects(denied.call({ operation: 'skill_result', skill_name: 'human-check', evidence: ['claimed approval'] }), /not approved/)
97
- assert.equal(denied.state().skill_results?.['开发']?.['human-check'], undefined)
98
- })
99
-
100
- test('completion rejects unresolved terminal skills and rules', async () => {
101
- const config = resolveFlow('minimal', { flow: 'minimal', stage_bindings: {
102
- '交付': { skills: [{ skill: { source: 'project', name: 'must-run' }, rules: [{ source: 'project', name: 'missing' }], evidence: 'command' }] },
103
- } }).config
104
- const f = fixture('交付', {
105
- flow: { flow: 'minimal', version: 3, config },
106
- items: [{ id: 'A', title: 'small', status: 'done', review: { spec: { outcome: 'pass' }, quality: { outcome: 'pass' } } }],
107
- commits: [{ label: 'TASK', hash: 'abcdef1' }],
108
- })
109
- const status = JSON.parse(await f.call({ operation: 'status' }))
110
- assert.ok(status.skill_blockers.length > 0)
111
- assert.ok(status.missing_rules.length > 0)
112
- await assert.rejects(f.call({ operation: 'complete' }), /cannot complete.*must-run|cannot complete.*missing/)
113
- assert.equal(f.state().completed, undefined)
114
- })
115
-
116
- test('same-named skill and rule freeze separately and disclose the rule body', async () => {
117
- const f = fixture()
118
- f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
119
- f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal', stage_bindings: {
120
- '开发': { skills: [{ skill: { source: 'project', name: 'custom' }, rules: [{ source: 'project', name: 'custom' }], evidence: 'none' }] },
121
- } }))
122
- f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'SKILL BODY')
123
- f.records.set(join(f.cwd, '.dsh/rules/custom.md'), 'RULE BODY')
124
- await f.call({ operation: 'create', title: 'collision', branch: 'main', files: ['app.js'] })
125
- const resources = f.state().flow.resources.filter(resource => resource.ref.name === 'custom')
126
- assert.deepEqual(resources.map(resource => resource.kind).sort(), ['rule', 'skill'])
127
- assert.equal(JSON.parse(await f.call({ operation: 'status' })).bindings.rules[0].content, 'RULE BODY')
128
- f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'CHANGED SKILL')
129
- assert.equal(JSON.parse(await f.call({ operation: 'status' })).bindings.skill_contents[0].content, 'SKILL BODY')
130
- })
131
-
132
- test('task creation applies skill_profiles rules even when legacy inline bindings are empty', async () => {
133
- const f = fixture()
134
- const skill = { source: 'project', name: 'custom' }
135
- const rule = { source: 'project', name: 'my-rule' }
136
- f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
137
- f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal',
138
- stage_bindings: {
139
- '开发': { skills: [{ skill, rules: [], evidence: 'none' }] },
140
- '交付': { skills: [{ skill, rules: [], evidence: 'none' }] },
141
- },
142
- skill_profiles: { 'project:custom': { rules: [rule], evidence: 'none' } },
143
- }))
144
- f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'SKILL BODY')
145
- await assert.rejects(f.call({ operation: 'create', title: 'profile regression', branch: 'main', files: ['app.js'] }), /rule:project:my-rule/)
146
- f.records.set(join(f.cwd, '.dsh/rules/my-rule.md'), 'RULE BODY')
147
- await f.call({ operation: 'create', title: 'profile regression', branch: 'main', files: ['app.js'] })
148
- assert.deepEqual(f.state().flow.config.stage_bindings['开发'].skills[0].rules, [rule])
149
- assert.deepEqual(f.state().flow.config.stage_bindings['交付'].skills[0].rules, [rule])
150
- assert.equal(f.state().flow.resources.find(resource => resource.kind === 'rule')?.content, 'RULE BODY')
151
- assert.equal(JSON.parse(await f.call({ operation: 'status' })).bindings.rules[0].content, 'RULE BODY')
152
- })
153
-
154
- test('task creation rejects an unavailable skill or rule before writing a record', async () => {
155
- const f = fixture()
156
- f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
157
- f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal', stage_bindings: {
158
- '开发': { skills: [{ skill: { source: 'project', name: 'missing-skill' }, rules: [] }] },
159
- } }))
160
- await assert.rejects(f.call({ operation: 'create', title: 'missing', branch: 'main' }), /unreadable bound resources/)
161
- assert.equal(f.records.has(join(f.cwd, '.dsh/task-LIVE-1.json')), false)
162
- })
89
+ }
90
+
91
+ test('manual skill evidence is approved by the host without a shell command', async () => {
92
+ const config = resolveFlow('minimal', { flow: 'minimal', stage_bindings: {
93
+ '开发': { skills: [{ skill: { source: 'project', name: 'human-check' }, rules: [], evidence: 'manual' }] },
94
+ } }).config
95
+ const approvals = []
96
+ const f = fixture('开发', { flow: { flow: 'minimal', version: 3, config } }, {
97
+ approval: { request: async request => { approvals.push(request); return 'allowed-once' } },
98
+ })
99
+ f.load('human-check')
100
+ await f.call({ operation: 'skill_result', skill_name: 'human-check', evidence: ['checked by owner'] })
101
+ assert.equal(f.runs.length, 0)
102
+ assert.equal(approvals.length, 1)
103
+ assert.equal(f.state().skill_results['开发']['human-check'].approved, true)
104
+ assert.deepEqual(JSON.parse(await f.call({ operation: 'status' })).skill_blockers, [])
105
+ const denied = fixture('开发', { flow: { flow: 'minimal', version: 3, config } }, {
106
+ approval: { request: async () => 'rejected' },
107
+ })
108
+ denied.load('human-check')
109
+ await assert.rejects(denied.call({ operation: 'skill_result', skill_name: 'human-check', evidence: ['claimed approval'] }), /not approved/)
110
+ assert.equal(denied.state().skill_results?.['开发']?.['human-check'], undefined)
111
+ })
112
+
113
+ test('completion rejects unresolved terminal skills and rules', async () => {
114
+ const config = resolveFlow('minimal', { flow: 'minimal', stage_bindings: {
115
+ '交付': { skills: [{ skill: { source: 'project', name: 'must-run' }, rules: [{ source: 'project', name: 'missing' }], evidence: 'command' }] },
116
+ } }).config
117
+ const f = fixture('交付', {
118
+ flow: { flow: 'minimal', version: 3, config },
119
+ items: [{ id: 'A', title: 'small', status: 'done', review: { spec: { outcome: 'pass' }, quality: { outcome: 'pass' } } }],
120
+ commits: [{ label: 'TASK', hash: 'abcdef1' }],
121
+ })
122
+ const status = JSON.parse(await f.call({ operation: 'status' }))
123
+ assert.ok(status.skill_blockers.length > 0)
124
+ assert.ok(status.missing_rules.length > 0)
125
+ await assert.rejects(f.call({ operation: 'complete' }), /cannot complete.*must-run|cannot complete.*missing/)
126
+ assert.equal(f.state().completed, undefined)
127
+ })
128
+
129
+ test('same-named skill and rule freeze separately and disclose the rule body', async () => {
130
+ const f = fixture()
131
+ f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
132
+ f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal', stage_bindings: {
133
+ '开发': { skills: [{ skill: { source: 'project', name: 'custom' }, rules: [{ source: 'project', name: 'custom' }], evidence: 'none' }] },
134
+ } }))
135
+ f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'SKILL BODY')
136
+ f.records.set(join(f.cwd, '.dsh/rules/custom.md'), 'RULE BODY')
137
+ await f.call({ operation: 'create', title: 'collision', branch: 'main', files: ['app.js'] })
138
+ const resources = f.state().flow.resources.filter(resource => resource.ref.name === 'custom')
139
+ assert.deepEqual(resources.map(resource => resource.kind).sort(), ['rule', 'skill'])
140
+ assert.equal(JSON.parse(await f.call({ operation: 'status' })).bindings.rules[0].content, 'RULE BODY')
141
+ f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'CHANGED SKILL')
142
+ const status = JSON.parse(await f.call({ operation: 'status' }))
143
+ assert.equal(status.bindings.skill_contents[0].content, 'CHANGED SKILL')
144
+ assert.ok(status.changed_source_skills.some(ref => ref.includes('project:custom')))
145
+ f.records.delete(join(f.cwd, '.dsh/skills/custom/SKILL.md'))
146
+ const missing = JSON.parse(await f.call({ operation: 'status' }))
147
+ assert.ok(missing.missing_skills.includes('project:custom'))
148
+ assert.equal(missing.bindings.skill_contents.length, 0)
149
+ })
150
+
151
+ test('task creation applies skill_profiles rules even when legacy inline bindings are empty', async () => {
152
+ const f = fixture()
153
+ const skill = { source: 'project', name: 'custom' }
154
+ const rule = { source: 'project', name: 'my-rule' }
155
+ f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
156
+ f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal',
157
+ stage_bindings: {
158
+ '开发': { skills: [{ skill, rules: [], evidence: 'none' }] },
159
+ '交付': { skills: [{ skill, rules: [], evidence: 'none' }] },
160
+ },
161
+ skill_profiles: { 'project:custom': { rules: [rule], evidence: 'none' } },
162
+ }))
163
+ f.records.set(join(f.cwd, '.dsh/skills/custom/SKILL.md'), 'SKILL BODY')
164
+ await assert.rejects(f.call({ operation: 'create', title: 'profile regression', branch: 'main', files: ['app.js'] }), /rule:project:my-rule/)
165
+ f.records.set(join(f.cwd, '.dsh/rules/my-rule.md'), 'RULE BODY')
166
+ await f.call({ operation: 'create', title: 'profile regression', branch: 'main', files: ['app.js'] })
167
+ assert.deepEqual(f.state().flow.config.stage_bindings['开发'].skills[0].rules, [rule])
168
+ assert.deepEqual(f.state().flow.config.stage_bindings['交付'].skills[0].rules, [rule])
169
+ assert.equal(f.state().flow.resources.find(resource => resource.kind === 'rule')?.content, 'RULE BODY')
170
+ assert.equal(JSON.parse(await f.call({ operation: 'status' })).bindings.rules[0].content, 'RULE BODY')
171
+ })
172
+
173
+ test('task creation rejects an unavailable skill or rule before writing a record', async () => {
174
+ const f = fixture()
175
+ f.records.delete(join(f.cwd, '.dsh/task-LIVE-1.json'))
176
+ f.records.set(join(f.cwd, '.dsh/eng.json'), JSON.stringify({ flow: 'minimal', stage_bindings: {
177
+ '开发': { skills: [{ skill: { source: 'project', name: 'missing-skill' }, rules: [] }] },
178
+ } }))
179
+ await assert.rejects(f.call({ operation: 'create', title: 'missing', branch: 'main' }), /unreadable bound resources/)
180
+ assert.equal(f.records.has(join(f.cwd, '.dsh/task-LIVE-1.json')), false)
181
+ })
163
182
 
164
183
  test('审查通过后更新状态并激活下一项,保留已完成项的审计', async () => {
165
184
  const f = fixture()
package/README.md CHANGED
@@ -27,7 +27,7 @@
27
27
  让 AI 修改代码时,你可以先明确需求,再检查方案、推进实施、验证结果,最后审核提交。DSH Task Engine 把这些步骤组织成可检查、可追踪的任务流程,适合在本机使用 DeepSeek Harness 开发个人项目。
28
28
 
29
29
  - **知道下一步做什么**:按所选流程推进,当前阶段需要的条件和产物清楚可查。
30
- - **复用自己的工作方法**:把技能和规则安装到项目或个人目录,将规则配置在技能下,再把技能挂到节点上;同一技能在任何节点都使用同一套约束。
30
+ - **复用自己的工作方法**:把技能和规则安装到项目或个人目录,将规则配置在技能下,再把技能挂到节点上;同一技能在任何节点都使用同一套约束。
31
31
  - **找得到过程记录**:任务台账集中查看阶段、实施项、验证与审核状态。
32
32
 
33
33
  ## 快速开始
@@ -53,22 +53,22 @@ dsh plugin --profile web add @godv61/dsh-task-engine
53
53
  | **项目初始化** | 查看或编辑 `AGENTS.md`,让 AI 生成项目说明,预览后保存。 |
54
54
  | **流程配置** | 选择流程骨架,给节点挂技能,并在技能上配置它遵循的规则。需要现成起点时可「采用推荐配置」。 |
55
55
  | **任务台账** | 查看实施、验证和审核记录,按关键词、阶段或风险筛选。 |
56
- | **技能** | 安装、编辑技能,并集中维护每个技能唯一的规则列表。 |
56
+ | **技能** | 安装、编辑技能,并集中维护每个技能唯一的规则列表。 |
57
57
  | **规则** | 选择 Markdown 文件安装规则,维护项目或个人开发约定。 |
58
58
 
59
- 内置技能和规则只读;项目与个人资源可编辑。安装、删除都会明确展示目标位置。
59
+ 内置技能和规则是只读样本,可用“以此为模板新建”复制正文到项目或个人目录后编辑;新资源的规则和阶段绑定由你单独配置。安装、删除都会明确展示目标位置。
60
60
 
61
- **流程与工作方法是两回事。** 流程只决定工作怎么流转(阶段顺序与门禁),预设不自带任何技能或提交格式。要用什么技能、遵守哪些规则、提交信息写什么,都由你配置——「采用推荐配置」只是把一份现成的起点**写入**你的配置,之后完全归你:删掉的不再补回,升级也不覆盖。规则挂在技能下,所以打开一个技能看到的就是它完整的规则列表,挂到任何节点都一样。
61
+ **流程与工作方法是两回事。** 流程只决定工作怎么流转(阶段顺序与门禁),预设不自带任何技能或提交格式。要用什么技能、遵守哪些规则、提交信息写什么,都由你配置。「采用推荐配置」并保存时,内置样本各复制一份到项目目录,配置改为引用项目资源;之后可编辑,升级不覆盖。同一条规则仍可由多个技能共享。用户级技能及其规则配置由所有项目和会话共用。任务的流程与资源引用在创建时确定;Skill/Rule 正文在每次交互读取最新版本,创建时副本仅用于审计。[配置细节](docs/configuration.md)
62
62
 
63
63
  ## 选择适合这次任务的流程
64
64
 
65
65
  | 预设 | 阶段顺序 | 适用场景 |
66
66
  | :--- | :--- | :--- |
67
- | **完整研发** `standard` | 需求评审 → 设计 → 开发 → 交付 → 代码审核 → 完成 | 需要需求、方案和交付检查的完整开发任务。 |
68
- | **日常迭代** `agile` | 需求 → 开发 → 交付 → 审查 | 目标明确的日常开发任务。 |
69
- | **快速修改** `minimal` | 开发 → 交付 | 已明确做法的小改动。 |
67
+ | **完整研发** `standard` | 需求评审 → 设计 → 开发 → 交付 → 代码审核 → 完成 | 需要需求、方案和交付检查的完整开发任务。 |
68
+ | **日常迭代** `agile` | 需求 → 开发 → 交付 → 审查 | 目标明确的日常开发任务。 |
69
+ | **快速修改** `minimal` | 开发 → 交付 | 已明确做法的小改动。 |
70
70
 
71
- 阶段顺序和检查条件由预设固定;技能与规则由用户配置。选择流程不会强制加入内置技能;需要现成起点时可显式采用推荐配置。**可视化自定义流程不在计划内**,详见[功能规划](docs/roadmap.md)。
71
+ 阶段顺序和检查条件由预设固定;技能与规则由用户配置。选择流程不会强制加入内置技能;需要现成起点时可显式采用推荐配置。**可视化自定义流程不在计划内**,详见[功能规划](docs/roadmap.md)。
72
72
 
73
73
  ## 把自己的技能和规则带进来
74
74
 
@@ -98,7 +98,7 @@ dsh plugin --profile web add @godv61/dsh-task-engine
98
98
 
99
99
  ## 能力说明
100
100
 
101
- `dev_task` 按配置检查阶段、产物和提交条件。新任务的验证要求真实命令回执;技能可按自身配置要求命令、产物、审核、人工批准或无需额外证据,执行义务可在任务状态中查看。任务保存创建时的流程与资源快照,之后修改项目配置不会直接改变进行中的任务。
101
+ `dev_task` 按配置检查阶段、产物和提交条件。新任务的验证要求真实命令回执;技能可按自身配置要求命令、产物、审核、人工批准或无需额外证据,执行义务可在任务状态中查看。任务保存创建时的流程与资源引用;之后修改流程配置不会改变进行中任务的状态机,编辑所引用资源的正文会从下一次交互生效。
102
102
 
103
103
  审核结论、实施项完成情况和测试覆盖面仍需要你判断。本地提交钩子提供即时检查,不能替代人工审核或项目自己的 CI。详细说明见[常见问题](docs/faq.md)。
104
104
 
package/defaults/eng.json CHANGED
@@ -1,97 +1,97 @@
1
- {
2
- "flow": "standard",
3
- "stage_bindings": {
4
- "需求评审": {
5
- "skill_refs": [
6
- {
7
- "source": "bundled",
8
- "name": "requirement-analysis"
9
- }
10
- ]
11
- },
12
- "设计": {
13
- "skill_refs": [
14
- {
15
- "source": "bundled",
16
- "name": "solution-design"
17
- }
18
- ]
19
- },
20
- "开发": {
21
- "skill_refs": [
22
- {
23
- "source": "bundled",
24
- "name": "code-implement"
25
- }
26
- ]
27
- },
28
- "交付": {
29
- "skill_refs": [
30
- {
31
- "source": "bundled",
32
- "name": "code-verify"
33
- }
34
- ]
35
- },
36
- "代码审核": {
37
- "skill_refs": [
38
- {
39
- "source": "bundled",
40
- "name": "code-review"
41
- },
42
- {
43
- "source": "bundled",
44
- "name": "code-commit"
45
- }
46
- ]
47
- }
48
- },
49
- "skill_profiles": {
50
- "bundled:requirement-analysis": {
51
- "rules": [
52
- {
53
- "source": "bundled",
54
- "name": "security-redlines"
55
- }
56
- ]
57
- },
58
- "bundled:solution-design": {
59
- "rules": []
60
- },
61
- "bundled:code-implement": {
62
- "rules": [
63
- {
64
- "source": "bundled",
65
- "name": "coding-conventions"
66
- },
67
- {
68
- "source": "bundled",
69
- "name": "security-redlines"
70
- }
71
- ]
72
- },
73
- "bundled:code-verify": {
74
- "rules": []
75
- },
76
- "bundled:code-review": {
77
- "rules": [
78
- {
79
- "source": "bundled",
80
- "name": "coding-conventions"
81
- },
82
- {
83
- "source": "bundled",
84
- "name": "security-redlines"
85
- }
86
- ]
87
- },
88
- "bundled:code-commit": {
89
- "rules": [
90
- {
91
- "source": "bundled",
92
- "name": "commit-conventions"
93
- }
94
- ]
95
- }
96
- }
97
- }
1
+ {
2
+ "flow": "standard",
3
+ "stage_bindings": {
4
+ "需求评审": {
5
+ "skill_refs": [
6
+ {
7
+ "source": "bundled",
8
+ "name": "requirement-analysis"
9
+ }
10
+ ]
11
+ },
12
+ "设计": {
13
+ "skill_refs": [
14
+ {
15
+ "source": "bundled",
16
+ "name": "solution-design"
17
+ }
18
+ ]
19
+ },
20
+ "开发": {
21
+ "skill_refs": [
22
+ {
23
+ "source": "bundled",
24
+ "name": "code-implement"
25
+ }
26
+ ]
27
+ },
28
+ "交付": {
29
+ "skill_refs": [
30
+ {
31
+ "source": "bundled",
32
+ "name": "code-verify"
33
+ }
34
+ ]
35
+ },
36
+ "代码审核": {
37
+ "skill_refs": [
38
+ {
39
+ "source": "bundled",
40
+ "name": "code-review"
41
+ },
42
+ {
43
+ "source": "bundled",
44
+ "name": "code-commit"
45
+ }
46
+ ]
47
+ }
48
+ },
49
+ "skill_profiles": {
50
+ "bundled:requirement-analysis": {
51
+ "rules": [
52
+ {
53
+ "source": "bundled",
54
+ "name": "security-redlines"
55
+ }
56
+ ]
57
+ },
58
+ "bundled:solution-design": {
59
+ "rules": []
60
+ },
61
+ "bundled:code-implement": {
62
+ "rules": [
63
+ {
64
+ "source": "bundled",
65
+ "name": "coding-conventions"
66
+ },
67
+ {
68
+ "source": "bundled",
69
+ "name": "security-redlines"
70
+ }
71
+ ]
72
+ },
73
+ "bundled:code-verify": {
74
+ "rules": []
75
+ },
76
+ "bundled:code-review": {
77
+ "rules": [
78
+ {
79
+ "source": "bundled",
80
+ "name": "coding-conventions"
81
+ },
82
+ {
83
+ "source": "bundled",
84
+ "name": "security-redlines"
85
+ }
86
+ ]
87
+ },
88
+ "bundled:code-commit": {
89
+ "rules": [
90
+ {
91
+ "source": "bundled",
92
+ "name": "commit-conventions"
93
+ }
94
+ ]
95
+ }
96
+ }
97
+ }
package/docs/CHANGELOG.md CHANGED
@@ -2,19 +2,32 @@
2
2
 
3
3
  [← 文档导航](README.md)
4
4
 
5
- 按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
6
-
7
- ## 0.26.0
8
-
9
- - 修复旧内联阶段绑定与新 `skill_profiles` 同时存在时,技能档案规则未展开、同一技能跨节点误报冲突的问题;明确的技能档案现在覆盖旧内联副本,并在任务创建时冻结实际生效的规则。
10
- - 技能规则改为项目级 `skill_profiles`,节点在 `.dsh/eng.json` 中只保存 `skill_refs`。旧内联绑定继续读取;没有明确技能档案时,同一技能在不同节点的规则或证据冲突会明确报错,保存时转换为单一配置。
11
- - “技能”页可集中编辑技能规则和完成凭证。流程页只选择技能,并可查看当前节点的有效规则;修改同一技能的配置会同步其所有节点引用。
12
- - 任务快照以资源类型、来源、名称定位技能和规则,避免同名资源正文串用;任务创建时拒绝缺失的绑定资源,并在运行时披露冻结的技能与规则正文。
13
- - `complete` 检查终态技能、证据与缺失规则,完成后禁止继续修改任务(可通过 `revise` 返工)。`status` 增加完成状态和完成阻塞原因。
14
- - `manual` 技能证据通过宿主人工审批记录,不再强制执行 shell;`artifact` 证据要求该阶段有产物定义且必填字段完整。
15
- - `commit_required: false` 同时取消离开提交检查点的强制提交要求。返工会使受影响阶段的技能结果失效。
16
- - 增加配置保存读回、同名资源、人工审批、终态阻塞及返工证据回归测试。
17
- - 工作台改用更宽的自适应布局;流程页用双栏穿梭框绑定技能,技能卡片和已绑定技能都可打开右侧规则配置抽屉,窄屏自动改为纵向布局。
5
+ 按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
6
+
7
+ ## 0.27.0
8
+
9
+ - 采用推荐配置时将所引用的内置技能和规则各复制一份到项目目录,并改写为项目引用;共享规则仍只有一份,重复采用保留用户已编辑的项目文件。旧项目可单独迁移内置引用,打开或普通保存时不自动改写。
10
+ - 用户级技能的规则配置移到用户目录,跨项目和会话共用;不允许用户级技能依赖项目级规则。任务保留创建时流程和资源引用,Skill/Rule 正文从下一次交互读取最新版本;创建时正文仅作为审计基线。缺失的源资源阻止继续流转。
11
+ - 流程切换时清理不属于新流程的阶段绑定,修复残留阶段造成的校验报错。技能规则配置改为挤压主内容的侧栏,不再覆盖蒙版。修复浏览器与宿主间的配置编解码,保存时保留资源引用、规则档案、提交和产物设置。
12
+ - 正文实时读取是有意的行为变更:任务创建前已经记录的验证或人工确认不会因之后修改 Skill/Rule 自动作废。`status` 会报告资源变更;变更约束后请重新核查相关证据。
13
+
14
+ ## 0.26.1
15
+
16
+ - 将会话预设强制加载的 `eng-delivery` 收敛为纯流程编排:只按任务状态和冻结配置加载技能、遵守规则及检查证据,不再暗示固定阶段技能映射或所有技能都需要命令回执;同步修正预设人设和工具参数说明。
17
+ - 内置业务技能与规则明确作为只读样本;工作台可从样本预填新资源,改名后保存到项目或个人目录,再单独配置完成凭证、规则和阶段绑定。复制与保存不会自动启用样本。
18
+ - 修正内置实现技能对未绑定 `coding-conventions` 的强制引用,以及旧规则迁移提示中的配置位置。
19
+
20
+ ## 0.26.0
21
+
22
+ - 修复旧内联阶段绑定与新 `skill_profiles` 同时存在时,技能档案规则未展开、同一技能跨节点误报冲突的问题;明确的技能档案现在覆盖旧内联副本,并在任务创建时冻结实际生效的规则。
23
+ - 技能规则改为项目级 `skill_profiles`,节点在 `.dsh/eng.json` 中只保存 `skill_refs`。旧内联绑定继续读取;没有明确技能档案时,同一技能在不同节点的规则或证据冲突会明确报错,保存时转换为单一配置。
24
+ - “技能”页可集中编辑技能规则和完成凭证。流程页只选择技能,并可查看当前节点的有效规则;修改同一技能的配置会同步其所有节点引用。
25
+ - 任务快照以资源类型、来源、名称定位技能和规则,避免同名资源正文串用;任务创建时拒绝缺失的绑定资源,并在运行时披露冻结的技能与规则正文。
26
+ - `complete` 检查终态技能、证据与缺失规则,完成后禁止继续修改任务(可通过 `revise` 返工)。`status` 增加完成状态和完成阻塞原因。
27
+ - `manual` 技能证据通过宿主人工审批记录,不再强制执行 shell;`artifact` 证据要求该阶段有产物定义且必填字段完整。
28
+ - `commit_required: false` 同时取消离开提交检查点的强制提交要求。返工会使受影响阶段的技能结果失效。
29
+ - 增加配置保存读回、同名资源、人工审批、终态阻塞及返工证据回归测试。
30
+ - 工作台改用更宽的自适应布局;流程页用双栏穿梭框绑定技能,技能卡片和已绑定技能都可打开右侧规则配置抽屉,窄屏自动改为纵向布局。
18
31
 
19
32
  ## 0.25.0
20
33
 
@@ -24,35 +24,39 @@
24
24
  }
25
25
  ```
26
26
 
27
- `stage_bindings` 的键是当前流程中的阶段名称,`skill_refs` 只保存技能引用。`skill_profiles` 为每个技能保存唯一的规则列表和证据类型。
27
+ `stage_bindings` 的键是当前流程中的阶段名称,`skill_refs` 只保存技能引用。项目级和内置技能的 `skill_profiles` 在 `.dsh/eng.json` 中保存唯一的规则列表和证据类型;用户级技能的规则配置保存在 `$DSH_HOME/skills/<技能名>/profile.json`,所有项目和会话共用。用户级技能只能关联用户级或内置规则,避免引用某个项目独有的规则。
28
28
 
29
29
  ```json
30
30
  {
31
31
  "flow": "standard",
32
32
  "stage_bindings": {
33
33
  "开发": {
34
- "skill_refs": [{ "source": "bundled", "name": "code-implement" }]
35
- }
36
- },
37
- "skill_profiles": {
38
- "bundled:code-implement": {
39
- "rules": [{ "source": "bundled", "name": "coding-conventions" },
40
- { "source": "project", "name": "api-contract" }],
41
- "evidence": "none"
42
- }
43
- }
44
- }
34
+ "skill_refs": [{ "source": "bundled", "name": "code-implement" }]
35
+ }
36
+ },
37
+ "skill_profiles": {
38
+ "bundled:code-implement": {
39
+ "rules": [{ "source": "bundled", "name": "coding-conventions" },
40
+ { "source": "project", "name": "api-contract" }],
41
+ "evidence": "none"
42
+ }
43
+ }
44
+ }
45
45
  ```
46
46
 
47
- **规则挂载在技能下,节点不提供规则追加、禁用或覆盖。** 在工作台“技能”页或流程页的技能规则侧栏配置规则;同一技能挂到任何节点,都是同一套规则。需要不同规则组合时,复制成另一个独立技能。旧版把规则内联在各节点技能绑定的配置仍可读取;保存后会转为上述格式。若旧内联绑定与 `skill_profiles` 同时存在,明确配置的技能档案优先,所有节点使用它的规则和凭证;若没有技能档案而同一旧技能在不同节点的规则不同,系统会要求先复制为独立技能,不会任意选一套覆盖另一套。
47
+ **规则挂载在技能下,节点不提供规则追加、禁用或覆盖。** 在工作台“技能”页或流程页的技能规则侧栏配置规则;同一技能挂到任何节点,都是同一套规则。需要不同规则组合时,复制成另一个独立技能。旧版把规则内联在各节点技能绑定的配置仍可读取;保存后会转为上述格式。若旧内联绑定与 `skill_profiles` 同时存在,明确配置的技能档案优先,所有节点使用它的规则和凭证;若没有技能档案而同一旧技能在不同节点的规则不同,系统会要求先复制为独立技能,不会任意选一套覆盖另一套。
48
48
 
49
49
  规则引用带来源(`bundled:` / `project:` / `user:`),因此同名资源不会被混淆。同一份规则可被多个技能引用,不需要复制正文;编辑共享规则时界面会显示受影响的技能。
50
50
 
51
51
  [默认配置示例](../defaults/eng.json)列出了标准流程采用推荐配置后的样子。
52
52
 
53
- **预设不带任何绑定。** 一个只写了 `flow` 的配置就是字面意思:该流程的节点没有绑定,阶段仍按流程骨架流转。要一份现成的工程起点,在工作台点「采用推荐配置」,它会把技能、提交信息格式与产物字段**写入**你的配置一次;此后这些值就是你的普通配置,删掉不会补回,升级也不覆盖。
53
+ **预设不带任何绑定。** 一个只写了 `flow` 的配置就是字面意思:该流程的节点没有绑定,阶段仍按流程骨架流转。要一份现成的工程起点,在工作台点「采用推荐配置」并保存。系统会把所引用的内置技能和规则各复制一份到项目 `.dsh/skills/` 与 `.dsh/rules/`,把配置引用改为 `project:`,并写入提交信息格式与产物字段。共享规则只复制一份,多个技能继续引用同一份。之后这些文件与配置均归项目所有,可编辑,升级不覆盖;重复采用也不会覆盖已经修改的项目副本。
54
+
55
+ 以前已采用的项目如仍使用 `bundled:` 引用,打开或保存时不会擅自迁移。它们继续引用内置样本,新任务会读取当前插件版本的正文;若要固定为项目资源,可在流程页点「把当前内置引用迁移到项目」并保存,保留其他现有配置。
56
+
57
+ 内置业务技能和规则作为只读样本保留。在“技能”或“规则”页选择“以此为模板新建”,可预填一份可编辑的项目或个人资源;复制只带正文,不自动复制该技能的规则档案,也不挂到任何阶段。保存后请单独配置完成凭证、规则与阶段引用。
54
58
 
55
- 进入阶段后,`dev_task` 披露该阶段技能和规则的冻结正文。新任务离开阶段前会检查 Harness 的 skill 工具成功加载记录;技能可声明证据类型(`command` / `artifact` / `review` / `manual` / `none`)。`manual` 通过宿主人工审批记录,不要求执行命令;`artifact` 需要该阶段有产物定义且必填字段完整。未声明时沿用命令回执。`status.skill_obligations` 中的 `command_receipts_required` 列出需要命令回执的技能。
59
+ 进入阶段后,`dev_task` 按稳定的资源引用读取并披露该阶段技能和规则的最新正文。新任务离开阶段前会检查 Harness 的 skill 工具成功加载记录;技能可声明证据类型(`command` / `artifact` / `review` / `manual` / `none`)。`manual` 通过宿主人工审批记录,不要求执行命令;`artifact` 需要该阶段有产物定义且必填字段完整。未声明时沿用命令回执。`status.skill_obligations` 中的 `command_receipts_required` 列出需要命令回执的技能。
56
60
 
57
61
  挂在终态(例如"完成")的技能在进入终态前执行。测试技能通常建议挂在"交付";现有"完成"绑定也会在审核阶段执行后才放行。标准流程 v2 在审核通过后提交,并核对真实 Git HEAD。修改文件或声明范围后,旧验证回执失效。
58
62
 
@@ -72,6 +76,6 @@
72
76
 
73
77
  新任务记录创建时的完整流程快照,后续按该快照执行。修改项目配置影响之后创建的任务,不会自动迁移进行中的任务。
74
78
 
75
- 任务开始时会冻结所引用的技能和规则正文。资源类型、来源和名称共同构成身份,因此同名技能和规则不会混淆。缺失的必需资源会阻止新任务创建;已完成任务若需返工,使用 `revise` 显式重开。
79
+ 任务开始时会保存所引用技能和规则正文的**审计副本**,但运行时按原来的来源与名称重新读取:项目级资源可随时编辑,同项目的其他会话及进行中的任务在下一次交互读取最新正文;用户级资源在所有项目和会话中同理。状态机会继续使用任务创建时的流程快照。正文变化会在 `status` 中提示;删除仍被绑定的资源会阻止阶段流转。已记录的验证或人工确认不会因为正文编辑自动失效,修改约束后应重新检查这些结果。资源类型、来源和名称共同构成身份,因此同名技能和规则不会混淆。缺失的必需资源会阻止新任务创建;已完成任务若需返工,使用 `revise` 显式重开。
76
80
 
77
81
  下一步:[资源安装](resource-install.md) · [常见问题](faq.md)
@@ -25,7 +25,7 @@ CI 在 Windows 的 Node 22/24 上运行。每次功能修改选择相关验证
25
25
  | 位置 | 职责 |
26
26
  | :--- | :--- |
27
27
  | [src/engine.ts](../src/engine.ts) | 状态机、阶段条件和提交规则检查。 |
28
- | [src/workflows.ts](../src/workflows.ts) | 三套流程骨架及可选的推荐配置。 |
28
+ | [src/workflows.ts](../src/workflows.ts) | 三套流程骨架及可选的推荐配置。 |
29
29
  | [src/dev-task.ts](../src/dev-task.ts) | 模型使用的 dev_task 工具与任务文件操作。 |
30
30
  | [src/controller.ts](../src/controller.ts) | 工作台读取配置、任务和资源的 Remote 控制器。 |
31
31
  | [src/resource-import.ts](../src/resource-import.ts) | 导入校验、安装预览、独占写入与失败清理。 |