@godv61/dsh-task-engine 0.23.9 → 0.25.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/.acceptance.mjs +151 -0
- package/.assessment-batch1.mjs +423 -0
- package/.e2e-presets.mjs +230 -0
- package/.enforce-test.mjs +139 -0
- package/.evidence-test.mjs +119 -0
- package/.filter-test.mjs +93 -0
- package/.freeze-test.mjs +199 -0
- package/.hook-consistency.mjs +60 -0
- package/.hook-test.mjs +8 -3
- package/.p0-test.mjs +141 -50
- package/.revision-test.mjs +114 -0
- package/.roundtrip-test.mjs +63 -0
- package/.workflow-test.mjs +94 -65
- package/README.md +4 -2
- package/defaults/eng.json +45 -6
- package/docs/BRIEF-FOR-REVIEW.md +163 -0
- package/docs/CHANGELOG.md +96 -0
- package/docs/configuration.md +31 -6
- package/hooks/commit-msg +162 -65
- package/lib/client.js +638 -431
- package/lib/client.js.map +3 -3
- package/lib/controller.d.ts +18 -1
- package/lib/controller.js +64 -5
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +363 -47
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +368 -6
- package/lib/engine.js +328 -13
- package/lib/engine.js.map +1 -1
- package/lib/hook.js +14 -4
- package/lib/hook.js.map +1 -1
- package/lib/skill-audit.d.ts +14 -3
- package/lib/skill-audit.js +71 -4
- package/lib/skill-audit.js.map +1 -1
- package/lib/workflows.d.ts +75 -12
- package/lib/workflows.js +213 -77
- package/lib/workflows.js.map +1 -1
- package/package.json +14 -4
- package/scripts/verify-package.mjs +1 -1
package/.workflow-test.mjs
CHANGED
|
@@ -5,14 +5,41 @@ import { tmpdir } from 'node:os'
|
|
|
5
5
|
import { join, resolve, sep } from 'node:path'
|
|
6
6
|
import { registerDevTask } from './lib/dev-task.js'
|
|
7
7
|
import { newTask } from './lib/engine.js'
|
|
8
|
-
import { resolveFlow } from './lib/workflows.js'
|
|
8
|
+
import { adoptRecommendation, resolveFlow } from './lib/workflows.js'
|
|
9
9
|
import Controller from './lib/controller.js'
|
|
10
10
|
import { registerShippedSkills } from './lib/shipped-skills.js'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The preset as a project would actually run it: the skeleton plus the shipped
|
|
14
|
+
* recommendation, adopted exactly the way a user adopts it. A test that asserts
|
|
15
|
+
* on bindings, commit rules or artifacts wants this, because those no longer come
|
|
16
|
+
* from the preset itself.
|
|
17
|
+
* @param {string} id - preset id.
|
|
18
|
+
* @returns {import('./lib/engine.js').WorkflowConfig} the adopted config.
|
|
19
|
+
*/
|
|
20
|
+
function adoptedFlow(id, extra) {
|
|
21
|
+
const base = adoptRecommendation(id)
|
|
22
|
+
if (base === undefined) throw new Error('unknown preset: ' + id)
|
|
23
|
+
return resolveFlow(id, { ...base, ...extra }).config
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function fixture(stage = '开发', extra = {}, services = {}) {
|
|
13
28
|
const cwd = resolve('test-project')
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
// A project running the standard flow: the skeleton plus the adopted
|
|
30
|
+
// recommendation, with one extra skill mounted on 完成 for the tests that need
|
|
31
|
+
// a binding beyond the shipped set. The bindings are MERGED, not replaced —
|
|
32
|
+
// replacing them would drop the recommended skills and artifacts the tests
|
|
33
|
+
// assert on, since the preset skeleton itself carries none.
|
|
34
|
+
const adopted = adoptRecommendation('standard')
|
|
35
|
+
const config = resolveFlow('standard', {
|
|
36
|
+
...adopted,
|
|
37
|
+
stage_bindings: {
|
|
38
|
+
...adopted.stage_bindings,
|
|
39
|
+
完成: { skills: [{ skill: { source: 'bundled', name: 'software-testing' }, rules: [] }] },
|
|
40
|
+
},
|
|
41
|
+
}).config
|
|
42
|
+
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 })
|
|
16
43
|
Object.assign(state, { stage, execution_version: 1, files: ['app.js'], requirement_confirmed: true, solution_confirmed: true, ...extra })
|
|
17
44
|
const records = new Map([[join(cwd, '.dsh/task-LIVE-1.json'), JSON.stringify(state)], [join(cwd, 'app.js'), 'source']])
|
|
18
45
|
const events = [], runs = []
|
|
@@ -28,8 +55,8 @@ function fixture(stage = '开发', extra = {}, services = {}) {
|
|
|
28
55
|
lstat: async (path, options) => records.has(key(path, options)) ? { version: 'v' } : undefined,
|
|
29
56
|
writeText: async (target, content) => { records.set(target.targetKey, content) },
|
|
30
57
|
}
|
|
31
|
-
const ctx = { fs, tools: { register(tool) { execute = tool.execute; return () => {} } }, get(name) {
|
|
32
|
-
if (Object.hasOwn(services, name)) return services[name]
|
|
58
|
+
const ctx = { fs, tools: { register(tool) { execute = tool.execute; return () => {} } }, get(name) {
|
|
59
|
+
if (Object.hasOwn(services, name)) return services[name]
|
|
33
60
|
if (name === 'sandboxPolicy') return { resolve(request) { assert.equal(request.session, session); return policy } }
|
|
34
61
|
if (name === 'shell') return { resolve(request) { runs.push(request); return request }, async run(request) {
|
|
35
62
|
const fail = request.command === 'fail'
|
|
@@ -85,66 +112,66 @@ test('派发立即反映进行中,重派不复用旧审核且保持单一进
|
|
|
85
112
|
assert.equal(f.state().items[0].review, undefined)
|
|
86
113
|
})
|
|
87
114
|
|
|
88
|
-
test('无任务 id 的 status 发现当前工作区任务并支持分支过滤', async () => {
|
|
115
|
+
test('无任务 id 的 status 发现当前工作区任务并支持分支过滤', async () => {
|
|
89
116
|
const f = fixture()
|
|
90
117
|
assert.equal(JSON.parse(await f.call({ operation: 'status', task_id: undefined })).tasks[0].id, 'LIVE-1')
|
|
91
118
|
assert.equal(JSON.parse(await f.call({ operation: 'status', task_id: undefined, branch: 'other' })).tasks.length, 0)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('verify 升级审批不可用时不得先执行命令', async () => {
|
|
95
|
-
const f = fixture('交付')
|
|
96
|
-
const before = JSON.stringify(f.state())
|
|
97
|
-
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', sandbox_permissions: 'danger-full-access', justification: 'Retry the denied test command' }), /approval/)
|
|
98
|
-
assert.equal(f.runs.length, 0, 'approval must precede shell execution')
|
|
99
|
-
assert.equal(JSON.stringify(f.state()), before)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test('verify 拒绝、取消及无效升级参数均不执行命令或改写台账', async () => {
|
|
103
|
-
for (const outcome of ['rejected', 'cancelled', 'unavailable']) {
|
|
104
|
-
const f = fixture('交付', {}, { approval: { request: async () => outcome } })
|
|
105
|
-
const before = JSON.stringify(f.state())
|
|
106
|
-
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', sandbox_permissions: 'danger-full-access', justification: 'Retry denied command' }), /not approved/)
|
|
107
|
-
assert.equal(f.runs.length, 0)
|
|
108
|
-
assert.equal(JSON.stringify(f.state()), before)
|
|
109
|
-
}
|
|
110
|
-
for (const args of [{ sandbox_permissions: 'danger-full-access' }, { justification: 'orphan justification' }]) {
|
|
111
|
-
const f = fixture('交付')
|
|
112
|
-
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', ...args }), /invalid escalation/)
|
|
113
|
-
assert.equal(f.runs.length, 0)
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
test('验证命令审批先于执行,只授权本次命令且回执写入不重复审批', async () => {
|
|
118
|
-
for (const operation of ['verify', 'skill_result']) {
|
|
119
|
-
const approvals = []
|
|
120
|
-
const f = fixture('完成', {}, { approval: { request: async request => {
|
|
121
|
-
assert.equal(f.runs.length, 0)
|
|
122
|
-
approvals.push(request)
|
|
123
|
-
return 'allowed-once'
|
|
124
|
-
} } })
|
|
125
|
-
f.load('software-testing')
|
|
126
|
-
await f.call({ operation, command: 'npm test', skill_name: 'software-testing', evidence: ['real checks'], sandbox_permissions: 'danger-full-access', justification: 'Retry denied test command' })
|
|
127
|
-
assert.equal(approvals.length, 1)
|
|
128
|
-
assert.match(approvals[0].reason, /npm test/)
|
|
129
|
-
assert.equal(f.runs[0].sandboxPolicy.mode, 'danger-full-access')
|
|
130
|
-
assert.equal(f.runs[0].sandboxPolicy.workspaceRoot, f.cwd)
|
|
131
|
-
assert.equal(f.runs[0].sandboxPolicy.sessionId, f.policy.sessionId)
|
|
132
|
-
assert.equal(f.runs[0].signal, f.exec.signal)
|
|
133
|
-
assert.equal(f.policy.mode, 'workspace-write')
|
|
134
|
-
await f.call({ operation: 'verify', command: 'npm test' })
|
|
135
|
-
assert.equal(f.runs[1].sandboxPolicy, f.policy)
|
|
136
|
-
assert.equal(approvals.length, 1)
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
test('skill_result 审批拒绝时不执行命令、不写入技能回执', async () => {
|
|
141
|
-
const f = fixture('完成', {}, { approval: { request: async () => 'rejected' } })
|
|
142
|
-
f.load('software-testing')
|
|
143
|
-
const before = JSON.stringify(f.state())
|
|
144
|
-
await assert.rejects(f.call({ operation: 'skill_result', skill_name: 'software-testing', command: 'npm test', evidence: ['test'], sandbox_permissions: 'danger-full-access', justification: 'Retry denied tests' }), /not approved/)
|
|
145
|
-
assert.equal(f.runs.length, 0)
|
|
146
|
-
assert.equal(JSON.stringify(f.state()), before)
|
|
147
|
-
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('verify 升级审批不可用时不得先执行命令', async () => {
|
|
122
|
+
const f = fixture('交付')
|
|
123
|
+
const before = JSON.stringify(f.state())
|
|
124
|
+
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', sandbox_permissions: 'danger-full-access', justification: 'Retry the denied test command' }), /approval/)
|
|
125
|
+
assert.equal(f.runs.length, 0, 'approval must precede shell execution')
|
|
126
|
+
assert.equal(JSON.stringify(f.state()), before)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('verify 拒绝、取消及无效升级参数均不执行命令或改写台账', async () => {
|
|
130
|
+
for (const outcome of ['rejected', 'cancelled', 'unavailable']) {
|
|
131
|
+
const f = fixture('交付', {}, { approval: { request: async () => outcome } })
|
|
132
|
+
const before = JSON.stringify(f.state())
|
|
133
|
+
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', sandbox_permissions: 'danger-full-access', justification: 'Retry denied command' }), /not approved/)
|
|
134
|
+
assert.equal(f.runs.length, 0)
|
|
135
|
+
assert.equal(JSON.stringify(f.state()), before)
|
|
136
|
+
}
|
|
137
|
+
for (const args of [{ sandbox_permissions: 'danger-full-access' }, { justification: 'orphan justification' }]) {
|
|
138
|
+
const f = fixture('交付')
|
|
139
|
+
await assert.rejects(f.call({ operation: 'verify', command: 'npm test', ...args }), /invalid escalation/)
|
|
140
|
+
assert.equal(f.runs.length, 0)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('验证命令审批先于执行,只授权本次命令且回执写入不重复审批', async () => {
|
|
145
|
+
for (const operation of ['verify', 'skill_result']) {
|
|
146
|
+
const approvals = []
|
|
147
|
+
const f = fixture('完成', {}, { approval: { request: async request => {
|
|
148
|
+
assert.equal(f.runs.length, 0)
|
|
149
|
+
approvals.push(request)
|
|
150
|
+
return 'allowed-once'
|
|
151
|
+
} } })
|
|
152
|
+
f.load('software-testing')
|
|
153
|
+
await f.call({ operation, command: 'npm test', skill_name: 'software-testing', evidence: ['real checks'], sandbox_permissions: 'danger-full-access', justification: 'Retry denied test command' })
|
|
154
|
+
assert.equal(approvals.length, 1)
|
|
155
|
+
assert.match(approvals[0].reason, /npm test/)
|
|
156
|
+
assert.equal(f.runs[0].sandboxPolicy.mode, 'danger-full-access')
|
|
157
|
+
assert.equal(f.runs[0].sandboxPolicy.workspaceRoot, f.cwd)
|
|
158
|
+
assert.equal(f.runs[0].sandboxPolicy.sessionId, f.policy.sessionId)
|
|
159
|
+
assert.equal(f.runs[0].signal, f.exec.signal)
|
|
160
|
+
assert.equal(f.policy.mode, 'workspace-write')
|
|
161
|
+
await f.call({ operation: 'verify', command: 'npm test' })
|
|
162
|
+
assert.equal(f.runs[1].sandboxPolicy, f.policy)
|
|
163
|
+
assert.equal(approvals.length, 1)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('skill_result 审批拒绝时不执行命令、不写入技能回执', async () => {
|
|
168
|
+
const f = fixture('完成', {}, { approval: { request: async () => 'rejected' } })
|
|
169
|
+
f.load('software-testing')
|
|
170
|
+
const before = JSON.stringify(f.state())
|
|
171
|
+
await assert.rejects(f.call({ operation: 'skill_result', skill_name: 'software-testing', command: 'npm test', evidence: ['test'], sandbox_permissions: 'danger-full-access', justification: 'Retry denied tests' }), /not approved/)
|
|
172
|
+
assert.equal(f.runs.length, 0)
|
|
173
|
+
assert.equal(JSON.stringify(f.state()), before)
|
|
174
|
+
})
|
|
148
175
|
|
|
149
176
|
test('状态明确区分内置节点门禁与附加技能命令回执,避免重复验证', async () => {
|
|
150
177
|
const f = fixture('代码审核')
|
|
@@ -168,7 +195,7 @@ test('status 披露当前阶段记录字段和缺项,字段误用不写入且
|
|
|
168
195
|
})
|
|
169
196
|
|
|
170
197
|
test('记录字段来自冻结流程,精简流程不硬编码标准字段,无记录阶段返回空列表', async () => {
|
|
171
|
-
const config =
|
|
198
|
+
const config = adoptedFlow('agile')
|
|
172
199
|
const f = fixture('需求', { flow: { flow: 'agile', version: 1, config } })
|
|
173
200
|
assert.deepEqual(JSON.parse(await f.call({ operation: 'status' })).artifact_requirements, [{ id: 'requirement', name: '需求说明', fields: ['scope'], missing_fields: ['scope'] }])
|
|
174
201
|
await f.call({ operation: 'record', artifact: 'requirement', fields: { scope: '目标、验收与疑问' } })
|
|
@@ -196,7 +223,9 @@ test('验证使用会话策略与取消信号,并明确返回失败及沙箱
|
|
|
196
223
|
})
|
|
197
224
|
|
|
198
225
|
test('只声明已测试、或加载失败,均不能冒充执行绑定技能', async () => {
|
|
199
|
-
|
|
226
|
+
// 代码审核坐在验证门之后,所以该阶段本就要求验证通过;
|
|
227
|
+
// 不给出验证状态会让验证阻塞先于本用例要测的技能阻塞。
|
|
228
|
+
const f = fixture('代码审核', { verification: { passed: true, evidence: ['checks passed'] } })
|
|
200
229
|
f.load('code-review'); f.load('code-commit'); f.load('software-testing', false)
|
|
201
230
|
await assert.rejects(f.call({ operation: 'skill_result', target_stage: '完成', skill_name: 'software-testing', command: 'check', evidence: ['tested'] }), /load skill/)
|
|
202
231
|
await assert.rejects(f.call({ operation: 'advance', target_stage: '完成' }), /software-testing/)
|
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
|
## 快速开始
|
|
@@ -51,13 +51,15 @@ dsh plugin --profile web add @godv61/dsh-task-engine
|
|
|
51
51
|
| 页面 | 你可以做什么 |
|
|
52
52
|
| :--- | :--- |
|
|
53
53
|
| **项目初始化** | 查看或编辑 `AGENTS.md`,让 AI 生成项目说明,预览后保存。 |
|
|
54
|
-
| **流程配置** |
|
|
54
|
+
| **流程配置** | 选择流程骨架,给节点挂技能,并在技能上配置它遵循的规则。需要现成起点时可「采用推荐配置」。 |
|
|
55
55
|
| **任务台账** | 查看实施、验证和审核记录,按关键词、阶段或风险筛选。 |
|
|
56
56
|
| **技能** | 选择技能文件夹,预览内容与目标目录后安装;支持查看、编辑和删除。 |
|
|
57
57
|
| **规则** | 选择 Markdown 文件安装规则,维护项目或个人开发约定。 |
|
|
58
58
|
|
|
59
59
|
内置技能和规则只读;项目与个人资源可编辑。安装、删除都会明确展示目标位置。
|
|
60
60
|
|
|
61
|
+
**流程与工作方法是两回事。** 流程只决定工作怎么流转(阶段顺序与门禁),预设不自带任何技能或提交格式。要用什么技能、遵守哪些规则、提交信息写什么,都由你配置——「采用推荐配置」只是把一份现成的起点**写入**你的配置,之后完全归你:删掉的不再补回,升级也不覆盖。规则挂在技能下,所以打开一个技能看到的就是它完整的规则列表,挂到任何节点都一样。
|
|
62
|
+
|
|
61
63
|
## 选择适合这次任务的流程
|
|
62
64
|
|
|
63
65
|
| 预设 | 阶段顺序 | 适用场景 |
|
package/defaults/eng.json
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"flow": "standard",
|
|
3
3
|
"stage_bindings": {
|
|
4
|
-
"需求评审": {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
"需求评审": {
|
|
5
|
+
"skills": [
|
|
6
|
+
{
|
|
7
|
+
"skill": { "source": "bundled", "name": "requirement-analysis" },
|
|
8
|
+
"rules": [{ "source": "bundled", "name": "security-redlines" }]
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"设计": {
|
|
13
|
+
"skills": [
|
|
14
|
+
{ "skill": { "source": "bundled", "name": "solution-design" }, "rules": [] }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"开发": {
|
|
18
|
+
"skills": [
|
|
19
|
+
{
|
|
20
|
+
"skill": { "source": "bundled", "name": "code-implement" },
|
|
21
|
+
"rules": [
|
|
22
|
+
{ "source": "bundled", "name": "coding-conventions" },
|
|
23
|
+
{ "source": "bundled", "name": "security-redlines" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"交付": {
|
|
29
|
+
"skills": [
|
|
30
|
+
{ "skill": { "source": "bundled", "name": "code-verify" }, "rules": [] }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"代码审核": {
|
|
34
|
+
"skills": [
|
|
35
|
+
{
|
|
36
|
+
"skill": { "source": "bundled", "name": "code-review" },
|
|
37
|
+
"rules": [
|
|
38
|
+
{ "source": "bundled", "name": "coding-conventions" },
|
|
39
|
+
{ "source": "bundled", "name": "security-redlines" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"skill": { "source": "bundled", "name": "code-commit" },
|
|
44
|
+
"rules": [{ "source": "bundled", "name": "commit-conventions" }]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
9
48
|
}
|
|
10
|
-
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# DSH Task Engine —— 技术简报(供评估与优化讨论)
|
|
2
|
+
|
|
3
|
+
> 用途:把事实交给 GPT 讨论时使用。以下内容均经代码/实测核对,不是印象。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 它是什么
|
|
8
|
+
|
|
9
|
+
DeepSeek Harness(DSH)的插件:给 AI 编码会话加一套**可检查的工程交付流程**。
|
|
10
|
+
|
|
11
|
+
- npm: `@godv61/dsh-task-engine`(`latest` = 0.23.9,MIT)
|
|
12
|
+
- GitHub: https://github.com/godv61/dsh-task-engine(**已被 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 收录**,PR #5681 一次通过)
|
|
13
|
+
- 规模:host 面 **4079 行** TS,client 面 **2173 行** TSX,测试 146 项 P0 断言 + 52 项 `node:test`
|
|
14
|
+
|
|
15
|
+
### 核心机制
|
|
16
|
+
|
|
17
|
+
**一个工具 `dev_task`**,7 个操作:`status` / `config` / `init` / `create` / `commit` / `install_hook` / `verify_hook`。
|
|
18
|
+
|
|
19
|
+
它持有任务状态机,**阶段流转是硬门禁**(模型无法绕过):
|
|
20
|
+
|
|
21
|
+
| 守卫 | 含义 |
|
|
22
|
+
| :--- | :--- |
|
|
23
|
+
| `requirement_confirmation` | 需求须经人确认 |
|
|
24
|
+
| `solution_confirmation` | 方案须经人确认 |
|
|
25
|
+
| `artifacts_present` | 阶段产物须齐备 |
|
|
26
|
+
| `todos_done` | 实施项须全部完成 |
|
|
27
|
+
| `verified` | 须有**真实命令回执**(退出码 0,未超时/中止/被沙箱拒) |
|
|
28
|
+
| `review_passed` | 须有审核结论 |
|
|
29
|
+
|
|
30
|
+
**三个内置流程**(阶段顺序与守卫由预设固化):
|
|
31
|
+
|
|
32
|
+
| 流程 | 阶段 |
|
|
33
|
+
| :--- | :--- |
|
|
34
|
+
| `standard` | 需求评审 → 设计 → 开发 → 交付 → 代码审核 → 完成 |
|
|
35
|
+
| `agile` | 需求 → 开发 → 交付 → 审查 |
|
|
36
|
+
| `minimal` | 开发 → 交付 |
|
|
37
|
+
|
|
38
|
+
**渐进式披露**:每个阶段挂「技能 + 规则」,进入该阶段才披露,避免一次性灌入上下文。
|
|
39
|
+
|
|
40
|
+
**工作台 5 个标签页**:项目初始化 / 流程配置 / 任务台账 / 技能 / 规则。
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. 已知的设计缺口(当前讨论起点)
|
|
45
|
+
|
|
46
|
+
### 2.1 技能与规则是平级的,但存在**隐式依赖**
|
|
47
|
+
|
|
48
|
+
**数据模型**(`src/engine.ts`):
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface StageBinding {
|
|
52
|
+
skills?: string[] // 独立数组
|
|
53
|
+
rules?: string[] // 独立数组
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
引擎分别解析:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const rules = await resolveRules(binding?.rules ?? [], fs, cwd) // 不经技能
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**但技能文本里引用了具体规则**,例如 `code-implement/SKILL.md`:
|
|
64
|
+
|
|
65
|
+
> 代码质量:按内置规则 **coding-conventions** 查「做得好不好」
|
|
66
|
+
|
|
67
|
+
**问题**:这个依赖**只写在散文里,没有结构化**。因此:
|
|
68
|
+
|
|
69
|
+
- 用户在 UI 勾了 `code-implement` 但没勾 `coding-conventions` → 技能执行时引用一个不存在的规则
|
|
70
|
+
- **引擎不校验,UI 不提示**
|
|
71
|
+
- 默认配置碰巧配对,用户改绑定就可能拆散
|
|
72
|
+
|
|
73
|
+
**默认配置印证耦合确实存在**:规则 `coding-conventions` 同时挂在「开发」(`code-implement`)和「交付」(`code-verify`)两个阶段。
|
|
74
|
+
|
|
75
|
+
### 2.2 待决策的四个方案
|
|
76
|
+
|
|
77
|
+
| 方案 | 做法 | 代价 |
|
|
78
|
+
| :--- | :--- | :--- |
|
|
79
|
+
| **A. 声明式依赖** | `SKILL.md` frontmatter 加 `requires_rules: [...]` | 是 B/C/D 的前置条件;需改 7 个内置技能 |
|
|
80
|
+
| **B. 软提示** | 规则列表全量显示,标注「此技能建议配合 X」 | 保留自由组合;仅提示 |
|
|
81
|
+
| **C. 强联动** | 勾技能 → 规则列表过滤到相关项 | ⚠️ 破坏自由组合(同一规则复用于多技能) |
|
|
82
|
+
| **D. 仅保存时校验** | 不联动 UI,保存时警告「技能 X 引用了未挂载的规则 Y」 | 最轻量;问题暴露较晚 |
|
|
83
|
+
|
|
84
|
+
**当前倾向**:A + D(先让关系可表达,再做校验),或 A + B。**C 需要在 A 的数据之上才成立。**
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. 其他可能值得评估的方向
|
|
89
|
+
|
|
90
|
+
### 3.1 流程定制能力的边界
|
|
91
|
+
|
|
92
|
+
**现状**:阶段顺序与守卫由预设固化,**可视化自定义流程不在计划内**(曾评估,结论是不做——把流程设计负担转嫁给使用者)。
|
|
93
|
+
|
|
94
|
+
**可调部分**:项目根 `.dsh/eng.json` 只声明 `flow`(选哪套预设)+ `stage_bindings`(每阶段挂什么)。
|
|
95
|
+
|
|
96
|
+
**待评估**:这个边界是否过窄?例如是否该允许「加一个自定义阶段但保留守卫语义」?
|
|
97
|
+
|
|
98
|
+
### 3.2 验证与审核的可信度边界
|
|
99
|
+
|
|
100
|
+
**现状**(README 与 FAQ 都声明):
|
|
101
|
+
|
|
102
|
+
> 命令覆盖是否充分、审核是否准确仍需判断,**不能把一个成功退出码当作全部需求已验证**。
|
|
103
|
+
|
|
104
|
+
- 验证需真实回执,但**回执不等于需求被完整覆盖**
|
|
105
|
+
- 审核/实施项结论**由模型记录**,非独立验证
|
|
106
|
+
- 任务 JSON **无签名**,有文件写权限者可同时改内容与摘要
|
|
107
|
+
|
|
108
|
+
**待评估**:对「成熟开源产品」而言,这个边界是否需要更强机制(如独立验收、产物哈希链)?
|
|
109
|
+
|
|
110
|
+
### 3.3 提交门禁可被绕过
|
|
111
|
+
|
|
112
|
+
**现状**:`hooks/commit-msg` 检查任务、阶段、消息与文件范围。但:
|
|
113
|
+
|
|
114
|
+
> `--no-verify`、替换 `hooksPath` 或直接改本地数据**仍可能绕过**。
|
|
115
|
+
|
|
116
|
+
**待评估**:是否需要与 CI 结合的服务端校验?还是明确声明「个人本机工具,不提供防篡改」即可?
|
|
117
|
+
|
|
118
|
+
### 3.4 多环境一致性
|
|
119
|
+
|
|
120
|
+
**现状问题**:同一插件需在**桌面版 DSH**(`0.1.2-rc.1`)与**源码版 DSH**(`0.1.7-alpha.1`)上工作,两者契约不同:
|
|
121
|
+
|
|
122
|
+
- persona 字段:桌面版要 `text`,源码版要 `prefix`(已在 0.23.7 通过「跟着运行环境走」解决)
|
|
123
|
+
- 插件管理:桌面版用 generation 机制,源码版用 pnpm
|
|
124
|
+
|
|
125
|
+
**已采取的防护**:CI 加 `dsh-contract` job(跑 `scripts/verify-dsh-compat.mjs`),peer 范围用显式 `||` 分支覆盖各版本。
|
|
126
|
+
|
|
127
|
+
**待评估**:DSH 处于 alpha、**1–3 天一个 tag**,这个跟踪成本是否可持续?
|
|
128
|
+
|
|
129
|
+
### 3.5 其他
|
|
130
|
+
|
|
131
|
+
- **国际化**:目前中文为主,README 有中英双版。是否需要完整 i18n?
|
|
132
|
+
- **可访问性**:工作台介入了 `aria-label`,但未系统测试
|
|
133
|
+
- **性能**:任务台账在任务多时的表现未测
|
|
134
|
+
- **文档**:本次刚做过一次重组(发布说明归入 `docs/releases/`,索引分「当前/历史」)
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 4. 近期已完成的修复(供判断演进方向)
|
|
139
|
+
|
|
140
|
+
| 版本 | 内容 |
|
|
141
|
+
| :--- | :--- |
|
|
142
|
+
| 0.23.9 | 可搜索绑定选择器(独立搜索/仅看已选/计数/就地滚动/预设默认锁定);source map 停止内联源码(447→148 KB);修 10 处 UTF-8 字节损坏 |
|
|
143
|
+
| 0.23.8 | 文档修正(4 处与代码矛盾的陈述)+ 目录重组 |
|
|
144
|
+
| 0.23.7 | persona 字段兼容(新旧 DSH 都能用)——**读源预设用哪个字段就写哪个**,不再硬编码 |
|
|
145
|
+
| 0.23.6 | eng 预设每次启动从运行中 harness 重新派生(此前只在首次生成,DSH 升级后失效) |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 5. 环境依赖(影响可测试性)
|
|
150
|
+
|
|
151
|
+
- **DSH 0.1.7-alpha.1** 曾有 bug 致**所有工具调用失败**(`ctx.tools[TOOL_RUNTIME_SCHEDULER]` 为 `undefined`),社区在 discussions #7035 / #7194 报告,**0.1.7 已修**
|
|
152
|
+
- 源码版用 `pnpm dsh web` 会触发模块双重加载;绕过方式:`node apps/cli/lib/bin.js <profile>`
|
|
153
|
+
- 桌面版插件由 `dshmarket` + generation 机制管理,**不能用 CLI 的 `dsh plugin` 直接操作**(会与它冲突)
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 6. 想请对方重点回答的问题
|
|
158
|
+
|
|
159
|
+
1. **技能↔规则依赖**:A/B/C/D 选哪个?还是有更好的模型(如规则直接属于技能、互不独立)?
|
|
160
|
+
2. **流程定制边界**:「不做自定义流程」对一个成熟产品是否合理?还是该提供受限的扩展点?
|
|
161
|
+
3. **可信度声明**:当前「不防篡改」的边界,是明确声明即可,还是需要机制补强?
|
|
162
|
+
4. **对 DSH alpha 的跟踪成本**:1–3 天一个 tag,插件该怎么定位(追最新?固定版本?)
|
|
163
|
+
5. **还缺什么才能称得上「成熟开源产品」**:贡献指南、issue 模板、语义化版本、变更日志规范、安全策略、行为准则?
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,102 @@
|
|
|
4
4
|
|
|
5
5
|
按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
|
|
6
6
|
|
|
7
|
+
## 0.25.0
|
|
8
|
+
|
|
9
|
+
针对 2026-09-24 复评的改造。**这是一个破坏性版本**:预设不再自带任何技能、产物或提交格式,`SkillBinding` 新增 `evidence` 字段,敏捷流程的门禁语义与版本号都变了。核心是落实产品原则:**流程只控制状态,业务方法由用户配置,Rule 真正归属 Skill。**
|
|
10
|
+
|
|
11
|
+
已有项目不会因此失效,但**行为会变**:预设不再提供内置技能,所以一个从未采用推荐配置的项目,其节点将没有绑定。升级后打开工作台点一次「采用推荐配置」即可获得与升级前等价的起点,此后完全由你控制——删除的不再补回,升级也不覆盖。已有任务按各自的冻结快照执行,不受影响。
|
|
12
|
+
|
|
13
|
+
### 流程骨架与可选推荐配置(破坏性)
|
|
14
|
+
|
|
15
|
+
预设此前把技能、提交规则和产物字段烘焙进解析结果,`mergeBindings` 又只做**追加**——于是内置技能无法移除,而用户给内置技能配自己的规则时,条目会因引用已存在被整个跳过,静默保留预设规则。工作台里选择流程也会一并写入这些内容,等于选择流程即接受内置工作方法。
|
|
16
|
+
|
|
17
|
+
现在分成两层:
|
|
18
|
+
|
|
19
|
+
- **骨架**只含阶段图、每条边的守卫,以及提交规则中属于**流程控制**的那一半:何时需要提交、哪个节点是检查点、是否强制文件范围。最后一项留在骨架上是有意的——高风险检查读它作为安全底线,不能因为用户没采用推荐配置就消失。
|
|
20
|
+
- **推荐配置**(`FlowRecommendation`)单独存放技能绑定、提交**文本**约定、产物字段与审查深度。
|
|
21
|
+
- `adoptRecommendation()` 只在用户显式请求时写入。这是一次性动作:此后这些值就是用户的普通配置,用户删除的值不会回来,升级也不覆盖。
|
|
22
|
+
- `resolveFlow` 逐字采用项目配置。用户没配的节点就是没有绑定,这是合法状态,不是待补齐的空缺。
|
|
23
|
+
- 工作台新增「采用推荐配置」按钮;**选择流程不再写入任何绑定**。
|
|
24
|
+
|
|
25
|
+
### 冻结真正接入执行链
|
|
26
|
+
|
|
27
|
+
此前 `flow.resources` 只**存档**正文,`status` 与阶段披露仍走 `readRuleAt` 读实时文件——改一条规则会无声改变进行中任务所遵循的内容,正是快照本该阻止的不稳定。现在解析优先使用冻结正文,并把漂移**报告**出来:源被改动记为 `stale_source_rules`(source edited),源被删除也记为漂移而非缺失——任务手里已有内容,说它缺失是错的。没有快照的旧任务继续读实时文件并把删除报为缺失,两条路径可区分,不会互相悄悄退化。
|
|
28
|
+
|
|
29
|
+
### 缺失资源、节点结果与完成条件改为真正阻塞
|
|
30
|
+
|
|
31
|
+
- **规则无法解析则不可推进**。`missing_rules` 以前只披露不阻塞,删掉绑定规则仍能推进——阶段在缺少既定约束的情况下走完,而记录看起来正常。判定实现为引擎里的纯函数,工具与 hook 从同样输入得到同样结论。
|
|
32
|
+
- **完成检查流程声明的节点结果**。此前只查「是否站在终态、实施项是否完成、有无提交」,于是一个在进入终态的路上声明了 `review_passed` 的流程,只要**到达**就满足——审核被 blocked 仍能完成。要求由 `terminalRequirements` 从流程推导,没声明验证门禁的流程不会被强加。
|
|
33
|
+
- 敏捷流程的审查要求需要另一种机制:`审查` 是终态且**就是**审查本身,没有出边可承载守卫。把 `review_passed` 挂到 `交付 → 审查` 会要求「在产生结论的阶段之前就有结论」,还会挡住 `交付` 的提交(提交要求其出边守卫全部满足)。改用 `completion_guards` 显式声明,这正是 `complete` 作为独立操作存在的原因。敏捷版本号升至 4。
|
|
34
|
+
|
|
35
|
+
### 技能声明自己需要哪类证据
|
|
36
|
+
|
|
37
|
+
每种非内置技能都必须记录「真实验收命令」,于是需求或设计类技能——产出是一份文档——被迫运行一条无关命令来满足门禁。证据本来就有,只是形态不同。技能绑定现在可声明 `evidence`:`command` / `artifact` / `review` / `manual` / `none`,引擎按各自形态检查对应记录。未声明时保持历史行为(命令回执),因此既有配置与冻结快照不受影响。
|
|
38
|
+
|
|
39
|
+
### 保存完整配置 + 列表可用性
|
|
40
|
+
|
|
41
|
+
- **保存不再只写 `flow` 与 `stage_bindings`**。提交规则、产物声明、审查深度与 `commit_required` 以前只用于预览,保存时被静默丢弃——面板显示一套配置、文件里是另一套。写入载荷与面板状态现在都覆盖全部字段。
|
|
42
|
+
- **技能与规则列表在规模增长后可用**:加搜索(匹配名称、说明与来源层)、「仅看已选」筛选,展开的规则列表改为**有界滚动区**。一条规则贯穿全部筛选:**已绑定或预设自带的条目任何查询下都保持可见**——面板展示的是配置本身,筛选藏起正在生效的资源会让面板与它显示的配置不一致,那比列表长更糟。
|
|
43
|
+
|
|
44
|
+
## 0.24.0
|
|
45
|
+
|
|
46
|
+
针对 2026-09-23 外部深度评测的改造。**这是一个破坏性版本**:配置里的 `stage_bindings[*].rules` 不再作为节点级规则读取,`skills` 的格式也从字符串数组改为带来源的对象。已有配置会继续工作——旧的节点级规则以 `legacy_rules` 保留并**仍然生效**——但它们的归属需要你确认一次,见下面「规则改为只挂在技能下」。
|
|
47
|
+
|
|
48
|
+
每个问题都先以独立复现确认存在,再修,并做正反两面验证。四个批次按实施顺序列在下面。
|
|
49
|
+
|
|
50
|
+
### 返工、资源冻结与预设重新定位(第三批·续)
|
|
51
|
+
|
|
52
|
+
- **新增 `revise` 操作:受控返工**。流程图只有正向边,但工作不是——需求会在实现开始后变化,缺陷会在审核时发现。此前唯一的回头方式是手改记录,而那会让每一条下游结论**名义上依然成立**:确认、验证回执、审核结论都描述着一棵已被取代的代码树,却仍读作"通过"。
|
|
53
|
+
- **失效范围按声明的类型推导,不是一刀切**:`requirement` 清掉需求确认及其全部下游;`solution` 保留需求确认,清掉方案确认与实现相关证据;`defect` 保留需求与方案确认,只清掉"旧实现是正确的"这一批证据。**每次返工都清空一切会更简单,也会丢掉仍然成立的结论**,让工作无谓重做。
|
|
54
|
+
- 被清掉审核的实施项**退回未完成状态**——留着 `done` 会让 `todos_done` 在审核刚被作废的工作上通过。
|
|
55
|
+
- 返工历史记入 `revisions`,含类型、原因、从哪个阶段回到哪个阶段、以及本次失效了哪些结论。
|
|
56
|
+
- **任务创建时冻结技能与规则正文**。名字无法让进行中的任务保持稳定:改一条正在使用的规则会**无声改变该任务正在做的事**,删掉它则让一条已配置的约束凭空消失。现在每个解析到的正文都连同一个内容 hash 存入 `flow.resources`,**同一正文按 hash 只存一份**(`security-redlines` 被两个技能引用,正文不重复)。读不到正文的资源会被报为 `unreadable`,而不是假装冻结成功。
|
|
57
|
+
- **预设重新定位为三种任务复杂度**,而不是三种安全底线。名称与描述改为面向任务的表述:**完整研发**(新功能、架构或跨模块改动、高风险)、**日常迭代**(目标明确的常规功能与缺陷修复)、**快速修改**(局部、低风险、方案明确的改动)。三者都以同一个 `completed` 生命周期收尾。
|
|
58
|
+
- **修复 UI 的两个编辑丢失问题**:点击**当前已选中**的流程卡片会重新用预设默认值覆盖绑定——一次误点就会丢掉全部自定义;现在点击当前项无操作。有未保存修改时切换流程会**先确认**,因为绑定是整体替换、编辑无法恢复。保存按钮在有未保存修改时才可点,并显示状态。
|
|
59
|
+
|
|
60
|
+
### 规则改为只挂在技能下(第三批,破坏性)
|
|
61
|
+
|
|
62
|
+
**规则改为只挂在技能下。** 此前 `StageBinding` 是 `{ skills: string[], rules: string[] }` 两个平级数组,于是「这个技能在什么规则下运行」这个问题,必须先把预设默认、项目追加、优先级解析全部算一遍才能回答——而且答案会随阶段变化。现在:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
流程 → 节点 → 技能 → 规则
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `StageBinding` 只保留 `skills`,每项是 `SkillBinding { skill: ResourceRef, rules: ResourceRef[] }`。**节点不再有规则列表,也不提供追加、禁用或覆盖**;打开一个技能看到的就是它完整的规则列表,挂到任何节点都是同一套。
|
|
69
|
+
- 同一技能需要不同规则时**复制成另一个独立技能**,不建立隐式继承。测试里有一条断言专门锁住这一点:同一个技能引用在任何流程的任何节点都必须携带完全相同的规则集合。
|
|
70
|
+
- **资源引用带来源**(`bundled:` / `project:` / `user:`)。裸名字无法区分「项目里的 coding-conventions」和「用户目录里的 coding-conventions」——过去二者只能靠优先级隐式决定,现在引用本身说明去哪一层找,解析不再走优先级遍历。
|
|
71
|
+
- **规则可被多个技能引用**,正文不复制。`security-redlines` 同时被 `code-implement` 与 `code-review` 引用,就是这种共享(有断言覆盖)。同一阶段的重复引用会去重披露。
|
|
72
|
+
- **内置规则的归属按正文内容逐条审查,不按文件名机械搬迁**:`coding-conventions` 给 `code-implement`(写时遵循)与 `code-review`(审的就是这些);`commit-conventions` 只给 `code-commit`;`security-redlines` 给 `code-implement`、`code-review` 与 `requirement-analysis`——它的「服务端校验才是边界,前端校验不是」在需求阶段就要定。
|
|
73
|
+
- **旧配置的节点级规则不丢弃、不猜归属**。`legacy_rules` 保留原名,**仍然生效**,同时 `status.unassigned_legacy_rules` 列出它们等待归属;阶段披露文本也会说明这是待分配规则。`validateWorkflow` 会报出未分配的旧规则,让这个迁移状态保持可见而不是沉淀成两套并存的模型。
|
|
74
|
+
- `status` 新增 `unassigned_legacy_rules`,`rules[]` 现在带 `source`;`skill_obligations` 与 `skill_result` 按技能名比较(DSH 的 skill 工具按名寻址),来源只决定解析到哪一层。
|
|
75
|
+
|
|
76
|
+
**界面**:节点页只列技能;勾选技能后展开「规则设置」,在**技能**上增删规则。旧的平级「技能 / 规则」双选择器(`BindingPicker`)已删除——它表达的正是被取消的双层模型。技能卡片区分预设绑定(锁定)与项目追加,并显示该技能当前携带几条规则。
|
|
77
|
+
|
|
78
|
+
**关于测试**:`.p0-test.mjs` 与 `.workflow-test.mjs` 里各有一条断言依赖旧的节点级规则语义(「清空节点规则仍保留核心规则」「技能以字符串数组绑定」),已改为按新模型断言——不变量没有变(核心能力不可被覆盖取消),变的只是它落在技能上。新增 4 条第三批断言。
|
|
79
|
+
|
|
80
|
+
### 显式完成与可配置的审查深度(第二批)
|
|
81
|
+
|
|
82
|
+
- **完成成为显式动作,不再由「站在最后一个阶段」推定**。`minimal` 的终态恰好又是它的提交检查点,而「离开检查点前必须提交」这条规则只在**离开**阶段时触发——终态没有出口,于是该流程能在 `commits` 为空时抵达终点,**没有任何交付记录**。新增 `complete` 操作与 `TaskCompletion` 记录:三个预设统一以同一生命周期收尾,`completionBlockers()` 在完成时检查「是否处于终态」「`todos_done` 条件是否满足」「交付方式是否要求提交」。
|
|
83
|
+
- **是否必须提交改为流程的交付方式**。`commit_required`(缺省 `true`,保持既有行为)允许非代码任务或非 Git 项目完成而不需要提交——Git 提交是一种交付方式,不是通用终点。
|
|
84
|
+
- **每项审查深度改为流程属性**。`review_depth`(`two-stage` / `single`,缺省 `two-stage` 保持既有行为)。此前 `todosBlockers` 被三个预设共用且硬编码要求 `spec` 与 `quality` **两个**结论,因此 `minimal` 虽只有两个阶段,逐项审查负担与完整流程完全相同——实测三者对同一状态返回**逐字节相同**的阻塞列表。现有 `minimal` 声明 `single`:**轻量是减少重复判定,不是跳过检查**——未审核的项、未通过的结论仍然阻塞。
|
|
85
|
+
- **`review` 结构改为防御性读取**。`todosBlockers` 原先假设 `quality` 必然存在;加载轻量深度或较早快照写下的记录时会崩溃,而不是报告问题。
|
|
86
|
+
- **未知的 `review_depth` 是配置错误**,不会静默退回最严档位把拼写错误藏在更严行为背后。`validateWorkflow` 会报出它。
|
|
87
|
+
- `minimal` 版本升到 2;`standard` / `agile` 的 v1 快照继续按原语义读取。
|
|
88
|
+
|
|
89
|
+
**关于测试**:第一批里那条针对 `minimal` 的断言是**空过的**——它检查 `status.skill_blockers`/`commit_blockers`,而 `todosBlockers` 的输出从不进入这两个字段,所以断言的失败分支不可达,对着完全未修复的引擎也能通过。现已改为**直接调用被测函数**,并加了一条「三个预设的审查负担不得完全相同」的断言。反向验证:临时移除 `review_depth` 声明,两条测试立即失败。
|
|
90
|
+
|
|
91
|
+
### 门禁一致性修复(第一批)
|
|
92
|
+
|
|
93
|
+
针对 2026-09-23 外部深度评测的第一批修复。全部问题先用独立复现确认存在(`.assessment-batch1.mjs`,8 项),再修,且每条都有正反两面验证。
|
|
94
|
+
|
|
95
|
+
- **验证失败不再能取得提交许可**。标准流程的 `交付 → 代码审核` 要求 `verified`,而 `代码审核 → 完成` 只要求 `review_passed`;`evidenceBlockers` 又把陈旧检查挂在 `verification.passed` 为真之下,于是**重新验证失败反而让检查彻底消失**——失败比从不验证更宽松。现在 `verified` 作为流程级约束计算:`verificationHeldStages()` 从所有 `verified` 边**向前闭包**推导出「该要求仍在生效的阶段集合」,使要求只在跨过验证门之后适用(任务在需求阶段尚无物可验,不应被阻塞),跨过之后则一直适用。提交与完成共用该判断。
|
|
96
|
+
- **敏捷流程的提交标签契约统一**。检查点阶段返回标签 `TASK`,而该预设的消息正则只接受 `T\d+`,导致引擎交给模型的状态行与校验器对同一份契约互相矛盾。正则改为同时接受 `TASK` 与 `T\d+`,覆盖 `item` 策略实际产生的两种标签形状(实施项提交与收尾提交)。
|
|
97
|
+
- **敏捷流程补齐 `review` 产物**。该预设的 `审查` 阶段绑定 `code-review`,而该技能指示模型 `record artifact=review`;预设却只声明了 `requirement`,于是被绑定技能的指令被引擎拒绝——预设自己制造了一个不可能满足的契约。现声明 `审查` 阶段的 `review` 产物。
|
|
98
|
+
- **缺失规则不再静默跳过**。`resolveRules` 在 bundled/project/user 三处都找不到时只是不加入结果,调用方无从察觉——被删除的规则文件与从未绑定的规则无法区分。现在解析结果区分 `resolved`/`missing`,并记录每条规则的**来源层**(内置规则按设计优先于同名项目/用户文件,不记录来源就无法判断哪一份在生效)。`status` 新增 `missing_rules` 与 `rules[]`(含来源),阶段披露文本也会明确列出找不到的绑定。
|
|
99
|
+
- **敏捷版本号提升到 2**。上一项改变了预设的产物契约与消息契约,因此按版本区分:新任务采用 v2,既有任务继续读取各自冻结的 v1 快照。
|
|
100
|
+
|
|
101
|
+
**关于既有测试**:`.workflow-test.mjs` 中一项用例从 `代码审核` 阶段起步却未给出验证状态,此前正是靠上述缺陷(`passed` 为假时陈旧检查不触发)才得以看到它真正要测的技能门禁。已为该项补上验证状态——被测意图不变(未执行的附加技能不得冒充完成),只是不再依赖缺陷的副作用。
|
|
102
|
+
|
|
7
103
|
## 0.23.9
|
|
8
104
|
|
|
9
105
|
- **工作台的阶段绑定改为可搜索的选择器**。此前技能和规则各是一串可点击的胶囊标签:没有搜索、没有筛选、没有计数,列表也没有滚动容器,资源一多就把页面撑得很长。现在每类资源各有一个选择器:
|
package/docs/configuration.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
项目没有配置文件时使用 `standard`;配置文件存在但 flow 缺失或无效时会报错,不会自动忽略错误。
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## 技能与规则
|
|
18
18
|
|
|
19
19
|
最小配置如下:
|
|
20
20
|
|
|
@@ -24,15 +24,40 @@
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
`stage_bindings`
|
|
27
|
+
`stage_bindings` 的键是当前流程中的阶段名称,值只有 `skills`——**节点只选择技能,规则属于技能**。
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"flow": "standard",
|
|
32
|
+
"stage_bindings": {
|
|
33
|
+
"开发": {
|
|
34
|
+
"skills": [
|
|
35
|
+
{ "skill": { "source": "bundled", "name": "code-implement" },
|
|
36
|
+
"rules": [{ "source": "bundled", "name": "coding-conventions" },
|
|
37
|
+
{ "source": "project", "name": "api-contract" }] }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**规则挂载在技能下,节点不提供规则追加、禁用或覆盖。** 打开一个技能就能看到它完整的规则列表;把它挂到任何节点,都是同一套规则。需要同一技能在不同节点用不同规则时,**复制成另一个独立技能**,再分别配置——不建立隐式继承关系。
|
|
45
|
+
|
|
46
|
+
规则引用带来源(`bundled:` / `project:` / `user:`),因此同名资源不会被混淆。同一份规则可被多个技能引用,不需要复制正文;编辑共享规则时界面会显示受影响的技能。
|
|
47
|
+
|
|
48
|
+
[默认配置示例](../defaults/eng.json)列出了标准流程采用推荐配置后的样子。
|
|
49
|
+
|
|
50
|
+
**预设不带任何绑定。** 一个只写了 `flow` 的配置就是字面意思:该流程的节点没有绑定,阶段仍按流程骨架流转。要一份现成的工程起点,在工作台点「采用推荐配置」,它会把技能、提交信息格式与产物字段**写入**你的配置一次;此后这些值就是你的普通配置,删掉不会补回,升级也不覆盖。
|
|
51
|
+
|
|
52
|
+
进入阶段后,`dev_task` 披露该阶段全部技能的规则内容。新任务离开阶段前会检查 Harness 的 skill 工具成功加载记录;技能还可以**声明自己需要哪类证据**(`command` / `artifact` / `review` / `manual` / `none`),引擎按各自形态检查对应记录——需求或设计类技能产出的是文档,不必运行一条无关命令。未声明时沿用命令回执。`status.skill_obligations` 中的 `command_receipts_required` 列出确实需要命令回执的技能。安装一个技能不会自动执行其中的脚本。
|
|
53
|
+
|
|
54
|
+
挂在终态(例如"完成")的技能在进入终态前执行。测试技能通常建议挂在"交付";现有"完成"绑定也会在审核阶段执行后才放行。标准流程 v2 在审核通过后提交,并核对真实 Git HEAD。修改文件或声明范围后,旧验证回执失效。
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
资源管理保留项目和个人目录的同名条目,避免来源误标和误操作。技能最终由 Harness 的技能目录加载。
|
|
32
57
|
|
|
33
|
-
|
|
58
|
+
### 旧配置迁移
|
|
34
59
|
|
|
35
|
-
|
|
60
|
+
旧配置把规则写在**节点**下(`"开发": { "rules": [...] }`)。这类规则不记录它属于哪个技能,因此**不会被自动分配**:它们保留在 `legacy_rules` 中,**仍然生效**,同时 `status.unassigned_legacy_rules` 会列出它们等待归属。请把每条规则加到你希望承载它的技能下,再从 `legacy_rules` 中移除;若同一技能在不同节点需要不同规则,复制该技能再分别配置。
|
|
36
61
|
|
|
37
62
|
## 哪些内容暂时不能修改
|
|
38
63
|
|