@godv61/dsh-task-engine 0.29.3 → 0.29.8
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/.adaptive-test.mjs +302 -149
- package/.codex-project-test.mjs +18 -18
- package/.evidence-test.mjs +16 -3
- package/.hook-test.mjs +19 -19
- package/.resource-test.mjs +19 -2
- package/.roundtrip-test.mjs +8 -3
- package/.sonar-credential-test.mjs +38 -0
- package/.sonarlint-local-test.mjs +65 -0
- package/.workflow-test.mjs +29 -12
- package/README.md +22 -22
- package/docs/CHANGELOG.md +33 -19
- package/docs/README.md +6 -6
- package/docs/adaptive-workflows.md +85 -73
- package/docs/configuration.md +4 -4
- package/docs/faq.md +24 -18
- package/docs/getting-started.md +9 -9
- package/docs/manual-legacy.html +1 -1
- package/docs/manual.html +124 -117
- package/docs/roadmap.md +7 -7
- package/hooks/commit-msg +1 -1
- package/lib/adaptive.js +1 -1
- package/lib/adaptive.js.map +1 -1
- package/lib/client.js +289 -17
- package/lib/client.js.map +2 -2
- package/lib/controller.d.ts +35 -0
- package/lib/controller.js +68 -0
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.d.ts +2 -0
- package/lib/dev-task.js +261 -31
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +1 -1
- package/lib/hook.js +3 -3
- package/lib/hook.js.map +1 -1
- package/lib/project-init.d.ts +4 -0
- package/lib/project-init.js +21 -2
- package/lib/project-init.js.map +1 -1
- package/lib/skill-audit.js +9 -1
- package/lib/skill-audit.js.map +1 -1
- package/lib/sonar-credential.d.ts +17 -0
- package/lib/sonar-credential.js +17 -0
- package/lib/sonar-credential.js.map +1 -0
- package/lib/sonar-report.d.ts +4 -0
- package/lib/sonar-report.js +40 -0
- package/lib/sonar-report.js.map +1 -0
- package/lib/sonar.d.ts +23 -2
- package/lib/sonar.js +55 -4
- package/lib/sonar.js.map +1 -1
- package/lib/sonarlint-local.d.ts +15 -0
- package/lib/sonarlint-local.js +307 -0
- package/lib/sonarlint-local.js.map +1 -0
- package/package.json +9 -7
- package/preset/enable.mjs +2 -2
- package/preset/persona.md +4 -4
- package/scripts/verify-dsh-compat.mjs +14 -14
- package/scripts/verify-package.mjs +7 -7
- package/skills/architecture-design/SKILL.md +11 -11
- package/skills/code-development/SKILL.md +11 -11
- package/skills/code-review/SKILL.md +11 -11
- package/skills/eng-delivery/SKILL.md +6 -4
- package/skills/requirements-analysis/SKILL.md +11 -11
- package/skills/task-orchestration/SKILL.md +11 -11
- package/skills/test-validation/SKILL.md +11 -11
package/.hook-test.mjs
CHANGED
|
@@ -113,7 +113,7 @@ async function standardConfig() {
|
|
|
113
113
|
return resolved.config
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
test('a task at its checkpoint with the right message commits', async () => {
|
|
116
|
+
test('a task at its checkpoint with the right message commits', async () => {
|
|
117
117
|
const config = await standardConfig()
|
|
118
118
|
const root = repo('allow', approvedTask({ flow: { flow: 'standard', version: 2, config } }))
|
|
119
119
|
try {
|
|
@@ -193,21 +193,21 @@ test('touching a sensitive path without a high-risk receipt is refused', async (
|
|
|
193
193
|
} finally {
|
|
194
194
|
rmSync(root, { recursive: true, force: true })
|
|
195
195
|
}
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
test('adaptive task can make its first CI commit before Sonar review', async () => {
|
|
199
|
-
const { adaptiveWorkflow } = await import('./lib/adaptive.js')
|
|
200
|
-
const config = adaptiveWorkflow('medium')
|
|
201
|
-
const root = repo('adaptive-sonar', approvedTask({
|
|
202
|
-
stage: '测试',
|
|
203
|
-
review: { outcome: 'pending' },
|
|
204
|
-
sonar_policy: { enabled: true, host_url: 'https://sonar.example.test', project_key: 'example', mode: 'branch', token_env: 'SONAR_TOKEN' },
|
|
205
|
-
flow: { flow: 'adaptive-medium', version: 1, config },
|
|
206
|
-
}))
|
|
207
|
-
try {
|
|
208
|
-
const result = tryCommit(root, 'first CI scan')
|
|
209
|
-
assert.equal(result.allowed, true, `the commit must precede its Sonar analysis: ${result.stderr}`)
|
|
210
|
-
} finally {
|
|
211
|
-
rmSync(root, { recursive: true, force: true })
|
|
212
|
-
}
|
|
213
|
-
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('adaptive task can make its first CI commit before Sonar review', async () => {
|
|
199
|
+
const { adaptiveWorkflow } = await import('./lib/adaptive.js')
|
|
200
|
+
const config = adaptiveWorkflow('medium')
|
|
201
|
+
const root = repo('adaptive-sonar', approvedTask({
|
|
202
|
+
stage: '测试',
|
|
203
|
+
review: { outcome: 'pending' },
|
|
204
|
+
sonar_policy: { enabled: true, host_url: 'https://sonar.example.test', project_key: 'example', mode: 'branch', token_env: 'SONAR_TOKEN' },
|
|
205
|
+
flow: { flow: 'adaptive-medium', version: 1, config },
|
|
206
|
+
}))
|
|
207
|
+
try {
|
|
208
|
+
const result = tryCommit(root, 'first CI scan')
|
|
209
|
+
assert.equal(result.allowed, true, `the commit must precede its Sonar analysis: ${result.stderr}`)
|
|
210
|
+
} finally {
|
|
211
|
+
rmSync(root, { recursive: true, force: true })
|
|
212
|
+
}
|
|
213
|
+
})
|
package/.resource-test.mjs
CHANGED
|
@@ -122,7 +122,7 @@ test('ledger shows absent storage as empty and propagates access errors',async()
|
|
|
122
122
|
controller.fs=()=>({resolve:async p=>p,listDir:async()=>{throw Object.assign(new Error('denied'),{code:'EACCES'})}})
|
|
123
123
|
await assert.rejects(()=>controller.readTasks('/project'),/denied/)
|
|
124
124
|
})
|
|
125
|
-
test('ledger rejects malformed records instead of pretending there are no tasks',async()=>{
|
|
125
|
+
test('ledger rejects malformed records instead of pretending there are no tasks',async()=>{
|
|
126
126
|
const {default:Controller}=await import('./lib/controller.js')
|
|
127
127
|
const controller=Object.create(Controller.prototype)
|
|
128
128
|
controller.authorizedPath=async p=>p
|
|
@@ -130,4 +130,21 @@ test('ledger rejects malformed records instead of pretending there are no tasks'
|
|
|
130
130
|
controller.fs=()=>({resolve:async p=>p,listDir:async()=>[{name:'task-broken.json'}],readText:async()=>content})
|
|
131
131
|
await assert.rejects(()=>controller.readTasks('/project'),/任务记录/)
|
|
132
132
|
}
|
|
133
|
-
})
|
|
133
|
+
})
|
|
134
|
+
test('ledger exposes Sonar findings without project credentials',async()=>{
|
|
135
|
+
const {default:Controller}=await import('./lib/controller.js')
|
|
136
|
+
const controller=Object.create(Controller.prototype)
|
|
137
|
+
controller.authorizedPath=async p=>p
|
|
138
|
+
controller.fs=()=>({resolve:async p=>p,listDir:async()=>[{name:'task-sample.json'}],readText:async()=>JSON.stringify({
|
|
139
|
+
id:'sample',title:'Sample',stage:'代码审核',branch:'feature/sample',items:[],
|
|
140
|
+
sonar_policy:{source:'ide-local',token_env:'SONAR_TOKEN',host_url:'https://sonar.example',project_key:'sample'},
|
|
141
|
+
sonar_audit:{gate:'ERROR',checked_at:'2026-10-03T00:00:00Z',target:'main',
|
|
142
|
+
findings:[{key:'issue-1',rule:'java:S103',message:'Line too long',severity:'MAJOR',file:'Sample.java',line:3}],
|
|
143
|
+
blocking:[{key:'issue-1'}],uncovered_files:[]},
|
|
144
|
+
})})
|
|
145
|
+
const {tasks}=await controller.readTasks('/project')
|
|
146
|
+
assert.equal(tasks[0].sonar.audit.gate,'ERROR')
|
|
147
|
+
assert.equal(tasks[0].sonar.audit.blocking_count,1)
|
|
148
|
+
assert.deepEqual(tasks[0].sonar.audit.findings[0],{key:'issue-1',rule:'java:S103',message:'Line too long',severity:'MAJOR',file:'Sample.java',line:3})
|
|
149
|
+
assert.equal(JSON.stringify(tasks).includes('SONAR_TOKEN'),false)
|
|
150
|
+
})
|
package/.roundtrip-test.mjs
CHANGED
|
@@ -7,18 +7,23 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import test from 'node:test'
|
|
9
9
|
import assert from 'node:assert/strict'
|
|
10
|
-
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
10
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
11
11
|
import { tmpdir } from 'node:os'
|
|
12
12
|
import { dirname, join, resolve, sep } from 'node:path'
|
|
13
13
|
import { resolveFlow, adoptRecommendation, compactProjectConfig, materializeBundledReferences } from './lib/workflows.js'
|
|
14
14
|
import Controller from './lib/controller.js'
|
|
15
15
|
import { assertAdvance, validateWorkflow } from './lib/engine.js'
|
|
16
|
-
|
|
16
|
+
// The npm package ships the built client, not its TypeScript source. Keep the
|
|
17
|
+
// source codec assertion in repository tests while allowing shipped tests to run.
|
|
18
|
+
const remoteSource = new URL('./src/client/remote.ts', import.meta.url)
|
|
19
|
+
const hasRemoteSource = existsSync(remoteSource)
|
|
20
|
+
const TYPERT_REMOTE = hasRemoteSource ? (await import(remoteSource.href)).TYPERT_REMOTE : undefined
|
|
17
21
|
|
|
18
22
|
/** The fields a project config can carry, so a round-trip is checked on all of them. */
|
|
19
23
|
const FIELDS = ['flow', 'stage_bindings', 'commit', 'artifacts', 'review_depth', 'commit_required']
|
|
20
24
|
|
|
21
|
-
test('browser remote codec retains source-qualified bindings and all saved fields',
|
|
25
|
+
test('browser remote codec retains source-qualified bindings and all saved fields',
|
|
26
|
+
{ skip: hasRemoteSource ? false : 'TypeScript source is excluded from the npm package' }, () => {
|
|
22
27
|
const write = TYPERT_REMOTE.descriptors.find(descriptor => descriptor.method === 'write')
|
|
23
28
|
const read = TYPERT_REMOTE.descriptors.find(descriptor => descriptor.method === 'read')
|
|
24
29
|
const adopted = compactProjectConfig(adoptRecommendation('minimal'))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { resolveSonarToken, sonarCredentialRef } from './lib/sonar-credential.js'
|
|
4
|
+
import Controller from './lib/controller.js'
|
|
5
|
+
|
|
6
|
+
test('Sonar credential references stay distinct for different workspaces', () => {
|
|
7
|
+
const qms = sonarCredentialRef('D:/projects/qms')
|
|
8
|
+
const eam = sonarCredentialRef('D:/projects/eam')
|
|
9
|
+
assert.match(qms, /^DSH_TASK_ENGINE_SONAR_[A-F0-9]{24}$/)
|
|
10
|
+
assert.notEqual(qms, eam)
|
|
11
|
+
assert.equal(qms, sonarCredentialRef('D:/projects/qms'))
|
|
12
|
+
assert.ok(!qms.includes('qms'))
|
|
13
|
+
if (process.platform === 'win32') assert.equal(qms, sonarCredentialRef('d:/PROJECTS/QMS'))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('Sonar token management keeps each workspace value private', async () => {
|
|
17
|
+
const stored = new Map()
|
|
18
|
+
const credentials = {
|
|
19
|
+
describe: async ref => ({ configured: stored.has(ref), writable: true, source: stored.has(ref) ? 'file' : undefined }),
|
|
20
|
+
set: async (ref, value) => { stored.set(ref, value) },
|
|
21
|
+
unset: async ref => { stored.delete(ref) },
|
|
22
|
+
resolve: async ref => stored.has(ref) ? { value: stored.get(ref), source: 'file' } : undefined,
|
|
23
|
+
}
|
|
24
|
+
const controller = Object.create(Controller.prototype)
|
|
25
|
+
controller.authorizedPath = async path => path
|
|
26
|
+
Object.defineProperty(controller, 'ctx', { value: { get: name => name === 'credentials' ? credentials : undefined } })
|
|
27
|
+
const qms = 'D:/projects/qms'
|
|
28
|
+
const eam = 'D:/projects/eam'
|
|
29
|
+
const result = await controller.setSonarToken({ path: qms, token: 'qms-secret' })
|
|
30
|
+
assert.equal(result.configured, true)
|
|
31
|
+
assert.ok(!JSON.stringify(result).includes('qms-secret'))
|
|
32
|
+
assert.equal((await controller.describeSonarToken(eam)).configured, false)
|
|
33
|
+
assert.equal((await credentials.resolve(sonarCredentialRef(qms))).value, 'qms-secret')
|
|
34
|
+
assert.equal(await resolveSonarToken(credentials, qms, 'old-env-token'), 'qms-secret')
|
|
35
|
+
assert.equal(await resolveSonarToken(credentials, eam, 'old-env-token'), 'old-env-token')
|
|
36
|
+
await controller.unsetSonarToken(qms)
|
|
37
|
+
assert.equal((await controller.describeSonarToken(qms)).configured, false)
|
|
38
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
import { changedLines, uncoveredLocalFiles } from './lib/sonarlint-local.js'
|
|
8
|
+
import { isIncludedAuditPath, validAuditIncludePaths } from './lib/sonar.js'
|
|
9
|
+
import { renderSonarReport, sonarReportPath } from './lib/sonar-report.js'
|
|
10
|
+
|
|
11
|
+
test('project scan roots mirror Maven backend scope without including the Vue app', () => {
|
|
12
|
+
const roots = ['fp-bussiness', 'fp-ruoyi', 'fp-ruoyi-spring-boot', 'pom.xml']
|
|
13
|
+
assert.equal(validAuditIncludePaths(roots), true)
|
|
14
|
+
assert.equal(isIncludedAuditPath('fp-bussiness/src/main/java/A.java', roots), true)
|
|
15
|
+
assert.equal(isIncludedAuditPath('fp-ruoyi-ui/src/views/auth.vue', roots), false)
|
|
16
|
+
assert.equal(validAuditIncludePaths(['../outside']), false)
|
|
17
|
+
assert.equal(validAuditIncludePaths(['C:/outside']), false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('one review gets a credential-free Markdown report path and issue details', () => {
|
|
21
|
+
const audit = { analysis_id: 'abc12345-0000', checked_at: '2026-10-03T10:11:12.000Z',
|
|
22
|
+
target: 'qdm_gw_chemical_260930', gate: 'ERROR', findings: [{key:'x',rule:'java:S103',
|
|
23
|
+
severity:'MAJOR',file:'A.java',line:12,message:'Line too long'}], blocking:[{key:'x'}],
|
|
24
|
+
scanned_files:['A.java'],uncovered_files:[] }
|
|
25
|
+
const path = sonarReportPath('QMS-1', audit)
|
|
26
|
+
assert.match(path, /^\.dsh\/reviews\/QMS-1\/2026-/)
|
|
27
|
+
const report = renderSonarReport('QMS-1', {source:'ide-local',project_key:'project',include_paths:['fp-bussiness']}, audit)
|
|
28
|
+
assert.match(report, /java:S103/)
|
|
29
|
+
assert.match(report, /A.java:12/)
|
|
30
|
+
assert.equal(report.includes('token-secret'),false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('unsupported files block only when the project has active rules for their language', () => {
|
|
34
|
+
const paths = ['migration.sql', 'settings.json', 'script.py']
|
|
35
|
+
const profiles = [
|
|
36
|
+
{ language: 'java', activeRuleCount: 66 },
|
|
37
|
+
{ language: 'json', activeRuleCount: 0 },
|
|
38
|
+
{ language: 'py', activeRuleCount: 3 },
|
|
39
|
+
]
|
|
40
|
+
assert.deepEqual(uncoveredLocalFiles(paths, profiles), ['script.py'])
|
|
41
|
+
assert.deepEqual(uncoveredLocalFiles(paths, [...profiles, { language: 'plsql', activeRuleCount: 5 }]),
|
|
42
|
+
['migration.sql', 'script.py'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('local Sonar audit considers Git new code but excludes generated DSH records', async () => {
|
|
46
|
+
const root = mkdtempSync(join(tmpdir(), 'dsh-sonarlint-diff-test-'))
|
|
47
|
+
const git = (...args) => execFileSync('git', args, { cwd: root, stdio: 'pipe' })
|
|
48
|
+
try {
|
|
49
|
+
git('init', '--quiet')
|
|
50
|
+
writeFileSync(join(root, 'Sample.java'), 'class Sample {}\n')
|
|
51
|
+
git('add', 'Sample.java')
|
|
52
|
+
git('-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', 'commit', '--quiet', '-m', 'baseline')
|
|
53
|
+
writeFileSync(join(root, 'Sample.java'), 'class Sample {}\n// new code\n')
|
|
54
|
+
writeFileSync(join(root, 'change.sql'), 'SELECT 1;\n')
|
|
55
|
+
mkdirSync(join(root, '.dsh'))
|
|
56
|
+
writeFileSync(join(root, '.dsh', 'task.json'), '{"generated":true}\n')
|
|
57
|
+
const changed = await changedLines(root, 'HEAD')
|
|
58
|
+
assert.deepEqual(changed.map(file => file.path).sort(), ['Sample.java', 'change.sql'])
|
|
59
|
+
assert.deepEqual([...changed.find(file => file.path === 'Sample.java').lines], [2])
|
|
60
|
+
assert.equal(changed.find(file => file.path === 'change.sql').newFile, true)
|
|
61
|
+
assert.deepEqual((await changedLines(root, 'HEAD', ['Sample.java'])).map(file => file.path), ['Sample.java'])
|
|
62
|
+
} finally {
|
|
63
|
+
rmSync(root, { recursive: true, force: true })
|
|
64
|
+
}
|
|
65
|
+
})
|
package/.workflow-test.mjs
CHANGED
|
@@ -71,9 +71,10 @@ function fixture(stage = '开发', extra = {}, services = {}) {
|
|
|
71
71
|
const ctx = { fs, tools: { register(tool) { execute = tool.execute; return () => {} } }, get(name) {
|
|
72
72
|
if (Object.hasOwn(services, name)) return services[name]
|
|
73
73
|
if (name === 'sandboxPolicy') return { resolve(request) { assert.equal(request.session, session); return policy } }
|
|
74
|
-
if (name === 'shell') return { resolve(request) { runs.push(request); return request }, async
|
|
75
|
-
const fail = request.command === 'fail'
|
|
76
|
-
|
|
74
|
+
if (name === 'shell') return { resolve(request) { runs.push(request); return request }, async execute(request) {
|
|
75
|
+
const fail = request.command === 'fail'
|
|
76
|
+
const outcome = { exitCode: fail ? 1 : 0, timedOut: false, aborted: false, sandbox: { mode: 'workspace-write', denied: false }, stdout: { text: request.command.startsWith('git -c core.quotepath=false log') ? 'abcdef1234567890\n【LIVE-1】【TASK】done\n\napp.js\n' : 'checks passed' }, stderr: { text: '' } }
|
|
77
|
+
return { result: async () => outcome }
|
|
77
78
|
} }
|
|
78
79
|
return undefined
|
|
79
80
|
} }
|
|
@@ -193,7 +194,7 @@ test('审查通过后更新状态并激活下一项,保留已完成项的审
|
|
|
193
194
|
await assert.rejects(f.call({ operation: 'items', items: [{ id: 'A', title: 'x' }, { id: 'A', title: 'y' }] }), /unique/)
|
|
194
195
|
})
|
|
195
196
|
|
|
196
|
-
test('追加修复项可按 id 保留旧标题,拒绝已完成项改名后静默丢审核', async () => {
|
|
197
|
+
test('追加修复项可按 id 保留旧标题,拒绝已完成项改名后静默丢审核', async () => {
|
|
197
198
|
const f = fixture()
|
|
198
199
|
await f.call({ operation: 'items', items: [{ id: 'A', title: 'reviewed models', status: 'doing' }] })
|
|
199
200
|
await f.call({ operation: 'review_item', item_id: 'A', spec_outcome: 'pass', quality_outcome: 'pass' })
|
|
@@ -203,8 +204,24 @@ test('追加修复项可按 id 保留旧标题,拒绝已完成项改名后静
|
|
|
203
204
|
await assert.rejects(f.call({ operation: 'items', items: [{ id: 'A', title: 'rewritten summary', status: 'done' }, { id: 'B', status: 'doing' }] }), /omit title|reopen/)
|
|
204
205
|
assert.equal(f.state().items[0].review.quality.outcome, 'pass')
|
|
205
206
|
assert.equal(f.state().items.length, 2)
|
|
206
|
-
await assert.rejects(f.call({ operation: 'items', items: [{ id: 'NEW' }] }), /title/)
|
|
207
|
-
})
|
|
207
|
+
await assert.rejects(f.call({ operation: 'items', items: [{ id: 'NEW' }] }), /title/)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('重排实施项时不静默丢弃已完成项和审查记录', async () => {
|
|
211
|
+
const f = fixture()
|
|
212
|
+
await f.call({ operation: 'items', items: [
|
|
213
|
+
{ id: 'I1', title: 'models', status: 'doing' },
|
|
214
|
+
{ id: 'I2', title: 'mapper', status: 'todo' },
|
|
215
|
+
] })
|
|
216
|
+
await f.call({ operation: 'dispatch', item_id: 'I1', description: 'direct implementation' })
|
|
217
|
+
await f.call({ operation: 'review_item', item_id: 'I1', spec_outcome: 'pass', quality_outcome: 'pass' })
|
|
218
|
+
await f.call({ operation: 'items', items: [{ id: 'I1', status: 'done' }, { id: 'I2' }] })
|
|
219
|
+
await f.call({ operation: 'items', items: [{ id: 'I3', title: 'service', status: 'todo' }] })
|
|
220
|
+
assert.deepEqual(f.state().items.map(item => item.id), ['I3', 'I1'])
|
|
221
|
+
assert.equal(f.state().items[1].status, 'done')
|
|
222
|
+
assert.equal(f.state().items[1].review.quality.outcome, 'pass')
|
|
223
|
+
assert.equal(f.state().items[1].dispatch.description, 'direct implementation')
|
|
224
|
+
})
|
|
208
225
|
|
|
209
226
|
test('派发立即反映进行中,重派不复用旧审核且保持单一进行项', async () => {
|
|
210
227
|
const f = fixture()
|
|
@@ -411,14 +428,14 @@ test('同名技能与规则在项目和个人目录各自保留管理入口', as
|
|
|
411
428
|
assert.match(read.content, /Run tests/)
|
|
412
429
|
})
|
|
413
430
|
|
|
414
|
-
test('安装包注册会话编排和元技能,阶段目录只隐藏会话编排技能', async () => {
|
|
431
|
+
test('安装包注册会话编排和元技能,阶段目录只隐藏会话编排技能', async () => {
|
|
415
432
|
const registered = []
|
|
416
433
|
registerShippedSkills({ get: name => name === 'skills' ? { register: skill => { registered.push(skill); return () => {} } } : undefined })
|
|
417
|
-
const expected = ['architecture-design', 'code-development', 'code-review', 'eng-delivery',
|
|
418
|
-
'requirements-analysis', 'task-orchestration', 'test-validation']
|
|
434
|
+
const expected = ['architecture-design', 'code-development', 'code-review', 'eng-delivery',
|
|
435
|
+
'requirements-analysis', 'task-orchestration', 'test-validation']
|
|
419
436
|
assert.deepEqual(registered.map(skill => skill.name).sort(), expected)
|
|
420
437
|
assert.ok(registered.every(skill => skill.content.trim().length > 0))
|
|
421
438
|
const catalog = await Controller.prototype.listSkills.call({ authorizedPath: async path => path }, '')
|
|
422
|
-
assert.deepEqual(catalog.skills.filter(skill => skill.source === 'bundled').map(skill => skill.name).sort(),
|
|
423
|
-
expected.filter(name => name !== 'eng-delivery'))
|
|
424
|
-
})
|
|
439
|
+
assert.deepEqual(catalog.skills.filter(skill => skill.source === 'bundled').map(skill => skill.name).sort(),
|
|
440
|
+
expected.filter(name => name !== 'eng-delivery'))
|
|
441
|
+
})
|
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<img src="docs/assets/workflow-banner.svg" alt="DSH Task Engine:DeepSeek Harness 的个人工程流程工作台,标准流程从需求评审走向完成" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h1 align="center">DSH Task Engine</h1>
|
|
6
|
-
|
|
7
|
-
> `0.29.0` 新增自适应流程、项目技能初始化与可选 SonarQube CI 审核。SonarQube 集成目前读取提交后的 CI 分析结果,不提供未提交代码的本地 Sonar 审核。
|
|
5
|
+
<h1 align="center">DSH Task Engine</h1>
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
> 自适应流程、项目技能初始化与可选 SonarQube 审核支持复用 CI 分析、上传式本机扫描,以及未提交代码的本地规则审核。新代码范围由参考分支定义。
|
|
8
|
+
|
|
9
|
+
<p align="center">按每个需求选择工程路径,用项目 Skill 与 Rule 复用团队开发规范。</p>
|
|
10
|
+
<p align="center"><sub>Task-scoped engineering workflows for DeepSeek Harness.</sub></p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a href="https://www.npmjs.com/package/@godv61/dsh-task-engine"><img src="https://img.shields.io/npm/v/%40godv61%2Fdsh-task-engine?style=flat-square&color=238636" alt="npm version" /></a>
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
|
|
27
27
|
## 为什么用它
|
|
28
28
|
|
|
29
|
-
让 AI 在持续开发的项目中处理不同需求时,按这次需求的复杂度选择低、中、高、超高四档任务流程。元技能规定交接产物,项目 Skill/Rule 承载团队编码规范;同一项目下的多个会话可以各自推进不同任务。
|
|
29
|
+
让 AI 在持续开发的项目中处理不同需求时,按这次需求的复杂度选择低、中、高、超高四档任务流程。元技能规定交接产物,项目 Skill/Rule 承载团队编码规范;同一项目下的多个会话可以各自推进不同任务。
|
|
30
30
|
|
|
31
|
-
- **知道下一步做什么**:任务创建时选择复杂度并冻结流程,当前阶段的条件和产物清楚可查。
|
|
31
|
+
- **知道下一步做什么**:任务创建时选择复杂度并冻结流程,当前阶段的条件和产物清楚可查。
|
|
32
32
|
- **复用自己的工作方法**:把技能和规则安装到项目或个人目录,将规则配置在技能下,再把技能挂到节点上;同一技能在任何节点都使用同一套约束。
|
|
33
33
|
- **找得到过程记录**:任务台账集中查看阶段、实施项、验证与审核状态。
|
|
34
34
|
|
|
@@ -40,37 +40,37 @@
|
|
|
40
40
|
dsh plugin --profile web add @godv61/dsh-task-engine
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
1. 重启 Harness Web,打开侧边栏的 **工程任务**。
|
|
44
|
-
2. 选择工作区,在 **自适应流程** 中查看四档路径,按需挂载项目技能。
|
|
43
|
+
1. 重启 Harness Web,打开侧边栏的 **工程任务**。
|
|
44
|
+
2. 选择工作区,在 **自适应流程** 中查看四档路径,按需挂载项目技能。
|
|
45
45
|
3. 新建会话,选择 **工程化开发引擎** 预设,描述要完成的开发任务。
|
|
46
46
|
|
|
47
|
-
看到“工程任务”入口和“工程化开发引擎”会话预设,就说明工作台与任务工具已接入。使用 Harness 源码启动的安装方式见[安装与启用](docs/getting-started.md)。
|
|
47
|
+
看到“工程任务”入口和“工程化开发引擎”会话预设,就说明工作台与任务工具已接入。使用 Harness 源码启动的安装方式见[安装与启用](docs/getting-started.md)。
|
|
48
48
|
|
|
49
|
-
> 工程化会话先分析需求复杂度,再以 `dev_task assess` 预览并创建任务;`.dsh/meta.json` 只配置元技能挂载和可选 SonarQube 审核,不强迫项目所有会话走同一条流程。
|
|
49
|
+
> 工程化会话先分析需求复杂度,再以 `dev_task assess` 预览并创建任务;`.dsh/meta.json` 只配置元技能挂载和可选 SonarQube 审核,不强迫项目所有会话走同一条流程。
|
|
50
50
|
|
|
51
51
|
## 工作台里有什么
|
|
52
52
|
|
|
53
53
|
| 页面 | 你可以做什么 |
|
|
54
54
|
| :--- | :--- |
|
|
55
|
-
| **项目初始化** | 分开管理 `AGENTS.md` 与项目 Skill/Rule。后者可在页面复制初始化请求,发给“工程化开发引擎”会话后由 `dev_task init_project` 扫描、预览并应用。 |
|
|
56
|
-
| **自适应流程** | 查看四档任务路径、元技能绑定与可选 SonarQube
|
|
57
|
-
| **任务台账** |
|
|
55
|
+
| **项目初始化** | 分开管理 `AGENTS.md` 与项目 Skill/Rule。后者可在页面复制初始化请求,发给“工程化开发引擎”会话后由 `dev_task init_project` 扫描、预览并应用。 |
|
|
56
|
+
| **自适应流程** | 查看四档任务路径、元技能绑定与可选 SonarQube 审核配置;按项目保存 Token 到本机 DSH 凭据存储。 |
|
|
57
|
+
| **任务台账** | 查看实施、验证和审核记录;展开 SonarQube 结果可看触发的规则、问题位置和逐次审核文件。 |
|
|
58
58
|
| **技能** | 安装、编辑技能,并集中维护每个技能唯一的规则列表。 |
|
|
59
59
|
| **规则** | 选择 Markdown 文件安装规则,维护项目或个人开发约定。 |
|
|
60
60
|
|
|
61
|
-
插件内置会话编排 Skill `eng-delivery` 和六个通用元技能 Skill。项目知识、领域编码方法和 Rule 由使用者创建或通过 init 生成,可放在项目或个人目录;同名项目 Skill 在新任务中覆盖用户级 Skill。
|
|
62
|
-
|
|
63
|
-
新路径的四档顺序、元技能交接契约、`init_project` 和可选 SonarQube 审核见[自适应工程任务](docs/adaptive-workflows.md)。
|
|
61
|
+
插件内置会话编排 Skill `eng-delivery` 和六个通用元技能 Skill。项目知识、领域编码方法和 Rule 由使用者创建或通过 init 生成,可放在项目或个人目录;同名项目 Skill 在新任务中覆盖用户级 Skill。
|
|
62
|
+
|
|
63
|
+
新路径的四档顺序、元技能交接契约、`init_project` 和可选 SonarQube 审核见[自适应工程任务](docs/adaptive-workflows.md)。
|
|
64
64
|
|
|
65
|
-
**流程与工作方法各有职责。** 四档流程规定阶段与门禁,并在相应阶段加载内置元技能;项目业务技能和规则由使用者配置。同一条规则可由多个技能共享。点击元技能的“配置核心 Skill 的 Rule”,内置或用户级 Skill 可在保存时生成同名项目副本,再挂载项目 Rule。任务的流程与资源引用在创建时确定;Skill/Rule 正文在每次交互读取最新版本,创建时副本仅用于审计。[自适应流程说明](docs/adaptive-workflows.md)
|
|
65
|
+
**流程与工作方法各有职责。** 四档流程规定阶段与门禁,并在相应阶段加载内置元技能;项目业务技能和规则由使用者配置。同一条规则可由多个技能共享。点击元技能的“配置核心 Skill 的 Rule”,内置或用户级 Skill 可在保存时生成同名项目副本,再挂载项目 Rule。任务的流程与资源引用在创建时确定;Skill/Rule 正文在每次交互读取最新版本,创建时副本仅用于审计。[自适应流程说明](docs/adaptive-workflows.md)
|
|
66
66
|
|
|
67
|
-
已有任务仍按创建时的快照运行;旧版 `.dsh/eng.json` 也继续可读。工作台只展示自适应流程,旧版配置细节保存在[历史文档](docs/configuration.md)。
|
|
67
|
+
已有任务仍按创建时的快照运行;旧版 `.dsh/eng.json` 也继续可读。工作台只展示自适应流程,旧版配置细节保存在[历史文档](docs/configuration.md)。
|
|
68
68
|
|
|
69
69
|
## 把自己的技能和规则带进来
|
|
70
70
|
|
|
71
71
|
**选择文件 → 检查预览 → 选择范围 → 确认安装 → 挂到阶段。**
|
|
72
72
|
|
|
73
|
-
技能选择含 `SKILL.md` 的文件夹,规则选择一个 `.md` 文件。预览会显示名称、正文、文件数、体积和目标路径;目标目录中已有同名资源不会被覆盖。项目级 Skill 可与内置 Skill 同名,以项目版本优先生效。
|
|
73
|
+
技能选择含 `SKILL.md` 的文件夹,规则选择一个 `.md` 文件。预览会显示名称、正文、文件数、体积和目标路径;目标目录中已有同名资源不会被覆盖。项目级 Skill 可与内置 Skill 同名,以项目版本优先生效。
|
|
74
74
|
|
|
75
75
|
| 安装范围 | 存放位置 | 用途 |
|
|
76
76
|
| :--- | :--- | :--- |
|
|
@@ -86,8 +86,8 @@ dsh plugin --profile web add @godv61/dsh-task-engine
|
|
|
86
86
|
| 想了解什么 | 从这里开始 |
|
|
87
87
|
| :--- | :--- |
|
|
88
88
|
| 安装、启用与第一次使用 | [快速上手](docs/getting-started.md) |
|
|
89
|
-
| 任务级流程、元技能与 SonarQube | [自适应工程任务](docs/adaptive-workflows.md) |
|
|
90
|
-
| 旧项目配置与阶段绑定 | [流程配置](docs/configuration.md) |
|
|
89
|
+
| 任务级流程、元技能与 SonarQube | [自适应工程任务](docs/adaptive-workflows.md) |
|
|
90
|
+
| 旧项目配置与阶段绑定 | [流程配置](docs/configuration.md) |
|
|
91
91
|
| 技能文件夹与 Markdown 规则 | [资源安装](docs/resource-install.md) |
|
|
92
92
|
| 完整操作说明 | [HTML 手册](docs/manual.html)(下载后在浏览器打开) |
|
|
93
93
|
| 当前能力与常见问题 | [常见问题](docs/faq.md) |
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,28 +4,42 @@
|
|
|
4
4
|
|
|
5
5
|
按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
|
|
6
6
|
|
|
7
|
-
## 0.29.
|
|
7
|
+
## 0.29.8(2026-10-04)
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- 任务台账将实施开始与子代理派发区分开,待办、实施中和缺少审查记录分别显示明确状态。
|
|
10
|
+
- 更新实施项列表时自动保留已完成或已有实施/审查记录的项目,避免重排任务时静默丢失审计。
|
|
11
|
+
- 高/超高任务在离开“任务编排”前核对 `task-plan.steps` 中逐行列出的实施项 ID 与台账 `items`,缺项或多项会阻止流转。
|
|
12
|
+
- `init_project` 的项目地图草稿必须覆盖扫描发现的主要模块和构建清单;仓库结构变化会使旧预览哈希失效。应用结果列出应纳入 Git 的团队 Skill、Rule 和 `.dsh/meta.json`。
|
|
13
|
+
- Sonar 审核及任务状态按服务器规则汇总尚未沉淀的失败案例,供人工判断是否用 `learn_rule` 写成可复用规范;误报不会自动进入 Rule。
|
|
14
|
+
- 任务台账读取超过 15 秒会显示可重试错误,避免服务重启后的旧页面一直停留在“读取中”。
|
|
11
15
|
|
|
12
|
-
## 0.29.
|
|
16
|
+
## 0.29.7(2026-10-03)
|
|
13
17
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
## 0.29.
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
## 0.29.
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
18
|
+
- 修复 DSH 0.2 的 Shell 接口适配:验证命令和可选本地 Sonar 扫描通过 `execute(...).result()` 取得结果,并兼容旧接口。
|
|
19
|
+
- 项目初始化明确要求 `project-map` 覆盖整个仓库;扫描仅提供证据和候选名称,Skill 正文由模型提案、经用户审阅后写入。
|
|
20
|
+
|
|
21
|
+
## 0.29.3(2026-10-01)
|
|
22
|
+
|
|
23
|
+
- “项目初始化”页明确区分项目 Skill/Rule 初始化与 `AGENTS.md` 生成,提供可复制的工程会话请求;“自适应流程”页增加直达入口。
|
|
24
|
+
- 说明 Web 服务更新后旧页面可能失去交互,需要刷新页面;规则配置按钮在新页面可打开编辑窗口。
|
|
25
|
+
|
|
26
|
+
## 0.29.2(2026-10-01)
|
|
27
|
+
|
|
28
|
+
- “配置核心 Skill 的 Rule”现在可打开内置或用户级核心 Skill 的规则编辑器;保存时创建同名项目 Skill 并挂载 Rule,创建前不会修改项目文件。
|
|
29
|
+
- 工作台移除“旧版流程”标签;既有任务快照和 `.dsh/eng.json` 继续可读。
|
|
30
|
+
|
|
31
|
+
## 0.29.1(2026-09-30)
|
|
32
|
+
|
|
33
|
+
- 修复 0.29.0 浏览器 Remote 清单漏掉四个自适应配置接口,导致“工程任务”工作台打开时报 `remote.readAdaptive is not a function` 的问题。
|
|
34
|
+
- DSH 兼容检查现在比对 Host 暴露的方法与浏览器描述符,防止同类遗漏再次发布。
|
|
35
|
+
|
|
36
|
+
## 0.29.0(2026-09-30)
|
|
37
|
+
|
|
38
|
+
- 新任务可按低、中、高、超高复杂度选择任务级流程;内置六个元技能及交接契约,旧 `.dsh/eng.json` 与旧任务快照兼容。
|
|
39
|
+
- 项目、Codex 项目、用户、内置同名 Skill 按该顺序解析;生效 Skill 自带 Rule 档案,`.dsh/meta.json` 挂载附加 Skill。
|
|
40
|
+
- `init_project` 扫描项目结构与依赖清单,分预览与应用两步生成多个项目 Skill/Rule。
|
|
41
|
+
- 可选 SonarQube 审核在代码审核阶段读取已提交代码的 CI 分析结果;失败案例经预览可成为项目 Rule,修复后需重新测试与扫描。当前不支持未提交代码的本地 Sonar 审核。
|
|
42
|
+
- 工作台新增“自适应流程”,旧版流程保留为兼容入口;提交钩子按任务快照判断,代码审核阶段由任务工具检查已启用的 SonarQube 审核。
|
|
29
43
|
|
|
30
44
|
## 0.28.0
|
|
31
45
|
|
package/docs/README.md
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[← 返回项目首页](../README.md)
|
|
4
4
|
|
|
5
|
-
DSH Task Engine 是 DeepSeek Harness 的工程任务工作台。先完成安装,再按需要配置技能和规则。`0.29.0`
|
|
5
|
+
DSH Task Engine 是 DeepSeek Harness 的工程任务工作台。先完成安装,再按需要配置技能和规则。`0.29.0` 起提供任务级流程、项目初始化与可选 SonarQube 审核。
|
|
6
6
|
|
|
7
7
|
## 开始使用
|
|
8
8
|
|
|
9
9
|
| 文档 | 内容 |
|
|
10
10
|
| :--- | :--- |
|
|
11
|
-
| [安装与启用](getting-started.md) | 安装到 Web profile、启用工程会话、确认安装结果。 |
|
|
12
|
-
| [自适应工程任务](adaptive-workflows.md) | 四档任务流程、元技能、项目 init 与可选 SonarQube
|
|
13
|
-
| [旧版流程配置](configuration.md) | 旧三流程、阶段绑定、配置文件与任务快照。 |
|
|
11
|
+
| [安装与启用](getting-started.md) | 安装到 Web profile、启用工程会话、确认安装结果。 |
|
|
12
|
+
| [自适应工程任务](adaptive-workflows.md) | 四档任务流程、元技能、项目 init 与可选 SonarQube;支持 CI 结果、上传式扫描和未提交代码的本地规则审核。 |
|
|
13
|
+
| [旧版流程配置](configuration.md) | 旧三流程、阶段绑定、配置文件与任务快照。 |
|
|
14
14
|
| [技能与规则安装](resource-install.md) | 系统文件选择、安装预览、项目/个人范围和格式要求。 |
|
|
15
15
|
| [常见问题](faq.md) | 预设区别、资源使用、项目目录与能力限制。 |
|
|
16
|
-
| [完整 HTML 手册](manual.html) | 当前功能与旧版兼容说明;下载后在浏览器中打开。 |
|
|
16
|
+
| [完整 HTML 手册](manual.html) | 当前功能与旧版兼容说明;下载后在浏览器中打开。 |
|
|
17
17
|
|
|
18
18
|
## 参与开发
|
|
19
19
|
|
|
@@ -39,4 +39,4 @@ DSH Task Engine 是 DeepSeek Harness 的工程任务工作台。先完成安装
|
|
|
39
39
|
| [0.23.0 验证记录](testing/0.23.0/测试报告.md) | 资源导入、工作台交互与包入口的验证。 |
|
|
40
40
|
| [0.23.0 发布说明](releases/0.23.0.md) | 早期安装交互及界面调整。 |
|
|
41
41
|
|
|
42
|
-
> 0.23.3 起的版本变化全部记录在[更新日志](CHANGELOG.md)中,不再单独出具发布说明。
|
|
42
|
+
> 0.23.3 起的版本变化全部记录在[更新日志](CHANGELOG.md)中,不再单独出具发布说明。
|