@godv61/dsh-task-engine 0.23.5 → 0.23.7

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.
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Tests for the `eng` preset derivation.
3
+ *
4
+ * The preset must track the RUNNING harness's `standard`, because a copy that
5
+ * freezes once goes stale the moment DSH edits its own composition, and a stale
6
+ * preset fails to load entirely. Two real regressions motivate these cases:
7
+ *
8
+ * - DSH 2026-09-06 moved the persona row from `text:` to `prefix`/`suffix`.
9
+ * - DSH 2026-09-13 replaced the `workflow-worker-thread` row with
10
+ * `workflow-ptc`; the plugin kept emitting the removed row, whose package
11
+ * had also left `apps/cli`'s dependencies, so the roster could not resolve
12
+ * it and reported "加载失败" for the whole preset.
13
+ *
14
+ * Run: `node --test .preset-test.mjs`
15
+ * @module dsh-task-engine/.preset-test
16
+ */
17
+
18
+ import assert from 'node:assert/strict'
19
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
20
+ import { tmpdir } from 'node:os'
21
+ import { join, resolve, sep } from 'node:path'
22
+ import { test } from 'node:test'
23
+ import { derivedEngComposition, wasSeeded, applyPersona, personaFieldOf } from './lib/seed-preset.js'
24
+
25
+ /**
26
+ * The live derivation resolves `@deepseek-ai/dsh-agent-presets`, a PEER the
27
+ * running harness provides. In a consumer's install it may be absent or a
28
+ * different version, so the assertions that read a real `standard` skip when it
29
+ * is unavailable rather than failing �?the fixture-driven cases below cover the
30
+ * logic itself and always run.
31
+ */
32
+ const derived = derivedEngComposition()
33
+ const liveSource = derived !== undefined
34
+ const live = { skip: liveSource ? false : 'no resolvable @deepseek-ai/dsh-agent-presets in this install' }
35
+
36
+ test('the derivation reads a readable standard composition', live, () => {
37
+ assert.ok(derived !== undefined, 'standard must be resolvable when a peer provides it')
38
+ assert.ok(derived.text.length > 0, 'the derived composition must not be empty')
39
+ })
40
+
41
+ test('the engineering persona replaces the shipped one', live, () => {
42
+ assert.equal(derived.personaApplied, true, 'the persona row must be found and rewritten')
43
+ assert.match(derived.text, /engineering-delivery coding agent/u, 'the persona body must be the engineering one')
44
+ // The shipped prose must be gone, in whichever field the source used.
45
+ assert.doesNotMatch(derived.text, /You are a coding agent powered by the \{\{model\}\} model/u,
46
+ 'the shipped persona prose must be replaced, not merely shadowed')
47
+ })
48
+
49
+ test('the derived persona row is well formed and never sets complete', live, () => {
50
+ // Which field is correct depends on the running harness, so assert the SHAPE
51
+ // (a block scalar under `config:`) rather than one field name �?the field
52
+ // itself is pinned by the two fixtures below.
53
+ assert.match(derived.text, /- id: persona\n {2}name: '@deepseek-ai\/dsh-persona'\n {2}config:\n(?: {4}suffix: .*\n)? {4}(?:text|prefix): [|>]-/u,
54
+ 'the persona row must carry a block scalar under config')
55
+ // `complete: true` would make this prose the ENTIRE system prompt and
56
+ // suppress every other section, silently gutting the agent.
57
+ assert.doesNotMatch(derived.text, /complete: true/u, 'complete must not be set')
58
+ })
59
+
60
+ test('the agent row is appended exactly once', live, () => {
61
+ const matches = derived.text.match(/id: task-engine-agent/gu) ?? []
62
+ assert.equal(matches.length, 1, 'task-engine-agent must appear once')
63
+ assert.match(derived.text, /name: '@godv61\/dsh-task-engine\/agent'/u, 'the row must name the agent entry')
64
+ })
65
+
66
+ test('every top-level row of standard survives the derivation', live, () => {
67
+ // The point of deriving rather than hand-writing: the preset must inherit the
68
+ // harness's whole tool roster, not a remembered subset of it.
69
+ const ids = (text) => (text.match(/^- id: (\S+)$/gmu) ?? []).map(line => line.replace(/^- id: /u, ''))
70
+ const derivedIds = ids(derived.text)
71
+ assert.ok(derivedIds.length > 10, `expected the full roster, saw ${derivedIds.length} rows`)
72
+ assert.ok(derivedIds.includes('persona'), 'persona must be present')
73
+ assert.equal(new Set(derivedIds).size, derivedIds.length, 'row ids must be unique')
74
+ })
75
+
76
+ test('a composition carrying the agent row is recognised as seeded', () => {
77
+ assert.equal(wasSeeded(" - id: task-engine-agent\n name: 'x'\n"), true, 'the agent row marks a seeded file')
78
+ assert.equal(wasSeeded('- id: persona\n name: y\n'), false, 'a hand-authored composition is not ours')
79
+ })
80
+
81
+ test('re-deriving is stable: the same standard yields the same text', live, () => {
82
+ // Idempotence is what lets the seed run on every boot without rewriting.
83
+ const again = derivedEngComposition()
84
+ assert.equal(again.text, derived.text, 'two derivations of one source must agree')
85
+ })
86
+
87
+ // The persona plugin renamed `text` to `prefix`/`suffix` in DSH commit 40792330c0
88
+ // (2026-09-06, first shipped in dsh-v0.1.3-alpha.2). The two schemas are mutually
89
+ // exclusive: a preset written with `prefix` is rejected by an older persona
90
+ // plugin with "$.text missing required value", and vice versa. The seed must
91
+ // therefore follow whichever field the SOURCE composition uses, which is what
92
+ // these two fixtures pin.
93
+ const PERSONA_FIXTURE_LEGACY = [
94
+ '- id: persona',
95
+ " name: '@deepseek-ai/dsh-persona'",
96
+ ' config:',
97
+ ' text: >-',
98
+ ' You are a coding agent powered by the {{model}} model.',
99
+ '',
100
+ '- id: agent-instructions',
101
+ " name: '@deepseek-ai/dsh-agent-instructions'",
102
+ '',
103
+ ].join('\n')
104
+
105
+ const PERSONA_FIXTURE_MODERN = [
106
+ '- id: persona',
107
+ " name: '@deepseek-ai/dsh-persona'",
108
+ ' config:',
109
+ ' suffix: Your working directory is {{cwd}}.',
110
+ ' prefix: >-',
111
+ ' You are a coding agent powered by the {{model}} model.',
112
+ '',
113
+ '- id: agent-instructions',
114
+ " name: '@deepseek-ai/dsh-agent-instructions'",
115
+ '',
116
+ ].join('\n')
117
+
118
+ /** Read the scalar keys of a persona row's `config:` mapping. */
119
+ function personaConfig(text) {
120
+ const lines = text.split('\n')
121
+ const start = lines.findIndex(line => line.trimEnd() === '- id: persona')
122
+ assert.ok(start >= 0, 'the fixture must carry a persona row')
123
+ let end = start + 1
124
+ while (end < lines.length && !/^- /u.test(lines[end] ?? '')) end++
125
+ const config = {}
126
+ for (const line of lines.slice(start, end)) {
127
+ const match = /^ {4}([a-z]+):(.*)$/u.exec(line)
128
+ if (match !== null) config[match[1]] = match[2].trim()
129
+ }
130
+ return config
131
+ }
132
+
133
+ test('the persona field follows the source schema, both generations', () => {
134
+ assert.equal(personaFieldOf(PERSONA_FIXTURE_LEGACY), 'text', 'a text-based source is detected')
135
+ assert.equal(personaFieldOf(PERSONA_FIXTURE_MODERN), 'prefix', 'a prefix-based source is detected')
136
+
137
+ const legacy = applyPersona(PERSONA_FIXTURE_LEGACY, 'ENGINEER')
138
+ assert.equal(legacy.applied, true, 'the legacy persona row is rewritten')
139
+ const legacyConfig = personaConfig(legacy.text)
140
+ assert.equal(legacyConfig.text, '>-', 'the legacy shape keeps `text`, never `prefix`')
141
+ assert.equal(legacyConfig.prefix, undefined, 'no prefix field is introduced for an old harness')
142
+
143
+ const modern = applyPersona(PERSONA_FIXTURE_MODERN, 'ENGINEER')
144
+ const modernConfig = personaConfig(modern.text)
145
+ assert.equal(modernConfig.prefix, '>-', 'the modern shape uses `prefix`')
146
+ assert.equal(modernConfig.text, undefined, 'no text field is introduced for a new harness')
147
+ assert.equal(modernConfig.suffix, 'Your working directory is {{cwd}}.', 'the shipped suffix is carried over')
148
+ })
149
+
150
+ test('the rewritten persona row keeps valid indentation for both shapes', () => {
151
+ // A mis-indented `suffix:` under `config:` silently changes the YAML shape,
152
+ // so assert the exact lines rather than trusting the mapping parse alone.
153
+ const modern = applyPersona(PERSONA_FIXTURE_MODERN, 'ENGINEER').text
154
+ assert.match(modern, /- id: persona\n {2}name: '@deepseek-ai\/dsh-persona'\n {2}config:\n {4}suffix: /u,
155
+ 'suffix sits at four spaces under config')
156
+ assert.match(modern, /\n {4}prefix: [|>]-\n {6}ENGINEER\n/u, 'prefix and its body are indented correctly')
157
+
158
+ const legacy = applyPersona(PERSONA_FIXTURE_LEGACY, 'ENGINEER').text
159
+ assert.match(legacy, /- id: persona\n {2}name: '@deepseek-ai\/dsh-persona'\n {2}config:\n {4}text: [|>]-\n {6}ENGINEER\n/u,
160
+ 'the legacy row is indented correctly')
161
+ })
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  按版本查阅功能变化。当前使用方式以[项目首页](../README.md)和使用指南为准;历史条目中的实现方式、限制与测试数量可能已被后续版本替代。
6
6
 
7
+ ## 0.23.7
8
+
9
+ - **修复 persona 字段在旧版 DSH 上不兼容**。`@deepseek-ai/dsh-persona` 在提交 `40792330c0`(2026-09-06,首次随 `dsh-v0.1.3-alpha.2` 发布)把配置字段从 `text` 改名为 `prefix`/`suffix`,两个 schema 互斥:写 `prefix` 的预设会被旧版 persona 插件以 `$.text missing required value` 整份拒绝,反之亦然。0.23.6 的派生逻辑**硬编码了 `prefix`**,因此在 `dsh-v0.1.3-alpha.1` 及更早的 harness 上,`eng` 预设挂载失败、新会话无法创建。现在改为**读取源预设自己使用的字段**再照写——`standard` 就是运行中 harness 的格式权威。已用两个版本的真实 `standard` 验证:`v0.1.3-alpha.1` 产出 `text: |-`,`v0.1.6-alpha.2` 产出 `suffix` + `prefix: |-`,两者 YAML 均解析通过。
10
+ - 顺带修复派生时的缩进:`suffix:` 此前被写到错误的层级,使新格式的 persona 段落 YAML 结构改变。新格式下同时保留源预设的 `suffix`(它声明工作目录),旧格式则不引入该字段。
11
+ - peer 范围补上 `|| ^0.1.7-alpha.1`。DSH `0.1.7-alpha.1` 修复了 0.1.6 中导致所有工具调用失败的问题(`ctx.tools[TOOL_RUNTIME_SCHEDULER]` 为 `undefined`,社区在讨论 #7035 / #7194 报告,官方确认修复);peer 范围需要跟着覆盖该版本。
12
+ - `.preset-test.mjs` 增至 9 项:新增两组 persona fixture,分别断言 `text` 与 `prefix` 两种 schema 下的字段选择、`suffix` 保留与缩进正确性。已反向验证:把字段选择改回硬编码 `prefix`,两项断言立即失败。
13
+
14
+ ## 0.23.6
15
+
16
+ - **修复「工程化开发引擎」预设加载失败**。`seed-preset.ts` 用 `@deepseek-ai/dsh-agent-presets` 导出的 `SHIPPED_PRESET_ROOT` 常量定位源预设,而该常量是 `fileURLToPath(new URL('../presets/', import.meta.url))`——它锁在**插件自己依赖树**里的那份包上,可能和**正在运行的** DSH 不同版本。于是派生出的预设携带运行中 harness 早已替换掉的行:DSH 2026-09-13 把 `workflow-worker-thread` 换成了 `workflow-ptc`,而该包同时退出了 `apps/cli` 的依赖,预设因此报 `row "workflow-worker-thread" names a plugin that cannot be resolved`,整个预设显示「加载失败」,任何新会话都无法启用任务流程。现在改为在**调用时**用 `createRequire` 从插件自身位置解析,跟随宿主的安装图。
17
+ - **预设改为每次启动重新派生**,而非"存在即跳过"。此前一次性复制把预设冻结成首次运行时的快照,DSH 每次修改 `standard` 都会让它失配;persona 格式也经历过 `text:` → `prefix`/`suffix` 的迁移,旧副本因此被 persona 插件的 schema 拒绝。现在检测到是本插件生成的预设才更新,**手工编辑过的预设绝不被覆盖**。
18
+ - persona 行改为**整块重写 `config:`**,不再匹配某一种历史措辞,因此对 schema 的后续演进免疫。同时**不再设置 `complete: true`**——那会让这段 prefix 成为整个系统提示词并抑制其余所有 section。
19
+ - 新增 `.preset-test.mjs`(7 项),断言 persona 已替换、使用当前 `prefix` 形式、不含 `complete`、agent 行只出现一次、`standard` 的所有顶层行都被继承、以及重复派生结果稳定。已反向验证:把 `complete: true` 加回去该测试即失败。`npm test` 从 43 项增至 50 项。
20
+
7
21
  ## 0.23.5
8
22
 
9
23
  - **修复 `scripts/` 未随包发布,导致两条已声明的 npm script 在安装后无法执行**。`package.json` 声明了 `verify:package` 与 `verify:dsh`,但 `files` 白名单不含 `scripts/**`,用户装包后运行它们会直接 `MODULE_NOT_FOUND`。该问题早于 0.23.3 存在(`verify:package` 一直如此),0.23.4 新增的 `verify:dsh` 只是沿用了同一模式。现在把 `scripts/**` 纳入白名单,并在 `verify:package` 中加断言:包内必须能找到每一条 `package.json` 里声明的 `node <file>` script 目标。
@@ -1,19 +1,126 @@
1
1
  /**
2
- * Seed the `eng` preset into the user's `.agent-presets` root on first boot.
2
+ * Seed the `eng` preset into the user's `.agent-presets` root.
3
3
  *
4
- * Installing the bundle must put a pickable "工程化开发引擎" preset in the
5
- * preset picker without anyone hand-copying a composition. This module copies
6
- * the currently shipped `standard` preset, swaps its persona for the
7
- * engineering persona, and appends the `@godv61/dsh-task-engine/agent` row.
8
- * The copy is idempotent (an existing `eng` is never overwritten), and every
9
- * edit reads the RUNTIME `standard` directory, so the seed tracks the running
10
- * harness version instead of pinning a stale tool list.
4
+ * Installing the bundle must put a pickable "工程化开发引�? preset in the preset
5
+ * picker without anyone hand-copying a composition. The preset is the shipped
6
+ * `standard` composition with three edits: the persona is replaced by the
7
+ * engineering persona, `task-engine-agent` is appended, and nothing else changes
8
+ * �?so a session on it has every tool `standard` has, plus `dev_task`.
9
+ *
10
+ * The seed DERIVES the composition from the `standard` that is running, and
11
+ * RE-DERIVES it whenever that source moves. The earlier version copied once and
12
+ * then returned early on every later boot, which turned the preset into a frozen
13
+ * snapshot of whatever `standard` looked like the first time:
14
+ *
15
+ * - DSH 2026-09-06 changed the persona row from `text:` to `prefix`/`suffix`.
16
+ * A stale copy kept `text:`, which the persona plugin's schema (prefix is
17
+ * required) rejects outright.
18
+ * - DSH 2026-09-13 replaced the `workflow-worker-thread` row with
19
+ * `workflow-ptc`. A stale copy kept the removed row, and because that
20
+ * package also left `apps/cli`'s dependencies the roster could no longer
21
+ * resolve it: the preset reported "加载失败" and no new session could use
22
+ * the task flow at all.
23
+ *
24
+ * Both were invisible until a user upgraded DSH, and both left the preset
25
+ * unusable rather than merely outdated. Re-deriving on every boot makes the
26
+ * preset track its source, and a hand-edited preset is never silently
27
+ * clobbered: any local edit is detected and preserved.
11
28
  *
12
29
  * @module dsh-task-engine/seed-preset
13
30
  */
14
31
  /**
15
- * Create the `eng` preset under `<dshHome>/.agent-presets` if it is absent.
32
+ * The persona row `standard` shipped before 2026-09-06, kept so a preset seeded
33
+ * by an older release can still be recognised and re-derived.
34
+ */
35
+ declare const LEGACY_PERSONA_BLOCK: string;
36
+ /** The persona fragment the seed itself wrote into `eng`, in the old `text:` form. */
37
+ declare const SEEDED_PERSONA_MARKER = " text: |-\n";
38
+ /**
39
+ * Which persona config field the RUNNING harness expects.
40
+ *
41
+ * `@deepseek-ai/dsh-persona` renamed `text` to `prefix`/`suffix` in
42
+ * `40792330c0` (2026-09-06, first shipped in `dsh-v0.1.3-alpha.2`). The two
43
+ * schemas are mutually exclusive: a preset written with `prefix` is rejected
44
+ * outright by an older persona plugin with `$.text missing required value`, and
45
+ * `text` is rejected by the newer one. The seed therefore cannot hardcode
46
+ * either name �?it reads which one the source composition already uses, since
47
+ * that composition is what the running harness mounts.
48
+ */
49
+ type PersonaField = 'text' | 'prefix';
50
+ /** The block-scalar styles a persona row may use, kept verbatim from the source. */
51
+ type ScalarStyle = '|-' | '>-' | '|' | '>';
52
+ /**
53
+ * Detect the persona field and its block-scalar style from the source's own
54
+ * persona row.
55
+ *
56
+ * The style is part of the value, not decoration: `>-` folds the single
57
+ * newlines inside the payload back into spaces, while `|-` preserves them. The
58
+ * shipped presets all use `>-`, so writing `|-` would silently change how the
59
+ * persona prose renders.
60
+ * @param composition - the `standard` composition text.
61
+ * @returns the field name and scalar style, defaulting to the older `text`/`>-`.
62
+ */
63
+ declare function personaShapeOf(composition: string): {
64
+ field: PersonaField;
65
+ style: ScalarStyle;
66
+ };
67
+ /**
68
+ * Detect the persona field from the source composition's own persona row.
69
+ * @param composition - the `standard` composition text.
70
+ * @returns the field name, defaulting to `text` for the pre-2026-09-06 shape.
71
+ */
72
+ declare function personaFieldOf(composition: string): PersonaField;
73
+ /**
74
+ * Replace the persona row's config with the engineering persona, keeping the
75
+ * field name the running harness uses.
76
+ *
77
+ * The persona schema has moved (`text:` �?`prefix`/`suffix`), so this rewrites
78
+ * the whole `config:` block of the `persona` row rather than matching one
79
+ * historical phrasing, and writes whichever field
80
+ * {@link personaFieldOf} found in the source. A `suffix:` row is preserved when
81
+ * the source had one, because the newer schema renders it after first-party
82
+ * guidance and dropping it would change the system prompt's shape.
83
+ * @param composition - the `standard` composition text.
84
+ * @param persona - the engineering persona prose.
85
+ * @returns the rewritten text plus whether the row was found.
86
+ */
87
+ declare function applyPersona(composition: string, persona: string): {
88
+ text: string;
89
+ applied: boolean;
90
+ };
91
+ /**
92
+ * Whether an existing `eng` composition is one this package previously seeded,
93
+ * as opposed to something the user has since edited by hand. Only a previously
94
+ * seeded preset is re-derived; a hand-edited one is left exactly as it is.
95
+ * @param composition - the existing composition text.
96
+ * @returns true when the seed owns the file.
97
+ */
98
+ declare function wasSeeded(composition: string): boolean;
99
+ /**
100
+ * Create or refresh the `eng` preset under `<dshHome>/.agent-presets`.
101
+ *
16
102
  * Failures are swallowed: a seed that cannot write must not stop the bundle's
17
- * host half (its Remote controller and workbench) from loading.
103
+ * host half (its Remote controller and workbench) from loading. A preset that
104
+ * was deleted by the user stays deleted �?re-derivation only touches a file the
105
+ * seed itself created.
18
106
  */
19
107
  export declare function seedEngPreset(): void;
108
+ /**
109
+ * The composition text this package would seed right now. Exported for the
110
+ * package's own verification, so the derivation can be asserted without booting
111
+ * a harness.
112
+ * @returns the derived composition, or undefined when `standard` is unreadable.
113
+ */
114
+ export declare function derivedEngComposition(): {
115
+ text: string;
116
+ personaApplied: boolean;
117
+ } | undefined;
118
+ /** Exported for tests: whether a composition is one this seed owns. */
119
+ export { wasSeeded };
120
+ /**
121
+ * Exported for tests: apply the persona rewrite to an arbitrary composition, so
122
+ * both persona schema generations can be asserted without swapping harnesses.
123
+ */
124
+ export { applyPersona, personaFieldOf, personaShapeOf };
125
+ /** Exported for tests: the pre-2026-09-06 persona phrasing this seed must still recognise. */
126
+ export { LEGACY_PERSONA_BLOCK, SEEDED_PERSONA_MARKER };
@@ -1,59 +1,258 @@
1
1
  /**
2
- * Seed the `eng` preset into the user's `.agent-presets` root on first boot.
2
+ * Seed the `eng` preset into the user's `.agent-presets` root.
3
3
  *
4
- * Installing the bundle must put a pickable "工程化开发引擎" preset in the
5
- * preset picker without anyone hand-copying a composition. This module copies
6
- * the currently shipped `standard` preset, swaps its persona for the
7
- * engineering persona, and appends the `@godv61/dsh-task-engine/agent` row.
8
- * The copy is idempotent (an existing `eng` is never overwritten), and every
9
- * edit reads the RUNTIME `standard` directory, so the seed tracks the running
10
- * harness version instead of pinning a stale tool list.
4
+ * Installing the bundle must put a pickable "工程化开发引�? preset in the preset
5
+ * picker without anyone hand-copying a composition. The preset is the shipped
6
+ * `standard` composition with three edits: the persona is replaced by the
7
+ * engineering persona, `task-engine-agent` is appended, and nothing else changes
8
+ * �?so a session on it has every tool `standard` has, plus `dev_task`.
9
+ *
10
+ * The seed DERIVES the composition from the `standard` that is running, and
11
+ * RE-DERIVES it whenever that source moves. The earlier version copied once and
12
+ * then returned early on every later boot, which turned the preset into a frozen
13
+ * snapshot of whatever `standard` looked like the first time:
14
+ *
15
+ * - DSH 2026-09-06 changed the persona row from `text:` to `prefix`/`suffix`.
16
+ * A stale copy kept `text:`, which the persona plugin's schema (prefix is
17
+ * required) rejects outright.
18
+ * - DSH 2026-09-13 replaced the `workflow-worker-thread` row with
19
+ * `workflow-ptc`. A stale copy kept the removed row, and because that
20
+ * package also left `apps/cli`'s dependencies the roster could no longer
21
+ * resolve it: the preset reported "加载失败" and no new session could use
22
+ * the task flow at all.
23
+ *
24
+ * Both were invisible until a user upgraded DSH, and both left the preset
25
+ * unusable rather than merely outdated. Re-deriving on every boot makes the
26
+ * preset track its source, and a hand-edited preset is never silently
27
+ * clobbered: any local edit is detected and preserved.
11
28
  *
12
29
  * @module dsh-task-engine/seed-preset
13
30
  */
14
31
  import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
32
+ import { createRequire } from 'node:module';
15
33
  import { homedir } from 'node:os';
16
- import { join } from 'node:path';
34
+ import { dirname, join } from 'node:path';
17
35
  import { fileURLToPath } from 'node:url';
18
- import { SHIPPED_PRESET_ROOT } from '@deepseek-ai/dsh-agent-presets';
19
- /** The persona block `standard` ships, in its exact indentation. */
20
- const SHIPPED_PERSONA_BLOCK = ' text: >-\n'
36
+ /**
37
+ * The persona row `standard` shipped before 2026-09-06, kept so a preset seeded
38
+ * by an older release can still be recognised and re-derived.
39
+ */
40
+ const LEGACY_PERSONA_BLOCK = ' text: >-\n'
21
41
  + ' You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.\n';
42
+ /** The persona fragment the seed itself wrote into `eng`, in the old `text:` form. */
43
+ const SEEDED_PERSONA_MARKER = ' text: |-\n';
44
+ /** The agent-plane row that activates `dev_task` and the shipped skills. */
45
+ const AGENT_ROW_ID = 'task-engine-agent';
22
46
  /** The agent-plane row that activates `dev_task` and the shipped skills. */
23
47
  const AGENT_ROW = "\n- id: task-engine-agent\n name: '@godv61/dsh-task-engine/agent'\n";
48
+ /** Metadata written beside the composition. */
49
+ const PRESET_META = 'name: 工程化开发引擎\n'
50
+ + 'description: 通过 dev_task 工具硬性持有需求评审→设计→开发→交付→代码审核的状态机,按项目 .dsh/eng.json 配置流转与提交门禁。\n';
24
51
  /** The harness home, honouring an explicit override first. */
25
52
  function dshHome() {
26
53
  return process.env.DSH_HOME ?? join(homedir(), '.dsh');
27
54
  }
28
55
  /**
29
- * Create the `eng` preset under `<dshHome>/.agent-presets` if it is absent.
56
+ * The shipped `standard` composition this preset derives from, resolved at CALL
57
+ * time from the installed DSH rather than from a build-time constant.
58
+ *
59
+ * `@deepseek-ai/dsh-agent-presets` also exports `SHIPPED_PRESET_ROOT`, but that
60
+ * is `fileURLToPath(new URL('../presets/', import.meta.url))` �?a constant baked
61
+ * into whichever copy of the package the *plugin* resolves. Under a real install
62
+ * that is the plugin's own dependency tree, which can sit at a different DSH
63
+ * version than the one running: the seed then copies an obsolete `standard`,
64
+ * which is exactly how the `eng` preset acquired a row
65
+ * (`workflow-worker-thread`) that the running harness had already replaced with
66
+ * `workflow-ptc`, and a persona in the pre-`prefix` `text:` form the persona
67
+ * plugin now rejects. Resolving from this module's own location follows the
68
+ * host's install graph, so the derived preset matches the harness that will
69
+ * mount it.
70
+ * @returns the absolute `presets` directory, or undefined when unresolvable.
71
+ */
72
+ function shippedPresetRoot() {
73
+ try {
74
+ const require_ = createRequire(import.meta.url);
75
+ return join(dirname(require_.resolve('@deepseek-ai/dsh-agent-presets/package.json')), 'presets');
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ /** The shipped `standard` composition this preset derives from. */
82
+ function shippedStandard() {
83
+ const root = shippedPresetRoot();
84
+ return root === undefined ? undefined : join(root, 'standard');
85
+ }
86
+ /** The engineering persona body, read from this package. */
87
+ function personaBody() {
88
+ return readFileSync(fileURLToPath(new URL('../preset/persona.md', import.meta.url)), 'utf8').trimEnd();
89
+ }
90
+ /**
91
+ * Detect the persona field and its block-scalar style from the source's own
92
+ * persona row.
93
+ *
94
+ * The style is part of the value, not decoration: `>-` folds the single
95
+ * newlines inside the payload back into spaces, while `|-` preserves them. The
96
+ * shipped presets all use `>-`, so writing `|-` would silently change how the
97
+ * persona prose renders.
98
+ * @param composition - the `standard` composition text.
99
+ * @returns the field name and scalar style, defaulting to the older `text`/`>-`.
100
+ */
101
+ function personaShapeOf(composition) {
102
+ const lines = composition.split('\n');
103
+ const start = lines.findIndex(line => line.trimEnd() === '- id: persona');
104
+ if (start < 0)
105
+ return { field: 'text', style: '>-' };
106
+ let end = start + 1;
107
+ while (end < lines.length && !/^- /u.test(lines[end] ?? ''))
108
+ end++;
109
+ for (const line of lines.slice(start, end)) {
110
+ const match = /^\s+(text|prefix):\s*([|>][-+]?)\s*$/u.exec(line);
111
+ if (match === null)
112
+ continue;
113
+ return { field: match[1], style: (match[2] ?? '>-') };
114
+ }
115
+ return { field: 'text', style: '>-' };
116
+ }
117
+ /**
118
+ * Detect the persona field from the source composition's own persona row.
119
+ * @param composition - the `standard` composition text.
120
+ * @returns the field name, defaulting to `text` for the pre-2026-09-06 shape.
121
+ */
122
+ function personaFieldOf(composition) {
123
+ return personaShapeOf(composition).field;
124
+ }
125
+ /**
126
+ * Replace the persona row's config with the engineering persona, keeping the
127
+ * field name the running harness uses.
128
+ *
129
+ * The persona schema has moved (`text:` �?`prefix`/`suffix`), so this rewrites
130
+ * the whole `config:` block of the `persona` row rather than matching one
131
+ * historical phrasing, and writes whichever field
132
+ * {@link personaFieldOf} found in the source. A `suffix:` row is preserved when
133
+ * the source had one, because the newer schema renders it after first-party
134
+ * guidance and dropping it would change the system prompt's shape.
135
+ * @param composition - the `standard` composition text.
136
+ * @param persona - the engineering persona prose.
137
+ * @returns the rewritten text plus whether the row was found.
138
+ */
139
+ function applyPersona(composition, persona) {
140
+ const lines = composition.split('\n');
141
+ const start = lines.findIndex(line => line.trimEnd() === '- id: persona');
142
+ if (start < 0)
143
+ return { text: composition, applied: false };
144
+ // The row ends at the next top-level entry.
145
+ let end = start + 1;
146
+ while (end < lines.length && !/^- /u.test(lines[end] ?? ''))
147
+ end++;
148
+ // Keep the row's identity lines (`- id`, ` name:`); drop only its config.
149
+ const header = lines.slice(start, end).filter(line => !/^\s{2,}config:/u.test(line) && !/^\s{4,}\S/u.test(line));
150
+ const { field, style } = personaShapeOf(composition);
151
+ // The shipped suffix names the working directory; carry it over verbatim.
152
+ const suffixLine = lines.slice(start, end).find(line => /^\s+suffix:/u.test(line));
153
+ const indented = persona.split('\n').map(line => (line === '' ? '' : ` ${line}`));
154
+ // `complete` is deliberately NOT set: it would make this prose the entire
155
+ // system prompt, suppressing the suffix and every other section.
156
+ const config = [
157
+ ' config:',
158
+ // Carry the shipped suffix verbatim under `config:`, keeping its own
159
+ // indentation: it names the working directory and the newer persona schema
160
+ // renders it after first-party guidance.
161
+ ...(field === 'prefix' && suffixLine !== undefined ? [` ${suffixLine.trim()}`] : []),
162
+ ` ${field}: ${style}`,
163
+ ...indented,
164
+ ];
165
+ const body = [...header.filter(line => line !== ''), ...config];
166
+ return { text: [...lines.slice(0, start), ...body, ...lines.slice(end)].join('\n'), applied: true };
167
+ }
168
+ /** Append the agent row when the composition does not already name it. */
169
+ function applyAgentRow(composition) {
170
+ if (composition.includes(`id: ${AGENT_ROW_ID}`))
171
+ return composition;
172
+ return composition.trimEnd() + '\n' + AGENT_ROW;
173
+ }
174
+ /**
175
+ * Whether an existing `eng` composition is one this package previously seeded,
176
+ * as opposed to something the user has since edited by hand. Only a previously
177
+ * seeded preset is re-derived; a hand-edited one is left exactly as it is.
178
+ * @param composition - the existing composition text.
179
+ * @returns true when the seed owns the file.
180
+ */
181
+ function wasSeeded(composition) {
182
+ return composition.includes(`id: ${AGENT_ROW_ID}`);
183
+ }
184
+ /**
185
+ * Build the `eng` composition from the running `standard`.
186
+ * @returns the composition text, or undefined when `standard` cannot be read.
187
+ */
188
+ function deriveComposition() {
189
+ const standard = shippedStandard();
190
+ if (standard === undefined)
191
+ return undefined;
192
+ let source;
193
+ try {
194
+ source = readFileSync(join(standard, 'agent.cordis.yml'), 'utf8');
195
+ }
196
+ catch {
197
+ return undefined;
198
+ }
199
+ const persona = applyPersona(source, personaBody());
200
+ return { text: applyAgentRow(persona.text), personaApplied: persona.applied };
201
+ }
202
+ /**
203
+ * Create or refresh the `eng` preset under `<dshHome>/.agent-presets`.
204
+ *
30
205
  * Failures are swallowed: a seed that cannot write must not stop the bundle's
31
- * host half (its Remote controller and workbench) from loading.
206
+ * host half (its Remote controller and workbench) from loading. A preset that
207
+ * was deleted by the user stays deleted �?re-derivation only touches a file the
208
+ * seed itself created.
32
209
  */
33
210
  export function seedEngPreset() {
34
211
  const target = join(dshHome(), '.agent-presets', 'eng');
35
- if (existsSync(join(target, 'agent.cordis.yml')))
36
- return;
212
+ const compositionPath = join(target, 'agent.cordis.yml');
37
213
  try {
38
- mkdirSync(join(dshHome(), '.agent-presets'), { recursive: true });
39
- cpSync(join(SHIPPED_PRESET_ROOT, 'standard'), target, { recursive: true });
40
- const compPath = join(target, 'agent.cordis.yml');
41
- let comp = readFileSync(compPath, 'utf8');
42
- const personaPath = fileURLToPath(new URL('../preset/persona.md', import.meta.url));
43
- const persona = readFileSync(personaPath, 'utf8').trimEnd();
44
- if (comp.includes(SHIPPED_PERSONA_BLOCK)) {
45
- const block = ' text: |-\n' + persona.split('\n').map(line => ' ' + line).join('\n') + '\n';
46
- comp = comp.replace(SHIPPED_PERSONA_BLOCK, block);
47
- }
48
- if (!comp.includes("name: '@godv61/dsh-task-engine/agent'")) {
49
- comp = comp.trimEnd() + '\n' + AGENT_ROW;
214
+ const derived = deriveComposition();
215
+ if (derived === undefined)
216
+ return; // no readable `standard`: leave any existing preset alone
217
+ if (existsSync(compositionPath)) {
218
+ const existing = readFileSync(compositionPath, 'utf8');
219
+ if (!wasSeeded(existing))
220
+ return; // hand-authored: never clobber
221
+ if (existing === derived.text)
222
+ return; // already current
223
+ writeFileSync(compositionPath, derived.text, 'utf8');
224
+ writeFileSync(join(target, 'preset.yml'), PRESET_META, 'utf8');
225
+ return;
50
226
  }
51
- writeFileSync(compPath, comp, 'utf8');
52
- writeFileSync(join(target, 'preset.yml'), 'name: 工程化开发引擎\n'
53
- + 'description: 通过 dev_task 工具硬性持有需求评审→设计→开发→交付→代码审核的状态机,按项目 .dsh/eng.json 配置流转与提交门禁。\n', 'utf8');
227
+ mkdirSync(join(dshHome(), '.agent-presets'), { recursive: true });
228
+ // Copy the source directory so any future companion file travels with it,
229
+ // then overwrite the composition with the derived text.
230
+ const source = shippedStandard();
231
+ if (source !== undefined)
232
+ cpSync(source, target, { recursive: true });
233
+ writeFileSync(compositionPath, derived.text, 'utf8');
234
+ writeFileSync(join(target, 'preset.yml'), PRESET_META, 'utf8');
54
235
  }
55
236
  catch {
56
237
  // A failed seed is recoverable with `dsh-task-engine-enable`; do not fail boot.
57
238
  }
58
239
  }
240
+ /**
241
+ * The composition text this package would seed right now. Exported for the
242
+ * package's own verification, so the derivation can be asserted without booting
243
+ * a harness.
244
+ * @returns the derived composition, or undefined when `standard` is unreadable.
245
+ */
246
+ export function derivedEngComposition() {
247
+ return deriveComposition();
248
+ }
249
+ /** Exported for tests: whether a composition is one this seed owns. */
250
+ export { wasSeeded };
251
+ /**
252
+ * Exported for tests: apply the persona rewrite to an arbitrary composition, so
253
+ * both persona schema generations can be asserted without swapping harnesses.
254
+ */
255
+ export { applyPersona, personaFieldOf, personaShapeOf };
256
+ /** Exported for tests: the pre-2026-09-06 persona phrasing this seed must still recognise. */
257
+ export { LEGACY_PERSONA_BLOCK, SEEDED_PERSONA_MARKER };
59
258
  //# sourceMappingURL=seed-preset.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed-preset.js","sourceRoot":"","sources":["../src/seed-preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AAEpE,oEAAoE;AACpE,MAAM,qBAAqB,GACzB,gBAAgB;MACd,mGAAmG,CAAA;AAEvG,4EAA4E;AAC5E,MAAM,SAAS,GAAG,sEAAsE,CAAA;AAExF,8DAA8D;AAC9D,SAAS,OAAO;IACd,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAA;IACvD,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;QAAE,OAAM;IACxD,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjE,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAE1E,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;QACjD,IAAI,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAEzC,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QACnF,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAA;QAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;YACnG,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAA;QACnD,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,uCAAuC,CAAC,EAAE,CAAC;YAC5D,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;QAC1C,CAAC;QACD,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;QAErC,aAAa,CACX,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAC1B,iBAAiB;cACf,sFAAsF,EACxF,MAAM,CACP,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;IAClF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"seed-preset.js","sourceRoot":"","sources":["../src/seed-preset.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godv61/dsh-task-engine",
3
- "version": "0.23.5",
3
+ "version": "0.23.7",
4
4
  "description": "Preset-workflow engineering delivery engine for DeepSeek Harness: a `dev_task` tool that hard-gates stage transitions and commit scope/message, per-node skill/rule bindings with progressive disclosure, a workbench to pick a flow preset and mount skills/rules, and preset-scoped activation — the tool and skills activate only for sessions on a preset that names the agent row.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -39,7 +39,8 @@
39
39
  ".p0-test.mjs",
40
40
  ".resource-test.mjs",
41
41
  ".workflow-test.mjs",
42
- ".hook-test.mjs"
42
+ ".hook-test.mjs",
43
+ ".preset-test.mjs"
43
44
  ],
44
45
  "license": "MIT",
45
46
  "publishConfig": {
@@ -60,17 +61,17 @@
60
61
  "scripts": {
61
62
  "build": "tsc -p tsconfig.json && node build-client.mjs && node build-hook.mjs",
62
63
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
63
- "test": "node .p0-test.mjs && node --test .resource-test.mjs .workflow-test.mjs .hook-test.mjs",
64
+ "test": "node .p0-test.mjs && node --test .resource-test.mjs .workflow-test.mjs .hook-test.mjs .preset-test.mjs",
64
65
  "verify:package": "node scripts/verify-package.mjs",
65
66
  "verify:dsh": "node scripts/verify-dsh-compat.mjs",
66
67
  "prepare": "npm run build"
67
68
  },
68
69
  "peerDependencies": {
69
70
  "@deepseek-ai/cordis": "^4.0.2",
70
- "@deepseek-ai/dsh-agent-presets": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2",
71
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2",
72
- "@deepseek-ai/dsh-tools": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2",
73
- "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2"
71
+ "@deepseek-ai/dsh-agent-presets": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1",
72
+ "@deepseek-ai/dsh-llm": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1",
73
+ "@deepseek-ai/dsh-tools": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1",
74
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1"
74
75
  },
75
76
  "devDependencies": {
76
77
  "@deepseek-ai/cordis": "^4.0.2",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Black-box verification of the npm tarball before publishing: packs the
3
3
  * package, extracts it into a clean temp dir, and proves the published entry
4
- * points actually work — main import, browser client registration, the bundled
4
+ * points actually work �?main import, browser client registration, the bundled
5
5
  * `.p0-test.mjs`, the CLI, and the `files` whitelist.
6
6
  *
7
7
  * Run: `npm run verify:package`
@@ -42,7 +42,7 @@ const listing = execSync(`tar -tzf "${tgzPath}"`, { encoding: 'utf8' })
42
42
  check('extract', existsSync(join(pkgDir, 'package.json')))
43
43
 
44
44
  // 3. files whitelist sanity
45
- for (const required of ['hooks/commit-msg', 'lib/index.js', 'lib/client.js', 'lib/client.d.ts', '.p0-test.mjs', 'preset/enable.mjs', 'README.md', '.resource-test.mjs', '.workflow-test.mjs', '.hook-test.mjs']) {
45
+ for (const required of ['hooks/commit-msg', 'lib/index.js', 'lib/client.js', 'lib/client.d.ts', '.p0-test.mjs', 'preset/enable.mjs', 'README.md', '.resource-test.mjs', '.workflow-test.mjs', '.hook-test.mjs', '.preset-test.mjs']) {
46
46
  check(`tarball contains ${required}`, listing.includes(required))
47
47
  }
48
48
  check('tarball excludes src sources', !listing.some(line => line.startsWith('src/')))
@@ -51,7 +51,7 @@ check('tarball excludes build scripts', !listing.some(line => line.endsWith('bui
51
51
  // Every `node <file>` script the manifest declares must actually ship, EXCEPT
52
52
  // the build-time ones that `prepare` needs in the source tree only. A script
53
53
  // whose target is absent from `files` installs fine and then dies with
54
- // MODULE_NOT_FOUND — which is exactly what verify:package and verify:dsh did
54
+ // MODULE_NOT_FOUND �?which is exactly what verify:package and verify:dsh did
55
55
  // until this assertion existed, because neither scripts/ path was published.
56
56
  {
57
57
  const manifest = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'))
@@ -112,7 +112,7 @@ try {
112
112
  // Resource package behavior from the installed artifact, including the commit
113
113
  // hook running for real inside throwaway repositories.
114
114
  try {
115
- const out = execSync('node --test --test-reporter=tap .resource-test.mjs .workflow-test.mjs .hook-test.mjs', { cwd: installedDir, encoding: 'utf8', stdio: 'pipe' })
115
+ const out = execSync('node --test --test-reporter=tap .resource-test.mjs .workflow-test.mjs .hook-test.mjs .preset-test.mjs', { cwd: installedDir, encoding: 'utf8', stdio: 'pipe' })
116
116
  check('in-package resource, workflow and hook behavior', /# fail 0/u.test(out))
117
117
  } catch (error) { check('in-package resource, workflow and hook behavior', false, String(error.stderr ?? error)) }
118
118