dsh-ppt-studio 1.2.1 → 1.2.2

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.
@@ -1,43 +1,6 @@
1
- # The `standard` agent preset: the full coding agent, mounted once per process.
2
- #
3
- # This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
4
- # standing scope; every session naming it joins by scope parentage, so the
5
- # tools and prompt sections registered here cover each joined agent while a
6
- # session's own state stays keyed per Session/Agent inside the plugins. The
7
- # host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
8
- # preset must not own: the registries themselves, the sandbox and approval
9
- # stack, persistence, and the model route.
10
- #
11
- # A service row here MUST sit inside a group carrying an `isolate` realm.
12
- # Without one it publishes into the root realm, where it is process-global —
13
- # another preset publishing the same name collides, and a host reader would
14
- # resolve one preset's instance for every session; the agent-preset registry
15
- # rejects that at mount. `true` means an entry-local realm: this standing
16
- # mount's own private instance, apart from every other preset's. (A shared
17
- # label does NOT pool instances — `provide()` throws on the second registration
18
- # under the same realm symbol; labels join REALMS, and are not what this file
19
- # needs.)
20
- #
21
- # 【本文件在 DSH 0.1.7 上怎么被用(2026-09-24 适配)】
22
- # 它是一份 **Loader 方言的条目数组**,由 `lib/preset-delivery.js` 读出来、作为
23
- # `plugins` 交给 `ctx.agentPresets.register({id:'ppt', name, description, order, plugins})`。
24
- # 0.1.6 及以前是靠把本文件**写到 `<dshHome>/.agent-presets/ppt/`** 让宿主扫到——那条路
25
- # 在 0.1.7 已被上游删除(原文:"the harness discovers no preset on disk"),所以现在是**声明**。
26
- # 两个必须守住的细节(都在 preset-delivery.js 里有源码级依据):
27
- # · `!!js` 会被 Loader 求值(`{__jsExpr}`),自己读 YAML 时必须**保真**,否则平台开关会失真;
28
- # · 相对模块名会被解析到**注册表所在层**(= dsh-web-app 包目录),不是本目录——本文件里没有相对名。
29
-
30
- # ── identity ────────────────────────────────────────────────────────────────
31
-
32
- # The preset's own persona, shadowing the deployment default for this agent.
33
- # `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
34
- #
35
- # 【本预设唯一的**有意差异**(相对随包 standard)】2026-09-18:
36
- # 这行原来与 standard 逐字相同(通用编码人格),于是本预设只是"改了名字的 standard"——
37
- # 用户提问"我们的预设和标准模式有啥区别?"时,diff 给出的答案是"0 行差异"。
38
- # 工具/技能按预设隔离在这版 DSH 上做不到(切换预设时 agent 已存在 ⇒ `agent/created` 钩子不触发;
39
- # 技能注册表在父层读 ⇒ 子层注册看不见;见 docs/03 最新条目),所以**预设能可靠交付的只有人格**。
40
- # 改这行必须同步更新 scripts/check-preset.mjs 顶部的 INTENTIONAL 白名单(否则门禁会报配置漂移)。
1
+ # Agent preset «PPT 工作室» —— 以 DSH 0.2.1-rc.2 的 standard 预设体为基准复制,
2
+ # 唯一有意差异 = persona(PPT 工作室的人格/开工状态)。
3
+ # 该文件由 scripts/check-preset.mjs 与随装 standard 预设逐行比对,漂移即失败。
41
4
  - id: persona
42
5
  name: '@deepseek-ai/dsh-persona'
43
6
  config:
@@ -51,81 +14,36 @@
51
14
  before showing it. The ppt_* tools, the /ppt commands and the PPT Studio skill manuals are your
52
15
  working set — reach for them instead of hand-rolling artifacts.
53
16
  If the human's request is clearly unrelated to decks, answer it normally without forcing this workflow.
54
-
55
17
  - id: agent-instructions
56
18
  name: '@deepseek-ai/dsh-agent-instructions'
57
19
  config:
58
20
  maxBytes: 65536
59
-
60
- # ── shell ───────────────────────────────────────────────────────────────────
61
-
62
- # `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
63
- # publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
64
- # the criterion for host-plane ownership — injection resolves before any session
65
- # exists, so there is no agent to key by. Behind a preset realm those variables
66
- # never reached the model's shell at all. Both shell tools consume the host
67
- # registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
68
- # host-plane too.
21
+ - id: time-context
22
+ name: '@deepseek-ai/dsh-time-context'
69
23
  - id: tool-bash
70
24
  name: '@deepseek-ai/dsh-tool-bash'
71
25
  disabled: !!js process.platform === 'win32'
72
-
73
26
  - id: tool-pwsh
74
27
  name: '@deepseek-ai/dsh-tool-pwsh'
75
28
  disabled: !!js process.platform !== 'win32'
76
-
77
- # ── filesystem ──────────────────────────────────────────────────────────────
78
-
79
- # Both register into the host `tools` registry and provide nothing, so
80
- # they need no realm. The `fs` service and its policy stay in the host.
81
29
  - id: tool-fs
82
30
  name: '@deepseek-ai/dsh-tool-fs'
83
-
84
31
  - id: tool-fs-search
85
32
  name: '@deepseek-ai/dsh-tool-fs-search'
86
33
  config:
87
34
  sampleOverCapGlobResults: false
88
-
89
- # ── background jobs ────────────────────────────────────────────────────────
90
-
91
- # Only the model-facing controls. The task REGISTRY stays on the host plane:
92
- # its producers sit outside any realm this file could put it in — `tool-bash`
93
- # above resolves it with `ctx.get`, and an entry-local realm here is invisible
94
- # to every sibling row, so `run_in_background` would answer "background jobs
95
- # unavailable" while these controls sat in the catalog. The registry is keyed by
96
- # owning agent anyway, so one host instance serves every session. What a preset
97
- # chooses is whether its agent can collect and stop background work at all.
98
35
  - id: tool-jobs
99
36
  name: '@deepseek-ai/dsh-tool-jobs'
100
-
101
- # ── skills ──────────────────────────────────────────────────────────────────
102
-
103
- # The skill REGISTRY lives in the host composition and is layered per scope:
104
- # these rows register into THIS preset's layer of it, so they need no realm.
105
- # `skill-filesystem` contributes local-root discovery for agents on this preset, and
106
- # `tool-skill` gives them the catalog and loader; the merged catalog also
107
- # carries whatever the deployment registered globally (repository plugins).
37
+ - id: tool-schedule
38
+ name: '@deepseek-ai/dsh-tool-schedule'
108
39
  - id: skill-filesystem
109
40
  name: '@deepseek-ai/dsh-skill-filesystem'
110
-
111
41
  - id: tool-skill
112
42
  name: '@deepseek-ai/dsh-tool-skill'
113
-
114
- # ── goals ───────────────────────────────────────────────────────────────────
115
-
116
- # The goal service and session driver stay on the host plane, where the Gateway
117
- # can resolve them. The human command and model-facing tool register into this
118
- # preset's scoped layers.
119
43
  - id: command-goal
120
44
  name: '@deepseek-ai/dsh-command-goal'
121
-
122
45
  - id: tool-goal
123
46
  name: '@deepseek-ai/dsh-tool-goal'
124
-
125
- # ── plan mode ───────────────────────────────────────────────────────────────
126
-
127
- # Plan state is per-agent by nature, so an entry-local realm is not a
128
- # workaround here — it is the correct lifetime.
129
47
  - id: planning
130
48
  name: cordis:group
131
49
  group: true
@@ -136,29 +54,17 @@
136
54
  name: '@deepseek-ai/dsh-plan-mode'
137
55
  config:
138
56
  section: |
139
- You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
140
-
141
- Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
142
-
143
- The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
57
+ You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
144
58
 
145
- Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
59
+ Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
146
60
 
147
- Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
61
+ The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
148
62
 
149
- When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
63
+ Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
150
64
 
151
- # ── compaction ──────────────────────────────────────────────────────────────
65
+ Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
152
66
 
153
- # `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
154
- # share this realm rather than sit outside it.
155
- #
156
- # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
157
- # plane, and the rows here resolve that one instance. It takes no configuration,
158
- # keys every fold by Session, and owns the context-meter projection units the
159
- # browser reads for every session — behind a realm those units would come and go
160
- # with whichever presets happen to be mounted. What a preset chooses is whether
161
- # its agent compacts at all, which is `compaction-basic` below.
67
+ When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
162
68
  - id: compaction
163
69
  name: cordis:group
164
70
  group: true
@@ -168,28 +74,14 @@
168
74
  config:
169
75
  - id: compaction-basic
170
76
  name: '@deepseek-ai/dsh-compaction-basic'
171
-
172
77
  - id: command-compact
173
78
  name: '@deepseek-ai/dsh-command-compact'
174
-
175
79
  - id: tool-result-pruner
176
80
  name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
177
81
  config:
178
82
  thresholdChars: 8192
179
83
  headChars: 4096
180
84
  tailChars: 1024
181
-
182
- # ── delegation and workflows ────────────────────────────────────────────────
183
-
184
- # The `subagents` registry and its spawn/fork backends live in the HOST
185
- # composition: the registry is a process singleton whose cross-session queries
186
- # the api-proxy serves to the browser, and a provider name may only be
187
- # registered once. This preset contributes the delegation TOOLS, which resolve
188
- # that host registry.
189
- #
190
- # `workflows` is different — nothing outside an agent reads it — so every row
191
- # that reaches it shares one entry-local realm here, and a consumer left
192
- # outside would resolve a host registry this preset does not populate.
193
85
  - id: delegation
194
86
  name: cordis:group
195
87
  group: true
@@ -198,118 +90,41 @@
198
90
  config:
199
91
  - id: tool-subagent-control
200
92
  name: '@deepseek-ai/dsh-tool-subagent-control'
201
-
202
93
  - id: tool-subagent-list-agents
203
94
  name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
204
-
205
95
  - id: tool-subagent
206
96
  name: '@deepseek-ai/dsh-tool-subagent'
207
97
  config:
208
98
  provider: spawn
209
99
  toolName: subagent
210
100
  modelSelectionSettings: true
211
- backgroundMode: continuable
212
-
213
- # Fork omits model selection so provider/model stay equal to the parent and
214
- # the inherited history remains eligible for KV Cache reuse. This preset
215
- # keeps fork continuable; parent and child inherit the same messaging tool,
216
- # while the parent id and return guidance follow the inherited history.
217
101
  - id: tool-subagent-fork
218
102
  name: '@deepseek-ai/dsh-tool-subagent'
219
103
  config:
220
104
  provider: fork
221
105
  toolName: subagent_fork
222
- backgroundMode: continuable
223
-
224
- # Production dsh does not install these optional providers. Install the
225
- # matching Bundle in this Profile and restart the Host, then copy this
226
- # preset and remove `disabled` from the matching tool row. Host availability
227
- # alone grants no tool.
228
- - id: tool-subagent-codex
229
- name: '@deepseek-ai/dsh-tool-subagent'
230
- disabled: true
231
- config:
232
- provider: codex
233
- toolName: subagent_codex
234
- backgroundMode: one-shot
235
- maxDepth: provider-managed
236
-
237
- - id: tool-subagent-claude-code
238
- name: '@deepseek-ai/dsh-tool-subagent'
239
- disabled: true
240
- config:
241
- provider: claude-code
242
- toolName: subagent_claude_code
243
- backgroundMode: one-shot
244
- maxDepth: provider-managed
245
-
246
106
  - id: workflow-ptc
247
107
  name: '@deepseek-ai/dsh-workflow-ptc'
248
108
  config:
249
109
  provider: spawn
250
-
251
110
  - id: tool-workflow
252
111
  name: '@deepseek-ai/dsh-tool-workflow'
253
-
254
- # Off by default with the base row it mirrors: the tool description
255
- # restricts `ralph` to runs the human explicitly asked for, and completion
256
- # is a worker self-report, not an independent evaluation. Preset files take
257
- # no patches, so a session that wants it duplicates this preset under a new
258
- # id and drops `disabled`; the shipped root shadows a copy that reuses this
259
- # id.
260
- - id: tool-ralph
261
- name: '@deepseek-ai/dsh-tool-ralph'
262
- disabled: true
263
- config:
264
- subagentProvider: spawn
265
- maxRounds: 64
266
-
267
- # ── remaining model-facing rows ─────────────────────────────────────────────
268
-
269
112
  - id: tool-ask-user
270
113
  name: '@deepseek-ai/dsh-tool-ask-user'
271
-
272
114
  - id: tool-todo
273
115
  name: '@deepseek-ai/dsh-tool-todo'
274
116
  config:
275
117
  allowParallelInProgress: true
276
-
277
- # The `web` service and its search provider stay in the host composition; only
278
- # the model-facing tool is per-session.
279
118
  - id: tool-web
280
119
  name: '@deepseek-ai/dsh-tool-web'
281
120
  config:
282
121
  fetch: true
283
122
  searchTimeoutMs: 60000
284
-
285
123
  - id: present
286
124
  name: '@deepseek-ai/dsh-tool-present'
287
-
288
125
  - id: tool-plugin-manager
289
126
  name: '@deepseek-ai/dsh-plugin-manager/tools'
290
127
  disabled: true
291
-
292
- # ── 本插件的"预设档"行**不写在这里**(2026-09-26 第四次修订)─────────────────────────────
293
- # 本文件是「PPT 工作室」预设的组合真相:装配时由 lib/preset-delivery.js 声明给 agentPresets 注册表。
294
- # 隔离靠**一行自定位行**实现,但那一行**由声明时程序化追加**(`preset-delivery.js` 的 `selfPresetRow()`):
295
- # { id: 'ppt-studio-scope', name: 'file:///<本包绝对路径>/lib/index.js', config: { scope: 'preset', autoPreset: false } }
296
- # 为什么不写死在 YAML 里:
297
- # ① 写死就要在安装期把绝对路径填进来 ⇒ 换 profile/升级换代会陈旧;`import.meta.url` 现算永远是对的;
298
- # ② 本文件的"不含插件行"是**被门禁断言**的(`scripts/verify-bundle-install.mjs` 断言预设组合里没有
299
- # `ppt-studio` 插件行、`scripts/check-preset.mjs` 做逐行意图比对)——程序化追加让 YAML 保持干净,门禁全绿。
300
- # 为什么必须是**绝对 file://**(而裸包名不行):
301
- # 预设的行由注册表在**它自己 ctx 的 baseUrl** 下 mount(源码 `mountPreset(scope.ctx.extend({ baseUrl: record.context.baseUrl }), …)`,
302
- # 而 `register()` 里 `context = this.ctx`)——那是 `dsh-web-app` 的包目录,**不是** profile、也不是预设目录
303
- # ⇒ 裸包名 `dsh-ppt-studio` 解析不到 ⇒ 预设被判 `broken` ⇒ 前端选择器只渲染健康预设
304
- # (`presetOptions() = presets.filter(p => p.broken === void 0)`)⇒ 用户根本选不到;
305
- # 而 Loader 对**非 `.` 开头**的名字是**原样 `import(name)`** ⇒ `file://` 绝对名不查 baseUrl。
306
- # (0.1.7-rc.2 上真宿主实测:工具 22/0、技能 4/0、提示段有/无、切换预设、负面对照,全过。)
307
- #
308
- # 历史沿革(两次"证伪"的正确结论,别再按旧结论行事):
309
- # ① 2026-09-18 的"按 agent 动态门控"(`agent/created` 钩子 + 子层注册)**确实不可行** ——
310
- # 空白会话切预设时 agent 已存在、钩子不会为它触发。但那只是"插件自己按 agent 门控"这一种写法;
311
- # **预设行挂载**不经过 agent 生命周期:声明即挂载,切换预设时宿主把 agent 的 scope 父指针 `rebind`
312
- # 到新代,可见性自动跟随。
313
- # ② "技能分层 ⇒ 子层注册父层看不见"方向对、**但不是障碍**:技能读路径按"查看方 scope 的链"合并
314
- # (含 agent 自己那一层)⇒ 注册进**预设作用域**正是技能能被该预设看到的**充要姿势**。
315
-
128
+ - id: tool-plugin-market
129
+ name: '@deepseek-ai/dsh-plugin-market/tools'
130
+ disabled: true
package/cordis.patch.yml CHANGED
@@ -12,7 +12,7 @@
12
12
  # · 本行 = 把插件挂进 **profile 层**。`config.scope` 决定它的角色:
13
13
  # - `auto`(缺省,= 本行现在写的值):声明「PPT 工作室」预设,并给它追加一行**自定位行**
14
14
  # (本包入口的绝对 `file://`)⇒ 预设的常驻组合会再挂本包一次(`scope: 'preset'`),
15
- # **那一次**把 22 个 `ppt_*` 工具 / `/ppt` 命令 / `ppt_state` / 4 本内嵌技能 / 语义路由 / 工作流提示段
15
+ # **那一次**把 22 个 `ppt_*` 工具 / `/ppt` 命令 / `ppt_state` / 17 本内嵌技能 / 语义路由 / 工作流提示段
16
16
  # 全部注册在**预设作用域** ⇒ 只有「PPT 工作室」的会话看得到,**其他预设零影响**。
17
17
  # 本档自己不注册任何能力面;只有判定隔离没生效时才**自动回落**到全局(功能不丢,只是没隔离)。
18
18
  # - `profile`:传统档,一律全局(所有预设都能用)。想回到旧行为就改成这个。
@@ -2,6 +2,37 @@
2
2
 
3
3
  > 格式:`## [版本号] 日期` → 目的(为什么改,引测试反馈编号)→ 改动 → 验证 → 影响/后续。
4
4
 
5
+ ## [1.2.2] 2026-10-18 —— 适配 DSH 0.2.1-rc.2 + CI 稳定性
6
+
7
+ **环境事实(实测 2026-10-17)**
8
+ - 桌面 App 外壳 `app.asar/package.json` = `@deepseek-ai/dsh-desktop@0.2.1-rc.2`;运行宿主 `plugin_manager list_bundles` 的 `dsh-base` / `dsh-web-app` / agent-team 全部 = 0.2.1-rc.2 ⇒ **运行宿主 = 0.2.1-rc.2** ✓。
9
+ - npm 全局 CLI(`dsh --version`)原为 0.2.0-rc.2(**落后**)⇒ 按"先对齐后适配"用 `npm install -g @deepseek-ai/dsh@next` 升到 0.2.1-rc.2(守卫:`@next` = 0.2.1-rc.2 = 桌面端,未超前 ✓)。
10
+ - `~/.dsh/dsh-runtimes/dsh-primary-runtime/runtime.json` 的 `desktopVersion` 仍是 0.2.0-rc.2 —— **陈旧元数据**,不代表运行宿主;版本判据以 `app.asar/package.json` + 宿主 bundle 目录为准。
11
+ - profile 仍为 desktop / web / headless 三个(`tui` 只出现在检查脚本的搜索列表里,不是实际 profile)。
12
+
13
+ **兼容性复核(0.2.1-rc.2 上实测)**
14
+ - 服务与签名不变:`tools.register(definition)`、`commands.register(definition)`、`systemPrompt.section(section)`、`skills.register(skill)`(另有 `registerProvider`)、`agentPresets.register(definition) → Promise<() => Promise<void>>`。
15
+ - `dsh-skill` 同时保留 `register` 与 `registerProvider` ⇒ 17 本内嵌技能仍按原通道注册。
16
+ - `dsh.bundle.patch`(profile 层 patch)语义不变;`plugin_manager` 仍能识别本插件 bundle。
17
+ - **标准预设改了形状**:参照从 `dsh-agent-presets/presets/standard/agent.cordis.yml` 迁到 `dsh-web-app/presets/standard.patch.yml`(`- insert:` → `preset-standard` → `config.plugins`),standard 组合行 **19 → 22**(新增 `time-context`、`tool-schedule` 等),`planning` / `delegation` 两个分组的 config 也变了。
18
+ - **修法**:以 0.2.1 的 standard 组合为基准重写 `agent-presets/ppt/agent.cordis.yml`(**文本级拼接**,保留 `!!js` 标签与多行字符串,不靠 YAML 重序列化),只保留唯一有意差异 `persona`(PPT 工作室人格)。
19
+ - 同步更新 smoke §40 的三处行数期望(19/20 → 22/23)。
20
+
21
+ **验收证据(全部实测)**
22
+ - `npm test`:**全部通过**(`check-preset` 漂移 0 / 缺行 0 / 有意差异 1 ✓;`lib ⟷ src` 逐字节一致 ✓;痕迹扫描 0 ✓;生成器 9/9 ✓)
23
+ - `node scripts/verify-preset-scope.mjs --expect-skills 17`:**PASS 22 / 0** —— ppt 预设 **17 技能 / 22 个 ppt_* 工具**、standard 预设 **0**、全局视图 **0** ✓
24
+ - `npm run verify:package`:通过(249 文件、装到干净目录后 `require` 可用 ✓)
25
+ - `npm run verify:products`:通过(13 页 PNG 与基线一致,差异 0 ✓)
26
+
27
+ **CI 稳定性(同版一并交付)**
28
+ - **被测 DSH 版本单源化**:新增 `dsh-target.json`(唯一事实源);`ci.yml` 用 `node -p` 读取,不再手写死数字;`scripts/check-dsh-target.mjs` 并入 `npm test`,强制它 == `docs/14` 的 `dsh-target:` 行,且 `dsh.engines.dsh` 下限不得高于目标。
29
+ - **PR 不跑安装链**:`install-path` 加 `if: github.event_name != 'pull_request'` ⇒ PR 只跑干净检出的快测档,安装链在 main / 手动 时跑。
30
+ - **`@next` 周检**:新增 `dsh-next-probe.yml`(每周一 + 手动,作业级 `continue-on-error`)⇒ 上游 DSH 变化**只告警、不拦 PR**。
31
+ - **工作流语法守卫**:新增 `scripts/check-workflows.mjs`(并入 `npm test`),解析全部 workflow 并校验结构。起因:`dsh-next-probe.yml` 的 step 名含 `: ` 未加引号 ⇒ GitHub 判文件无效(显示文件路径 + "No jobs were run");该类错误现在**本地即被拦住**。
32
+ - **修复**:`ci.yml` 的 one-command install 钉版由 `0.1.6-alpha.2` 改为跟随 `dsh-target.json`(= 0.2.1-rc.2),消除"预设来自新版、CLI 是旧版"的安装自证假红。
33
+
34
+ **发布**:本条目即 **1.2.2**(2026-10-18);发版前检查全绿:`npm test` 322/0、`verify:package`、`verify:products` 13/13。安装方式:两端统一改为 npm 安装 `dsh-ppt-studio@1.2.2`。
35
+
5
36
  ## [1.2.1] 2026-09-30 —— 修复:复杂图 skills 未被注册(用户实测)
6
37
 
7
38
  **问题**:新预设会话里,斜杠命令与助手的 skill 清单都找不到复杂图相关 skill(四份 `ppt-studio-*` 正常可见)。
@@ -55,3 +55,65 @@
55
55
  3. 发布后必须**从 registry 复核**,并把结果写进 `docs/03` 与发布报告。
56
56
 
57
57
  **标准发布序列**:`npm test` 全绿 → `verify:package` / `verify:products` → 版本号 + `docs/03` 定稿 → 提交并推 `main` → 打 `v<版本>` 标签并推 → **创建 Release(附 tgz)** → 轮询 registry 至目标版本 → 两端安装升级 → 复核。
58
+
59
+ ---
60
+
61
+ ## 8. 发布 RUNBOOK(npm OIDC 专用 · 照抄执行)
62
+
63
+ > **一句话**:本包**没有也不该有**本机 npm 凭据;发布**只能**通过"创建 GitHub Release → `publish.yml` 用 OIDC 发到 npm"这条路。
64
+
65
+ ### 8.1 铁律(违反即算事故)
66
+ 1. **没有用户明确指令,不得**:改 `package.json` 版本号 / 打 `v*` 标签 / 创建 GitHub Release。**创建 Release = 发布**。
67
+ 2. **不得用本机 `npm publish`** —— 必然 `E401/E404`,这是**设计如此**,不是故障;**永远不要**把它的退出码当发布判据。
68
+ 3. **发布判据只有一个**:`npm view dsh-ppt-studio version`(与 `dist-tags`)显示目标版本。
69
+
70
+ ### 8.2 标准序列(逐条执行,任一不过即停)
71
+ ```powershell
72
+ # 0) 发版前检查(全绿才能继续)
73
+ npm test # 期望 322 通过 / 0 失败
74
+ npm run verify:package # 打包与安装一致性(装到干净目录后 require 可用)
75
+ npm run verify:products # 本机专用:重出 → 导出 → 渲染 13 PNG → 与基线比对
76
+
77
+ # 1) 版本与日志
78
+ # package.json 的 version → <新版本>;docs/03 写 [<新版本>] <日期> 条目(含已知限制)
79
+
80
+ # 2) 提交并推主干
81
+ git add -A; git commit -m "release(<版本>): …"; git push origin main
82
+
83
+ # 3) 打标签(注意:**只推 tag 不会发布**)
84
+ git tag -a v<版本> -m "<一句话>"; git push origin v<版本>
85
+
86
+ # 4) 打包并**创建 Release** —— 这一步才触发发布
87
+ $tmp = Join-Path $env:TEMP ("rel" + (Get-Random)); New-Item -ItemType Directory -Force -Path $tmp | Out-Null
88
+ $tgz = (npm pack --pack-destination $tmp | Select-Object -Last 1).Trim()
89
+ gh release create v<版本> (Join-Path $tmp $tgz) -R zbsph/dsh-ppt-studio -t "dsh-ppt-studio <版本>" -n "<发布说明>"
90
+
91
+ # 5) 等发布并**从 registry 复核**(唯一判据)
92
+ gh run list -R zbsph/dsh-ppt-studio --limit 3 # 看 "Publish to npm" 是否 success
93
+ npm view dsh-ppt-studio version # 必须等于 <版本>
94
+ npm view dsh-ppt-studio dist-tags --json
95
+
96
+ # 6) 两端安装升级
97
+ # web: dsh plugin --profile web add dsh-ppt-studio@<版本>
98
+ # 桌面端:应用「插件管理」界面升级(CLI 对 desktop profile 会被拒:managed exclusively by the Electron application)
99
+ # ⇒ 桌面端升级后需**重启应用**才生效;重启后在插件管理里确认版本
100
+
101
+ # 7) 记录:docs/15 追加发布小节;docs/03 条目保持与实际一致
102
+ ```
103
+
104
+ ### 8.3 撤回(仅当尚未真正发布到 npm)
105
+ ```powershell
106
+ gh release delete v<版本> -R zbsph/dsh-ppt-studio --yes # 移除触发源
107
+ gh run list -R zbsph/dsh-ppt-studio --limit 5 # 找到 queued/in_progress 的 Publish run
108
+ gh run cancel <数据库ID> -R zbsph/dsh-ppt-studio # 取消它
109
+ npm view dsh-ppt-studio version # 确认仍是上一版(= 未发布)
110
+ ```
111
+ **一旦已发布**:**不要** unpublish(会伤消费者)⇒ 改用**发下一个补丁版本**修正。
112
+
113
+ ### 8.4 补发/重发
114
+ 用工作流自带的手动入口(`publish.yml` 支持 `workflow_dispatch` 并接受 tag 入参):
115
+ ```powershell
116
+ gh workflow run publish.yml -R zbsph/dsh-ppt-studio -f tag=v<版本>
117
+ ```
118
+
119
+ `dsh-target: 0.2.1-rc.2`(CI install-path 的被测 DSH 版本;与 `dsh-target.json` 同源,由 `scripts/check-dsh-target.mjs` 强制一致)
@@ -44,3 +44,16 @@
44
44
  - **遗留 3**:装配语义(C)只核到"名字与结构仍在"层,未逐行验证行为等价。
45
45
  - **回滚点**:`pre-release`、`pre-adapt-D/E/F`、`pre-release-1.2.0`;上一版 tag `v1.1.5`。
46
46
  - **平台事实**:桌面 profile 由 Electron 独占管理(`dsh plugin --profile desktop …` 会被拒 ⇒ 桌面端插件变更只能走应用「插件管理」);`web` / `headless` profile 可用 CLI 管理。
47
+
48
+ ---
49
+
50
+ ## 7. 1.2.1 发布小节(2026-09-30)
51
+
52
+ | 项 | 值 |
53
+ |---|---|
54
+ | 版本 | **1.2.1**(`package.json`) |
55
+ | 内容 | 修复复杂图 skills 未被注册;13 份统一命名为 `ppt-studio-diagram` + 12 份 `ppt-studio-diagram-<类型>`;描述/正文长度与「优先级边界」合规;smoke §40/§52 期望技能数 4→17 |
56
+ | 发布通道 | **创建 GitHub Release `v1.2.1`** ⇒ `publish.yml` 以 **OIDC 受信发布** → npm(**未使用本机 `npm publish`**,本机无凭据属设计如此) |
57
+ | 发布证据 | `npm view dsh-ppt-studio version` → **1.2.1** ✓;Release 资产 `dsh-ppt-studio-1.2.1.tgz`(含 **17** 个 skill)✓ |
58
+ | 发布前检查 | `npm test` 322/0 ✓、`verify:package` ✓、`verify:products` 13/13 ✓、痕迹扫描 0 ✓、`lib ⟷ src` 39/39 ✓ |
59
+ | 回滚 | 上一版 `v1.2.0`;撤回流程见 `docs/14` §8.3(仅未发布时可用) |
@@ -0,0 +1,4 @@
1
+ {
2
+ "dsh": "0.2.1-rc.2",
3
+ "note": "CI install-path 的被测 DSH 版本(唯一事实源)。升级 DSH 时:改本文件 + docs/14 的 `dsh-target:` 行 + 适配预设,三处由 npm test 的 check-dsh-target 守卫强制一致。"
4
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,13 +1,31 @@
1
1
  {
2
2
  "name": "dsh-ppt-studio",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "在 DeepSeek Harness 里做 PPT:说需求 → 逐页制作 → 导出可编辑的 .pptx。含布局检查、Office 真渲染复核,以及可扩展的本地模板库",
5
5
  "//publish": "2026-09-18 定:发 npm,**无 scope** 包名 `dsh-ppt-studio`。**状态(2026-09-26):已发布** —— `dsh-ppt-studio@1.1.0` 在 npm 上(`dist-tags.latest`、public,发布者 `zbsph`),registry 的 tarball 与 GitHub Release 资产**逐字节相同**(sha256 `9303cc5c…`)。首个版本是 `npm publish <发版 tgz>` 直发的;**从下一版起改走 Trusted Publishing(OIDC)全自动通道**:`.github/workflows/publish.yml` 由 release 事件触发 → 下载 Release 资产 → OIDC `npm publish` → 发后自证 integrity —— 零长期 token、零手工(npm 已公告:Bypass-2FA 直发 2027-01 移除)。发布纪律不变:**用发版时那个 tgz**(`.sync-state.json` 的 `localTgz`),**别在 main 前进后跑裸 `npm publish`**(会按当前工作区重新打包 ⇒ 破坏'两条通道同一份字节')。手动补发入口:Actions → Publish to npm → Run workflow。见 README §10 与 docs/05「发布-同步纪律」。",
6
- "repository": { "type": "git", "url": "git+https://github.com/zbsph/dsh-ppt-studio.git" },
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zbsph/dsh-ppt-studio.git"
9
+ },
7
10
  "homepage": "https://github.com/zbsph/dsh-ppt-studio#readme",
8
- "bugs": { "url": "https://github.com/zbsph/dsh-ppt-studio/issues" },
9
- "keywords": ["dsh", "ppt", "pptx", "powerpoint", "slides", "deck", "presentation", "cordis", "plugin", "deepseek-harness"],
10
- "publishConfig": { "access": "public" },
11
+ "bugs": {
12
+ "url": "https://github.com/zbsph/dsh-ppt-studio/issues"
13
+ },
14
+ "keywords": [
15
+ "dsh",
16
+ "ppt",
17
+ "pptx",
18
+ "powerpoint",
19
+ "slides",
20
+ "deck",
21
+ "presentation",
22
+ "cordis",
23
+ "plugin",
24
+ "deepseek-harness"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
11
29
  "type": "module",
12
30
  "main": "./lib/index.js",
13
31
  "//lib": "lib/ 是**提交**的构建产物(src 的纯拷贝):商店(创意工坊)对无 npm 包的条目生成的命令是 `dsh plugin add <repo URL>`,git 安装只拿得到已提交内容,lib 不入库则插件挂不上。守卫:npm run check:lib + smoke 断言。",
@@ -36,7 +54,8 @@
36
54
  "cordis.patch.yml",
37
55
  "examples/smoke",
38
56
  "examples/fx",
39
- "README.md"
57
+ "README.md",
58
+ "dsh-target.json"
40
59
  ],
41
60
  "license": "MIT",
42
61
  "dependencies": {
@@ -68,7 +87,7 @@
68
87
  "build": "bash scripts/build.sh",
69
88
  "build:node": "node scripts/build.mjs",
70
89
  "sync": "node scripts/release-sync.mjs",
71
- "test": "node scripts/build.mjs && node scripts/check-tracked-lf.mjs && node scripts/check-lib-sync.mjs && node scripts/check-unknown-keys.mjs && node scripts/check-removed-traces.mjs && node scripts/check-generators.mjs && node scripts/smoke.mjs && node scripts/check-preset.mjs && node scripts/verify-handdrawn.mjs && node scripts/red-probe.mjs && node scripts/check-line-text.mjs && node scripts/handdrawn-option-probe.mjs",
90
+ "test": "node scripts/build.mjs && node scripts/check-tracked-lf.mjs && node scripts/check-lib-sync.mjs && node scripts/check-unknown-keys.mjs && node scripts/check-removed-traces.mjs && node scripts/check-generators.mjs && node scripts/smoke.mjs && node scripts/check-preset.mjs && node scripts/verify-handdrawn.mjs && node scripts/red-probe.mjs && node scripts/check-line-text.mjs && node scripts/handdrawn-option-probe.mjs && node scripts/check-dsh-target.mjs && node scripts/check-workflows.mjs",
72
91
  "test:fast": "node scripts/test-fast.mjs",
73
92
  "test:release": "node scripts/test-release.mjs",
74
93
  "test:map": "node scripts/test-map.mjs",
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dsh 目标版本一致性守卫(无网络、秒级)。
4
+ *
5
+ * 事实源 = dsh-target.json(CI install-path 的被测 DSH 版本)。
6
+ * 本脚本强制三处一致,防"改了预设忘了改钉版"这类假红/漏测:
7
+ * ① dsh-target.json 的 dsh
8
+ * ② docs/14-发布清单(草稿).md 里的 `dsh-target: <ver>` 行
9
+ * ③ package.json 的 dsh.engines.dsh 下限 ≤ 目标版本(下限写高了会把老宿主挡在门外)
10
+ */
11
+ import { readFileSync } from 'node:fs'
12
+ import { dirname, join } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+
15
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
16
+ const fail = []
17
+ const read = (p) => readFileSync(join(ROOT, p), 'utf8')
18
+
19
+ const target = JSON.parse(read('dsh-target.json')).dsh
20
+ if (typeof target !== 'string' || !/^\d+\.\d+\.\d+(-[0-9a-z.-]+)?$/i.test(target)) {
21
+ fail.push(`dsh-target.json 的 dsh 不是合法版本号:${JSON.stringify(target)}`)
22
+ }
23
+
24
+ const docLine = /`dsh-target:\s*([0-9][0-9a-z.\-]*)`/i.exec(read('docs/14-发布清单(草稿).md'))
25
+ if (!docLine) fail.push('docs/14 缺少 `dsh-target: <版本>` 行(与 dsh-target.json 同源)')
26
+ else if (docLine[1] !== target) fail.push(`docs/14 的 dsh-target=${docLine[1]} 与 dsh-target.json=${target} 不一致`)
27
+
28
+ const engines = JSON.parse(read('package.json'))?.dsh?.engines?.dsh ?? ''
29
+ const low = /(\d+\.\d+\.\d+(?:-[0-9a-z.-]+)?)/i.exec(engines)?.[1]
30
+ if (!low) fail.push('package.json 缺少 dsh.engines.dsh 下限')
31
+ else {
32
+ const cmp = (a, b) => {
33
+ const pa = a.split(/[.-]/).map((x) => (/^\d+$/.test(x) ? Number(x) : x))
34
+ const pb = b.split(/[.-]/).map((x) => (/^\d+$/.test(x) ? Number(x) : x))
35
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
36
+ const x = pa[i] ?? 0, y = pb[i] ?? 0
37
+ if (x === y) continue
38
+ if (typeof x === 'number' && typeof y === 'number') return x - y
39
+ return String(x).localeCompare(String(y))
40
+ }
41
+ return 0
42
+ }
43
+ if (cmp(low, target) > 0) fail.push(`dsh.engines.dsh 下限 ${low} 高于目标 ${target}(会把老宿主挡在门外)`)
44
+ }
45
+
46
+ if (fail.length) {
47
+ console.error('✗ dsh 目标版本一致性:失败')
48
+ for (const f of fail) console.error(' · ' + f)
49
+ process.exit(1)
50
+ }
51
+ console.log(`✓ dsh 目标版本一致:${target}(dsh-target.json = docs/14 = engines 下限 ${low})`)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * .github/workflows 语法守卫(无网络、秒级)。
4
+ *
5
+ * 为什么需要:2026-10-17 我在 dsh-next-probe.yml 里写了 `- name: Probe: xxx`(纯量里的 `: `),
6
+ * GitHub 判定工作流文件无效 ⇒ 显示为"文件路径"的失败运行 + "No jobs were run" 邮件。
7
+ * 这种错误**本地可查**,不该等到 push 之后由邮件告知。
8
+ *
9
+ * 校验:① YAML 可解析;② 顶层有 name / on / jobs;③ 每个 job 有 runs-on 与 steps;
10
+ * ④ 每个 step 有 uses 或 run;⑤ 所有 name 是字符串(含 `: ` 的纯量会被 YAML 解析器先抓住)。
11
+ */
12
+ import { readFileSync, readdirSync } from 'node:fs'
13
+ import { dirname, join } from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+ import YAML from 'yaml'
16
+
17
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
18
+ const DIR = join(ROOT, '.github', 'workflows')
19
+ const fail = []
20
+ let files = []
21
+ try { files = readdirSync(DIR).filter((f) => /\.ya?ml$/.test(f)) } catch { fail.push(`找不到目录 ${DIR}`) }
22
+
23
+ const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
24
+ for (const f of files) {
25
+ const rel = `.github/workflows/${f}`
26
+ let doc
27
+ try { doc = YAML.parse(readFileSync(join(DIR, f), 'utf8')) } catch (e) { fail.push(`${rel}: YAML 解析失败 — ${e.message.split('\n')[0]}`); continue }
28
+ if (!isPlainObject(doc)) { fail.push(`${rel}: 顶层不是映射`); continue }
29
+ for (const k of ['name', 'on', 'jobs']) if (!(k in doc)) fail.push(`${rel}: 缺少顶层键 ${k}`)
30
+ if (typeof doc.name !== 'string' || !doc.name.trim()) fail.push(`${rel}: name 必须是非空字符串`)
31
+ if (!isPlainObject(doc.jobs) || Object.keys(doc.jobs).length === 0) fail.push(`${rel}: jobs 必须是非空映射`)
32
+ for (const [jid, job] of Object.entries(doc.jobs ?? {})) {
33
+ if (!isPlainObject(job)) { fail.push(`${rel}: jobs.${jid} 不是映射`); continue }
34
+ if (!('runs-on' in job) && !('uses' in job)) fail.push(`${rel}: jobs.${jid} 缺少 runs-on / uses`)
35
+ if ('uses' in job) continue
36
+ if (!Array.isArray(job.steps) || job.steps.length === 0) fail.push(`${rel}: jobs.${jid} 缺少 steps`)
37
+ for (const [i, st] of (job.steps ?? []).entries()) {
38
+ if (!isPlainObject(st)) { fail.push(`${rel}: jobs.${jid}.steps[${i}] 不是映射`); continue }
39
+ if (!('uses' in st) && !('run' in st)) fail.push(`${rel}: jobs.${jid}.steps[${i}] 既无 uses 也无 run`)
40
+ if ('name' in st && typeof st.name !== 'string') fail.push(`${rel}: jobs.${jid}.steps[${i}].name 不是字符串`)
41
+ }
42
+ }
43
+ }
44
+
45
+ if (fail.length) {
46
+ console.error('✗ 工作流语法检查:失败')
47
+ for (const f of fail) console.error(' · ' + f)
48
+ process.exit(1)
49
+ }
50
+ console.log(`✓ 工作流语法:${files.length} 个文件全部可解析且结构合规`)
package/scripts/smoke.mjs CHANGED
@@ -2331,12 +2331,12 @@ ok('bundle:已转为公开发布 npm(无 private + publishConfig.access=publ
2331
2331
  ok('§40 自定位行:入口文件真实存在(拿不到入口就不追加该行——宁可不隔离,也不加一行注定 broken 的)',
2332
2332
  selfRow !== null && existsSync(fileURLToPath(selfRow.name)), String(selfRow?.name))
2333
2333
 
2334
- // ── 缺省档(auto):声明 20 行(19 + 自定位行)→ 预设档实例未出现 ⇒ 自动回落全局 ────────────
2334
+ // ── 缺省档(auto):声明 23 行(22 + 自定位行)→ 预设档实例未出现 ⇒ 自动回落全局 ────────────
2335
2335
  resetAll()
2336
2336
  indexMod.apply(makeCtx(), { presetIds: ['ppt'] })
2337
2337
  await flush() // 声明走的是异步队列(inject → register),让它落地
2338
- ok('§40 缺省档:声明 20 行 = 19 条 standard 组合行 + 第 20 行自定位行("只在预设里可见"靠它)',
2339
- declared.length === 1 && declared[0]?.plugins?.length === 20 && declared[0]?.plugins?.at(-1)?.id === 'ppt-studio-scope',
2338
+ ok('§40 缺省档:声明 23 行 = 22 条 standard 组合行 + 第 23 行自定位行("只在预设里可见"靠它)',
2339
+ declared.length === 1 && declared[0]?.plugins?.length === 23 && declared[0]?.plugins?.at(-1)?.id === 'ppt-studio-scope',
2340
2340
  `声明=${declared.length} 次|行数=${declared[0]?.plugins?.length}|末行=${declared[0]?.plugins?.at(-1)?.id ?? '(无)'}`)
2341
2341
  ok('§40 缺省档:健康判定期间**不注册**能力面(隔离生效时 profile 档必须零能力面)',
2342
2342
  tools === 0 && cmds === 0 && skills === 0 && listeners === 0,
@@ -2380,14 +2380,14 @@ ok('bundle:已转为公开发布 npm(无 private + publishConfig.access=publ
2380
2380
  typeof globalThis.__pptScopeApplied?.at === 'number' && globalThis.__pptScopeApplied.at > 0,
2381
2381
  JSON.stringify(globalThis.__pptScopeApplied))
2382
2382
 
2383
- // ── 传统档(scope=profile):不隔离、能力面全局,声明回到 19 行(不含自定位行)──────────────
2383
+ // ── 传统档(scope=profile):不隔离、能力面全局,声明回到 22 行(不含自定位行)──────────────
2384
2384
  resetAll()
2385
2385
  const trBase = { tools, skills }
2386
2386
  indexMod.apply(makeCtx(), { scope: 'profile' })
2387
2387
  await grace()
2388
- ok('§40 传统档:能力面注册在装入层(全局可见),且声明**不含**自定位行(19 行)',
2388
+ ok('§40 传统档:能力面注册在装入层(全局可见),且声明**不含**自定位行(22 行)',
2389
2389
  tools - trBase.tools === 22 && skills - trBase.skills === 17 && declared.length === 1
2390
- && declared[0]?.plugins?.length === 19 && declared[0]?.plugins?.at(-1)?.id !== 'ppt-studio-scope'
2390
+ && declared[0]?.plugins?.length === 22 && declared[0]?.plugins?.at(-1)?.id !== 'ppt-studio-scope'
2391
2391
  && presetMod.presetDeliveryStatus().isolation === 'profile',
2392
2392
  `增量 tools=${tools - trBase.tools} skills=${skills - trBase.skills}|行数=${declared[0]?.plugins?.length}|isolation=${presetMod.presetDeliveryStatus().isolation}`)
2393
2393
 
@@ -2426,8 +2426,8 @@ ok('bundle:已转为公开发布 npm(无 private + publishConfig.access=publ
2426
2426
  // ── 0.1.7 的**真适配**:预设靠"向注册表声明"交付(写目录那条路已被上游删除)────────────
2427
2427
  // 判据分三层:① 真的调到 register 了;② 声明形状对(id/名/序/行数/内容);③ 双挂载不会声明两次。
2428
2428
  const declDirect = presetMod.presetDeclaration()
2429
- ok('§40 预设组合:名字/简介/序号取自包内 `agent-presets/ppt/`(19 条 standard 组合行;选择器里显示的就是这些)',
2430
- declDirect.name === 'PPT 工作室' && declDirect.order === 2 && declDirect.plugins.length === 19
2429
+ ok('§40 预设组合:名字/简介/序号取自包内 `agent-presets/ppt/`(22 条 standard 组合行;选择器里显示的就是这些)',
2430
+ declDirect.name === 'PPT 工作室' && declDirect.order === 2 && declDirect.plugins.length === 22
2431
2431
  && typeof declDirect.description === 'string' && declDirect.description.length > 10,
2432
2432
  `name=${declDirect.name}|order=${declDirect.order}|行数=${declDirect.plugins.length}|desc=${declDirect.description.length} 字`)
2433
2433
  // `!!js` 必须**保真**成 Loader 认的 `{__jsExpr}`:降级成普通字符串会让
@@ -95,3 +95,13 @@ node scripts/smoke.mjs
95
95
  - **G3 创建 Release 就等于发布** ✗ —— 未获用户明确指令不得创建 Release;撤回需**删 Release + `gh run cancel`**(本地 pack 的 tag 不会自动发)。
96
96
  - **G4 skill 的 frontmatter 是 YAML** ✗ —— 描述里写 `role: decoration` 这类含 **`: `** 的纯量会导致该 skill **静默解析失败**(症状:14 个对、1 个不见)⇒ 描述里用全角「:」或加引号。
97
97
  - **G5 用自己的注册器自证** ✓ —— 改完 skill 立刻跑 `bundledSkillNames()`(插件自带扫描器)确认数量与名字,**别等装到用户机器上才发现**。
98
+
99
+ ## H. DSH 升级适配坑(2026-10-17)
100
+
101
+ - **H1 升级会改标准预设** ✗ —— 0.2.1-rc.2 把 standard 组合行 19→22,且参照位置迁到 `dsh-web-app/presets/standard.patch.yml` 的 `config.plugins` ⇒ 升级后**先跑 `check-preset`**,再按新基准重写 `agent-presets/ppt/agent.cordis.yml`(保留 `persona` 有意差异)。
102
+ - **H2 别拿旧清单判版本** ✗ —— `~/.dsh/dsh-runtimes/*/runtime.json` 的 `desktopVersion` 可能是旧的;运行宿主版本看 `app.asar/package.json` + `plugin_manager list_bundles` 的 bundle 版本。
103
+ - **H3 全局 npm CLI 中断安装会坏** ✗ —— 超时/断网会留下残缺树 + `EEXIST` shim,表现为 `Cannot find module ...lib/bin.js`;修法:删 `%APPDATA%\npm\dsh*` 与 `node_modules\@deepseek-ai\dsh` 后**后台**重装;且**不要用 `| tail` 之类的管道去取退出码**。
104
+ - **H4 verify:products 需要导出目录存在** ✗ —— 缺 `ppt-deliverable/手画12+1/` 会在导出阶段 ENOENT;先建目录再跑。
105
+ - **H5 历史生成器会写 skills** ✗ —— `examples/hand-drawn/gen/s3.cjs` 曾直接写 `skills/*`;已加 `ALLOW_HISTORICAL=1` 保护、默认跳过,防误跑覆盖正式 skill。
106
+
107
+ - **H6 CI 被测的 DSH 版本必须是单源、并与目标端一致** ✗ —— 版本写在 `dsh-target.json`(唯一事实源),CI 用 `node -p` 读它;`docs/14` 的 `dsh-target:` 行与 `dsh.engines.dsh` 下限由 `scripts/check-dsh-target.mjs`(并入 `npm test`)强制一致。历史上曾把手写死版钉在 `0.1.6-alpha.2`,而预设来自 0.2.1 ⇒ 旧 mount base 解析不到新行 ⇒ 安装自证 27/1 假红;另设 `dsh-next-probe.yml` 用 `@next` 周检(只告警、不拦 PR),提前暴露「上游已变、该适配了」。