@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.
- package/.preset-test.mjs +161 -0
- package/docs/CHANGELOG.md +14 -0
- package/lib/seed-preset.d.ts +117 -10
- package/lib/seed-preset.js +230 -31
- package/lib/seed-preset.js.map +1 -1
- package/package.json +8 -7
- package/scripts/verify-package.mjs +4 -4
package/.preset-test.mjs
ADDED
|
@@ -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 目标。
|
package/lib/seed-preset.d.ts
CHANGED
|
@@ -1,19 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Seed the `eng` preset into the user's `.agent-presets` root
|
|
2
|
+
* Seed the `eng` preset into the user's `.agent-presets` root.
|
|
3
3
|
*
|
|
4
|
-
* Installing the bundle must put a pickable "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* engineering persona,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
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 };
|
package/lib/seed-preset.js
CHANGED
|
@@ -1,59 +1,258 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Seed the `eng` preset into the user's `.agent-presets` root
|
|
2
|
+
* Seed the `eng` preset into the user's `.agent-presets` root.
|
|
3
3
|
*
|
|
4
|
-
* Installing the bundle must put a pickable "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* engineering persona,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
36
|
-
return;
|
|
212
|
+
const compositionPath = join(target, 'agent.cordis.yml');
|
|
37
213
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
package/lib/seed-preset.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed-preset.js","sourceRoot":"","sources":["../src/seed-preset.ts"],"names":[],"mappings":"AAAA
|
|
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.
|
|
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
|
|
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
|
|
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
|
|