@bruc3van/dsh-doctor 0.5.3 → 0.5.5
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/README.en.md +17 -12
- package/README.md +17 -12
- package/migrations/dsh-v0.1.1-rc.2__dsh-v0.1.2-alpha.2/packages.json +11 -0
- package/package.json +1 -1
- package/skills/dsh-plugin-upgrade/SKILL.md +36 -9
- package/skills/dsh-plugin-upgrade/references/compatibility-strategy.md +51 -0
- package/skills/dsh-plugin-upgrade/references/migration-map.md +10 -0
- package/skills/dsh-plugin-upgrade/references/source-investigation.md +97 -0
- package/skills/dsh-plugin-upgrade/references/verification.md +3 -1
- package/src/cli.mjs +8 -1
- package/src/migrate-verify.mjs +71 -4
- package/src/migrate.mjs +99 -13
- package/src/safe-write.mjs +28 -1
- package/skills/dsh-plugin-upgrade/evals/evals.json +0 -49
package/README.en.md
CHANGED
|
@@ -29,14 +29,17 @@ Upgrade this plugin from DSH 0.1.1 to DSH 0.1.2.
|
|
|
29
29
|
Analyze compatibility first, modify the code, then complete build and runtime verification.
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
An upgrade request does not itself authorize dropping the old runtime. If the developer has not said, the skill explicitly asks whether the same upgraded plugin release must still support DSH 0.1.1 before any migration write, dependency installation, build, or runtime command. When compatibility must be preserved, the agent designs a dual-version approach first and verifies 0.1.1 and 0.1.2 separately; one successful 0.1.2 run is not dual-version evidence.
|
|
33
|
+
|
|
32
34
|
The skill reminds the agent to work in this order:
|
|
33
35
|
|
|
34
36
|
1. inspect the plugin root, Harness checkout, package manager, and available DSH Doctor;
|
|
35
37
|
2. analyze source, type imports, dependencies, manifest, client graph, patches, and build output;
|
|
36
|
-
3.
|
|
37
|
-
4.
|
|
38
|
-
5.
|
|
39
|
-
6.
|
|
38
|
+
3. confirm whether the upgraded release targets only 0.1.2 or must remain compatible with 0.1.1;
|
|
39
|
+
4. preview and apply code changes that are known to be equivalent under the selected compatibility strategy;
|
|
40
|
+
5. use the new API owners to handle semantic changes that require understanding the plugin;
|
|
41
|
+
6. rebuild the plugin and run static, build, and isolated runtime verification, covering both versions in dual-version mode;
|
|
42
|
+
7. report the compatibility intent, changed files, remaining work, backups, and the verification level actually reached.
|
|
40
43
|
|
|
41
44
|
`npx skills add` installs agent instructions only. It does not install DSH Doctor globally. The skill checks the local CLI and npm registry first. If the local version is unsuitable, it uses a pinned `npx` version by default and does not change the global npm installation.
|
|
42
45
|
|
|
@@ -96,11 +99,11 @@ Source analysis uses the TypeScript AST and is cross-checked against the manifes
|
|
|
96
99
|
## Safety
|
|
97
100
|
|
|
98
101
|
- `diagnose`, `migrate analyze`, and static verification are read-only and do not load or execute the inspected plugin;
|
|
99
|
-
- `migrate apply` requires `--safe`;
|
|
102
|
+
- `migrate apply` requires `--safe --plan-file`; preview only creates a new plan outside the plugin root, never overwrites an existing file, and persists the complete analysis plus every input-file hash; `--yes` applies only that same reviewed plan;
|
|
100
103
|
- only `exact` migrations are auto-edited; semantic changes are not guessed;
|
|
101
104
|
- SHA-256 is checked before writing, so a file changed after preview is rejected;
|
|
102
105
|
- existing files receive timestamped backups and are replaced atomically through a temporary file;
|
|
103
|
-
- build and runtime verification execute plugin scripts
|
|
106
|
+
- build and runtime verification synchronize dependencies and execute plugin scripts, so they require explicit `--yes --install`; install lifecycle scripts are disabled and lockfile plus resolved-version evidence is recorded;
|
|
104
107
|
- runtime verification uses a temporary `DSH_HOME`, not the normal `~/.dsh`;
|
|
105
108
|
- JSON, baselines, and recovery snapshots redact plugin configuration and common secret/token/password/key fields;
|
|
106
109
|
- global CLI installation, persistent quarantine, plugin removal, and publishing are never performed automatically by the skill.
|
|
@@ -112,7 +115,7 @@ Node.js `^22.19.0` or `>=24.0.0` is required.
|
|
|
112
115
|
First confirm that the CLI contains the required migration:
|
|
113
116
|
|
|
114
117
|
```sh
|
|
115
|
-
npx --yes --package=@bruc3van/dsh-doctor@0.5.
|
|
118
|
+
npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
|
|
116
119
|
dsh-doctor migrations list
|
|
117
120
|
```
|
|
118
121
|
|
|
@@ -133,31 +136,33 @@ Analysis checks source, dependencies, manifest, client graph, patch targets, and
|
|
|
133
136
|
```sh
|
|
134
137
|
# Preview
|
|
135
138
|
dsh-doctor migrate apply /path/to/plugin --safe \
|
|
139
|
+
--plan-file /temporary/path/reviewed-migration-plan.json \
|
|
136
140
|
--harness-root /path/to/deepseek-harness --json
|
|
137
141
|
|
|
138
142
|
# Write after confirmation
|
|
139
143
|
dsh-doctor migrate apply /path/to/plugin --safe --yes \
|
|
144
|
+
--plan-file /temporary/path/reviewed-migration-plan.json \
|
|
140
145
|
--harness-root /path/to/deepseek-harness --json
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
Apply
|
|
148
|
+
The plan must stay outside the plugin root so it is not analyzed as plugin input. Apply checks the plan digest, every analyzed input, and each edit's before/after hashes; source, manifest, or other analyzed-input changes require a new reviewed plan. Deterministic dependency edits use catalog-owned Client/Host and peer/dev policies instead of inheriting the removed package's dependency section. Every changed file receives a backup.
|
|
144
149
|
|
|
145
150
|
### 3. Verify
|
|
146
151
|
|
|
147
152
|
```sh
|
|
148
153
|
dsh-doctor migrate verify /path/to/plugin --level static \
|
|
149
154
|
--harness-root /path/to/deepseek-harness --json
|
|
150
|
-
dsh-doctor migrate verify /path/to/plugin --level build --yes \
|
|
155
|
+
dsh-doctor migrate verify /path/to/plugin --level build --yes --install \
|
|
151
156
|
--harness-root /path/to/deepseek-harness --json
|
|
152
|
-
dsh-doctor migrate verify /path/to/plugin --level runtime --yes \
|
|
157
|
+
dsh-doctor migrate verify /path/to/plugin --level runtime --yes --install \
|
|
153
158
|
--harness-root /path/to/deepseek-harness --json
|
|
154
159
|
```
|
|
155
160
|
|
|
156
161
|
| Level | What it checks |
|
|
157
162
|
|---|---|
|
|
158
163
|
| `static` | Rechecks source, manifest, client graph, patches, and artifacts |
|
|
159
|
-
| `build` |
|
|
160
|
-
| `runtime` |
|
|
164
|
+
| `build` | Synchronizes and verifies target dependencies and the lockfile, then runs build/test scripts and scans output again |
|
|
165
|
+
| `runtime` | After dependency and build gates, packs the real tarball and checks target DSH, installed package, bundle, and effective configuration in a temporary profile |
|
|
161
166
|
|
|
162
167
|
Verification states are:
|
|
163
168
|
|
package/README.md
CHANGED
|
@@ -29,14 +29,17 @@ npx skills add bruc3van/dsh-doctor
|
|
|
29
29
|
先分析兼容问题,再修改代码,最后完成构建和运行时验证。
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
升级请求本身不代表可以放弃旧版。若开发者没有说明,Skill 会在任何迁移写入、依赖安装、构建或运行时命令前,显式询问升级后的同一插件版本是否还要兼容 DSH 0.1.1。选择保留兼容时,Agent 会先设计双版本适配方式,并分别验证 0.1.1 和 0.1.2;不能用一次 0.1.2 验证代替双版本结论。
|
|
33
|
+
|
|
32
34
|
Skill 会提醒 Agent 按下面的顺序工作:
|
|
33
35
|
|
|
34
36
|
1. 检查插件目录、Harness checkout、包管理器和可用的 DSH Doctor;
|
|
35
37
|
2. 分析源码、类型导入、依赖、manifest、client graph、patch 和构建产物;
|
|
36
|
-
3.
|
|
37
|
-
4.
|
|
38
|
-
5.
|
|
39
|
-
6.
|
|
38
|
+
3. 确认升级后是仅支持 0.1.2,还是同一版本继续兼容 0.1.1;
|
|
39
|
+
4. 预览并应用可以确定等价、且符合所选兼容策略的代码修改;
|
|
40
|
+
5. 根据新的 API 所有者处理需要理解业务的语义迁移;
|
|
41
|
+
6. 重新构建插件,并依次做静态、构建和隔离运行时验证;双版本模式分别验证两端;
|
|
42
|
+
7. 报告兼容目标、修改内容、剩余问题、备份和实际达到的验证等级。
|
|
40
43
|
|
|
41
44
|
`npx skills add` 只安装 Agent 指令,不会安装全局 DSH Doctor。Skill 会先检查本地 CLI 和 npm registry;本地版本不合适时,默认使用固定版本的 `npx`,不会自行修改全局 npm 安装。
|
|
42
45
|
|
|
@@ -96,11 +99,11 @@ Migration catalog 保存 source/target tag 和 Git commit,并记录 package、
|
|
|
96
99
|
## 安全性
|
|
97
100
|
|
|
98
101
|
- `diagnose`、`migrate analyze` 和静态验证只读,不加载或执行待检查插件;
|
|
99
|
-
- `migrate apply` 必须带 `--safe
|
|
102
|
+
- `migrate apply` 必须带 `--safe --plan-file`;预览只会新建插件目录外的 plan,不会覆盖已有文件,并把完整分析和所有输入文件哈希固化下来;`--yes` 只应用同一个已审阅 plan;
|
|
100
103
|
- 只有 `exact` 迁移会自动改代码,语义变化不会自动猜测;
|
|
101
104
|
- 写入前检查文件 SHA-256,预览后文件发生变化会拒绝写入;
|
|
102
105
|
- 修改已有文件前创建时间戳备份,并使用临时文件原子替换;
|
|
103
|
-
- build 和 runtime
|
|
106
|
+
- build 和 runtime 会同步依赖并执行插件脚本,因此必须显式使用 `--yes --install`;依赖安装禁用 lifecycle scripts,并记录 lockfile 和实际解析版本证据;
|
|
104
107
|
- runtime 使用临时 `DSH_HOME`,不会安装到正常的 `~/.dsh`;
|
|
105
108
|
- JSON、baseline 和恢复快照会脱敏插件配置和常见 secret/token/password/key 字段;
|
|
106
109
|
- 全局 CLI 安装、持久隔离、删除插件和发布都不会由 Skill 自动执行。
|
|
@@ -112,7 +115,7 @@ Migration catalog 保存 source/target tag 和 Git commit,并记录 package、
|
|
|
112
115
|
先确认 CLI 包含需要的迁移:
|
|
113
116
|
|
|
114
117
|
```sh
|
|
115
|
-
npx --yes --package=@bruc3van/dsh-doctor@0.5.
|
|
118
|
+
npx --yes --package=@bruc3van/dsh-doctor@0.5.5 \
|
|
116
119
|
dsh-doctor migrations list
|
|
117
120
|
```
|
|
118
121
|
|
|
@@ -133,31 +136,33 @@ dsh-doctor migrate analyze /path/to/plugin \
|
|
|
133
136
|
```sh
|
|
134
137
|
# 预览
|
|
135
138
|
dsh-doctor migrate apply /path/to/plugin --safe \
|
|
139
|
+
--plan-file /temporary/path/reviewed-migration-plan.json \
|
|
136
140
|
--harness-root /path/to/deepseek-harness --json
|
|
137
141
|
|
|
138
142
|
# 确认后写入
|
|
139
143
|
dsh-doctor migrate apply /path/to/plugin --safe --yes \
|
|
144
|
+
--plan-file /temporary/path/reviewed-migration-plan.json \
|
|
140
145
|
--harness-root /path/to/deepseek-harness --json
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
Apply
|
|
148
|
+
Plan 必须放在插件目录外,避免被当成插件输入。Apply 会核对 plan digest、完整分析输入和每个修改的 before/after hash;源码、manifest 或其他分析输入在预览后变化时必须重新生成并审阅 plan。确定性依赖改写使用 catalog 明确的 Client/Host 与 peer/dev 策略,不再沿用旧包所在的 dependency section。每个被修改的文件都会保留备份。
|
|
144
149
|
|
|
145
150
|
### 3. 验证
|
|
146
151
|
|
|
147
152
|
```sh
|
|
148
153
|
dsh-doctor migrate verify /path/to/plugin --level static \
|
|
149
154
|
--harness-root /path/to/deepseek-harness --json
|
|
150
|
-
dsh-doctor migrate verify /path/to/plugin --level build --yes \
|
|
155
|
+
dsh-doctor migrate verify /path/to/plugin --level build --yes --install \
|
|
151
156
|
--harness-root /path/to/deepseek-harness --json
|
|
152
|
-
dsh-doctor migrate verify /path/to/plugin --level runtime --yes \
|
|
157
|
+
dsh-doctor migrate verify /path/to/plugin --level runtime --yes --install \
|
|
153
158
|
--harness-root /path/to/deepseek-harness --json
|
|
154
159
|
```
|
|
155
160
|
|
|
156
161
|
| 级别 | 验证内容 |
|
|
157
162
|
|---|---|
|
|
158
163
|
| `static` | 再次检查源码、manifest、client graph、patch 和产物 |
|
|
159
|
-
| `build` |
|
|
160
|
-
| `runtime` |
|
|
164
|
+
| `build` | 先同步并核验目标依赖和 lockfile,再运行已有构建/测试脚本并重新扫描产物 |
|
|
165
|
+
| `runtime` | 完成依赖与构建门后,打真实 tarball,在临时 profile 中验证目标 DSH、安装包、bundle 和生效配置 |
|
|
161
166
|
|
|
162
167
|
验证状态依次是:
|
|
163
168
|
|
|
@@ -42,6 +42,17 @@
|
|
|
42
42
|
"@deepseek-ai/dsh-client-store": "0.1.2-alpha.2",
|
|
43
43
|
"@deepseek-ai/dsh-session": "0.1.2-alpha.2"
|
|
44
44
|
},
|
|
45
|
+
"dependencyPolicies": {
|
|
46
|
+
"@deepseek-ai/cordis": {
|
|
47
|
+
"client": ["peerDependencies", "devDependencies"]
|
|
48
|
+
},
|
|
49
|
+
"@deepseek-ai/dsh-client-store": {
|
|
50
|
+
"client": ["devDependencies"]
|
|
51
|
+
},
|
|
52
|
+
"@deepseek-ai/dsh-session": {
|
|
53
|
+
"client": ["devDependencies"]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
45
56
|
"semanticReplacements": {
|
|
46
57
|
"@deepseek-ai/dsh-client-runtime": [
|
|
47
58
|
"@deepseek-ai/dsh-api-session-controller",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dsh-plugin-upgrade
|
|
3
|
-
description: Help diagnose and upgrade a DeepSeek Harness plugin from DSH 0.1.1 to 0.1.2 with dsh-doctor. Use when a plugin developer asks to assess compatibility, identify changed APIs, replace removed dsh-client-runtime or dsh-host-apiproxy usage, update DSH dependencies, modify plugin code, rebuild artifacts, or verify the plugin before releasing a new version. Apply only catalog-confirmed exact rewrites automatically, guide semantic code changes, and report what still needs developer verification. The current catalog uses dsh-v0.1.1-rc.2 and dsh-v0.1.2-alpha.2 as its reference points.
|
|
3
|
+
description: Help diagnose and upgrade a DeepSeek Harness plugin from DSH 0.1.1 to 0.1.2 with dsh-doctor, while deciding explicitly whether the upgraded release must remain compatible with DSH 0.1.1. Use when a plugin developer asks to assess compatibility, identify changed APIs, replace removed dsh-client-runtime or dsh-host-apiproxy usage, update DSH dependencies, modify plugin code, rebuild artifacts, or verify the plugin before releasing a new version. Apply only catalog-confirmed exact rewrites automatically, guide semantic code changes, and report what still needs developer verification. The current catalog uses dsh-v0.1.1-rc.2 and dsh-v0.1.2-alpha.2 as its reference points.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Upgrade a DSH 0.1.1 plugin to 0.1.2
|
|
@@ -16,6 +16,7 @@ Determine:
|
|
|
16
16
|
- the plugin repository root;
|
|
17
17
|
- the DSH Harness checkout when available;
|
|
18
18
|
- the plugin's actual DSH dependency and peer ranges, and the requested 0.1.2 target;
|
|
19
|
+
- whether the upgraded plugin must keep supporting DSH 0.1.1, target only DSH 0.1.2, or still needs that decision from the developer;
|
|
19
20
|
- whether the developer authorizes writes and project command execution;
|
|
20
21
|
- whether the developer authorizes a global CLI install or update;
|
|
21
22
|
- the plugin's package manager and build scripts.
|
|
@@ -25,12 +26,26 @@ Use these catalog reference points in the current CLI commands:
|
|
|
25
26
|
- source: `dsh-v0.1.1-rc.2`
|
|
26
27
|
- target: `dsh-v0.1.2-alpha.2`
|
|
27
28
|
|
|
28
|
-
Read [cli-bootstrap.md](references/cli-bootstrap.md) before running any migration command, [migration-map.md](references/migration-map.md) before making semantic changes, and [verification.md](references/verification.md) before build or runtime verification.
|
|
29
|
+
Read [cli-bootstrap.md](references/cli-bootstrap.md) before running any migration command, [compatibility-strategy.md](references/compatibility-strategy.md) before proposing or writing changes, [source-investigation.md](references/source-investigation.md) before investigating an unlisted version or semantic task, [migration-map.md](references/migration-map.md) before making semantic changes, and [verification.md](references/verification.md) before dependency, build, or runtime verification.
|
|
29
30
|
|
|
30
31
|
Before touching the plugin, inspect the local CLI and perform the read-only registry update check described in `cli-bootstrap.md`. Select one exact DSH Doctor version, verify it exposes this migration catalog, and keep the same invocation for analyze, apply, and verify. Prefer an exact-version `npx` fallback over changing the developer's global installation. Never globally install or update the CLI without explicit authorization.
|
|
31
32
|
|
|
32
33
|
The examples below use `dsh-doctor` for readability. When bootstrap selected an npx invocation, substitute the complete pinned prefix, `npx --yes --package=@bruc3van/dsh-doctor@<selected-version> dsh-doctor`, in every phase.
|
|
33
34
|
|
|
35
|
+
## Compatibility decision gate
|
|
36
|
+
|
|
37
|
+
An upgrade request does not say whether the developer accepts dropping DSH 0.1.1. Write authorization also does not answer that product decision. Do not infer the answer from the current peer range, the word "upgrade," or the fact that a 0.1.2-only rewrite is simpler.
|
|
38
|
+
|
|
39
|
+
You may inspect files, bootstrap the CLI, and run read-only analysis to collect evidence. Before any `migrate apply` preview, source or manifest edit, dependency install, build, or runtime command:
|
|
40
|
+
|
|
41
|
+
1. record an explicit choice already present in the developer's request; or
|
|
42
|
+
2. ask: **"Should the same upgraded plugin release continue to support DSH 0.1.1, or may it target DSH 0.1.2 only?"**
|
|
43
|
+
|
|
44
|
+
If the answer is unavailable or ambiguous, stop at `analyzed`, identify the compatibility decision as pending, and do not modify the plugin. Follow the selected path in [compatibility-strategy.md](references/compatibility-strategy.md):
|
|
45
|
+
|
|
46
|
+
- **0.1.2-only:** the catalog's exact rewrites may be applied after their normal preview and write confirmation.
|
|
47
|
+
- **dual-version:** treat every 0.1.2 rewrite as a candidate, not an automatically compatible change. Design and review an adapter or conditional entry/build approach before writing. If the same release cannot honestly support both versions, explain the conflict and ask whether separate releases are acceptable; that changes the compatibility intent and must not be presented as same-release dual compatibility.
|
|
48
|
+
|
|
34
49
|
## Phase 1: analyze
|
|
35
50
|
|
|
36
51
|
Run from any directory:
|
|
@@ -44,6 +59,8 @@ dsh-doctor migrate analyze <plugin-root> \
|
|
|
44
59
|
|
|
45
60
|
Append `--harness-root <deepseek-harness-root>` when the checkout is available. Prefer an exact Harness checkout so the CLI verifies both tag commits. Both tags must exist and resolve to the commits recorded by the catalog; fetch the repository tags first when a shallow checkout lacks them. Without `--harness-root`, analysis is catalog-only and must be reported as such.
|
|
46
61
|
|
|
62
|
+
Follow `source-investigation.md` to record the actual PATH or explicit DSH command, installed package, profile, plugin manifest and resolved dependency versions. When the actual source or target differs from the catalog refs, inspect that additional ref interval separately with read-only Git commands. For each semantic finding, use its target module and the catalog reference paths to inspect the exact exported API and plugin callers; do not stop at naming a likely new owner.
|
|
63
|
+
|
|
47
64
|
Group the result by:
|
|
48
65
|
|
|
49
66
|
1. errors blocking compatibility;
|
|
@@ -56,19 +73,25 @@ Do not infer compatibility from a clean bundled artifact alone. The analyzer int
|
|
|
56
73
|
|
|
57
74
|
## Phase 2: apply exact changes
|
|
58
75
|
|
|
76
|
+
Enter this phase only after the compatibility decision gate is resolved. The commands below are the 0.1.2-target migration path. For dual-version work, first apply the additional constraints in `compatibility-strategy.md`; do not assume a catalog-safe codemod is safe for 0.1.1.
|
|
77
|
+
|
|
59
78
|
Preview first:
|
|
60
79
|
|
|
61
80
|
```sh
|
|
62
|
-
dsh-doctor migrate apply <plugin-root> --safe
|
|
81
|
+
dsh-doctor migrate apply <plugin-root> --safe \
|
|
82
|
+
--plan-file <reviewed-plan-outside-plugin-root.json> \
|
|
83
|
+
--harness-root <deepseek-harness-root> --json
|
|
63
84
|
```
|
|
64
85
|
|
|
65
|
-
Review affected files and hashes. Apply only with explicit authorization:
|
|
86
|
+
Store the plan outside the plugin root so it is not treated as plugin input. Review the plan id, complete analysis, affected files, and before/after hashes. Apply that exact plan only with explicit authorization:
|
|
66
87
|
|
|
67
88
|
```sh
|
|
68
|
-
dsh-doctor migrate apply <plugin-root> --safe --yes
|
|
89
|
+
dsh-doctor migrate apply <plugin-root> --safe --yes \
|
|
90
|
+
--plan-file <same-reviewed-plan.json> \
|
|
91
|
+
--harness-root <deepseek-harness-root> --json
|
|
69
92
|
```
|
|
70
93
|
|
|
71
|
-
The CLI creates timestamped backups and refuses writes when
|
|
94
|
+
The CLI binds the apply to the persisted report and edit hashes, creates timestamped backups, and refuses writes when the plugin analysis changed after preview. It may split a mixed import: exact symbols move to their new owners while semantic symbols remain unresolved. Dependency additions follow catalog-owned Client/Host and peer/dev policies instead of copying the removed package's old dependency section. It may pin non-removed DSH development dependencies. It does not widen the ranges of existing published peers automatically. Never mechanically replace the removed Client Runtime with one aggregate package; no such replacement exists.
|
|
72
95
|
|
|
73
96
|
After apply, inspect every `MIG_SEMANTIC_API_CHANGE`. Rewrite behavior using the new domain owner and its current snapshot/lifecycle contract. Keep these edits separate enough to review and test.
|
|
74
97
|
|
|
@@ -78,11 +101,11 @@ Run the gates in order:
|
|
|
78
101
|
|
|
79
102
|
```sh
|
|
80
103
|
dsh-doctor migrate verify <plugin-root> --level static --harness-root <deepseek-harness-root> --json
|
|
81
|
-
dsh-doctor migrate verify <plugin-root> --level build --yes --harness-root <deepseek-harness-root> --json
|
|
82
|
-
dsh-doctor migrate verify <plugin-root> --level runtime --yes --harness-root <deepseek-harness-root> --json
|
|
104
|
+
dsh-doctor migrate verify <plugin-root> --level build --yes --install --harness-root <deepseek-harness-root> --json
|
|
105
|
+
dsh-doctor migrate verify <plugin-root> --level runtime --yes --install --harness-root <deepseek-harness-root> --json
|
|
83
106
|
```
|
|
84
107
|
|
|
85
|
-
Build and runtime levels
|
|
108
|
+
Build and runtime levels require `--yes --install`: the CLI first runs the detected package manager with lifecycle scripts disabled, updates the lockfile when needed, and verifies the installed DSH/Cordis versions against runtime, development, peer, and optional dependency declarations before executing project scripts. A required peer must resolve and satisfy every declared range; a missing optional peer is recorded but does not fail the gate. Review lockfile changes as migration changes. Runtime verification then packs the real plugin, creates a temporary `DSH_HOME`, installs into a new web profile, dumps effective config, and performs an activation smoke. It does not modify the developer's normal `~/.dsh`.
|
|
86
109
|
|
|
87
110
|
Resolve every semantic task and rebuild stale artifacts before expecting static verification to pass. An `apply` exit code of 1 after successful writes means migration blockers remain in the follow-up analysis; inspect `mode`, `writes`, and `report` instead of treating it as a write failure.
|
|
88
111
|
|
|
@@ -90,6 +113,8 @@ On failure, preserve and report the temporary directory. On success, the CLI rem
|
|
|
90
113
|
|
|
91
114
|
Runtime activation is not business-behavior proof. Finish with targeted manual or automated checks for visible UI, service lifecycle, event subscriptions, cleanup, configuration, and plugin-specific workflows.
|
|
92
115
|
|
|
116
|
+
For a dual-version result, verify the produced release against both the actual 0.1.1 and 0.1.2 environments. The catalog-driven `migrate verify` commands prove only the configured target side. Use the plugin's own build/test flow and an isolated 0.1.1 Harness profile for the legacy side, and name the evidence from each matrix row. A successful 0.1.2 runtime smoke must never be reported as dual-version compatibility.
|
|
117
|
+
|
|
93
118
|
## Prepare for release when requested
|
|
94
119
|
|
|
95
120
|
Verification prepares the plugin for release but does not publish it. If the developer explicitly asks to release the upgraded plugin, first follow the repository's own release instructions and confirm that required semantic and behavior checks are complete. Then update the plugin version and changelog, inspect the packed artifact, and use the repository's existing commit, tag, publish, and registry-verification workflow. Do not commit, tag, or publish merely because the migration skill was installed or run.
|
|
@@ -107,3 +132,5 @@ State the highest achieved gate exactly:
|
|
|
107
132
|
Do not call the plugin compatible while errors, semantic tasks, stale artifacts, or required behavior checks remain. Include backups and retained temporary directories in the handoff.
|
|
108
133
|
Also report the selected DSH Doctor version and source (`local`, exact-version `npx`, or explicitly authorized global install), the registry version observed at bootstrap, and whether update status was current, outdated, missing, or unknown.
|
|
109
134
|
Report the plugin's actual source/target version evidence separately from the catalog reference refs. State whether the plugin is ready for its normal release process; if a release was explicitly requested and completed, include the commit, tag, registry, and release verification evidence.
|
|
135
|
+
Report the migration plan file and plan id, dependency-install command, lockfile change, and resolved target dependency versions. For semantic or unlisted-version work, include the DSH executable/package evidence, checkout refs and commits, inspected source paths, and remaining unknowns described in `source-investigation.md`.
|
|
136
|
+
State the compatibility intent as one of `0.1.2-only`, `dual-version`, or `pending developer decision`. For `dual-version`, report build, artifact, runtime, and behavior evidence separately for 0.1.1 and 0.1.2; only call the release dual-compatible when every required row passes.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Compatibility strategy
|
|
2
|
+
|
|
3
|
+
## Decide before changing the plugin
|
|
4
|
+
|
|
5
|
+
Moving a plugin to 0.1.2 and preserving 0.1.1 are different deliverables. The 0.1.2 catalog describes target migrations; it does not prove that the resulting source, dependency graph, artifact, or manifest still works on 0.1.1.
|
|
6
|
+
|
|
7
|
+
Record one explicit intent:
|
|
8
|
+
|
|
9
|
+
| Intent | Meaning | Write policy |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `0.1.2-only` | The next plugin release may drop 0.1.1 | Follow the catalog migration after preview and authorization |
|
|
12
|
+
| `dual-version` | The same release must support both 0.1.1 and 0.1.2 | Design the compatibility mechanism and two-version verification matrix before editing |
|
|
13
|
+
| `pending developer decision` | The developer has not chosen | Read-only analysis only; stop before apply, edits, installs, build, or runtime execution |
|
|
14
|
+
|
|
15
|
+
Do not treat general write authorization as permission to drop an older runtime. If the request does not settle the choice, ask the developer directly.
|
|
16
|
+
|
|
17
|
+
## Assess whether one release can support both
|
|
18
|
+
|
|
19
|
+
For `dual-version`, inventory each migration finding and determine whether the old and new owners coexist:
|
|
20
|
+
|
|
21
|
+
- static imports of packages that exist in only one DSH version;
|
|
22
|
+
- changed Service names, snapshot shapes, lifecycle, or event behavior;
|
|
23
|
+
- client graph `inject`, `external`, platform modules, and client exports;
|
|
24
|
+
- peer, optional peer, development, and bundled dependency ranges;
|
|
25
|
+
- patch targets and profile entries that differ between Harness versions;
|
|
26
|
+
- generated output that may capture version-specific imports.
|
|
27
|
+
|
|
28
|
+
Choose a repository-appropriate same-release mechanism based on evidence, such as a shared compatibility adapter with runtime feature detection or conditional version-specific entry points/builds inside the same package. Do not invent dynamic loading when the bundler or Harness activation model cannot support it. Do not publish a peer range that claims both versions until installation and runtime evidence supports that range.
|
|
29
|
+
|
|
30
|
+
The safe codemod is intentionally target-oriented. In dual-version mode, preview its changes only after the decision is recorded and review each proposed import and dependency edit against 0.1.1 before applying it. Semantic migrations always require the same cross-version review.
|
|
31
|
+
|
|
32
|
+
If the same release cannot load because required packages or static graph declarations are mutually exclusive, report the exact conflict. Ask the developer whether to change scope to separate plugin releases or accept a 0.1.2-only release. Neither alternative satisfies the original same-release `dual-version` intent.
|
|
33
|
+
|
|
34
|
+
## Verify a two-version matrix
|
|
35
|
+
|
|
36
|
+
Use exact Harness versions or checkouts and keep evidence separate:
|
|
37
|
+
|
|
38
|
+
| Gate | DSH 0.1.1 | DSH 0.1.2 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| dependency installation and peer resolution | required | required |
|
|
41
|
+
| typecheck/build/test and packed artifact inspection | required | required |
|
|
42
|
+
| isolated profile install and activation smoke | required | required |
|
|
43
|
+
| plugin-specific UI, Service lifecycle, commands, settings, and cleanup | required where applicable | required where applicable |
|
|
44
|
+
|
|
45
|
+
The current migration catalog and `migrate verify` target 0.1.2. They do not independently certify the legacy row. Verify 0.1.1 with the plugin repository's supported scripts and an isolated profile using the actual 0.1.1 Harness CLI. Preserve failure workspaces and report the exact commands, versions, artifacts, logs, or screenshots for each row.
|
|
46
|
+
|
|
47
|
+
Use precise conclusions:
|
|
48
|
+
|
|
49
|
+
- `0.1.2 runtime-verified; 0.1.1 not tested` is not dual-compatible.
|
|
50
|
+
- Passing builds on both versions is not runtime or behavior proof.
|
|
51
|
+
- Claim `dual-version` compatibility only after all required matrix rows pass for the same release artifact or for the explicitly documented version-specific artifact strategy.
|
|
@@ -25,6 +25,16 @@ Session snapshots no longer own Conversation views or all pending-interaction fa
|
|
|
25
25
|
- React, Cordis, client-store, ui-slots, and ui-primitives baseline modules are implicit and should not be repeated in `dsh.client.external`.
|
|
26
26
|
- A declared web client requires a published `exports["./client"]` artifact.
|
|
27
27
|
|
|
28
|
+
## Dependency ownership
|
|
29
|
+
|
|
30
|
+
An exact symbol move and its npm dependency placement are separate catalog decisions. The current target policy records Client relationships explicitly:
|
|
31
|
+
|
|
32
|
+
- Cordis must be present in matching `peerDependencies` and `devDependencies` for a client plugin;
|
|
33
|
+
- `dsh-client-store` and `dsh-session` Client/type relationships are development-only;
|
|
34
|
+
- existing published peer ranges are not widened automatically merely because a development dependency is pinned to the target catalog version.
|
|
35
|
+
|
|
36
|
+
Do not copy a replacement package into every dependency section that contained `dsh-client-runtime`. After apply, use the dependency verification gate to synchronize the lockfile and confirm the versions actually resolved in `node_modules`.
|
|
37
|
+
|
|
28
38
|
## Patch targets
|
|
29
39
|
|
|
30
40
|
For `MIG_PATCH_TARGET_CHANGED`, first confirm the Harness checkout is exact and its tag scan succeeded. Then compare the old and target web profile bundle patches: remove an obsolete override only when the old row disappeared without a replacement, or update the id when upstream deliberately renamed/moved the row. Do not guess a replacement id from a similar name.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Investigate the actual DSH source and installed runtime
|
|
2
|
+
|
|
3
|
+
Use this workflow whenever the plugin's declared or requested DSH version differs from a catalog reference, a semantic migration remains, or the developer asks what changed in DSH. The catalog is a starting point, not a substitute for checking the actual runtime and source.
|
|
4
|
+
|
|
5
|
+
## 1. Record the runtime actually in use
|
|
6
|
+
|
|
7
|
+
Keep these as separate facts:
|
|
8
|
+
|
|
9
|
+
- the DSH executable selected by PATH or an explicit command;
|
|
10
|
+
- the version printed by that executable;
|
|
11
|
+
- the DSH package installation backing the executable;
|
|
12
|
+
- the plugin's dependency, peer, optional-peer, and development ranges;
|
|
13
|
+
- the DSH packages resolved in the plugin lockfile and `node_modules`;
|
|
14
|
+
- the requested target version;
|
|
15
|
+
- the source and target refs used by the migration catalog.
|
|
16
|
+
|
|
17
|
+
On Windows, use read-only commands such as:
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
Get-Command dsh -All
|
|
21
|
+
dsh --version
|
|
22
|
+
npm root --global
|
|
23
|
+
Get-Content "$env:APPDATA\npm\node_modules\@deepseek-ai\dsh\package.json"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
On macOS or Linux:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
command -v -a dsh
|
|
30
|
+
dsh --version
|
|
31
|
+
npm root --global
|
|
32
|
+
cat "$(npm root --global)/@deepseek-ai/dsh/package.json"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Also inspect `DSH_HOME`, the selected profile manifest and lockfile, the plugin's lockfile, and any project-local DSH package. Do not assume PATH, a running DSH process, the profile, and the plugin workspace all resolve the same version.
|
|
36
|
+
|
|
37
|
+
## 2. Locate or obtain a Harness checkout
|
|
38
|
+
|
|
39
|
+
Prefer, in order:
|
|
40
|
+
|
|
41
|
+
1. an explicit checkout supplied by the developer;
|
|
42
|
+
2. a nearby project checkout whose remote and refs can be verified;
|
|
43
|
+
3. source provenance recorded by the installed package or repository metadata;
|
|
44
|
+
4. an upstream checkout obtained only when network access and cloning/fetching are within the developer's authorization.
|
|
45
|
+
|
|
46
|
+
Record `git remote -v`, worktree status, `git rev-parse <ref>`, and whether the checkout contains both relevant refs. Do not switch, reset, clean, or update a developer's checkout merely to inspect another ref. Use `git show`, `git diff`, `git grep`, and `git ls-tree` against refs without changing the worktree. A dirty checkout can still be used for ref-based inspection; report that the worktree itself was not treated as target evidence.
|
|
47
|
+
|
|
48
|
+
## 3. Build the additional-version delta
|
|
49
|
+
|
|
50
|
+
When the plugin's actual source or requested target is not exactly the catalog pair, compare the missing interval separately. At minimum inspect:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
git diff --name-status <actual-source-ref>..<actual-target-ref> -- packages apps
|
|
54
|
+
git diff <actual-source-ref>..<actual-target-ref> -- '**/package.json'
|
|
55
|
+
git grep -n '<old-symbol-or-service>' <actual-source-ref> -- packages apps
|
|
56
|
+
git grep -n '<old-symbol-or-service>' <actual-target-ref> -- packages apps
|
|
57
|
+
git grep -n '<new-owner-or-symbol>' <actual-target-ref> -- packages apps
|
|
58
|
+
git show <actual-target-ref>:<candidate-package>/package.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Check package existence and exports, declaration files or TypeScript source, Service names and Cordis injection, snapshot shapes, event and cleanup lifecycles, client manifest rules, profile patches, build presets, and upstream architecture or migration notes. Do not infer an API from a similarly named package or symbol.
|
|
62
|
+
|
|
63
|
+
Classify every additional difference as:
|
|
64
|
+
|
|
65
|
+
- confirmed exact move;
|
|
66
|
+
- semantic behavior change;
|
|
67
|
+
- removed with no replacement;
|
|
68
|
+
- configuration or packaging change;
|
|
69
|
+
- unknown because source or evidence is unavailable.
|
|
70
|
+
|
|
71
|
+
Only the catalog-confirmed pair is eligible for the built-in safe codemod. Treat additional exact-looking changes as proposed manual edits until independently reviewed and tested.
|
|
72
|
+
|
|
73
|
+
## 4. Investigate each semantic task in the plugin
|
|
74
|
+
|
|
75
|
+
Start from the finding's `file`, `symbol`, `targetModule`, `targetSymbol`, and `reason`. Then:
|
|
76
|
+
|
|
77
|
+
1. trace every caller and lifecycle owner in the plugin;
|
|
78
|
+
2. inspect the target package export and implementation at the exact target ref;
|
|
79
|
+
3. compare source and target snapshot/event shapes;
|
|
80
|
+
4. identify activation, subscription, cleanup, error, and recovery behavior;
|
|
81
|
+
5. decide whether one source tree can support both requested DSH versions;
|
|
82
|
+
6. propose the smallest behavior-preserving change and name the tests needed to prove it.
|
|
83
|
+
|
|
84
|
+
If the catalog reference paths in `migration.references` exist in the Harness checkout, read them at the target ref. They are supporting evidence, not a replacement for inspecting the actual exported API and the plugin's callers.
|
|
85
|
+
|
|
86
|
+
## 5. Report provenance and uncertainty
|
|
87
|
+
|
|
88
|
+
For each conclusion, report:
|
|
89
|
+
|
|
90
|
+
- actual installed/runtime version evidence;
|
|
91
|
+
- plugin manifest and resolved dependency evidence;
|
|
92
|
+
- checkout remote, refs, and commits;
|
|
93
|
+
- catalog-covered delta versus additionally inspected delta;
|
|
94
|
+
- exact source paths, symbols, or commands used;
|
|
95
|
+
- remaining unknowns and the developer decision they block.
|
|
96
|
+
|
|
97
|
+
Never call an unlisted version combination compatible merely because the catalog pair passed.
|
|
@@ -6,7 +6,9 @@ Static analysis covers source/type imports, dependency ranges, client graph decl
|
|
|
6
6
|
|
|
7
7
|
## Build
|
|
8
8
|
|
|
9
|
-
The CLI selects the package manager from lockfiles and runs declared scripts in this order: `typecheck`, `build`, `test`, `pack:check`, stopping at the first failure. Artifact verification requires a successful `build` or `pack:check`; `typecheck`/`test` alone do not pass this gate.
|
|
9
|
+
Build and runtime verification require `--yes --install`. The CLI selects the package manager from lockfiles, synchronizes dependencies with dependency lifecycle scripts disabled, records the lockfile hash before and after, and verifies installed DSH/Cordis versions against `dependencies`, `devDependencies`, `peerDependencies`, and `optionalDependencies`. Required peers must resolve and satisfy every declared range. Missing optional dependencies or peers are recorded as `optional-missing`; when installed, they must satisfy their ranges. It then runs declared scripts in this order: `typecheck`, `build`, `test`, `pack:check`, stopping at the first failure. Artifact verification requires a successful `build` or `pack:check`; `typecheck`/`test` alone do not pass this gate.
|
|
10
|
+
|
|
11
|
+
If dependency installation or resolution verification fails, stop before running project scripts. Review and retain package manifest and lockfile changes with the source migration.
|
|
10
12
|
|
|
11
13
|
## Runtime
|
|
12
14
|
|
package/src/cli.mjs
CHANGED
|
@@ -45,7 +45,9 @@ Options:
|
|
|
45
45
|
--from <ref> source DSH ref (default: dsh-v0.1.1-rc.2)
|
|
46
46
|
--to <ref> target DSH ref (default: dsh-v0.1.2-alpha.2)
|
|
47
47
|
--safe restrict migrate apply to catalog-confirmed exact rewrites
|
|
48
|
+
--plan-file <path> persist or consume the reviewed migrate apply plan
|
|
48
49
|
--level <level> static, build, or runtime verification
|
|
50
|
+
--install synchronize dependencies before build/runtime verification
|
|
49
51
|
--keep-temp retain a successful runtime verification directory
|
|
50
52
|
-h, --help show help
|
|
51
53
|
-v, --version show version
|
|
@@ -85,7 +87,9 @@ const HELP_ZH = `用法:
|
|
|
85
87
|
--from <ref> 源 DSH ref(默认:dsh-v0.1.1-rc.2)
|
|
86
88
|
--to <ref> 目标 DSH ref(默认:dsh-v0.1.2-alpha.2)
|
|
87
89
|
--safe migrate apply 仅执行 catalog 确认的精确改写
|
|
90
|
+
--plan-file <路径> 保存或读取已审阅的 migrate apply 计划
|
|
88
91
|
--level <级别> static、build 或 runtime
|
|
92
|
+
--install 在 build/runtime 验证前同步依赖
|
|
89
93
|
--keep-temp 成功后仍保留运行时验证目录
|
|
90
94
|
-h, --help 显示帮助
|
|
91
95
|
-v, --version 显示版本
|
|
@@ -143,8 +147,11 @@ function parse(args) {
|
|
|
143
147
|
else if (arg === '--to') options.to = valueAfter(args, index++, arg)
|
|
144
148
|
else if (arg.startsWith('--to=')) options.to = optionValue(arg, '--to')
|
|
145
149
|
else if (arg === '--safe') options.safe = true
|
|
150
|
+
else if (arg === '--plan-file') options.planFile = valueAfter(args, index++, arg)
|
|
151
|
+
else if (arg.startsWith('--plan-file=')) options.planFile = optionValue(arg, '--plan-file')
|
|
146
152
|
else if (arg === '--level') options.level = valueAfter(args, index++, arg)
|
|
147
153
|
else if (arg.startsWith('--level=')) options.level = optionValue(arg, '--level')
|
|
154
|
+
else if (arg === '--install') options.install = true
|
|
148
155
|
else if (arg === '--keep-temp') options.keepTemp = true
|
|
149
156
|
else if (arg === '--help' || arg === '-h') options.help = true
|
|
150
157
|
else if (arg === '--version' || arg === '-v') options.version = true
|
|
@@ -293,7 +300,7 @@ async function main() {
|
|
|
293
300
|
if (!['analyze', 'apply', 'verify'].includes(options.migrateAction)) throw new Error('migrate needs analyze, apply, or verify')
|
|
294
301
|
if (options.migrateAction === 'apply' && options.safe !== true) throw new Error('migrate apply requires --safe')
|
|
295
302
|
if (options.migrateAction === 'apply' && options.pluginRoot === undefined) throw new Error('migrate apply requires an explicit plugin root')
|
|
296
|
-
const migrationOptions = { from: options.from, to: options.to, harnessRoot: options.harnessRoot, dshCommand: options.dshCommand, level: options.level, yes: options.yes, safe: options.safe, keepTemp: options.keepTemp }
|
|
303
|
+
const migrationOptions = { from: options.from, to: options.to, harnessRoot: options.harnessRoot, dshCommand: options.dshCommand, level: options.level, yes: options.yes, safe: options.safe, keepTemp: options.keepTemp, planFile: options.planFile, install: options.install }
|
|
297
304
|
if (options.migrateAction === 'verify') {
|
|
298
305
|
const { formatVerification, verifyMigration } = await import('./migrate-verify.mjs')
|
|
299
306
|
const result = verifyMigration(options.pluginRoot, migrationOptions)
|
package/src/migrate-verify.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
|
-
import { basename, join, resolve } from 'node:path'
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
4
4
|
import crossSpawn from 'cross-spawn'
|
|
5
|
+
import semver from 'semver'
|
|
5
6
|
import { analyzeMigration, publicMigrationReport } from './migrate.mjs'
|
|
6
7
|
import { sha256 } from './safe-write.mjs'
|
|
7
8
|
|
|
@@ -38,9 +39,55 @@ function commandEvidence(result) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function packageManager(root) {
|
|
41
|
-
if (existsSync(join(root, 'pnpm-lock.yaml'))) return { command: 'pnpm', run: script => ['run', script], pack: destination => ['pack', '--pack-destination', destination] }
|
|
42
|
-
if (existsSync(join(root, 'yarn.lock'))) return { command: 'yarn', run: script => [script], pack: destination => ['pack', '--out', join(destination, 'plugin.tgz')] }
|
|
43
|
-
return { command: 'npm', run: script => ['run', script], pack: destination => ['pack', '--json', '--pack-destination', destination] }
|
|
42
|
+
if (existsSync(join(root, 'pnpm-lock.yaml'))) return { command: 'pnpm', install: ['install', '--no-frozen-lockfile', '--ignore-scripts'], lockfile: 'pnpm-lock.yaml', run: script => ['run', script], pack: destination => ['pack', '--pack-destination', destination] }
|
|
43
|
+
if (existsSync(join(root, 'yarn.lock'))) return { command: 'yarn', install: ['install', '--ignore-scripts'], lockfile: 'yarn.lock', run: script => [script], pack: destination => ['pack', '--out', join(destination, 'plugin.tgz')] }
|
|
44
|
+
return { command: 'npm', install: ['install', '--ignore-scripts'], lockfile: 'package-lock.json', run: script => ['run', script], pack: destination => ['pack', '--json', '--pack-destination', destination] }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function lockfileHash(root, manager) {
|
|
48
|
+
const file = join(root, manager.lockfile)
|
|
49
|
+
return existsSync(file) ? sha256(readFileSync(file)) : undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function installedManifestFile(root, name) {
|
|
53
|
+
let current = resolve(root)
|
|
54
|
+
while (true) {
|
|
55
|
+
const file = join(current, 'node_modules', ...name.split('/'), 'package.json')
|
|
56
|
+
if (existsSync(file)) return file
|
|
57
|
+
const parent = dirname(current)
|
|
58
|
+
if (parent === current) return undefined
|
|
59
|
+
current = parent
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function installedDependencyEvidence(root, manifest) {
|
|
64
|
+
const requested = new Map()
|
|
65
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
66
|
+
for (const [name, range] of Object.entries(manifest[field] ?? {})) {
|
|
67
|
+
if (name !== '@deepseek-ai/cordis' && !name.startsWith('@deepseek-ai/dsh-')) continue
|
|
68
|
+
const optional = field === 'optionalDependencies' || (field === 'peerDependencies' && manifest.peerDependenciesMeta?.[name]?.optional === true)
|
|
69
|
+
const declarations = requested.get(name) ?? []
|
|
70
|
+
declarations.push({ field, range, optional })
|
|
71
|
+
requested.set(name, declarations)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...requested].map(([name, declarations]) => {
|
|
75
|
+
const file = installedManifestFile(root, name)
|
|
76
|
+
if (file === undefined) {
|
|
77
|
+
const optional = declarations.every(item => item.optional)
|
|
78
|
+
return { name, declarations, installed: false, optional, passed: optional, ...(optional ? { status: 'optional-missing' } : { error: 'declared dependency is not installed' }) }
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const installed = JSON.parse(readFileSync(file, 'utf8'))
|
|
82
|
+
const checks = declarations.map(item => {
|
|
83
|
+
const validRange = typeof item.range === 'string' ? semver.validRange(item.range) : null
|
|
84
|
+
return { ...item, passed: validRange !== null && semver.satisfies(installed.version, validRange) }
|
|
85
|
+
})
|
|
86
|
+
return { name, declarations: checks, installed: true, manifestFile: file, installedVersion: installed.version, passed: installed.name === name && checks.every(item => item.passed) }
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return { name, declarations, installed: true, manifestFile: file, passed: false, error: error instanceof Error ? error.message : String(error) }
|
|
89
|
+
}
|
|
90
|
+
})
|
|
44
91
|
}
|
|
45
92
|
|
|
46
93
|
function discoverDsh(options) {
|
|
@@ -98,6 +145,7 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
98
145
|
const level = options.level ?? 'static'
|
|
99
146
|
if (!['static', 'build', 'runtime'].includes(level)) throw new Error(`unsupported verification level ${level}`)
|
|
100
147
|
if (level !== 'static' && options.yes !== true) throw new Error(`migrate verify --level ${level} executes project commands and requires --yes`)
|
|
148
|
+
if (level !== 'static' && options.install !== true) throw new Error(`migrate verify --level ${level} requires --install to synchronize and verify target dependencies`)
|
|
101
149
|
const analysis = analyzeMigration(pluginRoot, options)
|
|
102
150
|
const result = {
|
|
103
151
|
schemaVersion: 1,
|
|
@@ -111,10 +159,29 @@ export function verifyMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
111
159
|
manualBehaviorVerificationRequired: true,
|
|
112
160
|
}
|
|
113
161
|
if (level === 'static') return result
|
|
162
|
+
if (!result.passed) return result
|
|
114
163
|
|
|
115
164
|
const root = analysis.plugin.root
|
|
116
165
|
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
117
166
|
const manager = packageManager(root)
|
|
167
|
+
const lockfileBefore = lockfileHash(root, manager)
|
|
168
|
+
const installRun = commandResult(manager.command, manager.install, { cwd: root })
|
|
169
|
+
const dependencies = installedDependencyEvidence(root, manifest)
|
|
170
|
+
const dependenciesPassed = installRun.passed && dependencies.every(item => item.passed)
|
|
171
|
+
result.stages.push({
|
|
172
|
+
name: 'dependencies',
|
|
173
|
+
passed: dependenciesPassed,
|
|
174
|
+
packageManager: manager.command,
|
|
175
|
+
command: commandEvidence(installRun),
|
|
176
|
+
lockfile: {
|
|
177
|
+
file: manager.lockfile,
|
|
178
|
+
beforeHash: lockfileBefore,
|
|
179
|
+
afterHash: lockfileHash(root, manager),
|
|
180
|
+
},
|
|
181
|
+
resolved: dependencies,
|
|
182
|
+
})
|
|
183
|
+
result.passed = dependenciesPassed
|
|
184
|
+
if (!result.passed) return result
|
|
118
185
|
const scripts = ['typecheck', 'build', 'test', 'pack:check'].filter(name => typeof manifest.scripts?.[name] === 'string')
|
|
119
186
|
const commandRuns = []
|
|
120
187
|
for (const script of scripts) {
|
package/src/migrate.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
-
import { basename, join, relative, resolve } from 'node:path'
|
|
2
|
+
import { basename, isAbsolute, join, relative, resolve } from 'node:path'
|
|
3
3
|
import semver from 'semver'
|
|
4
4
|
import ts from 'typescript'
|
|
5
5
|
import { parseDocument } from 'yaml'
|
|
6
|
-
import { atomicWrite, sha256 } from './safe-write.mjs'
|
|
6
|
+
import { atomicWrite, sha256, sha256File, writeNewFile } from './safe-write.mjs'
|
|
7
7
|
import { loadMigration, verifyHarnessCheckout } from './migration-catalog.mjs'
|
|
8
8
|
|
|
9
9
|
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts'])
|
|
10
10
|
const SOURCE_DIRS_TO_SKIP = new Set(['.git', 'node_modules', 'coverage'])
|
|
11
|
-
const ARTIFACT_DIRS = ['lib', 'dist', 'build']
|
|
11
|
+
const ARTIFACT_DIRS = ['lib', 'dist', 'build', 'out']
|
|
12
12
|
const DEPENDENCY_FIELDS = ['dependencies', 'peerDependencies', 'devDependencies', 'optionalDependencies']
|
|
13
13
|
const LOCKFILES = new Set(['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock'])
|
|
14
14
|
const NON_RUNTIME_TEXT_EXTENSIONS = new Set(['.md', '.mdx', '.txt', '.snap'])
|
|
@@ -132,13 +132,25 @@ function analyzeFile(file, pluginRoot, catalog, origin) {
|
|
|
132
132
|
remaining.push(spec)
|
|
133
133
|
const semantic = rule?.confidence === 'semantic'
|
|
134
134
|
findings.push({ code: semantic ? 'MIG_SEMANTIC_API_CHANGE' : 'MIG_REMOVED_PACKAGE_REFERENCE', severity: 'error', message: semantic ? `${spec.imported} requires a semantic migration: ${rule.reason}` : `${moduleName} was removed without a safe automatic replacement`, location: where, evidence: { module: moduleName, ...(spec.imported ? { symbol: spec.imported } : {}) }, autoFix: 'none' })
|
|
135
|
-
unresolved.push({
|
|
135
|
+
unresolved.push({
|
|
136
|
+
file: where.file,
|
|
137
|
+
package: rootName,
|
|
138
|
+
symbol: spec.imported,
|
|
139
|
+
...(rule?.toModule ? { targetModule: rule.toModule } : {}),
|
|
140
|
+
...(rule?.toSymbol ? { targetSymbol: rule.toSymbol } : {}),
|
|
141
|
+
reason: rule?.reason ?? 'No exact replacement is known.',
|
|
142
|
+
})
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
const declarationText = ts.isImportDeclaration(node) ? text.slice(node.getStart(source), node.getEnd()) : ''
|
|
139
146
|
const simpleNamedImport = exact.length > 0 && ts.isImportDeclaration(node) && node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings) && node.importClause.name === undefined && node.attributes === undefined && node.assertClause === undefined && !/\/(?:\/|\*)/.test(declarationText)
|
|
140
147
|
if (simpleNamedImport) {
|
|
141
|
-
for (const item of exact) exactTargets.set(`${rootName}\0${item.toModule}`, {
|
|
148
|
+
for (const item of exact) exactTargets.set(`${rootName}\0${item.toModule}`, {
|
|
149
|
+
fromPackage: rootName,
|
|
150
|
+
toPackage: packageRoot(item.toModule),
|
|
151
|
+
relationship: 'client',
|
|
152
|
+
typeOnly: item.typeOnly,
|
|
153
|
+
})
|
|
142
154
|
const groups = new Map()
|
|
143
155
|
for (const item of exact) {
|
|
144
156
|
const list = groups.get(item.toModule) ?? []
|
|
@@ -203,6 +215,12 @@ function manifestFindings(manifest, catalog) {
|
|
|
203
215
|
}
|
|
204
216
|
}
|
|
205
217
|
const client = manifest.value.dsh?.client
|
|
218
|
+
if (client !== undefined && (client === null || Array.isArray(client) || typeof client !== 'object')) {
|
|
219
|
+
findings.push({ code: 'MIG_INVALID_MANIFEST', severity: 'error', message: 'dsh.client must be an object', location: { file: 'package.json' }, evidence: { field: 'dsh.client' }, autoFix: 'none' })
|
|
220
|
+
return findings
|
|
221
|
+
}
|
|
222
|
+
if (client !== undefined && typeof client.platform !== 'string') findings.push({ code: 'MIG_INVALID_MANIFEST', severity: 'error', message: 'dsh.client.platform must be a string', location: { file: 'package.json' }, evidence: { field: 'dsh.client.platform' }, autoFix: 'none' })
|
|
223
|
+
if (client?.immediately !== undefined && typeof client.immediately !== 'boolean') findings.push({ code: 'MIG_INVALID_MANIFEST', severity: 'error', message: 'dsh.client.immediately must be a boolean', location: { file: 'package.json' }, evidence: { field: 'dsh.client.immediately' }, autoFix: 'none' })
|
|
206
224
|
for (const field of ['inject', 'external']) {
|
|
207
225
|
const values = client?.[field]
|
|
208
226
|
if (values !== undefined && !Array.isArray(values)) {
|
|
@@ -302,10 +320,6 @@ function planManifest(manifest, sourceResults, artifactResults, opaqueReferences
|
|
|
302
320
|
for (const removedPackage of presentRemoved) {
|
|
303
321
|
const stillUsed = sourceReferences.has(removedPackage) || clientGraph.includes(removedPackage) || opaqueReferences.has(removedPackage) || (sourceResults.length === 0 && artifactText.includes(removedPackage))
|
|
304
322
|
if (!stillUsed) delete deps[removedPackage]
|
|
305
|
-
for (const move of exactTargets.filter(item => item.fromPackage === removedPackage)) {
|
|
306
|
-
const version = targetVersion(move.toPackage, catalog)
|
|
307
|
-
if (version !== undefined && deps[move.toPackage] === undefined) deps[move.toPackage] = version
|
|
308
|
-
}
|
|
309
323
|
}
|
|
310
324
|
if (field === 'devDependencies') {
|
|
311
325
|
for (const [name, range] of Object.entries(deps)) {
|
|
@@ -314,6 +328,16 @@ function planManifest(manifest, sourceResults, artifactResults, opaqueReferences
|
|
|
314
328
|
}
|
|
315
329
|
}
|
|
316
330
|
}
|
|
331
|
+
for (const move of new Map(exactTargets.map(item => [`${item.relationship}\0${item.toPackage}`, item])).values()) {
|
|
332
|
+
const fields = catalog.packages.dependencyPolicies?.[move.toPackage]?.[move.relationship]
|
|
333
|
+
const version = targetVersion(move.toPackage, catalog)
|
|
334
|
+
if (!Array.isArray(fields) || version === undefined) continue
|
|
335
|
+
for (const field of fields) {
|
|
336
|
+
if (!DEPENDENCY_FIELDS.includes(field)) throw new Error(`migration catalog has an invalid dependency field ${field}`)
|
|
337
|
+
const deps = next[field] ??= {}
|
|
338
|
+
if (deps[move.toPackage] === undefined) deps[move.toPackage] = version
|
|
339
|
+
}
|
|
340
|
+
}
|
|
317
341
|
const changes = []
|
|
318
342
|
for (const field of DEPENDENCY_FIELDS) {
|
|
319
343
|
const before = manifest.value[field] ?? {}
|
|
@@ -336,6 +360,12 @@ function summarize(findings, safeEdits, unresolved) {
|
|
|
336
360
|
}
|
|
337
361
|
}
|
|
338
362
|
|
|
363
|
+
function analysisInputs(root) {
|
|
364
|
+
return walkAll(root, SOURCE_DIRS_TO_SKIP)
|
|
365
|
+
.map(file => ({ file: relativePath(root, file), hash: sha256File(file) }))
|
|
366
|
+
.sort((left, right) => left.file.localeCompare(right.file))
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
|
|
340
370
|
const root = resolve(pluginRoot)
|
|
341
371
|
if (!statSync(root).isDirectory()) throw new Error(`plugin root is not a directory: ${root}`)
|
|
@@ -363,28 +393,84 @@ export function analyzeMigration(pluginRoot = process.cwd(), options = {}) {
|
|
|
363
393
|
return {
|
|
364
394
|
schemaVersion: 1,
|
|
365
395
|
command: 'migrate analyze',
|
|
366
|
-
migration: { id: catalog.manifest.id, from: catalog.manifest.from, to: catalog.manifest.to, harness },
|
|
396
|
+
migration: { id: catalog.manifest.id, from: catalog.manifest.from, to: catalog.manifest.to, references: catalog.manifest.references ?? {}, harness },
|
|
367
397
|
plugin: { root, name: manifest.value.name ?? basename(root), version: manifest.value.version ?? 'unknown', manifestFile: manifest.file, packageManager: existsSync(join(root, 'pnpm-lock.yaml')) ? 'pnpm' : existsSync(join(root, 'yarn.lock')) ? 'yarn' : 'npm' },
|
|
368
398
|
summary: summarize(findings, safeEdits, unresolved),
|
|
369
399
|
findings,
|
|
370
400
|
safeEdits,
|
|
371
401
|
semanticTasks: unresolved,
|
|
402
|
+
sourceInvestigation: {
|
|
403
|
+
required: harness.exact !== true || unresolved.length > 0,
|
|
404
|
+
targetRef: catalog.manifest.to.ref,
|
|
405
|
+
catalogReferences: catalog.manifest.references ?? {},
|
|
406
|
+
semanticTargets: [...new Set(unresolved.map(item => item.targetModule).filter(Boolean))],
|
|
407
|
+
},
|
|
372
408
|
verification: { status: 'analyzed', level: 'static-analysis', passed: findings.every(item => item.severity !== 'error') },
|
|
409
|
+
_inputs: analysisInputs(root),
|
|
373
410
|
_plan: changed.map(item => ({ file: item.file, snapshot: { file: item.file, exists: true, hash: sha256(item.text) }, nextText: item.nextText })),
|
|
374
411
|
}
|
|
375
412
|
}
|
|
376
413
|
|
|
377
414
|
export function publicMigrationReport(report) {
|
|
378
|
-
const { _plan, ...value } = report
|
|
415
|
+
const { _inputs, _plan, ...value } = report
|
|
379
416
|
const harness = { ...value.migration.harness }
|
|
380
417
|
delete harness.fromEntryIds
|
|
381
418
|
delete harness.toEntryIds
|
|
382
419
|
return { ...value, migration: { ...value.migration, harness } }
|
|
383
420
|
}
|
|
384
421
|
|
|
422
|
+
export function createMigrationPlan(report) {
|
|
423
|
+
const publicReport = publicMigrationReport(report)
|
|
424
|
+
const payload = {
|
|
425
|
+
schemaVersion: 1,
|
|
426
|
+
command: 'migrate apply plan',
|
|
427
|
+
plugin: { root: report.plugin.root, manifestFile: report.plugin.manifestFile },
|
|
428
|
+
migration: {
|
|
429
|
+
id: report.migration.id,
|
|
430
|
+
from: report.migration.from.ref,
|
|
431
|
+
to: report.migration.to.ref,
|
|
432
|
+
harness: {
|
|
433
|
+
status: report.migration.harness.status,
|
|
434
|
+
fromCommit: report.migration.harness.fromCommit,
|
|
435
|
+
toCommit: report.migration.harness.toCommit,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
reportHash: sha256(JSON.stringify(publicReport)),
|
|
439
|
+
inputs: report._inputs,
|
|
440
|
+
edits: report.safeEdits,
|
|
441
|
+
}
|
|
442
|
+
return { ...payload, planId: sha256(JSON.stringify(payload)) }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function readMigrationPlan(file) {
|
|
446
|
+
const value = JSON.parse(readFileSync(resolve(file), 'utf8'))
|
|
447
|
+
if (value?.schemaVersion !== 1 || value.command !== 'migrate apply plan' || typeof value.planId !== 'string') throw new Error('invalid migration plan file')
|
|
448
|
+
const { planId, ...payload } = value
|
|
449
|
+
if (sha256(JSON.stringify(payload)) !== planId) throw new Error('migration plan file digest does not match its contents')
|
|
450
|
+
return value
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function assertMigrationPlan(report, plan) {
|
|
454
|
+
const current = createMigrationPlan(report)
|
|
455
|
+
if (plan.plugin.root !== current.plugin.root || plan.plugin.manifestFile !== current.plugin.manifestFile) throw new Error('migration plan targets a different plugin')
|
|
456
|
+
if (plan.migration.id !== current.migration.id || plan.migration.from !== current.migration.from || plan.migration.to !== current.migration.to) throw new Error('migration plan targets a different catalog')
|
|
457
|
+
if (plan.reportHash !== current.reportHash || JSON.stringify(plan.inputs) !== JSON.stringify(current.inputs) || JSON.stringify(plan.edits) !== JSON.stringify(current.edits)) throw new Error('plugin analysis changed after the preview; create and review a new migration plan')
|
|
458
|
+
return current
|
|
459
|
+
}
|
|
460
|
+
|
|
385
461
|
export function applyMigration(report, options = {}) {
|
|
386
462
|
if (options.safe !== true) throw new Error('migrate apply requires --safe')
|
|
387
|
-
if (options.
|
|
463
|
+
if (options.planFile === undefined) throw new Error('migrate apply requires --plan-file so the confirmed preview can be verified')
|
|
464
|
+
const planFile = resolve(options.planFile)
|
|
465
|
+
const planRelative = relative(report.plugin.root, planFile)
|
|
466
|
+
if (planRelative === '' || (planRelative !== '..' && !planRelative.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) && !isAbsolute(planRelative))) throw new Error('migration plan file must be outside the plugin root')
|
|
467
|
+
if (options.yes !== true) {
|
|
468
|
+
const plan = createMigrationPlan(report)
|
|
469
|
+
writeNewFile(planFile, `${JSON.stringify(plan, null, 2)}\n`)
|
|
470
|
+
return { mode: 'preview', plan: { file: planFile, id: plan.planId }, ...publicMigrationReport(report) }
|
|
471
|
+
}
|
|
472
|
+
const plan = readMigrationPlan(planFile)
|
|
473
|
+
const verifiedPlan = assertMigrationPlan(report, plan)
|
|
388
474
|
// Exact edits remain safe when semantic work remains; unresolved references keep the removed dependency.
|
|
389
475
|
for (const item of report._plan) {
|
|
390
476
|
const currentExists = existsSync(item.snapshot.file)
|
|
@@ -393,7 +479,7 @@ export function applyMigration(report, options = {}) {
|
|
|
393
479
|
}
|
|
394
480
|
const writes = report._plan.map(item => ({ ...atomicWrite(item.snapshot, item.nextText), beforeHash: item.snapshot.hash, afterHash: sha256(item.nextText) }))
|
|
395
481
|
const verification = analyzeMigration(report.plugin.root, { from: report.migration.from.ref, to: report.migration.to.ref, harnessRoot: report.migration.harness.root })
|
|
396
|
-
return { mode: 'applied', writes, report: publicMigrationReport(verification) }
|
|
482
|
+
return { mode: 'applied', plan: { file: planFile, id: verifiedPlan.planId }, writes, report: publicMigrationReport(verification) }
|
|
397
483
|
}
|
|
398
484
|
|
|
399
485
|
export function formatMigrationReport(report, language = 'en') {
|
package/src/safe-write.mjs
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto'
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
|
|
5
5
|
export function sha256(text) {
|
|
6
6
|
return createHash('sha256').update(text).digest('hex')
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export function sha256File(file) {
|
|
10
|
+
const hash = createHash('sha256')
|
|
11
|
+
const descriptor = openSync(file, 'r')
|
|
12
|
+
const buffer = Buffer.allocUnsafe(64 * 1024)
|
|
13
|
+
try {
|
|
14
|
+
let bytesRead
|
|
15
|
+
do {
|
|
16
|
+
bytesRead = readSync(descriptor, buffer, 0, buffer.length, null)
|
|
17
|
+
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead))
|
|
18
|
+
} while (bytesRead > 0)
|
|
19
|
+
} finally {
|
|
20
|
+
closeSync(descriptor)
|
|
21
|
+
}
|
|
22
|
+
return hash.digest('hex')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function writeNewFile(file, text) {
|
|
26
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
27
|
+
try {
|
|
28
|
+
writeFileSync(file, text, { flag: 'wx', mode: 0o600 })
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error?.code === 'EEXIST') throw new Error(`${file} already exists; choose a new migration plan path`)
|
|
31
|
+
throw error
|
|
32
|
+
}
|
|
33
|
+
return { file }
|
|
34
|
+
}
|
|
35
|
+
|
|
9
36
|
export function snapshotFile(file) {
|
|
10
37
|
const exists = existsSync(file)
|
|
11
38
|
const text = exists ? readFileSync(file, 'utf8') : ''
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"skill_name": "dsh-plugin-upgrade",
|
|
3
|
-
"evals": [
|
|
4
|
-
{
|
|
5
|
-
"id": 1,
|
|
6
|
-
"prompt": "检查这个只有后端 bundle 的 DSH 插件能否从 DSH 0.1.1 升级到 0.1.2。它的 peerDependencies 仍是 ^0.1.1,不要改文件。",
|
|
7
|
-
"expected_output": "Records the plugin's actual version ranges, uses the current exact-ref catalog as the analysis baseline, identifies target range mismatch, and does not claim runtime compatibility.",
|
|
8
|
-
"expectations": [
|
|
9
|
-
"Uses migrate analyze before proposing edits",
|
|
10
|
-
"Distinguishes the requested 0.1.1 to 0.1.2 upgrade from the catalog's exact reference refs",
|
|
11
|
-
"Reports the exact verification status as analyzed",
|
|
12
|
-
"Does not write files or execute build scripts"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": 2,
|
|
17
|
-
"prompt": "把这个前端插件升级到 DSH 0.1.2。源码里有 import type { ClientContext, ISessions } from '@deepseek-ai/dsh-client-runtime/client',可以安全改的帮我改,其他的列出来。",
|
|
18
|
-
"expected_output": "Safely moves ClientContext to Cordis while leaving ISessions as an explicit semantic migration task, with backup evidence.",
|
|
19
|
-
"expectations": [
|
|
20
|
-
"Detects type-only imports from source rather than relying on the bundle",
|
|
21
|
-
"Uses migrate apply --safe and requires explicit confirmation for writes",
|
|
22
|
-
"Does not mechanically rewrite the semantic ISessions contract"
|
|
23
|
-
]
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": 3,
|
|
27
|
-
"prompt": "完整升级这个混合前后端插件并验证,Harness 在 /workspace/deepseek-harness。构建失败也要把现场路径告诉我。",
|
|
28
|
-
"expected_output": "Performs analyze, safe apply, semantic work, build and isolated runtime gates; reports the highest proven status and retained failure workspace.",
|
|
29
|
-
"expectations": [
|
|
30
|
-
"Uses the exact Harness refs and verifies the catalog commits",
|
|
31
|
-
"Runs static, build, then runtime verification in order",
|
|
32
|
-
"Uses a temporary DSH_HOME and reports retained failure state",
|
|
33
|
-
"Keeps business behavior verification separate from activation smoke"
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"id": 4,
|
|
38
|
-
"prompt": "帮我升级这个插件,但机器上的 dsh-doctor 是旧版,而且我没有授权修改全局 npm 包。请先检查有没有新版本,再继续迁移。",
|
|
39
|
-
"expected_output": "Checks the registry read-only, selects one exact-version npx CLI with the required catalog, avoids global installation, and reports the selected version and update status.",
|
|
40
|
-
"expectations": [
|
|
41
|
-
"Checks the local CLI version and exact migration catalog before use",
|
|
42
|
-
"Uses npm view as a read-only registry update check",
|
|
43
|
-
"Pins one resolved npx package version for all migration phases",
|
|
44
|
-
"Does not install or update a global CLI without explicit authorization",
|
|
45
|
-
"Reports local, registry, selected CLI, catalog, and update-status evidence"
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
}
|